@terrafirmagreg-team/game 0.1.0 → 1.2.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.
Files changed (86) hide show
  1. package/package.json +15 -14
  2. package/src/components/entry/atlas-sprite.tsx +22 -35
  3. package/src/components/entry/copy-id-menu.tsx +22 -29
  4. package/src/components/entry/entry.tsx +99 -20
  5. package/src/components/entry/group.tsx +27 -46
  6. package/src/components/entry/icon.tsx +21 -27
  7. package/src/components/entry/list.tsx +15 -18
  8. package/src/components/entry/mc-text.tsx +3 -89
  9. package/src/components/entry/stack-count.tsx +12 -8
  10. package/src/components/entry/tooltip.tsx +52 -44
  11. package/src/components/mdx/item-icon.tsx +23 -15
  12. package/src/components/preview-icon.tsx +2 -1
  13. package/src/hooks/use-dump-location.ts +4 -2
  14. package/src/hooks/use-entry-context.ts +3 -1
  15. package/src/hooks/use-icon-source.ts +22 -13
  16. package/src/hooks/use-lang-map.ts +4 -2
  17. package/src/lib/entry/display.ts +2 -1
  18. package/src/lib/entry/entry.ts +5 -3
  19. package/src/lib/entry/formula.ts +1 -0
  20. package/src/lib/entry/href.ts +20 -12
  21. package/src/lib/entry/icon-load.ts +67 -31
  22. package/src/lib/entry/icon.ts +3 -2
  23. package/src/lib/entry/tooltip.ts +12 -10
  24. package/src/lib/preview.ts +4 -2
  25. package/src/lib/search.ts +18 -19
  26. package/src/locales/en_us.json +3 -7
  27. package/src/locales/ru_ru.json +3 -7
  28. package/src/locales.ts +1 -0
  29. package/src/types/entry.ts +41 -2
  30. package/styles/fonts.css +18 -2
  31. package/src/components/entry/frame.tsx +0 -32
  32. package/src/components/entry/icon/tabs.tsx +0 -118
  33. package/src/lib/dump/assets/atlas/dto.ts +0 -49
  34. package/src/lib/dump/assets/atlas/lookup.ts +0 -206
  35. package/src/lib/dump/assets/atlas/sprites.ts +0 -130
  36. package/src/lib/dump/assets/lang/load.ts +0 -17
  37. package/src/lib/dump/assets/lang/names.ts +0 -244
  38. package/src/lib/dump/assets/urls.ts +0 -143
  39. package/src/lib/dump/client/atlases.ts +0 -83
  40. package/src/lib/dump/client/dump.ts +0 -188
  41. package/src/lib/dump/client/groups.ts +0 -31
  42. package/src/lib/dump/client/keybinds.ts +0 -11
  43. package/src/lib/dump/client/mods.ts +0 -16
  44. package/src/lib/dump/client/registry.ts +0 -125
  45. package/src/lib/dump/client/tabs.ts +0 -27
  46. package/src/lib/dump/client/tags.ts +0 -64
  47. package/src/lib/dump/contract/kind.ts +0 -61
  48. package/src/lib/dump/contract/read.ts +0 -77
  49. package/src/lib/dump/contract/ref.ts +0 -262
  50. package/src/lib/dump/contract/text.ts +0 -86
  51. package/src/lib/dump/data/registry/dto.ts +0 -56
  52. package/src/lib/dump/data/registry/parse.ts +0 -291
  53. package/src/lib/dump/data/tab/dto.ts +0 -13
  54. package/src/lib/dump/data/tab/parse.ts +0 -26
  55. package/src/lib/dump/data/tag/dto.ts +0 -7
  56. package/src/lib/dump/data/tag/parse.ts +0 -57
  57. package/src/lib/dump/gen/data/guides/book.ts +0 -56
  58. package/src/lib/dump/gen/data/guides/category.ts +0 -30
  59. package/src/lib/dump/gen/data/guides/entry.ts +0 -48
  60. package/src/lib/dump/gen/data/models/model.ts +0 -3
  61. package/src/lib/dump/gen/data/quests/chapter.ts +0 -126
  62. package/src/lib/dump/gen/data/quests/chapter_groups.ts +0 -11
  63. package/src/lib/dump/gen/data/quests/data.ts +0 -40
  64. package/src/lib/dump/gen/data/recipes/recipe.ts +0 -78
  65. package/src/lib/dump/gen/index.ts +0 -20
  66. package/src/lib/dump/gen/keybinds.ts +0 -3
  67. package/src/lib/dump/gen/lang.ts +0 -3
  68. package/src/lib/dump/gen/manifest.ts +0 -59
  69. package/src/lib/dump/gen/registry/blocks.ts +0 -60
  70. package/src/lib/dump/gen/registry/entities.ts +0 -49
  71. package/src/lib/dump/gen/registry/fluids.ts +0 -48
  72. package/src/lib/dump/gen/registry/groups.ts +0 -14
  73. package/src/lib/dump/gen/registry/items.ts +0 -51
  74. package/src/lib/dump/gen/registry/recipe_categories.ts +0 -64
  75. package/src/lib/dump/gen/registry/recipes.ts +0 -3
  76. package/src/lib/dump/gen/registry/tabs.ts +0 -46
  77. package/src/lib/dump/gen/registry/tags.ts +0 -11
  78. package/src/lib/dump/json-reader.ts +0 -231
  79. package/src/lib/dump/manifest/dto.ts +0 -11
  80. package/src/lib/dump/manifest/parse.ts +0 -167
  81. package/src/lib/dump/mods/dto.ts +0 -40
  82. package/src/lib/dump/mods/index.ts +0 -81
  83. package/src/lib/dump/mods/load.ts +0 -61
  84. package/src/lib/dump/paths.ts +0 -171
  85. package/src/lib/dump/reader.ts +0 -15
  86. package/src/lib/snbt.ts +0 -246
@@ -1,244 +0,0 @@
1
- import {
2
- nbtAt,
3
- parseStackId,
4
- stackIdBase,
5
- type DumpNbt,
6
- } from '@terrafirmagreg-team/game/lib/dump/contract/ref';
7
-
8
- function unwrapLangKey(raw: string): string {
9
- const value = raw.trim();
10
- return value.startsWith('{') && value.endsWith('}') && value.length > 2
11
- ? value.slice(1, -1).trim()
12
- : value;
13
- }
14
-
15
- export function langString(
16
- map: Record<string, string>,
17
- key: string | undefined,
18
- ): string | undefined {
19
- if (!key) return undefined;
20
- const bare = unwrapLangKey(key);
21
- return map[key] ?? map[bare];
22
- }
23
-
24
- function applyFormat(template: string, args: string[]): string {
25
- let sequential = 0;
26
- return template.replace(
27
- /%(?:(\d+)\$)?([A-Za-z%])/g,
28
- (_, n: string | undefined, conv: string) => {
29
- if (conv === '%') return '%';
30
- if (conv === 'n') return '\n';
31
- const index = n ? Number(n) - 1 : sequential++;
32
- return args[index] ?? '';
33
- },
34
- );
35
- }
36
-
37
- export function customStackName(
38
- map: Record<string, string>,
39
- compactId: string,
40
- ): string | undefined {
41
- const raw = nbtAt(parseStackId(compactId).nbt, 'display', 'Name');
42
- if (typeof raw !== 'string' || !raw) return undefined;
43
- try {
44
- const node: unknown = JSON.parse(raw);
45
- if (node && typeof node === 'object') {
46
- return dumpTextString(map, node as Parameters<typeof dumpTextString>[1]);
47
- }
48
- if (typeof node === 'string') return node;
49
- } catch {
50
-
51
- }
52
- return raw;
53
- }
54
-
55
- export function dumpTextString(
56
- map: Record<string, string>,
57
- text: {
58
- translate?: string;
59
- text?: string;
60
- keybind?: string;
61
- with?: unknown[];
62
- extra?: unknown[];
63
- },
64
- keybinds?: Record<string, string>,
65
- ): string {
66
- const parts: string[] = [];
67
- if (text.translate) {
68
- const template = langString(map, text.translate) ?? text.translate;
69
- const args = Array.isArray(text.with)
70
- ? text.with.map((item) =>
71
- item && typeof item === 'object'
72
- ? dumpTextString(map, item as typeof text, keybinds)
73
- : String(item ?? ''),
74
- )
75
- : [];
76
- parts.push(args.length ? applyFormat(template, args) : template);
77
- } else if (text.text !== undefined) {
78
- parts.push(text.text);
79
- } else if (text.keybind) {
80
- parts.push(
81
- keybinds?.[text.keybind] ?? langString(map, text.keybind) ?? text.keybind,
82
- );
83
- }
84
- if (Array.isArray(text.extra)) {
85
- for (const item of text.extra) {
86
- if (item && typeof item === 'object') {
87
- parts.push(dumpTextString(map, item as typeof text, keybinds));
88
- }
89
- }
90
- }
91
- return parts.join('');
92
- }
93
-
94
- export function dumpTextLines(
95
- map: Record<string, string>,
96
- lines: Array<{
97
- translate?: string;
98
- text?: string;
99
- keybind?: string;
100
- with?: unknown[];
101
- extra?: unknown[];
102
- }>,
103
- keybinds?: Record<string, string>,
104
- ): string[] {
105
-
106
- return lines.map((line) => dumpTextString(map, line, keybinds));
107
- }
108
-
109
- export function resolveName(
110
- map: Record<string, string>,
111
- id: string,
112
- langKey?: string,
113
- ): string {
114
- if (langKey) {
115
- return langString(map, langKey) ?? langKey;
116
- }
117
- return langString(map, id) ?? id;
118
- }
119
-
120
- export function stripMcFormat(value: string): string {
121
- return value.replace(/§x(?:§[0-9a-f]){6}|§[0-9a-fk-or]/gi, '');
122
- }
123
-
124
- const KIND_LANG_PREFIXES: Record<string, readonly string[]> = {
125
- item: ['item', 'block'],
126
- block: ['block', 'item'],
127
- fluid: ['fluid', 'fluid_type', 'block'],
128
- entity: ['entity'],
129
- itemTag: ['tag.item'],
130
- blockTag: ['tag.block', 'tag.item'],
131
- fluidTag: ['tag.fluid'],
132
- entityTag: ['tag.entity'],
133
- };
134
-
135
- const DEFAULT_LANG_PREFIXES = ['item', 'block', 'fluid', 'fluid_type', 'entity'];
136
- const TAG_LANG_PREFIXES = ['tag.item', 'tag.block', 'tag.fluid', 'tag.entity'];
137
-
138
- function stackLangKeys(
139
- kind: string | undefined,
140
- id: string,
141
- typeKeys = true,
142
- ): string[] {
143
- const raw = parseStackId(id).id;
144
- const hashed = raw.startsWith('#');
145
- const resource = hashed ? raw.slice(1) : raw;
146
- if (!resource.includes(':')) return [];
147
- const [ns, path] = resource.split(':', 2);
148
- const flat = path.replaceAll('/', '.');
149
- const prefixes = hashed
150
- ? ((kind ? KIND_LANG_PREFIXES[kind] : undefined) ?? TAG_LANG_PREFIXES)
151
- : (KIND_LANG_PREFIXES[kind ?? ''] ?? DEFAULT_LANG_PREFIXES);
152
- return [
153
- ...prefixes.map((prefix) => `${prefix}.${ns}.${flat}`),
154
- ...(typeKeys ? [`${ns}.type.${flat}.name`, `${ns}.type.${flat}`] : []),
155
- ];
156
- }
157
-
158
- function nbtLangKeys(nbt: DumpNbt | undefined): string[] {
159
- if (!nbt || typeof nbt !== 'object' || Array.isArray(nbt)) return [];
160
- const keys: string[] = [];
161
- for (const [field, value] of Object.entries(nbt)) {
162
- if (!field.endsWith('Id') || field.length <= 2) continue;
163
- if (typeof value !== 'string' || !value.includes(':')) continue;
164
- const [ns, path] = value.split(':', 2);
165
- const flat = path.replaceAll('/', '.');
166
- const stem = field
167
- .slice(0, -2)
168
- .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
169
- .toLowerCase();
170
- if (!stem) continue;
171
- keys.push(`${ns}.${stem}.${flat}`, `${ns}.${stem}.${flat}.name`);
172
- }
173
- return keys;
174
- }
175
-
176
- export function stackDisplay(
177
- langMap: Record<string, string> | undefined,
178
- compactId: string,
179
- kind?: string,
180
- langKey?: string,
181
- ): { name: string; localized: boolean } {
182
- const parsed = parseStackId(compactId);
183
- if (!langMap) return { name: langKey ?? parsed.id, localized: false };
184
- const custom = customStackName(langMap, compactId);
185
- if (custom) return { name: stripMcFormat(custom), localized: true };
186
- const canonical = stackLangKeys(kind, parsed.id, false);
187
- const candidates: { key: string | undefined; localized: boolean }[] = [
188
- { key: compactId, localized: true },
189
- { key: langKey, localized: true },
190
- ...nbtLangKeys(parsed.nbt).map((key) => ({ key, localized: true })),
191
- ...canonical.map((key) => ({ key, localized: true })),
192
- ...stackLangKeys(kind, parsed.id, true)
193
- .slice(canonical.length)
194
- .map((key) => ({ key, localized: false })),
195
- { key: parsed.id, localized: false },
196
- ];
197
- for (const { key, localized } of candidates) {
198
- if (!key) continue;
199
- const value = langString(langMap, key);
200
- if (value !== undefined) {
201
- return { name: stripMcFormat(value), localized };
202
- }
203
- }
204
- return {
205
- name: humanizeKey(langKey ?? stackIdBase(compactId)),
206
- localized: false,
207
- };
208
- }
209
-
210
- export function stackDisplayName(
211
- langMap: Record<string, string> | undefined,
212
- compactId: string,
213
- kind?: string,
214
- langKey?: string,
215
- ): string {
216
- return stackDisplay(langMap, compactId, kind, langKey).name;
217
- }
218
-
219
- export function humanizeKey(key: string): string {
220
- const value = unwrapLangKey(key);
221
- const segment = value.includes(':')
222
- ? (value.split(':', 2)[1] ?? value)
223
- : (value.split('.').pop() ?? value);
224
- const words = segment
225
- .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
226
- .split(/[_.\-/]+/)
227
- .filter(Boolean);
228
- if (!words.length) return key;
229
- return words
230
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
231
- .join(' ');
232
- }
233
-
234
- export function creativeTabName(
235
- map: Record<string, string>,
236
- nameKey?: string,
237
- ): string {
238
- return langString(map, nameKey) ?? humanizeKey(nameKey ?? '');
239
- }
240
-
241
- export function isTechnicalId(map: Record<string, string>, id: string): boolean {
242
- return resolveName(map, id) === id;
243
- }
244
-
@@ -1,143 +0,0 @@
1
- import type {
2
- AtlasPage,
3
- IconAtlas,
4
- } from '@terrafirmagreg-team/game/lib/dump/assets/atlas/dto';
5
- import {
6
- cleanDumpRel,
7
- dumpLocation,
8
- dumpRelPublicUrl,
9
- dumpStorageUrl,
10
- splitDumpId,
11
- type DumpLocation,
12
- } from '@terrafirmagreg-team/game/lib/dump/paths';
13
-
14
- export { dumpRelPublicUrl } from '@terrafirmagreg-team/game/lib/dump/paths';
15
-
16
- export function texturePublicUrl(
17
- dataset: string,
18
- textureId: string,
19
- ): string | undefined {
20
- const parts = splitDumpId(textureId.trim());
21
- if (!parts) return undefined;
22
- const path = parts.path.startsWith('textures/')
23
- ? parts.path
24
- : `textures/${parts.path}`;
25
- const rel = `${path.replace(/\.(png|webp)$/i, '')}.webp`;
26
- return dumpRelPublicUrl(dataset, `assets/${parts.ns}/${rel}`);
27
- }
28
-
29
- export function modelPublicUrl(
30
- dataset: string,
31
- namespace: string,
32
- rel: string | null | undefined,
33
- ): string | undefined {
34
- const cleaned = typeof rel === 'string' ? cleanDumpRel(rel) : null;
35
- return cleaned
36
- ? dumpRelPublicUrl(dataset, `assets/${namespace}/${cleaned}`)
37
- : undefined;
38
- }
39
-
40
- export function recipeImageUrl(
41
- dataset: string,
42
- recipe: { modId: string; image?: string },
43
- ): string | undefined {
44
- const rel = recipe.image ? cleanDumpRel(recipe.image) : undefined;
45
- if (!rel) return undefined;
46
- if (rel.startsWith('assets/')) return dumpRelPublicUrl(dataset, rel);
47
- if (!recipe.modId) return undefined;
48
- return dumpRelPublicUrl(dataset, `assets/${recipe.modId}/${rel}`);
49
- }
50
-
51
- export function atlasPagePublicUrl(
52
- dataset: string,
53
- atlas: IconAtlas,
54
- page: AtlasPage,
55
- ): string | undefined {
56
- const src = cleanDumpRel(page.image);
57
- if (!src || !atlas.modId) return undefined;
58
- return dumpRelPublicUrl(dataset, `assets/${atlas.modId}/${src}`);
59
- }
60
-
61
- function pathDir(path: string): string {
62
- const slash = path.lastIndexOf('/');
63
- return slash >= 0 ? path.slice(0, slash + 1) : '';
64
- }
65
-
66
- function relativePath(fromDir: string, toPath: string): string {
67
- const a = fromDir.split('/').filter(Boolean);
68
- const b = toPath.split('/').filter(Boolean);
69
- let i = 0;
70
- while (i < a.length && i < b.length && a[i] === b[i]) i += 1;
71
- return '../'.repeat(a.length - i) + b.slice(i).join('/');
72
- }
73
-
74
- function resolveAssetRel(baseDir: string, relUri: string): string | null {
75
- const out: string[] = [];
76
- for (const seg of `${baseDir}${relUri}`.split('/')) {
77
- if (seg === '..') {
78
- if (!out.pop()) return null;
79
- } else if (seg !== '.' && seg !== '') {
80
- out.push(seg);
81
- }
82
- }
83
- return out.length ? out.join('/') : null;
84
- }
85
-
86
- const reverseFileIndexes = new WeakMap<
87
- Record<string, string>,
88
- Map<string, string>
89
- >();
90
- function reverseFileIndex(files: Record<string, string>): Map<string, string> {
91
- let map = reverseFileIndexes.get(files);
92
- if (!map) {
93
- map = new Map(Object.entries(files).map(([rel, key]) => [key, rel]));
94
- reverseFileIndexes.set(files, map);
95
- }
96
- return map;
97
- }
98
-
99
- function dumpRelForStorageRel(
100
- loc: DumpLocation,
101
- storageRel: string,
102
- ): string | undefined {
103
- if (loc.files) return reverseFileIndex(loc.files).get(storageRel);
104
- return storageRel.startsWith(`${loc.root}/`)
105
- ? storageRel.slice(loc.root.length + 1)
106
- : undefined;
107
- }
108
-
109
- export function createDumpAssetResolver(
110
- dataset: string,
111
- ): (resourceUrl: string, modelUrl: string) => string | undefined {
112
-
113
- let prefix: string | null = null;
114
- return (resourceUrl, modelUrl) => {
115
- const loc = dumpLocation(dataset);
116
- if (!loc) return undefined;
117
- const base =
118
- typeof window === 'undefined' ? 'http://x.invalid' : location.origin;
119
- prefix ??= new URL(dumpStorageUrl(''), base).href;
120
- let resourcePath: string;
121
- let modelAbs: URL;
122
- try {
123
- modelAbs = new URL(modelUrl, base);
124
- const resource = new URL(resourceUrl, modelAbs);
125
- if (resource.origin !== modelAbs.origin) return undefined;
126
- resourcePath = resource.pathname;
127
- } catch {
128
- return undefined;
129
- }
130
- const modelHref = modelAbs.href;
131
- if (!modelHref.startsWith(prefix)) return undefined;
132
- const modelRel = dumpRelForStorageRel(
133
- loc,
134
- modelHref.slice(prefix.length),
135
- );
136
- if (!modelRel) return undefined;
137
- const rel = relativePath(pathDir(modelAbs.pathname), resourcePath);
138
- if (!rel || !rel.includes('/')) return undefined;
139
- const dumpRel = resolveAssetRel(pathDir(modelRel), rel);
140
- if (!dumpRel) return undefined;
141
- return dumpRelPublicUrl(dataset, dumpRel);
142
- };
143
- }
@@ -1,83 +0,0 @@
1
- import type { AtlasBundle, IconAtlas } from '@terrafirmagreg-team/game/lib/dump/assets/atlas/dto';
2
- import {
3
- iconAtlasFromEntries,
4
- mergeAtlases,
5
- } from '@terrafirmagreg-team/game/lib/dump/assets/atlas/sprites';
6
- import {
7
- clientDumpJson,
8
- loadClientManifest,
9
- } from '@terrafirmagreg-team/game/lib/dump/client/dump';
10
- import { asRecord } from '@terrafirmagreg-team/game/lib/dump/contract/read';
11
- import { createAsyncCache } from '@terrafirmagreg-team/ui/lib/utils/async-cache';
12
-
13
- async function modEntries(
14
- dataset: string,
15
- modId: string,
16
- kind: string,
17
- ): Promise<Record<string, unknown> | undefined> {
18
- const entries = asRecord(
19
- await clientDumpJson(dataset, `registry/${kind}.json`, 'high'),
20
- );
21
- if (!entries) return undefined;
22
- const prefix = `${modId}:`;
23
- const out: Record<string, unknown> = {};
24
- for (const [key, value] of Object.entries(entries)) {
25
- if (key.startsWith(prefix)) out[key] = value;
26
- }
27
- return out;
28
- }
29
-
30
- const loadClientModAtlas = createAsyncCache(
31
- async (
32
- dataset: string,
33
- modId: string,
34
- kind: 'items' | 'blocks',
35
- ): Promise<IconAtlas | null> => {
36
- const manifest = await loadClientManifest(dataset);
37
- const listed = manifest?.namespaces[modId];
38
- if (!listed) return null;
39
- const has = (file: string) => listed.registry.includes(file);
40
- if (kind === 'blocks') {
41
- if (!has('blocks')) return null;
42
- return iconAtlasFromEntries(
43
- await modEntries(dataset, modId, 'blocks'),
44
- dataset,
45
- modId,
46
- 'blocks',
47
- 'block',
48
- );
49
- }
50
- return mergeAtlases(
51
- [
52
- has('items')
53
- ? iconAtlasFromEntries(
54
- await modEntries(dataset, modId, 'items'),
55
- dataset,
56
- modId,
57
- 'items',
58
- 'item',
59
- )
60
- : null,
61
- has('fluids')
62
- ? iconAtlasFromEntries(
63
- await modEntries(dataset, modId, 'fluids'),
64
- dataset,
65
- modId,
66
- 'fluids',
67
- 'item',
68
- )
69
- : null,
70
- ],
71
- dataset,
72
- modId,
73
- 'item',
74
- );
75
- },
76
- );
77
-
78
- export const loadClientModAtlases = createAsyncCache(
79
- async (dataset: string, modId: string): Promise<AtlasBundle> => ({
80
- items: await loadClientModAtlas(dataset, modId, 'items'),
81
- blocks: await loadClientModAtlas(dataset, modId, 'blocks'),
82
- }),
83
- );
@@ -1,188 +0,0 @@
1
- import { parseDumpManifest } from '@terrafirmagreg-team/game/lib/dump/manifest/parse';
2
- import { dumpReader } from '@terrafirmagreg-team/game/lib/dump/reader';
3
- import { dumpRelPublicUrl, splitDumpId } from '@terrafirmagreg-team/game/lib/dump/paths';
4
- import { stackIdBase } from '@terrafirmagreg-team/game/lib/dump/contract/ref';
5
- import { asRecord } from '@terrafirmagreg-team/game/lib/dump/contract/read';
6
- import {
7
- modelPublicUrl,
8
- texturePublicUrl,
9
- } from '@terrafirmagreg-team/game/lib/dump/assets/urls';
10
- import { createAsyncCache } from '@terrafirmagreg-team/ui/lib/utils/async-cache';
11
- import type { DumpManifest, NamespaceDisplay } from '@terrafirmagreg-team/game/lib/dump/manifest/dto';
12
- import type { ClientDumpMod, GuideBookRef } from '@terrafirmagreg-team/game/lib/dump/mods/dto';
13
-
14
- export type { ClientDumpMod, GuideBookRef };
15
-
16
- type ClientDump = {
17
- manifest: DumpManifest;
18
- mods: ClientDumpMod[];
19
- };
20
-
21
- function clientBook(
22
- namespace: string,
23
- name: string,
24
- locales: string[],
25
- ): GuideBookRef | null {
26
- if (!name || name.includes('..') || name.includes('/')) return null;
27
- const dir = `data/${namespace}/guides/${name}`;
28
- return {
29
- namespace,
30
- modId: namespace,
31
- bookPath: name,
32
- bookId: `${namespace}:${name}`,
33
- dir,
34
- locales,
35
- };
36
- }
37
-
38
- export const loadClientManifest = createAsyncCache(
39
- async (dataset: string): Promise<DumpManifest | null> => {
40
- const reader = await dumpReader(dataset);
41
- return parseDumpManifest(await reader.getJson('manifest.json')) ?? null;
42
- },
43
- );
44
-
45
- export type DatasetVersions = { latest?: string; versions: string[] };
46
-
47
- export const loadClientVersions = createAsyncCache(
48
- async (dataset: string): Promise<DatasetVersions | null> => {
49
- const reader = await dumpReader(dataset);
50
- const raw = await reader.getJson('versions.json');
51
- if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null;
52
- const row = raw as Record<string, unknown>;
53
- const versions = Array.isArray(row.versions)
54
- ? row.versions.filter((v): v is string => typeof v === 'string')
55
- : [];
56
- if (!versions.length) return null;
57
- return {
58
- latest: typeof row.latest === 'string' ? row.latest : undefined,
59
- versions,
60
- };
61
- },
62
- );
63
-
64
- export const loadClientDump = createAsyncCache(
65
- async (dataset: string): Promise<ClientDump | null> => {
66
- const manifest = await loadClientManifest(dataset);
67
- if (!manifest) return null;
68
- const mods = Object.entries(manifest.namespaces)
69
- .sort(([a], [b]) => a.localeCompare(b))
70
- .map(([namespace, listed]) => ({
71
- namespace,
72
- books: (listed?.guides ?? []).flatMap((name) => {
73
- const book = clientBook(namespace, name, manifest.languages);
74
- return book ? [book] : [];
75
- }),
76
- }));
77
- return { manifest, mods };
78
- },
79
- );
80
-
81
- export function clientDumpJson(
82
- dataset: string,
83
- rel: string | null | undefined,
84
- priority?: 'high',
85
- ): Promise<unknown | undefined> {
86
- if (!rel) return Promise.resolve(undefined);
87
- return dumpReader(dataset).then((reader) => reader.getJson(rel, priority));
88
- }
89
-
90
- export async function clientNamespaceEntries(
91
- dataset: string,
92
- rel: (namespace: string) => string,
93
- include: (ns: NamespaceDisplay) => boolean,
94
- priority?: 'high',
95
- merged = true,
96
- ): Promise<{ namespace: string; entries: Record<string, unknown> }[]> {
97
- const mergedRows = merged
98
- ? asRecord(await clientDumpJson(dataset, rel('meta'), priority))
99
- : undefined;
100
- if (mergedRows) return [{ namespace: 'meta', entries: mergedRows }];
101
- const manifest = await loadClientManifest(dataset);
102
- if (!manifest) return [];
103
- const namespaces = Object.keys(manifest.namespaces)
104
- .flatMap((ns) => {
105
- const listed = manifest.namespaces[ns];
106
- return listed && include(listed) ? [ns] : [];
107
- })
108
- .sort((a, b) => a.localeCompare(b));
109
- const rows = await Promise.all(
110
- namespaces.map(async (namespace) => ({
111
- namespace,
112
- entries: asRecord(await clientDumpJson(dataset, rel(namespace), priority)),
113
- })),
114
- );
115
- return rows.flatMap((row) =>
116
- row.entries ? [{ namespace: row.namespace, entries: row.entries }] : [],
117
- );
118
- }
119
-
120
- export function clientDumpLang(
121
- dataset: string,
122
- lang: string,
123
- ): Promise<Record<string, string>> {
124
- return dumpReader(dataset).then((reader) => reader.getLang(lang));
125
- }
126
-
127
- export function clientDumpLangRaw(
128
- dataset: string,
129
- lang: string,
130
- ): Promise<Record<string, string>> {
131
- return dumpReader(dataset).then((reader) => reader.getLangRaw(lang));
132
- }
133
-
134
- export async function clientRegistryDataFileRel(
135
- dataset: string,
136
- ns: string,
137
- file: string,
138
- ): Promise<string | undefined> {
139
- const manifest = await loadClientManifest(dataset);
140
- const listed = manifest?.namespaces[ns];
141
- if (!listed || !listed.registry.includes(file)) return undefined;
142
- return `registry/${file}.json`;
143
- }
144
-
145
- function stackNs(id: string): string | undefined {
146
- const ns = stackIdBase(id).split(':', 1)[0];
147
- return ns || undefined;
148
- }
149
-
150
- export function clientTagFileRel(
151
- dataset: string,
152
- tagId: string,
153
- ): Promise<string | undefined> {
154
- const ns = stackNs(tagId.startsWith('#') ? tagId.slice(1) : tagId);
155
- return ns
156
- ? clientRegistryDataFileRel(dataset, ns, 'tags')
157
- : Promise.resolve(undefined);
158
- }
159
-
160
- export function dumpTextureUrl(
161
- dataset: string,
162
- textureId: string,
163
- ): string | undefined {
164
- return texturePublicUrl(dataset, textureId);
165
- }
166
-
167
- export async function dumpModelUrl(
168
- dataset: string,
169
- modelId: string,
170
- ): Promise<string | undefined> {
171
- const parts = splitDumpId(modelId.trim());
172
- if (!parts) return undefined;
173
- const index = await clientDumpJson(
174
- dataset,
175
- `data/${parts.ns}/models/multiblock.json`,
176
- );
177
- const rel =
178
- index && typeof index === 'object' && !Array.isArray(index)
179
- ? (index as Record<string, unknown>)[modelId]
180
- : undefined;
181
- return modelPublicUrl(
182
- dataset,
183
- parts.ns,
184
- typeof rel === 'string' ? rel : undefined,
185
- );
186
- }
187
-
188
- export { dumpRelPublicUrl };
@@ -1,31 +0,0 @@
1
- import { asRecord, asStringList } from '@terrafirmagreg-team/game/lib/dump/contract/read';
2
- import { asDumpText, type DumpText } from '@terrafirmagreg-team/game/lib/dump/contract/text';
3
- import { clientNamespaceEntries } from '@terrafirmagreg-team/game/lib/dump/client/dump';
4
- import { createAsyncCache } from '@terrafirmagreg-team/ui/lib/utils/async-cache';
5
-
6
- type RegistryGroupRow = {
7
- id: string;
8
- name?: DumpText;
9
- members: string[];
10
- };
11
-
12
- export const loadClientRegistryGroups = createAsyncCache(
13
- async (dataset: string): Promise<RegistryGroupRow[]> => {
14
- const out: RegistryGroupRow[] = [];
15
- const files = await clientNamespaceEntries(
16
- dataset,
17
- () => 'registry/groups.json',
18
- (ns) => ns.registry.includes('groups'),
19
- );
20
- for (const { entries } of files) {
21
- for (const [id, raw] of Object.entries(entries)) {
22
- const row = asRecord(raw);
23
- const members = asStringList(row?.members);
24
- if (!id || members.length <= 1) continue;
25
- const name = asDumpText(row?.name);
26
- out.push({ id, ...(name ? { name } : {}), members });
27
- }
28
- }
29
- return out;
30
- },
31
- );
@@ -1,11 +0,0 @@
1
- import { asStringMap } from '@terrafirmagreg-team/game/lib/dump/contract/read';
2
- import { clientDumpJson } from '@terrafirmagreg-team/game/lib/dump/client/dump';
3
- import { createAsyncCache } from '@terrafirmagreg-team/ui/lib/utils/async-cache';
4
-
5
- export const loadClientKeybinds = createAsyncCache(
6
- async (dataset: string): Promise<Record<string, string>> => {
7
- return (
8
- asStringMap(await clientDumpJson(dataset, 'keybinds.json', 'high')) ?? {}
9
- );
10
- },
11
- );