jaml-ui 0.1.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/LICENSE +21 -0
- package/README.md +110 -0
- package/assets/8BitDeck.png +0 -0
- package/assets/BlindChips.png +0 -0
- package/assets/Boosters.png +0 -0
- package/assets/Editions.png +0 -0
- package/assets/Enhancers.png +0 -0
- package/assets/Jokers.png +0 -0
- package/assets/Tarots.png +0 -0
- package/assets/Vouchers.png +0 -0
- package/assets/stickers.png +0 -0
- package/assets/tags.png +0 -0
- package/dist/assets.d.ts +18 -0
- package/dist/assets.js +66 -0
- package/dist/components/CardList.d.ts +8 -0
- package/dist/components/CardList.js +5 -0
- package/dist/components/GameCard.d.ts +52 -0
- package/dist/components/GameCard.js +351 -0
- package/dist/components/PlayingCard.d.ts +18 -0
- package/dist/components/PlayingCard.js +115 -0
- package/dist/core.d.ts +7 -0
- package/dist/core.js +7 -0
- package/dist/decode/motelyItemDecoder.d.ts +23 -0
- package/dist/decode/motelyItemDecoder.js +71 -0
- package/dist/decode/packedBalatroItem.d.ts +13 -0
- package/dist/decode/packedBalatroItem.js +26 -0
- package/dist/hooks/useShopStream.d.ts +22 -0
- package/dist/hooks/useShopStream.js +82 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +7 -0
- package/dist/motely.d.ts +1 -0
- package/dist/motely.js +2 -0
- package/dist/render/CanvasRenderer.d.ts +7 -0
- package/dist/render/CanvasRenderer.js +147 -0
- package/dist/render/Layer.d.ts +29 -0
- package/dist/render/Layer.js +18 -0
- package/dist/sprites/spriteData.d.ts +57 -0
- package/dist/sprites/spriteData.js +99 -0
- package/dist/sprites/spriteMapper.d.ts +11 -0
- package/dist/sprites/spriteMapper.js +42 -0
- package/dist/utils/gameCardUtils.d.ts +12 -0
- package/dist/utils/gameCardUtils.js +49 -0
- package/dist/utils/itemUtils.d.ts +11 -0
- package/dist/utils/itemUtils.js +71 -0
- package/package.json +72 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 pifreak
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# jaml-ui
|
|
2
|
+
|
|
3
|
+
Balatro rendering components, sprite metadata, and optional Motely helpers for React apps.
|
|
4
|
+
|
|
5
|
+
## Package shape
|
|
6
|
+
|
|
7
|
+
- `jaml-ui`
|
|
8
|
+
- React/client entry for rendered components and hooks
|
|
9
|
+
- `jaml-ui/core`
|
|
10
|
+
- Pure asset helpers, sprite metadata, and decode utilities that do not depend on `motely-wasm`
|
|
11
|
+
- `jaml-ui/motely`
|
|
12
|
+
- Optional plain `motely-wasm` helpers for decoding Motely item enums
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install jaml-ui react react-dom
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
If you want the Motely-specific helpers too:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install jaml-ui motely-wasm react react-dom
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick start
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
"use client";
|
|
30
|
+
|
|
31
|
+
import { JamlGameCard } from "jaml-ui";
|
|
32
|
+
|
|
33
|
+
export function Example() {
|
|
34
|
+
return (
|
|
35
|
+
<JamlGameCard
|
|
36
|
+
type="joker"
|
|
37
|
+
card={{
|
|
38
|
+
name: "Blueprint",
|
|
39
|
+
edition: "Foil",
|
|
40
|
+
isEternal: true,
|
|
41
|
+
scale: 1.5,
|
|
42
|
+
}}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Asset handling
|
|
49
|
+
|
|
50
|
+
By default, `jaml-ui` resolves its packaged sprite assets from the package `assets/` directory using `import.meta.url`.
|
|
51
|
+
|
|
52
|
+
If you want to host the assets yourself, set a custom base URL once during app startup:
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
"use client";
|
|
56
|
+
|
|
57
|
+
import { setJamlAssetBaseUrl } from "jaml-ui";
|
|
58
|
+
|
|
59
|
+
setJamlAssetBaseUrl("/vendor/jaml-ui/");
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Reset back to packaged assets with:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { clearJamlAssetBaseUrl } from "jaml-ui";
|
|
66
|
+
|
|
67
|
+
clearJamlAssetBaseUrl();
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Core utilities
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import { SPRITE_SHEETS, getSpriteData, resolveJamlAssetUrl } from "jaml-ui/core";
|
|
74
|
+
|
|
75
|
+
const jokerSheetUrl = SPRITE_SHEETS.jokers.src;
|
|
76
|
+
const blueprintSprite = getSpriteData("Blueprint");
|
|
77
|
+
const vouchersUrl = resolveJamlAssetUrl("vouchers");
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Motely helpers
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
"use client";
|
|
84
|
+
|
|
85
|
+
import { decodeMotelyItemName, motelyItemTypeName } from "jaml-ui/motely";
|
|
86
|
+
|
|
87
|
+
const itemName = decodeMotelyItemName(0x5001);
|
|
88
|
+
const enumKey = motelyItemTypeName(0x5001);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Next.js notes
|
|
92
|
+
|
|
93
|
+
- The root `jaml-ui` entry is client-oriented and preserves the `"use client"` boundary for component consumers.
|
|
94
|
+
- Import pure helpers from `jaml-ui/core` when you want server-safe metadata and asset utilities.
|
|
95
|
+
- If you are consuming `jaml-ui` from a local workspace package in a Next.js app, you may need:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
// next.config.ts
|
|
99
|
+
import type { NextConfig } from "next";
|
|
100
|
+
|
|
101
|
+
const nextConfig: NextConfig = {
|
|
102
|
+
transpilePackages: ["jaml-ui"],
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export default nextConfig;
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Browser-first runtime direction
|
|
109
|
+
|
|
110
|
+
`jaml-ui` is designed for browser/React consumers. The optional `jaml-ui/motely` entry targets plain `motely-wasm` and does not assume threaded WASM, SAB, or COEP setup.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/assets/tags.png
ADDED
|
Binary file
|
package/dist/assets.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const JAML_ASSET_FILES: {
|
|
2
|
+
readonly deck: "8BitDeck.png";
|
|
3
|
+
readonly blinds: "BlindChips.png";
|
|
4
|
+
readonly boosters: "Boosters.png";
|
|
5
|
+
readonly editions: "Editions.png";
|
|
6
|
+
readonly enhancers: "Enhancers.png";
|
|
7
|
+
readonly jokers: "Jokers.png";
|
|
8
|
+
readonly tarots: "Tarots.png";
|
|
9
|
+
readonly vouchers: "Vouchers.png";
|
|
10
|
+
readonly stickers: "stickers.png";
|
|
11
|
+
readonly tags: "tags.png";
|
|
12
|
+
};
|
|
13
|
+
export type JamlAssetKey = keyof typeof JAML_ASSET_FILES;
|
|
14
|
+
export type JamlAssetFile = (typeof JAML_ASSET_FILES)[JamlAssetKey];
|
|
15
|
+
export declare function setJamlAssetBaseUrl(baseUrl: string | null | undefined): void;
|
|
16
|
+
export declare function clearJamlAssetBaseUrl(): void;
|
|
17
|
+
export declare function resolveJamlAssetUrl(asset: JamlAssetKey | JamlAssetFile): string;
|
|
18
|
+
export declare function getDefaultJamlAssetUrlMap(): Readonly<Record<JamlAssetKey, string>>;
|
package/dist/assets.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export const JAML_ASSET_FILES = {
|
|
2
|
+
deck: "8BitDeck.png",
|
|
3
|
+
blinds: "BlindChips.png",
|
|
4
|
+
boosters: "Boosters.png",
|
|
5
|
+
editions: "Editions.png",
|
|
6
|
+
enhancers: "Enhancers.png",
|
|
7
|
+
jokers: "Jokers.png",
|
|
8
|
+
tarots: "Tarots.png",
|
|
9
|
+
vouchers: "Vouchers.png",
|
|
10
|
+
stickers: "stickers.png",
|
|
11
|
+
tags: "tags.png",
|
|
12
|
+
};
|
|
13
|
+
const assetKeyByFileName = Object.fromEntries(Object.entries(JAML_ASSET_FILES).map(([key, fileName]) => [fileName, key]));
|
|
14
|
+
const defaultAssetUrls = {
|
|
15
|
+
deck: new URL("../assets/8BitDeck.png", import.meta.url).href,
|
|
16
|
+
blinds: new URL("../assets/BlindChips.png", import.meta.url).href,
|
|
17
|
+
boosters: new URL("../assets/Boosters.png", import.meta.url).href,
|
|
18
|
+
editions: new URL("../assets/Editions.png", import.meta.url).href,
|
|
19
|
+
enhancers: new URL("../assets/Enhancers.png", import.meta.url).href,
|
|
20
|
+
jokers: new URL("../assets/Jokers.png", import.meta.url).href,
|
|
21
|
+
tarots: new URL("../assets/Tarots.png", import.meta.url).href,
|
|
22
|
+
vouchers: new URL("../assets/Vouchers.png", import.meta.url).href,
|
|
23
|
+
stickers: new URL("../assets/stickers.png", import.meta.url).href,
|
|
24
|
+
tags: new URL("../assets/tags.png", import.meta.url).href,
|
|
25
|
+
};
|
|
26
|
+
let customAssetBaseUrl = null;
|
|
27
|
+
function normalizeBaseUrl(baseUrl) {
|
|
28
|
+
const trimmed = baseUrl.trim();
|
|
29
|
+
if (trimmed.length === 0) {
|
|
30
|
+
throw new Error("Jaml asset base URL must not be empty.");
|
|
31
|
+
}
|
|
32
|
+
return trimmed.endsWith("/") ? trimmed : `${trimmed}/`;
|
|
33
|
+
}
|
|
34
|
+
function joinAssetUrl(baseUrl, fileName) {
|
|
35
|
+
const normalized = normalizeBaseUrl(baseUrl);
|
|
36
|
+
if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(normalized) || normalized.startsWith("//")) {
|
|
37
|
+
return new URL(fileName, normalized).href;
|
|
38
|
+
}
|
|
39
|
+
return `${normalized}${fileName}`;
|
|
40
|
+
}
|
|
41
|
+
export function setJamlAssetBaseUrl(baseUrl) {
|
|
42
|
+
if (baseUrl == null) {
|
|
43
|
+
customAssetBaseUrl = null;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const trimmed = baseUrl.trim();
|
|
47
|
+
customAssetBaseUrl = trimmed.length === 0 ? null : normalizeBaseUrl(trimmed);
|
|
48
|
+
}
|
|
49
|
+
export function clearJamlAssetBaseUrl() {
|
|
50
|
+
customAssetBaseUrl = null;
|
|
51
|
+
}
|
|
52
|
+
export function resolveJamlAssetUrl(asset) {
|
|
53
|
+
const assetKey = asset in JAML_ASSET_FILES
|
|
54
|
+
? asset
|
|
55
|
+
: assetKeyByFileName[asset];
|
|
56
|
+
if (!assetKey) {
|
|
57
|
+
throw new Error(`Unknown Jaml asset '${asset}'.`);
|
|
58
|
+
}
|
|
59
|
+
if (customAssetBaseUrl) {
|
|
60
|
+
return joinAssetUrl(customAssetBaseUrl, JAML_ASSET_FILES[assetKey]);
|
|
61
|
+
}
|
|
62
|
+
return defaultAssetUrls[assetKey];
|
|
63
|
+
}
|
|
64
|
+
export function getDefaultJamlAssetUrlMap() {
|
|
65
|
+
return defaultAssetUrls;
|
|
66
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export interface CardListProps {
|
|
3
|
+
title: string;
|
|
4
|
+
subtitle?: string;
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
className?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function CardList({ title, subtitle, children, className }: CardListProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
export function CardList({ title, subtitle, children, className = "" }) {
|
|
4
|
+
return (_jsxs("div", { className: className, children: [_jsxs("div", { style: { marginBottom: 4 }, children: [_jsx("span", { style: { fontWeight: 600, fontSize: 12 }, children: title }), subtitle && _jsx("span", { style: { fontSize: 10, opacity: 0.6, marginLeft: 4 }, children: subtitle })] }), _jsx("div", { style: { display: "flex", gap: 4, overflowX: "auto", paddingBottom: 4 }, children: children })] }));
|
|
5
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export interface JamlGameCardProps {
|
|
2
|
+
card: {
|
|
3
|
+
name: string;
|
|
4
|
+
edition?: "Foil" | "Holographic" | "Polychrome" | "Negative";
|
|
5
|
+
isEternal?: boolean;
|
|
6
|
+
isPerishable?: boolean;
|
|
7
|
+
isRental?: boolean;
|
|
8
|
+
rank?: string;
|
|
9
|
+
suit?: string;
|
|
10
|
+
enhancements?: string[];
|
|
11
|
+
seal?: string;
|
|
12
|
+
scale?: number;
|
|
13
|
+
};
|
|
14
|
+
type: "joker" | "consumable" | "playing";
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
export type AnalyzerShopItem = {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
value?: number;
|
|
21
|
+
};
|
|
22
|
+
export type AnalyzerResolvedItem = {
|
|
23
|
+
kind: "voucher";
|
|
24
|
+
voucherName: string;
|
|
25
|
+
} | {
|
|
26
|
+
kind: "joker" | "consumable" | "playing";
|
|
27
|
+
card: JamlGameCardProps["card"];
|
|
28
|
+
type: JamlGameCardProps["type"];
|
|
29
|
+
} | {
|
|
30
|
+
kind: "unknown";
|
|
31
|
+
label: string;
|
|
32
|
+
};
|
|
33
|
+
export declare function resolveAnalyzerShopItem(item: AnalyzerShopItem, scale?: number): AnalyzerResolvedItem;
|
|
34
|
+
export declare function JamlGameCard({ card, type, className }: JamlGameCardProps): import("react/jsx-runtime").JSX.Element;
|
|
35
|
+
export interface VoucherProps {
|
|
36
|
+
voucherName: string;
|
|
37
|
+
scale?: number;
|
|
38
|
+
className?: string;
|
|
39
|
+
}
|
|
40
|
+
export declare function JamlVoucher({ voucherName, scale, className }: VoucherProps): import("react/jsx-runtime").JSX.Element | null;
|
|
41
|
+
export interface TagProps {
|
|
42
|
+
tagName: string;
|
|
43
|
+
scale?: number;
|
|
44
|
+
className?: string;
|
|
45
|
+
}
|
|
46
|
+
export declare function JamlTag({ tagName, scale, className }: TagProps): import("react/jsx-runtime").JSX.Element | null;
|
|
47
|
+
export interface BossProps {
|
|
48
|
+
bossName: string;
|
|
49
|
+
scale?: number;
|
|
50
|
+
className?: string;
|
|
51
|
+
}
|
|
52
|
+
export declare function JamlBoss({ bossName, scale, className }: BossProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Layer } from "../render/Layer.js";
|
|
4
|
+
import { JamlCardRenderer } from "../render/CanvasRenderer.js";
|
|
5
|
+
import { JOKERS, JOKER_FACES, TAROTS_AND_PLANETS, CONSUMABLE_FACES, TAGS, VOUCHERS, BOSSES, EDITION_MAP, SPRITE_SHEETS, STICKER_MAP, } from "../sprites/spriteData.js";
|
|
6
|
+
import { BalatroItemCategory, isPackedItemValid, packedItemCategory } from "../decode/packedBalatroItem.js";
|
|
7
|
+
import { getEnhancerPosition, getSealPosition, getStandardCardPosition } from "../utils/gameCardUtils.js";
|
|
8
|
+
function normalizeCardRank(raw) {
|
|
9
|
+
const value = raw.trim().toUpperCase();
|
|
10
|
+
if (value === "A" || value === "ACE")
|
|
11
|
+
return "Ace";
|
|
12
|
+
if (value === "K" || value === "KING")
|
|
13
|
+
return "King";
|
|
14
|
+
if (value === "Q" || value === "QUEEN")
|
|
15
|
+
return "Queen";
|
|
16
|
+
if (value === "J" || value === "JACK")
|
|
17
|
+
return "Jack";
|
|
18
|
+
return raw.trim();
|
|
19
|
+
}
|
|
20
|
+
function normalizeCardSuit(raw) {
|
|
21
|
+
const value = raw.trim().toLowerCase();
|
|
22
|
+
if (value === "heart" || value === "hearts")
|
|
23
|
+
return "Hearts";
|
|
24
|
+
if (value === "club" || value === "clubs")
|
|
25
|
+
return "Clubs";
|
|
26
|
+
if (value === "diamond" || value === "diamonds")
|
|
27
|
+
return "Diamonds";
|
|
28
|
+
if (value === "spade" || value === "spades")
|
|
29
|
+
return "Spades";
|
|
30
|
+
return raw.trim();
|
|
31
|
+
}
|
|
32
|
+
function parsePlayingCardName(name) {
|
|
33
|
+
const trimmed = name.trim();
|
|
34
|
+
const ofMatch = /^(A|K|Q|J|10|[2-9]|Ace|King|Queen|Jack)\s+of\s+(Hearts|Clubs|Diamonds|Spades)$/i.exec(trimmed);
|
|
35
|
+
if (ofMatch) {
|
|
36
|
+
return {
|
|
37
|
+
rank: normalizeCardRank(ofMatch[1]),
|
|
38
|
+
suit: normalizeCardSuit(ofMatch[2]),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const shortMatch = /^(A|K|Q|J|10|[2-9])\s*([HCDS])$/i.exec(trimmed);
|
|
42
|
+
if (shortMatch) {
|
|
43
|
+
const suitMap = {
|
|
44
|
+
H: "Hearts",
|
|
45
|
+
C: "Clubs",
|
|
46
|
+
D: "Diamonds",
|
|
47
|
+
S: "Spades",
|
|
48
|
+
};
|
|
49
|
+
return {
|
|
50
|
+
rank: normalizeCardRank(shortMatch[1]),
|
|
51
|
+
suit: suitMap[shortMatch[2].toUpperCase()],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
const MODIFIER_PREFIXES = [
|
|
57
|
+
"Eternal",
|
|
58
|
+
"Perishable",
|
|
59
|
+
"Rental",
|
|
60
|
+
"Foil",
|
|
61
|
+
"Holographic",
|
|
62
|
+
"Polychrome",
|
|
63
|
+
"Negative",
|
|
64
|
+
"Bonus",
|
|
65
|
+
"Mult",
|
|
66
|
+
"Wild",
|
|
67
|
+
"Glass",
|
|
68
|
+
"Steel",
|
|
69
|
+
"Stone",
|
|
70
|
+
"Gold",
|
|
71
|
+
"Lucky",
|
|
72
|
+
"Gold Seal",
|
|
73
|
+
"Red Seal",
|
|
74
|
+
"Blue Seal",
|
|
75
|
+
"Purple Seal",
|
|
76
|
+
];
|
|
77
|
+
function stripModifiers(name) {
|
|
78
|
+
let remaining = name;
|
|
79
|
+
let edition;
|
|
80
|
+
let isEternal = false;
|
|
81
|
+
let isPerishable = false;
|
|
82
|
+
let isRental = false;
|
|
83
|
+
let changed = true;
|
|
84
|
+
while (changed) {
|
|
85
|
+
changed = false;
|
|
86
|
+
for (const prefix of MODIFIER_PREFIXES) {
|
|
87
|
+
if (remaining.startsWith(prefix + " ")) {
|
|
88
|
+
const stripped = remaining.slice(prefix.length + 1);
|
|
89
|
+
if (prefix === "Foil" || prefix === "Holographic" || prefix === "Polychrome" || prefix === "Negative") {
|
|
90
|
+
edition = prefix;
|
|
91
|
+
}
|
|
92
|
+
else if (prefix === "Eternal") {
|
|
93
|
+
isEternal = true;
|
|
94
|
+
}
|
|
95
|
+
else if (prefix === "Perishable") {
|
|
96
|
+
isPerishable = true;
|
|
97
|
+
}
|
|
98
|
+
else if (prefix === "Rental") {
|
|
99
|
+
isRental = true;
|
|
100
|
+
}
|
|
101
|
+
remaining = stripped;
|
|
102
|
+
changed = true;
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return { baseName: remaining, edition, isEternal, isPerishable, isRental };
|
|
108
|
+
}
|
|
109
|
+
function resolvePackedAnalyzerItem(item, scale) {
|
|
110
|
+
if (typeof item.value !== "number" || !Number.isFinite(item.value) || !isPackedItemValid(item.value)) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
const displayName = String(item.name || "").trim();
|
|
114
|
+
const { baseName, edition, isEternal, isPerishable, isRental } = stripModifiers(displayName);
|
|
115
|
+
const category = packedItemCategory(item.value);
|
|
116
|
+
if (category === BalatroItemCategory.Joker) {
|
|
117
|
+
const jokerName = JOKERS.some((joker) => joker.name === baseName) ? baseName : displayName;
|
|
118
|
+
if (JOKERS.some((joker) => joker.name === jokerName)) {
|
|
119
|
+
return { kind: "joker", type: "joker", card: { name: jokerName, edition, isEternal, isPerishable, isRental, scale } };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (category === BalatroItemCategory.Tarot ||
|
|
123
|
+
category === BalatroItemCategory.Planet ||
|
|
124
|
+
category === BalatroItemCategory.Spectral) {
|
|
125
|
+
const consumableName = TAROTS_AND_PLANETS.some((consumable) => consumable.name === baseName) ? baseName : displayName;
|
|
126
|
+
if (TAROTS_AND_PLANETS.some((consumable) => consumable.name === consumableName)) {
|
|
127
|
+
return { kind: "consumable", type: "consumable", card: { name: consumableName, edition, scale } };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (baseName !== displayName) {
|
|
131
|
+
if (JOKERS.some((joker) => joker.name === baseName)) {
|
|
132
|
+
return { kind: "joker", type: "joker", card: { name: baseName, edition, isEternal, isPerishable, isRental, scale } };
|
|
133
|
+
}
|
|
134
|
+
if (TAROTS_AND_PLANETS.some((consumable) => consumable.name === baseName)) {
|
|
135
|
+
return { kind: "consumable", type: "consumable", card: { name: baseName, edition, scale } };
|
|
136
|
+
}
|
|
137
|
+
if (VOUCHERS.some((voucher) => voucher.name === baseName)) {
|
|
138
|
+
return { kind: "voucher", voucherName: baseName };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const playingCard = parsePlayingCardName(displayName) ?? parsePlayingCardName(baseName);
|
|
142
|
+
if (playingCard) {
|
|
143
|
+
return {
|
|
144
|
+
kind: "playing",
|
|
145
|
+
type: "playing",
|
|
146
|
+
card: {
|
|
147
|
+
name: displayName,
|
|
148
|
+
rank: playingCard.rank,
|
|
149
|
+
suit: playingCard.suit,
|
|
150
|
+
scale,
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
return { kind: "unknown", label: displayName };
|
|
155
|
+
}
|
|
156
|
+
export function resolveAnalyzerShopItem(item, scale = 1) {
|
|
157
|
+
const displayName = String(item.name || "").trim();
|
|
158
|
+
if (!displayName) {
|
|
159
|
+
return { kind: "unknown", label: String(item.id || "").trim() || "Unknown Item" };
|
|
160
|
+
}
|
|
161
|
+
const packedResolved = resolvePackedAnalyzerItem(item, scale);
|
|
162
|
+
if (packedResolved && packedResolved.kind !== "unknown") {
|
|
163
|
+
return packedResolved;
|
|
164
|
+
}
|
|
165
|
+
if (VOUCHERS.some((voucher) => voucher.name === displayName)) {
|
|
166
|
+
return { kind: "voucher", voucherName: displayName };
|
|
167
|
+
}
|
|
168
|
+
if (JOKERS.some((joker) => joker.name === displayName)) {
|
|
169
|
+
return { kind: "joker", type: "joker", card: { name: displayName, scale } };
|
|
170
|
+
}
|
|
171
|
+
if (TAROTS_AND_PLANETS.some((consumable) => consumable.name === displayName)) {
|
|
172
|
+
return { kind: "consumable", type: "consumable", card: { name: displayName, scale } };
|
|
173
|
+
}
|
|
174
|
+
const { baseName, edition, isEternal, isPerishable, isRental } = stripModifiers(displayName);
|
|
175
|
+
if (baseName !== displayName) {
|
|
176
|
+
if (JOKERS.some((joker) => joker.name === baseName)) {
|
|
177
|
+
return { kind: "joker", type: "joker", card: { name: baseName, edition, isEternal, isPerishable, isRental, scale } };
|
|
178
|
+
}
|
|
179
|
+
if (TAROTS_AND_PLANETS.some((consumable) => consumable.name === baseName)) {
|
|
180
|
+
return { kind: "consumable", type: "consumable", card: { name: baseName, edition, scale } };
|
|
181
|
+
}
|
|
182
|
+
if (VOUCHERS.some((voucher) => voucher.name === baseName)) {
|
|
183
|
+
return { kind: "voucher", voucherName: baseName };
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const playingCard = parsePlayingCardName(displayName) ?? parsePlayingCardName(baseName);
|
|
187
|
+
if (playingCard) {
|
|
188
|
+
return {
|
|
189
|
+
kind: "playing",
|
|
190
|
+
type: "playing",
|
|
191
|
+
card: {
|
|
192
|
+
name: displayName,
|
|
193
|
+
rank: playingCard.rank,
|
|
194
|
+
suit: playingCard.suit,
|
|
195
|
+
scale,
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
return packedResolved ?? { kind: "unknown", label: displayName };
|
|
200
|
+
}
|
|
201
|
+
export function JamlGameCard({ card, type, className = "" }) {
|
|
202
|
+
const { name, edition, isEternal, isPerishable, isRental, rank, suit, enhancements, seal, scale = 1 } = card;
|
|
203
|
+
const layers = [];
|
|
204
|
+
if (type === "joker") {
|
|
205
|
+
const jokerData = JOKERS.find((j) => j.name === name);
|
|
206
|
+
if (jokerData)
|
|
207
|
+
layers.push(new Layer({ ...jokerData, source: SPRITE_SHEETS.jokers.src, order: 0, columns: SPRITE_SHEETS.jokers.columns, rows: SPRITE_SHEETS.jokers.rows }));
|
|
208
|
+
const face = JOKER_FACES.find((j) => j.name === name);
|
|
209
|
+
if (face)
|
|
210
|
+
layers.push(new Layer({ ...face, source: SPRITE_SHEETS.jokers.src, order: 1, columns: SPRITE_SHEETS.jokers.columns, rows: SPRITE_SHEETS.jokers.rows }));
|
|
211
|
+
}
|
|
212
|
+
else if (type === "consumable") {
|
|
213
|
+
const consumable = TAROTS_AND_PLANETS.find((t) => t.name === name);
|
|
214
|
+
if (consumable)
|
|
215
|
+
layers.push(new Layer({ ...consumable, order: 0, source: SPRITE_SHEETS.tarots.src, rows: SPRITE_SHEETS.tarots.rows, columns: SPRITE_SHEETS.tarots.columns }));
|
|
216
|
+
const face = CONSUMABLE_FACES.find((t) => t.name === name);
|
|
217
|
+
if (face)
|
|
218
|
+
layers.push(new Layer({
|
|
219
|
+
...face,
|
|
220
|
+
order: 1,
|
|
221
|
+
source: SPRITE_SHEETS.enhancers.src,
|
|
222
|
+
rows: SPRITE_SHEETS.enhancers.rows,
|
|
223
|
+
columns: SPRITE_SHEETS.enhancers.columns,
|
|
224
|
+
animated: face.animated,
|
|
225
|
+
}));
|
|
226
|
+
}
|
|
227
|
+
else if (rank && suit) {
|
|
228
|
+
layers.push(new Layer({
|
|
229
|
+
pos: getEnhancerPosition(enhancements ?? []),
|
|
230
|
+
name: "background",
|
|
231
|
+
order: 0,
|
|
232
|
+
source: SPRITE_SHEETS.enhancers.src,
|
|
233
|
+
rows: SPRITE_SHEETS.enhancers.rows,
|
|
234
|
+
columns: SPRITE_SHEETS.enhancers.columns,
|
|
235
|
+
}));
|
|
236
|
+
layers.push(new Layer({
|
|
237
|
+
pos: getStandardCardPosition(rank, suit),
|
|
238
|
+
name,
|
|
239
|
+
order: 1,
|
|
240
|
+
source: SPRITE_SHEETS.deck.src,
|
|
241
|
+
rows: SPRITE_SHEETS.deck.rows,
|
|
242
|
+
columns: SPRITE_SHEETS.deck.columns,
|
|
243
|
+
}));
|
|
244
|
+
}
|
|
245
|
+
if (edition) {
|
|
246
|
+
const index = EDITION_MAP[edition];
|
|
247
|
+
if (index !== undefined) {
|
|
248
|
+
layers.push(new Layer({
|
|
249
|
+
pos: { x: index, y: 0 },
|
|
250
|
+
name: edition,
|
|
251
|
+
order: 2,
|
|
252
|
+
source: SPRITE_SHEETS.editions.src,
|
|
253
|
+
rows: SPRITE_SHEETS.editions.rows,
|
|
254
|
+
columns: SPRITE_SHEETS.editions.columns,
|
|
255
|
+
}));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (isEternal) {
|
|
259
|
+
layers.push(new Layer({
|
|
260
|
+
pos: STICKER_MAP["Eternal"],
|
|
261
|
+
name: "Eternal",
|
|
262
|
+
order: 3,
|
|
263
|
+
source: SPRITE_SHEETS.stickers.src,
|
|
264
|
+
rows: SPRITE_SHEETS.stickers.rows,
|
|
265
|
+
columns: SPRITE_SHEETS.stickers.columns,
|
|
266
|
+
}));
|
|
267
|
+
}
|
|
268
|
+
if (isPerishable) {
|
|
269
|
+
layers.push(new Layer({
|
|
270
|
+
pos: STICKER_MAP["Perishable"],
|
|
271
|
+
name: "Perishable",
|
|
272
|
+
order: 4,
|
|
273
|
+
source: SPRITE_SHEETS.stickers.src,
|
|
274
|
+
rows: SPRITE_SHEETS.stickers.rows,
|
|
275
|
+
columns: SPRITE_SHEETS.stickers.columns,
|
|
276
|
+
}));
|
|
277
|
+
}
|
|
278
|
+
if (isRental) {
|
|
279
|
+
layers.push(new Layer({
|
|
280
|
+
pos: STICKER_MAP["Rental"],
|
|
281
|
+
name: "Rental",
|
|
282
|
+
order: 5,
|
|
283
|
+
source: SPRITE_SHEETS.stickers.src,
|
|
284
|
+
rows: SPRITE_SHEETS.stickers.rows,
|
|
285
|
+
columns: SPRITE_SHEETS.stickers.columns,
|
|
286
|
+
}));
|
|
287
|
+
}
|
|
288
|
+
if (seal) {
|
|
289
|
+
const sealPos = getSealPosition(seal);
|
|
290
|
+
if (sealPos) {
|
|
291
|
+
layers.push(new Layer({
|
|
292
|
+
pos: sealPos,
|
|
293
|
+
name: seal,
|
|
294
|
+
order: 6,
|
|
295
|
+
source: SPRITE_SHEETS.enhancers.src,
|
|
296
|
+
rows: SPRITE_SHEETS.enhancers.rows,
|
|
297
|
+
columns: SPRITE_SHEETS.enhancers.columns,
|
|
298
|
+
}));
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
const wrapperStyle = { width: `${71 * scale}px` };
|
|
302
|
+
return (_jsx("div", { style: wrapperStyle, className: className, children: _jsx(JamlCardRenderer, { invert: edition === "Negative", layers: layers }) }));
|
|
303
|
+
}
|
|
304
|
+
export function JamlVoucher({ voucherName, scale = 1, className = "" }) {
|
|
305
|
+
const voucherData = VOUCHERS.find((v) => v.name === voucherName);
|
|
306
|
+
if (!voucherData)
|
|
307
|
+
return null;
|
|
308
|
+
const layers = [
|
|
309
|
+
new Layer({
|
|
310
|
+
...voucherData,
|
|
311
|
+
order: 0,
|
|
312
|
+
source: SPRITE_SHEETS.vouchers.src,
|
|
313
|
+
rows: SPRITE_SHEETS.vouchers.rows,
|
|
314
|
+
columns: SPRITE_SHEETS.vouchers.columns,
|
|
315
|
+
}),
|
|
316
|
+
];
|
|
317
|
+
const wrapperStyle = { width: `${71 * scale}px` };
|
|
318
|
+
return (_jsx("div", { style: wrapperStyle, className: className, children: _jsx(JamlCardRenderer, { layers: layers }) }));
|
|
319
|
+
}
|
|
320
|
+
export function JamlTag({ tagName, scale = 1, className = "" }) {
|
|
321
|
+
const tagData = TAGS.find((t) => t.name === tagName);
|
|
322
|
+
if (!tagData)
|
|
323
|
+
return null;
|
|
324
|
+
const layers = [
|
|
325
|
+
new Layer({
|
|
326
|
+
...tagData,
|
|
327
|
+
order: 0,
|
|
328
|
+
source: SPRITE_SHEETS.tags.src,
|
|
329
|
+
rows: SPRITE_SHEETS.tags.rows,
|
|
330
|
+
columns: SPRITE_SHEETS.tags.columns,
|
|
331
|
+
}),
|
|
332
|
+
];
|
|
333
|
+
const wrapperStyle = { width: `${71 * scale}px` };
|
|
334
|
+
return (_jsx("div", { style: wrapperStyle, className: className, children: _jsx(JamlCardRenderer, { layers: layers }) }));
|
|
335
|
+
}
|
|
336
|
+
export function JamlBoss({ bossName, scale = 1, className = "" }) {
|
|
337
|
+
const bossData = BOSSES.find((b) => b.name === bossName);
|
|
338
|
+
if (!bossData)
|
|
339
|
+
return null;
|
|
340
|
+
const layers = [
|
|
341
|
+
new Layer({
|
|
342
|
+
...bossData,
|
|
343
|
+
order: 0,
|
|
344
|
+
source: SPRITE_SHEETS.blinds.src,
|
|
345
|
+
rows: SPRITE_SHEETS.blinds.rows,
|
|
346
|
+
columns: SPRITE_SHEETS.blinds.columns,
|
|
347
|
+
}),
|
|
348
|
+
];
|
|
349
|
+
const wrapperStyle = { width: `${71 * scale}px` };
|
|
350
|
+
return (_jsx("div", { style: wrapperStyle, className: className, children: _jsx(JamlCardRenderer, { layers: layers }) }));
|
|
351
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type CardSuit = 'Hearts' | 'Diamonds' | 'Clubs' | 'Spades' | 'hearts' | 'diamonds' | 'clubs' | 'spades';
|
|
3
|
+
export type CardRank = 'Ace' | 'King' | 'Queen' | 'Jack' | '10' | '9' | '8' | '7' | '6' | '5' | '4' | '3' | '2' | 'A' | 'K' | 'Q' | 'J';
|
|
4
|
+
export type CardEnhancement = 'bonus' | 'mult' | 'wild' | 'glass' | 'steel' | 'stone' | 'gold' | 'lucky' | null;
|
|
5
|
+
export type CardSeal = 'gold' | 'red' | 'blue' | 'purple' | null;
|
|
6
|
+
export type CardEdition = 'Foil' | 'Holographic' | 'Polychrome' | 'Negative' | null;
|
|
7
|
+
interface RealPlayingCardProps {
|
|
8
|
+
suit: CardSuit;
|
|
9
|
+
rank: CardRank;
|
|
10
|
+
enhancement?: CardEnhancement;
|
|
11
|
+
seal?: CardSeal;
|
|
12
|
+
edition?: CardEdition;
|
|
13
|
+
className?: string;
|
|
14
|
+
size?: number;
|
|
15
|
+
style?: React.CSSProperties;
|
|
16
|
+
}
|
|
17
|
+
export declare function RealPlayingCard({ suit, rank, enhancement, seal, edition, className, size, style }: RealPlayingCardProps): import("react/jsx-runtime").JSX.Element | null;
|
|
18
|
+
export {};
|