Compare commits

..
Author SHA1 Message Date
ChrisM-PekandCursor 7387096d98 chore(sanctuary): aligner la verif alpha.223 sur le tri des conteneurs
Verification 26.2 / check (pull_request) Canceled after 0s
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 05:54:43 +02:00
ChrisM-PekandCursor 457f91b92e fix(sanctuary): trier coffres et barrels et garder la hotbar en bas
Le clic molette range aussi les conteneurs de stockage, et la hotbar reste sur la derniere ligne a la connexion.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 05:52:17 +02:00
koka 6ee5cdc2fb Merge pull request 'release: tester les dernières PR dans Packwiz alpha.223' (#117) from release/26.2.0-alpha.223 into main
Verification 26.2 / check (push) Canceled after 0s
Reviewed-on: #117
2026-08-31 03:15:43 +00:00
koka 87428c5f0d release: test latest PRs in Packwiz alpha.223
Verification 26.2 / check (pull_request) Canceled after 0s
2026-08-31 05:13:46 +02:00
koka 0599bf6885 Merge pull request 'feat(sanctuary): ajouter les modes du suivi de quêtes' (#107) from feature/quest-tracker-display-modes into main
Verification 26.2 / check (push) Canceled after 0s
Reviewed-on: #107
2026-08-31 03:02:03 +00:00
koka 624f441578 Merge pull request 'feat(sanctuary): trier le stockage sans deplacer la hotbar' (#112) from feature/inventory-sorter into main
Verification 26.2 / check (push) Canceled after 0s
Reviewed-on: #112
2026-08-31 03:01:13 +00:00
koka 53fa32bbc5 feat(sanctuary): ajouter les modes du suivi de quetes
Verification 26.2 / check (pull_request) Canceled after 0s
2026-08-31 03:27:36 +02:00
34 changed files with 863 additions and 265 deletions
+13
View File
@@ -6,6 +6,19 @@ 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.223`
Module modifié : **Sanctuary `0.0.0-alpha.125`**. Les neuf autres modules conservent leur version issue de l'alpha.222.
- ajoute un tri natif de linventaire par clic molette configurable, uniquement sur les rangées de stockage débloquées ;
- fusionne et range les piles depuis le haut sans déplacer la hotbar, larmure, la main secondaire, le craft ni les emplacements spéciaux ;
- garde le tri sous autorité serveur, avec un délai de huit ticks, et lignore en créatif ou pendant la saisie dans un champ texte ;
- ajoute un style configurable au suivi des quêtes (`N` par défaut), entre le texte transparent et le panneau original de Chris ;
- place automatiquement le style texte sous la hauteur réelle de la colonne gauche du F3 permanent afin que les deux affichages ne se recouvrent pas ;
- nomme et ordonne les contrats suivis par niveau de récompense, rubis puis saphir puis émeraude/autres, au lieu du libellé générique « Contrat du jour » ;
- actualise JEI vers la bêta Fabric 26.2 `30.29.0.198` et synchronise ses empreintes entre Packwiz et Prism ;
- conserve les données Sanctuary v11, le protocole réseau 22, les IDs et les sauvegardes existantes ; aucun nouveau monde ni réinitialisation nest nécessaire.
## `26.2.0-alpha.222`
Modules modifiés : **Red-Stoner `0.0.0-alpha.13`** et **Sanctuary `0.0.0-alpha.124`**. Les huit autres modules conservent leur version issue de l'alpha.221.
+3 -1
View File
@@ -22,10 +22,12 @@ Licence : **GNU GPL 3.0 or Later** (`GPL-3.0-or-later`)
Les descriptions FR/EN/RU sont déclarées dans chaque `fabric.mod.json` et dans les fichiers de langue. Les images fournies servent d'icônes Fabric de leur module.
Version courante du pack distribué : **`26.2.0-alpha.222`**. Cette release distribue Red-Stoner **`0.0.0-alpha.13`** et Sanctuary **`0.0.0-alpha.124`** ; les huit autres modules conservent leur version issue de l'alpha.221.
Version courante du pack distribué : **`26.2.0-alpha.223`**. Cette release distribue Sanctuary **`0.0.0-alpha.125`** ; les neuf autres modules conservent leur version issue de l'alpha.222.
La migration [CANA-01 à CANA-09](CANAPLIA.md) porte Canaplia vers Minecraft 26.2. It's Alive **`0.0.0-alpha.35`** enregistre les seize familles, les découvertes, hybridations, résines, effets serveur et le Livre interdit personnel, partageable temporairement sur un pupitre. Ambiance **`0.0.0-alpha.14`** compose les profils visuels bornés avec ses réglages d'accessibilité, et Only Fun **`0.0.0-alpha.21`** exclut explicitement Canaplia et les raccourcis de progression du Shop.
Lalpha.223 ajoute le tri natif des rangées de stockage par clic molette sans déplacer la hotbar ni les emplacements spéciaux. Le suivi des quêtes propose désormais le texte transparent aligné sous la colonne gauche du F3 ou le panneau original de Chris, avec bascule configurable sur `N` et contrats ordonnés par récompense. JEI passe à la bêta Fabric 26.2 `30.29.0.198`.
Lalpha.222 améliore le terminal de stockage avec une recherche Unicode, des quantités compactes et des emplacements qui ne glissent plus lorsqu'une pile est vidée. Elle ajoute aussi le suivi textuel des quêtes actives en haut à gauche, masquable avec une touche configurable, ainsi que des suggestions dIDs ditems et de mobs dans l’éditeur de demandes. JEI passe à la bêta Fabric 26.2 `30.29.0.196`.
Lalpha.221 corrige la cause réelle du disque blanc ralenti et trop grave : Ambiance décode désormais la source AAC en stéréo native à 48 kHz, puis moyenne explicitement chaque paire gauche/droite vers le flux mono positionnel de Minecraft. OpenAL reste à sa hauteur normale `1.0`, sans compensation artificielle ni changement de durée.
+336 -186
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -28,7 +28,7 @@ iliketomoveit_version=0.0.0-alpha.16
iliketomoveit_lifecycle=active
onlyfun_version=0.0.0-alpha.25
onlyfun_lifecycle=active
sanctuary_version=0.0.0-alpha.124
sanctuary_version=0.0.0-alpha.125
sanctuary_lifecycle=active
pack_version=26.2.0-alpha.222
pack_version=26.2.0-alpha.223
maven_group=fr.koka99cab.sanctuary26
@@ -0,0 +1,60 @@
{
"schema_version": 1,
"source": {
"pack_version": "26.2.0-alpha.222",
"modules": {
"sanctuary": "0.0.0-alpha.124"
}
},
"target": {
"pack_version": "26.2.0-alpha.223",
"modules": {
"sanctuary": "0.0.0-alpha.125"
}
},
"inventory_sort": {
"owner": "sanctuary",
"default_binding": "middle_mouse",
"unlocked_storage_rows_only": true,
"packs_from_top": true,
"merges_identical_stacks": true,
"hotbar_armor_offhand_crafting_and_special_slots_unchanged": true,
"server_authoritative": true,
"server_cooldown_ticks": 8,
"ignored_in_creative": true,
"ignored_while_typing": true
},
"quest_tracker_display": {
"owner": "sanctuary",
"default_mode": "transparent_text",
"mode_toggle_default_key": "N",
"original_chris_panel_available": true,
"transparent_text_below_f3_left_column": true,
"contracts_named_and_sorted_by_reward_tier": true,
"reward_order": [
"ruby",
"sapphire",
"emerald_or_other"
]
},
"compatibility": {
"new_world_required": false,
"save_reset_required": false,
"existing_saves_supported": true,
"registry_ids_changed": false,
"sanctuary_data_version": 11,
"sanctuary_network_protocol": 22
},
"distribution": {
"pack_release_updated": true,
"packwiz_index_refreshed": true,
"prism_modrinth_index_updated": true,
"shared_client_server_mrpack": true,
"jei": {
"source": "30.29.0.196",
"target": "30.29.0.198",
"modrinth_version_id": "67Dvn8sL",
"packwiz_and_prism_synchronized": true
}
}
}
+12 -12
View File
@@ -14,15 +14,15 @@ hash = "41f4f4c88c71d0a23446d5014f7407d55ad477441847099cad24492b032335a0"
[[files]]
file = "mods/ambiance-0.0.0-alpha.22.jar"
hash = "a6b7d98971f411b202f5679aae05c1bc93754ee9a42d10dc903413b17ba97a63"
hash = "34aa79ab54ba2506b0ffef062662917de8b29f6f3f9b8d7a920a664d01185641"
[[files]]
file = "mods/anotherworld-0.0.0-alpha.55.jar"
hash = "cac322006ad9a724bb656b400cf47d84a18e76cd1a35f4bd13c112f443d89275"
hash = "2c93c364af7f617a488a8ef5c64d584041eeb24b93e4ea727570c042bd73fe2d"
[[files]]
file = "mods/events-0.0.0-alpha.0.jar"
hash = "8d2566ceb838a486fe9d8539acd127446e51d7085dcbefa86ee3c1aea4630a6d"
hash = "6c94ba89f6daf64d7144e9ea2283a98a609a01a20b7913bd51821034ee8a26fd"
[[files]]
file = "mods/fabric-api.pw.toml"
@@ -31,7 +31,7 @@ metafile = true
[[files]]
file = "mods/iliketomoveit-0.0.0-alpha.16.jar"
hash = "94e5afefab9485c40a47342dc84f401fa126be40064832929601e781b3a03738"
hash = "f0f54b9746977b085abb74fea61e8cd230f3dda905cc337866e7950fb2ee9293"
[[files]]
file = "mods/iris.pw.toml"
@@ -40,32 +40,32 @@ metafile = true
[[files]]
file = "mods/itsalive-0.0.0-alpha.36.jar"
hash = "94899dba383aff41c39f7f25ee1a6b7998aa1e9889e7b9e9039a984e0bc0882f"
hash = "ae2201e91e4ab2eaf09f32ea41c920d460cf3cf426e71bd76be5241b7e81e1a0"
[[files]]
file = "mods/jei.pw.toml"
hash = "115d2239bc86ecdad88705dbc215d27ddb6a08d94f74fa538abb6a67a00bf71e"
hash = "cffb13bae33cd8df605c62e7fa66089428dc684e0ab973f662a8a710c45c306d"
metafile = true
[[files]]
file = "mods/masterkey-0.0.0-alpha.8.jar"
hash = "102c3da5d201452be23ab84338e023c284f4770270ca945b0c63b6a996fb2922"
hash = "60de65566b1434709cdfb4c915d5bc6a7f9019dd48ebeaa04fe474f0ec7fd3b4"
[[files]]
file = "mods/onlyfun-0.0.0-alpha.25.jar"
hash = "2676072de7dbc9b27c4fd0e50e797832356fbc903e80c5bd9a5bd55c1a5aff1b"
hash = "165ffe713b09db64484cceb6c508ac5e53b5d7e69e86ea6658c3e8ea04ee1ccf"
[[files]]
file = "mods/ouch-0.0.0-alpha.10.jar"
hash = "59815a0415c48c92acfc7c18bd2399780bab9b0b5eee69c2b35a66cb1474109b"
hash = "3f66a1b0657c40b653ec9e7a7aa92e4edf72475af7e372a49d9254743696f0a5"
[[files]]
file = "mods/redstoner-0.0.0-alpha.13.jar"
hash = "0bd3888b13c894aed806696a5e73e076c7ed985eff2c242f0df9ec64ff3fcb37"
hash = "226c000f5febd841d69420316af2c14dd4ec0ef1c774c21d5926f7120f44b7e6"
[[files]]
file = "mods/sanctuary-0.0.0-alpha.124.jar"
hash = "39a3db3d1d9876ac5c7a7135a3b524c82508d0ee2e0c5c410c6515ff8931c6fb"
file = "mods/sanctuary-0.0.0-alpha.125.jar"
hash = "f0826d9be66f6498bc0455ea8075bfbaefc97c1f6a7b0d21b225003fe71ad958"
[[files]]
file = "mods/simple-voice-chat.pw.toml"
+4 -4
View File
@@ -1,14 +1,14 @@
name = "Just Enough Items (JEI)"
filename = "jei-26.2-fabric-30.29.0.196.jar"
filename = "jei-26.2-fabric-30.29.0.198.jar"
side = "both"
pin = true
[download]
url = "https://cdn.modrinth.com/data/u6dRKJwZ/versions/vvwsu2BR/jei-26.2-fabric-30.29.0.196.jar"
url = "https://cdn.modrinth.com/data/u6dRKJwZ/versions/67Dvn8sL/jei-26.2-fabric-30.29.0.198.jar"
hash-format = "sha512"
hash = "c62a3e264d4b0ec398b9aa717a4832eecb8663bbcec4483dc58a9999d5e4e55ca4f5bca285a0d4a003220c698c3e87c8a3294366e085e72545e4afe84aec92c7"
hash = "06131c9528549840e07ddc9634e23454cd454380d7749a23d818f0efe0dacc02874e967ceb74a24ee82efb13e0fe2df4026f988a68f54f890586967e91dcaa5b"
[update]
[update.modrinth]
mod-id = "u6dRKJwZ"
version = "vvwsu2BR"
version = "67Dvn8sL"
+2 -2
View File
@@ -1,12 +1,12 @@
name = "Sanctuary 26.2"
author = "KOKA99CAB"
version = "26.2.0-alpha.222"
version = "26.2.0-alpha.223"
pack-format = "packwiz:1.1.0"
[index]
file = "index.toml"
hash-format = "sha256"
hash = "53b5bec1b113ab2b353e6a142e3dbc4617ea7d27003bcbb09b668938dd262ddd"
hash = "9e06da88d562c7a2ed5fbcd56a06779a37715d4f9cf26479ee1a664758ada599"
[versions]
fabric = "0.19.3"
+6 -6
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.222",
"versionId": "26.2.0-alpha.223",
"files": [
{
"path": "mods/fabric-api-0.158.0+26.2.jar",
@@ -21,19 +21,19 @@
"fileSize": 2535765
},
{
"path": "mods/jei-26.2-fabric-30.29.0.196.jar",
"path": "mods/jei-26.2-fabric-30.29.0.198.jar",
"hashes": {
"sha1": "c2f2c6b5822a560fe9872921b908ffc2f3297d00",
"sha512": "c62a3e264d4b0ec398b9aa717a4832eecb8663bbcec4483dc58a9999d5e4e55ca4f5bca285a0d4a003220c698c3e87c8a3294366e085e72545e4afe84aec92c7"
"sha1": "652e964c71a699fdfdd96cda265ea703b13c6ef4",
"sha512": "06131c9528549840e07ddc9634e23454cd454380d7749a23d818f0efe0dacc02874e967ceb74a24ee82efb13e0fe2df4026f988a68f54f890586967e91dcaa5b"
},
"env": {
"client": "required",
"server": "required"
},
"downloads": [
"https://cdn.modrinth.com/data/u6dRKJwZ/versions/vvwsu2BR/jei-26.2-fabric-30.29.0.196.jar"
"https://cdn.modrinth.com/data/u6dRKJwZ/versions/67Dvn8sL/jei-26.2-fabric-30.29.0.198.jar"
],
"fileSize": 1838353
"fileSize": 1839279
},
{
"path": "mods/voicechat-fabric-2.6.22+26.2.jar",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"schema_version": 1,
"pack_version": "26.2.0-alpha.222",
"pack_version": "26.2.0-alpha.223",
"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.222.mrpack",
"primary_file": "sanctuary-26.2.0-alpha.223.mrpack",
"shared_client_server_pack": true,
"update_owner": "packwiz-installer",
"check_on_launch": true,
+28 -11
View File
@@ -76,6 +76,14 @@ tasks.register("questTrackerModelSmoke", JavaExec) {
mainClass = "fr.koka99cab.sanctuary26.sanctuary.shop.QuestTrackerModelSmoke"
}
tasks.register("questTrackerDisplaySmoke", JavaExec) {
group = "verification"
description = "Checks quest-tracker display modes and permanent-F3 layout."
dependsOn tasks.named("testClasses")
classpath = sourceSets.test.runtimeClasspath
mainClass = "fr.koka99cab.sanctuary26.sanctuary.client.QuestTrackerDisplaySmoke"
}
tasks.register("questIdSuggestionsSmoke", JavaExec) {
group = "verification"
description = "Checks accent-folded quest-panel item name ranking and inventory pins."
@@ -159,7 +167,7 @@ tasks.register("verifyBlackMarket") {
def questData = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/QuestBoardData.java").text
def quests = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/QuestBoardService.java").text
def inventoryView = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/api/inventory/SanctuaryPlayerInventoryView.java").text
if (project.version.toString() != "0.0.0-alpha.124"
if (project.version.toString() != "0.0.0-alpha.125"
|| !data.contains('SAVE_KEY = "black_market"') || !data.contains("DATA_VERSION = 2")
|| !data.contains("ItemStack.CODEC.fieldOf(\"item\")") || !data.contains("replaceListing")
|| !service.contains("LISTING_FEE_RUBIES = 1") || !service.contains("COMMISSION_PERCENT = 5")
@@ -631,6 +639,14 @@ tasks.register("verifySanctuary") {
,'matchesMouse'
,'int bankIndex(int visualRow, int visibleRows)'
,'playButtonClickSound'
,'requestVisualRow(client, 0, currentRows)'
,'isSortableStorageMenu'
,'instanceof ChestMenu'
,'instanceof ShulkerBoxMenu'
,'instanceof HopperMenu'
,'instanceof DispenserMenu'
,'buffer.writeBoolean(container)'
,'sortOpenContainer'
,'GLFW.GLFW_KEY_TAB'
,'HOTBAR_TEXTURE'
,'0.0F, 0.0F, 182, 22, 182, 22'
@@ -2867,7 +2883,7 @@ tasks.register("verifyShopCurrencyRowsRelease") {
"src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/BlackMarketService.java").text
def shop = file(
"src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/ShopService.java").text
if (project.version.toString() != "0.0.0-alpha.124"
if (project.version.toString() != "0.0.0-alpha.125"
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.117"
|| migration.shop_currency_inventory?.all_unlocked_rows_counted != true
|| migration.shop_currency_inventory?.overflow_rows_counted != true
@@ -3033,7 +3049,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.124"
if (project.version.toString() != "0.0.0-alpha.125"
|| 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
@@ -3071,7 +3087,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.124"
if (project.version.toString() != "0.0.0-alpha.125"
|| 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]
@@ -3102,7 +3118,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.124"
if (project.version.toString() != "0.0.0-alpha.125"
|| migration.target?.pack_version != "26.2.0-alpha.195"
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.111"
|| migration.compatibility?.sanctuary_data_version != 11
@@ -3133,7 +3149,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.124"
if (project.version.toString() != "0.0.0-alpha.125"
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.112"
|| migration.mailbox?.id != "sanctuary:delivery_box"
|| migration.mailbox?.horizontal_facing != true
@@ -3161,7 +3177,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.124"
if (project.version.toString() != "0.0.0-alpha.125"
|| !transfer.contains("shouldAppendOverflow")
|| !transfer.contains("isVanillaStorageRange")
|| !transfer.contains("hotbarStart")
@@ -3207,7 +3223,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.124"
if (project.version.toString() != "0.0.0-alpha.125"
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.113"
|| migration.quest_requests?.save_key != "quest_requests"
|| migration.quest_requests?.data_version != 1
@@ -3265,7 +3281,7 @@ tasks.register("verifyQuestRequestContracts") {
def locales = ["fr_fr", "en_us", "ru_ru"].collectEntries { locale ->
[(locale): new JsonSlurper().parse(file("src/main/resources/assets/sanctuary/lang/${locale}.json"))]
}
if (project.version.toString() != "0.0.0-alpha.124"
if (project.version.toString() != "0.0.0-alpha.125"
|| 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"
@@ -3334,7 +3350,7 @@ tasks.register("verifyQuestRequestEscrow") {
def locales = ["fr_fr", "en_us", "ru_ru"].collectEntries { locale ->
[(locale): new JsonSlurper().parse(file("src/main/resources/assets/sanctuary/lang/${locale}.json"))]
}
if (project.version.toString() != "0.0.0-alpha.124"
if (project.version.toString() != "0.0.0-alpha.125"
|| migration.source?.pack_version != "26.2.0-alpha.200"
|| migration.source?.modules?.sanctuary != "0.0.0-alpha.115"
|| migration.target?.pack_version != "26.2.0-alpha.201"
@@ -3406,7 +3422,7 @@ tasks.register("verifyAlphaIndev") {
def locales = ["fr_fr", "en_us", "ru_ru"].collect {
new JsonSlurper().parse(file("src/main/resources/assets/sanctuary/lang/${it}.json"))
}
if (project.version.toString() != "0.0.0-alpha.124"
if (project.version.toString() != "0.0.0-alpha.125"
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.122"
|| migration.alpha_world?.dimension != "sanctuary:alpha"
|| migration.alpha_world?.legacy_dimension != "portals:alpha"
@@ -3438,6 +3454,7 @@ tasks.named("check") {
dependsOn tasks.named("celestialGraphSmoke")
dependsOn tasks.named("questRequestModelSmoke")
dependsOn tasks.named("questTrackerModelSmoke")
dependsOn tasks.named("questTrackerDisplaySmoke")
dependsOn tasks.named("questIdSuggestionsSmoke")
dependsOn tasks.named("alphaIndevGeneratorSmoke")
dependsOn tasks.named("capeJoinSyncSmoke")
@@ -0,0 +1,19 @@
package fr.koka99cab.sanctuary26.sanctuary.client;
/** Tracks the exact height occupied by the left-hand F3 column. */
public final class QuestTrackerDebugLayout {
private static final int TOP_MARGIN = 2;
private static final int DEBUG_LINE_HEIGHT = 9;
private static final int GAP = 2;
private static int leftLineCount;
private QuestTrackerDebugLayout() {}
public static void captureLeftLines(int count) {
leftLineCount = Math.max(0, count);
}
static int textTop(boolean debugVisible) {
return debugVisible ? TOP_MARGIN + leftLineCount * DEBUG_LINE_HEIGHT + GAP : TOP_MARGIN;
}
}
@@ -0,0 +1,30 @@
package fr.koka99cab.sanctuary26.sanctuary.client;
/** Persisted visual styles offered by the quest tracker. */
enum QuestTrackerDisplayMode {
TEXT("text"),
PANEL("panel");
private final String id;
QuestTrackerDisplayMode(String id) {
this.id = id;
}
String id() {
return id;
}
QuestTrackerDisplayMode next() {
return this == TEXT ? PANEL : TEXT;
}
static QuestTrackerDisplayMode byId(String value) {
if (value != null) {
for (QuestTrackerDisplayMode mode : values()) {
if (mode.id.equalsIgnoreCase(value)) return mode;
}
}
return TEXT;
}
}
@@ -14,11 +14,21 @@ import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;
import net.minecraft.world.item.ItemStack;
/** Transparent, toggleable text overlay for accepted quests. */
/** Toggleable quest overlay with transparent F3-aligned and original panel modes. */
final class QuestTrackerHud {
private static final int MAX_WIDTH = 240;
private static final int MARGIN = 2;
private static final int ENTRY_SPACING = 2;
private static final int TEXT_MAX_WIDTH = 240;
private static final int TEXT_MARGIN = 2;
private static final int TEXT_ENTRY_SPACING = 2;
private static final int PANEL_WIDTH = 168;
private static final int PANEL_MARGIN = 6;
private static final int PANEL_HEADER_HEIGHT = 14;
private static final int PANEL_ENTRY_HEIGHT = 22;
private static final int PANEL_EMPTY_HEIGHT = 22;
private static final int PANEL_BACKGROUND = 0xD0181417;
private static final int PANEL_OUTLINE = 0xFFE8A8C3;
private static final int PANEL_TITLE = 0xFFF2C4D7;
private static final int PANEL_MUTED = 0xFF9F8B95;
private static final int PANEL_BAR_BACK = 0xFF3A2A30;
private static final int BODY = 0xFFE9DDE2;
private QuestTrackerHud() {}
@@ -32,29 +42,82 @@ final class QuestTrackerHud {
if (client.player == null || client.level == null || client.gui.screen() != null) return;
if (!SanctuaryClientQuests.visible()) return;
List<QuestTrackerSnapshotPayload.Entry> entries = SanctuaryClientQuests.entries();
if (SanctuaryClientQuests.displayMode() == QuestTrackerDisplayMode.PANEL) {
renderPanel(graphics, client, entries);
return;
}
renderText(graphics, client, entries);
}
private static void renderText(GuiGraphicsExtractor graphics, Minecraft client,
List<QuestTrackerSnapshotPayload.Entry> entries) {
if (entries.isEmpty()) return;
int width = Math.max(1, Math.min(MAX_WIDTH, graphics.guiWidth() - MARGIN * 2));
int y = MARGIN;
int width = Math.max(1, Math.min(TEXT_MAX_WIDTH, graphics.guiWidth() - TEXT_MARGIN * 2));
int y = QuestTrackerDebugLayout.textTop(client.getDebugOverlay().showDebugScreen());
for (QuestTrackerSnapshotPayload.Entry entry : entries) {
drawEntry(graphics, client.font, entry, MARGIN, y, width);
y += client.font.lineHeight * 2 + ENTRY_SPACING;
drawTextEntry(graphics, client.font, entry, TEXT_MARGIN, y, width);
y += client.font.lineHeight * 2 + TEXT_ENTRY_SPACING;
}
}
private static void drawEntry(GuiGraphicsExtractor graphics, Font font,
private static void drawTextEntry(GuiGraphicsExtractor graphics, Font font,
QuestTrackerSnapshotPayload.Entry entry, int x, int y, int width) {
int accent = 0xFF000000 | (entry.accent() & 0xFFFFFF);
graphics.text(font, ellipsize(font, title(entry), width), x, y, accent, true);
Component progress = Component.translatable("hud.sanctuary.quest_tracker.progress",
action(entry), describeTarget(entry), entry.progress(), entry.required());
Component progress = progress(entry);
graphics.text(font, ellipsize(font, progress, width), x, y + font.lineHeight, BODY, true);
}
private static void renderPanel(GuiGraphicsExtractor graphics, Minecraft client,
List<QuestTrackerSnapshotPayload.Entry> entries) {
int height = PANEL_HEADER_HEIGHT + 6
+ (entries.isEmpty() ? PANEL_EMPTY_HEIGHT : entries.size() * PANEL_ENTRY_HEIGHT);
int right = PANEL_MARGIN + PANEL_WIDTH;
int bottom = PANEL_MARGIN + height;
graphics.fill(PANEL_MARGIN, PANEL_MARGIN, right, bottom, PANEL_BACKGROUND);
graphics.outline(PANEL_MARGIN, PANEL_MARGIN, PANEL_WIDTH, height, PANEL_OUTLINE);
graphics.text(client.font, Component.translatable("hud.sanctuary.quest_tracker.title"),
PANEL_MARGIN + 6, PANEL_MARGIN + 4, PANEL_TITLE);
if (entries.isEmpty()) {
graphics.text(client.font, Component.translatable("hud.sanctuary.quest_tracker.empty"),
PANEL_MARGIN + 6, PANEL_MARGIN + PANEL_HEADER_HEIGHT + 4, PANEL_MUTED);
return;
}
int y = PANEL_MARGIN + PANEL_HEADER_HEIGHT + 2;
for (QuestTrackerSnapshotPayload.Entry entry : entries) {
drawPanelEntry(graphics, client.font, entry, PANEL_MARGIN + 6, y, PANEL_WIDTH - 12);
y += PANEL_ENTRY_HEIGHT;
}
}
private static void drawPanelEntry(GuiGraphicsExtractor graphics, Font font,
QuestTrackerSnapshotPayload.Entry entry, int x, int y, int width) {
int accent = 0xFF000000 | (entry.accent() & 0xFFFFFF);
graphics.text(font, ellipsize(font, title(entry), width), x, y, accent);
graphics.text(font, ellipsize(font, progress(entry), width), x, y + 9, BODY);
int barTop = y + 18;
graphics.fill(x, barTop, x + width, barTop + 2, PANEL_BAR_BACK);
int filled = Math.round(width * Math.min(1.0F, entry.progress() / (float) entry.required()));
if (filled > 0) graphics.fill(x, barTop, x + filled, barTop + 2, accent);
}
private static Component title(QuestTrackerSnapshotPayload.Entry entry) {
if (QuestTrackerSync.SOURCE_BOARD.equals(entry.source())) {
return switch (entry.title()) {
case "ruby", "sapphire", "emerald" -> Component.translatable(
"hud.sanctuary.quest_tracker.reward." + entry.title());
default -> Component.translatable("hud.sanctuary.quest_tracker.source.board");
};
}
if (!entry.title().isBlank()) return Component.literal(entry.title());
return Component.translatable("hud.sanctuary.quest_tracker.source." + entry.source());
}
private static Component progress(QuestTrackerSnapshotPayload.Entry entry) {
return Component.translatable("hud.sanctuary.quest_tracker.progress",
action(entry), describeTarget(entry), entry.progress(), entry.required());
}
private static Component action(QuestTrackerSnapshotPayload.Entry entry) {
if (QuestTrackerSync.SOURCE_BOARD.equals(entry.source())) {
return Component.translatable("sign.sanctuary.quest_board.action." + entry.action());
@@ -52,6 +52,7 @@ public final class SanctuaryClient implements ClientModInitializer {
private static KeyMapping switchHotbarRowKey;
private static KeyMapping sortInventoryKey;
private static KeyMapping questTrackerKey;
private static KeyMapping questTrackerModeKey;
private static boolean zoomWasHeld;
private static boolean smokeScreenOpened;
private static boolean inventorySmokeScreenOpened;
@@ -107,6 +108,12 @@ public final class SanctuaryClient implements ClientModInitializer {
GLFW.GLFW_KEY_J,
SANCTUARY_CATEGORY
));
questTrackerModeKey = KeyMappingHelper.registerKeyMapping(new KeyMapping(
"key.sanctuary.quest_tracker_mode",
InputConstants.Type.KEYSYM,
GLFW.GLFW_KEY_N,
SANCTUARY_CATEGORY
));
SanctuaryAtlasOptions.load();
SanctuaryClientQuests.load();
SanctuaryBulkActionsClient.initialize();
@@ -206,6 +213,10 @@ public final class SanctuaryClient implements ClientModInitializer {
if (client.player != null && client.level != null && client.gui.screen() == null)
toggleQuestTracker(client);
}
while (questTrackerModeKey.consumeClick()) {
if (client.player != null && client.level != null && client.gui.screen() == null)
cycleQuestTrackerMode(client);
}
while (celestialKey.consumeClick()) {
if (client.player != null && client.level != null && client.gui.screen() == null)
client.gui.setScreen(new CelestialSkyScreen(null));
@@ -316,6 +327,13 @@ public final class SanctuaryClient implements ClientModInitializer {
: "hud.sanctuary.quest_tracker.toggle_off"));
}
private static void cycleQuestTrackerMode(Minecraft client) {
QuestTrackerDisplayMode mode = SanctuaryClientQuests.cycleDisplayMode();
if (client.player == null) return;
client.player.sendOverlayMessage(Component.translatable(
"hud.sanctuary.quest_tracker.mode." + mode.id()));
}
private static void toggleAtlas(Minecraft client) {
if (client.gui.screen() instanceof SanctuaryAtlasScreen) {
client.gui.setScreen(null);
@@ -33,7 +33,9 @@ public final class SanctuaryClientProgress {
Minecraft client = Minecraft.getInstance();
if (currentRows > 1) {
SanctuaryHotbarRowsClient.requestVisualRow(client, currentRows - 1, currentRows);
// Visual 0 is the bottom bank line after bankIndex inversion. Keep the
// live hotbar there on login and after an inventory upgrade.
SanctuaryHotbarRowsClient.requestVisualRow(client, 0, currentRows);
}
if (client.gui.screen() instanceof AbstractContainerScreen<?> screen) {
screen.resize(client.getWindow().getGuiScaledWidth(),
@@ -13,7 +13,7 @@ import java.nio.file.Path;
import java.util.List;
import net.fabricmc.loader.api.FabricLoader;
/** Client-side quest tracker snapshot and visibility preference. */
/** Client-side quest tracker snapshot and persisted display preferences. */
final class SanctuaryClientQuests {
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
private static final Path FILE = FabricLoader.getInstance().getConfigDir()
@@ -61,6 +61,19 @@ final class SanctuaryClientQuests {
return settings.visible;
}
static QuestTrackerDisplayMode displayMode() {
load();
return QuestTrackerDisplayMode.byId(settings.mode);
}
static QuestTrackerDisplayMode cycleDisplayMode() {
load();
QuestTrackerDisplayMode mode = displayMode().next();
settings.mode = mode.id();
save();
return mode;
}
private static void save() {
try {
Files.createDirectories(FILE.getParent());
@@ -75,5 +88,6 @@ final class SanctuaryClientQuests {
private static final class Settings {
boolean visible = true;
String mode = QuestTrackerDisplayMode.TEXT.id();
}
}
@@ -44,7 +44,7 @@ public final class SanctuaryHotbarRowsClient {
return requestVisualRow(client, targetVisualRow, unlockedRows);
}
/** Makes the requested visible row the real hotbar, notably after an inventory upgrade. */
/** Makes the requested visible row the real hotbar. Visual 0 is the bottom line. */
public static boolean requestVisualRow(Minecraft client, int targetVisualRow, int unlockedRows) {
unlockedRows = Math.clamp(unlockedRows, 1, SanctuaryInventoryCapacity.MAX_ROW_COUNT);
if (pending || unlockedRows <= 1 || client.player == null || client.getConnection() == null
@@ -1,5 +1,7 @@
package fr.koka99cab.sanctuary26.sanctuary.client;
import fr.koka99cab.sanctuary26.sanctuary.gameplay.SanctuaryInventorySort;
import fr.koka99cab.sanctuary26.sanctuary.mixin.client.AbstractContainerScreenAccessor;
import fr.koka99cab.sanctuary26.sanctuary.network.SortInventoryPayload;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.minecraft.client.Minecraft;
@@ -7,6 +9,8 @@ import net.minecraft.client.gui.components.AbstractWidget;
import net.minecraft.client.gui.components.EditBox;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.client.gui.screens.inventory.CreativeModeInventoryScreen;
import net.minecraft.client.gui.screens.inventory.InventoryScreen;
import net.minecraft.world.inventory.Slot;
/** Sends a sort request while a survival container is open. */
public final class SanctuaryInventorySortClient {
@@ -17,18 +21,35 @@ public final class SanctuaryInventorySortClient {
if (client.gui.screen() != null && client.gui.screen().getFocused() instanceof EditBox) {
return false;
}
return request(client);
return request(client, null);
}
public static boolean request(Minecraft client) {
return request(client, null);
}
public static boolean request(Minecraft client, Slot underCursor) {
if (client.player == null || client.level == null
|| !(client.gui.screen() instanceof AbstractContainerScreen<?>)
|| client.gui.screen() instanceof CreativeModeInventoryScreen
|| !(client.gui.screen() instanceof AbstractContainerScreen<?> screen)
|| screen instanceof CreativeModeInventoryScreen
|| !ClientPlayNetworking.canSend(SortInventoryPayload.ID)) {
return false;
}
boolean container = resolveContainerTarget(screen, underCursor);
AbstractWidget.playButtonClickSound(client.getSoundManager());
ClientPlayNetworking.send(new SortInventoryPayload());
ClientPlayNetworking.send(new SortInventoryPayload(container));
return true;
}
private static boolean resolveContainerTarget(AbstractContainerScreen<?> screen,
Slot underCursor) {
if (screen instanceof InventoryScreen) return false;
Slot hovered = underCursor != null ? underCursor
: ((AbstractContainerScreenAccessor) screen).sanctuary$getHoveredSlot();
if (hovered != null) {
return SanctuaryInventorySort.isSortableStorageMenu(screen.getMenu())
&& !SanctuaryInventorySort.isPlayerInventorySlot(hovered);
}
return SanctuaryInventorySort.isSortableStorageMenu(screen.getMenu());
}
}
@@ -10,11 +10,18 @@ import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.ChestMenu;
import net.minecraft.world.inventory.DispenserMenu;
import net.minecraft.world.inventory.HopperMenu;
import net.minecraft.world.inventory.ShulkerBoxMenu;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
/**
* Server-authoritative sort of every unlocked inventory row except the live hotbar.
* Armor, off-hand, crafting, cape and companion slots stay untouched.
* Server-authoritative sort of player storage or an open chest-like container.
* Armor, off-hand, crafting, cape, companion slots and the live hotbar stay
* untouched. Furnaces, shops and the storage terminal are never rewritten.
*/
public final class SanctuaryInventorySort {
private static final int COOLDOWN_TICKS = 8;
@@ -85,11 +92,31 @@ public final class SanctuaryInventorySort {
ServerPlayerEvents.LEAVE.register(player -> LAST_SORT_TICK.remove(player.getUUID()));
}
public static void sort(ServerPlayer player) {
public static boolean isSortableStorageMenu(AbstractContainerMenu menu) {
return menu instanceof ChestMenu
|| menu instanceof ShulkerBoxMenu
|| menu instanceof HopperMenu
|| menu instanceof DispenserMenu;
}
public static boolean isPlayerInventorySlot(Slot slot) {
if (slot == null) return false;
Container container = slot.container;
return container instanceof Inventory
|| container instanceof SanctuaryOverflowContainer
|| container instanceof SanctuaryCapeContainer
|| container instanceof SanctuaryCompanionEggContainer;
}
public static void sort(ServerPlayer player, boolean container) {
long now = player.level().getGameTime();
Long previous = LAST_SORT_TICK.get(player.getUUID());
if (previous != null && now - previous < COOLDOWN_TICKS) return;
LAST_SORT_TICK.put(player.getUUID(), now);
if (container) {
sortOpenContainer(player);
return;
}
int unlockedRows = SanctuaryInventoryCapacity.unlockedRows(player);
if (unlockedRows <= 1) return;
@@ -123,6 +150,28 @@ public final class SanctuaryInventorySort {
if (player.containerMenu != player.inventoryMenu) player.containerMenu.broadcastChanges();
}
private static void sortOpenContainer(ServerPlayer player) {
AbstractContainerMenu menu = player.containerMenu;
if (menu == null || menu == player.inventoryMenu || !isSortableStorageMenu(menu)
|| !menu.stillValid(player)) {
return;
}
List<Slot> sortable = new ArrayList<>();
for (Slot slot : menu.slots) {
if (!slot.isActive() || isPlayerInventorySlot(slot)) continue;
sortable.add(slot);
}
if (sortable.isEmpty()) return;
List<ItemStack> items = new ArrayList<>(sortable.size());
for (Slot slot : sortable) items.add(slot.getItem().copy());
SanctuaryInventorySorter.compactAndSort(items, STACKS);
for (int index = 0; index < sortable.size(); index++) {
Slot slot = sortable.get(index);
slot.container.setItem(slot.getContainerSlot(), items.get(index));
}
menu.broadcastChanges();
}
private static ItemStack getRowItem(Inventory inventory, Container overflow,
int logicalRow, int column) {
if (logicalRow < SanctuaryInventoryCapacity.VANILLA_ROW_COUNT) {
@@ -1,6 +1,7 @@
package fr.koka99cab.sanctuary26.sanctuary.mixin.client;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.world.inventory.Slot;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.gen.Accessor;
@@ -21,4 +22,7 @@ public interface AbstractContainerScreenAccessor {
@Mutable
@Accessor("imageHeight")
void sanctuary$setImageHeight(int imageHeight);
@Accessor("hoveredSlot")
Slot sanctuary$getHoveredSlot();
}
@@ -251,7 +251,8 @@ abstract class AbstractContainerScreenMixin implements SanctuaryContainerScreenG
private void sanctuary$sortInventory(MouseButtonEvent event, boolean doubleClick,
CallbackInfoReturnable<Boolean> cir) {
if (!SanctuaryClient.isSortInventoryMouse(event)) return;
if (SanctuaryInventorySortClient.request(Minecraft.getInstance())) {
if (SanctuaryInventorySortClient.request(Minecraft.getInstance(),
sanctuary$slotAt(event.x(), event.y()))) {
cir.setReturnValue(true);
}
}
@@ -0,0 +1,20 @@
package fr.koka99cab.sanctuary26.sanctuary.mixin.client;
import fr.koka99cab.sanctuary26.sanctuary.client.QuestTrackerDebugLayout;
import java.util.List;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.components.DebugScreenOverlay;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/** Lets the transparent quest tracker start immediately below the current F3 lines. */
@Mixin(DebugScreenOverlay.class)
abstract class DebugScreenOverlayMixin {
@Inject(method = "extractLines", at = @At("HEAD"))
private void sanctuary$captureLeftColumn(GuiGraphicsExtractor graphics, List<String> lines,
boolean left, CallbackInfo ci) {
if (left) QuestTrackerDebugLayout.captureLeftLines(lines.size());
}
}
@@ -6,6 +6,7 @@ import fr.koka99cab.sanctuary26.sanctuary.client.SanctuaryInventorySortClient;
import fr.koka99cab.sanctuary26.sanctuary.client.SanctuaryZoom;
import net.minecraft.client.Minecraft;
import net.minecraft.client.MouseHandler;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.client.input.MouseButtonEvent;
import net.minecraft.client.input.MouseButtonInfo;
import org.lwjgl.glfw.GLFW;
@@ -32,6 +33,9 @@ public abstract class MouseHandlerMixin {
if (action != GLFW.GLFW_PRESS) return;
Minecraft client = Minecraft.getInstance();
if (windowPointer != client.getWindow().handle()) return;
if (client.gui.screen() instanceof AbstractContainerScreen<?>) {
return;
}
MouseButtonEvent event = new MouseButtonEvent(0.0, 0.0, buttonInfo);
if (!SanctuaryClient.isSortInventoryMouse(event)) return;
if (SanctuaryInventorySortClient.request(client)) {
@@ -75,7 +75,8 @@ public final class SanctuaryNetworking {
}
}));
ServerPlayNetworking.registerGlobalReceiver(SortInventoryPayload.ID, (payload, context) ->
context.server().execute(() -> SanctuaryInventorySort.sort(context.player())));
context.server().execute(() ->
SanctuaryInventorySort.sort(context.player(), payload.container())));
ServerPlayNetworking.registerGlobalReceiver(CompanionPowerUsePayload.ID, (payload, context) ->
context.server().execute(() -> SanctuaryCompanionPowers.use(context.player())));
ServerPlayNetworking.registerGlobalReceiver(SitRequestPayload.ID, (payload, context) ->
@@ -5,19 +5,22 @@ import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
/** A parameterless client intent. The server owns which unlocked rows are sorted. */
public record SortInventoryPayload() implements CustomPacketPayload {
/**
* Bounded client intent: player storage or the open storage container.
* The server still decides which slots exist and ignores non-storage menus.
*/
public record SortInventoryPayload(boolean container) implements CustomPacketPayload {
public static final Type<SortInventoryPayload> ID =
new Type<>(SanctuaryMod.id("c2s/sort_inventory"));
public static final StreamCodec<RegistryFriendlyByteBuf, SortInventoryPayload> CODEC =
CustomPacketPayload.codec(SortInventoryPayload::write, SortInventoryPayload::new);
private SortInventoryPayload(RegistryFriendlyByteBuf buffer) {
this();
this(buffer.readBoolean());
}
private void write(RegistryFriendlyByteBuf buffer) {
// Intentionally empty: no client-supplied slot list or item identity can be trusted.
buffer.writeBoolean(container);
}
@Override
@@ -3,8 +3,10 @@ package fr.koka99cab.sanctuary26.sanctuary.shop;
import fr.koka99cab.sanctuary26.sanctuary.network.QuestTrackerSnapshotPayload;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.minecraft.resources.Identifier;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
@@ -43,29 +45,58 @@ public final class QuestTrackerSync {
}
private static List<QuestTrackerSnapshotPayload.Entry> entries(ServerPlayer player) {
List<QuestTrackerSnapshotPayload.Entry> entries = new ArrayList<>();
List<RankedEntry> entries = new ArrayList<>();
UUID playerId = player.getUUID();
MinecraftServer server = player.level().getServer();
for (QuestRequestData.Request request : QuestRequestData.get(server).requests()) {
if (entries.size() >= QuestTrackerSnapshotPayload.MAX_ENTRIES) break;
if (!tracksAcceptedRequest(request, playerId)) continue;
entries.add(new QuestTrackerSnapshotPayload.Entry(
addRanked(entries, new RankedEntry(new QuestTrackerSnapshotPayload.Entry(
SOURCE_REQUEST, clip(request.id(), 36), clip(request.objective(), 96),
clip(request.action(), 16), clip(request.target(), 64),
request.progress(), request.quantity(), request.ownerColor()));
request.progress(), request.quantity(), request.ownerColor()),
rewardOrder(request.rewardTarget())));
}
for (QuestBoardData.Board board : QuestBoardData.get(server).boards()) {
if (entries.size() >= QuestTrackerSnapshotPayload.MAX_ENTRIES) break;
QuestBoardData.Contract contract = board.contract().orElse(null);
if (contract == null || contract.claim().isEmpty()
|| !contract.claim().get().playerId().equals(playerId)) continue;
QuestBoardData.Objective objective = contract.objective();
entries.add(new QuestTrackerSnapshotPayload.Entry(
SOURCE_BOARD, clip(board.id(), 36), "",
addRanked(entries, new RankedEntry(new QuestTrackerSnapshotPayload.Entry(
SOURCE_BOARD, clip(board.id(), 36), board.questTier().id(),
clip(objective.kind(), 16), clip(objective.target(), 64),
objective.progress(), objective.required(), tierColor(board.questTier())));
objective.progress(), objective.required(), tierColor(board.questTier())),
rewardOrder(board.questTier())));
}
return entries;
return entries.stream().map(RankedEntry::entry).toList();
}
private static void addRanked(List<RankedEntry> entries, RankedEntry candidate) {
int index = 0;
while (index < entries.size() && entries.get(index).rewardOrder() <= candidate.rewardOrder()) {
index++;
}
entries.add(index, candidate);
if (entries.size() > QuestTrackerSnapshotPayload.MAX_ENTRIES) entries.removeLast();
}
static int rewardOrder(QuestBoardTier tier) {
return switch (tier) {
case RUBY -> 0;
case SAPPHIRE -> 1;
case EMERALD -> 2;
};
}
static int rewardOrder(String rewardTarget) {
Identifier id = Identifier.tryParse(rewardTarget);
String path = (id == null ? rewardTarget : id.getPath());
if (path == null) return 3;
path = path.toLowerCase(Locale.ROOT);
if (path.equals("ruby") || path.startsWith("ruby_")) return 0;
if (path.equals("sapphire") || path.equals("saphire")
|| path.startsWith("sapphire_") || path.startsWith("saphire_")) return 1;
if (path.equals("emerald") || path.startsWith("emerald_")) return 2;
return 3;
}
static boolean tracksAcceptedRequest(QuestRequestData.Request request, UUID playerId) {
@@ -87,4 +118,6 @@ public final class QuestTrackerSync {
if (value == null) return "";
return value.length() <= maximumLength ? value : value.substring(0, maximumLength);
}
private record RankedEntry(QuestTrackerSnapshotPayload.Entry entry, int rewardOrder) {}
}
@@ -245,11 +245,19 @@
"key.sanctuary.switch_hotbar_row": "Cycle hotbar row",
"key.sanctuary.sort_inventory": "Sort inventory",
"key.sanctuary.quest_tracker": "Quest tracker",
"key.sanctuary.quest_tracker_mode": "Quest tracker style",
"hud.sanctuary.quest_tracker.title": "Quests",
"hud.sanctuary.quest_tracker.empty": "No quest in progress",
"hud.sanctuary.quest_tracker.toggle_on": "Quest tracker shown",
"hud.sanctuary.quest_tracker.toggle_off": "Quest tracker hidden",
"hud.sanctuary.quest_tracker.mode.text": "Quest tracker: F3 text",
"hud.sanctuary.quest_tracker.mode.panel": "Quest tracker: Chris's panel",
"hud.sanctuary.quest_tracker.source.request": "Accepted request",
"hud.sanctuary.quest_tracker.source.owned": "Your request",
"hud.sanctuary.quest_tracker.source.board": "Daily contract",
"hud.sanctuary.quest_tracker.source.board": "Quest contract",
"hud.sanctuary.quest_tracker.reward.emerald": "Emerald quest",
"hud.sanctuary.quest_tracker.reward.ruby": "Ruby quest",
"hud.sanctuary.quest_tracker.reward.sapphire": "Sapphire quest",
"hud.sanctuary.quest_tracker.progress": "%s · %s · %s/%s",
"message.sanctuary.zoom": "Zoom · FOV %s",
"message.sanctuary.companion.passive_only": "This essence only has a passive power.",
@@ -245,11 +245,19 @@
"key.sanctuary.switch_hotbar_row": "Changer de rangée de hotbar",
"key.sanctuary.sort_inventory": "Trier l'inventaire",
"key.sanctuary.quest_tracker": "Suivi des quêtes",
"key.sanctuary.quest_tracker_mode": "Style du suivi des quêtes",
"hud.sanctuary.quest_tracker.title": "Quêtes",
"hud.sanctuary.quest_tracker.empty": "Aucune quête en cours",
"hud.sanctuary.quest_tracker.toggle_on": "Suivi des quêtes affiché",
"hud.sanctuary.quest_tracker.toggle_off": "Suivi des quêtes masqué",
"hud.sanctuary.quest_tracker.mode.text": "Suivi des quêtes : texte F3",
"hud.sanctuary.quest_tracker.mode.panel": "Suivi des quêtes : panneau de Chris",
"hud.sanctuary.quest_tracker.source.request": "Demande acceptée",
"hud.sanctuary.quest_tracker.source.owned": "Votre demande",
"hud.sanctuary.quest_tracker.source.board": "Contrat du jour",
"hud.sanctuary.quest_tracker.source.board": "Contrat de quête",
"hud.sanctuary.quest_tracker.reward.emerald": "Quête émeraude",
"hud.sanctuary.quest_tracker.reward.ruby": "Quête rubis",
"hud.sanctuary.quest_tracker.reward.sapphire": "Quête saphir",
"hud.sanctuary.quest_tracker.progress": "%s · %s · %s/%s",
"message.sanctuary.zoom": "Zoom · FOV %s",
"message.sanctuary.companion.passive_only": "Cette essence ne possède qu'un pouvoir passif.",
@@ -157,11 +157,19 @@
"key.sanctuary.switch_hotbar_row": "Сменить ряд панели быстрого доступа",
"key.sanctuary.sort_inventory": "Сортировать инвентарь",
"key.sanctuary.quest_tracker": "Трекер заданий",
"key.sanctuary.quest_tracker_mode": "Стиль трекера заданий",
"hud.sanctuary.quest_tracker.title": "Задания",
"hud.sanctuary.quest_tracker.empty": "Нет активного задания",
"hud.sanctuary.quest_tracker.toggle_on": "Трекер заданий показан",
"hud.sanctuary.quest_tracker.toggle_off": "Трекер заданий скрыт",
"hud.sanctuary.quest_tracker.mode.text": "Трекер заданий: текст F3",
"hud.sanctuary.quest_tracker.mode.panel": "Трекер заданий: панель Криса",
"hud.sanctuary.quest_tracker.source.request": "Принятый запрос",
"hud.sanctuary.quest_tracker.source.owned": "Ваш запрос",
"hud.sanctuary.quest_tracker.source.board": "Дневной контракт",
"hud.sanctuary.quest_tracker.source.board": "Контракт задания",
"hud.sanctuary.quest_tracker.reward.emerald": "Изумрудное задание",
"hud.sanctuary.quest_tracker.reward.ruby": "Рубиновое задание",
"hud.sanctuary.quest_tracker.reward.sapphire": "Сапфировое задание",
"hud.sanctuary.quest_tracker.progress": "%s · %s · %s/%s",
"message.sanctuary.zoom": "Зум · FOV %s",
"message.sanctuary.companion.passive_only": "У этой сущности есть только пассивная способность.",
@@ -25,6 +25,7 @@
"client.CreativeModeInventoryScreenMixin",
"client.CreativeSlotWrapperAccessor",
"client.CreateWorldScreenWorldTabMixin",
"client.DebugScreenOverlayMixin",
"client.GuiGraphicsExtractorMixin",
"client.InventoryScreenMixin",
"client.JeiBookmarkButtonControllerMixin",
@@ -0,0 +1,25 @@
package fr.koka99cab.sanctuary26.sanctuary.client;
/** Checks the persisted display-mode fallback and the F3 column offset. */
public final class QuestTrackerDisplaySmoke {
private QuestTrackerDisplaySmoke() {}
public static void main(String[] args) {
require(QuestTrackerDisplayMode.byId(null) == QuestTrackerDisplayMode.TEXT,
"Missing legacy settings must keep the transparent text mode");
require(QuestTrackerDisplayMode.byId("panel") == QuestTrackerDisplayMode.PANEL,
"Chris's original panel mode must remain selectable");
require(QuestTrackerDisplayMode.PANEL.next() == QuestTrackerDisplayMode.TEXT,
"Display mode cycling must wrap back to text");
QuestTrackerDebugLayout.captureLeftLines(7);
require(QuestTrackerDebugLayout.textTop(false) == 2,
"The tracker must keep its top-left margin when F3 is hidden");
require(QuestTrackerDebugLayout.textTop(true) == 67,
"The tracker must start below all seven permanent F3 lines");
}
private static void require(boolean value, String message) {
if (!value) throw new AssertionError(message);
}
}
@@ -43,6 +43,21 @@ public final class SanctuaryInventorySortSmoke {
"Dirt remainder must keep the leftover count");
require(storage.get(6).isEmpty(), "Unused storage slots must end empty");
List<FakeStack> chest = new ArrayList<>(List.of(
FakeStack.EMPTY,
new FakeStack("minecraft:oak_log", "", 16, 64),
new FakeStack("minecraft:oak_log", "", 50, 64),
new FakeStack("minecraft:arrow", "", 32, 64)
));
SanctuaryInventorySorter.compactAndSort(chest, access);
require(chest.get(0).id.equals("minecraft:arrow") && chest.get(0).count == 32,
"A chest-like list must compact from the first slot");
require(chest.get(1).id.equals("minecraft:oak_log") && chest.get(1).count == 64,
"Identical chest stacks must merge up to max size");
require(chest.get(2).id.equals("minecraft:oak_log") && chest.get(2).count == 2,
"Chest remainder must follow the merged stack");
require(chest.get(3).isEmpty(), "Unused chest slots must end empty");
List<FakeStack> hotbar = new ArrayList<>(List.of(
new FakeStack("minecraft:torch", "", 8, 64),
new FakeStack("minecraft:bread", "", 3, 64)
@@ -28,6 +28,15 @@ public final class QuestTrackerModelSmoke {
QuestTrackerSnapshotPayload mixed = new QuestTrackerSnapshotPayload(List.of(owned, board));
require(mixed.entries().get(0).required() == 4 && mixed.entries().get(1).progress() == 2,
"Owned and board progress must be preserved");
require(QuestTrackerSync.rewardOrder("anotherworld:ruby")
< QuestTrackerSync.rewardOrder("anotherworld:sapphire"),
"Ruby rewards must be ordered before sapphire rewards");
require(QuestTrackerSync.rewardOrder("anotherworld:saphire")
== QuestTrackerSync.rewardOrder(QuestBoardTier.SAPPHIRE),
"The legacy saphire spelling must remain in the sapphire reward group");
require(QuestTrackerSync.rewardOrder(QuestBoardTier.SAPPHIRE)
< QuestTrackerSync.rewardOrder(QuestBoardTier.EMERALD),
"Sapphire rewards must be ordered before emerald and uncategorized rewards");
require(new QuestTrackerSnapshotPayload(null).entries().isEmpty(),
"Null snapshot must become an empty tracker");
UUID owner = UUID.fromString("6c43c35a-3498-41bb-a77e-10c679e635d1");