0.6.2: Adventurer's Journal — bounties/rates/plots + smart Back button
Build / build (push) Has been cancelled

QuestLogScreen reworked from a generic status summary into a proper
adventure journal. Three sections, top to bottom:
- Active Bounties (N / cap) with progress bars + reward preview
- Counting House Rates — every wealth-token's effective per-unit spur
  payout for the player's tier fee + lifetime DR. Lets players check
  rates from the field instead of travelling to the Counting House.
- Owned Plots (N / cap)
- Footer: "FTB Quests is a separate mod — open your Quest Book item"

Lang title changed to "Adventurer's Journal" so it's distinct from
the FTB Quests system. Internal class + key stay QuestLog* for
backward-compat of /bns open questlog.

BaseBnsScreen.makeBackOrClose(screen) — labels the close button "Back"
with a back-arrow when FTB Library's previous-screen is set, "Close"
otherwise. Hub now passes the current screen wrapper as the previous
screen when opening the Journal, so close-from-Journal returns to Hub.

Bug fix: sub-panel children now pass `this` correctly to their parent
(was missing — same FTB Library latent bug).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Matt
2026-06-08 16:19:43 +00:00
parent 2888cf1814
commit 8cc2e45011
5 changed files with 117 additions and 38 deletions
+1 -1
View File
@@ -19,5 +19,5 @@ loader_version_range=[1,)
mod_id=bnstoolkit
mod_name=Brass and Sigil Toolkit
mod_license=All Rights Reserved
mod_version=0.6.1
mod_version=0.6.2
mod_group_id=uk.sijbers.bnstoolkit
@@ -1,7 +1,9 @@
package uk.sijbers.bnstoolkit.client.screens;
import dev.ftb.mods.ftblibrary.icon.Color4I;
import dev.ftb.mods.ftblibrary.icon.Icons;
import dev.ftb.mods.ftblibrary.ui.BaseScreen;
import dev.ftb.mods.ftblibrary.ui.SimpleTextButton;
import dev.ftb.mods.ftblibrary.ui.Theme;
import dev.ftb.mods.ftblibrary.ui.WidgetType;
import net.minecraft.client.gui.GuiGraphics;
@@ -59,4 +61,20 @@ public abstract class BaseBnsScreen extends BaseScreen {
protected int contentBottom() {
return getHeight() - 24;
}
/**
* Build a smart close button: labelled "Back" with a back-arrow icon
* if there's a previous screen registered (e.g. opened from the Hub),
* otherwise labelled "Close". In both cases the button calls
* {@code closeGui()} — FTB Library's BaseScreen returns to the
* registered previous screen automatically when one was set.
*/
public static SimpleTextButton makeBackOrClose(BaseBnsScreen screen) {
boolean hasPrev = screen.getPrevScreen() != null;
String label = hasPrev ? "Back" : "Close";
return SimpleTextButton.create(screen,
Component.literal(label),
hasPrev ? Icons.BACK : Icons.CLOSE,
btn -> screen.closeGui());
}
}
@@ -6,7 +6,9 @@ import dev.ftb.mods.ftblibrary.ui.Panel;
import dev.ftb.mods.ftblibrary.ui.SimpleTextButton;
import dev.ftb.mods.ftblibrary.ui.Theme;
import dev.ftb.mods.ftblibrary.ui.Widget;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;
import uk.sijbers.bnstoolkit.client.ClientBnsState;
import uk.sijbers.bnstoolkit.data.CivicTier;
@@ -43,8 +45,14 @@ public final class HubScreen extends BaseBnsScreen {
sp.setSize(getWidth() - 16, getHeight() - 50);
add(sp);
add(SimpleTextButton.create(this, Component.literal("Quest Log"), Icons.INFO,
btn -> { NetSend.requestSnapshot("ALL"); new QuestLogScreen().openGui(); }));
add(SimpleTextButton.create(this, Component.literal("Journal"), Icons.INFO,
btn -> {
NetSend.requestSnapshot("ALL");
QuestLogScreen ql = new QuestLogScreen();
Screen prev = Minecraft.getInstance().screen; // current screen wrapper
if (prev != null) ql.setPreviousScreen(prev);
ql.openGui();
}));
add(SimpleTextButton.create(this, Component.literal("Refresh"), Icons.REFRESH,
btn -> NetSend.requestSnapshot("ALL")));
add(SimpleTextButton.create(this, Component.literal("Close"), Icons.CLOSE,
@@ -1,6 +1,7 @@
package uk.sijbers.bnstoolkit.client.screens;
import dev.ftb.mods.ftblibrary.icon.Color4I;
import dev.ftb.mods.ftblibrary.icon.Icon;
import dev.ftb.mods.ftblibrary.icon.Icons;
import dev.ftb.mods.ftblibrary.ui.Panel;
import dev.ftb.mods.ftblibrary.ui.SimpleTextButton;
@@ -8,22 +9,33 @@ import dev.ftb.mods.ftblibrary.ui.Theme;
import dev.ftb.mods.ftblibrary.ui.Widget;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import uk.sijbers.bnstoolkit.client.ClientBnsState;
import uk.sijbers.bnstoolkit.data.Bounty;
import uk.sijbers.bnstoolkit.data.CivicTier;
import uk.sijbers.bnstoolkit.data.Plot;
import uk.sijbers.bnstoolkit.data.SellCatalogue;
import uk.sijbers.bnstoolkit.network.NetSend;
import java.util.Map;
/**
* Read-only "what am I doing right now" overview.
* Adventurer's Journal — at-a-glance overview of the player's active
* adventure. Read-only; no economy mutation happens here.
*
* Shows the player's tier + balance summary at the top, then three
* sections: active bounties with progress bars, owned plots, and the
* shop fee preview. FTB Quests integration deferred — when the quest
* book lands we'll add a fourth section that mirrors the player's
* current chapter progress here.
* <p>Three sections, top to bottom:
* <ul>
* <li><b>Active Bounties</b> — kills in progress, with progress bars
* and turn-in rewards. Primary use case.</li>
* <li><b>Counting House Rates</b> — current per-unit spur payout for
* each wealth-token after the player's tier fee + lifetime DR.
* Lets players check rates from the field without travelling to
* the Counting House.</li>
* <li><b>Owned Plots</b> — what you own + your slot cap.</li>
* </ul>
*
* <p>Not to be confused with FTB Quests, which is a separate
* chapter-based progression mod with its own Quest Book item. A footer
* line points players there.
*/
public final class QuestLogScreen extends BaseBnsScreen {
public QuestLogScreen() {
@@ -32,58 +44,84 @@ public final class QuestLogScreen extends BaseBnsScreen {
@Override
public void addWidgets() {
SummaryPanel summary = new SummaryPanel(this);
summary.setPos(8, contentTop());
summary.setSize(getWidth() - 16, getHeight() - 30);
add(summary);
// Pre-size the content panel so its children get correct
// dimensions during the recursive refreshWidgets.
ContentPanel content = new ContentPanel(this);
content.setPos(8, contentTop());
content.setSize(getWidth() - 16, getHeight() - 32);
add(content);
add(SimpleTextButton.create(this, Component.literal("Refresh"), Icons.REFRESH,
btn -> NetSend.requestSnapshot("ALL")));
add(BaseBnsScreen.makeBackOrClose(this));
}
@Override
public void alignWidgets() {
Widget rf = getWidgets().get(1);
rf.setSize(60, 16);
rf.setPos(getWidth() - 68, getHeight() - 22);
int btnY = getHeight() - 22;
Widget refresh = getWidgets().get(1);
refresh.setSize(60, 16); refresh.setPos(8, btnY);
Widget close = getWidgets().get(2);
close.setSize(60, 16); close.setPos(getWidth() - 68, btnY);
}
private static final class SummaryPanel extends Panel {
SummaryPanel(Panel parent) { super(parent); }
/** Three-section journal body. */
private static final class ContentPanel extends Panel {
ContentPanel(Panel parent) { super(parent); }
@Override public void addWidgets() {
CivicTier cfg = CivicTier.byId(ClientBnsState.tier);
add(new Header(this, "§e=== Status ==="));
add(new Header(this, "Tier: §" + cfg.colour().getChar() + cfg.name() + "§r ("
+ ClientBnsState.tier + " / " + CivicTier.MAX + ")"));
add(new Header(this, "Balance: §6" + TierUpgradeScreen.formatSpurs(ClientBnsState.spurs) + " spurs§r"));
add(new Header(this, "Shop fee: §c" + ClientBnsState.effectiveFeePct + "%§r"));
add(new Header(this, ""));
add(new Header(this, "§e=== Active bounties ==="));
// Section 1: Active Bounties
int activeCount = ClientBnsState.activeBounties.size();
int cap = ClientBnsState.bountySlotCap;
add(new Header(this,"§e=== Active Bounties (" + activeCount + " / " + cap + ") ==="));
if (ClientBnsState.activeBounties.isEmpty()) {
add(new Header(this, "§7none — visit the Bounty Board (B)"));
add(new Header(this,"§8 none — visit the Bounty Board at spawn"));
} else {
for (Map.Entry<String, ClientBnsState.ActiveBounty> e : ClientBnsState.activeBounties.entrySet()) {
Bounty b = Bounty.byId(e.getKey());
if (b == null) continue;
ClientBnsState.ActiveBounty ab = e.getValue();
String bar = progressBar(ab.progress(), ab.target(), 14);
String line = " " + b.name() + " " + bar + " §7" + ab.progress() + "/" + ab.target() + "§r";
add(new Header(this, line));
String bar = progressBar(ab.progress(), ab.target(), 10);
String line = " " + b.name() + " " + bar
+ " §7" + ab.progress() + "/" + ab.target()
+ "§r §6+" + ab.reward() + "§r";
add(new Header(this,line));
}
}
add(new Header(this, ""));
add(new Header(this, "§e=== Owned plots ==="));
add(new Header(this,""));
// Section 2: Counting House rates
add(new Header(this,"§e=== Counting House Rates ==="));
int fee = ClientBnsState.effectiveFeePct;
add(new Header(this,"§7 Your tier fee: §c" + fee + "%§r"));
for (Map.Entry<ResourceLocation, Long> entry : SellCatalogue.PRICES.entrySet()) {
ResourceLocation id = entry.getKey();
long base = entry.getValue();
long sold = ClientBnsState.lifetimeSoldOf(id);
long current = SellCatalogue.singleUnitPriceDisplay(id, sold, fee);
String name = displayName(id);
String line = String.format(" %s§r §7%d§r → §a%d§r spurs", name, base, current);
if (sold > 0) line += " §8(sold " + sold + ")§r";
add(new Header(this,line));
}
add(new Header(this,""));
// Section 3: Owned Plots
int owned = ClientBnsState.ownedPlotIds.size();
add(new Header(this,"§e=== Owned Plots (" + owned + " / " + ClientBnsState.plotSlotCap + ") ==="));
if (ClientBnsState.ownedPlotIds.isEmpty()) {
add(new Header(this, "§7none — visit the Plot Office (P)"));
add(new Header(this,"§8 none — visit the Plot Office at spawn"));
} else {
for (String pid : ClientBnsState.ownedPlotIds) {
Plot p = Plot.byId(pid);
if (p != null) add(new Header(this, " §a★§r " + p.name() + " §7(" + p.size() + ")§r"));
if (p != null) add(new Header(this," §a★§r " + p.name() + " §7(" + p.size() + ")§r"));
}
}
add(new Header(this,""));
add(new Header(this,"§8FTB Quests is a separate mod — open your Quest Book item for chapters."));
}
@Override public void alignWidgets() {
@@ -104,13 +142,28 @@ public final class QuestLogScreen extends BaseBnsScreen {
sb.append("§r");
return sb.toString();
}
private static String displayName(ResourceLocation id) {
String path = id.getPath();
// Friendly-cased name from the id: "ancient_debris" -> "Ancient Debris"
String[] parts = path.split("_");
StringBuilder out = new StringBuilder();
for (int i = 0; i < parts.length; i++) {
if (parts[i].isEmpty()) continue;
out.append(Character.toUpperCase(parts[i].charAt(0)));
if (parts[i].length() > 1) out.append(parts[i].substring(1));
if (i + 1 < parts.length) out.append(' ');
}
return out.toString();
}
}
private static final class Header extends Widget {
private final String text;
Header(Panel parent, String text) { super(parent); this.text = text; }
@Override public void draw(GuiGraphics gfx, Theme theme, int x, int y, int w, int h) {
theme.drawString(gfx, Component.literal(text), x, y + 2, Color4I.WHITE, Theme.SHADOW);
theme.drawString(gfx, Component.literal(text), x + 2, y + 2, Color4I.WHITE, Theme.SHADOW);
}
}
}
@@ -10,7 +10,7 @@
"screen.bnstoolkit.hub": "Brass and Sigil",
"screen.bnstoolkit.tier_upgrade": "Civic Tier",
"screen.bnstoolkit.bounty_board": "Bounty Board",
"screen.bnstoolkit.quest_log": "Quest Log",
"screen.bnstoolkit.quest_log": "Adventurer's Journal",
"screen.bnstoolkit.plot_purchase": "Plot Office",
"screen.bnstoolkit.shop_browser": "Counting House",
"screen.bnstoolkit.counting_house": "Counting House"