@terrafirmagreg-team/game 0.0.0-stage → 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/package.json +58 -3
- package/src/components/entry/atlas-sprite.tsx +57 -0
- package/src/components/entry/copy-id-menu.tsx +55 -0
- package/src/components/entry/entry.tsx +229 -0
- package/src/components/entry/frame.tsx +32 -0
- package/src/components/entry/group.tsx +180 -0
- package/src/components/entry/icon/tabs.tsx +118 -0
- package/src/components/entry/icon.tsx +187 -0
- package/src/components/entry/list.tsx +70 -0
- package/src/components/entry/mc-text.tsx +92 -0
- package/src/components/entry/stack-count.tsx +14 -0
- package/src/components/entry/tooltip.tsx +129 -0
- package/src/components/mdx/item-icon.tsx +69 -0
- package/src/components/preview-icon.tsx +25 -0
- package/src/hooks/use-dump-location.ts +18 -0
- package/src/hooks/use-entry-context.ts +35 -0
- package/src/hooks/use-icon-source.ts +297 -0
- package/src/hooks/use-lang-map.ts +18 -0
- package/src/lib/dump/assets/atlas/dto.ts +49 -0
- package/src/lib/dump/assets/atlas/lookup.ts +206 -0
- package/src/lib/dump/assets/atlas/sprites.ts +130 -0
- package/src/lib/dump/assets/lang/load.ts +17 -0
- package/src/lib/dump/assets/lang/names.ts +244 -0
- package/src/lib/dump/assets/urls.ts +143 -0
- package/src/lib/dump/client/atlases.ts +83 -0
- package/src/lib/dump/client/dump.ts +188 -0
- package/src/lib/dump/client/groups.ts +31 -0
- package/src/lib/dump/client/keybinds.ts +11 -0
- package/src/lib/dump/client/mods.ts +16 -0
- package/src/lib/dump/client/registry.ts +125 -0
- package/src/lib/dump/client/tabs.ts +27 -0
- package/src/lib/dump/client/tags.ts +64 -0
- package/src/lib/dump/contract/kind.ts +61 -0
- package/src/lib/dump/contract/read.ts +77 -0
- package/src/lib/dump/contract/ref.ts +262 -0
- package/src/lib/dump/contract/text.ts +86 -0
- package/src/lib/dump/data/registry/dto.ts +56 -0
- package/src/lib/dump/data/registry/parse.ts +291 -0
- package/src/lib/dump/data/tab/dto.ts +13 -0
- package/src/lib/dump/data/tab/parse.ts +26 -0
- package/src/lib/dump/data/tag/dto.ts +7 -0
- package/src/lib/dump/data/tag/parse.ts +57 -0
- package/src/lib/dump/gen/data/guides/book.ts +56 -0
- package/src/lib/dump/gen/data/guides/category.ts +30 -0
- package/src/lib/dump/gen/data/guides/entry.ts +48 -0
- package/src/lib/dump/gen/data/models/model.ts +3 -0
- package/src/lib/dump/gen/data/quests/chapter.ts +126 -0
- package/src/lib/dump/gen/data/quests/chapter_groups.ts +11 -0
- package/src/lib/dump/gen/data/quests/data.ts +40 -0
- package/src/lib/dump/gen/data/recipes/recipe.ts +78 -0
- package/src/lib/dump/gen/index.ts +20 -0
- package/src/lib/dump/gen/keybinds.ts +3 -0
- package/src/lib/dump/gen/lang.ts +3 -0
- package/src/lib/dump/gen/manifest.ts +59 -0
- package/src/lib/dump/gen/registry/blocks.ts +60 -0
- package/src/lib/dump/gen/registry/entities.ts +49 -0
- package/src/lib/dump/gen/registry/fluids.ts +48 -0
- package/src/lib/dump/gen/registry/groups.ts +14 -0
- package/src/lib/dump/gen/registry/items.ts +51 -0
- package/src/lib/dump/gen/registry/recipe_categories.ts +64 -0
- package/src/lib/dump/gen/registry/recipes.ts +3 -0
- package/src/lib/dump/gen/registry/tabs.ts +46 -0
- package/src/lib/dump/gen/registry/tags.ts +11 -0
- package/src/lib/dump/json-reader.ts +231 -0
- package/src/lib/dump/manifest/dto.ts +11 -0
- package/src/lib/dump/manifest/parse.ts +167 -0
- package/src/lib/dump/mods/dto.ts +40 -0
- package/src/lib/dump/mods/index.ts +81 -0
- package/src/lib/dump/mods/load.ts +61 -0
- package/src/lib/dump/paths.ts +171 -0
- package/src/lib/dump/reader.ts +15 -0
- package/src/lib/entry/display.ts +4 -0
- package/src/lib/entry/entry.ts +57 -0
- package/src/lib/entry/formula.ts +226 -0
- package/src/lib/entry/href.ts +148 -0
- package/src/lib/entry/icon-load.ts +195 -0
- package/src/lib/entry/icon.ts +10 -0
- package/src/lib/entry/tooltip.ts +60 -0
- package/src/lib/preview.ts +18 -0
- package/src/lib/search.ts +31 -0
- package/src/lib/snbt.ts +246 -0
- package/src/locales/en_us.json +9 -0
- package/src/locales/ru_ru.json +9 -0
- package/src/locales.ts +5 -0
- package/src/types/entry.ts +52 -0
- package/styles/fonts.css +15 -0
- package/styles/tokens.css +15 -0
- package/styles.css +2 -0
- package/README.md +0 -3
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
import { formatNbt, parseSnbt } from '@terrafirmagreg-team/game/lib/snbt';
|
|
4
|
+
import { asRecord, asString } from '@terrafirmagreg-team/game/lib/dump/contract/read';
|
|
5
|
+
|
|
6
|
+
const REFERENCE_KINDS = [
|
|
7
|
+
'item',
|
|
8
|
+
'block',
|
|
9
|
+
'fluid',
|
|
10
|
+
'entity',
|
|
11
|
+
'itemTag',
|
|
12
|
+
'blockTag',
|
|
13
|
+
'fluidTag',
|
|
14
|
+
'entityTag',
|
|
15
|
+
'texture',
|
|
16
|
+
'model',
|
|
17
|
+
'recipe',
|
|
18
|
+
'guide',
|
|
19
|
+
'quest',
|
|
20
|
+
'chapter',
|
|
21
|
+
] as const;
|
|
22
|
+
|
|
23
|
+
export type ReferenceKind = (typeof REFERENCE_KINDS)[number];
|
|
24
|
+
|
|
25
|
+
const TAG_KINDS = ['itemTag', 'blockTag', 'fluidTag', 'entityTag'] as const;
|
|
26
|
+
|
|
27
|
+
const ICON_KINDS: readonly ReferenceKind[] = [
|
|
28
|
+
'item',
|
|
29
|
+
'block',
|
|
30
|
+
'fluid',
|
|
31
|
+
'entity',
|
|
32
|
+
...TAG_KINDS,
|
|
33
|
+
'texture',
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
export type DumpNbt =
|
|
37
|
+
| string
|
|
38
|
+
| number
|
|
39
|
+
| boolean
|
|
40
|
+
| null
|
|
41
|
+
| DumpNbt[]
|
|
42
|
+
| { [key: string]: DumpNbt };
|
|
43
|
+
|
|
44
|
+
export type TaggedReference = {
|
|
45
|
+
kind: ReferenceKind;
|
|
46
|
+
id: string;
|
|
47
|
+
tag?: DumpNbt;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type ParsedStackId = {
|
|
51
|
+
id: string;
|
|
52
|
+
nbt?: DumpNbt;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
function asReferenceKind(value: unknown): ReferenceKind | undefined {
|
|
56
|
+
if (typeof value !== 'string') return undefined;
|
|
57
|
+
return (REFERENCE_KINDS as readonly string[]).includes(value)
|
|
58
|
+
? (value as ReferenceKind)
|
|
59
|
+
: undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function asNbt(value: unknown): DumpNbt | undefined {
|
|
63
|
+
if (value === null) return null;
|
|
64
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
if (Array.isArray(value)) return value as DumpNbt[];
|
|
68
|
+
const row = asRecord(value);
|
|
69
|
+
if (!row || Object.keys(row).length === 0) return undefined;
|
|
70
|
+
return row as { [key: string]: DumpNbt };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function nbtAt(
|
|
74
|
+
tag: DumpNbt | undefined,
|
|
75
|
+
...path: (string | number)[]
|
|
76
|
+
): DumpNbt | undefined {
|
|
77
|
+
let cur: DumpNbt | undefined = tag;
|
|
78
|
+
for (const key of path) {
|
|
79
|
+
if (cur === undefined || cur === null) return undefined;
|
|
80
|
+
if (typeof key === 'number') {
|
|
81
|
+
if (!Array.isArray(cur)) return undefined;
|
|
82
|
+
cur = cur[key];
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (typeof cur !== 'object' || Array.isArray(cur)) return undefined;
|
|
86
|
+
cur = cur[key];
|
|
87
|
+
}
|
|
88
|
+
return cur;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function parseStackId(compact: string): ParsedStackId {
|
|
92
|
+
const brace = compact.indexOf('{');
|
|
93
|
+
if (brace <= 0) return { id: compact };
|
|
94
|
+
const id = compact.slice(0, brace);
|
|
95
|
+
if (!id) return { id: compact };
|
|
96
|
+
try {
|
|
97
|
+
const nbt = asNbt(parseSnbt(compact.slice(brace)));
|
|
98
|
+
return nbt !== undefined ? { id, nbt } : { id };
|
|
99
|
+
} catch {
|
|
100
|
+
return { id };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function stackIdBase(compact: string): string {
|
|
105
|
+
const brace = compact.indexOf('{');
|
|
106
|
+
return brace > 0 ? compact.slice(0, brace) : compact;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function canonicalStackId(compact: string): string {
|
|
110
|
+
const brace = compact.indexOf('{');
|
|
111
|
+
if (brace <= 0) return compact;
|
|
112
|
+
const parsed = parseStackId(compact);
|
|
113
|
+
const nbt = formatNbt(parsed.nbt);
|
|
114
|
+
return nbt !== undefined && nbt.startsWith('{')
|
|
115
|
+
? `${parsed.id}${nbt}`
|
|
116
|
+
: compact;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function asTaggedReference(value: unknown): TaggedReference | undefined {
|
|
120
|
+
const row = asRecord(value);
|
|
121
|
+
if (!row) return undefined;
|
|
122
|
+
const kind = asReferenceKind(row.kind);
|
|
123
|
+
const id = asString(row.id);
|
|
124
|
+
if (!kind || !id) return undefined;
|
|
125
|
+
const tag = asNbt(row.tag);
|
|
126
|
+
return { kind, id, ...(tag !== undefined ? { tag } : {}) };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function isTagKind(
|
|
130
|
+
kind: ReferenceKind | undefined,
|
|
131
|
+
): kind is (typeof TAG_KINDS)[number] {
|
|
132
|
+
return kind !== undefined && (TAG_KINDS as readonly string[]).includes(kind);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function tagKindOf(
|
|
136
|
+
kind: ReferenceKind | undefined,
|
|
137
|
+
): 'item' | 'block' | 'fluid' | 'entity' | undefined {
|
|
138
|
+
switch (kind) {
|
|
139
|
+
case 'itemTag':
|
|
140
|
+
return 'item';
|
|
141
|
+
case 'blockTag':
|
|
142
|
+
return 'block';
|
|
143
|
+
case 'fluidTag':
|
|
144
|
+
return 'fluid';
|
|
145
|
+
case 'entityTag':
|
|
146
|
+
return 'entity';
|
|
147
|
+
default:
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function isIconKind(kind: ReferenceKind | undefined): boolean {
|
|
153
|
+
return kind !== undefined && ICON_KINDS.includes(kind);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function classifyRefId(
|
|
157
|
+
id: string,
|
|
158
|
+
kind?: ReferenceKind,
|
|
159
|
+
): TaggedReference | undefined {
|
|
160
|
+
const trimmed = id.trim();
|
|
161
|
+
if (!trimmed) return undefined;
|
|
162
|
+
const parsed = parseStackId(
|
|
163
|
+
trimmed.startsWith('#') ? trimmed.slice(1) : trimmed,
|
|
164
|
+
);
|
|
165
|
+
const resolved = trimmed.startsWith('#')
|
|
166
|
+
? (isTagKind(kind) ? kind : 'itemTag')
|
|
167
|
+
: (kind ?? 'item');
|
|
168
|
+
return {
|
|
169
|
+
kind: resolved,
|
|
170
|
+
id: parsed.id,
|
|
171
|
+
...(parsed.nbt !== undefined ? { tag: parsed.nbt } : {}),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function refId(ref: TaggedReference): string {
|
|
176
|
+
return stackIdFromRef(ref);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function compactRefId(value: unknown): string | undefined {
|
|
180
|
+
if (typeof value === 'string' && value) return value;
|
|
181
|
+
const tagged = asTaggedReference(value);
|
|
182
|
+
return tagged ? stackIdFromRef(tagged) : undefined;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function nbtEquals(left: DumpNbt | undefined, right: DumpNbt | undefined): boolean {
|
|
186
|
+
if (left === right) return true;
|
|
187
|
+
if (left === undefined || right === undefined) return false;
|
|
188
|
+
if (left === null || right === null) return left === right;
|
|
189
|
+
if (typeof left !== typeof right) return false;
|
|
190
|
+
if (typeof left !== 'object') return left === right;
|
|
191
|
+
if (Array.isArray(left)) {
|
|
192
|
+
return (
|
|
193
|
+
Array.isArray(right) &&
|
|
194
|
+
left.length === right.length &&
|
|
195
|
+
left.every((value, index) => nbtEquals(value, right[index]))
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
if (Array.isArray(right)) return false;
|
|
199
|
+
const leftRow = left as { [key: string]: DumpNbt };
|
|
200
|
+
const rightRow = right as { [key: string]: DumpNbt };
|
|
201
|
+
const leftKeys = Object.keys(leftRow).sort();
|
|
202
|
+
const rightKeys = Object.keys(rightRow).sort();
|
|
203
|
+
if (leftKeys.length !== rightKeys.length) return false;
|
|
204
|
+
return leftKeys.every(
|
|
205
|
+
(key, index) => key === rightKeys[index] && nbtEquals(leftRow[key], rightRow[key]),
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function nbtMatches(pattern: DumpNbt | undefined, actual: DumpNbt | undefined): boolean {
|
|
210
|
+
if (pattern === undefined) return actual === undefined;
|
|
211
|
+
if (actual === undefined || actual === null) return pattern === actual;
|
|
212
|
+
if (pattern === null) return actual === null;
|
|
213
|
+
if (typeof pattern !== 'object') return pattern === actual;
|
|
214
|
+
if (Array.isArray(pattern)) {
|
|
215
|
+
if (!Array.isArray(actual) || pattern.length !== actual.length) return false;
|
|
216
|
+
return pattern.every((item, index) => nbtMatches(item, actual[index]));
|
|
217
|
+
}
|
|
218
|
+
if (typeof actual !== 'object' || Array.isArray(actual) || actual === null) return false;
|
|
219
|
+
const actualRow = actual as { [key: string]: DumpNbt };
|
|
220
|
+
return Object.entries(pattern).every(([key, value]) => nbtMatches(value, actualRow[key]));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function nbtKeyCount(tag: DumpNbt | undefined): number {
|
|
224
|
+
return tag !== null && typeof tag === 'object' && !Array.isArray(tag)
|
|
225
|
+
? Object.keys(tag ?? {}).length
|
|
226
|
+
: 0;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function nbtSharedKeys(left: DumpNbt | undefined, right: DumpNbt | undefined): number {
|
|
230
|
+
if (!left || !right) return 0;
|
|
231
|
+
if (typeof left !== 'object' || Array.isArray(left)) return 0;
|
|
232
|
+
if (typeof right !== 'object' || Array.isArray(right)) return 0;
|
|
233
|
+
const rightRow = right as { [key: string]: DumpNbt };
|
|
234
|
+
let shared = 0;
|
|
235
|
+
for (const [key, value] of Object.entries(left)) {
|
|
236
|
+
if (key in rightRow && nbtEquals(value, rightRow[key])) shared++;
|
|
237
|
+
}
|
|
238
|
+
return shared;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function nbtSpecificity(candidate: DumpNbt, query: DumpNbt): number {
|
|
242
|
+
if (nbtEquals(candidate, query)) return 500;
|
|
243
|
+
if (nbtMatches(candidate, query)) return 400 + nbtKeyCount(candidate);
|
|
244
|
+
if (nbtMatches(query, candidate)) return 300 - nbtKeyCount(candidate);
|
|
245
|
+
const shared = nbtSharedKeys(candidate, query);
|
|
246
|
+
return shared > 0 ? 200 + shared : -1;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function stackIdFromRef(ref: TaggedReference): string {
|
|
250
|
+
switch (ref.kind) {
|
|
251
|
+
case 'itemTag':
|
|
252
|
+
case 'blockTag':
|
|
253
|
+
case 'fluidTag':
|
|
254
|
+
case 'entityTag':
|
|
255
|
+
return `#${ref.id.replace(/^#+/, '')}`;
|
|
256
|
+
default: {
|
|
257
|
+
const tag = formatNbt(ref.tag);
|
|
258
|
+
return tag !== undefined && tag.startsWith('{') ? `${ref.id}${tag}` : ref.id;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { asRecord, asString } from '@terrafirmagreg-team/game/lib/dump/contract/read';
|
|
2
|
+
|
|
3
|
+
export const MC_FORMAT_COLORS: Record<string, string> = {
|
|
4
|
+
'0': '#000000',
|
|
5
|
+
'1': '#0000aa',
|
|
6
|
+
'2': '#00aa00',
|
|
7
|
+
'3': '#00aaaa',
|
|
8
|
+
'4': '#aa0000',
|
|
9
|
+
'5': '#aa00aa',
|
|
10
|
+
'6': '#ffaa00',
|
|
11
|
+
'7': '#aaaaaa',
|
|
12
|
+
'8': '#555555',
|
|
13
|
+
'9': '#5555ff',
|
|
14
|
+
a: '#55ff55',
|
|
15
|
+
b: '#55ffff',
|
|
16
|
+
c: '#ff5555',
|
|
17
|
+
d: '#ff55ff',
|
|
18
|
+
e: '#ffff55',
|
|
19
|
+
f: '#ffffff',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type DumpText = {
|
|
23
|
+
translate?: string;
|
|
24
|
+
text?: string;
|
|
25
|
+
keybind?: string;
|
|
26
|
+
with?: DumpText[];
|
|
27
|
+
extra?: DumpText[];
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type DumpDisplay = {
|
|
31
|
+
name?: string;
|
|
32
|
+
tooltip?: DumpText[];
|
|
33
|
+
|
|
34
|
+
tooltipMods?: Record<string, DumpText[]>;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export function asDumpText(value: unknown): DumpText | undefined {
|
|
38
|
+
const row = asRecord(value);
|
|
39
|
+
if (!row) return undefined;
|
|
40
|
+
const translate = asString(row.translate);
|
|
41
|
+
const text = typeof row.text === 'string' ? row.text : undefined;
|
|
42
|
+
const keybind = asString(row.keybind);
|
|
43
|
+
const withParts = asDumpTextList(row.with);
|
|
44
|
+
const extra = asDumpTextList(row.extra);
|
|
45
|
+
if (!translate && text === undefined && !keybind && !withParts && !extra) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
...(translate ? { translate } : {}),
|
|
50
|
+
...(text !== undefined ? { text } : {}),
|
|
51
|
+
...(keybind ? { keybind } : {}),
|
|
52
|
+
...(withParts ? { with: withParts } : {}),
|
|
53
|
+
...(extra ? { extra } : {}),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function asDumpTextList(value: unknown): DumpText[] | undefined {
|
|
58
|
+
if (!Array.isArray(value)) return undefined;
|
|
59
|
+
const out = value.flatMap((item) => {
|
|
60
|
+
const text = asDumpText(item);
|
|
61
|
+
return text ? [text] : [];
|
|
62
|
+
});
|
|
63
|
+
return out.length ? out : undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function asDisplay(value: unknown): DumpDisplay | undefined {
|
|
67
|
+
const row = asRecord(value);
|
|
68
|
+
if (!row) return undefined;
|
|
69
|
+
const name = asString(row.name);
|
|
70
|
+
const tooltip = asDumpTextList(row.tooltip);
|
|
71
|
+
const modsRow = asRecord(row.tooltipMods);
|
|
72
|
+
const tooltipMods = modsRow
|
|
73
|
+
? Object.fromEntries(
|
|
74
|
+
Object.entries(modsRow).flatMap(([mod, lines]) => {
|
|
75
|
+
const list = asDumpTextList(lines);
|
|
76
|
+
return list ? [[mod, list] as const] : [];
|
|
77
|
+
}),
|
|
78
|
+
)
|
|
79
|
+
: undefined;
|
|
80
|
+
if (!name && !tooltip && !tooltipMods) return undefined;
|
|
81
|
+
return {
|
|
82
|
+
...(name ? { name } : {}),
|
|
83
|
+
...(tooltip ? { tooltip } : {}),
|
|
84
|
+
...(tooltipMods && Object.keys(tooltipMods).length ? { tooltipMods } : {}),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { DumpNbt, TaggedReference } from '@terrafirmagreg-team/game/lib/dump/contract/ref';
|
|
2
|
+
import type { DumpDisplay } from '@terrafirmagreg-team/game/lib/dump/contract/text';
|
|
3
|
+
import type {
|
|
4
|
+
BlockEntry,
|
|
5
|
+
EntityEntry,
|
|
6
|
+
FluidEntry,
|
|
7
|
+
ItemEntry,
|
|
8
|
+
SpriteEntry,
|
|
9
|
+
VariantEntry,
|
|
10
|
+
} from '@terrafirmagreg-team/game/lib/dump/gen';
|
|
11
|
+
|
|
12
|
+
export type { SpriteEntry };
|
|
13
|
+
|
|
14
|
+
export type DumpEntryVariant = Omit<VariantEntry, 'display' | 'tag'> & {
|
|
15
|
+
tag: DumpNbt;
|
|
16
|
+
display?: DumpDisplay;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type DumpItemEntry = Omit<
|
|
20
|
+
ItemEntry,
|
|
21
|
+
'block' | 'display' | 'maxCount' | 'rarity' | 'variants'
|
|
22
|
+
> & {
|
|
23
|
+
maxCount?: number;
|
|
24
|
+
rarity?: string;
|
|
25
|
+
block?: string;
|
|
26
|
+
display?: DumpDisplay;
|
|
27
|
+
variants?: DumpEntryVariant[];
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type DumpBlockEntry = Omit<BlockEntry, 'display' | 'item'> & {
|
|
31
|
+
item?: TaggedReference;
|
|
32
|
+
display?: DumpDisplay;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type DumpFluidEntry = Omit<FluidEntry, 'display' | 'variants'> & {
|
|
36
|
+
display?: DumpDisplay;
|
|
37
|
+
variants?: DumpEntryVariant[];
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type DumpEntityEntry = Omit<
|
|
41
|
+
EntityEntry,
|
|
42
|
+
'display' | 'model' | 'texture' | 'variants'
|
|
43
|
+
> & {
|
|
44
|
+
display?: DumpDisplay;
|
|
45
|
+
texture?: string;
|
|
46
|
+
model?: string;
|
|
47
|
+
variants?: DumpEntryVariant[];
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type DumpRegistryKind = 'items' | 'blocks' | 'fluids' | 'entities';
|
|
51
|
+
|
|
52
|
+
export type DumpRegistryEntry =
|
|
53
|
+
| DumpItemEntry
|
|
54
|
+
| DumpBlockEntry
|
|
55
|
+
| DumpFluidEntry
|
|
56
|
+
| DumpEntityEntry;
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import {
|
|
2
|
+
asEdgeList,
|
|
3
|
+
asNumber,
|
|
4
|
+
asRecord,
|
|
5
|
+
asString,
|
|
6
|
+
asStringList,
|
|
7
|
+
} from '@terrafirmagreg-team/game/lib/dump/contract/read';
|
|
8
|
+
import {
|
|
9
|
+
asNbt,
|
|
10
|
+
asTaggedReference,
|
|
11
|
+
nbtSpecificity,
|
|
12
|
+
type DumpNbt,
|
|
13
|
+
} from '@terrafirmagreg-team/game/lib/dump/contract/ref';
|
|
14
|
+
import { formatNbt } from '@terrafirmagreg-team/game/lib/snbt';
|
|
15
|
+
import { asDisplay, type DumpDisplay } from '@terrafirmagreg-team/game/lib/dump/contract/text';
|
|
16
|
+
import type {
|
|
17
|
+
DumpBlockEntry,
|
|
18
|
+
DumpEntityEntry,
|
|
19
|
+
DumpEntryVariant,
|
|
20
|
+
DumpFluidEntry,
|
|
21
|
+
DumpItemEntry,
|
|
22
|
+
DumpRegistryEntry,
|
|
23
|
+
DumpRegistryKind,
|
|
24
|
+
SpriteEntry,
|
|
25
|
+
} from '@terrafirmagreg-team/game/lib/dump/data/registry/dto';
|
|
26
|
+
|
|
27
|
+
export function asSpriteEntry(value: unknown): SpriteEntry | undefined {
|
|
28
|
+
const row = asRecord(value);
|
|
29
|
+
if (!row) return undefined;
|
|
30
|
+
const image = asString(row.image);
|
|
31
|
+
const x = asNumber(row.x);
|
|
32
|
+
const y = asNumber(row.y);
|
|
33
|
+
const w = asNumber(row.w);
|
|
34
|
+
const h = asNumber(row.h);
|
|
35
|
+
const pw = asNumber(row.pw);
|
|
36
|
+
const ph = asNumber(row.ph);
|
|
37
|
+
if (
|
|
38
|
+
!image ||
|
|
39
|
+
x === undefined ||
|
|
40
|
+
y === undefined ||
|
|
41
|
+
w === undefined ||
|
|
42
|
+
h === undefined ||
|
|
43
|
+
pw === undefined ||
|
|
44
|
+
ph === undefined
|
|
45
|
+
) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
const tint = asString(row.tint);
|
|
49
|
+
const tintColor = asString(row.tintColor);
|
|
50
|
+
return {
|
|
51
|
+
image,
|
|
52
|
+
x,
|
|
53
|
+
y,
|
|
54
|
+
w,
|
|
55
|
+
h,
|
|
56
|
+
pw,
|
|
57
|
+
ph,
|
|
58
|
+
...(tint ? { tint } : {}),
|
|
59
|
+
...(tintColor ? { tintColor } : {}),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function asVariant(value: unknown): DumpEntryVariant | undefined {
|
|
64
|
+
const row = asRecord(value);
|
|
65
|
+
if (!row) return undefined;
|
|
66
|
+
const tag = asNbt(row.tag);
|
|
67
|
+
if (tag === undefined) return undefined;
|
|
68
|
+
const display = asDisplay(row.display);
|
|
69
|
+
const sprite = asSpriteEntry(row.sprite);
|
|
70
|
+
const texture = asString(row.texture);
|
|
71
|
+
const model = asString(row.model);
|
|
72
|
+
return {
|
|
73
|
+
tag,
|
|
74
|
+
...(display ? { display } : {}),
|
|
75
|
+
...(sprite ? { sprite } : {}),
|
|
76
|
+
...(texture ? { texture } : {}),
|
|
77
|
+
...(model ? { model } : {}),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function asVariants(value: unknown): DumpEntryVariant[] | undefined {
|
|
82
|
+
if (!Array.isArray(value)) return undefined;
|
|
83
|
+
const out = value.flatMap((item) => {
|
|
84
|
+
const variant = asVariant(item);
|
|
85
|
+
return variant ? [variant] : [];
|
|
86
|
+
});
|
|
87
|
+
return out.length ? out : undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const EXACT_SAFE_MAX_KEYS = 99;
|
|
91
|
+
|
|
92
|
+
const variantExactIndex = new WeakMap<
|
|
93
|
+
DumpEntryVariant[],
|
|
94
|
+
{ byTag: Map<string, DumpEntryVariant>; maxTagKeys: number }
|
|
95
|
+
>();
|
|
96
|
+
|
|
97
|
+
function exactVariantIndex(variants: DumpEntryVariant[]) {
|
|
98
|
+
let index = variantExactIndex.get(variants);
|
|
99
|
+
if (!index) {
|
|
100
|
+
const byTag = new Map<string, DumpEntryVariant>();
|
|
101
|
+
let maxTagKeys = 0;
|
|
102
|
+
for (const variant of variants) {
|
|
103
|
+
if (variant.tag === undefined) continue;
|
|
104
|
+
const keyCount =
|
|
105
|
+
variant.tag !== null &&
|
|
106
|
+
typeof variant.tag === 'object' &&
|
|
107
|
+
!Array.isArray(variant.tag)
|
|
108
|
+
? Object.keys(variant.tag).length
|
|
109
|
+
: 0;
|
|
110
|
+
if (keyCount > maxTagKeys) maxTagKeys = keyCount;
|
|
111
|
+
const key = formatNbt(variant.tag);
|
|
112
|
+
if (key !== undefined && !byTag.has(key)) byTag.set(key, variant);
|
|
113
|
+
}
|
|
114
|
+
index = { byTag, maxTagKeys };
|
|
115
|
+
variantExactIndex.set(variants, index);
|
|
116
|
+
}
|
|
117
|
+
return index;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function matchVariant(
|
|
121
|
+
variants: DumpEntryVariant[] | undefined,
|
|
122
|
+
nbt: DumpNbt | undefined,
|
|
123
|
+
): DumpEntryVariant | undefined {
|
|
124
|
+
if (!variants?.length) return undefined;
|
|
125
|
+
if (nbt === undefined) {
|
|
126
|
+
return variants.find((variant) => variant.tag === undefined);
|
|
127
|
+
}
|
|
128
|
+
const key = formatNbt(nbt);
|
|
129
|
+
const index =
|
|
130
|
+
variants.length > 16 && key !== undefined
|
|
131
|
+
? exactVariantIndex(variants)
|
|
132
|
+
: undefined;
|
|
133
|
+
if (index && index.maxTagKeys <= EXACT_SAFE_MAX_KEYS && key !== undefined) {
|
|
134
|
+
const exact = index.byTag.get(key);
|
|
135
|
+
if (exact) return exact;
|
|
136
|
+
}
|
|
137
|
+
let best: DumpEntryVariant | undefined;
|
|
138
|
+
let bestScore = -1;
|
|
139
|
+
for (const variant of variants) {
|
|
140
|
+
if (variant.tag === undefined) continue;
|
|
141
|
+
const score = nbtSpecificity(variant.tag, nbt);
|
|
142
|
+
if (score > bestScore) {
|
|
143
|
+
best = variant;
|
|
144
|
+
bestScore = score;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return best;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function displayOf(
|
|
151
|
+
entry: { display?: DumpDisplay; variants?: DumpEntryVariant[] },
|
|
152
|
+
nbt?: DumpNbt,
|
|
153
|
+
): DumpDisplay | undefined {
|
|
154
|
+
return matchVariant(entry.variants, nbt)?.display ?? entry.display;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function asId(value: unknown, fallbackId?: string): string | undefined {
|
|
158
|
+
const row = asRecord(value);
|
|
159
|
+
return asString(row?.id) ?? fallbackId;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function asEntryMeta(row: Record<string, unknown>, ids?: string[]) {
|
|
163
|
+
const tags = asStringList(row.tags);
|
|
164
|
+
const usedBy = asEdgeList(row.usedBy, ids);
|
|
165
|
+
const producedBy = asEdgeList(row.producedBy, ids);
|
|
166
|
+
const order = asNumber(row.order);
|
|
167
|
+
return {
|
|
168
|
+
...(tags.length ? { tags } : {}),
|
|
169
|
+
...(usedBy.length ? { usedBy } : {}),
|
|
170
|
+
...(producedBy.length ? { producedBy } : {}),
|
|
171
|
+
...(order !== undefined ? { order } : {}),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function asItemEntry(
|
|
176
|
+
value: unknown,
|
|
177
|
+
fallbackId?: string,
|
|
178
|
+
ids?: string[],
|
|
179
|
+
): DumpItemEntry | undefined {
|
|
180
|
+
const id = asId(value, fallbackId);
|
|
181
|
+
if (!id) return undefined;
|
|
182
|
+
const row = asRecord(value);
|
|
183
|
+
if (!row) return undefined;
|
|
184
|
+
const rarity = asString(row.rarity);
|
|
185
|
+
const block = asString(row.block);
|
|
186
|
+
const maxCount =
|
|
187
|
+
typeof row.maxCount === 'number' && Number.isFinite(row.maxCount)
|
|
188
|
+
? row.maxCount
|
|
189
|
+
: undefined;
|
|
190
|
+
const display = asDisplay(row.display);
|
|
191
|
+
const variants = asVariants(row.variants);
|
|
192
|
+
const sprite = asSpriteEntry(row.sprite);
|
|
193
|
+
return {
|
|
194
|
+
id,
|
|
195
|
+
...(maxCount !== undefined ? { maxCount } : {}),
|
|
196
|
+
...(rarity ? { rarity } : {}),
|
|
197
|
+
...(block ? { block } : {}),
|
|
198
|
+
...(display ? { display } : {}),
|
|
199
|
+
...(variants ? { variants } : {}),
|
|
200
|
+
...(sprite ? { sprite } : {}),
|
|
201
|
+
...asEntryMeta(row, ids),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function asBlockEntry(
|
|
206
|
+
value: unknown,
|
|
207
|
+
fallbackId?: string,
|
|
208
|
+
ids?: string[],
|
|
209
|
+
): DumpBlockEntry | undefined {
|
|
210
|
+
const id = asId(value, fallbackId);
|
|
211
|
+
if (!id) return undefined;
|
|
212
|
+
const row = asRecord(value);
|
|
213
|
+
if (!row) return undefined;
|
|
214
|
+
const item = asTaggedReference(row.item);
|
|
215
|
+
const display = asDisplay(row.display);
|
|
216
|
+
const sprite = asSpriteEntry(row.sprite);
|
|
217
|
+
return {
|
|
218
|
+
id,
|
|
219
|
+
...(item ? { item } : {}),
|
|
220
|
+
...(display ? { display } : {}),
|
|
221
|
+
...(sprite ? { sprite } : {}),
|
|
222
|
+
...asEntryMeta(row, ids),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function asFluidEntry(
|
|
227
|
+
value: unknown,
|
|
228
|
+
fallbackId?: string,
|
|
229
|
+
ids?: string[],
|
|
230
|
+
): DumpFluidEntry | undefined {
|
|
231
|
+
const id = asId(value, fallbackId);
|
|
232
|
+
if (!id) return undefined;
|
|
233
|
+
const row = asRecord(value);
|
|
234
|
+
if (!row) return undefined;
|
|
235
|
+
const display = asDisplay(row.display);
|
|
236
|
+
const variants = asVariants(row.variants);
|
|
237
|
+
const sprite = asSpriteEntry(row.sprite);
|
|
238
|
+
return {
|
|
239
|
+
id,
|
|
240
|
+
...(display ? { display } : {}),
|
|
241
|
+
...(variants ? { variants } : {}),
|
|
242
|
+
...(sprite ? { sprite } : {}),
|
|
243
|
+
...asEntryMeta(row, ids),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function asEntityEntry(
|
|
248
|
+
value: unknown,
|
|
249
|
+
fallbackId?: string,
|
|
250
|
+
ids?: string[],
|
|
251
|
+
): DumpEntityEntry | undefined {
|
|
252
|
+
const id = asId(value, fallbackId);
|
|
253
|
+
if (!id) return undefined;
|
|
254
|
+
const row = asRecord(value);
|
|
255
|
+
if (!row) return undefined;
|
|
256
|
+
const display = asDisplay(row.display);
|
|
257
|
+
const texture = asString(row.texture);
|
|
258
|
+
const model = asString(row.model);
|
|
259
|
+
const variants = asVariants(row.variants);
|
|
260
|
+
return {
|
|
261
|
+
id,
|
|
262
|
+
...(display ? { display } : {}),
|
|
263
|
+
...(texture ? { texture } : {}),
|
|
264
|
+
...(model ? { model } : {}),
|
|
265
|
+
...(variants ? { variants } : {}),
|
|
266
|
+
...asEntryMeta(row, ids),
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function asEntry(
|
|
271
|
+
value: unknown,
|
|
272
|
+
kind: DumpRegistryKind,
|
|
273
|
+
fallbackId?: string,
|
|
274
|
+
ids?: string[],
|
|
275
|
+
): DumpRegistryEntry | undefined {
|
|
276
|
+
switch (kind) {
|
|
277
|
+
case 'items':
|
|
278
|
+
return asItemEntry(value, fallbackId, ids);
|
|
279
|
+
case 'blocks':
|
|
280
|
+
return asBlockEntry(value, fallbackId, ids);
|
|
281
|
+
case 'fluids':
|
|
282
|
+
return asFluidEntry(value, fallbackId, ids);
|
|
283
|
+
case 'entities':
|
|
284
|
+
return asEntityEntry(value, fallbackId, ids);
|
|
285
|
+
default: {
|
|
286
|
+
const _never: never = kind;
|
|
287
|
+
return _never;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { DumpDisplay } from '@terrafirmagreg-team/game/lib/dump/contract/text';
|
|
2
|
+
import type { TaggedReference } from '@terrafirmagreg-team/game/lib/dump/contract/ref';
|
|
3
|
+
import type { TabEntry } from '@terrafirmagreg-team/game/lib/dump/gen';
|
|
4
|
+
|
|
5
|
+
export type DumpCreativeTab = Omit<TabEntry, 'display' | 'icon' | 'items'> & {
|
|
6
|
+
display: DumpDisplay;
|
|
7
|
+
icon?: TaggedReference;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type DumpCreativeTabs = {
|
|
11
|
+
tabs: DumpCreativeTab[];
|
|
12
|
+
members: Record<string, string[]>;
|
|
13
|
+
};
|