0.8.3: Adventurer's Journal becomes the "what's available + where to go" view
Build / build (push) Has been cancelled
Build / build (push) Has been cancelled
Rebuilt the Journal as a town-board view rather than just personal status. Five sections, top to bottom: 1. Available Bounties — today's full pool of 5 with rewards + state markers (active/ready/cooldown). Header: "Visit the Bounty Officer at the Adventurer's Guild" 2. Plots For Sale — every plot with state (for sale / taken by X / yours). Header: "Visit the Plot Clerk at the Plot Office" 3. Exchange Rates — each wealth-token with list + your effective rate. Header: "Visit the Exchange Clerk at the Exchange" 4. Civic Tier — your current + next-tier cost. Header: "Visit the Civic Registrar at the Town Hall" 5. Your Status — active bounty progress bars, owned plot count, wealth summary Each section header includes the NPC name + building so players know which spawn building to walk to. The Journal itself is still read-only; all transactions happen at the NPCs. Content overflows the 360x220 screen, so the body uses a PanelScrollBar (right edge) for vertical scrolling. Mouse wheel scrolls too. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -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.8.2
|
||||
mod_version=0.8.3
|
||||
mod_group_id=uk.sijbers.bnstoolkit
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
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;
|
||||
import dev.ftb.mods.ftblibrary.ui.PanelScrollBar;
|
||||
import dev.ftb.mods.ftblibrary.ui.Theme;
|
||||
import dev.ftb.mods.ftblibrary.ui.Widget;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
@@ -12,30 +10,30 @@ 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;
|
||||
|
||||
/**
|
||||
* Adventurer's Journal — at-a-glance overview of the player's active
|
||||
* adventure. Read-only; no economy mutation happens here.
|
||||
* Adventurer's Journal — what's available in town and what you've got.
|
||||
*
|
||||
* <p>Three sections, top to bottom:
|
||||
* <p>Read-only. Five 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>Exchange 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 Exchange.</li>
|
||||
* <li><b>Owned Plots</b> — what you own + your slot cap.</li>
|
||||
* <li><b>Available Bounties</b> — today's offered pool (5), see Bounty Officer</li>
|
||||
* <li><b>Plots For Sale</b> — unsold plots, see Plot Clerk</li>
|
||||
* <li><b>Exchange Rates</b> — wealth-token rates, see Exchange Clerk</li>
|
||||
* <li><b>Civic Tier</b> — your tier + next cost, see Civic Registrar</li>
|
||||
* <li><b>Your Status</b> — active bounties / owned plots / wealth summary</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.
|
||||
* <p>Each section heads with "Visit X at Y" so players know which NPC at
|
||||
* spawn to interact with. The Journal itself doesn't transact —
|
||||
* everything below is informational.
|
||||
*
|
||||
* <p>Content overflows the screen; a {@link PanelScrollBar} on the right
|
||||
* lets the player scroll through.
|
||||
*/
|
||||
public final class QuestLogScreen extends BaseBnsScreen {
|
||||
public QuestLogScreen() {
|
||||
@@ -46,76 +44,123 @@ public final class QuestLogScreen extends BaseBnsScreen {
|
||||
public void addWidgets() {
|
||||
ContentPanel content = new ContentPanel(this);
|
||||
content.setPos(8, contentTop());
|
||||
content.setSize(getWidth() - 16, getHeight() - 50);
|
||||
content.setSize(getWidth() - 24, getHeight() - 50); // -24 to leave room for scrollbar
|
||||
content.setOnlyRenderWidgetsInside(true);
|
||||
add(content);
|
||||
|
||||
// Vertical scrollbar to the right of the content panel
|
||||
PanelScrollBar scrollBar = new PanelScrollBar(this, content);
|
||||
scrollBar.setPos(getWidth() - 14, contentTop());
|
||||
scrollBar.setSize(6, getHeight() - 50);
|
||||
add(scrollBar);
|
||||
|
||||
add(BaseBnsScreen.makeBackOrClose(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void alignWidgets() {
|
||||
Widget close = getWidgets().get(1);
|
||||
Widget close = getWidgets().get(2);
|
||||
close.setSize(72, 16);
|
||||
close.setPos(getWidth() - 80, getHeight() - 22);
|
||||
}
|
||||
|
||||
/** Three-section journal body. */
|
||||
/** Scrollable journal body. */
|
||||
private static final class ContentPanel extends Panel {
|
||||
ContentPanel(Panel parent) { super(parent); }
|
||||
|
||||
@Override public void addWidgets() {
|
||||
// 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,"§8 none — visit the Bounty Board at spawn"));
|
||||
// ─── 1. Available Bounties ─────────────────────────────────
|
||||
add(new Header(this, "§e=== Available Bounties ==="));
|
||||
add(new Header(this, "§7Visit the Bounty Officer at the Adventurer's Guild"));
|
||||
if (ClientBnsState.dailyPoolIds.isEmpty()) {
|
||||
add(new Header(this, "§8 (none rolled yet — log in to refresh)"));
|
||||
} else {
|
||||
for (Map.Entry<String, ClientBnsState.ActiveBounty> e : ClientBnsState.activeBounties.entrySet()) {
|
||||
Bounty b = Bounty.byId(e.getKey());
|
||||
int shown = 0;
|
||||
for (String id : ClientBnsState.dailyPoolIds) {
|
||||
Bounty b = Bounty.byId(id);
|
||||
if (b == null) continue;
|
||||
ClientBnsState.ActiveBounty ab = e.getValue();
|
||||
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));
|
||||
Bounty.State state = ClientBnsState.stateOf(id);
|
||||
String prefix;
|
||||
switch (state) {
|
||||
case ACTIVE -> prefix = "§e●§r ";
|
||||
case READY -> prefix = "§a✓§r ";
|
||||
case COOLDOWN -> prefix = "§8○§r ";
|
||||
default -> prefix = "§7○§r ";
|
||||
}
|
||||
add(new Header(this, " " + prefix + b.name() + " §6+" + b.reward() + " sp§r"));
|
||||
shown++;
|
||||
}
|
||||
if (shown == 0) add(new Header(this, "§8 (pool empty — log in to refresh)"));
|
||||
}
|
||||
add(new Header(this, ""));
|
||||
|
||||
add(new Header(this,""));
|
||||
// ─── 2. Plots For Sale ─────────────────────────────────────
|
||||
add(new Header(this, "§e=== Plots For Sale ==="));
|
||||
add(new Header(this, "§7Visit the Plot Clerk at the Plot Office"));
|
||||
int forSale = 0, taken = 0, mine = 0;
|
||||
for (Plot p : Plot.all()) {
|
||||
String owner = ClientBnsState.ownerOf(p.id());
|
||||
boolean isMine = ClientBnsState.ownsPlot(p.id());
|
||||
String marker;
|
||||
String suffix;
|
||||
if (isMine) { marker = "§a★§r "; suffix = "§a yours§r"; mine++; }
|
||||
else if (!owner.isEmpty()) { marker = "§c●§r "; suffix = "§c taken by " + owner + "§r"; taken++; }
|
||||
else { marker = "§e○§r "; suffix = "§6 " + p.price() + " sp§r"; forSale++; }
|
||||
add(new Header(this, " " + marker + p.name() + " §7(" + p.size() + ")§r" + suffix));
|
||||
}
|
||||
if (Plot.all().isEmpty()) {
|
||||
add(new Header(this, "§8 (no plots defined yet)"));
|
||||
}
|
||||
add(new Header(this, ""));
|
||||
|
||||
// Section 2: Exchange rates
|
||||
add(new Header(this,"§e=== Exchange Rates ==="));
|
||||
// ─── 3. Exchange Rates ─────────────────────────────────────
|
||||
add(new Header(this, "§e=== Exchange Rates ==="));
|
||||
add(new Header(this, "§7Visit the Exchange Clerk at the Exchange"));
|
||||
int fee = ClientBnsState.effectiveFeePct;
|
||||
add(new Header(this,"§7 Your tier fee: §c" + fee + "%§r"));
|
||||
add(new Header(this, "§7Your 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));
|
||||
String line = String.format(" %s§r §7list %d§r → §a%d sp/unit§r", displayName(id), base, current);
|
||||
add(new Header(this, line));
|
||||
}
|
||||
add(new Header(this, ""));
|
||||
|
||||
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,"§8 none — visit the Plot Office at spawn"));
|
||||
// ─── 4. Civic Tier ─────────────────────────────────────────
|
||||
add(new Header(this, "§e=== Civic Tier ==="));
|
||||
add(new Header(this, "§7Visit the Civic Registrar at the Town Hall"));
|
||||
CivicTier cur = CivicTier.byId(ClientBnsState.tier);
|
||||
add(new Header(this, " Current: §" + cur.colour().getChar() + cur.name() + "§r §7(tier "
|
||||
+ cur.id() + "/" + CivicTier.MAX + ")§r"));
|
||||
if (cur.id() < CivicTier.MAX) {
|
||||
CivicTier next = CivicTier.byId(cur.id() + 1);
|
||||
add(new Header(this, " Next: §" + next.colour().getChar() + next.name()
|
||||
+ "§r — §6" + next.cost() + " sp§r"));
|
||||
} 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"));
|
||||
}
|
||||
add(new Header(this, " §8(you have reached the highest civic rank)§r"));
|
||||
}
|
||||
add(new Header(this, ""));
|
||||
|
||||
add(new Header(this,""));
|
||||
add(new Header(this,"§8FTB Quests is a separate mod — open your Quest Book item for chapters."));
|
||||
// ─── 5. Your Status ────────────────────────────────────────
|
||||
add(new Header(this, "§e=== Your Status ==="));
|
||||
int activeBounties = ClientBnsState.activeBounties.size();
|
||||
add(new Header(this, " Active bounties: §f" + activeBounties + " / " + ClientBnsState.bountySlotCap));
|
||||
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(), 10);
|
||||
add(new Header(this, " " + b.name() + " " + bar + " §7"
|
||||
+ ab.progress() + "/" + ab.target() + "§r"));
|
||||
}
|
||||
add(new Header(this, " Owned plots: §f" + mine + " / " + ClientBnsState.plotSlotCap));
|
||||
add(new Header(this, " Wealth: §6" + (ClientBnsState.cashOnHand + ClientBnsState.bankBalance)
|
||||
+ " sp§r §7(cash " + ClientBnsState.cashOnHand
|
||||
+ " + bank " + ClientBnsState.bankBalance + ")§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() {
|
||||
@@ -138,9 +183,7 @@ public final class QuestLogScreen extends BaseBnsScreen {
|
||||
}
|
||||
|
||||
private static String displayName(ResourceLocation id) {
|
||||
String path = id.getPath();
|
||||
// Friendly-cased name from the id: "ancient_debris" -> "Ancient Debris"
|
||||
String[] parts = path.split("_");
|
||||
String[] parts = id.getPath().split("_");
|
||||
StringBuilder out = new StringBuilder();
|
||||
for (int i = 0; i < parts.length; i++) {
|
||||
if (parts[i].isEmpty()) continue;
|
||||
@@ -159,5 +202,4 @@ public final class QuestLogScreen extends BaseBnsScreen {
|
||||
theme.drawString(gfx, Component.literal(text), x + 2, y + 2, Color4I.WHITE, Theme.SHADOW);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user