@mauricode/token-derby 2.4.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +759 -92
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -113,27 +113,64 @@ function tagColor(tag, colors) {
|
|
|
113
113
|
return null;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
// src/
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
116
|
+
// src/hats/render.ts
|
|
117
|
+
function composeHatGrid(baseSprite, hat, variantIdx, horseColors) {
|
|
118
|
+
const horseH = baseSprite.length;
|
|
119
|
+
const horseW = baseSprite[0]?.length ?? 0;
|
|
120
|
+
const hatH = hat.rows.length;
|
|
121
|
+
const hatW = hat.width;
|
|
122
|
+
const ext = Math.max(0, hatH - 4);
|
|
123
|
+
const canvasLeft = Math.min(0, hat.anchor_x);
|
|
124
|
+
const canvasRight = Math.max(horseW, hat.anchor_x + hatW);
|
|
125
|
+
const canvasW = canvasRight - canvasLeft;
|
|
126
|
+
const horseOffsetX = -canvasLeft;
|
|
127
|
+
const grid = Array.from(
|
|
128
|
+
{ length: horseH + ext },
|
|
129
|
+
() => Array(canvasW).fill(null)
|
|
130
|
+
);
|
|
131
|
+
for (let y = 0; y < horseH; y++) {
|
|
132
|
+
for (let x = 0; x < horseW; x++) {
|
|
133
|
+
const tag = baseSprite[y][x];
|
|
134
|
+
if (tag === null) continue;
|
|
135
|
+
grid[y + ext][x + horseOffsetX] = tagToColor(tag, horseColors);
|
|
133
136
|
}
|
|
134
137
|
}
|
|
135
|
-
|
|
138
|
+
const hatColors = hatColorsFor(hat, variantIdx);
|
|
139
|
+
for (let y = 0; y < hatH; y++) {
|
|
140
|
+
const row = hat.rows[y];
|
|
141
|
+
for (let x = 0; x < hatW; x++) {
|
|
142
|
+
const ch = row[x];
|
|
143
|
+
if (ch === "." || ch === void 0) continue;
|
|
144
|
+
const gx = hat.anchor_x + x + horseOffsetX;
|
|
145
|
+
if (gx < 0 || gx >= canvasW) continue;
|
|
146
|
+
const color = ch === "A" ? hatColors.A : hatColors.Q ?? hatColors.A;
|
|
147
|
+
grid[y][gx] = color;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return { grid, offsetX: canvasLeft };
|
|
136
151
|
}
|
|
152
|
+
function tagToColor(tag, c) {
|
|
153
|
+
switch (tag) {
|
|
154
|
+
case "B":
|
|
155
|
+
return c.body;
|
|
156
|
+
case "M":
|
|
157
|
+
return c.mane;
|
|
158
|
+
case "T":
|
|
159
|
+
return c.tail;
|
|
160
|
+
case "S":
|
|
161
|
+
return c.saddle;
|
|
162
|
+
case "E":
|
|
163
|
+
return FIXED_COLORS.E;
|
|
164
|
+
case "H":
|
|
165
|
+
return FIXED_COLORS.H;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function hatColorsFor(hat, variantIdx) {
|
|
169
|
+
if (hat.rarity === "legendary") return hat.colors;
|
|
170
|
+
return hat.variants[variantIdx] ?? hat.variants[0];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// src/ui/half-blocks.ts
|
|
137
174
|
var RESET = "\x1B[0m";
|
|
138
175
|
function hexToRgb(hex) {
|
|
139
176
|
const h = hex.replace("#", "");
|
|
@@ -151,6 +188,52 @@ function ansiBg(hex) {
|
|
|
151
188
|
const [r, g, b] = hexToRgb(hex);
|
|
152
189
|
return `\x1B[48;2;${r};${g};${b}m`;
|
|
153
190
|
}
|
|
191
|
+
function hexGridToHalfBlocks(grid) {
|
|
192
|
+
const W = grid[0]?.length ?? 0;
|
|
193
|
+
const padded = grid.length % 2 === 0 ? grid : [...grid, Array(W).fill(null)];
|
|
194
|
+
const lines = [];
|
|
195
|
+
for (let y = 0; y < padded.length; y += 2) {
|
|
196
|
+
let line = "";
|
|
197
|
+
for (let x = 0; x < W; x++) {
|
|
198
|
+
const top = padded[y][x] ?? null;
|
|
199
|
+
const bot = padded[y + 1][x] ?? null;
|
|
200
|
+
if (top === null && bot === null) line += " ";
|
|
201
|
+
else if (top !== null && bot !== null) line += ansiFg(top) + ansiBg(bot) + "\u2580" + RESET;
|
|
202
|
+
else if (top !== null) line += ansiFg(top) + "\u2580" + RESET;
|
|
203
|
+
else line += ansiFg(bot) + "\u2584" + RESET;
|
|
204
|
+
}
|
|
205
|
+
lines.push(line);
|
|
206
|
+
}
|
|
207
|
+
return lines;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/ui/HorseSprite.tsx
|
|
211
|
+
import { jsx } from "react/jsx-runtime";
|
|
212
|
+
function HorseSprite({ sprite, colors, hat }) {
|
|
213
|
+
if (!hat) {
|
|
214
|
+
const grid2 = renderSprite(sprite, colors);
|
|
215
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: grid2.map((row, y) => /* @__PURE__ */ jsx(Text, { children: rowToAnsi(row) }, y)) });
|
|
216
|
+
}
|
|
217
|
+
const { grid } = composeHatGrid(sprite, hat.hat, hat.variant ?? 0, colors);
|
|
218
|
+
const lines = hexGridToHalfBlocks(grid);
|
|
219
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: lines.map((line, i) => /* @__PURE__ */ jsx(Text, { children: line }, i)) });
|
|
220
|
+
}
|
|
221
|
+
var RESET2 = "\x1B[0m";
|
|
222
|
+
function rowToAnsi(row) {
|
|
223
|
+
let out = "";
|
|
224
|
+
for (const cell of row) {
|
|
225
|
+
if (cell.top === null && cell.bottom === null) {
|
|
226
|
+
out += " ";
|
|
227
|
+
} else if (cell.top !== null && cell.bottom !== null) {
|
|
228
|
+
out += ansiFg(cell.top) + ansiBg(cell.bottom) + "\u2580" + RESET2;
|
|
229
|
+
} else if (cell.top !== null) {
|
|
230
|
+
out += ansiFg(cell.top) + "\u2580" + RESET2;
|
|
231
|
+
} else {
|
|
232
|
+
out += ansiFg(cell.bottom) + "\u2584" + RESET2;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return out;
|
|
236
|
+
}
|
|
154
237
|
|
|
155
238
|
// src/ui/palette.ts
|
|
156
239
|
var SLOTS = ["body", "mane", "tail", "saddle"];
|
|
@@ -337,8 +420,8 @@ var HEARTBEAT_RETRY_DELAYS_MS = [1e3, 2e3, 4e3, 8e3, 15e3];
|
|
|
337
420
|
// src/version.ts
|
|
338
421
|
import { createRequire } from "module";
|
|
339
422
|
function readVersion() {
|
|
340
|
-
if ("2.
|
|
341
|
-
return "2.
|
|
423
|
+
if ("2.5.0".length > 0) {
|
|
424
|
+
return "2.5.0";
|
|
342
425
|
}
|
|
343
426
|
try {
|
|
344
427
|
const req = createRequire(import.meta.url);
|
|
@@ -466,6 +549,56 @@ var MIDRACE_THRESHOLDS = {
|
|
|
466
549
|
// sliding window for recent_events
|
|
467
550
|
};
|
|
468
551
|
|
|
552
|
+
// ../shared/dist/hats.js
|
|
553
|
+
function hatById(id) {
|
|
554
|
+
return HATS.find((h) => h.id === id);
|
|
555
|
+
}
|
|
556
|
+
var HATS = [
|
|
557
|
+
// ── COMMON (18) ────────────────────────────────────────────────────────
|
|
558
|
+
{ id: "flat_cap", name: "Flat Cap", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...........", "....AAAA...", "...AAAAAA..", "..AAAAAAA.."], variants: [{ A: "#8B6914" }, { A: "#12301b" }, { A: "#aab5cb" }, { A: "#bdcbaa" }] },
|
|
559
|
+
{ id: "bucket_hat", name: "Bucket Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...QQQQQ...", "...AAAAA...", "...AAAAA...", ".AAAAAAAAA."], variants: [{ A: "#CC2200", Q: "#FFFFFF" }, { A: "#0007cc", Q: "#FFFFFF" }, { A: "#becc00", Q: "#4d3dc7" }, { A: "#cc00c5", Q: "#d6d6dc" }] },
|
|
560
|
+
{ id: "beanie", name: "Beanie", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "....AAA....", "...AAAAA...", "...AAAAA...", "..AAAAAAA.."], variants: [{ A: "#4A7C59" }, { A: "#b21f35" }, { A: "#ec9418" }, { A: "#3a17e6" }] },
|
|
561
|
+
{ id: "stetson", name: "Stetson", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...AA.AA...", "...AAAAA...", "...AAAAA...", "A..AAAAA..A", "AAAAAAAAAAA", "AAAAAAAAAAA"], variants: [{ A: "#C49A00" }, { A: "#272004" }, { A: "#040404" }, { A: "#373606" }] },
|
|
562
|
+
{ id: "party_hat", name: "Party Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", ".....A.....", "....AAA....", "....AQA....", "...AQAQA...", "...AAAAA..."], variants: [{ A: "#FF69B4", Q: "#FFD700" }, { A: "#c46bff", Q: "#ff000d" }, { A: "#fdff6b", Q: "#0400ff" }, { A: "#6bff97", Q: "#05050a" }] },
|
|
563
|
+
{ id: "fez", name: "Fez", rarity: "common", width: 11, anchor_x: 22, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "....Q......", "....AAA....", "....AAA....", "....AAA...."], variants: [{ A: "#CC0000", Q: "#8B0000" }, { A: "#CC0000", Q: "#f5e5e5" }, { A: "#CC0000", Q: "#f1e209" }, { A: "#CC0000", Q: "#0956f1" }] },
|
|
564
|
+
{ id: "sailor_hat", name: "Sailor Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...QQQQQ...", "..AAAAAAA..", "..QQQQQQQ..", "..AAAAAAA.."], variants: [{ A: "#FFFFFF", Q: "#000080" }, { A: "#FFFFFF", Q: "#c0c0ed" }, { A: "#FFFFFF", Q: "#00000a" }, { A: "#FFFFFF", Q: "#0505ef" }] },
|
|
565
|
+
{ id: "newsboy_cap", name: "Newsboy Cap", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...AAAAA...", "..AAAQAAA..", "..AAAAAAA..", "..AAAAAA..."], variants: [{ A: "#5C4033", Q: "#3D2B1F" }, { A: "#e96020", Q: "#131211" }, { A: "#342c28", Q: "#131211" }, { A: "#0a0300", Q: "#ddab78" }] },
|
|
566
|
+
{ id: "tam_o_shanter", name: "Tam O'Shanter", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", ".....Q.....", "....AAA....", "..AAAAAAA..", "..AAAAAAA..", "...AAAAA..."], variants: [{ A: "#006400", Q: "#FF0000" }, { A: "#ff0000", Q: "#016400" }] },
|
|
567
|
+
{ id: "trucker_cap", name: "Trucker Cap", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...AAQQ....", "...AAQQ....", "..AAQQQAA..", "..AAQQQAA.."], variants: [{ A: "#2196F3", Q: "#FFFFFF" }, { A: "#f41fe3", Q: "#FFFFFF" }, { A: "#1ff45f", Q: "#FFFFFF" }] },
|
|
568
|
+
{ id: "hard_hat", name: "Hard Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "....AAA....", "...AAAAA...", "..AAAAAAA..", "..AAAAAAAAA"], variants: [{ A: "#FFD600" }, { A: "#d74c1d" }] },
|
|
569
|
+
{ id: "chef_toque", name: "Chef's Toque", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...AAAAA...", "..AAQAQAA..", "...AQAQA...", "...AQAQA...", "...AQAQA...", "...AAAAA..."], variants: [{ A: "#FFFFFF", Q: "#f9f9f9" }] },
|
|
570
|
+
{ id: "bobble_hat", name: "Bobble Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", ".....Q.....", "...AAAAA...", "...AAAAA...", "..AAAAAAA.."], variants: [{ A: "#C62828", Q: "#FFFFFF" }, { A: "#293bc7", Q: "#FFFFFF" }, { A: "#29c775", Q: "#231f1f" }, { A: "#a529c7", Q: "#d50707" }] },
|
|
571
|
+
{ id: "cowboy_hat", name: "Cowboy Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...AAAAA...", "...AAAAA...", "A.AAAAAAA.A", ".AAAAAAAAA."], variants: [{ A: "#8B4513" }, { A: "#0a0400" }, { A: "#956c50" }] },
|
|
572
|
+
{ id: "baseball_cap", name: "Baseball Cap", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...AAAAA...", "..AAAAAA...", "..AAAAAAA..", "..AAAAAAAAA"], variants: [{ A: "#1565C0" }, { A: "#8515c1" }, { A: "#15c157" }, { A: "#ef2d0b" }] },
|
|
573
|
+
{ id: "tinfoil_hat", name: "Tinfoil Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", ".....A.....", "....AAA....", "...AAAAA...", "..AAAAAAA.."], variants: [{ A: "#B0BEC5" }, { A: "#6a6c6c" }] },
|
|
574
|
+
{ id: "dunce_cap", name: "Dunce Cap", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", ".....Q.....", "....QAQ....", "...QAAAQ...", "...QAAAQ...", "..QAAAAAQ..", "..AAAAAAA.."], variants: [{ A: "#F8F8F8", Q: "#F44336" }] },
|
|
575
|
+
{ id: "mini_top_hat", name: "Mini Top Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...AAAAA...", "...AAAAA...", "...AAAAA...", "..AAAAAAA..", "..AAAAAAA.."], variants: [{ A: "#212121" }, { A: "#626060" }] },
|
|
576
|
+
// ── RARE (10) ──────────────────────────────────────────────────────────
|
|
577
|
+
{ id: "bicorne", name: "Bicorne", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "..AAAQQQQ..", "..AAAAAAA..", "..AAAAAAA..", "...QQQQQ..."], variants: [{ A: "#1A237E", Q: "#FFD700" }, { A: "#01052d", Q: "#544803" }] },
|
|
578
|
+
{ id: "viking_helmet", name: "Viking Helmet", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "..A.AAA.A..", "..AAAAAAA..", "..AAAAAAA..", "..AQQQQQA.."], variants: [{ A: "#9E9E9E", Q: "#8D6E63" }, { A: "#dcdbdb", Q: "#bf3908" }] },
|
|
579
|
+
{ id: "jesters_cap", name: "Jester's Cap", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "..AQAQAQA..", "..AQAQAQA..", "...AAAAA...", "...AAAAA..."], variants: [{ A: "#E53935", Q: "#FFD600" }, { A: "#e5a434", Q: "#0040ff" }, { A: "#34e537", Q: "#eeff00" }, { A: "#e234e5", Q: "#00ff40" }] },
|
|
580
|
+
{ id: "plague_doctor", name: "Plague Doctor Beak", rarity: "rare", width: 11, anchor_x: 24, rows: ["...........", "...........", "...........", "...........", "....QQQ....", "...QAAAQQ..", "...QQQQQQQ.", "..QQQQQ.QQQ", "..QQQQ....Q", "..QQQQ....."], variants: [{ A: "#F5F5F5", Q: "#795548" }, { A: "#e1dbdb", Q: "#080707" }] },
|
|
581
|
+
{ id: "morion", name: "Conquistador Morion", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", ".....Q.....", "...QQQQQ...", "...QAAAQ...", ".A..AAA..A.", "..AAQQQAA.."], variants: [{ A: "#B0BEC5", Q: "#FFD600" }, { A: "#0a4768", Q: "#e6c10a" }] },
|
|
582
|
+
{ id: "shako", name: "Shako", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "........Q..", "........Q..", "........Q..", "........Q..", "..AAAAAAA..", "..AAAAAAA..", "..AAAAAAA..", "..AQQQQQA.."], variants: [{ A: "#45464f", Q: "#00ff2a" }, { A: "#45464f", Q: "#ff4d00" }, { A: "#45464f", Q: "#ff0019" }, { A: "#45464f", Q: "#fff700" }] },
|
|
583
|
+
{ id: "centurion_helm", name: "Centurion Helm", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "....QQQ....", "...QQQQQQ..", "..QQQQQQQQ.", ".QQ..A...Q.", ".Q..AAA....", "...AAAAA...", "..AAAAAAA..", "..AAAAAAA.."], variants: [{ A: "#B0BEC5", Q: "#C62828" }, { A: "#2f3131", Q: "#C62828" }] },
|
|
584
|
+
{ id: "papal_mitre", name: "Papal Mitre", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "....QQQ....", "...QQQQQ...", "...QAAAQ...", "..AAAAAAA.."], variants: [{ A: "#FFFFFF", Q: "#FFD700" }] },
|
|
585
|
+
{ id: "headdress", name: "Headdress", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "..AQAQAQA..", "..AQAQAQA..", "..AAAAAAA..", "...QQQQQ..."], variants: [{ A: "#FF8F00", Q: "#1565C0" }] },
|
|
586
|
+
{ id: "sombrero", name: "Sombrero", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", ".....A.....", "....AAA....", "...AAAAA...", "...AQAQA...", "AAAAAAAAAAA"], variants: [{ A: "#F57F17", Q: "#BF360C" }, { A: "#f8b377", Q: "#3e1204" }, { A: "#d6f877", Q: "#04133e" }, { A: "#d6f877", Q: "#04133e" }] },
|
|
587
|
+
// ── EPIC (6) ───────────────────────────────────────────────────────────
|
|
588
|
+
{ id: "papal_tiara", name: "Papal Tiara", rarity: "epic", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "....QQ.....", "...QQQQQ...", "...QAAQQ...", "...QQQQQ...", "..AAAAAAA..", "..AQQQQQA.."], variants: [{ A: "#FFFFFF", Q: "#FFD700" }, { A: "#b18e10", Q: "#f3f3f1" }] },
|
|
589
|
+
{ id: "samurai_kabuto", name: "Samurai Kabuto", rarity: "epic", width: 11, anchor_x: 23, rows: ["....Q...Q..", "....QQ.QQ..", ".....QQQ...", ".......Q...", "...AAAAQ...", "..AAAAAQQ..", "..AAAAAQQ..", ".AAAAAAAQ..", "AAAAAAAAQ..", "AAAAAAAAQ.."], variants: [{ A: "#B0BEC5", Q: "#C62828" }, { A: "#050505", Q: "#C62828" }, { A: "#675f5f", Q: "#c4c729" }] },
|
|
590
|
+
{ id: "gladiator_galea", name: "Gladiator Galea", rarity: "epic", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "....QQQQQQ.", "...QQQQQ...", "...QQQQQ...", "...QAAA....", "..AAAAAAA..", "..AAAAAAA..", "..AQQQQQA.."], variants: [{ A: "#B0BEC5", Q: "#C62828" }, { A: "#ffe907", Q: "#C62828" }] },
|
|
591
|
+
{ id: "pharaoh_nemes", name: "Pharaoh Nemes", rarity: "epic", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "..AAAAAAA..", "..AAQAQAA..", "..QAQAQAQ..", "..QAQAQAQ..", "..QQAAAQQ..", "..QQAAAAA..", "...QAQA...."], variants: [{ A: "#FFD700", Q: "#1565C0" }, { A: "#8d342a", Q: "#63686e" }] },
|
|
592
|
+
{ id: "spartan_helmet", name: "Spartan Helmet", rarity: "epic", width: 11, anchor_x: 23, rows: ["...........", "........Q..", ".......QQ..", ".....QQQQ..", "....QQQQQ..", "..QQQQQQ...", ".QQQAA.....", "QQAAAAAAA..", "Q.AAAAAAA..", "..AQQQQQA.."], variants: [{ A: "#B0BEC5", Q: "#B71C1C" }, { A: "#B0BEC5", Q: "#b2b51c" }, { A: "#B0BEC5", Q: "#b5611c" }] },
|
|
593
|
+
{ id: "conquistador_full", name: "Conquistador Helm", rarity: "epic", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", ".....A.....", "....AAA....", "...AAAAA...", "..AAAAAAA..", "...AQQQA...", "...AQQQA..."], variants: [{ A: "#B0BEC5", Q: "#FFD700" }, { A: "#B0BEC5", Q: "#0040ff" }, { A: "#B0BEC5", Q: "#36123b" }] },
|
|
594
|
+
// ── LEGENDARY (5) ──────────────────────────────────────────────────────
|
|
595
|
+
{ id: "rainbow_crown", name: "Rainbow Crown", rarity: "legendary", width: 11, anchor_x: 23, rows: ["...........", "...........", ".....A.....", "....AQA....", "....AQA....", "....AQA....", "...AAQAA...", "...AQQQA...", "..AAQQQAA..", "..AAAAAAA.."], colors: { A: "#FFD700", Q: "#553f3f" }, animation: { type: "cycle", frames: ["#FF0000", "#FF7F00", "#FFFF00", "#00FF00", "#0000FF", "#8B00FF"], fps: 8 } },
|
|
596
|
+
{ id: "inferno_cap", name: "Inferno Cap", rarity: "legendary", width: 11, anchor_x: 23, rows: ["...........", "....AAA....", "...AAAAA...", "....AAA....", ".....Q.....", "A....Q....A", "A...QQQ...A", ".A..QQQ..A.", "..AAAAAAA..", "..AAAAAAA.."], colors: { A: "#FF4500", Q: "#FFD700" }, animation: { type: "cycle", frames: ["#FF0000", "#FF2200", "#FF4500", "#FF6600", "#FF8C00", "#FFA500"], fps: 12 } },
|
|
597
|
+
{ id: "void_hood", name: "Void Hood", rarity: "legendary", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...AAA.....", "..AAAAAA...", ".AAAAAAA...", ".AAAAAAAA..", "AAAAAAAAA..", "AAAAQQQAA..", "AAAAQQQAA.."], colors: { A: "#1A0033", Q: "#d9cfe3" }, animation: { type: "cycle", frames: ["#0D0019", "#1A0033", "#2D004D", "#3D0066", "#2D004D", "#1A0033"], fps: 3 } },
|
|
598
|
+
{ id: "prismatic_jester", name: "Prismatic Jester", rarity: "legendary", width: 11, anchor_x: 23, rows: ["...........", "..Q.Q.Q.Q..", "Q..A.A.A..Q", ".Q.A.A.A.Q.", "..AQAQAQA..", "..AQAQAQA..", "..AAAAAAA..", "..AAAAAAA..", "..AAAAAAA..", "..AAAAAAA.."], colors: { A: "#FF0000", Q: "#0000FF" }, animation: { type: "cycle", frames: ["#FF0000", "#FF7F00", "#FFFF00", "#00FF00", "#0000FF", "#8B00FF", "#FF00FF", "#00FFFF"], fps: 15 } },
|
|
599
|
+
{ id: "aurora_helm", name: "Aurora Helm", rarity: "legendary", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...QQQQQ...", "...QQQQQ...", "..AAAAAAA..", "..AAAAAAA..", "..AQAAAQA..", "..AQAAAQA.."], colors: { A: "#00CED1", Q: "#00FF7F" }, animation: { type: "cycle", frames: ["#0000FF", "#0066FF", "#00BFFF", "#00CED1", "#00FF7F", "#7CFC00", "#00FF7F", "#00CED1"], fps: 4 } }
|
|
600
|
+
];
|
|
601
|
+
|
|
469
602
|
// src/identity/identity.ts
|
|
470
603
|
import { promises as fs } from "fs";
|
|
471
604
|
import * as path2 from "path";
|
|
@@ -668,6 +801,12 @@ function deleteOrgWebhook(orgName) {
|
|
|
668
801
|
void 0
|
|
669
802
|
);
|
|
670
803
|
}
|
|
804
|
+
function rollHat(stableHorseId) {
|
|
805
|
+
return request("POST", `/jockey/me/horses/${encodeURIComponent(stableHorseId)}/roll`, void 0, void 0);
|
|
806
|
+
}
|
|
807
|
+
function equipHat(stableHorseId, body) {
|
|
808
|
+
return request("POST", `/jockey/me/horses/${encodeURIComponent(stableHorseId)}/equip`, body, void 0);
|
|
809
|
+
}
|
|
671
810
|
|
|
672
811
|
// src/commands/stable-create.ts
|
|
673
812
|
async function stableCreateCommand() {
|
|
@@ -800,8 +939,114 @@ async function stableDeleteCommand(name) {
|
|
|
800
939
|
}
|
|
801
940
|
|
|
802
941
|
// src/commands/stable-edit.ts
|
|
803
|
-
import
|
|
942
|
+
import React6 from "react";
|
|
804
943
|
import { render as render3 } from "ink";
|
|
944
|
+
|
|
945
|
+
// src/ui/HatPicker.tsx
|
|
946
|
+
import { useState as useState3 } from "react";
|
|
947
|
+
import { Box as Box5, Text as Text5, useInput as useInput2 } from "ink";
|
|
948
|
+
|
|
949
|
+
// src/ui/AnimatedHorseSprite.tsx
|
|
950
|
+
import { useEffect, useState as useState2 } from "react";
|
|
951
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
952
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
953
|
+
function AnimatedHorseSprite({ sprite, colors, hat }) {
|
|
954
|
+
const isLegendary = hat.rarity === "legendary";
|
|
955
|
+
const frames = isLegendary ? hat.animation.frames : [];
|
|
956
|
+
const fps = isLegendary ? hat.animation.fps : 1;
|
|
957
|
+
const [idx, setIdx] = useState2(0);
|
|
958
|
+
useEffect(() => {
|
|
959
|
+
if (!isLegendary || frames.length <= 1) return;
|
|
960
|
+
const interval = setInterval(
|
|
961
|
+
() => setIdx((i) => (i + 1) % frames.length),
|
|
962
|
+
Math.max(1, Math.round(1e3 / fps))
|
|
963
|
+
);
|
|
964
|
+
return () => clearInterval(interval);
|
|
965
|
+
}, [isLegendary, frames.length, fps]);
|
|
966
|
+
const renderedHat = isLegendary && frames[idx] ? { ...hat, colors: { ...hat.colors, A: frames[idx] } } : hat;
|
|
967
|
+
const { grid } = composeHatGrid(sprite, renderedHat, 0, colors);
|
|
968
|
+
const lines = hexGridToHalfBlocks(grid);
|
|
969
|
+
return /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", children: lines.map((line, i) => /* @__PURE__ */ jsx4(Text4, { children: line }, i)) });
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// src/ui/HatPicker.tsx
|
|
973
|
+
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
974
|
+
function HatPicker({ hats, equipped, colors, onPick, onCancel }) {
|
|
975
|
+
const entries = [
|
|
976
|
+
{ kind: "unequip" },
|
|
977
|
+
...hats.map((c, idx) => ({ kind: "hat", idx, collected: c }))
|
|
978
|
+
];
|
|
979
|
+
const [cursor, setCursor] = useState3(0);
|
|
980
|
+
useInput2((input, key) => {
|
|
981
|
+
if (key.escape) {
|
|
982
|
+
onCancel();
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
if (key.upArrow) {
|
|
986
|
+
setCursor((cursor - 1 + entries.length) % entries.length);
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
if (key.downArrow) {
|
|
990
|
+
setCursor((cursor + 1) % entries.length);
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
if (key.return) {
|
|
994
|
+
const e = entries[cursor];
|
|
995
|
+
onPick(e.kind === "unequip" ? null : e.idx);
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
});
|
|
999
|
+
const focused = entries[cursor];
|
|
1000
|
+
return /* @__PURE__ */ jsxs3(Box5, { flexDirection: "column", children: [
|
|
1001
|
+
/* @__PURE__ */ jsx5(Text5, { children: "Pick a hat to equip:" }),
|
|
1002
|
+
entries.map((e, i) => {
|
|
1003
|
+
const isCursor = i === cursor;
|
|
1004
|
+
if (e.kind === "unequip") {
|
|
1005
|
+
const isEquipped2 = equipped == null;
|
|
1006
|
+
return /* @__PURE__ */ jsx5(Box5, { flexDirection: "row", children: /* @__PURE__ */ jsxs3(Text5, { children: [
|
|
1007
|
+
isCursor ? "\u25BA" : " ",
|
|
1008
|
+
" ",
|
|
1009
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Unequip" }),
|
|
1010
|
+
isEquipped2 ? " \u2713" : ""
|
|
1011
|
+
] }) }, "unequip");
|
|
1012
|
+
}
|
|
1013
|
+
const hat = hatById(e.collected.id);
|
|
1014
|
+
const name = hat?.name ?? e.collected.id;
|
|
1015
|
+
const variantSuffix = hat && hat.rarity !== "legendary" && e.collected.variant !== void 0 ? ` #${e.collected.variant + 1}` : "";
|
|
1016
|
+
const isEquipped = equipped === e.idx;
|
|
1017
|
+
const rarityColor = hat ? hat.rarity === "legendary" ? "yellow" : hat.rarity === "epic" ? "magenta" : hat.rarity === "rare" ? "blue" : "gray" : "gray";
|
|
1018
|
+
return /* @__PURE__ */ jsx5(Box5, { flexDirection: "row", children: /* @__PURE__ */ jsxs3(Text5, { children: [
|
|
1019
|
+
isCursor ? "\u25BA" : " ",
|
|
1020
|
+
" ",
|
|
1021
|
+
name,
|
|
1022
|
+
variantSuffix,
|
|
1023
|
+
" ",
|
|
1024
|
+
/* @__PURE__ */ jsxs3(Text5, { color: rarityColor, children: [
|
|
1025
|
+
"[",
|
|
1026
|
+
hat?.rarity ?? "?",
|
|
1027
|
+
"]"
|
|
1028
|
+
] }),
|
|
1029
|
+
isEquipped ? " \u2713" : ""
|
|
1030
|
+
] }) }, `hat-${e.idx}`);
|
|
1031
|
+
}),
|
|
1032
|
+
/* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Preview:" }) }),
|
|
1033
|
+
/* @__PURE__ */ jsx5(PreviewArea, { focused, colors }),
|
|
1034
|
+
/* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u2191/\u2193 choose \xB7 Enter pick \xB7 Esc cancel" }) })
|
|
1035
|
+
] });
|
|
1036
|
+
}
|
|
1037
|
+
function PreviewArea({ focused, colors }) {
|
|
1038
|
+
if (focused.kind === "unequip") {
|
|
1039
|
+
return /* @__PURE__ */ jsx5(HorseSprite, { sprite: MAIN_SPRITE, colors });
|
|
1040
|
+
}
|
|
1041
|
+
const hat = hatById(focused.collected.id);
|
|
1042
|
+
if (!hat) return /* @__PURE__ */ jsx5(HorseSprite, { sprite: MAIN_SPRITE, colors });
|
|
1043
|
+
if (hat.rarity === "legendary") {
|
|
1044
|
+
return /* @__PURE__ */ jsx5(AnimatedHorseSprite, { sprite: MAIN_SPRITE, colors, hat });
|
|
1045
|
+
}
|
|
1046
|
+
return /* @__PURE__ */ jsx5(HorseSprite, { sprite: MAIN_SPRITE, colors, hat: { hat, variant: focused.collected.variant ?? 0 } });
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// src/commands/stable-edit.ts
|
|
805
1050
|
async function stableEditCommand(name) {
|
|
806
1051
|
if (!name) {
|
|
807
1052
|
console.error("Usage: token-derby stable edit <name>");
|
|
@@ -815,8 +1060,9 @@ async function stableEditCommand(name) {
|
|
|
815
1060
|
return 1;
|
|
816
1061
|
}
|
|
817
1062
|
let exitCode = 0;
|
|
1063
|
+
let liveColors = existing.colors;
|
|
818
1064
|
const app = render3(
|
|
819
|
-
|
|
1065
|
+
React6.createElement(HorseCreator, {
|
|
820
1066
|
initialColors: existing.colors,
|
|
821
1067
|
initialName: existing.name,
|
|
822
1068
|
lockName: true,
|
|
@@ -824,6 +1070,7 @@ async function stableEditCommand(name) {
|
|
|
824
1070
|
onSubmit: async (_name, colors) => {
|
|
825
1071
|
try {
|
|
826
1072
|
await updateStableHorse(existing.stable_horse_id, { colors });
|
|
1073
|
+
liveColors = colors;
|
|
827
1074
|
app.unmount();
|
|
828
1075
|
console.log(`\u2713 Updated "${existing.name}".`);
|
|
829
1076
|
} catch (e) {
|
|
@@ -844,6 +1091,34 @@ async function stableEditCommand(name) {
|
|
|
844
1091
|
})
|
|
845
1092
|
);
|
|
846
1093
|
await app.waitUntilExit();
|
|
1094
|
+
if (exitCode === 0 && existing.hats && existing.hats.length > 0) {
|
|
1095
|
+
const equipResult = await new Promise((resolve) => {
|
|
1096
|
+
const app2 = render3(
|
|
1097
|
+
React6.createElement(HatPicker, {
|
|
1098
|
+
hats: existing.hats,
|
|
1099
|
+
equipped: existing.equipped_hat ?? null,
|
|
1100
|
+
colors: liveColors,
|
|
1101
|
+
onPick: (idx) => {
|
|
1102
|
+
app2.unmount();
|
|
1103
|
+
resolve({ done: true, idx });
|
|
1104
|
+
},
|
|
1105
|
+
onCancel: () => {
|
|
1106
|
+
app2.unmount();
|
|
1107
|
+
resolve({ done: false, idx: null });
|
|
1108
|
+
}
|
|
1109
|
+
})
|
|
1110
|
+
);
|
|
1111
|
+
});
|
|
1112
|
+
if (equipResult.done) {
|
|
1113
|
+
try {
|
|
1114
|
+
await equipHat(existing.stable_horse_id, { hat_index: equipResult.idx });
|
|
1115
|
+
console.log(equipResult.idx === null ? "Hat unequipped." : "Hat equipped.");
|
|
1116
|
+
} catch (e) {
|
|
1117
|
+
if (e instanceof ApiError) console.error(`Equip failed: ${e.code} ${e.message}`);
|
|
1118
|
+
else throw e;
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
847
1122
|
return exitCode;
|
|
848
1123
|
}
|
|
849
1124
|
async function fetchStable() {
|
|
@@ -944,16 +1219,16 @@ function isIso(s) {
|
|
|
944
1219
|
}
|
|
945
1220
|
|
|
946
1221
|
// src/commands/join.ts
|
|
947
|
-
import
|
|
1222
|
+
import React9 from "react";
|
|
948
1223
|
import { render as render4 } from "ink";
|
|
949
1224
|
|
|
950
1225
|
// src/ui/HorsePicker.tsx
|
|
951
|
-
import { useState as
|
|
952
|
-
import { Box as
|
|
953
|
-
import { jsx as
|
|
1226
|
+
import { useState as useState4 } from "react";
|
|
1227
|
+
import { Box as Box6, Text as Text6, useInput as useInput3 } from "ink";
|
|
1228
|
+
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
954
1229
|
function HorsePicker({ horses, onPick, onCancel }) {
|
|
955
|
-
const [idx, setIdx] =
|
|
956
|
-
|
|
1230
|
+
const [idx, setIdx] = useState4(0);
|
|
1231
|
+
useInput3((input, key) => {
|
|
957
1232
|
if (key.escape) {
|
|
958
1233
|
onCancel();
|
|
959
1234
|
return;
|
|
@@ -973,31 +1248,31 @@ function HorsePicker({ horses, onPick, onCancel }) {
|
|
|
973
1248
|
}
|
|
974
1249
|
});
|
|
975
1250
|
if (horses.length === 0) {
|
|
976
|
-
return /* @__PURE__ */
|
|
977
|
-
/* @__PURE__ */
|
|
978
|
-
/* @__PURE__ */
|
|
1251
|
+
return /* @__PURE__ */ jsxs4(Box6, { flexDirection: "column", children: [
|
|
1252
|
+
/* @__PURE__ */ jsx6(Text6, { children: "No horses in your stable." }),
|
|
1253
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "Run `token-derby stable create` to make one." })
|
|
979
1254
|
] });
|
|
980
1255
|
}
|
|
981
|
-
return /* @__PURE__ */
|
|
982
|
-
/* @__PURE__ */
|
|
983
|
-
horses.map((h, i) => /* @__PURE__ */
|
|
984
|
-
/* @__PURE__ */
|
|
1256
|
+
return /* @__PURE__ */ jsxs4(Box6, { flexDirection: "column", children: [
|
|
1257
|
+
/* @__PURE__ */ jsx6(Text6, { children: "Pick a horse to race:" }),
|
|
1258
|
+
horses.map((h, i) => /* @__PURE__ */ jsxs4(Box6, { flexDirection: "column", children: [
|
|
1259
|
+
/* @__PURE__ */ jsx6(Box6, { flexDirection: "row", children: /* @__PURE__ */ jsxs4(Text6, { children: [
|
|
985
1260
|
i === idx ? "\u25BA" : " ",
|
|
986
1261
|
" ",
|
|
987
1262
|
h.name,
|
|
988
1263
|
" ",
|
|
989
|
-
/* @__PURE__ */
|
|
1264
|
+
/* @__PURE__ */ jsxs4(Text6, { color: "cyan", children: [
|
|
990
1265
|
"[Lvl. ",
|
|
991
1266
|
levelFromXp(h.xp),
|
|
992
1267
|
"]"
|
|
993
1268
|
] })
|
|
994
1269
|
] }) }),
|
|
995
|
-
/* @__PURE__ */
|
|
996
|
-
/* @__PURE__ */
|
|
997
|
-
/* @__PURE__ */
|
|
1270
|
+
/* @__PURE__ */ jsxs4(Box6, { flexDirection: "row", children: [
|
|
1271
|
+
/* @__PURE__ */ jsx6(Text6, { children: " " }),
|
|
1272
|
+
/* @__PURE__ */ jsx6(HorseSprite, { sprite: MINI_SPRITE, colors: h.colors })
|
|
998
1273
|
] })
|
|
999
1274
|
] }, h.stable_horse_id)),
|
|
1000
|
-
/* @__PURE__ */
|
|
1275
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "\u2191/\u2193 choose \xB7 Enter pick \xB7 Esc cancel" }) })
|
|
1001
1276
|
] });
|
|
1002
1277
|
}
|
|
1003
1278
|
|
|
@@ -1023,45 +1298,45 @@ async function saveActiveRace(active) {
|
|
|
1023
1298
|
}
|
|
1024
1299
|
|
|
1025
1300
|
// src/runtime/run-race.tsx
|
|
1026
|
-
import { useEffect, useRef, useState as
|
|
1027
|
-
import { Box as
|
|
1301
|
+
import { useEffect as useEffect2, useRef, useState as useState5 } from "react";
|
|
1302
|
+
import { Box as Box8, Text as Text8, useApp } from "ink";
|
|
1028
1303
|
|
|
1029
1304
|
// src/ui/StatusScreen.tsx
|
|
1030
|
-
import { Box as
|
|
1031
|
-
import { jsx as
|
|
1305
|
+
import { Box as Box7, Text as Text7 } from "ink";
|
|
1306
|
+
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1032
1307
|
function StatusScreen(props) {
|
|
1033
1308
|
const { race, ownHorseId, ownHorseName, ownColors, ownUserName, lastHeartbeatAgoSec, lastHeartbeatOk } = props;
|
|
1034
1309
|
if (!race) {
|
|
1035
|
-
return /* @__PURE__ */
|
|
1310
|
+
return /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", children: /* @__PURE__ */ jsx7(Text7, { children: "Joining race\u2026" }) });
|
|
1036
1311
|
}
|
|
1037
1312
|
const own = race.horses.find((h) => h.horse_id === ownHorseId);
|
|
1038
1313
|
const leader = race.horses[0];
|
|
1039
1314
|
const elapsedPct = elapsed(race);
|
|
1040
1315
|
const timeLeft = formatDuration(race.time_left_seconds);
|
|
1041
1316
|
const lvl = levelInfo((own?.xp ?? 0) + (own?.live_xp ?? 0));
|
|
1042
|
-
return /* @__PURE__ */
|
|
1043
|
-
/* @__PURE__ */
|
|
1317
|
+
return /* @__PURE__ */ jsxs5(Box7, { flexDirection: "column", borderStyle: "round", paddingX: 1, children: [
|
|
1318
|
+
/* @__PURE__ */ jsxs5(Text7, { children: [
|
|
1044
1319
|
"\u{1F3C7} TOKEN DERBY \u2500\u2500\u2500 ",
|
|
1045
|
-
/* @__PURE__ */
|
|
1320
|
+
/* @__PURE__ */ jsx7(Text7, { bold: true, children: race.name }),
|
|
1046
1321
|
" \u2500\u2500\u2500 status: ",
|
|
1047
|
-
/* @__PURE__ */
|
|
1322
|
+
/* @__PURE__ */ jsx7(Text7, { color: statusColor(race.status), children: race.status })
|
|
1048
1323
|
] }),
|
|
1049
|
-
/* @__PURE__ */
|
|
1050
|
-
/* @__PURE__ */
|
|
1051
|
-
/* @__PURE__ */
|
|
1052
|
-
/* @__PURE__ */
|
|
1324
|
+
/* @__PURE__ */ jsxs5(Box7, { marginTop: 1, flexDirection: "row", children: [
|
|
1325
|
+
/* @__PURE__ */ jsx7(HorseSprite, { sprite: MINI_SPRITE, colors: ownColors }),
|
|
1326
|
+
/* @__PURE__ */ jsxs5(Box7, { flexDirection: "column", children: [
|
|
1327
|
+
/* @__PURE__ */ jsxs5(Text7, { children: [
|
|
1053
1328
|
" ",
|
|
1054
1329
|
ownHorseName,
|
|
1055
1330
|
" ",
|
|
1056
|
-
/* @__PURE__ */
|
|
1331
|
+
/* @__PURE__ */ jsxs5(Text7, { color: "cyan", children: [
|
|
1057
1332
|
"[Lvl. ",
|
|
1058
1333
|
lvl.level,
|
|
1059
1334
|
"]"
|
|
1060
1335
|
] })
|
|
1061
1336
|
] }),
|
|
1062
|
-
/* @__PURE__ */
|
|
1337
|
+
/* @__PURE__ */ jsxs5(Text7, { children: [
|
|
1063
1338
|
" ",
|
|
1064
|
-
/* @__PURE__ */
|
|
1339
|
+
/* @__PURE__ */ jsxs5(Text7, { dimColor: true, children: [
|
|
1065
1340
|
"(",
|
|
1066
1341
|
ownUserName,
|
|
1067
1342
|
")"
|
|
@@ -1069,43 +1344,43 @@ function StatusScreen(props) {
|
|
|
1069
1344
|
] })
|
|
1070
1345
|
] })
|
|
1071
1346
|
] }),
|
|
1072
|
-
/* @__PURE__ */
|
|
1073
|
-
/* @__PURE__ */
|
|
1347
|
+
/* @__PURE__ */ jsxs5(Box7, { flexDirection: "column", marginTop: 1, children: [
|
|
1348
|
+
/* @__PURE__ */ jsxs5(Text7, { children: [
|
|
1074
1349
|
"Tokens (race): ",
|
|
1075
1350
|
own?.current_tokens ?? 0
|
|
1076
1351
|
] }),
|
|
1077
|
-
/* @__PURE__ */
|
|
1352
|
+
/* @__PURE__ */ jsxs5(Text7, { children: [
|
|
1078
1353
|
"Position: ",
|
|
1079
1354
|
own?.rank ?? "\u2014",
|
|
1080
1355
|
" of ",
|
|
1081
1356
|
race.horses.length
|
|
1082
1357
|
] }),
|
|
1083
|
-
/* @__PURE__ */
|
|
1358
|
+
/* @__PURE__ */ jsxs5(Text7, { children: [
|
|
1084
1359
|
"Leader: ",
|
|
1085
1360
|
leader ? `${leader.name}${leader.user_name ? ` (${leader.user_name})` : ""} \u2014 ${leader.current_tokens}` : "\u2014"
|
|
1086
1361
|
] }),
|
|
1087
|
-
/* @__PURE__ */
|
|
1362
|
+
/* @__PURE__ */ jsxs5(Text7, { children: [
|
|
1088
1363
|
"Race elapsed: ",
|
|
1089
1364
|
(elapsedPct * 100).toFixed(0),
|
|
1090
1365
|
"% ",
|
|
1091
1366
|
bar(elapsedPct, 20)
|
|
1092
1367
|
] }),
|
|
1093
|
-
/* @__PURE__ */
|
|
1368
|
+
/* @__PURE__ */ jsxs5(Text7, { children: [
|
|
1094
1369
|
"Time left: ",
|
|
1095
1370
|
timeLeft
|
|
1096
1371
|
] }),
|
|
1097
|
-
/* @__PURE__ */
|
|
1372
|
+
/* @__PURE__ */ jsxs5(Text7, { children: [
|
|
1098
1373
|
"XP: ",
|
|
1099
1374
|
lvl.next_level_xp === null ? `${lvl.xp} (max level) ${bar(1, 20)}` : `${lvl.xp_into_level}/${lvl.xp_for_level} \u2192 Lvl. ${lvl.level + 1} ${bar(lvl.progress, 20)}`
|
|
1100
1375
|
] }),
|
|
1101
|
-
/* @__PURE__ */
|
|
1376
|
+
/* @__PURE__ */ jsxs5(Text7, { children: [
|
|
1102
1377
|
"Last heartbeat: ",
|
|
1103
1378
|
lastHeartbeatAgoSec === null ? "\u2014" : `${lastHeartbeatAgoSec}s ago`,
|
|
1104
1379
|
" ",
|
|
1105
|
-
/* @__PURE__ */
|
|
1380
|
+
/* @__PURE__ */ jsx7(Text7, { color: lastHeartbeatOk ? "green" : "yellow", children: lastHeartbeatOk ? "\u2713" : "\u26A0" })
|
|
1106
1381
|
] })
|
|
1107
1382
|
] }),
|
|
1108
|
-
/* @__PURE__ */
|
|
1383
|
+
/* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Press Ctrl+C to crash out of the race." }) })
|
|
1109
1384
|
] });
|
|
1110
1385
|
}
|
|
1111
1386
|
function elapsed(race) {
|
|
@@ -1254,25 +1529,25 @@ function initialBaseline(args) {
|
|
|
1254
1529
|
}
|
|
1255
1530
|
|
|
1256
1531
|
// src/runtime/run-race.tsx
|
|
1257
|
-
import { jsx as
|
|
1532
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1258
1533
|
function RunRace({ active, startingBaseline, pendingMode, ownUserName }) {
|
|
1259
1534
|
const { exit } = useApp();
|
|
1260
|
-
const [race, setRace] =
|
|
1261
|
-
const [lastHbAt, setLastHbAt] =
|
|
1262
|
-
const [lastHbOk, setLastHbOk] =
|
|
1263
|
-
const [tickNow, setTickNow] =
|
|
1264
|
-
const [fatalError, setFatalError] =
|
|
1265
|
-
const [achievements, setAchievements] =
|
|
1535
|
+
const [race, setRace] = useState5(null);
|
|
1536
|
+
const [lastHbAt, setLastHbAt] = useState5(null);
|
|
1537
|
+
const [lastHbOk, setLastHbOk] = useState5(true);
|
|
1538
|
+
const [tickNow, setTickNow] = useState5(/* @__PURE__ */ new Date());
|
|
1539
|
+
const [fatalError, setFatalError] = useState5(null);
|
|
1540
|
+
const [achievements, setAchievements] = useState5([]);
|
|
1266
1541
|
const shownAchievementAtRef = useRef(0);
|
|
1267
1542
|
const baselineRef = useRef(startingBaseline);
|
|
1268
1543
|
const pendingRef = useRef(pendingMode);
|
|
1269
1544
|
const lastTokenSampleRef = useRef(startingBaseline);
|
|
1270
1545
|
const ctrl = useRef(new AbortController());
|
|
1271
|
-
|
|
1546
|
+
useEffect2(() => {
|
|
1272
1547
|
const t = setInterval(() => setTickNow(/* @__PURE__ */ new Date()), 1e3);
|
|
1273
1548
|
return () => clearInterval(t);
|
|
1274
1549
|
}, []);
|
|
1275
|
-
|
|
1550
|
+
useEffect2(() => {
|
|
1276
1551
|
if (pendingRef.current && race?.status === "live") {
|
|
1277
1552
|
sumTokensForRace(active).then((total) => {
|
|
1278
1553
|
baselineRef.current = total;
|
|
@@ -1280,7 +1555,7 @@ function RunRace({ active, startingBaseline, pendingMode, ownUserName }) {
|
|
|
1280
1555
|
});
|
|
1281
1556
|
}
|
|
1282
1557
|
}, [race?.status]);
|
|
1283
|
-
|
|
1558
|
+
useEffect2(() => {
|
|
1284
1559
|
runHeartbeatLoop({
|
|
1285
1560
|
sendHeartbeat: async (currentTokens) => {
|
|
1286
1561
|
const resp = await heartbeat(
|
|
@@ -1346,13 +1621,13 @@ function RunRace({ active, startingBaseline, pendingMode, ownUserName }) {
|
|
|
1346
1621
|
}, []);
|
|
1347
1622
|
const lastHeartbeatAgoSec = lastHbAt ? Math.max(0, Math.floor((tickNow.getTime() - lastHbAt.getTime()) / 1e3)) : null;
|
|
1348
1623
|
if (fatalError) {
|
|
1349
|
-
return /* @__PURE__ */
|
|
1350
|
-
/* @__PURE__ */
|
|
1351
|
-
/* @__PURE__ */
|
|
1624
|
+
return /* @__PURE__ */ jsxs6(Box8, { flexDirection: "column", padding: 1, children: [
|
|
1625
|
+
/* @__PURE__ */ jsx8(Text8, { color: "red", bold: true, children: "CLI version mismatch \u2014 disconnected" }),
|
|
1626
|
+
/* @__PURE__ */ jsx8(Text8, { children: fatalError })
|
|
1352
1627
|
] });
|
|
1353
1628
|
}
|
|
1354
|
-
return /* @__PURE__ */
|
|
1355
|
-
/* @__PURE__ */
|
|
1629
|
+
return /* @__PURE__ */ jsxs6(Box8, { flexDirection: "column", children: [
|
|
1630
|
+
/* @__PURE__ */ jsx8(
|
|
1356
1631
|
StatusScreen,
|
|
1357
1632
|
{
|
|
1358
1633
|
race,
|
|
@@ -1364,23 +1639,23 @@ function RunRace({ active, startingBaseline, pendingMode, ownUserName }) {
|
|
|
1364
1639
|
lastHeartbeatOk: lastHbOk
|
|
1365
1640
|
}
|
|
1366
1641
|
),
|
|
1367
|
-
achievements.length > 0 && /* @__PURE__ */
|
|
1368
|
-
/* @__PURE__ */
|
|
1642
|
+
achievements.length > 0 && /* @__PURE__ */ jsxs6(Box8, { flexDirection: "column", marginTop: 1, children: [
|
|
1643
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, children: "Achievements" }),
|
|
1369
1644
|
achievements.map(({ key, event }) => {
|
|
1370
1645
|
const description = describeAchievement(event, active);
|
|
1371
|
-
return /* @__PURE__ */
|
|
1372
|
-
/* @__PURE__ */
|
|
1646
|
+
return /* @__PURE__ */ jsxs6(Box8, { flexDirection: "row", children: [
|
|
1647
|
+
/* @__PURE__ */ jsxs6(Text8, { dimColor: true, children: [
|
|
1373
1648
|
" ",
|
|
1374
1649
|
formatClockTime(event.at),
|
|
1375
1650
|
" "
|
|
1376
1651
|
] }),
|
|
1377
|
-
/* @__PURE__ */
|
|
1652
|
+
/* @__PURE__ */ jsxs6(Text8, { color: "yellow", bold: true, children: [
|
|
1378
1653
|
"+",
|
|
1379
1654
|
event.xp,
|
|
1380
1655
|
" XP "
|
|
1381
1656
|
] }),
|
|
1382
|
-
/* @__PURE__ */
|
|
1383
|
-
/* @__PURE__ */
|
|
1657
|
+
/* @__PURE__ */ jsx8(Text8, { children: event.name }),
|
|
1658
|
+
/* @__PURE__ */ jsxs6(Text8, { dimColor: true, children: [
|
|
1384
1659
|
" \u2014 ",
|
|
1385
1660
|
description
|
|
1386
1661
|
] })
|
|
@@ -1527,14 +1802,14 @@ async function joinCommand(joinCode) {
|
|
|
1527
1802
|
};
|
|
1528
1803
|
await saveActiveRace(active);
|
|
1529
1804
|
const initial = await buildInitialState({ active, raceStatus: status, rejoin: isResume });
|
|
1530
|
-
const app = render4(
|
|
1805
|
+
const app = render4(React9.createElement(RunRace, { active, ...initial, ownUserName: identity.display_name }));
|
|
1531
1806
|
await app.waitUntilExit();
|
|
1532
1807
|
return 0;
|
|
1533
1808
|
}
|
|
1534
1809
|
async function pickHorse(horses) {
|
|
1535
1810
|
return new Promise((resolve) => {
|
|
1536
1811
|
const app = render4(
|
|
1537
|
-
|
|
1812
|
+
React9.createElement(HorsePicker, {
|
|
1538
1813
|
horses,
|
|
1539
1814
|
onPick: (h) => {
|
|
1540
1815
|
app.unmount();
|
|
@@ -1669,7 +1944,7 @@ var FETCH_TIMEOUT_MS = 5e3;
|
|
|
1669
1944
|
async function updateCommand(deps = {}) {
|
|
1670
1945
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
1671
1946
|
const spawnImpl = deps.spawnImpl ?? spawn;
|
|
1672
|
-
const
|
|
1947
|
+
const promptYesNo2 = deps.promptYesNo ?? defaultPromptYesNo;
|
|
1673
1948
|
let latest;
|
|
1674
1949
|
try {
|
|
1675
1950
|
latest = await fetchLatestVersion(fetchImpl);
|
|
@@ -1683,7 +1958,7 @@ async function updateCommand(deps = {}) {
|
|
|
1683
1958
|
return 0;
|
|
1684
1959
|
}
|
|
1685
1960
|
console.log(`Current: ${CLI_VERSION} Latest: ${latest}`);
|
|
1686
|
-
const yes = await
|
|
1961
|
+
const yes = await promptYesNo2("Run upgrade now? [y/N]: ");
|
|
1687
1962
|
if (!yes) {
|
|
1688
1963
|
console.log(`To upgrade manually: ${UPGRADE_CMD}`);
|
|
1689
1964
|
return 0;
|
|
@@ -1732,6 +2007,393 @@ function runNpmUpgrade(spawnImpl) {
|
|
|
1732
2007
|
});
|
|
1733
2008
|
}
|
|
1734
2009
|
|
|
2010
|
+
// src/commands/roll.ts
|
|
2011
|
+
import React13 from "react";
|
|
2012
|
+
import { render as render5 } from "ink";
|
|
2013
|
+
|
|
2014
|
+
// src/ui/RollReveal.tsx
|
|
2015
|
+
import { useState as useState7, useEffect as useEffect4, useMemo } from "react";
|
|
2016
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
2017
|
+
|
|
2018
|
+
// src/ui/HatSprite.tsx
|
|
2019
|
+
import { useEffect as useEffect3, useState as useState6 } from "react";
|
|
2020
|
+
import { Box as Box9, Text as Text9 } from "ink";
|
|
2021
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
2022
|
+
function HatSprite({ hat, variant, centerIn }) {
|
|
2023
|
+
const colors = hatColorsFor2(hat, variant ?? 0);
|
|
2024
|
+
const grid = makeHatGrid(hat, colors, centerIn);
|
|
2025
|
+
const lines = hexGridToHalfBlocks(grid);
|
|
2026
|
+
return /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", children: lines.map((line, i) => /* @__PURE__ */ jsx9(Text9, { children: line }, i)) });
|
|
2027
|
+
}
|
|
2028
|
+
function AnimatedHatSprite({ hat, variant, centerIn }) {
|
|
2029
|
+
if (hat.rarity !== "legendary") {
|
|
2030
|
+
return /* @__PURE__ */ jsx9(HatSprite, { hat, variant, centerIn });
|
|
2031
|
+
}
|
|
2032
|
+
const frames = hat.animation.frames;
|
|
2033
|
+
const fps = hat.animation.fps;
|
|
2034
|
+
const [idx, setIdx] = useState6(0);
|
|
2035
|
+
useEffect3(() => {
|
|
2036
|
+
if (frames.length <= 1) return;
|
|
2037
|
+
const interval = setInterval(
|
|
2038
|
+
() => setIdx((i) => (i + 1) % frames.length),
|
|
2039
|
+
Math.max(1, Math.round(1e3 / fps))
|
|
2040
|
+
);
|
|
2041
|
+
return () => clearInterval(interval);
|
|
2042
|
+
}, [frames.length, fps]);
|
|
2043
|
+
const framed = { ...hat, colors: { ...hat.colors, A: frames[idx] } };
|
|
2044
|
+
return /* @__PURE__ */ jsx9(HatSprite, { hat: framed, variant, centerIn });
|
|
2045
|
+
}
|
|
2046
|
+
function hatColorsFor2(hat, variantIdx) {
|
|
2047
|
+
if (hat.rarity === "legendary") return hat.colors;
|
|
2048
|
+
return hat.variants[variantIdx] ?? hat.variants[0];
|
|
2049
|
+
}
|
|
2050
|
+
function makeHatGrid(hat, colors, centerIn) {
|
|
2051
|
+
const w = centerIn?.w ?? hat.width;
|
|
2052
|
+
const h = centerIn?.h ?? hat.rows.length;
|
|
2053
|
+
const offX = Math.floor((w - hat.width) / 2);
|
|
2054
|
+
const offY = Math.floor((h - hat.rows.length) / 2);
|
|
2055
|
+
const grid = Array.from({ length: h }, () => Array(w).fill(null));
|
|
2056
|
+
for (let y = 0; y < hat.rows.length; y++) {
|
|
2057
|
+
const row = hat.rows[y];
|
|
2058
|
+
for (let x = 0; x < hat.width; x++) {
|
|
2059
|
+
const ch = row[x];
|
|
2060
|
+
if (ch === "." || ch === void 0) continue;
|
|
2061
|
+
const gx = x + offX;
|
|
2062
|
+
const gy = y + offY;
|
|
2063
|
+
if (gx < 0 || gx >= w || gy < 0 || gy >= h) continue;
|
|
2064
|
+
grid[gy][gx] = ch === "A" ? colors.A : colors.Q ?? colors.A;
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
return grid;
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
// src/ui/RollReveal.tsx
|
|
2071
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
2072
|
+
var RESET3 = "\x1B[0m";
|
|
2073
|
+
var BOX_COLOR = "#E5C76B";
|
|
2074
|
+
var TIER_PALETTE = {
|
|
2075
|
+
common: ["#FFFFFF", "#DDDDDD", "#AAAAAA", "#9E9E9E"],
|
|
2076
|
+
rare: ["#42A5F5", "#1E88E5", "#90CAF9", "#0277BD"],
|
|
2077
|
+
epic: ["#AB47BC", "#8E24AA", "#CE93D8", "#6A1B9A"],
|
|
2078
|
+
legendary: ["#FFD700", "#FF7F00", "#FF0000", "#FF00FF", "#00BFFF", "#7CFC00", "#8B00FF"]
|
|
2079
|
+
};
|
|
2080
|
+
var CONFETTI_CHARS = ["\u2726", "\u2727", "\u22C6", "\u2605", "\u2606", "\u2728", "*", "\u2022", "\u25C6", "\u25C7"];
|
|
2081
|
+
var SCENE_W = 32;
|
|
2082
|
+
var SCENE_H = 10;
|
|
2083
|
+
function pad(line) {
|
|
2084
|
+
const visible = stripAnsi(line).length;
|
|
2085
|
+
const lead = Math.max(0, Math.floor((SCENE_W - visible) / 2));
|
|
2086
|
+
return " ".repeat(lead) + line + " ".repeat(Math.max(0, SCENE_W - lead - visible));
|
|
2087
|
+
}
|
|
2088
|
+
function stripAnsi(s) {
|
|
2089
|
+
return s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
2090
|
+
}
|
|
2091
|
+
var BOX_CLOSED = [
|
|
2092
|
+
"",
|
|
2093
|
+
"",
|
|
2094
|
+
"\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557",
|
|
2095
|
+
"\u2551 \u2591\u2591\u2591\u2591\u2591 \u2551",
|
|
2096
|
+
"\u2551\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2551",
|
|
2097
|
+
"\u2551 \u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 \u2551",
|
|
2098
|
+
"\u2551\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2551",
|
|
2099
|
+
"\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D",
|
|
2100
|
+
"",
|
|
2101
|
+
""
|
|
2102
|
+
].map(pad);
|
|
2103
|
+
var BOX_OPENING_1 = [
|
|
2104
|
+
"",
|
|
2105
|
+
"\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557",
|
|
2106
|
+
"\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D",
|
|
2107
|
+
"",
|
|
2108
|
+
"\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557",
|
|
2109
|
+
"\u2551 \u2726 \u2551",
|
|
2110
|
+
"\u2551\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2551",
|
|
2111
|
+
"\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D",
|
|
2112
|
+
"",
|
|
2113
|
+
""
|
|
2114
|
+
].map(pad);
|
|
2115
|
+
var BOX_OPENING_2 = [
|
|
2116
|
+
" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557",
|
|
2117
|
+
" \u2551 \u2551",
|
|
2118
|
+
" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D",
|
|
2119
|
+
"",
|
|
2120
|
+
" \u2728 \u2605 \u2726",
|
|
2121
|
+
"",
|
|
2122
|
+
"\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557",
|
|
2123
|
+
"\u2551 \u2551",
|
|
2124
|
+
"\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D",
|
|
2125
|
+
""
|
|
2126
|
+
].map(pad);
|
|
2127
|
+
var BOX_EMPTY = [
|
|
2128
|
+
"",
|
|
2129
|
+
"",
|
|
2130
|
+
"",
|
|
2131
|
+
"",
|
|
2132
|
+
"",
|
|
2133
|
+
"",
|
|
2134
|
+
"\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557",
|
|
2135
|
+
"\u2551 \u2551",
|
|
2136
|
+
"\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D",
|
|
2137
|
+
""
|
|
2138
|
+
].map(pad);
|
|
2139
|
+
function GiftBox({ frame, color }) {
|
|
2140
|
+
return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: frame.map((line, i) => /* @__PURE__ */ jsx10(Text10, { children: line ? ansiFg(color) + line + RESET3 : line }, i)) });
|
|
2141
|
+
}
|
|
2142
|
+
function printClosedBox() {
|
|
2143
|
+
for (const line of BOX_CLOSED) {
|
|
2144
|
+
const colored = line.trim().length > 0 ? `${ansiFg(BOX_COLOR)}${line}${RESET3}` : line;
|
|
2145
|
+
process.stdout.write(colored + "\n");
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
function spawnParticles(tier, count, cx, cy) {
|
|
2149
|
+
const palette = TIER_PALETTE[tier];
|
|
2150
|
+
const out = [];
|
|
2151
|
+
for (let i = 0; i < count; i++) {
|
|
2152
|
+
const angle = i / count * Math.PI * 2 + (Math.random() - 0.5) * 0.6;
|
|
2153
|
+
const speed = 0.8 + Math.random() * 1.6;
|
|
2154
|
+
out.push({
|
|
2155
|
+
x: cx,
|
|
2156
|
+
y: cy,
|
|
2157
|
+
vx: Math.cos(angle) * speed,
|
|
2158
|
+
vy: Math.sin(angle) * speed * 0.5,
|
|
2159
|
+
char: CONFETTI_CHARS[Math.floor(Math.random() * CONFETTI_CHARS.length)],
|
|
2160
|
+
color: palette[Math.floor(Math.random() * palette.length)]
|
|
2161
|
+
});
|
|
2162
|
+
}
|
|
2163
|
+
return out;
|
|
2164
|
+
}
|
|
2165
|
+
function ConfettiBurst({ tier }) {
|
|
2166
|
+
const cx = Math.floor(SCENE_W / 2);
|
|
2167
|
+
const cy = Math.floor(SCENE_H / 2);
|
|
2168
|
+
const particles = useMemo(() => spawnParticles(tier, 36, cx, cy), [tier, cx, cy]);
|
|
2169
|
+
const [tick, setTick] = useState7(0);
|
|
2170
|
+
useEffect4(() => {
|
|
2171
|
+
const i = setInterval(() => setTick((t) => t + 1), 70);
|
|
2172
|
+
return () => clearInterval(i);
|
|
2173
|
+
}, []);
|
|
2174
|
+
const grid = Array.from({ length: SCENE_H }, () => Array(SCENE_W).fill(" "));
|
|
2175
|
+
for (const p of particles) {
|
|
2176
|
+
const x = Math.round(p.x + p.vx * tick);
|
|
2177
|
+
const y = Math.round(p.y + p.vy * tick);
|
|
2178
|
+
if (x >= 0 && x < SCENE_W && y >= 0 && y < SCENE_H) {
|
|
2179
|
+
grid[y][x] = ansiFg(p.color) + p.char + RESET3;
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: grid.map((row, y) => /* @__PURE__ */ jsx10(Text10, { children: row.join("") }, y)) });
|
|
2183
|
+
}
|
|
2184
|
+
function RollReveal({ outcome, onDone }) {
|
|
2185
|
+
const isNoHat = outcome.kind === "no_hat";
|
|
2186
|
+
const isLegendary = outcome.kind !== "no_hat" && outcome.hat.rarity === "legendary";
|
|
2187
|
+
const [phase, setPhase] = useState7("open1");
|
|
2188
|
+
useEffect4(() => {
|
|
2189
|
+
const timers = [];
|
|
2190
|
+
timers.push(setTimeout(() => setPhase("open2"), 350));
|
|
2191
|
+
if (isNoHat) {
|
|
2192
|
+
timers.push(setTimeout(() => setPhase("empty"), 700));
|
|
2193
|
+
timers.push(setTimeout(onDone, 1500));
|
|
2194
|
+
} else {
|
|
2195
|
+
timers.push(setTimeout(() => setPhase("burst"), 700));
|
|
2196
|
+
timers.push(setTimeout(() => setPhase("reveal"), 1650));
|
|
2197
|
+
timers.push(setTimeout(onDone, isLegendary ? 4650 : 2650));
|
|
2198
|
+
}
|
|
2199
|
+
return () => timers.forEach(clearTimeout);
|
|
2200
|
+
}, [isNoHat, isLegendary, onDone]);
|
|
2201
|
+
if (phase === "open1") return /* @__PURE__ */ jsx10(GiftBox, { frame: BOX_OPENING_1, color: BOX_COLOR });
|
|
2202
|
+
if (phase === "open2") return /* @__PURE__ */ jsx10(GiftBox, { frame: BOX_OPENING_2, color: BOX_COLOR });
|
|
2203
|
+
if (phase === "empty") return /* @__PURE__ */ jsx10(GiftBox, { frame: BOX_EMPTY, color: BOX_COLOR });
|
|
2204
|
+
if (phase === "burst" && outcome.kind !== "no_hat") {
|
|
2205
|
+
return /* @__PURE__ */ jsx10(ConfettiBurst, { tier: outcome.hat.rarity });
|
|
2206
|
+
}
|
|
2207
|
+
if (outcome.kind === "no_hat") return /* @__PURE__ */ jsx10(GiftBox, { frame: BOX_EMPTY, color: BOX_COLOR });
|
|
2208
|
+
return outcome.hat.rarity === "legendary" ? /* @__PURE__ */ jsx10(AnimatedHatSprite, { hat: outcome.hat, centerIn: { w: SCENE_W, h: SCENE_H } }) : /* @__PURE__ */ jsx10(HatSprite, { hat: outcome.hat, variant: outcome.variant, centerIn: { w: SCENE_W, h: SCENE_H } });
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
// src/ui/RollHorsePicker.tsx
|
|
2212
|
+
import { useState as useState8 } from "react";
|
|
2213
|
+
import { Box as Box11, Text as Text11, useInput as useInput4 } from "ink";
|
|
2214
|
+
import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2215
|
+
function RollHorsePicker({ horses, onPick, onCancel }) {
|
|
2216
|
+
const [idx, setIdx] = useState8(0);
|
|
2217
|
+
useInput4((input, key) => {
|
|
2218
|
+
if (key.escape) {
|
|
2219
|
+
onCancel();
|
|
2220
|
+
return;
|
|
2221
|
+
}
|
|
2222
|
+
if (horses.length === 0) return;
|
|
2223
|
+
if (key.upArrow) {
|
|
2224
|
+
setIdx((idx - 1 + horses.length) % horses.length);
|
|
2225
|
+
return;
|
|
2226
|
+
}
|
|
2227
|
+
if (key.downArrow) {
|
|
2228
|
+
setIdx((idx + 1) % horses.length);
|
|
2229
|
+
return;
|
|
2230
|
+
}
|
|
2231
|
+
if (key.return) {
|
|
2232
|
+
onPick(horses[idx]);
|
|
2233
|
+
return;
|
|
2234
|
+
}
|
|
2235
|
+
});
|
|
2236
|
+
return /* @__PURE__ */ jsxs7(Box11, { flexDirection: "column", children: [
|
|
2237
|
+
/* @__PURE__ */ jsx11(Text11, { children: "Pick a horse to roll for:" }),
|
|
2238
|
+
horses.map((h, i) => /* @__PURE__ */ jsxs7(Box11, { flexDirection: "column", children: [
|
|
2239
|
+
/* @__PURE__ */ jsx11(Box11, { flexDirection: "row", children: /* @__PURE__ */ jsxs7(Text11, { children: [
|
|
2240
|
+
i === idx ? "\u25BA" : " ",
|
|
2241
|
+
" ",
|
|
2242
|
+
h.name,
|
|
2243
|
+
" ",
|
|
2244
|
+
/* @__PURE__ */ jsxs7(Text11, { color: "cyan", children: [
|
|
2245
|
+
"[Lvl. ",
|
|
2246
|
+
levelFromXp(h.xp),
|
|
2247
|
+
"]"
|
|
2248
|
+
] }),
|
|
2249
|
+
" ",
|
|
2250
|
+
/* @__PURE__ */ jsxs7(Text11, { color: "yellow", children: [
|
|
2251
|
+
"\u2014 ",
|
|
2252
|
+
h.pending,
|
|
2253
|
+
" roll",
|
|
2254
|
+
h.pending === 1 ? "" : "s"
|
|
2255
|
+
] })
|
|
2256
|
+
] }) }),
|
|
2257
|
+
/* @__PURE__ */ jsxs7(Box11, { flexDirection: "row", children: [
|
|
2258
|
+
/* @__PURE__ */ jsx11(Text11, { children: " " }),
|
|
2259
|
+
/* @__PURE__ */ jsx11(HorseSprite, { sprite: MINI_SPRITE, colors: h.colors })
|
|
2260
|
+
] })
|
|
2261
|
+
] }, h.stable_horse_id)),
|
|
2262
|
+
/* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "\u2191/\u2193 choose \xB7 Enter pick \xB7 Esc cancel" }) })
|
|
2263
|
+
] });
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
// src/commands/roll.ts
|
|
2267
|
+
function pendingFor(horse) {
|
|
2268
|
+
const level = levelFromXp(horse.xp);
|
|
2269
|
+
const lastRolled = horse.last_rolled_level ?? Math.max(0, level - 1);
|
|
2270
|
+
return level - lastRolled;
|
|
2271
|
+
}
|
|
2272
|
+
async function promptYesNo(question) {
|
|
2273
|
+
const readline7 = await import("readline/promises");
|
|
2274
|
+
const rl = readline7.createInterface({ input: process.stdin, output: process.stdout });
|
|
2275
|
+
const a = (await rl.question(question)).trim().toLowerCase();
|
|
2276
|
+
rl.close();
|
|
2277
|
+
return a === "" || a === "y" || a === "yes";
|
|
2278
|
+
}
|
|
2279
|
+
async function runReveal(outcome) {
|
|
2280
|
+
printClosedBox();
|
|
2281
|
+
const readline7 = await import("readline/promises");
|
|
2282
|
+
const rl = readline7.createInterface({ input: process.stdin, output: process.stdout });
|
|
2283
|
+
await rl.question("Press Enter to open the box\u2026 ");
|
|
2284
|
+
rl.close();
|
|
2285
|
+
await new Promise((resolve) => {
|
|
2286
|
+
const app = render5(React13.createElement(RollReveal, {
|
|
2287
|
+
outcome,
|
|
2288
|
+
onDone: () => {
|
|
2289
|
+
app.unmount();
|
|
2290
|
+
resolve();
|
|
2291
|
+
}
|
|
2292
|
+
}));
|
|
2293
|
+
});
|
|
2294
|
+
}
|
|
2295
|
+
async function rollCommand() {
|
|
2296
|
+
let stable;
|
|
2297
|
+
try {
|
|
2298
|
+
stable = await listStable();
|
|
2299
|
+
} catch (e) {
|
|
2300
|
+
if (e instanceof ApiError) {
|
|
2301
|
+
console.error(`Error: ${e.code} ${e.message}`);
|
|
2302
|
+
return 1;
|
|
2303
|
+
}
|
|
2304
|
+
throw e;
|
|
2305
|
+
}
|
|
2306
|
+
const eligible = stable.horses.map((h) => ({ ...h, pending: pendingFor(h) })).filter((h) => h.pending > 0);
|
|
2307
|
+
if (eligible.length === 0) {
|
|
2308
|
+
console.log("No rolls available. Level up a horse to earn a roll!");
|
|
2309
|
+
return 0;
|
|
2310
|
+
}
|
|
2311
|
+
let chosen = eligible[0];
|
|
2312
|
+
if (eligible.length > 1) {
|
|
2313
|
+
const picked = await new Promise((resolve) => {
|
|
2314
|
+
const app = render5(React13.createElement(RollHorsePicker, {
|
|
2315
|
+
horses: eligible,
|
|
2316
|
+
onPick: (h) => {
|
|
2317
|
+
app.unmount();
|
|
2318
|
+
resolve(h);
|
|
2319
|
+
},
|
|
2320
|
+
onCancel: () => {
|
|
2321
|
+
app.unmount();
|
|
2322
|
+
resolve(null);
|
|
2323
|
+
}
|
|
2324
|
+
}));
|
|
2325
|
+
});
|
|
2326
|
+
if (!picked) {
|
|
2327
|
+
console.log("Cancelled.");
|
|
2328
|
+
return 0;
|
|
2329
|
+
}
|
|
2330
|
+
chosen = picked;
|
|
2331
|
+
}
|
|
2332
|
+
while (true) {
|
|
2333
|
+
let result;
|
|
2334
|
+
try {
|
|
2335
|
+
result = await rollHat(chosen.stable_horse_id);
|
|
2336
|
+
} catch (e) {
|
|
2337
|
+
if (e instanceof ApiError) {
|
|
2338
|
+
if (e.code === "INSUFFICIENT_ROLLS") {
|
|
2339
|
+
console.log("No more rolls available.");
|
|
2340
|
+
return 0;
|
|
2341
|
+
}
|
|
2342
|
+
console.error(`Error: ${e.code} ${e.message}`);
|
|
2343
|
+
return 1;
|
|
2344
|
+
}
|
|
2345
|
+
throw e;
|
|
2346
|
+
}
|
|
2347
|
+
const outcome = (() => {
|
|
2348
|
+
if (result.result === "hat") {
|
|
2349
|
+
const hat = hatById(result.collected.id);
|
|
2350
|
+
if (!hat) return null;
|
|
2351
|
+
return { kind: "hat", hat, variant: result.collected.variant };
|
|
2352
|
+
}
|
|
2353
|
+
if (result.result === "duplicate") {
|
|
2354
|
+
const hat = hatById(result.hat_id);
|
|
2355
|
+
if (!hat) return null;
|
|
2356
|
+
return { kind: "duplicate", hat, variant: result.variant };
|
|
2357
|
+
}
|
|
2358
|
+
return { kind: "no_hat" };
|
|
2359
|
+
})();
|
|
2360
|
+
if (!outcome) {
|
|
2361
|
+
console.error("Server returned an unknown hat id \u2014 catalog mismatch.");
|
|
2362
|
+
return 1;
|
|
2363
|
+
}
|
|
2364
|
+
await runReveal(outcome);
|
|
2365
|
+
if (result.result === "hat") {
|
|
2366
|
+
const hat = hatById(result.collected.id);
|
|
2367
|
+
const variantSuffix = hat.rarity !== "legendary" && result.collected.variant !== void 0 ? ` #${result.collected.variant + 1}` : "";
|
|
2368
|
+
console.log(`
|
|
2369
|
+
\u2728 ${hat.name}${variantSuffix} [${hat.rarity.toUpperCase()}]
|
|
2370
|
+
`);
|
|
2371
|
+
if (await promptYesNo("Equip now? [Y/n] ")) {
|
|
2372
|
+
try {
|
|
2373
|
+
await equipHat(chosen.stable_horse_id, { hat_index: result.hat_index });
|
|
2374
|
+
console.log(`Equipped on ${chosen.name}.`);
|
|
2375
|
+
} catch (e) {
|
|
2376
|
+
if (e instanceof ApiError) {
|
|
2377
|
+
console.error(`Equip failed: ${e.code} ${e.message}`);
|
|
2378
|
+
} else throw e;
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
} else if (result.result === "duplicate") {
|
|
2382
|
+
const hat = hatById(result.hat_id);
|
|
2383
|
+
const variantSuffix = result.variant !== void 0 ? ` #${result.variant + 1}` : "";
|
|
2384
|
+
console.log(`
|
|
2385
|
+
You already have ${hat?.name ?? result.hat_id}${variantSuffix}. +${result.xp_awarded} XP.
|
|
2386
|
+
`);
|
|
2387
|
+
} else {
|
|
2388
|
+
console.log(`
|
|
2389
|
+
No hat this time. +${result.xp_awarded} XP toward your next level.
|
|
2390
|
+
`);
|
|
2391
|
+
}
|
|
2392
|
+
if (result.remaining_rolls <= 0) return 0;
|
|
2393
|
+
if (!await promptYesNo(`${result.remaining_rolls} more roll${result.remaining_rolls === 1 ? "" : "s"} available. Roll again? [Y/n] `)) return 0;
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
|
|
1735
2397
|
// src/commands/org-create.ts
|
|
1736
2398
|
import * as readline6 from "readline/promises";
|
|
1737
2399
|
import { stdin as stdin6, stdout as stdout6 } from "process";
|
|
@@ -1946,6 +2608,10 @@ Races:
|
|
|
1946
2608
|
token-derby join <join-code> Join (or resume) a race
|
|
1947
2609
|
token-derby end <admin-code> End a race early
|
|
1948
2610
|
|
|
2611
|
+
Cosmetics:
|
|
2612
|
+
token-derby roll Spend a pending roll to try for a hat.
|
|
2613
|
+
Earn rolls by leveling up horses.
|
|
2614
|
+
|
|
1949
2615
|
Environment:
|
|
1950
2616
|
TOKEN_DERBY_API_BASE Override API base URL (default: production)
|
|
1951
2617
|
TOKEN_DERBY_HOME Override identity/stable directory
|
|
@@ -2006,6 +2672,7 @@ async function main() {
|
|
|
2006
2672
|
}
|
|
2007
2673
|
if (cmd === "join") return joinCommand(argv[1]);
|
|
2008
2674
|
if (cmd === "end") return endCommand(argv[1]);
|
|
2675
|
+
if (cmd === "roll") return rollCommand();
|
|
2009
2676
|
console.error(`Unknown command: ${cmd}`);
|
|
2010
2677
|
console.error(HELP);
|
|
2011
2678
|
return 2;
|