feat(sanctuary): ajouter les modes du suivi de quêtes #107

Merged
koka merged 1 commits from feature/quest-tracker-display-modes into main 2026-08-31 03:02:03 +00:00
17 changed files with 328 additions and 53 deletions
+9
View File
@@ -6,6 +6,15 @@ Ce journal suit les **versions du modpack**. Lorsqu'un seul module change, sa ve
- Depuis `26.2.0-alpha.6`, le pack et les modules ont des versions indépendantes. I Like To Move It et Only Fun deviennent les cinquième et sixième modules actifs dans l'alpha 46.
- Les versions ci-dessous ont été reconstituées à partir des JAR archivés, des packs locaux et du code source actuel. Les dates n'étant pas enregistrées de manière fiable, elles ne sont pas inventées.
## Non distribué
Module modifié : **Sanctuary `0.0.0-alpha.125`**. Le pack public reste temporairement en `26.2.0-alpha.222` jusqu'à la publication.
- 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 » ;
- conserve les données Sanctuary v11, le protocole réseau 22, les IDs et les sauvegardes existantes.
## `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.
+18 -17
View File
@@ -3438,7 +3438,7 @@ tasks.register("verifyProgressivePauseMenuRelease") {
"sanctuary/src/main/resources/assets/sanctuary/lang/${locale}.json"))]
}
if (rootProject.pack_version != "26.2.0-alpha.222"
|| rootProject.sanctuary_version != "0.0.0-alpha.124"
|| rootProject.sanctuary_version != "0.0.0-alpha.125"
|| rootProject.onlyfun_version != "0.0.0-alpha.25"
|| 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"]
@@ -4279,7 +4279,7 @@ tasks.register("verifyShopPricingLayoutRelease") {
def index = new JsonSlurper().parse(file("pack/prism/modrinth.index.json"))
if (rootProject.pack_version != "26.2.0-alpha.222"
|| rootProject.onlyfun_version != "0.0.0-alpha.25"
|| rootProject.sanctuary_version != "0.0.0-alpha.124"
|| rootProject.sanctuary_version != "0.0.0-alpha.125"
|| 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"
@@ -4373,7 +4373,7 @@ tasks.register("verifyShopCsvBossGatesRelease") {
def offerCatalog = file("sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/ShopOfferCatalog.java").text
if (rootProject.pack_version != "26.2.0-alpha.222"
|| rootProject.onlyfun_version != "0.0.0-alpha.25"
|| rootProject.sanctuary_version != "0.0.0-alpha.124"
|| rootProject.sanctuary_version != "0.0.0-alpha.125"
|| 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"]
@@ -4465,7 +4465,7 @@ tasks.register("verifyQuestRequestsCauldronBatchesRelease") {
def sanctuaryManifest = new JsonSlurper().parse(file("sanctuary/src/main/resources/fabric.mod.json"))
if (rootProject.pack_version != "26.2.0-alpha.222"
|| rootProject.itsalive_version != "0.0.0-alpha.36"
|| rootProject.sanctuary_version != "0.0.0-alpha.124"
|| rootProject.sanctuary_version != "0.0.0-alpha.125"
|| 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"]
@@ -4565,7 +4565,7 @@ tasks.register("verifyQuestRequestContractsRelease") {
def sanctuaryManifest = new JsonSlurper().parse(file("sanctuary/src/main/resources/fabric.mod.json"))
if (rootProject.pack_version != "26.2.0-alpha.222"
|| rootProject.itsalive_version != "0.0.0-alpha.36"
|| rootProject.sanctuary_version != "0.0.0-alpha.124"
|| rootProject.sanctuary_version != "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"
@@ -4685,7 +4685,7 @@ tasks.register("verifyQuestRequestEscrowRelease") {
def packwizIndex = file("pack/packwiz/index.toml").text
def sanctuaryManifest = new JsonSlurper().parse(file("sanctuary/src/main/resources/fabric.mod.json"))
if (rootProject.pack_version != "26.2.0-alpha.222"
|| rootProject.sanctuary_version != "0.0.0-alpha.124"
|| rootProject.sanctuary_version != "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"
@@ -4742,7 +4742,7 @@ tasks.register("verifyWaystoneShopCulinaryAlpha202Release") {
|| rootProject.anotherworld_version != "0.0.0-alpha.55"
|| rootProject.itsalive_version != "0.0.0-alpha.36"
|| rootProject.iliketomoveit_version != "0.0.0-alpha.16"
|| rootProject.sanctuary_version != "0.0.0-alpha.124"
|| rootProject.sanctuary_version != "0.0.0-alpha.125"
|| migration.source?.pack_version != "26.2.0-alpha.201"
|| migration.source?.modules != [
itsalive: "0.0.0-alpha.30",
@@ -4900,7 +4900,7 @@ tasks.register("verifySafeVaultFeature") {
}.keySet()
if (rootProject.pack_version != "26.2.0-alpha.222"
|| rootProject.anotherworld_version != "0.0.0-alpha.55"
|| rootProject.sanctuary_version != "0.0.0-alpha.124"
|| rootProject.sanctuary_version != "0.0.0-alpha.125"
|| migration.schema_version != 1
|| migration.source?.pack_version != "26.2.0-alpha.201"
|| migration.target?.pack_version != "26.2.0-alpha.202"
@@ -5759,7 +5759,7 @@ tasks.register("verifyAlpha210Release") {
it.path == "mods/jei-26.2-fabric-30.29.0.196.jar"
}
if (rootProject.pack_version != "26.2.0-alpha.222"
|| rootProject.sanctuary_version != "0.0.0-alpha.124"
|| rootProject.sanctuary_version != "0.0.0-alpha.125"
|| rootProject.onlyfun_version != "0.0.0-alpha.25"
|| rootProject.iliketomoveit_version != currentModules.iliketomoveit
|| rootProject.itsalive_version != "0.0.0-alpha.36"
@@ -5858,7 +5858,7 @@ tasks.register("verifyAlpha211Release") {
onlyfun: "0.0.0-alpha.25",
ambiance: "0.0.0-alpha.22",
iliketomoveit: "0.0.0-alpha.16"]
def workspaceModules = currentModules
def workspaceModules = currentModules + [sanctuary: "0.0.0-alpha.125"]
def expectedDataVersions = [ambiance: 2, itsalive: 3, onlyfun: 2, ouch: 1, sanctuary: 11]
def expectedProtocols = [ambiance: 6, itsalive: 4, onlyfun: 3, ouch: 1, sanctuary: 22]
def manifests = expectedTarget.collectEntries { id, version ->
@@ -5956,7 +5956,7 @@ tasks.register("verifyAlpha212Release") {
it.path == "mods/jei-26.2-fabric-30.29.0.196.jar"
}
if (rootProject.pack_version != "26.2.0-alpha.222"
|| rootProject.sanctuary_version != "0.0.0-alpha.124"
|| rootProject.sanctuary_version != "0.0.0-alpha.125"
|| migration.source?.pack_version != "26.2.0-alpha.211"
|| migration.source?.modules != [sanctuary: "0.0.0-alpha.119"]
|| migration.target?.pack_version != "26.2.0-alpha.212"
@@ -6449,7 +6449,7 @@ tasks.register("verifyAlpha218Release") {
|| currentModules.findAll { id, version -> id != "redstoner" && id != "sanctuary" }.any {
id, version -> rootProject.property("${id}_version") != version
}
|| rootProject.sanctuary_version != "0.0.0-alpha.124"
|| rootProject.sanctuary_version != "0.0.0-alpha.125"
|| migration.source?.pack_version != "26.2.0-alpha.217"
|| migration.source?.modules != sourceModules
|| migration.target?.pack_version != "26.2.0-alpha.218"
@@ -6533,6 +6533,7 @@ tasks.register("verifyAlpha219Release") {
sanctuary: "0.0.0-alpha.122", iliketomoveit: "0.0.0-alpha.16", ambiance: "0.0.0-alpha.21"]
def currentModules = targetModules + [
ambiance: "0.0.0-alpha.22", sanctuary: "0.0.0-alpha.124"]
def workspaceModules = currentModules + [sanctuary: "0.0.0-alpha.125"]
def requiredJars = currentModules.collect { id, version ->
"file = \"mods/${id}-${version}.jar\""
}
@@ -6548,7 +6549,7 @@ tasks.register("verifyAlpha219Release") {
exclude "**/build/**", "**/.gradle/**", "**/.git/**"
}.files
if (rootProject.pack_version != "26.2.0-alpha.222"
|| currentModules.any { id, version -> rootProject.property("${id}_version") != version }
|| workspaceModules.any { id, version -> rootProject.property("${id}_version") != version }
|| migration.source?.pack_version != "26.2.0-alpha.218"
|| migration.source?.modules != sourceModules
|| migration.target?.pack_version != "26.2.0-alpha.219"
@@ -6857,7 +6858,7 @@ tasks.register("verifyAlpha222Release") {
}
if (rootProject.pack_version != "26.2.0-alpha.222"
|| rootProject.redstoner_version != "0.0.0-alpha.13"
|| rootProject.sanctuary_version != "0.0.0-alpha.124"
|| rootProject.sanctuary_version != "0.0.0-alpha.125"
|| migration.source?.pack_version != "26.2.0-alpha.221"
|| migration.source?.modules != [
redstoner: "0.0.0-alpha.12", sanctuary: "0.0.0-alpha.122"]
@@ -6921,9 +6922,10 @@ tasks.register("verifyAlpha222Release") {
|| !questPayload.contains('SanctuaryMod.id("s2c/quest_tracker")')
|| !questSync.contains("tracksAcceptedRequest(request, playerId)")
|| !questSync.contains("SOURCE_BOARD")
|| !questHud.contains("Transparent, toggleable text overlay")
|| !questHud.contains("transparent F3-aligned")
|| !questHud.contains("entries.isEmpty()) return")
|| questHud.contains("graphics.fill(")
|| !questHud.contains("renderText(")
|| !questHud.contains("renderPanel(")
|| !sanctuaryClient.contains("GLFW.GLFW_KEY_J")
|| !sanctuaryClient.contains("QuestTrackerSnapshotPayload.ID")
|| !questEditor.contains("QuestIdSuggestions.suggest")
@@ -6943,7 +6945,6 @@ tasks.register("verifyAlpha222Release") {
|| jeiFiles[0].hashes?.sha1 != "c2f2c6b5822a560fe9872921b908ffc2f3297d00"
|| jeiFiles[0].hashes?.sha512 != "c62a3e264d4b0ec398b9aa717a4832eecb8663bbcec4483dc58a9999d5e4e55ca4f5bca285a0d4a003220c698c3e87c8a3294366e085e72545e4afe84aec92c7"
|| !changelog.contains("## `26.2.0-alpha.222`")
|| changelog.contains("## Non distribué")
|| !readme.contains("Version courante du pack distribué : **`26.2.0-alpha.222`**")) {
throw new GradleException("The alpha.222 terminal-search and quest-tracker release is incomplete")
}
+1 -1
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
maven_group=fr.koka99cab.sanctuary26
+20 -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."
@@ -151,7 +159,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")
@@ -2850,7 +2858,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
@@ -3016,7 +3024,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
@@ -3054,7 +3062,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]
@@ -3085,7 +3093,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
@@ -3116,7 +3124,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
@@ -3144,7 +3152,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")
@@ -3190,7 +3198,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
@@ -3248,7 +3256,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"
@@ -3317,7 +3325,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"
@@ -3389,7 +3397,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"
@@ -3421,6 +3429,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("verifyAlphaIndev")
@@ -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());
@@ -50,6 +50,7 @@ public final class SanctuaryClient implements ClientModInitializer {
private static KeyMapping celestialKey;
private static KeyMapping switchHotbarRowKey;
private static KeyMapping questTrackerKey;
private static KeyMapping questTrackerModeKey;
private static boolean zoomWasHeld;
private static boolean smokeScreenOpened;
private static boolean inventorySmokeScreenOpened;
@@ -99,6 +100,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();
@@ -198,6 +205,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));
@@ -300,6 +311,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);
@@ -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();
}
}
@@ -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());
}
}
@@ -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) {}
}
@@ -244,11 +244,19 @@
"key.sanctuary.zoom": "Telephoto zoom",
"key.sanctuary.switch_hotbar_row": "Cycle hotbar row",
"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.",
@@ -244,11 +244,19 @@
"key.sanctuary.zoom": "Zoom téléobjectif",
"key.sanctuary.switch_hotbar_row": "Changer de rangée de hotbar",
"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.",
@@ -156,11 +156,19 @@
"key.sanctuary.zoom": "Телефото-зум",
"key.sanctuary.switch_hotbar_row": "Сменить ряд панели быстрого доступа",
"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);
}
}
@@ -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");