diff --git a/gradle.properties b/gradle.properties index 1ed3a20..31256ee 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 diff --git a/src/main/java/uk/sijbers/bnstoolkit/client/screens/QuestLogScreen.java b/src/main/java/uk/sijbers/bnstoolkit/client/screens/QuestLogScreen.java index f4efd27..51249c5 100644 --- a/src/main/java/uk/sijbers/bnstoolkit/client/screens/QuestLogScreen.java +++ b/src/main/java/uk/sijbers/bnstoolkit/client/screens/QuestLogScreen.java @@ -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. * - *

Three sections, top to bottom: + *

Read-only. Five sections, top to bottom: *

* - *

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. + *

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. + * + *

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 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 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 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); } } - }