From ca9bda65ea89ec159c15a54e7c96b802e3810875 Mon Sep 17 00:00:00 2001
From: oglassdev <23040613+oglassdev@users.noreply.github.com>
Date: Sun, 11 Oct 2026 09:09:44 -0400
Subject: [PATCH 1/4] feat(example): add a themed workshop window and show more
of the authoring API
The workshop is a second window under its own theme: a repeater of upgrade
cards with indexed handles, a value-driven switch holding each card's level
bar, a choice group, an opt-in hover hotspot, and a catalog sprite slot tinted
by the shared tone. The shop opens it from a new Workshop button, and
/upgrades opens it directly.
The pack now reports its output size against a budget, blanks vanilla's
experience bar and draws the wave bar in its place, and writes the clock HUD
with the raw API.
---
example/pack/rpp.config.ts | 3 +-
example/pack/src/window/art.ts | 17 +
example/pack/src/window/clock_hud.ts | 20 +
example/pack/src/window/handles.ts | 36 +-
example/pack/src/window/icons/rank_1.png | Bin 0 -> 113 bytes
example/pack/src/window/icons/rank_2.png | Bin 0 -> 129 bytes
example/pack/src/window/icons/rank_3.png | Bin 0 -> 125 bytes
example/pack/src/window/index.ts | 41 +-
example/pack/src/window/shop.tsx | 8 +-
example/pack/src/window/status_hud.tsx | 43 +-
example/pack/src/window/upgrades.tsx | 162 ++
.../com/chunkzero/window/example/HudDebug.kt | 1 +
.../com/chunkzero/window/example/Main.kt | 9 +-
.../com/chunkzero/window/example/Market.kt | 3 +-
.../com/chunkzero/window/example/MyShop.kt | 4 +
.../chunkzero/window/example/MyStatusHud.kt | 46 +-
.../chunkzero/window/example/MyUpgrades.kt | 135 ++
.../com/chunkzero/window/example/TonePin.kt | 2 +-
.../com/chunkzero/window/example/Workshop.kt | 47 +
.../window/example/generated/ShopView.kt | 4 +
.../generated/StatusBottomCenterHud.kt | 4 -
.../example/generated/StatusWaveBarHud.kt | 26 +
.../window/example/generated/UpgradesView.kt | 178 ++
.../example/generated/WindowDefinitions.kt | 1 +
.../window/example/generated/WindowEntries.kt | 1604 ++++++++++++++++-
.../window/example/generated/WindowFonts.kt | 9 +-
.../example/generated/WindowHudDefinitions.kt | 1 +
.../example/generated/WindowHudEntries.kt | 126 +-
.../window/example/generated/WindowSprite.kt | 3 +
.../window/example/generated/WindowSprites.kt | 24 +
30 files changed, 2420 insertions(+), 137 deletions(-)
create mode 100644 example/pack/src/window/clock_hud.ts
create mode 100644 example/pack/src/window/icons/rank_1.png
create mode 100644 example/pack/src/window/icons/rank_2.png
create mode 100644 example/pack/src/window/icons/rank_3.png
create mode 100644 example/pack/src/window/upgrades.tsx
create mode 100644 jvm/example/src/main/kotlin/com/chunkzero/window/example/MyUpgrades.kt
create mode 100644 jvm/example/src/main/kotlin/com/chunkzero/window/example/Workshop.kt
create mode 100644 jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/StatusWaveBarHud.kt
create mode 100644 jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/UpgradesView.kt
diff --git a/example/pack/rpp.config.ts b/example/pack/rpp.config.ts
index b1ab509a..262c3856 100644
--- a/example/pack/rpp.config.ts
+++ b/example/pack/rpp.config.ts
@@ -17,7 +17,8 @@ export default defineConfig({
window({
namespace: "window",
coreShaders: { hud: true, hover: true },
- vanilla: { anvil: { field: "search_field" }, slotHighlight: false },
+ vanilla: { anvil: { field: "search_field" }, hud: { experience: false }, slotHighlight: false },
+ size: { report: true, budget: 1_000_000 },
previews: { output: "build/previews" },
kotlin: {
package: "com.chunkzero.window.example.generated",
diff --git a/example/pack/src/window/art.ts b/example/pack/src/window/art.ts
index 97ef33cc..f538f638 100644
--- a/example/pack/src/window/art.ts
+++ b/example/pack/src/window/art.ts
@@ -63,6 +63,23 @@ export const budgetFill = shape({
inset_depth: 0,
});
+/** The plate a maxed upgrade shows in place of its level bar. */
+export const maxedPlate = shape({
+ kind: "panel",
+ fill: "#ffb20b",
+ border_color: "#8d3c03",
+ border_width: 1,
+ radius: 2,
+ inset_depth: 0,
+});
+
+/** White art for the `rank` sprite slot, which a tint colors. */
+export const ranks = {
+ rank_1: texture("window/icons/rank_1.png"),
+ rank_2: texture("window/icons/rank_2.png"),
+ rank_3: texture("window/icons/rank_3.png"),
+};
+
export const icons = {
back: texture("window/icons/back.png"),
clear: texture("window/icons/clear.png"),
diff --git a/example/pack/src/window/clock_hud.ts b/example/pack/src/window/clock_hud.ts
new file mode 100644
index 00000000..bed7a8d9
--- /dev/null
+++ b/example/pack/src/window/clock_hud.ts
@@ -0,0 +1,20 @@
+import { text } from "plugin:window/bind";
+import * as raw from "plugin:window/raw";
+import { art } from "plugin:window/theme/industrial";
+
+export const runtime = text("runtime");
+
+/** The clock, written with the function-style `raw` API; it builds the same document as a ``. */
+export default raw.hud({
+ name: "status_top_center",
+ frame: art.hud,
+ shader: { origin: { x: 0.5, y: 0 }, anchor: { x: 0.5, y: 0 }, x: 0, y: 4 },
+ children: [
+ raw.box({
+ style: { direction: "column", padding: 3 },
+ children: [
+ raw.text(runtime, { width: 34, align: "center", color: "#ffffff", shadow: true, small_caps: true }),
+ ],
+ }),
+ ],
+});
diff --git a/example/pack/src/window/handles.ts b/example/pack/src/window/handles.ts
index 71d4b22b..5f1411da 100644
--- a/example/pack/src/window/handles.ts
+++ b/example/pack/src/window/handles.ts
@@ -1,6 +1,19 @@
-import { action, collection, flag, fraction, input, selection, text, tint, toggle } from "plugin:window/bind";
+import {
+ action,
+ collection,
+ flag,
+ fraction,
+ input,
+ items,
+ selection,
+ sprite,
+ text,
+ tint,
+ toggle,
+ value,
+} from "plugin:window/bind";
-// Shared by the shop and the status HUD
+// Shared by the shop, the upgrades window, and the status HUD
export const tone = tint("tone", { ok: "#3ddc84", warn: "#ffb020", danger: "#ff4d4d" });
// Shop
@@ -36,8 +49,27 @@ export const balance = text("balance");
export const buy = action("buy");
export const canBuy = flag("can_buy");
export const buyLabel = text("buy_label");
+export const openUpgrades = action("open_upgrades");
// Catalog search
export const query = input("query");
export const back = action("back");
export const confirm = action("confirm");
+
+// Upgrades; `status` and `balance` are the shop's handles
+export const UPGRADES = 3;
+const cards = { shape: [UPGRADES] } as const;
+export const upgrade = action("upgrade", cards);
+export const upgradeIcon = items("upgrade_icon", cards);
+export const upgradeName = text("upgrade_name", cards);
+export const upgradeLevel = text("upgrade_level", cards);
+export const upgradeCost = text("upgrade_cost", cards);
+/** How far each upgrade is toward its last level. */
+export const upgradeProgress = fraction("upgrade_progress", cards);
+export const upgradeStage = value("upgrade_stage", ["new", "open", "maxed"], cards);
+
+export const amount = selection("amount", ["one", "five", "max"]);
+/** The workshop's rank badge, one of the catalog sprites `index.ts` registers. */
+export const rank = sprite("rank", { only: ["rank_1", "rank_2", "rank_3"] });
+export const rankName = text("rank_name");
+export const openShop = action("open_shop");
diff --git a/example/pack/src/window/icons/rank_1.png b/example/pack/src/window/icons/rank_1.png
new file mode 100644
index 0000000000000000000000000000000000000000..d5772dbae37c03e602438e2e96c76ac64e0491d6
GIT binary patch
literal 113
zcmeAS@N?(olHy`uVBq!ia0vp^oIuRY!3HGPMlP2IQWl;rjv*Ddj`kVyF(~jbEnNTT
ze{g+kx89OYF^~29;;iFWaUuh8k+yKHRkb8qf>|
MPgg&ebxsLQ0EvSmKmY&$
literal 0
HcmV?d00001
diff --git a/example/pack/src/window/icons/rank_2.png b/example/pack/src/window/icons/rank_2.png
new file mode 100644
index 0000000000000000000000000000000000000000..3bdd32325b8037ab6d96d1584de0766fad5352e0
GIT binary patch
literal 129
zcmeAS@N?(olHy`uVBq!ia0vp^oIuRY!3HGPMlP2IQXZZzjv*DdmiBKHJYc}XbTByc
zzgP6bW6Qfc-zY6~IWWILf0D{~H}MY-J}`K0>yG-gRV!^7gM!kJoXKl$7ryO!y@&Vt
e`uzp_O&Nd5M-?_l9ZUzB$>8bg=d#Wzp$PzG?J#%%
literal 0
HcmV?d00001
diff --git a/example/pack/src/window/icons/rank_3.png b/example/pack/src/window/icons/rank_3.png
new file mode 100644
index 0000000000000000000000000000000000000000..253d8143ac970bc44f51ed983e3f23af1f62f803
GIT binary patch
literal 125
zcmeAS@N?(olHy`uVBq!ia0vp^oIuRY!3HGPMlP2IQZAk@jv*Dd&Q37oV^H8>IvD=-
ze{g+kx0%-~J!Mus&4fuR-`&JNJov!ixve|u(^jptWef^R727
- ,
-
-
-
- ,
+
+);
+const topRight = (
- ,
+
+);
+const leftSide = (
- ,
+
+);
+const rightSide = (
- ,
+
+);
+export const bottomCenter = (
-
{Array.from({ length: 10 }, (_, i) => (
@@ -99,5 +101,18 @@ export default [
))}
- ,
-];
+
+);
+
+/**
+ * Window's own bar where vanilla draws the experience bar, which `vanilla.hud.experience` blanks: 182x5, centered,
+ * its top 29 px above the bottom of the GUI.
+ */
+export const waveBar = (
+
+
+
+);
+
+/** The status HUDs, one per anchor. */
+export default [topLeft, clock, topRight, leftSide, rightSide, bottomCenter, waveBar];
diff --git a/example/pack/src/window/upgrades.tsx b/example/pack/src/window/upgrades.tsx
new file mode 100644
index 00000000..44569c31
--- /dev/null
+++ b/example/pack/src/window/upgrades.tsx
@@ -0,0 +1,162 @@
+import { Bar, Box, Case, Image, Switch, Text, create, createTheme, defineVars, variants } from "plugin:window/ui";
+import {
+ Button,
+ Center,
+ Choice,
+ Column,
+ Container,
+ Grid,
+ Hotbar,
+ Hotspot,
+ Player,
+ Repeater,
+ Row,
+ Spacer,
+ Window,
+ art,
+ colors,
+} from "plugin:window/theme/industrial";
+
+import { barTrack, coin, maxedPlate, toneFill } from "./art.ts";
+import {
+ UPGRADES,
+ amount,
+ balance,
+ openShop,
+ rank,
+ rankName,
+ status,
+ tone,
+ upgrade,
+ upgradeCost,
+ upgradeIcon,
+ upgradeLevel,
+ upgradeName,
+ upgradeProgress,
+ upgradeStage,
+} from "./handles.ts";
+
+/** Industrial's colors, recolored for everything in this window. */
+const forge = createTheme(colors, {
+ border: "#1a0d08",
+ highlight: "#e8a36b",
+ shell: "#3a2620",
+ panel: "#8a4b2c",
+ recess: "#24140f",
+ slot: "#5a3220",
+ button: "#b3663a",
+ selected: "#5a2c18",
+});
+
+const ink = defineVars({ name: "#ffe2b8", muted: "#f2c9a0", gold: "#ffb20b", pad: 4 });
+
+const s = create({
+ card: variants({ cell: { frame: art.panel, gap: 2 } }),
+ amount: variants({
+ base: { text: { color: ink.name } },
+ selected: { frame: art.buttonAccent, text: { color: "#2a1200", shadow: false } },
+ }),
+ strip: { frame: art.recess, padding: ink.pad, gap: 4 },
+ name: { color: ink.name, align: "center" },
+ muted: { color: ink.muted },
+});
+
+/** What the next purchase of upgrade `index` costs. */
+function Cost(props: { index: number }) {
+ return (
+
+
+
+
+ );
+}
+
+const amounts = {
+ one: { label: "x1", tooltip: "Buy one level" },
+ five: { label: "x5", tooltip: "Buy five levels" },
+ max: { label: "Max", tooltip: "Buy every level you can afford" },
+};
+
+export default (
+
+
+
+ {(i) => [
+ ,
+
+
+
+
+
+
+ New
+
+
+
+
+
+
+
+
+
+
+
+
+ Maxed
+
+
+
+
+ ,
+ ]}
+
+
+
+
+ {amount.values.map((value) => (
+
+ {amounts[value].label}
+
+ ))}
+
+
+
+ {/* It takes no clicks, so `hover` opts it into the hover outline. */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Shop
+
+
+
+ Exit
+
+
+
+);
diff --git a/jvm/example/src/main/kotlin/com/chunkzero/window/example/HudDebug.kt b/jvm/example/src/main/kotlin/com/chunkzero/window/example/HudDebug.kt
index d84dc6d3..cba814c1 100644
--- a/jvm/example/src/main/kotlin/com/chunkzero/window/example/HudDebug.kt
+++ b/jvm/example/src/main/kotlin/com/chunkzero/window/example/HudDebug.kt
@@ -13,6 +13,7 @@ private val HUD_DEBUG_DEFINITIONS =
WindowHudDefinitions.statusLeftSide,
WindowHudDefinitions.statusRightSide,
WindowHudDefinitions.statusBottomCenter,
+ WindowHudDefinitions.statusWaveBar,
)
internal fun sendHudSpriteDebug(player: Player) {
diff --git a/jvm/example/src/main/kotlin/com/chunkzero/window/example/Main.kt b/jvm/example/src/main/kotlin/com/chunkzero/window/example/Main.kt
index 2f0d08a0..dd81f2e1 100644
--- a/jvm/example/src/main/kotlin/com/chunkzero/window/example/Main.kt
+++ b/jvm/example/src/main/kotlin/com/chunkzero/window/example/Main.kt
@@ -33,7 +33,8 @@ import java.nio.file.Path
* jvm/gradlew -p jvm :example:run
* ```
*
- * Then connect a Minecraft 26.2 client and the shop opens on spawn. Reopen it with `/shop` or the hotbar emerald.
+ * Then connect a Minecraft 26.2 client and the shop opens on spawn. Reopen it with `/shop` or the hotbar emerald, and
+ * open the workshop with `/upgrades` or the shop's Workshop button.
*/
fun main() {
val server = MinecraftServer.init()
@@ -114,11 +115,14 @@ private val SHOP_ITEM by lazy {
.build()
}
-/** Reopens the shop from `/shop` or by using the hotbar emerald handed out on first spawn. */
+/** Reopens the shop from `/shop` or by using the hotbar emerald handed out on first spawn; `/upgrades` opens the workshop. */
private fun registerShopAccess(market: Market) {
val command = Command("shop")
command.setDefaultExecutor { sender, _ -> (sender as? Player)?.let { openShop(it, market) } }
MinecraftServer.getCommandManager().register(command)
+ val upgrades = Command("upgrades")
+ upgrades.setDefaultExecutor { sender, _ -> (sender as? Player)?.let { MyUpgrades(it, market).open() } }
+ MinecraftServer.getCommandManager().register(upgrades)
MinecraftServer.getGlobalEventHandler().addListener(PlayerUseItemEvent::class.java) { event ->
if (event.itemStack.getTag(SHOP_ITEM_TAG) == true) openShop(event.player, market)
}
@@ -176,6 +180,7 @@ private fun createStatusHuds(
MyStatusLeftSideHud(player),
MyStatusRightSideHud(market),
MyStatusBottomCenterHud(player, startedAt),
+ MyStatusWaveBarHud(player, startedAt),
)
/** Locate the built resource-pack zip, or use `-Dwindow.pack=/path/to/window-example.zip`. */
diff --git a/jvm/example/src/main/kotlin/com/chunkzero/window/example/Market.kt b/jvm/example/src/main/kotlin/com/chunkzero/window/example/Market.kt
index 37f0cb87..3190ed34 100644
--- a/jvm/example/src/main/kotlin/com/chunkzero/window/example/Market.kt
+++ b/jvm/example/src/main/kotlin/com/chunkzero/window/example/Market.kt
@@ -4,12 +4,13 @@ import net.minestom.server.entity.Player
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
-/** Small in-memory catalog and wallet used by the visual example. */
+/** Small in-memory catalog, wallet, and workshop used by the visual example. */
class Market(
val unitPrice: Int = 10,
private val startingBalance: Int = 2_500,
) {
val products: List = MARKET_CATALOG
+ val workshop = Workshop()
private val balances = ConcurrentHashMap()
diff --git a/jvm/example/src/main/kotlin/com/chunkzero/window/example/MyShop.kt b/jvm/example/src/main/kotlin/com/chunkzero/window/example/MyShop.kt
index 8c99b31f..1d4571a6 100644
--- a/jvm/example/src/main/kotlin/com/chunkzero/window/example/MyShop.kt
+++ b/jvm/example/src/main/kotlin/com/chunkzero/window/example/MyShop.kt
@@ -100,6 +100,10 @@ class MyShop(
filtersChanged()
}
+ override fun onOpenUpgrades(click: Click) {
+ MyUpgrades(player, market).open()
+ }
+
override fun onBuy(click: Click) {
val product = catalog.selected ?: return
val newBalance = market.purchase(player, product.price)
diff --git a/jvm/example/src/main/kotlin/com/chunkzero/window/example/MyStatusHud.kt b/jvm/example/src/main/kotlin/com/chunkzero/window/example/MyStatusHud.kt
index 88fe8868..99ead76e 100644
--- a/jvm/example/src/main/kotlin/com/chunkzero/window/example/MyStatusHud.kt
+++ b/jvm/example/src/main/kotlin/com/chunkzero/window/example/MyStatusHud.kt
@@ -6,6 +6,7 @@ import com.chunkzero.window.example.generated.StatusRightSideHud
import com.chunkzero.window.example.generated.StatusTopCenterHud
import com.chunkzero.window.example.generated.StatusTopLeftHud
import com.chunkzero.window.example.generated.StatusTopRightHud
+import com.chunkzero.window.example.generated.StatusWaveBarHud
import net.kyori.adventure.text.Component
import net.minestom.server.entity.Player
@@ -63,27 +64,38 @@ class MyStatusBottomCenterHud(
private val player: Player,
private val startedAt: Long,
) : StatusBottomCenterHud() {
- override fun hint(): Component = Component.text("Next wave in ${(remaining() + 999) / 1000}s")
-
- /** Turns from ok to warn to danger as the wave nears, unless `/tone` pinned one. */
- override fun tone(): Tone {
- val pinned = pinnedTone(player)
- if (pinned != null) return Tone.entries.first { it.value == pinned }
- val remaining = remaining()
- return when {
- remaining > 6_000L -> Tone.OK
- remaining > 3_000L -> Tone.WARN
- else -> Tone.DANGER
- }
- }
-
- /** Fills as the wave nears, unless `/bar` pinned a fraction. */
- override fun waveCharge(): Float = pinnedBar(player) ?: (1f - remaining().toFloat() / WAVE_MILLIS)
+ override fun hint(): Component = Component.text("Next wave in ${(waveRemaining(startedAt) + 999) / 1000}s")
- private fun remaining(): Long = WAVE_MILLIS - (System.currentTimeMillis() - startedAt) % WAVE_MILLIS
+ override fun tone(): Tone = Tone.entries.first { it.value == waveTone(player, startedAt) }
override fun waveProgress(index: Int): Boolean =
(System.currentTimeMillis() - startedAt) % WAVE_MILLIS * 10 / WAVE_MILLIS >= index
}
+/** Window's bar in place of vanilla's experience bar. */
+class MyStatusWaveBarHud(
+ private val player: Player,
+ private val startedAt: Long,
+) : StatusWaveBarHud() {
+ override fun tone(): Tone = Tone.entries.first { it.value == waveTone(player, startedAt) }
+
+ /** Fills as the wave nears, unless `/bar` pinned a fraction. */
+ override fun waveCharge(): Float = pinnedBar(player) ?: (1f - waveRemaining(startedAt).toFloat() / WAVE_MILLIS)
+}
+
+private fun waveRemaining(startedAt: Long): Long = WAVE_MILLIS - (System.currentTimeMillis() - startedAt) % WAVE_MILLIS
+
+/** The `tone` entry: from ok to warn to danger as the wave nears, unless `/tone` pinned one. */
+private fun waveTone(
+ player: Player,
+ startedAt: Long,
+): String {
+ val remaining = waveRemaining(startedAt)
+ return pinnedTone(player) ?: when {
+ remaining > 6_000L -> "ok"
+ remaining > 3_000L -> "warn"
+ else -> "danger"
+ }
+}
+
private const val WAVE_MILLIS = 10_000L
diff --git a/jvm/example/src/main/kotlin/com/chunkzero/window/example/MyUpgrades.kt b/jvm/example/src/main/kotlin/com/chunkzero/window/example/MyUpgrades.kt
new file mode 100644
index 00000000..4a1f7d1a
--- /dev/null
+++ b/jvm/example/src/main/kotlin/com/chunkzero/window/example/MyUpgrades.kt
@@ -0,0 +1,135 @@
+package com.chunkzero.window.example
+
+import com.chunkzero.window.Click
+import com.chunkzero.window.IndexedClick
+import com.chunkzero.window.example.generated.UpgradesView
+import net.kyori.adventure.text.Component
+import net.kyori.adventure.text.format.NamedTextColor
+import net.kyori.adventure.text.format.TextDecoration
+import net.minestom.server.entity.Player
+import net.minestom.server.item.ItemStack
+
+/** The workshop: upgrade cards bought with the [market]'s coins, in the amount the player chose. */
+class MyUpgrades(
+ player: Player,
+ private val market: Market,
+) : UpgradesView(player) {
+ private var coins by state(0)
+ private var levels by state(market.workshop.levelsOf(player))
+ private var feedback by state(null)
+
+ override fun onOpen() {
+ coins = market.balanceOf(player)
+ }
+
+ override fun balance(): Component = Component.text(coins)
+
+ override fun upgradeIcon(index: Int): ItemStack {
+ val upgrade = Upgrade.entries[index]
+ return ItemStack
+ .builder(upgrade.material)
+ .customName(Component.text(upgrade.title, NamedTextColor.GOLD).decoration(TextDecoration.ITALIC, false))
+ .lore(Component.text("Click to upgrade", NamedTextColor.GRAY).decoration(TextDecoration.ITALIC, false))
+ .build()
+ }
+
+ override fun upgradeName(index: Int): Component = Component.text(Upgrade.entries[index].title)
+
+ override fun upgradeLevel(index: Int): Component = Component.text("Lv ${levels[index]}/${Upgrade.MAX_LEVEL}")
+
+ override fun upgradeProgress(index: Int): Float = levels[index].toFloat() / Upgrade.MAX_LEVEL
+
+ override fun upgradeStage(index: Int): UpgradeStage =
+ when (levels[index]) {
+ 0 -> UpgradeStage.NEW
+ Upgrade.MAX_LEVEL -> UpgradeStage.MAXED
+ else -> UpgradeStage.OPEN
+ }
+
+ /** The offer's price, red while the balance does not cover it. */
+ override fun upgradeCost(index: Int): Component {
+ val offer = offer(index) ?: return Component.empty()
+ return if (offer.price <=
+ coins
+ ) {
+ Component.text(offer.price)
+ } else {
+ Component.text(offer.price, NamedTextColor.RED)
+ }
+ }
+
+ override fun onUpgrade(click: IndexedClick) {
+ val upgrade = Upgrade.entries[click.index]
+ val offer = offer(click.index)
+ if (offer == null) {
+ feedback = "${upgrade.title} is maxed"
+ return
+ }
+ val newBalance = market.purchase(player, offer.price)
+ if (newBalance == null) {
+ feedback = "Need ${offer.price - coins} more"
+ } else {
+ coins = newBalance
+ levels = market.workshop.raise(player, upgrade, offer.count)
+ feedback = "Bought ${upgrade.title} x${offer.count}"
+ }
+ }
+
+ override fun onAmountChanged(value: Amount) {
+ feedback = null
+ }
+
+ override fun rankSprite(): RankSprite = RankSprite.entries[rank()]
+
+ override fun rankName(): Component = Component.text(RANKS[rank()])
+
+ override fun status(): Component = Component.text(feedback ?: "${levels.sum()} of $TOTAL_LEVELS levels")
+
+ /** Whether any offer is affordable, or warn once everything is maxed, unless `/tone` pinned a tone. */
+ override fun tone(): Tone {
+ val pinned = pinnedTone(player)
+ if (pinned != null) return Tone.entries.first { it.value == pinned }
+ val offers = Upgrade.entries.indices.mapNotNull(::offer)
+ return when {
+ offers.isEmpty() -> Tone.WARN
+ offers.any { it.price <= coins } -> Tone.OK
+ else -> Tone.DANGER
+ }
+ }
+
+ override fun onOpenShop(click: Click) {
+ MyShop(player, market).open()
+ }
+
+ /** One rank per [Upgrade.MAX_LEVEL] levels bought. */
+ private fun rank(): Int = minOf(levels.sum() / Upgrade.MAX_LEVEL, RANKS.lastIndex)
+
+ /**
+ * What a click on upgrade [index] buys, or `null` when it is maxed. `Max` buys as many levels as the balance
+ * covers, and prices one level when it covers none.
+ */
+ private fun offer(index: Int): Offer? {
+ val upgrade = Upgrade.entries[index]
+ val level = levels[index]
+ val left = Upgrade.MAX_LEVEL - level
+ if (left == 0) return null
+ var count =
+ when (amount) {
+ Amount.ONE -> 1
+ Amount.FIVE -> minOf(5, left)
+ Amount.MAX -> left
+ }
+ if (amount == Amount.MAX) {
+ while (count > 1 && upgrade.cost(level, count) > coins) count--
+ }
+ return Offer(count, upgrade.cost(level, count))
+ }
+
+ private data class Offer(
+ val count: Int,
+ val price: Int,
+ )
+}
+
+private val RANKS = listOf("Apprentice", "Journeyman", "Master")
+private val TOTAL_LEVELS = Upgrade.entries.size * Upgrade.MAX_LEVEL
diff --git a/jvm/example/src/main/kotlin/com/chunkzero/window/example/TonePin.kt b/jvm/example/src/main/kotlin/com/chunkzero/window/example/TonePin.kt
index ffa2f651..b5c19001 100644
--- a/jvm/example/src/main/kotlin/com/chunkzero/window/example/TonePin.kt
+++ b/jvm/example/src/main/kotlin/com/chunkzero/window/example/TonePin.kt
@@ -14,7 +14,7 @@ internal fun pinnedTone(player: Player): String? = player.getTag(TONE_TAG)
/**
* Registers `/tone `, a debug command pinning the tone the caller's status HUD draws in
- * from its next send, and the shops the caller opens afterwards; `auto` unpins it.
+ * from its next send, and the windows the caller opens afterwards; `auto` unpins it.
*/
internal fun registerToneCommand() {
val tone = ArgumentType.Word("tone").from("ok", "warn", "danger", AUTO)
diff --git a/jvm/example/src/main/kotlin/com/chunkzero/window/example/Workshop.kt b/jvm/example/src/main/kotlin/com/chunkzero/window/example/Workshop.kt
new file mode 100644
index 00000000..6e869ac2
--- /dev/null
+++ b/jvm/example/src/main/kotlin/com/chunkzero/window/example/Workshop.kt
@@ -0,0 +1,47 @@
+package com.chunkzero.window.example
+
+import net.minestom.server.entity.Player
+import net.minestom.server.item.Material
+import java.util.UUID
+import java.util.concurrent.ConcurrentHashMap
+
+/** A foundry upgrade; each level costs [baseCost] more than the one before. */
+enum class Upgrade(
+ val title: String,
+ val material: Material,
+ private val baseCost: Int,
+) {
+ DRILL("Drill", Material.PISTON, 60),
+ FURNACE("Furnace", Material.BLAST_FURNACE, 90),
+ CONVEYOR("Conveyor", Material.POWERED_RAIL, 120),
+ ;
+
+ /** What the next [count] levels above [level] cost together. */
+ fun cost(
+ level: Int,
+ count: Int,
+ ): Int = (1..count).sumOf { baseCost * (level + it) }
+
+ companion object {
+ const val MAX_LEVEL = 5
+ }
+}
+
+/** Each player's upgrade levels, kept for the server's lifetime. */
+class Workshop {
+ private val levels = ConcurrentHashMap>()
+
+ /** The level of every [Upgrade], in declaration order. */
+ fun levelsOf(player: Player): List = levels[player.uuid] ?: Upgrade.entries.map { 0 }
+
+ fun raise(
+ player: Player,
+ upgrade: Upgrade,
+ count: Int,
+ ): List {
+ val raised = levelsOf(player).toMutableList()
+ raised[upgrade.ordinal] = (raised[upgrade.ordinal] + count).coerceAtMost(Upgrade.MAX_LEVEL)
+ levels[player.uuid] = raised
+ return raised
+ }
+}
diff --git a/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/ShopView.kt b/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/ShopView.kt
index b9dc2561..fd7ae94a 100644
--- a/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/ShopView.kt
+++ b/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/ShopView.kt
@@ -76,6 +76,9 @@ public abstract class ShopView(protected val player: Player) : WindowView(WindowDefinitions.upgrades, MinestomHost(player)) {
+ /** The values of `amount`. */
+ public enum class Amount(public val value: String) {
+ ONE("one"),
+ FIVE("five"),
+ MAX("max"),
+ }
+
+ /** The `amount` selection. Clicks change it, then call [onAmountChanged]. */
+ protected var amount: Amount by state(Amount.ONE)
+
+ /** Called after a click changes `amount`; assigning it does not call this. */
+ protected open fun onAmountChanged(value: Amount) {}
+
+ /** Render the `balance` text. */
+ protected abstract fun balance(): Component
+
+ /** Handle a `open_shop` click. */
+ protected abstract fun onOpenShop(click: Click)
+
+ /** The sprites `rank` shows. */
+ public enum class RankSprite(public val sprite: WindowSprite) {
+ RANK_1(WindowSprite.RANK_1),
+ RANK_2(WindowSprite.RANK_2),
+ RANK_3(WindowSprite.RANK_3),
+ }
+
+ /** Render the `rank` sprite. */
+ protected abstract fun rankSprite(): RankSprite?
+
+ /** Render the `rank_name` text. */
+ protected abstract fun rankName(): Component
+
+ /** Render the `status` text. */
+ protected abstract fun status(): Component
+
+ /** The values of `tone`. */
+ public enum class Tone(public val value: String) {
+ OK("ok"),
+ WARN("warn"),
+ DANGER("danger"),
+ }
+
+ /** The palette entry the `tone` tint draws in. */
+ protected abstract fun tone(): Tone
+
+ /** Handle a `upgrade` click; `click.index` is the clicked entry, row-major. */
+ protected abstract fun onUpgrade(click: IndexedClick)
+
+ /** Render the `upgrade_cost` text. */
+ protected abstract fun upgradeCost(index: Int): Component
+
+ /** Render the `upgrade_icon` item. */
+ protected abstract fun upgradeIcon(index: Int): ItemStack?
+
+ /** Render the `upgrade_level` text. */
+ protected abstract fun upgradeLevel(index: Int): Component
+
+ /** Render the `upgrade_name` text. */
+ protected abstract fun upgradeName(index: Int): Component
+
+ /** How much of the `upgrade_progress` bar is filled, from 0 to 1. */
+ protected abstract fun upgradeProgress(index: Int): Float
+
+ /** The values of `upgrade_stage`. */
+ public enum class UpgradeStage(public val value: String) {
+ NEW("new"),
+ OPEN("open"),
+ MAXED("maxed"),
+ }
+
+ /** The `upgrade_stage` value. */
+ protected abstract fun upgradeStage(index: Int): UpgradeStage
+
+ final override fun WindowScope.bind() {
+ switch("amount?one") { (amount == Amount.ONE).toString() }
+ button("amount=one") { _ ->
+ if (amount != Amount.ONE) {
+ amount = Amount.ONE
+ onAmountChanged(amount)
+ }
+ }
+ button("amount=one~2") { _ ->
+ if (amount != Amount.ONE) {
+ amount = Amount.ONE
+ onAmountChanged(amount)
+ }
+ }
+ switch("amount?five") { (amount == Amount.FIVE).toString() }
+ button("amount=five") { _ ->
+ if (amount != Amount.FIVE) {
+ amount = Amount.FIVE
+ onAmountChanged(amount)
+ }
+ }
+ button("amount=five~2") { _ ->
+ if (amount != Amount.FIVE) {
+ amount = Amount.FIVE
+ onAmountChanged(amount)
+ }
+ }
+ switch("amount?max") { (amount == Amount.MAX).toString() }
+ button("amount=max") { _ ->
+ if (amount != Amount.MAX) {
+ amount = Amount.MAX
+ onAmountChanged(amount)
+ }
+ }
+ button("amount=max~2") { _ ->
+ if (amount != Amount.MAX) {
+ amount = Amount.MAX
+ onAmountChanged(amount)
+ }
+ }
+ slot("balance") { balance() }
+ button("open_shop", ::onOpenShop)
+ sprite("rank") { rankSprite()?.sprite?.id }
+ slot("rank_name") { rankName() }
+ slot("status") { status() }
+ tint("tone") { tone().value }
+ for (index in 0 until UPGRADE_SIZE) {
+ button("upgrade[$index]") { click ->
+ onUpgrade(IndexedClick(click.slot, index, click.shift, click.right))
+ }
+ }
+ slot("upgrade_cost[0]") { upgradeCost(0) }
+ slot("upgrade_cost[0]~2") { upgradeCost(0) }
+ slot("upgrade_cost[1]") { upgradeCost(1) }
+ slot("upgrade_cost[1]~2") { upgradeCost(1) }
+ slot("upgrade_cost[2]") { upgradeCost(2) }
+ slot("upgrade_cost[2]~2") { upgradeCost(2) }
+ for (index in 0 until UPGRADE_ICON_SIZE) {
+ item("upgrade_icon[$index]") { upgradeIcon(index) }
+ }
+ for (index in 0 until UPGRADE_LEVEL_SIZE) {
+ slot("upgrade_level[$index]") { upgradeLevel(index) }
+ }
+ for (index in 0 until UPGRADE_NAME_SIZE) {
+ slot("upgrade_name[$index]") { upgradeName(index) }
+ }
+ for (index in 0 until UPGRADE_PROGRESS_SIZE) {
+ bar("upgrade_progress[$index]") { upgradeProgress(index) }
+ }
+ for (index in 0 until UPGRADE_STAGE_SIZE) {
+ switch("upgrade_stage[$index]") { upgradeStage(index).value }
+ }
+ }
+
+ public companion object {
+ /** A shape dimension of `upgrade`. */
+ public const val UPGRADE_SIZE: Int = 3
+ /** A shape dimension of `upgrade_cost`. */
+ public const val UPGRADE_COST_SIZE: Int = 3
+ /** A shape dimension of `upgrade_icon`. */
+ public const val UPGRADE_ICON_SIZE: Int = 3
+ /** A shape dimension of `upgrade_level`. */
+ public const val UPGRADE_LEVEL_SIZE: Int = 3
+ /** A shape dimension of `upgrade_name`. */
+ public const val UPGRADE_NAME_SIZE: Int = 3
+ /** A shape dimension of `upgrade_progress`. */
+ public const val UPGRADE_PROGRESS_SIZE: Int = 3
+ /** A shape dimension of `upgrade_stage`. */
+ public const val UPGRADE_STAGE_SIZE: Int = 3
+ }
+}
diff --git a/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowDefinitions.kt b/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowDefinitions.kt
index 16b539d7..b3233e9d 100644
--- a/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowDefinitions.kt
+++ b/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowDefinitions.kt
@@ -7,4 +7,5 @@ import com.chunkzero.window.WindowDefinition
public object WindowDefinitions {
public val catalogSearch: WindowDefinition = WindowDefinition(WindowPackData.manifest, "catalog_search")
public val shop: WindowDefinition = WindowDefinition(WindowPackData.manifest, "shop")
+ public val upgrades: WindowDefinition = WindowDefinition(WindowPackData.manifest, "upgrades")
}
diff --git a/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowEntries.kt b/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowEntries.kt
index 7770b80a..bc084762 100644
--- a/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowEntries.kt
+++ b/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowEntries.kt
@@ -8,6 +8,7 @@ import com.chunkzero.window.manifest.ArtEntry
import com.chunkzero.window.manifest.BarEntry
import com.chunkzero.window.manifest.HitboxEntry
import com.chunkzero.window.manifest.HoverEntry
+import com.chunkzero.window.manifest.ItemEntry
import com.chunkzero.window.manifest.LayerEntry
import com.chunkzero.window.manifest.LayerKind
import com.chunkzero.window.manifest.RegionEntry
@@ -340,7 +341,7 @@ internal object WindowEntries {
"buy_label" to SlotEntry(
x = 8,
y = 201,
- width = 106,
+ width = 70,
align = Align.CENTER,
font = "window:small_caps/y195",
color = "#2a1200",
@@ -357,7 +358,7 @@ internal object WindowEntries {
"buy_label~2" to SlotEntry(
x = 8,
y = 201,
- width = 106,
+ width = 70,
align = Align.CENTER,
font = "window:small_caps/y195",
color = "#2a1200",
@@ -576,7 +577,24 @@ internal object WindowEntries {
shaderColor = null,
),
"label_6" to SlotEntry(
- x = 133,
+ x = 86,
+ y = 201,
+ width = 40,
+ align = Align.CENTER,
+ font = "window:small_caps/y195",
+ color = "#ffffff",
+ shadow = true,
+ bold = false,
+ italic = false,
+ underlined = false,
+ strikethrough = false,
+ obfuscated = false,
+ text = "Workshop",
+ shaderMarker = null,
+ shaderColor = null,
+ ),
+ "label_7" to SlotEntry(
+ x = 142,
y = 201,
width = 19,
align = Align.CENTER,
@@ -908,7 +926,7 @@ internal object WindowEntries {
"buy" to RegionEntry(
x = 8,
y = 197,
- width = 106,
+ width = 70,
height = 16,
slots = listOf(
SlotRefEntry(
@@ -927,14 +945,6 @@ internal object WindowEntries {
area = SlotAreaEntry.PLAYER,
index = 3,
),
- SlotRefEntry(
- area = SlotAreaEntry.PLAYER,
- index = 4,
- ),
- SlotRefEntry(
- area = SlotAreaEntry.PLAYER,
- index = 5,
- ),
),
fillSlots = null,
action = "buy",
@@ -948,7 +958,7 @@ internal object WindowEntries {
),
source = "region `buy`",
hover = HoverEntry(
- text = "\uF517\uDB80\uDC04\uDB80\uDC05\uDB80\uDC07\uDB80\uDC09\uDB80\uDC0A",
+ text = "\uED43\uDB80\uDC04\uDB80\uDC08\uDB80\uDC09\uDB80\uDC0A",
x = -80,
y = 86,
),
@@ -1366,6 +1376,42 @@ internal object WindowEntries {
y = -21,
),
),
+ "open_upgrades" to RegionEntry(
+ x = 80,
+ y = 197,
+ width = 52,
+ height = 16,
+ slots = listOf(
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 4,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 5,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 6,
+ ),
+ ),
+ fillSlots = null,
+ action = "open_upgrades",
+ defaultAction = null,
+ hitbox = HitboxEntry(
+ itemModel = null,
+ tooltip = TooltipEntry(
+ title = "Upgrade the foundry",
+ lines = emptyList(),
+ ),
+ ),
+ source = "region `open_upgrades`",
+ hover = HoverEntry(
+ text = "\uF3F1\uDB80\uDC05\uDB80\uDC06\uDB80\uDC08\uDB80\uDC0A",
+ x = -8,
+ y = 86,
+ ),
+ ),
"player_section" to RegionEntry(
x = 8,
y = 139,
@@ -1539,7 +1585,7 @@ internal object WindowEntries {
"region~3" to RegionEntry(
x = 8,
y = 197,
- width = 106,
+ width = 70,
height = 16,
slots = listOf(
SlotRefEntry(
@@ -1558,14 +1604,6 @@ internal object WindowEntries {
area = SlotAreaEntry.PLAYER,
index = 3,
),
- SlotRefEntry(
- area = SlotAreaEntry.PLAYER,
- index = 4,
- ),
- SlotRefEntry(
- area = SlotAreaEntry.PLAYER,
- index = 5,
- ),
),
fillSlots = null,
action = null,
@@ -1798,15 +1836,11 @@ internal object WindowEntries {
source = "region `sort=price~2`",
),
"window:close" to RegionEntry(
- x = 116,
+ x = 134,
y = 197,
- width = 52,
+ width = 34,
height = 16,
slots = listOf(
- SlotRefEntry(
- area = SlotAreaEntry.PLAYER,
- index = 6,
- ),
SlotRefEntry(
area = SlotAreaEntry.PLAYER,
index = 7,
@@ -1822,8 +1856,8 @@ internal object WindowEntries {
hitbox = null,
source = "region `window:close`",
hover = HoverEntry(
- text = "\uF3F1\uDB80\uDC05\uDB80\uDC06\uDB80\uDC08\uDB80\uDC0A",
- x = 28,
+ text = "\uE815\uDB80\uDC05\uDB80\uDC09\uDB80\uDC0A",
+ x = 46,
y = 86,
),
),
@@ -2258,7 +2292,7 @@ internal object WindowEntries {
cases = listOf(
SwitchCaseEntry(
value = "true",
- static = "\uF4F4\uDB80\uDC04\uDB80\uDC05\uDB80\uDC07\uDB80\uDC09\uDB80\uDC0A",
+ static = "\uF4F4\uDB80\uDC04\uDB80\uDC08\uDB80\uDC09\uDB80\uDC0A",
slots = listOf("buy_label"),
spriteSlots = emptyList(),
regions = listOf("buy"),
@@ -2266,7 +2300,7 @@ internal object WindowEntries {
),
SwitchCaseEntry(
value = "false",
- static = "\uE80C\uDB80\uDC04\uDB80\uDC05\uDB80\uDC07\uDB80\uDC09\uDB80\uDC0A",
+ static = "\uE80C\uDB80\uDC04\uDB80\uDC08\uDB80\uDC09\uDB80\uDC0A",
slots = listOf("buy_label~2"),
spriteSlots = emptyList(),
regions = listOf("region~3"),
@@ -2639,6 +2673,1512 @@ internal object WindowEntries {
LayerEntry(LayerKind.SLOT, "buy_label"),
LayerEntry(LayerKind.SLOT, "buy_label~2"),
LayerEntry(LayerKind.SLOT, "label_6"),
+ LayerEntry(LayerKind.SLOT, "label_7"),
+ ),
+ ),
+ "upgrades" to
+ WindowEntry(
+ surface = SurfaceEntry(
+ kind = "container",
+ container = "generic_9x3",
+ size = listOf(176, 168),
+ titleOrigin = listOf(8, 6),
+ ),
+ static = "\uDB80\uDC07\uDB80\uDC08\uF06E\uDB80\uDC03\uDB80\uDC05\uDB80\uDC07\uDB80\uDC08\uDB80\uDC0A",
+ slots = mapOf(
+ "balance" to SlotEntry(
+ x = 119,
+ y = 89,
+ width = 34,
+ align = Align.RIGHT,
+ font = "window:small_caps/y83",
+ color = "#ffb20b",
+ shadow = true,
+ bold = false,
+ italic = false,
+ underlined = false,
+ strikethrough = false,
+ obfuscated = false,
+ text = null,
+ shaderMarker = null,
+ shaderColor = null,
+ ),
+ "label_0" to SlotEntry(
+ x = 48,
+ y = 5,
+ width = 80,
+ align = Align.LEFT,
+ font = "window:small_caps/ym1",
+ color = "#ffffff",
+ shadow = true,
+ bold = false,
+ italic = false,
+ underlined = false,
+ strikethrough = false,
+ obfuscated = false,
+ text = "Foundry Workshop",
+ shaderMarker = null,
+ shaderColor = null,
+ ),
+ "label_1" to SlotEntry(
+ x = 11,
+ y = 49,
+ width = 46,
+ align = Align.CENTER,
+ font = "window:small_caps/y43",
+ color = "#f2c9a0",
+ shadow = true,
+ bold = false,
+ italic = false,
+ underlined = false,
+ strikethrough = false,
+ obfuscated = false,
+ text = "New",
+ shaderMarker = null,
+ shaderColor = null,
+ ),
+ "label_10" to SlotEntry(
+ x = 57,
+ y = 89,
+ width = 8,
+ align = Align.CENTER,
+ font = "window:small_caps/y83",
+ color = "#ffe2b8",
+ shadow = true,
+ bold = false,
+ italic = false,
+ underlined = false,
+ strikethrough = false,
+ obfuscated = false,
+ text = "x5",
+ shaderMarker = null,
+ shaderColor = null,
+ ),
+ "label_11" to SlotEntry(
+ x = 90,
+ y = 89,
+ width = 15,
+ align = Align.CENTER,
+ font = "window:small_caps/y83",
+ color = "#2a1200",
+ shadow = false,
+ bold = false,
+ italic = false,
+ underlined = false,
+ strikethrough = false,
+ obfuscated = false,
+ text = "Max",
+ shaderMarker = null,
+ shaderColor = null,
+ ),
+ "label_12" to SlotEntry(
+ x = 90,
+ y = 89,
+ width = 15,
+ align = Align.CENTER,
+ font = "window:small_caps/y83",
+ color = "#ffe2b8",
+ shadow = true,
+ bold = false,
+ italic = false,
+ underlined = false,
+ strikethrough = false,
+ obfuscated = false,
+ text = "Max",
+ shaderMarker = null,
+ shaderColor = null,
+ ),
+ "label_13" to SlotEntry(
+ x = 25,
+ y = 147,
+ width = 19,
+ align = Align.CENTER,
+ font = "window:small_caps/y141",
+ color = "#ffffff",
+ shadow = true,
+ bold = false,
+ italic = false,
+ underlined = false,
+ strikethrough = false,
+ obfuscated = false,
+ text = "Shop",
+ shaderMarker = null,
+ shaderColor = null,
+ ),
+ "label_14" to SlotEntry(
+ x = 133,
+ y = 147,
+ width = 19,
+ align = Align.CENTER,
+ font = "window:small_caps/y141",
+ color = "#ffffff",
+ shadow = true,
+ bold = false,
+ italic = false,
+ underlined = false,
+ strikethrough = false,
+ obfuscated = false,
+ text = "Exit",
+ shaderMarker = null,
+ shaderColor = null,
+ ),
+ "label_2" to SlotEntry(
+ x = 22,
+ y = 54,
+ width = 25,
+ align = Align.LEFT,
+ font = "window:small_caps/y48",
+ color = "#2a1200",
+ shadow = false,
+ bold = false,
+ italic = false,
+ underlined = false,
+ strikethrough = false,
+ obfuscated = false,
+ text = "Maxed",
+ shaderMarker = null,
+ shaderColor = null,
+ ),
+ "label_3" to SlotEntry(
+ x = 65,
+ y = 49,
+ width = 46,
+ align = Align.CENTER,
+ font = "window:small_caps/y43",
+ color = "#f2c9a0",
+ shadow = true,
+ bold = false,
+ italic = false,
+ underlined = false,
+ strikethrough = false,
+ obfuscated = false,
+ text = "New",
+ shaderMarker = null,
+ shaderColor = null,
+ ),
+ "label_4" to SlotEntry(
+ x = 76,
+ y = 54,
+ width = 25,
+ align = Align.LEFT,
+ font = "window:small_caps/y48",
+ color = "#2a1200",
+ shadow = false,
+ bold = false,
+ italic = false,
+ underlined = false,
+ strikethrough = false,
+ obfuscated = false,
+ text = "Maxed",
+ shaderMarker = null,
+ shaderColor = null,
+ ),
+ "label_5" to SlotEntry(
+ x = 119,
+ y = 49,
+ width = 46,
+ align = Align.CENTER,
+ font = "window:small_caps/y43",
+ color = "#f2c9a0",
+ shadow = true,
+ bold = false,
+ italic = false,
+ underlined = false,
+ strikethrough = false,
+ obfuscated = false,
+ text = "New",
+ shaderMarker = null,
+ shaderColor = null,
+ ),
+ "label_6" to SlotEntry(
+ x = 130,
+ y = 54,
+ width = 25,
+ align = Align.LEFT,
+ font = "window:small_caps/y48",
+ color = "#2a1200",
+ shadow = false,
+ bold = false,
+ italic = false,
+ underlined = false,
+ strikethrough = false,
+ obfuscated = false,
+ text = "Maxed",
+ shaderMarker = null,
+ shaderColor = null,
+ ),
+ "label_7" to SlotEntry(
+ x = 21,
+ y = 89,
+ width = 8,
+ align = Align.CENTER,
+ font = "window:small_caps/y83",
+ color = "#2a1200",
+ shadow = false,
+ bold = false,
+ italic = false,
+ underlined = false,
+ strikethrough = false,
+ obfuscated = false,
+ text = "x1",
+ shaderMarker = null,
+ shaderColor = null,
+ ),
+ "label_8" to SlotEntry(
+ x = 21,
+ y = 89,
+ width = 8,
+ align = Align.CENTER,
+ font = "window:small_caps/y83",
+ color = "#ffe2b8",
+ shadow = true,
+ bold = false,
+ italic = false,
+ underlined = false,
+ strikethrough = false,
+ obfuscated = false,
+ text = "x1",
+ shaderMarker = null,
+ shaderColor = null,
+ ),
+ "label_9" to SlotEntry(
+ x = 57,
+ y = 89,
+ width = 8,
+ align = Align.CENTER,
+ font = "window:small_caps/y83",
+ color = "#2a1200",
+ shadow = false,
+ bold = false,
+ italic = false,
+ underlined = false,
+ strikethrough = false,
+ obfuscated = false,
+ text = "x5",
+ shaderMarker = null,
+ shaderColor = null,
+ ),
+ "rank_name" to SlotEntry(
+ x = 31,
+ y = 110,
+ width = 134,
+ align = Align.LEFT,
+ font = "window:small_caps/y104",
+ color = "#ffe2b8",
+ shadow = true,
+ bold = false,
+ italic = false,
+ underlined = false,
+ strikethrough = false,
+ obfuscated = false,
+ text = null,
+ shaderMarker = null,
+ shaderColor = null,
+ ),
+ "status" to SlotEntry(
+ x = 31,
+ y = 122,
+ width = 134,
+ align = Align.LEFT,
+ font = "window:small_caps/y116",
+ color = "#f2c9a0",
+ shadow = true,
+ bold = false,
+ italic = false,
+ underlined = false,
+ strikethrough = false,
+ obfuscated = false,
+ text = null,
+ shaderMarker = null,
+ shaderColor = null,
+ ),
+ "upgrade_cost[0]" to SlotEntry(
+ x = 11,
+ y = 59,
+ width = 36,
+ align = Align.RIGHT,
+ font = "window:small_caps/y53",
+ color = "#ffb20b",
+ shadow = true,
+ bold = false,
+ italic = false,
+ underlined = false,
+ strikethrough = false,
+ obfuscated = false,
+ text = null,
+ shaderMarker = null,
+ shaderColor = null,
+ ),
+ "upgrade_cost[0]~2" to SlotEntry(
+ x = 11,
+ y = 59,
+ width = 36,
+ align = Align.RIGHT,
+ font = "window:small_caps/y53",
+ color = "#ffb20b",
+ shadow = true,
+ bold = false,
+ italic = false,
+ underlined = false,
+ strikethrough = false,
+ obfuscated = false,
+ text = null,
+ shaderMarker = null,
+ shaderColor = null,
+ ),
+ "upgrade_cost[1]" to SlotEntry(
+ x = 65,
+ y = 59,
+ width = 36,
+ align = Align.RIGHT,
+ font = "window:small_caps/y53",
+ color = "#ffb20b",
+ shadow = true,
+ bold = false,
+ italic = false,
+ underlined = false,
+ strikethrough = false,
+ obfuscated = false,
+ text = null,
+ shaderMarker = null,
+ shaderColor = null,
+ ),
+ "upgrade_cost[1]~2" to SlotEntry(
+ x = 65,
+ y = 59,
+ width = 36,
+ align = Align.RIGHT,
+ font = "window:small_caps/y53",
+ color = "#ffb20b",
+ shadow = true,
+ bold = false,
+ italic = false,
+ underlined = false,
+ strikethrough = false,
+ obfuscated = false,
+ text = null,
+ shaderMarker = null,
+ shaderColor = null,
+ ),
+ "upgrade_cost[2]" to SlotEntry(
+ x = 119,
+ y = 59,
+ width = 36,
+ align = Align.RIGHT,
+ font = "window:small_caps/y53",
+ color = "#ffb20b",
+ shadow = true,
+ bold = false,
+ italic = false,
+ underlined = false,
+ strikethrough = false,
+ obfuscated = false,
+ text = null,
+ shaderMarker = null,
+ shaderColor = null,
+ ),
+ "upgrade_cost[2]~2" to SlotEntry(
+ x = 119,
+ y = 59,
+ width = 36,
+ align = Align.RIGHT,
+ font = "window:small_caps/y53",
+ color = "#ffb20b",
+ shadow = true,
+ bold = false,
+ italic = false,
+ underlined = false,
+ strikethrough = false,
+ obfuscated = false,
+ text = null,
+ shaderMarker = null,
+ shaderColor = null,
+ ),
+ "upgrade_level[0]" to SlotEntry(
+ x = 11,
+ y = 25,
+ width = 46,
+ align = Align.RIGHT,
+ font = "window:small_caps/y19",
+ color = "#f2c9a0",
+ shadow = true,
+ bold = false,
+ italic = false,
+ underlined = false,
+ strikethrough = false,
+ obfuscated = false,
+ text = null,
+ shaderMarker = null,
+ shaderColor = null,
+ ),
+ "upgrade_level[1]" to SlotEntry(
+ x = 65,
+ y = 25,
+ width = 46,
+ align = Align.RIGHT,
+ font = "window:small_caps/y19",
+ color = "#f2c9a0",
+ shadow = true,
+ bold = false,
+ italic = false,
+ underlined = false,
+ strikethrough = false,
+ obfuscated = false,
+ text = null,
+ shaderMarker = null,
+ shaderColor = null,
+ ),
+ "upgrade_level[2]" to SlotEntry(
+ x = 119,
+ y = 25,
+ width = 46,
+ align = Align.RIGHT,
+ font = "window:small_caps/y19",
+ color = "#f2c9a0",
+ shadow = true,
+ bold = false,
+ italic = false,
+ underlined = false,
+ strikethrough = false,
+ obfuscated = false,
+ text = null,
+ shaderMarker = null,
+ shaderColor = null,
+ ),
+ "upgrade_name[0]" to SlotEntry(
+ x = 11,
+ y = 39,
+ width = 46,
+ align = Align.CENTER,
+ font = "window:small_caps/y33",
+ color = "#ffe2b8",
+ shadow = true,
+ bold = false,
+ italic = false,
+ underlined = false,
+ strikethrough = false,
+ obfuscated = false,
+ text = null,
+ shaderMarker = null,
+ shaderColor = null,
+ ),
+ "upgrade_name[1]" to SlotEntry(
+ x = 65,
+ y = 39,
+ width = 46,
+ align = Align.CENTER,
+ font = "window:small_caps/y33",
+ color = "#ffe2b8",
+ shadow = true,
+ bold = false,
+ italic = false,
+ underlined = false,
+ strikethrough = false,
+ obfuscated = false,
+ text = null,
+ shaderMarker = null,
+ shaderColor = null,
+ ),
+ "upgrade_name[2]" to SlotEntry(
+ x = 119,
+ y = 39,
+ width = 46,
+ align = Align.CENTER,
+ font = "window:small_caps/y33",
+ color = "#ffe2b8",
+ shadow = true,
+ bold = false,
+ italic = false,
+ underlined = false,
+ strikethrough = false,
+ obfuscated = false,
+ text = null,
+ shaderMarker = null,
+ shaderColor = null,
+ ),
+ ),
+ spriteSlots = mapOf(
+ "rank" to SpriteSlotEntry(
+ x = 15,
+ y = 115,
+ width = 9,
+ height = 11,
+ align = Align.LEFT,
+ font = "window:sprite_y109",
+ sprite = null,
+ only = listOf("rank_1", "rank_2", "rank_3"),
+ tint = "tone",
+ ),
+ ),
+ regions = mapOf(
+ "amount=five" to RegionEntry(
+ x = 44,
+ y = 85,
+ width = 34,
+ height = 16,
+ slots = listOf(
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 11,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 12,
+ ),
+ ),
+ fillSlots = null,
+ action = "amount=five",
+ defaultAction = null,
+ hitbox = HitboxEntry(
+ itemModel = null,
+ tooltip = TooltipEntry(
+ title = "Buy five levels",
+ lines = emptyList(),
+ ),
+ ),
+ source = "region `amount=five`",
+ hover = HoverEntry(
+ text = "\uE815\uDB80\uDC05\uDB80\uDC09\uDB80\uDC0A",
+ x = -44,
+ y = 1,
+ ),
+ ),
+ "amount=five~2" to RegionEntry(
+ x = 44,
+ y = 85,
+ width = 34,
+ height = 16,
+ slots = listOf(
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 11,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 12,
+ ),
+ ),
+ fillSlots = null,
+ action = "amount=five~2",
+ defaultAction = null,
+ hitbox = HitboxEntry(
+ itemModel = null,
+ tooltip = TooltipEntry(
+ title = "Buy five levels",
+ lines = emptyList(),
+ ),
+ ),
+ source = "region `amount=five~2`",
+ hover = HoverEntry(
+ text = "\uE815\uDB80\uDC05\uDB80\uDC09\uDB80\uDC0A",
+ x = -44,
+ y = 1,
+ ),
+ ),
+ "amount=max" to RegionEntry(
+ x = 80,
+ y = 85,
+ width = 34,
+ height = 16,
+ slots = listOf(
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 13,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 14,
+ ),
+ ),
+ fillSlots = null,
+ action = "amount=max",
+ defaultAction = null,
+ hitbox = HitboxEntry(
+ itemModel = null,
+ tooltip = TooltipEntry(
+ title = "Buy every level you can afford",
+ lines = emptyList(),
+ ),
+ ),
+ source = "region `amount=max`",
+ hover = HoverEntry(
+ text = "\uE815\uDB80\uDC05\uDB80\uDC09\uDB80\uDC0A",
+ x = -8,
+ y = 1,
+ ),
+ ),
+ "amount=max~2" to RegionEntry(
+ x = 80,
+ y = 85,
+ width = 34,
+ height = 16,
+ slots = listOf(
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 13,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 14,
+ ),
+ ),
+ fillSlots = null,
+ action = "amount=max~2",
+ defaultAction = null,
+ hitbox = HitboxEntry(
+ itemModel = null,
+ tooltip = TooltipEntry(
+ title = "Buy every level you can afford",
+ lines = emptyList(),
+ ),
+ ),
+ source = "region `amount=max~2`",
+ hover = HoverEntry(
+ text = "\uE815\uDB80\uDC05\uDB80\uDC09\uDB80\uDC0A",
+ x = -8,
+ y = 1,
+ ),
+ ),
+ "amount=one" to RegionEntry(
+ x = 8,
+ y = 85,
+ width = 34,
+ height = 16,
+ slots = listOf(
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 9,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 10,
+ ),
+ ),
+ fillSlots = null,
+ action = "amount=one",
+ defaultAction = null,
+ hitbox = HitboxEntry(
+ itemModel = null,
+ tooltip = TooltipEntry(
+ title = "Buy one level",
+ lines = emptyList(),
+ ),
+ ),
+ source = "region `amount=one`",
+ hover = HoverEntry(
+ text = "\uE815\uDB80\uDC05\uDB80\uDC09\uDB80\uDC0A",
+ x = -80,
+ y = 1,
+ ),
+ ),
+ "amount=one~2" to RegionEntry(
+ x = 8,
+ y = 85,
+ width = 34,
+ height = 16,
+ slots = listOf(
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 9,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 10,
+ ),
+ ),
+ fillSlots = null,
+ action = "amount=one~2",
+ defaultAction = null,
+ hitbox = HitboxEntry(
+ itemModel = null,
+ tooltip = TooltipEntry(
+ title = "Buy one level",
+ lines = emptyList(),
+ ),
+ ),
+ source = "region `amount=one~2`",
+ hover = HoverEntry(
+ text = "\uE815\uDB80\uDC05\uDB80\uDC09\uDB80\uDC0A",
+ x = -80,
+ y = 1,
+ ),
+ ),
+ "hotbar_section" to RegionEntry(
+ x = 8,
+ y = 143,
+ width = 160,
+ height = 16,
+ slots = listOf(
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 3,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 4,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 5,
+ ),
+ ),
+ fillSlots = null,
+ action = null,
+ defaultAction = null,
+ hitbox = null,
+ source = "hotbar section",
+ ),
+ "open_shop" to RegionEntry(
+ x = 8,
+ y = 143,
+ width = 52,
+ height = 16,
+ slots = listOf(
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 0,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 1,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 2,
+ ),
+ ),
+ fillSlots = null,
+ action = "open_shop",
+ defaultAction = null,
+ hitbox = HitboxEntry(
+ itemModel = null,
+ tooltip = TooltipEntry(
+ title = "Back to the shop",
+ lines = emptyList(),
+ ),
+ ),
+ source = "region `open_shop`",
+ hover = HoverEntry(
+ text = "\uF3F1\uDB80\uDC05\uDB80\uDC06\uDB80\uDC08\uDB80\uDC0A",
+ x = -80,
+ y = 59,
+ ),
+ ),
+ "player_section" to RegionEntry(
+ x = 8,
+ y = 85,
+ width = 160,
+ height = 52,
+ slots = listOf(
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 18,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 19,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 20,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 21,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 22,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 23,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 24,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 25,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 26,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 27,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 28,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 29,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 30,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 31,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 32,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 33,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 34,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 35,
+ ),
+ ),
+ fillSlots = null,
+ action = null,
+ defaultAction = null,
+ hitbox = null,
+ source = "player section",
+ ),
+ "region~0" to RegionEntry(
+ x = 115,
+ y = 84,
+ width = 54,
+ height = 18,
+ slots = listOf(
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 15,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 16,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 17,
+ ),
+ ),
+ fillSlots = null,
+ action = null,
+ defaultAction = null,
+ hitbox = HitboxEntry(
+ itemModel = null,
+ tooltip = TooltipEntry(
+ title = "Balance",
+ lines = listOf("Coins earned in the shop"),
+ ),
+ ),
+ source = "region `region~0`",
+ hover = HoverEntry(
+ text = "\uE9E2\uDB80\uDC05\uDB80\uDC06\uDB80\uDC08\uDB80\uDC09\uDB80\uDC0A",
+ x = 27,
+ y = 0,
+ ),
+ ),
+ "upgrade[0]" to RegionEntry(
+ x = 8,
+ y = 18,
+ width = 52,
+ height = 52,
+ slots = listOf(
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 0,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 1,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 2,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 9,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 10,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 11,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 18,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 19,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 20,
+ ),
+ ),
+ fillSlots = listOf(
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 1,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 2,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 9,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 10,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 11,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 18,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 19,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 20,
+ ),
+ ),
+ action = "upgrade[0]",
+ defaultAction = null,
+ hitbox = HitboxEntry(
+ itemModel = null,
+ tooltip = TooltipEntry(
+ title = "Click to upgrade",
+ lines = emptyList(),
+ ),
+ ),
+ source = "region `upgrade[0]`",
+ hover = HoverEntry(
+ text = "\uE35E\uDB80\uDC05\uDB80\uDC06\uDB80\uDC08\uDB80\uDC0A",
+ x = -80,
+ y = -66,
+ ),
+ ),
+ "upgrade[1]" to RegionEntry(
+ x = 62,
+ y = 18,
+ width = 52,
+ height = 52,
+ slots = listOf(
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 3,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 4,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 5,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 12,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 13,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 14,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 21,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 22,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 23,
+ ),
+ ),
+ fillSlots = listOf(
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 4,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 5,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 12,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 13,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 14,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 21,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 22,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 23,
+ ),
+ ),
+ action = "upgrade[1]",
+ defaultAction = null,
+ hitbox = HitboxEntry(
+ itemModel = null,
+ tooltip = TooltipEntry(
+ title = "Click to upgrade",
+ lines = emptyList(),
+ ),
+ ),
+ source = "region `upgrade[1]`",
+ hover = HoverEntry(
+ text = "\uE35E\uDB80\uDC05\uDB80\uDC06\uDB80\uDC08\uDB80\uDC0A",
+ x = -26,
+ y = -66,
+ ),
+ ),
+ "upgrade[2]" to RegionEntry(
+ x = 116,
+ y = 18,
+ width = 52,
+ height = 52,
+ slots = listOf(
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 6,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 7,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 8,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 15,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 16,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 17,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 24,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 25,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 26,
+ ),
+ ),
+ fillSlots = listOf(
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 7,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 8,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 15,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 16,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 17,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 24,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 25,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 26,
+ ),
+ ),
+ action = "upgrade[2]",
+ defaultAction = null,
+ hitbox = HitboxEntry(
+ itemModel = null,
+ tooltip = TooltipEntry(
+ title = "Click to upgrade",
+ lines = emptyList(),
+ ),
+ ),
+ source = "region `upgrade[2]`",
+ hover = HoverEntry(
+ text = "\uE35E\uDB80\uDC05\uDB80\uDC06\uDB80\uDC08\uDB80\uDC0A",
+ x = 28,
+ y = -66,
+ ),
+ ),
+ "window:close" to RegionEntry(
+ x = 116,
+ y = 143,
+ width = 52,
+ height = 16,
+ slots = listOf(
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 6,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 7,
+ ),
+ SlotRefEntry(
+ area = SlotAreaEntry.PLAYER,
+ index = 8,
+ ),
+ ),
+ fillSlots = null,
+ action = "window:close",
+ defaultAction = "window:close",
+ hitbox = null,
+ source = "region `window:close`",
+ hover = HoverEntry(
+ text = "\uF3F1\uDB80\uDC05\uDB80\uDC06\uDB80\uDC08\uDB80\uDC0A",
+ x = 28,
+ y = 59,
+ ),
+ ),
+ ),
+ items = mapOf(
+ "upgrade_icon[0]" to ItemEntry(
+ slots = listOf(
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 0,
+ ),
+ ),
+ ),
+ "upgrade_icon[1]" to ItemEntry(
+ slots = listOf(
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 3,
+ ),
+ ),
+ ),
+ "upgrade_icon[2]" to ItemEntry(
+ slots = listOf(
+ SlotRefEntry(
+ area = SlotAreaEntry.CONTAINER,
+ index = 6,
+ ),
+ ),
+ ),
+ ),
+ collections = emptyMap(),
+ inputs = emptyMap(),
+ switches = mapOf(
+ "amount?five" to SwitchEntry(
+ cases = listOf(
+ SwitchCaseEntry(
+ value = "true",
+ static = "\uDB80\uDC10\uDB80\uDC0D\uE6C8\uDB80\uDC04\uDB80\uDC08\uDB80\uDC09\uDB80\uDC0A",
+ slots = listOf("label_9"),
+ spriteSlots = emptyList(),
+ regions = listOf("amount=five"),
+ switches = emptyList(),
+ ),
+ SwitchCaseEntry(
+ value = "false",
+ static = "\uDB80\uDC10\uDB80\uDC0D\uE97B\uDB80\uDC04\uDB80\uDC08\uDB80\uDC09\uDB80\uDC0A",
+ slots = listOf("label_10"),
+ spriteSlots = emptyList(),
+ regions = listOf("amount=five~2"),
+ switches = emptyList(),
+ ),
+ ),
+ source = null,
+ ),
+ "amount?max" to SwitchEntry(
+ cases = listOf(
+ SwitchCaseEntry(
+ value = "true",
+ static = "\uDB80\uDC11\uDB80\uDC0E\uF098\uDB80\uDC04\uDB80\uDC05\uDB80\uDC07\uDB80\uDC09\uDB80\uDC0A",
+ slots = listOf("label_11"),
+ spriteSlots = emptyList(),
+ regions = listOf("amount=max"),
+ switches = emptyList(),
+ ),
+ SwitchCaseEntry(
+ value = "false",
+ static = "\uDB80\uDC11\uDB80\uDC0E\uE82F\uDB80\uDC04\uDB80\uDC05\uDB80\uDC07\uDB80\uDC09\uDB80\uDC0A",
+ slots = listOf("label_12"),
+ spriteSlots = emptyList(),
+ regions = listOf("amount=max~2"),
+ switches = emptyList(),
+ ),
+ ),
+ source = null,
+ ),
+ "amount?one" to SwitchEntry(
+ cases = listOf(
+ SwitchCaseEntry(
+ value = "true",
+ static = "\uE7B9\uDB80\uDC05\uDB80\uDC09\uDB80\uDC0A",
+ slots = listOf("label_7"),
+ spriteSlots = emptyList(),
+ regions = listOf("amount=one"),
+ switches = emptyList(),
+ ),
+ SwitchCaseEntry(
+ value = "false",
+ static = "\uE2CF\uDB80\uDC05\uDB80\uDC09\uDB80\uDC0A",
+ slots = listOf("label_8"),
+ spriteSlots = emptyList(),
+ regions = listOf("amount=one~2"),
+ switches = emptyList(),
+ ),
+ ),
+ source = null,
+ ),
+ "upgrade_stage[0]" to SwitchEntry(
+ cases = listOf(
+ SwitchCaseEntry(
+ value = "new",
+ static = "\uDB80\uDC10\uDB80\uDC0E\uDB80\uDC0B\uF630\uDB80\uDC05\uDB80\uDC06\uDB80\uDC09",
+ slots = listOf("label_1", "upgrade_cost[0]"),
+ spriteSlots = emptyList(),
+ regions = emptyList(),
+ switches = emptyList(),
+ ),
+ SwitchCaseEntry(
+ value = "open",
+ static = "\uDB80\uDC0C\uDB80\uDC0B\uEF38\uDB80\uDC05\uDB80\uDC06\uDB80\uDC09",
+ slots = listOf("upgrade_cost[0]~2"),
+ spriteSlots = emptyList(),
+ regions = emptyList(),
+ switches = emptyList(),
+ art = listOf("art/0"),
+ ),
+ SwitchCaseEntry(
+ value = "maxed",
+ static = "\uDB80\uDC0C\uDB80\uDC0B\uF153\uDB80\uDC05\uDB80\uDC06\uDB80\uDC09",
+ slots = listOf("label_2"),
+ spriteSlots = emptyList(),
+ regions = emptyList(),
+ switches = emptyList(),
+ ),
+ ),
+ source = null,
+ ),
+ "upgrade_stage[1]" to SwitchEntry(
+ cases = listOf(
+ SwitchCaseEntry(
+ value = "new",
+ static = "\uDB80\uDC11\uDB80\uDC0F\uDB80\uDC0E\uDB80\uDC0D\uDB80\uDC0C\uDB80\uDC0B\uE9F4\uDB80\uDC04\uDB80\uDC05\uDB80\uDC07",
+ slots = listOf("label_3", "upgrade_cost[1]"),
+ spriteSlots = emptyList(),
+ regions = emptyList(),
+ switches = emptyList(),
+ ),
+ SwitchCaseEntry(
+ value = "open",
+ static = "\uDB80\uDC10\uDB80\uDC0F\uDB80\uDC0E\uDB80\uDC0B\uE41B\uDB80\uDC04\uDB80\uDC05\uDB80\uDC07",
+ slots = listOf("upgrade_cost[1]~2"),
+ spriteSlots = emptyList(),
+ regions = emptyList(),
+ switches = emptyList(),
+ art = listOf("art/1"),
+ ),
+ SwitchCaseEntry(
+ value = "maxed",
+ static = "\uDB80\uDC10\uDB80\uDC0F\uDB80\uDC0E\uDB80\uDC0B\uE30A\uDB80\uDC04\uDB80\uDC05\uDB80\uDC07",
+ slots = listOf("label_4"),
+ spriteSlots = emptyList(),
+ regions = emptyList(),
+ switches = emptyList(),
+ ),
+ ),
+ source = null,
+ ),
+ "upgrade_stage[2]" to SwitchEntry(
+ cases = listOf(
+ SwitchCaseEntry(
+ value = "new",
+ static = "\uDB80\uDC12\uDB80\uDC0F\uDB80\uDC0D\uDB80\uDC0B\uE9C7\uDB80\uDC03\uDB80\uDC06\uDB80\uDC07\uDB80\uDC08\uDB80\uDC09",
+ slots = listOf("label_5", "upgrade_cost[2]"),
+ spriteSlots = emptyList(),
+ regions = emptyList(),
+ switches = emptyList(),
+ ),
+ SwitchCaseEntry(
+ value = "open",
+ static = "\uDB80\uDC11\uDB80\uDC10\uDB80\uDC0E\uDB80\uDC0D\uDB80\uDC0C\uDB80\uDC0B\uE59B\uDB80\uDC03\uDB80\uDC06\uDB80\uDC07\uDB80\uDC08\uDB80\uDC09",
+ slots = listOf("upgrade_cost[2]~2"),
+ spriteSlots = emptyList(),
+ regions = emptyList(),
+ switches = emptyList(),
+ art = listOf("art/2"),
+ ),
+ SwitchCaseEntry(
+ value = "maxed",
+ static = "\uDB80\uDC11\uDB80\uDC10\uDB80\uDC0E\uDB80\uDC0D\uDB80\uDC0C\uDB80\uDC0B\uE5AE\uDB80\uDC03\uDB80\uDC06\uDB80\uDC07\uDB80\uDC08\uDB80\uDC09",
+ slots = listOf("label_6"),
+ spriteSlots = emptyList(),
+ regions = emptyList(),
+ switches = emptyList(),
+ ),
+ ),
+ source = null,
+ ),
+ ),
+ art = mapOf(
+ "art/0" to ArtEntry(
+ static = "\uDB80\uDC0C\uDB80\uDC0B\uEA5C\uDB80\uDC0A\uE43C\uDB80\uDC0A\uE69D\uDB80\uDC0A\uF82F\uDB80\uDC05\uDB80\uDC06\uDB80\uDC09",
+ bar = BarEntry(
+ name = "upgrade_progress[0]",
+ x = 3,
+ width = 46,
+ levels = listOf("\uECFB\uECFB\uECFB\uECFB\uECFB\uECFB\uECFB\uECFB\uECFB\uECFB\uECFB\uECFB\uECFB\uECFB\uECFB\uECFB\uECFB\uECFB\uECFB\uECFB\uECFB\uECFB\uECFB", "\uF82F\uF82F\uF82F\uF82F\uF82F\uF82F\uF82F\uF82F\uF82F\uF82F\uF82F\uF82F", "\uE69D\uE69D\uE69D\uE69D\uE69D\uE69D", "\uE43C\uE43C\uE43C", "\uF5B3", "\uEA5C"),
+ advances = mapOf(
+ "\uE43C" to 9,
+ "\uE69D" to 5,
+ "\uEA5C" to 33,
+ "\uECFB" to 2,
+ "\uF5B3" to 17,
+ "\uF82F" to 3,
+ ),
+ ),
+ ),
+ "art/1" to ArtEntry(
+ static = "\uDB80\uDC10\uDB80\uDC0F\uDB80\uDC0E\uDB80\uDC0B\uF70F\uDB80\uDC0A\uE63B\uDB80\uDC0A\uE0E7\uDB80\uDC0A\uEAA6\uDB80\uDC04\uDB80\uDC05\uDB80\uDC07",
+ bar = BarEntry(
+ name = "upgrade_progress[1]",
+ x = 57,
+ width = 46,
+ levels = listOf("\uF337\uF337\uF337\uF337\uF337\uF337\uF337\uF337\uF337\uF337\uF337\uF337\uF337\uF337\uF337\uF337\uF337\uF337\uF337\uF337\uF337\uF337\uF337", "\uEAA6\uEAA6\uEAA6\uEAA6\uEAA6\uEAA6\uEAA6\uEAA6\uEAA6\uEAA6\uEAA6\uEAA6", "\uE0E7\uE0E7\uE0E7\uE0E7\uE0E7\uE0E7", "\uE63B\uE63B\uE63B", "\uF15E", "\uF70F"),
+ advances = mapOf(
+ "\uE0E7" to 5,
+ "\uE63B" to 9,
+ "\uEAA6" to 3,
+ "\uF15E" to 17,
+ "\uF337" to 2,
+ "\uF70F" to 33,
+ ),
+ ),
+ ),
+ "art/2" to ArtEntry(
+ static = "\uDB80\uDC11\uDB80\uDC10\uDB80\uDC0E\uDB80\uDC0D\uDB80\uDC0C\uDB80\uDC0B\uE81C\uDB80\uDC0A\uE8F5\uDB80\uDC0A\uEAB4\uDB80\uDC0A\uF24B\uDB80\uDC03\uDB80\uDC06\uDB80\uDC07\uDB80\uDC08\uDB80\uDC09",
+ bar = BarEntry(
+ name = "upgrade_progress[2]",
+ x = 111,
+ width = 46,
+ levels = listOf("\uEC3F\uEC3F\uEC3F\uEC3F\uEC3F\uEC3F\uEC3F\uEC3F\uEC3F\uEC3F\uEC3F\uEC3F\uEC3F\uEC3F\uEC3F\uEC3F\uEC3F\uEC3F\uEC3F\uEC3F\uEC3F\uEC3F\uEC3F", "\uF24B\uF24B\uF24B\uF24B\uF24B\uF24B\uF24B\uF24B\uF24B\uF24B\uF24B\uF24B", "\uEAB4\uEAB4\uEAB4\uEAB4\uEAB4\uEAB4", "\uE8F5\uE8F5\uE8F5", "\uE096", "\uE81C"),
+ advances = mapOf(
+ "\uE096" to 17,
+ "\uE81C" to 33,
+ "\uE8F5" to 9,
+ "\uEAB4" to 5,
+ "\uEC3F" to 2,
+ "\uF24B" to 3,
+ ),
+ ),
+ ),
+ ),
+ tints = mapOf(
+ "tone" to mapOf(
+ "danger" to "#ff4d4d",
+ "ok" to "#3ddc84",
+ "warn" to "#ffb020",
+ ),
+ ),
+ layers = listOf(
+ LayerEntry(LayerKind.SLOT, "label_0"),
+ LayerEntry(LayerKind.SLOT, "upgrade_level[0]"),
+ LayerEntry(LayerKind.SLOT, "upgrade_name[0]"),
+ LayerEntry(LayerKind.SWITCH, "upgrade_stage[0]"),
+ LayerEntry(LayerKind.SLOT, "label_1"),
+ LayerEntry(LayerKind.SLOT, "upgrade_cost[0]"),
+ LayerEntry(LayerKind.ART, "art/0"),
+ LayerEntry(LayerKind.SLOT, "upgrade_cost[0]~2"),
+ LayerEntry(LayerKind.SLOT, "label_2"),
+ LayerEntry(LayerKind.SLOT, "upgrade_level[1]"),
+ LayerEntry(LayerKind.SLOT, "upgrade_name[1]"),
+ LayerEntry(LayerKind.SWITCH, "upgrade_stage[1]"),
+ LayerEntry(LayerKind.SLOT, "label_3"),
+ LayerEntry(LayerKind.SLOT, "upgrade_cost[1]"),
+ LayerEntry(LayerKind.ART, "art/1"),
+ LayerEntry(LayerKind.SLOT, "upgrade_cost[1]~2"),
+ LayerEntry(LayerKind.SLOT, "label_4"),
+ LayerEntry(LayerKind.SLOT, "upgrade_level[2]"),
+ LayerEntry(LayerKind.SLOT, "upgrade_name[2]"),
+ LayerEntry(LayerKind.SWITCH, "upgrade_stage[2]"),
+ LayerEntry(LayerKind.SLOT, "label_5"),
+ LayerEntry(LayerKind.SLOT, "upgrade_cost[2]"),
+ LayerEntry(LayerKind.ART, "art/2"),
+ LayerEntry(LayerKind.SLOT, "upgrade_cost[2]~2"),
+ LayerEntry(LayerKind.SLOT, "label_6"),
+ LayerEntry(LayerKind.SWITCH, "amount?one"),
+ LayerEntry(LayerKind.SLOT, "label_7"),
+ LayerEntry(LayerKind.SLOT, "label_8"),
+ LayerEntry(LayerKind.SWITCH, "amount?five"),
+ LayerEntry(LayerKind.SLOT, "label_9"),
+ LayerEntry(LayerKind.SLOT, "label_10"),
+ LayerEntry(LayerKind.SWITCH, "amount?max"),
+ LayerEntry(LayerKind.SLOT, "label_11"),
+ LayerEntry(LayerKind.SLOT, "label_12"),
+ LayerEntry(LayerKind.SLOT, "balance"),
+ LayerEntry(LayerKind.SPRITE_SLOT, "rank"),
+ LayerEntry(LayerKind.SLOT, "rank_name"),
+ LayerEntry(LayerKind.SLOT, "status"),
+ LayerEntry(LayerKind.SLOT, "label_13"),
+ LayerEntry(LayerKind.SLOT, "label_14"),
),
),
)
diff --git a/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowFonts.kt b/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowFonts.kt
index 09828795..63ff120b 100644
--- a/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowFonts.kt
+++ b/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowFonts.kt
@@ -824,16 +824,23 @@ internal object WindowFonts {
val metrics: Map =
mapOf(
"minecraft:default" to vanillaMetrics,
+ "window:small_caps/y104" to fontMetrics0,
"window:small_caps/y106" to fontMetrics0,
+ "window:small_caps/y116" to fontMetrics0,
"window:small_caps/y137" to fontMetrics0,
+ "window:small_caps/y141" to fontMetrics0,
"window:small_caps/y155" to fontMetrics0,
"window:small_caps/y16" to fontMetrics0,
"window:small_caps/y173" to fontMetrics0,
"window:small_caps/y18" to fontMetrics0,
"window:small_caps/y19" to fontMetrics0,
"window:small_caps/y195" to fontMetrics0,
+ "window:small_caps/y33" to fontMetrics0,
+ "window:small_caps/y43" to fontMetrics0,
+ "window:small_caps/y48" to fontMetrics0,
+ "window:small_caps/y53" to fontMetrics0,
+ "window:small_caps/y83" to fontMetrics0,
"window:small_caps/y88" to fontMetrics0,
- "window:small_caps/y9" to fontMetrics0,
"window:small_caps/ym1" to fontMetrics0,
)
}
diff --git a/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowHudDefinitions.kt b/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowHudDefinitions.kt
index f2f4c287..f83b2dc9 100644
--- a/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowHudDefinitions.kt
+++ b/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowHudDefinitions.kt
@@ -11,4 +11,5 @@ public object WindowHudDefinitions {
public val statusTopCenter: HudDefinition = HudDefinition(WindowPackData.manifest, "status_top_center")
public val statusTopLeft: HudDefinition = HudDefinition(WindowPackData.manifest, "status_top_left")
public val statusTopRight: HudDefinition = HudDefinition(WindowPackData.manifest, "status_top_right")
+ public val statusWaveBar: HudDefinition = HudDefinition(WindowPackData.manifest, "status_wave_bar")
}
diff --git a/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowHudEntries.kt b/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowHudEntries.kt
index 33ba54b0..3058daf8 100644
--- a/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowHudEntries.kt
+++ b/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowHudEntries.kt
@@ -22,9 +22,9 @@ internal object WindowHudEntries {
kind = "hud",
channel = "actionbar",
width = 112,
- height = 20,
+ height = 16,
),
- static = "\uDB80\uDC0D\uDB80\uDC0C\uF744\uDB80\uDC04\uDB80\uDC05\uDB80\uDC07\uDB80\uDC09\uDB80\uDC0A",
+ static = "",
slots = mapOf(
"hint" to SlotEntry(
x = 6,
@@ -283,30 +283,6 @@ internal object WindowHudEntries {
"warn" to "#1b002a",
),
),
- "art/1" to ArtEntry(
- static = "\uDB80\uDC0D\uDB80\uDC0C\uF06B\uDB80\uDC0A\uF4F7\uDB80\uDC0A\uEF7E\uDB80\uDC04\uDB80\uDC05\uDB80\uDC07\uDB80\uDC09\uDB80\uDC0A",
- bar = BarEntry(
- name = "wave_charge",
- x = 6,
- width = 100,
- levels = listOf("\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214\uE214", "\uE068\uE068\uE068\uE068\uE068\uE068\uE068\uE068\uE068\uE068\uE068\uE068\uE068\uE068\uE068\uE068\uE068\uE068\uE068\uE068\uE068\uE068\uE068\uE068\uE068", "\uEF7E\uEF7E\uEF7E\uEF7E\uEF7E\uEF7E\uEF7E\uEF7E\uEF7E\uEF7E\uEF7E\uEF7E\uEF7E", "\uEAB9\uEAB9\uEAB9\uEAB9\uEAB9\uEAB9", "\uE4BA\uE4BA\uE4BA", "\uF4F7\uF4F7", "\uF06B"),
- advances = mapOf(
- "\uE068" to 3,
- "\uE214" to 2,
- "\uE4BA" to 17,
- "\uEAB9" to 9,
- "\uEF7E" to 5,
- "\uF06B" to 65,
- "\uF4F7" to 33,
- ),
- ),
- tint = "tone",
- tintMarkers = mapOf(
- "danger" to "#ff001e",
- "ok" to "#ba0044",
- "warn" to "#e100a0",
- ),
- ),
),
tints = mapOf(
"tone" to mapOf(
@@ -318,7 +294,6 @@ internal object WindowHudEntries {
layers = listOf(
LayerEntry(LayerKind.ART, "art/0"),
LayerEntry(LayerKind.SLOT, "hint"),
- LayerEntry(LayerKind.ART, "art/1"),
LayerEntry(LayerKind.SWITCH, "wave_progress[0]"),
LayerEntry(LayerKind.SWITCH, "wave_progress[1]"),
LayerEntry(LayerKind.SWITCH, "wave_progress[2]"),
@@ -345,7 +320,7 @@ internal object WindowHudEntries {
y = 10,
width = 64,
align = Align.LEFT,
- font = "window:small_caps/y9",
+ font = "window:small_caps/y43",
color = "#e0edff",
shadow = true,
bold = false,
@@ -362,7 +337,7 @@ internal object WindowHudEntries {
y = 0,
width = 64,
align = Align.LEFT,
- font = "window:small_caps/ym1",
+ font = "window:small_caps/y33",
color = "#ffffff",
shadow = true,
bold = false,
@@ -384,7 +359,7 @@ internal object WindowHudEntries {
anchorY = 0.5,
offsetX = 4,
offsetY = 0,
- rowOffset = -1,
+ rowOffset = 33,
),
layers = listOf(
LayerEntry(LayerKind.SLOT, "coords"),
@@ -405,7 +380,7 @@ internal object WindowHudEntries {
y = 0,
width = 80,
align = Align.RIGHT,
- font = "window:small_caps/ym1",
+ font = "window:small_caps/y33",
color = "#ffd75e",
shadow = true,
bold = false,
@@ -422,7 +397,7 @@ internal object WindowHudEntries {
y = 10,
width = 80,
align = Align.RIGHT,
- font = "window:small_caps/y9",
+ font = "window:small_caps/y43",
color = "#e0edff",
shadow = true,
bold = false,
@@ -444,7 +419,7 @@ internal object WindowHudEntries {
anchorY = 0.5,
offsetX = -4,
offsetY = 0,
- rowOffset = -1,
+ rowOffset = 33,
),
layers = listOf(
LayerEntry(LayerKind.SLOT, "objective"),
@@ -507,7 +482,7 @@ internal object WindowHudEntries {
y = 4,
width = 26,
align = Align.RIGHT,
- font = "window:small_caps/ym1",
+ font = "window:small_caps/y33",
color = "#ffd75e",
shadow = true,
bold = false,
@@ -524,7 +499,7 @@ internal object WindowHudEntries {
y = 4,
width = 34,
align = Align.LEFT,
- font = "window:small_caps/ym1",
+ font = "window:small_caps/y33",
color = "#8fb3d9",
shadow = true,
bold = false,
@@ -541,7 +516,7 @@ internal object WindowHudEntries {
y = 14,
width = 34,
align = Align.LEFT,
- font = "window:small_caps/y9",
+ font = "window:small_caps/y43",
color = "#8fb3d9",
shadow = true,
bold = false,
@@ -558,7 +533,7 @@ internal object WindowHudEntries {
y = 24,
width = 34,
align = Align.LEFT,
- font = "window:small_caps/y19",
+ font = "window:small_caps/y53",
color = "#8fb3d9",
shadow = true,
bold = false,
@@ -575,7 +550,7 @@ internal object WindowHudEntries {
y = 24,
width = 36,
align = Align.RIGHT,
- font = "window:small_caps/y19",
+ font = "window:small_caps/y53",
color = "#ffffff",
shadow = true,
bold = false,
@@ -592,7 +567,7 @@ internal object WindowHudEntries {
y = 14,
width = 36,
align = Align.RIGHT,
- font = "window:small_caps/y9",
+ font = "window:small_caps/y43",
color = "#80ff80",
shadow = true,
bold = false,
@@ -614,7 +589,7 @@ internal object WindowHudEntries {
anchorY = 0.0,
offsetX = 4,
offsetY = 4,
- rowOffset = -5,
+ rowOffset = 29,
),
layers = listOf(
LayerEntry(LayerKind.SLOT, "label_0"),
@@ -639,7 +614,7 @@ internal object WindowHudEntries {
y = 14,
width = 36,
align = Align.RIGHT,
- font = "window:small_caps/y9",
+ font = "window:small_caps/y43",
color = "#ffffff",
shadow = true,
bold = false,
@@ -656,7 +631,7 @@ internal object WindowHudEntries {
y = 4,
width = 34,
align = Align.LEFT,
- font = "window:small_caps/ym1",
+ font = "window:small_caps/y33",
color = "#8fb3d9",
shadow = true,
bold = false,
@@ -673,7 +648,7 @@ internal object WindowHudEntries {
y = 14,
width = 34,
align = Align.LEFT,
- font = "window:small_caps/y9",
+ font = "window:small_caps/y43",
color = "#8fb3d9",
shadow = true,
bold = false,
@@ -690,7 +665,7 @@ internal object WindowHudEntries {
y = 24,
width = 34,
align = Align.LEFT,
- font = "window:small_caps/y19",
+ font = "window:small_caps/y53",
color = "#8fb3d9",
shadow = true,
bold = false,
@@ -707,7 +682,7 @@ internal object WindowHudEntries {
y = 24,
width = 36,
align = Align.RIGHT,
- font = "window:small_caps/y19",
+ font = "window:small_caps/y53",
color = "#ffffff",
shadow = true,
bold = false,
@@ -724,7 +699,7 @@ internal object WindowHudEntries {
y = 4,
width = 36,
align = Align.RIGHT,
- font = "window:small_caps/ym1",
+ font = "window:small_caps/y33",
color = "#ffffff",
shadow = true,
bold = false,
@@ -746,7 +721,7 @@ internal object WindowHudEntries {
anchorY = 0.0,
offsetX = -4,
offsetY = 4,
- rowOffset = -5,
+ rowOffset = 29,
),
layers = listOf(
LayerEntry(LayerKind.SLOT, "label_0"),
@@ -757,5 +732,62 @@ internal object WindowHudEntries {
LayerEntry(LayerKind.SLOT, "latency"),
),
),
+ "status_wave_bar" to HudEntry(
+ surface = HudSurfaceEntry(
+ kind = "hud",
+ channel = "actionbar",
+ width = 182,
+ height = 5,
+ ),
+ static = "\uF050\uDB80\uDC03\uDB80\uDC05\uDB80\uDC06\uDB80\uDC08\uDB80\uDC09\uDB80\uDC0A",
+ slots = emptyMap(),
+ shader = HudShaderEntry(
+ staticMarker = "#9300e1",
+ sourceBottom = 59,
+ originX = 0.5,
+ originY = 1.0,
+ anchorX = 0.5,
+ anchorY = 1.0,
+ offsetX = 0,
+ offsetY = -24,
+ ),
+ art = mapOf(
+ "art/0" to ArtEntry(
+ static = "\uEF4C\uDB80\uDC0A\uF08E\uDB80\uDC0A\uEBA4\uDB80\uDC0A\uEECC\uDB80\uDC0A\uF37E\uDB80\uDC03\uDB80\uDC05\uDB80\uDC06\uDB80\uDC08\uDB80\uDC09\uDB80\uDC0A",
+ bar = BarEntry(
+ name = "wave_charge",
+ x = 0,
+ width = 182,
+ levels = listOf("\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D\uF39D", "\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E\uF37E", "\uEECC\uEECC\uEECC\uEECC\uEECC\uEECC\uEECC\uEECC\uEECC\uEECC\uEECC\uEECC\uEECC\uEECC\uEECC\uEECC\uEECC\uEECC\uEECC\uEECC\uEECC\uEECC\uEECC", "\uE4C0\uE4C0\uE4C0\uE4C0\uE4C0\uE4C0\uE4C0\uE4C0\uE4C0\uE4C0\uE4C0", "\uEBA4\uEBA4\uEBA4\uEBA4\uEBA4\uEBA4", "\uF08E\uF08E\uF08E", "\uF4C6", "\uEF4C"),
+ advances = mapOf(
+ "\uE4C0" to 9,
+ "\uEBA4" to 17,
+ "\uEECC" to 5,
+ "\uEF4C" to 129,
+ "\uF08E" to 33,
+ "\uF37E" to 3,
+ "\uF39D" to 2,
+ "\uF4C6" to 65,
+ ),
+ ),
+ tint = "tone",
+ tintMarkers = mapOf(
+ "danger" to "#85007a",
+ "ok" to "#a1009c",
+ "warn" to "#9400b2",
+ ),
+ ),
+ ),
+ tints = mapOf(
+ "tone" to mapOf(
+ "danger" to "#ff4d4d",
+ "ok" to "#3ddc84",
+ "warn" to "#ffb020",
+ ),
+ ),
+ layers = listOf(
+ LayerEntry(LayerKind.ART, "art/0"),
+ ),
+ ),
)
}
diff --git a/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowSprite.kt b/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowSprite.kt
index 5a58bf61..3e92622c 100644
--- a/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowSprite.kt
+++ b/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowSprite.kt
@@ -3,5 +3,8 @@ package com.chunkzero.window.example.generated
/** Runtime sprites of this Window pack, returned by sprite slot members. */
public enum class WindowSprite(public val id: String) {
+ RANK_1("rank_1"),
+ RANK_2("rank_2"),
+ RANK_3("rank_3"),
SEARCH_FIELD("search_field"),
}
diff --git a/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowSprites.kt b/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowSprites.kt
index b258df6b..bfa78b9d 100644
--- a/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowSprites.kt
+++ b/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowSprites.kt
@@ -15,6 +15,30 @@ internal object WindowSprites {
advance = 19,
glyph = "\uE409",
),
+ "rank_1" to SpriteEntry(
+ width = 9,
+ height = 11,
+ xOffset = 0,
+ glyphWidth = 9,
+ advance = 10,
+ glyph = "\uF1CB",
+ ),
+ "rank_2" to SpriteEntry(
+ width = 9,
+ height = 11,
+ xOffset = 0,
+ glyphWidth = 9,
+ advance = 10,
+ glyph = "\uE878",
+ ),
+ "rank_3" to SpriteEntry(
+ width = 9,
+ height = 11,
+ xOffset = 0,
+ glyphWidth = 9,
+ advance = 10,
+ glyph = "\uF7F5",
+ ),
"search_field" to SpriteEntry(
width = 110,
height = 16,
From 85bdb234b3a19d094140abb695205b90976002ec Mon Sep 17 00:00:00 2001
From: oglassdev <23040613+oglassdev@users.noreply.github.com>
Date: Sun, 11 Oct 2026 09:09:44 -0400
Subject: [PATCH 2/4] docs(example): describe the workshop, the commands, and
the new example sources
---
docs/AUTHORING.md | 5 +++--
example/README.md | 56 ++++++++++++++++++++++++++++++++---------------
2 files changed, 41 insertions(+), 20 deletions(-)
diff --git a/docs/AUTHORING.md b/docs/AUTHORING.md
index cb8058da..73ab9017 100644
--- a/docs/AUTHORING.md
+++ b/docs/AUTHORING.md
@@ -296,8 +296,9 @@ export default (
);
```
-The example pack's `window/` sources are a complete JSX project: `index.ts` lists the documents, `art.ts` declares its
-art, `handles.ts` declares the shop and search handles, and `shop.tsx`, `search.tsx`, and `status_hud.tsx` use them.
+The example pack's `window/` sources are a complete project: `index.ts` lists the documents, `art.ts` declares its art,
+`handles.ts` declares the windows' handles, `shop.tsx`, `search.tsx`, `upgrades.tsx`, and `status_hud.tsx` use them in
+JSX, and `clock_hud.ts` is a HUD written with the [`raw` API](#raw-api).
### Slot sections
diff --git a/example/README.md b/example/README.md
index f7cc34f8..d28a8f3a 100644
--- a/example/README.md
+++ b/example/README.md
@@ -11,23 +11,26 @@ example/
│ ├── src/
│ │ └── window/ # UI sources — stripped from the built pack
│ │ ├── index.ts # defineWindows entry: the sprite catalog, windows, HUDs, and previews
-│ │ ├── handles.ts # typed bindings shared by the shop and search windows
-│ │ ├── art.ts # inline art: icons, coin, vents, and the search field sprite
+│ │ ├── handles.ts # typed bindings shared by the windows
+│ │ ├── art.ts # inline art: icons, coin, vents, bars, and the rank sprites
│ │ ├── shop.tsx # paged catalog, selections, toggles, and buttons
│ │ ├── search.tsx # linked native-anvil catalog search
+│ │ ├── upgrades.tsx # themed workshop: styles, vars, a repeater of cards, choices, and a hotspot
│ │ ├── status_hud.tsx # the shader-relocated actionbar HUDs
-│ ├── build/previews/ # preview PNGs of the shop and HUDs, rewritten by every build
+│ │ └── clock_hud.ts # the clock HUD, written with the function-style raw API
+│ ├── build/previews/ # preview PNGs of the windows and HUDs, rewritten by every build
│ └── dist/ # build output: fonts, glyph atlas, shaders, zip
└── (server lives in ../jvm/example)
```
The Minestom server is the Gradle module `jvm/example`, which depends on `window-minestom`. `Main.kt` boots the server,
-`Market.kt` is a throwaway domain model, and `MyShop.kt` implements the **generated** `ShopView`. `MyStatusHud.kt`
-implements the **generated** HUD views. The pack uses `target: "minestom"`, so the shop view takes the `Player` it is
-shown to: `Main.kt` opens it with `MyShop(player, market).open()`. Generated HUD views take no player; the handwritten
-ones that need it take it themselves. `Main.kt` joins the status HUDs into one action-bar `HudStack` and resends it with
-`player.sendHud(stack)` every second, which re-renders their clocks and stays ahead of the action bar fading out. The
-bindings live under `generated/`, produced by the rpp plugin — committed, do not hand-edit.
+`Market.kt` and `Workshop.kt` are a throwaway domain model, and `MyShop.kt` and `MyUpgrades.kt` implement the
+**generated** `ShopView` and `UpgradesView`. `MyStatusHud.kt` implements the **generated** HUD views. The pack uses
+`target: "minestom"`, so a window view takes the `Player` it is shown to: `Main.kt` opens the shop with
+`MyShop(player, market).open()`. Generated HUD views take no player; the handwritten ones that need it take it
+themselves. `Main.kt` joins the status HUDs into one action-bar `HudStack` and resends it with `player.sendHud(stack)`
+every second, which re-renders their clocks and stays ahead of the action bar fading out. The bindings live under
+`generated/`, produced by the rpp plugin — committed, do not hand-edit.
## Run it
@@ -39,12 +42,26 @@ just example-run # boot the Minestom server on :25565
```
Then connect a 26.2 client. The catalog opens on spawn with 30 real item entries, category and sort choices,
-favorites/affordability toggles, paging, reactive selection and wallet text, plus buy/search/exit actions. Search opens
-a static anvil screen with the native rename field and back and search buttons; searching returns to the catalog
-filtered by the query, which the catalog's clear button resets and reopening search restores.
+favorites/affordability toggles, paging, reactive selection and wallet text, plus buy/search/workshop/exit actions.
+Search opens a static anvil screen with the native rename field and back and search buttons; searching returns to the
+catalog filtered by the query, which the catalog's clear button resets and reopening search restores.
+
+The workshop, under its own theme, sells three upgrades for the same coins: a click on a card buys the chosen amount of
+levels, each card shows its level as text and as a bar until it is maxed, and the rank badge takes the shared tone. Its
+levels last until the server stops.
The actionbar-backed HUD demo is disabled by default so the inventory examples stay visually focused. Enable it with
-`-Dwindow.hud.enabled=true`; use `-Dwindow.hud.spriteDebug=true` to print each HUD's static art to chat.
+`-Dwindow.hud.enabled=true`; use `-Dwindow.hud.spriteDebug=true` to print each HUD's static art to chat. The pack blanks
+vanilla's experience bar, and one of the HUDs draws its wave bar there.
+
+| command | does |
+| -------------------------------- | ---------------------------------------------------------------------- |
+| `/shop` | opens the catalog, like the hotbar emerald |
+| `/upgrades` | opens the workshop, like the catalog's Workshop button |
+| `/tone ` | pins the tone the HUDs and newly opened windows draw in; `auto` unpins |
+| `/bar ` | pins how full the wave bar and a newly opened shop's bar are |
+
+`rpp build` prints where the compiler's output bytes go and fails above the `size.budget` in `rpp.config.ts`.
Override the port with `-Dwindow.port=…`. The server also serves `dist/window-example.zip` as a required resource pack
at `http://127.0.0.1:25567/pack.zip`; override with `-Dwindow.pack=…`, `-Dwindow.pack.port=…`, or `-Dwindow.pack.url=…`
@@ -54,8 +71,11 @@ when testing from another machine.
The rpp plugin emits `internal` pack data (`WindowPackData`, `WindowFonts`, `WindowSpacers`, and friends), the public
`WindowDefinitions` and `WindowHudDefinitions`, and one public abstract class for each window/HUD. Each handle in
-`handles.ts` becomes a member of `ShopView`: `text` handles such as `balance` and `status` are functions returning a
-`Component`, `flag` handles such as `canBuy` are Boolean functions, `action` handles such as `buy` are `onBuy(click)`
-handlers, `products` is a `WindowCollection` that `MyShop` backs with a `WindowList`, and the `category`/`sort`
-selections and `favorites`/`affordable` toggles are UI-owned `var`s with `on…Changed` hooks. `MyStatusHud` implements
-each HUD's `text` handles. Omit one, rebuild, and the breakage surfaces at compile time.
+`handles.ts` becomes a member of the views that use it: `text` handles such as `balance` and `status` are functions
+returning a `Component`, `flag` handles such as `canBuy` are Boolean functions, `action` handles such as `buy` are
+`onBuy(click)` handlers, `products` is a `WindowCollection` that `MyShop` backs with a `WindowList`, and the
+`category`/`sort` selections and `favorites`/`affordable` toggles are UI-owned `var`s with `on…Changed` hooks. In
+`UpgradesView` the indexed handles take the card's index (`upgradeLevel(index)`, `onUpgrade(click)` with `click.index`),
+the `upgradeStage` value and the `rank` sprite return generated enums, and `upgradeProgress` and `tone` return a bar's
+fraction and a tint's palette entry. `MyStatusHud` implements each HUD's handles. Omit one, rebuild, and the breakage
+surfaces at compile time.
From 827c9e4e012fe94c45106ad80e99404633c0f659 Mon Sep 17 00:00:00 2001
From: oglassdev <23040613+oglassdev@users.noreply.github.com>
Date: Sun, 11 Oct 2026 09:12:54 -0400
Subject: [PATCH 3/4] fix(example): make the workshop preview and wording match
the server
---
example/README.md | 4 ++--
example/pack/src/window/index.ts | 4 ++--
example/pack/src/window/upgrades.tsx | 2 +-
.../com/chunkzero/window/example/generated/WindowEntries.kt | 2 +-
4 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/example/README.md b/example/README.md
index d28a8f3a..c0354977 100644
--- a/example/README.md
+++ b/example/README.md
@@ -47,8 +47,8 @@ Search opens a static anvil screen with the native rename field and back and sea
catalog filtered by the query, which the catalog's clear button resets and reopening search restores.
The workshop, under its own theme, sells three upgrades for the same coins: a click on a card buys the chosen amount of
-levels, each card shows its level as text and as a bar until it is maxed, and the rank badge takes the shared tone. Its
-levels last until the server stops.
+levels, each card shows its level as text and is marked new, shows a bar, or is marked maxed, and the rank badge takes
+the shared tone. Its levels last until the server stops.
The actionbar-backed HUD demo is disabled by default so the inventory examples stay visually focused. Enable it with
`-Dwindow.hud.enabled=true`; use `-Dwindow.hud.spriteDebug=true` to print each HUD's static art to chat. The pack blanks
diff --git a/example/pack/src/window/index.ts b/example/pack/src/window/index.ts
index 05c7cb98..9803d56f 100644
--- a/example/pack/src/window/index.ts
+++ b/example/pack/src/window/index.ts
@@ -37,14 +37,14 @@ export default defineWindows({
[
[handles.upgradeName, ["Drill", "Furnace", "Conveyor"]],
[handles.upgradeLevel, ["Lv 0/5", "Lv 3/5", "Lv 5/5"]],
- [handles.upgradeCost, ["120", "640", ""]],
+ [handles.upgradeCost, ["900", "810", ""]],
[handles.upgradeProgress, [0, 0.6, 1]],
[handles.upgradeStage, ["new", "open", "maxed"]],
[handles.amount, "five"],
[handles.balance, "2500"],
[handles.rank, "rank_2"],
[handles.rankName, "Journeyman"],
- [handles.status, "Bought Furnace x1"],
+ [handles.status, "8 of 15 levels"],
],
{ scale: 3 },
),
diff --git a/example/pack/src/window/upgrades.tsx b/example/pack/src/window/upgrades.tsx
index 44569c31..8efbc349 100644
--- a/example/pack/src/window/upgrades.tsx
+++ b/example/pack/src/window/upgrades.tsx
@@ -137,7 +137,7 @@ export default (
{/* It takes no clicks, so `hover` opts it into the hover outline. */}
-
+
diff --git a/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowEntries.kt b/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowEntries.kt
index bc084762..55ba1a17 100644
--- a/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowEntries.kt
+++ b/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/WindowEntries.kt
@@ -3575,7 +3575,7 @@ internal object WindowEntries {
itemModel = null,
tooltip = TooltipEntry(
title = "Balance",
- lines = listOf("Coins earned in the shop"),
+ lines = listOf("Coins shared with the shop"),
),
),
source = "region `region~0`",
From f3608d8cc268cc1ae1a6dc7df784eb89f78443b2 Mon Sep 17 00:00:00 2001
From: oglassdev <23040613+oglassdev@users.noreply.github.com>
Date: Sun, 11 Oct 2026 09:29:53 -0400
Subject: [PATCH 4/4] fix(example): make workshop cards follow their stage and
smooth the wave bar
Each workshop card is now a face per stage, so new and open cards are one
outlined hover target with "Click to upgrade" while a maxed card takes no
clicks and shows "Fully upgraded". The item icon and its handle are gone.
The shop's sort tabs are outlined like the category tabs, the bottom-centre
HUD loses its lamp row, and the status HUDs are resent every tick so the
wave bar advances smoothly.
---
example/README.md | 9 +-
example/pack/src/window/handles.ts | 2 -
example/pack/src/window/index.ts | 39 +-
example/pack/src/window/shop.tsx | 2 +-
example/pack/src/window/status_hud.tsx | 14 +-
example/pack/src/window/upgrades.tsx | 127 +--
.../com/chunkzero/window/example/Main.kt | 6 +-
.../chunkzero/window/example/MyStatusHud.kt | 3 -
.../chunkzero/window/example/MyUpgrades.kt | 17 +-
.../com/chunkzero/window/example/Workshop.kt | 8 +-
.../generated/StatusBottomCenterHud.kt | 11 -
.../window/example/generated/UpgradesView.kt | 53 +-
.../window/example/generated/WindowEntries.kt | 736 ++++++++++++++----
.../window/example/generated/WindowFonts.kt | 10 +-
.../example/generated/WindowHudEntries.kt | 266 +------
15 files changed, 777 insertions(+), 526 deletions(-)
diff --git a/example/README.md b/example/README.md
index c0354977..22d6d344 100644
--- a/example/README.md
+++ b/example/README.md
@@ -15,7 +15,7 @@ example/
│ │ ├── art.ts # inline art: icons, coin, vents, bars, and the rank sprites
│ │ ├── shop.tsx # paged catalog, selections, toggles, and buttons
│ │ ├── search.tsx # linked native-anvil catalog search
-│ │ ├── upgrades.tsx # themed workshop: styles, vars, a repeater of cards, choices, and a hotspot
+│ │ ├── upgrades.tsx # themed workshop: styles, vars, cards with a face per stage, choices, and a hotspot
│ │ ├── status_hud.tsx # the shader-relocated actionbar HUDs
│ │ └── clock_hud.ts # the clock HUD, written with the function-style raw API
│ ├── build/previews/ # preview PNGs of the windows and HUDs, rewritten by every build
@@ -29,8 +29,8 @@ The Minestom server is the Gradle module `jvm/example`, which depends on `window
`target: "minestom"`, so a window view takes the `Player` it is shown to: `Main.kt` opens the shop with
`MyShop(player, market).open()`. Generated HUD views take no player; the handwritten ones that need it take it
themselves. `Main.kt` joins the status HUDs into one action-bar `HudStack` and resends it with `player.sendHud(stack)`
-every second, which re-renders their clocks and stays ahead of the action bar fading out. The bindings live under
-`generated/`, produced by the rpp plugin — committed, do not hand-edit.
+every tick, which keeps the wave bar moving smoothly, re-renders the clocks, and stays ahead of the action bar fading
+out. The bindings live under `generated/`, produced by the rpp plugin — committed, do not hand-edit.
## Run it
@@ -48,7 +48,8 @@ catalog filtered by the query, which the catalog's clear button resets and reope
The workshop, under its own theme, sells three upgrades for the same coins: a click on a card buys the chosen amount of
levels, each card shows its level as text and is marked new, shows a bar, or is marked maxed, and the rank badge takes
-the shared tone. Its levels last until the server stops.
+the shared tone. A maxed card takes no clicks, so hovering it shows "Fully upgraded" and no outline. Its levels last
+until the server stops.
The actionbar-backed HUD demo is disabled by default so the inventory examples stay visually focused. Enable it with
`-Dwindow.hud.enabled=true`; use `-Dwindow.hud.spriteDebug=true` to print each HUD's static art to chat. The pack blanks
diff --git a/example/pack/src/window/handles.ts b/example/pack/src/window/handles.ts
index 5f1411da..c4986abe 100644
--- a/example/pack/src/window/handles.ts
+++ b/example/pack/src/window/handles.ts
@@ -4,7 +4,6 @@ import {
flag,
fraction,
input,
- items,
selection,
sprite,
text,
@@ -60,7 +59,6 @@ export const confirm = action("confirm");
export const UPGRADES = 3;
const cards = { shape: [UPGRADES] } as const;
export const upgrade = action("upgrade", cards);
-export const upgradeIcon = items("upgrade_icon", cards);
export const upgradeName = text("upgrade_name", cards);
export const upgradeLevel = text("upgrade_level", cards);
export const upgradeCost = text("upgrade_cost", cards);
diff --git a/example/pack/src/window/index.ts b/example/pack/src/window/index.ts
index 9803d56f..3e5219fb 100644
--- a/example/pack/src/window/index.ts
+++ b/example/pack/src/window/index.ts
@@ -1,4 +1,5 @@
import { defineWindows, preview } from "plugin:window/ui";
+import type { PreviewOptions } from "plugin:window/ui";
import { art } from "plugin:window/theme/industrial";
import { ranks } from "./art.ts";
@@ -9,6 +10,25 @@ import shop from "./shop.tsx";
import statusHuds, { bottomCenter, coins, hint, power, rate, topLeft, waveBar, waveCharge } from "./status_hud.tsx";
import upgrades from "./upgrades.tsx";
+/** A preview of the workshop with one card in each stage. */
+const upgradesPreview = (options: PreviewOptions) =>
+ preview(
+ upgrades,
+ [
+ [handles.upgradeName, ["Drill", "Furnace", "Conveyor"]],
+ [handles.upgradeLevel, ["Lv 0/5", "Lv 3/5", "Lv 5/5"]],
+ [handles.upgradeCost, ["900", "810", ""]],
+ [handles.upgradeProgress, [0, 0.6, 1]],
+ [handles.upgradeStage, ["new", "open", "maxed"]],
+ [handles.amount, "five"],
+ [handles.balance, "2500"],
+ [handles.rank, "rank_2"],
+ [handles.rankName, "Journeyman"],
+ [handles.status, "8 of 15 levels"],
+ ],
+ options,
+ );
+
export default defineWindows({
sprites: { searchField: art.inputField, ...ranks },
windows: [shop, search, upgrades],
@@ -32,24 +52,9 @@ export default defineWindows({
[handles.canBuy, true],
[handles.buyLabel, "Buy 210"],
]),
- preview(
- upgrades,
- [
- [handles.upgradeName, ["Drill", "Furnace", "Conveyor"]],
- [handles.upgradeLevel, ["Lv 0/5", "Lv 3/5", "Lv 5/5"]],
- [handles.upgradeCost, ["900", "810", ""]],
- [handles.upgradeProgress, [0, 0.6, 1]],
- [handles.upgradeStage, ["new", "open", "maxed"]],
- [handles.amount, "five"],
- [handles.balance, "2500"],
- [handles.rank, "rank_2"],
- [handles.rankName, "Journeyman"],
- [handles.status, "8 of 15 levels"],
- ],
- { scale: 3 },
- ),
+ upgradesPreview({ scale: 3 }),
// The same window with its regions outlined.
- preview(upgrades, [[handles.rank, "rank_3"]], { name: "upgrades_regions", outlines: true }),
+ upgradesPreview({ name: "upgrades_regions", outlines: true }),
preview(
topLeft,
[
diff --git a/example/pack/src/window/shop.tsx b/example/pack/src/window/shop.tsx
index 8478b68a..7f2e3c63 100644
--- a/example/pack/src/window/shop.tsx
+++ b/example/pack/src/window/shop.tsx
@@ -123,7 +123,7 @@ export default (
- sortTooltips[value]} hover={false}>
+ sortTooltips[value]}>
{(_, i) => }
diff --git a/example/pack/src/window/status_hud.tsx b/example/pack/src/window/status_hud.tsx
index 5b50e88e..5e42f3d7 100644
--- a/example/pack/src/window/status_hud.tsx
+++ b/example/pack/src/window/status_hud.tsx
@@ -1,6 +1,6 @@
-import { Bar, Box, Hud, Image, Switch, Text } from "plugin:window/ui";
+import { Bar, Box, Hud, Image, Text } from "plugin:window/ui";
import type { TextProps } from "plugin:window/ui";
-import { flag, fraction, text } from "plugin:window/bind";
+import { fraction, text } from "plugin:window/bind";
import type { TextHandle } from "plugin:window/bind";
import { Row, art } from "plugin:window/theme/industrial";
@@ -19,7 +19,6 @@ const altitude = text("altitude");
const objective = text("objective");
const stock = text("stock");
export const hint = text("hint");
-const waveProgress = flag("wave_progress", { shape: [10] });
/** How far the current wave's countdown has run. */
export const waveCharge = fraction("wave_charge");
@@ -90,17 +89,10 @@ const rightSide = (
);
export const bottomCenter = (
-
+
-
- {Array.from({ length: 10 }, (_, i) => (
-
- {{ true: , false: }}
-
- ))}
-
);
diff --git a/example/pack/src/window/upgrades.tsx b/example/pack/src/window/upgrades.tsx
index 8efbc349..b58b41b0 100644
--- a/example/pack/src/window/upgrades.tsx
+++ b/example/pack/src/window/upgrades.tsx
@@ -1,4 +1,17 @@
-import { Bar, Box, Case, Image, Switch, Text, create, createTheme, defineVars, variants } from "plugin:window/ui";
+import {
+ Bar,
+ Box,
+ Case,
+ Image,
+ Region,
+ Switch,
+ Text,
+ create,
+ createTheme,
+ defineVars,
+ variants,
+} from "plugin:window/ui";
+import type { Child } from "plugin:window/ui";
import {
Button,
Center,
@@ -9,7 +22,6 @@ import {
Hotbar,
Hotspot,
Player,
- Repeater,
Row,
Spacer,
Window,
@@ -29,7 +41,6 @@ import {
tone,
upgrade,
upgradeCost,
- upgradeIcon,
upgradeLevel,
upgradeName,
upgradeProgress,
@@ -51,26 +62,84 @@ const forge = createTheme(colors, {
const ink = defineVars({ name: "#ffe2b8", muted: "#f2c9a0", gold: "#ffb20b", pad: 4 });
const s = create({
- card: variants({ cell: { frame: art.panel, gap: 2 } }),
+ card: { frame: art.panel, justify: "center", align: "center", gap: 3 },
amount: variants({
base: { text: { color: ink.name } },
selected: { frame: art.buttonAccent, text: { color: "#2a1200", shadow: false } },
}),
strip: { frame: art.recess, padding: ink.pad, gap: 4 },
name: { color: ink.name, align: "center" },
+ level: { color: ink.muted, align: "center" },
muted: { color: ink.muted },
});
/** What the next purchase of upgrade `index` costs. */
function Cost(props: { index: number }) {
return (
-
+
);
}
+/**
+ * One face of upgrade `index`'s card: its name and level above `children`. `buy` makes the face take clicks, which
+ * also outlines it on hover; a face without it only shows its tooltip.
+ */
+function Face(props: { index: number; tooltip: string; buy?: boolean; children: Child }) {
+ const i = props.index;
+ return [
+ ,
+ ,
+
+ {props.children}
+ ,
+ ,
+ ];
+}
+
+/** An upgrade card over 3x3 slots. Each stage is its own face, so a maxed card takes no clicks. */
+function Card(props: { index: number }) {
+ const i = props.index;
+ return (
+
+
+
+
+ New
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Maxed
+
+
+
+
+
+
+ );
+}
+
const amounts = {
one: { label: "x1", tooltip: "Buy one level" },
five: { label: "x5", tooltip: "Buy five levels" },
@@ -80,51 +149,9 @@ const amounts = {
export default (
-
- {(i) => [
- ,
-
-
-
-
-
-
- New
-
-
-
-
-
-
-
-
-
-
-
-
- Maxed
-
-
-
-
- ,
- ]}
-
+ {Array.from({ length: UPGRADES }, (_, i) => (
+
+ ))}
diff --git a/jvm/example/src/main/kotlin/com/chunkzero/window/example/Main.kt b/jvm/example/src/main/kotlin/com/chunkzero/window/example/Main.kt
index dd81f2e1..b139f2aa 100644
--- a/jvm/example/src/main/kotlin/com/chunkzero/window/example/Main.kt
+++ b/jvm/example/src/main/kotlin/com/chunkzero/window/example/Main.kt
@@ -148,8 +148,8 @@ private fun openExampleUi(
}
}
-// The action bar fades after about 60 ticks, so the merged status line is resent every second; each send renders the
-// HUDs' current clock and player values.
+// The merged status line is rendered and resent every tick, so the wave bar advances smoothly and the action bar,
+// which fades after about 60 ticks, never does.
private fun showHuds(
player: Player,
huds: List,
@@ -159,7 +159,7 @@ private fun showHuds(
player.scheduler().submitTask {
if (!player.isOnline) return@submitTask TaskSchedule.stop()
player.sendHud(stack)
- TaskSchedule.seconds(1)
+ TaskSchedule.nextTick()
}
}
diff --git a/jvm/example/src/main/kotlin/com/chunkzero/window/example/MyStatusHud.kt b/jvm/example/src/main/kotlin/com/chunkzero/window/example/MyStatusHud.kt
index 99ead76e..9c283415 100644
--- a/jvm/example/src/main/kotlin/com/chunkzero/window/example/MyStatusHud.kt
+++ b/jvm/example/src/main/kotlin/com/chunkzero/window/example/MyStatusHud.kt
@@ -67,9 +67,6 @@ class MyStatusBottomCenterHud(
override fun hint(): Component = Component.text("Next wave in ${(waveRemaining(startedAt) + 999) / 1000}s")
override fun tone(): Tone = Tone.entries.first { it.value == waveTone(player, startedAt) }
-
- override fun waveProgress(index: Int): Boolean =
- (System.currentTimeMillis() - startedAt) % WAVE_MILLIS * 10 / WAVE_MILLIS >= index
}
/** Window's bar in place of vanilla's experience bar. */
diff --git a/jvm/example/src/main/kotlin/com/chunkzero/window/example/MyUpgrades.kt b/jvm/example/src/main/kotlin/com/chunkzero/window/example/MyUpgrades.kt
index 4a1f7d1a..7732dfea 100644
--- a/jvm/example/src/main/kotlin/com/chunkzero/window/example/MyUpgrades.kt
+++ b/jvm/example/src/main/kotlin/com/chunkzero/window/example/MyUpgrades.kt
@@ -5,9 +5,7 @@ import com.chunkzero.window.IndexedClick
import com.chunkzero.window.example.generated.UpgradesView
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor
-import net.kyori.adventure.text.format.TextDecoration
import net.minestom.server.entity.Player
-import net.minestom.server.item.ItemStack
/** The workshop: upgrade cards bought with the [market]'s coins, in the amount the player chose. */
class MyUpgrades(
@@ -24,15 +22,6 @@ class MyUpgrades(
override fun balance(): Component = Component.text(coins)
- override fun upgradeIcon(index: Int): ItemStack {
- val upgrade = Upgrade.entries[index]
- return ItemStack
- .builder(upgrade.material)
- .customName(Component.text(upgrade.title, NamedTextColor.GOLD).decoration(TextDecoration.ITALIC, false))
- .lore(Component.text("Click to upgrade", NamedTextColor.GRAY).decoration(TextDecoration.ITALIC, false))
- .build()
- }
-
override fun upgradeName(index: Int): Component = Component.text(Upgrade.entries[index].title)
override fun upgradeLevel(index: Int): Component = Component.text("Lv ${levels[index]}/${Upgrade.MAX_LEVEL}")
@@ -60,11 +49,7 @@ class MyUpgrades(
override fun onUpgrade(click: IndexedClick) {
val upgrade = Upgrade.entries[click.index]
- val offer = offer(click.index)
- if (offer == null) {
- feedback = "${upgrade.title} is maxed"
- return
- }
+ val offer = offer(click.index) ?: return
val newBalance = market.purchase(player, offer.price)
if (newBalance == null) {
feedback = "Need ${offer.price - coins} more"
diff --git a/jvm/example/src/main/kotlin/com/chunkzero/window/example/Workshop.kt b/jvm/example/src/main/kotlin/com/chunkzero/window/example/Workshop.kt
index 6e869ac2..94a25aec 100644
--- a/jvm/example/src/main/kotlin/com/chunkzero/window/example/Workshop.kt
+++ b/jvm/example/src/main/kotlin/com/chunkzero/window/example/Workshop.kt
@@ -1,19 +1,17 @@
package com.chunkzero.window.example
import net.minestom.server.entity.Player
-import net.minestom.server.item.Material
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
/** A foundry upgrade; each level costs [baseCost] more than the one before. */
enum class Upgrade(
val title: String,
- val material: Material,
private val baseCost: Int,
) {
- DRILL("Drill", Material.PISTON, 60),
- FURNACE("Furnace", Material.BLAST_FURNACE, 90),
- CONVEYOR("Conveyor", Material.POWERED_RAIL, 120),
+ DRILL("Drill", 60),
+ FURNACE("Furnace", 90),
+ CONVEYOR("Conveyor", 120),
;
/** What the next [count] levels above [level] cost together. */
diff --git a/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/StatusBottomCenterHud.kt b/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/StatusBottomCenterHud.kt
index fff71cab..94ad6331 100644
--- a/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/StatusBottomCenterHud.kt
+++ b/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/StatusBottomCenterHud.kt
@@ -20,19 +20,8 @@ public abstract class StatusBottomCenterHud : HudView(WindowHudDefinitions.statu
/** The palette entry the `tone` tint draws in. */
protected abstract fun tone(): Tone
- /** The `wave_progress` flag. */
- protected abstract fun waveProgress(index: Int): Boolean
-
final override fun HudScope.bind() {
slot("hint") { hint() }
tint("tone") { tone().value }
- for (index in 0 until WAVE_PROGRESS_SIZE) {
- switch("wave_progress[$index]") { waveProgress(index).toString() }
- }
- }
-
- public companion object {
- /** A shape dimension of `wave_progress`. */
- public const val WAVE_PROGRESS_SIZE: Int = 10
}
}
diff --git a/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/UpgradesView.kt b/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/UpgradesView.kt
index a350d36c..166783a4 100644
--- a/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/UpgradesView.kt
+++ b/jvm/example/src/main/kotlin/com/chunkzero/window/example/generated/UpgradesView.kt
@@ -63,9 +63,6 @@ public abstract class UpgradesView(protected val player: Player) : WindowView
- onUpgrade(IndexedClick(click.slot, index, click.shift, click.right))
- }
+ button("upgrade[0]") { click ->
+ onUpgrade(IndexedClick(click.slot, 0, click.shift, click.right))
+ }
+ button("upgrade[0]~2") { click ->
+ onUpgrade(IndexedClick(click.slot, 0, click.shift, click.right))
+ }
+ button("upgrade[1]") { click ->
+ onUpgrade(IndexedClick(click.slot, 1, click.shift, click.right))
+ }
+ button("upgrade[1]~2") { click ->
+ onUpgrade(IndexedClick(click.slot, 1, click.shift, click.right))
+ }
+ button("upgrade[2]") { click ->
+ onUpgrade(IndexedClick(click.slot, 2, click.shift, click.right))
+ }
+ button("upgrade[2]~2") { click ->
+ onUpgrade(IndexedClick(click.slot, 2, click.shift, click.right))
}
slot("upgrade_cost[0]") { upgradeCost(0) }
slot("upgrade_cost[0]~2") { upgradeCost(0) }
@@ -142,15 +152,24 @@ public abstract class UpgradesView(protected val player: Player) : WindowView