@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,188 @@
|
|
|
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 };
|
|
@@ -0,0 +1,31 @@
|
|
|
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
|
+
);
|
|
@@ -0,0 +1,11 @@
|
|
|
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
|
+
);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { langString } from '@terrafirmagreg-team/game/lib/dump/assets/lang/names';
|
|
2
|
+
import { clientDumpLang, loadClientManifest } from '@terrafirmagreg-team/game/lib/dump/client/dump';
|
|
3
|
+
import { createAsyncCache } from '@terrafirmagreg-team/ui/lib/utils/async-cache';
|
|
4
|
+
|
|
5
|
+
export const loadClientModNames = createAsyncCache(
|
|
6
|
+
async (dataset: string, lang: string): Promise<Record<string, string>> => {
|
|
7
|
+
const manifest = await loadClientManifest(dataset);
|
|
8
|
+
const map = await clientDumpLang(dataset, lang);
|
|
9
|
+
const out: Record<string, string> = {};
|
|
10
|
+
for (const [ns, entry] of Object.entries(manifest?.namespaces ?? {})) {
|
|
11
|
+
const key = entry?.display?.name;
|
|
12
|
+
out[ns] = langString(map, key) ?? key ?? ns;
|
|
13
|
+
}
|
|
14
|
+
return out;
|
|
15
|
+
},
|
|
16
|
+
);
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { asIdTable, EDGE_TABLE_FIELD } from '@terrafirmagreg-team/game/lib/dump/contract/read';
|
|
2
|
+
import { asEntry, displayOf } from '@terrafirmagreg-team/game/lib/dump/data/registry/parse';
|
|
3
|
+
import { parseStackId } from '@terrafirmagreg-team/game/lib/dump/contract/ref';
|
|
4
|
+
import type { DumpDisplay, DumpText } from '@terrafirmagreg-team/game/lib/dump/contract/text';
|
|
5
|
+
import type {
|
|
6
|
+
DumpBlockEntry,
|
|
7
|
+
DumpEntityEntry,
|
|
8
|
+
DumpFluidEntry,
|
|
9
|
+
DumpItemEntry,
|
|
10
|
+
DumpRegistryEntry,
|
|
11
|
+
DumpRegistryKind,
|
|
12
|
+
} from '@terrafirmagreg-team/game/lib/dump/data/registry/dto';
|
|
13
|
+
import { clientNamespaceEntries } from '@terrafirmagreg-team/game/lib/dump/client/dump';
|
|
14
|
+
import { createAsyncCache } from '@terrafirmagreg-team/ui/lib/utils/async-cache';
|
|
15
|
+
|
|
16
|
+
type RegistryByKind = {
|
|
17
|
+
items: DumpItemEntry;
|
|
18
|
+
blocks: DumpBlockEntry;
|
|
19
|
+
fluids: DumpFluidEntry;
|
|
20
|
+
entities: DumpEntityEntry;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const KIND_FILE: Record<DumpRegistryKind, string> = {
|
|
24
|
+
items: 'items',
|
|
25
|
+
blocks: 'blocks',
|
|
26
|
+
fluids: 'fluids',
|
|
27
|
+
entities: 'entities',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const clientRegistry = createAsyncCache(
|
|
31
|
+
async (
|
|
32
|
+
dataset: string,
|
|
33
|
+
kind: DumpRegistryKind,
|
|
34
|
+
): Promise<Map<string, DumpRegistryEntry>> => {
|
|
35
|
+
const out = new Map<string, DumpRegistryEntry>();
|
|
36
|
+
const file = KIND_FILE[kind];
|
|
37
|
+
const files = await clientNamespaceEntries(
|
|
38
|
+
dataset,
|
|
39
|
+
() => `registry/${file}.json`,
|
|
40
|
+
(ns) => ns.registry.includes(file),
|
|
41
|
+
);
|
|
42
|
+
for (const { entries } of files) {
|
|
43
|
+
const ids = asIdTable(entries);
|
|
44
|
+
for (const [id, raw] of Object.entries(entries)) {
|
|
45
|
+
if (id === EDGE_TABLE_FIELD) continue;
|
|
46
|
+
const entry = asEntry(raw, kind, id, ids);
|
|
47
|
+
if (!entry) continue;
|
|
48
|
+
if (!out.has(id)) out.set(id, entry);
|
|
49
|
+
if (entry.id !== id && !out.has(entry.id)) {
|
|
50
|
+
out.set(entry.id, entry);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
},
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
export function loadClientRegistry<K extends DumpRegistryKind>(
|
|
59
|
+
dataset: string,
|
|
60
|
+
kind: K,
|
|
61
|
+
): Promise<Map<string, RegistryByKind[K]>> {
|
|
62
|
+
return clientRegistry(dataset, kind) as Promise<Map<string, RegistryByKind[K]>>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function resolveClientStackDisplay(
|
|
66
|
+
compactId: string,
|
|
67
|
+
items: Map<string, DumpItemEntry>,
|
|
68
|
+
fluids: Map<string, DumpFluidEntry>,
|
|
69
|
+
): DumpDisplay | undefined {
|
|
70
|
+
const parsed = parseStackId(compactId);
|
|
71
|
+
const item = items.get(parsed.id);
|
|
72
|
+
if (item) return displayOf(item, parsed.nbt);
|
|
73
|
+
const fluid = fluids.get(parsed.id);
|
|
74
|
+
if (fluid) return displayOf(fluid, parsed.nbt);
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const SINGULAR_KIND: Record<string, DumpRegistryKind> = {
|
|
79
|
+
item: 'items',
|
|
80
|
+
block: 'blocks',
|
|
81
|
+
fluid: 'fluids',
|
|
82
|
+
entity: 'entities',
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export function clientRegistryKindFor(kind: string): DumpRegistryKind {
|
|
86
|
+
return SINGULAR_KIND[kind] ?? (kind as DumpRegistryKind);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function loadClientRegistryOrder(
|
|
90
|
+
dataset: string,
|
|
91
|
+
kind: string,
|
|
92
|
+
): Promise<string[]> {
|
|
93
|
+
const entries = await loadClientRegistry(dataset, clientRegistryKindFor(kind));
|
|
94
|
+
return [...entries.values()]
|
|
95
|
+
.flatMap((entry) => (entry.order != null ? [entry] : []))
|
|
96
|
+
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0) || a.id.localeCompare(b.id))
|
|
97
|
+
.map((entry) => entry.id);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function loadClientRegistryTooltips(
|
|
101
|
+
dataset: string,
|
|
102
|
+
kind: string,
|
|
103
|
+
): Promise<Record<string, DumpText[]> | undefined> {
|
|
104
|
+
const entries = await loadClientRegistry(dataset, clientRegistryKindFor(kind));
|
|
105
|
+
const out: Record<string, DumpText[]> = {};
|
|
106
|
+
for (const entry of entries.values()) {
|
|
107
|
+
const tooltip = entry.display?.tooltip;
|
|
108
|
+
if (tooltip?.length) out[entry.id] = tooltip;
|
|
109
|
+
}
|
|
110
|
+
return Object.keys(out).length ? out : undefined;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function loadClientRegistryTooltipsMod(
|
|
114
|
+
dataset: string,
|
|
115
|
+
kind: string,
|
|
116
|
+
mod: string,
|
|
117
|
+
): Promise<Record<string, DumpText[]> | undefined> {
|
|
118
|
+
const entries = await loadClientRegistry(dataset, clientRegistryKindFor(kind));
|
|
119
|
+
const out: Record<string, DumpText[]> = {};
|
|
120
|
+
for (const entry of entries.values()) {
|
|
121
|
+
const lines = entry.display?.tooltipMods?.[mod];
|
|
122
|
+
if (lines?.length) out[entry.id] = lines;
|
|
123
|
+
}
|
|
124
|
+
return Object.keys(out).length ? out : undefined;
|
|
125
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { asTab, asTabItems } from '@terrafirmagreg-team/game/lib/dump/data/tab/parse';
|
|
2
|
+
import type { DumpCreativeTab, DumpCreativeTabs } from '@terrafirmagreg-team/game/lib/dump/data/tab/dto';
|
|
3
|
+
import { clientNamespaceEntries } from '@terrafirmagreg-team/game/lib/dump/client/dump';
|
|
4
|
+
import { createAsyncCache } from '@terrafirmagreg-team/ui/lib/utils/async-cache';
|
|
5
|
+
|
|
6
|
+
export const loadClientTabs = createAsyncCache(
|
|
7
|
+
async (dataset: string): Promise<DumpCreativeTabs> => {
|
|
8
|
+
const tabs: DumpCreativeTab[] = [];
|
|
9
|
+
const members: Record<string, string[]> = {};
|
|
10
|
+
const files = await clientNamespaceEntries(
|
|
11
|
+
dataset,
|
|
12
|
+
() => 'registry/tabs.json',
|
|
13
|
+
(ns) => ns.registry.includes('tabs'),
|
|
14
|
+
);
|
|
15
|
+
for (const { entries } of files) {
|
|
16
|
+
for (const raw of Object.values(entries)) {
|
|
17
|
+
const tab = asTab(raw);
|
|
18
|
+
if (!tab) continue;
|
|
19
|
+
tabs.push(tab);
|
|
20
|
+
const items = asTabItems(raw);
|
|
21
|
+
if (items.length) members[tab.id] = items;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
tabs.sort((a, b) => a.order - b.order || a.id.localeCompare(b.id));
|
|
25
|
+
return { tabs, members };
|
|
26
|
+
},
|
|
27
|
+
);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { asIdTable, EDGE_TABLE_FIELD } from '@terrafirmagreg-team/game/lib/dump/contract/read';
|
|
2
|
+
import { asTag } from '@terrafirmagreg-team/game/lib/dump/data/tag/parse';
|
|
3
|
+
import type { DumpTag } from '@terrafirmagreg-team/game/lib/dump/data/tag/dto';
|
|
4
|
+
import { clientNamespaceEntries } from '@terrafirmagreg-team/game/lib/dump/client/dump';
|
|
5
|
+
import { tagHashId, tagIdForms } from '@terrafirmagreg-team/game/lib/dump/paths';
|
|
6
|
+
import { createAsyncCache } from '@terrafirmagreg-team/ui/lib/utils/async-cache';
|
|
7
|
+
|
|
8
|
+
function indexKeys(tag: DumpTag, manifestKey: string): string[] {
|
|
9
|
+
return [...new Set([manifestKey, ...tagIdForms(tag.id)])];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const loadClientTags = createAsyncCache(async (dataset: string) => {
|
|
13
|
+
const map = new Map<string, DumpTag>();
|
|
14
|
+
const files = await clientNamespaceEntries(
|
|
15
|
+
dataset,
|
|
16
|
+
() => 'registry/tags.json',
|
|
17
|
+
(ns) => ns.registry.includes('tags'),
|
|
18
|
+
);
|
|
19
|
+
for (const { entries } of files) {
|
|
20
|
+
const ids = asIdTable(entries);
|
|
21
|
+
for (const [manifestKey, raw] of Object.entries(entries)) {
|
|
22
|
+
if (manifestKey === EDGE_TABLE_FIELD) continue;
|
|
23
|
+
const tag = asTag(raw, manifestKey, ids);
|
|
24
|
+
if (!tag) continue;
|
|
25
|
+
for (const alias of indexKeys(tag, manifestKey)) {
|
|
26
|
+
if (!map.has(alias)) map.set(alias, tag);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return map;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export async function resolveClientTag(
|
|
34
|
+
dataset: string,
|
|
35
|
+
id: string,
|
|
36
|
+
): Promise<DumpTag | undefined> {
|
|
37
|
+
const tags = await loadClientTags(dataset);
|
|
38
|
+
for (const form of tagIdForms(id)) {
|
|
39
|
+
const hit = tags.get(form);
|
|
40
|
+
if (hit) return hit;
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const MEMBER_REGISTRY: Record<string, string> = {
|
|
46
|
+
item: 'items',
|
|
47
|
+
block: 'blocks',
|
|
48
|
+
fluid: 'fluids',
|
|
49
|
+
entity: 'entities',
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export async function loadClientTagMembers(
|
|
53
|
+
dataset: string,
|
|
54
|
+
kind: string,
|
|
55
|
+
): Promise<Record<string, string[]> | undefined> {
|
|
56
|
+
const registry = MEMBER_REGISTRY[kind] ?? kind;
|
|
57
|
+
const out: Record<string, string[]> = {};
|
|
58
|
+
for (const tag of (await loadClientTags(dataset)).values()) {
|
|
59
|
+
if (tag.registry !== registry || !tag.members.length) continue;
|
|
60
|
+
out[tagHashId(tag.id)] ??= tag.members;
|
|
61
|
+
}
|
|
62
|
+
return Object.keys(out).length ? out : undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import {
|
|
2
|
+
asTaggedReference,
|
|
3
|
+
stackIdFromRef,
|
|
4
|
+
type ReferenceKind,
|
|
5
|
+
type TaggedReference,
|
|
6
|
+
} from '@terrafirmagreg-team/game/lib/dump/contract/ref';
|
|
7
|
+
import { asRecord, asString } from '@terrafirmagreg-team/game/lib/dump/contract/read';
|
|
8
|
+
|
|
9
|
+
const LINK_KINDS = [
|
|
10
|
+
'item',
|
|
11
|
+
'block',
|
|
12
|
+
'fluid',
|
|
13
|
+
'entity',
|
|
14
|
+
'tag',
|
|
15
|
+
'texture',
|
|
16
|
+
'recipe',
|
|
17
|
+
'quest',
|
|
18
|
+
'guide',
|
|
19
|
+
] as const;
|
|
20
|
+
|
|
21
|
+
export type ResourceKind = (typeof LINK_KINDS)[number];
|
|
22
|
+
|
|
23
|
+
export function asResourceKind(value: unknown): ResourceKind | undefined {
|
|
24
|
+
return typeof value === 'string' &&
|
|
25
|
+
(LINK_KINDS as readonly string[]).includes(value)
|
|
26
|
+
? (value as ResourceKind)
|
|
27
|
+
: undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function referenceKindFromResource(
|
|
31
|
+
kind: ResourceKind | undefined,
|
|
32
|
+
): ReferenceKind | undefined {
|
|
33
|
+
switch (kind) {
|
|
34
|
+
case 'tag':
|
|
35
|
+
return 'itemTag';
|
|
36
|
+
case 'item':
|
|
37
|
+
case 'block':
|
|
38
|
+
case 'fluid':
|
|
39
|
+
case 'entity':
|
|
40
|
+
case 'texture':
|
|
41
|
+
case 'recipe':
|
|
42
|
+
case 'quest':
|
|
43
|
+
case 'guide':
|
|
44
|
+
return kind;
|
|
45
|
+
default:
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function referenceFrom(value: unknown): TaggedReference | undefined {
|
|
51
|
+
const tagged = asTaggedReference(value);
|
|
52
|
+
if (tagged) return { kind: tagged.kind, id: stackIdFromRef(tagged) };
|
|
53
|
+
const row = asRecord(value);
|
|
54
|
+
const tagId = asString(row?.tag);
|
|
55
|
+
if (tagId && row?.id === undefined && row?.kind === undefined) {
|
|
56
|
+
return { kind: 'itemTag', id: `#${tagId}` };
|
|
57
|
+
}
|
|
58
|
+
const kind = referenceKindFromResource(asResourceKind(row?.kind));
|
|
59
|
+
const id = asString(row?.id);
|
|
60
|
+
return kind && id ? { kind, id } : undefined;
|
|
61
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
export function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
4
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined;
|
|
5
|
+
return value as Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function asString(value: unknown): string | undefined {
|
|
9
|
+
return typeof value === 'string' && value ? value : undefined;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function asNumber(value: unknown): number | undefined {
|
|
13
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function asBool(value: unknown): boolean | undefined {
|
|
17
|
+
if (typeof value === 'boolean') return value;
|
|
18
|
+
if (value === 1) return true;
|
|
19
|
+
if (value === 0) return false;
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function asStringList(value: unknown): string[] {
|
|
24
|
+
if (!Array.isArray(value)) return [];
|
|
25
|
+
return value.filter((item): item is string => typeof item === 'string' && Boolean(item));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const EDGE_TABLE_FIELD = '$ids';
|
|
29
|
+
|
|
30
|
+
export function asIdTable(file: unknown): string[] | undefined {
|
|
31
|
+
const ids = asStringList(asRecord(file)?.[EDGE_TABLE_FIELD]);
|
|
32
|
+
return ids.length ? ids : undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function asEdgeList(value: unknown, ids?: string[]): string[] {
|
|
36
|
+
if (!Array.isArray(value)) return [];
|
|
37
|
+
const out: string[] = [];
|
|
38
|
+
for (const item of value) {
|
|
39
|
+
if (typeof item === 'string') {
|
|
40
|
+
if (item) out.push(item);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (typeof item === 'number' && Number.isInteger(item)) {
|
|
44
|
+
const ref = ids?.[item];
|
|
45
|
+
if (ref) out.push(ref);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function resolveEdgeRows(
|
|
52
|
+
map: Record<string, unknown>,
|
|
53
|
+
): Record<string, Record<string, unknown>> {
|
|
54
|
+
const ids = asIdTable(map);
|
|
55
|
+
const out: Record<string, Record<string, unknown>> = {};
|
|
56
|
+
for (const [key, value] of Object.entries(map)) {
|
|
57
|
+
if (key === EDGE_TABLE_FIELD) continue;
|
|
58
|
+
const row = asRecord(value);
|
|
59
|
+
if (!row) continue;
|
|
60
|
+
if (ids) {
|
|
61
|
+
if (Array.isArray(row.usedBy)) row.usedBy = asEdgeList(row.usedBy, ids);
|
|
62
|
+
if (Array.isArray(row.producedBy)) row.producedBy = asEdgeList(row.producedBy, ids);
|
|
63
|
+
}
|
|
64
|
+
out[key] = row;
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function asStringMap(value: unknown): Record<string, string> | undefined {
|
|
70
|
+
const row = asRecord(value);
|
|
71
|
+
if (!row) return undefined;
|
|
72
|
+
const out: Record<string, string> = {};
|
|
73
|
+
for (const [key, item] of Object.entries(row)) {
|
|
74
|
+
if (typeof item === 'string') out[key] = item;
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|