@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,48 @@
|
|
|
1
|
+
export interface RegistryFluids {
|
|
2
|
+
[k: string]: FluidEntry | undefined;
|
|
3
|
+
}
|
|
4
|
+
export interface FluidEntry {
|
|
5
|
+
display: Display;
|
|
6
|
+
id: string;
|
|
7
|
+
order?: number | null;
|
|
8
|
+
producedBy?: string[];
|
|
9
|
+
sprite?: null | SpriteEntry;
|
|
10
|
+
tags?: string[];
|
|
11
|
+
usedBy?: string[];
|
|
12
|
+
variants: VariantEntry[];
|
|
13
|
+
}
|
|
14
|
+
export interface Display {
|
|
15
|
+
name: string;
|
|
16
|
+
tooltip: DumpText[];
|
|
17
|
+
tooltipMods: {
|
|
18
|
+
[k: string]: DumpText[] | undefined;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export interface DumpText {
|
|
22
|
+
extra?: DumpText[];
|
|
23
|
+
keybind?: string;
|
|
24
|
+
text?: string;
|
|
25
|
+
translate?: string;
|
|
26
|
+
with?: DumpText[];
|
|
27
|
+
}
|
|
28
|
+
export interface SpriteEntry {
|
|
29
|
+
h: number;
|
|
30
|
+
image: string;
|
|
31
|
+
ph: number;
|
|
32
|
+
pw: number;
|
|
33
|
+
tint?: string | null;
|
|
34
|
+
tintColor?: string | null;
|
|
35
|
+
w: number;
|
|
36
|
+
x: number;
|
|
37
|
+
y: number;
|
|
38
|
+
}
|
|
39
|
+
export interface VariantEntry {
|
|
40
|
+
display?: null | Display;
|
|
41
|
+
model?: string | null;
|
|
42
|
+
sprite?: null | SpriteEntry;
|
|
43
|
+
tag?: null | JsonNode;
|
|
44
|
+
texture?: string | null;
|
|
45
|
+
}
|
|
46
|
+
export interface JsonNode {
|
|
47
|
+
[k: string]: unknown | undefined;
|
|
48
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface RegistryGroups {
|
|
2
|
+
[k: string]: StackGroupValue | undefined;
|
|
3
|
+
}
|
|
4
|
+
export interface StackGroupValue {
|
|
5
|
+
members: string[];
|
|
6
|
+
name?: null | DumpText;
|
|
7
|
+
}
|
|
8
|
+
export interface DumpText {
|
|
9
|
+
extra?: DumpText[];
|
|
10
|
+
keybind?: string;
|
|
11
|
+
text?: string;
|
|
12
|
+
translate?: string;
|
|
13
|
+
with?: DumpText[];
|
|
14
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export interface RegistryItems {
|
|
2
|
+
[k: string]: ItemEntry | undefined;
|
|
3
|
+
}
|
|
4
|
+
export interface ItemEntry {
|
|
5
|
+
block?: string | null;
|
|
6
|
+
display: Display;
|
|
7
|
+
id: string;
|
|
8
|
+
maxCount?: number | null;
|
|
9
|
+
order?: number | null;
|
|
10
|
+
producedBy?: string[];
|
|
11
|
+
rarity?: string | null;
|
|
12
|
+
sprite?: null | SpriteEntry;
|
|
13
|
+
tags?: string[];
|
|
14
|
+
usedBy?: string[];
|
|
15
|
+
variants: VariantEntry[];
|
|
16
|
+
}
|
|
17
|
+
export interface Display {
|
|
18
|
+
name: string;
|
|
19
|
+
tooltip: DumpText[];
|
|
20
|
+
tooltipMods: {
|
|
21
|
+
[k: string]: DumpText[] | undefined;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export interface DumpText {
|
|
25
|
+
extra?: DumpText[];
|
|
26
|
+
keybind?: string;
|
|
27
|
+
text?: string;
|
|
28
|
+
translate?: string;
|
|
29
|
+
with?: DumpText[];
|
|
30
|
+
}
|
|
31
|
+
export interface SpriteEntry {
|
|
32
|
+
h: number;
|
|
33
|
+
image: string;
|
|
34
|
+
ph: number;
|
|
35
|
+
pw: number;
|
|
36
|
+
tint?: string | null;
|
|
37
|
+
tintColor?: string | null;
|
|
38
|
+
w: number;
|
|
39
|
+
x: number;
|
|
40
|
+
y: number;
|
|
41
|
+
}
|
|
42
|
+
export interface VariantEntry {
|
|
43
|
+
display?: null | Display;
|
|
44
|
+
model?: string | null;
|
|
45
|
+
sprite?: null | SpriteEntry;
|
|
46
|
+
tag?: null | JsonNode;
|
|
47
|
+
texture?: string | null;
|
|
48
|
+
}
|
|
49
|
+
export interface JsonNode {
|
|
50
|
+
[k: string]: unknown | undefined;
|
|
51
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export type ResourceId = string;
|
|
2
|
+
export type ReferenceKind =
|
|
3
|
+
| "item"
|
|
4
|
+
| "block"
|
|
5
|
+
| "fluid"
|
|
6
|
+
| "entity"
|
|
7
|
+
| "itemTag"
|
|
8
|
+
| "blockTag"
|
|
9
|
+
| "fluidTag"
|
|
10
|
+
| "entityTag"
|
|
11
|
+
| "texture"
|
|
12
|
+
| "model"
|
|
13
|
+
| "recipe";
|
|
14
|
+
|
|
15
|
+
export interface RegistryRecipeCategories {
|
|
16
|
+
[k: string]: RecipeCategory | undefined;
|
|
17
|
+
}
|
|
18
|
+
export interface RecipeCategory {
|
|
19
|
+
display: Display;
|
|
20
|
+
icon?: null | TaggedReference;
|
|
21
|
+
layout?: null | RecipeCategoryLayout;
|
|
22
|
+
order: number;
|
|
23
|
+
workstations?: TaggedReference[];
|
|
24
|
+
}
|
|
25
|
+
export interface Display {
|
|
26
|
+
name: string;
|
|
27
|
+
tooltip: DumpText[];
|
|
28
|
+
tooltipMods: {
|
|
29
|
+
[k: string]: DumpText[] | undefined;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export interface DumpText {
|
|
33
|
+
extra?: DumpText[];
|
|
34
|
+
keybind?: string;
|
|
35
|
+
text?: string;
|
|
36
|
+
translate?: string;
|
|
37
|
+
with?: DumpText[];
|
|
38
|
+
}
|
|
39
|
+
export interface TaggedReference {
|
|
40
|
+
id: ResourceId;
|
|
41
|
+
kind: ReferenceKind;
|
|
42
|
+
tag?: JsonNode;
|
|
43
|
+
}
|
|
44
|
+
export interface JsonNode {
|
|
45
|
+
[k: string]: unknown | undefined;
|
|
46
|
+
}
|
|
47
|
+
export interface RecipeCategoryLayout {
|
|
48
|
+
h: number;
|
|
49
|
+
image?: string;
|
|
50
|
+
margin?: number;
|
|
51
|
+
scale?: number;
|
|
52
|
+
slots: {
|
|
53
|
+
[k: string]: RecipeLayoutSlot | undefined;
|
|
54
|
+
};
|
|
55
|
+
w: number;
|
|
56
|
+
}
|
|
57
|
+
export interface RecipeLayoutSlot {
|
|
58
|
+
h: number;
|
|
59
|
+
id: string;
|
|
60
|
+
role: string;
|
|
61
|
+
w: number;
|
|
62
|
+
x: number;
|
|
63
|
+
y: number;
|
|
64
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export type ResourceId = string;
|
|
2
|
+
export type ReferenceKind =
|
|
3
|
+
| "item"
|
|
4
|
+
| "block"
|
|
5
|
+
| "fluid"
|
|
6
|
+
| "entity"
|
|
7
|
+
| "itemTag"
|
|
8
|
+
| "blockTag"
|
|
9
|
+
| "fluidTag"
|
|
10
|
+
| "entityTag"
|
|
11
|
+
| "texture"
|
|
12
|
+
| "model"
|
|
13
|
+
| "recipe";
|
|
14
|
+
|
|
15
|
+
export interface RegistryTabs {
|
|
16
|
+
[k: string]: TabEntry | undefined;
|
|
17
|
+
}
|
|
18
|
+
export interface TabEntry {
|
|
19
|
+
display: Display;
|
|
20
|
+
icon?: null | TaggedReference;
|
|
21
|
+
id: string;
|
|
22
|
+
items: TaggedReference[];
|
|
23
|
+
order: number;
|
|
24
|
+
}
|
|
25
|
+
export interface Display {
|
|
26
|
+
name: string;
|
|
27
|
+
tooltip: DumpText[];
|
|
28
|
+
tooltipMods: {
|
|
29
|
+
[k: string]: DumpText[] | undefined;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export interface DumpText {
|
|
33
|
+
extra?: DumpText[];
|
|
34
|
+
keybind?: string;
|
|
35
|
+
text?: string;
|
|
36
|
+
translate?: string;
|
|
37
|
+
with?: DumpText[];
|
|
38
|
+
}
|
|
39
|
+
export interface TaggedReference {
|
|
40
|
+
id: ResourceId;
|
|
41
|
+
kind: ReferenceKind;
|
|
42
|
+
tag?: JsonNode;
|
|
43
|
+
}
|
|
44
|
+
export interface JsonNode {
|
|
45
|
+
[k: string]: unknown | undefined;
|
|
46
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { parseDumpManifest } from '@terrafirmagreg-team/game/lib/dump/manifest/parse';
|
|
2
|
+
import {
|
|
3
|
+
dumpStorageUrl,
|
|
4
|
+
registerDumpLocation,
|
|
5
|
+
type DumpLocation,
|
|
6
|
+
} from '@terrafirmagreg-team/game/lib/dump/paths';
|
|
7
|
+
import { parseDatasetRef } from '@terrafirmagreg-team/kit/lib/location';
|
|
8
|
+
import type { DumpReader } from '@terrafirmagreg-team/game/lib/dump/reader';
|
|
9
|
+
import { createAsyncCache } from '@terrafirmagreg-team/ui/lib/utils/async-cache';
|
|
10
|
+
|
|
11
|
+
const SAFE_LANG_CODE = /^[a-z0-9]+(_[a-z0-9]+)*$/i;
|
|
12
|
+
|
|
13
|
+
function cleanLangCode(code: string): string | undefined {
|
|
14
|
+
return SAFE_LANG_CODE.test(code) ? code : undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const MAX_INFLIGHT = 12;
|
|
18
|
+
const lanes = {
|
|
19
|
+
high: [] as Array<() => void>,
|
|
20
|
+
normal: [] as Array<() => void>,
|
|
21
|
+
};
|
|
22
|
+
let inflight = 0;
|
|
23
|
+
|
|
24
|
+
function pump() {
|
|
25
|
+
while (inflight < MAX_INFLIGHT) {
|
|
26
|
+
const run = lanes.high.shift() ?? lanes.normal.shift();
|
|
27
|
+
if (!run) return;
|
|
28
|
+
inflight++;
|
|
29
|
+
run();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function fetchLane<T>(task: () => Promise<T>, high: boolean): Promise<T> {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const run = () =>
|
|
36
|
+
task()
|
|
37
|
+
.then(resolve, reject)
|
|
38
|
+
.finally(() => {
|
|
39
|
+
inflight--;
|
|
40
|
+
pump();
|
|
41
|
+
});
|
|
42
|
+
(high ? lanes.high : lanes.normal).push(run);
|
|
43
|
+
pump();
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const FETCH_TIMEOUT = 20_000;
|
|
48
|
+
|
|
49
|
+
async function request(url: string): Promise<Response | undefined> {
|
|
50
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
51
|
+
try {
|
|
52
|
+
return await fetch(url, {
|
|
53
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT),
|
|
54
|
+
});
|
|
55
|
+
} catch {
|
|
56
|
+
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const fetchJson = createAsyncCache(
|
|
63
|
+
async (url: string, high: boolean) =>
|
|
64
|
+
fetchLane(async () => {
|
|
65
|
+
try {
|
|
66
|
+
const res = await request(url);
|
|
67
|
+
if (!res?.ok) return undefined;
|
|
68
|
+
return (await res.json()) as unknown;
|
|
69
|
+
} catch {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
}, high),
|
|
73
|
+
(url) => url,
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const fetchJsonGz = createAsyncCache(
|
|
77
|
+
async (url: string, high: boolean) =>
|
|
78
|
+
fetchLane(async () => {
|
|
79
|
+
try {
|
|
80
|
+
const res = await request(url);
|
|
81
|
+
if (!res?.ok || !res.body) return undefined;
|
|
82
|
+
const text = await new Response(
|
|
83
|
+
res.body.pipeThrough(new DecompressionStream('gzip')),
|
|
84
|
+
).text();
|
|
85
|
+
return JSON.parse(text) as unknown;
|
|
86
|
+
} catch {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
}, high),
|
|
90
|
+
(url) => url,
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
export const COMPRESSED_TREE = /^(?:data|meta)\//;
|
|
94
|
+
|
|
95
|
+
function toLangMap(raw: unknown): Record<string, string> {
|
|
96
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return {};
|
|
97
|
+
const map: Record<string, string> = {};
|
|
98
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
99
|
+
map[key] = typeof value === 'string' ? value : JSON.stringify(value);
|
|
100
|
+
}
|
|
101
|
+
return map;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export class JsonDumpReader implements DumpReader {
|
|
105
|
+
private manifestCache: Promise<ReturnType<typeof parseDumpManifest>> | undefined;
|
|
106
|
+
private locationCache: Promise<DumpLocation> | undefined;
|
|
107
|
+
private langCache = createAsyncCache((code: string) => this.loadLang(code));
|
|
108
|
+
private langRawCache = createAsyncCache((code: string) =>
|
|
109
|
+
this.loadLangRaw(code),
|
|
110
|
+
);
|
|
111
|
+
private readonly packId: string;
|
|
112
|
+
private readonly version?: string;
|
|
113
|
+
|
|
114
|
+
constructor(private dataset: string) {
|
|
115
|
+
const ref = parseDatasetRef(dataset);
|
|
116
|
+
this.packId = ref.id;
|
|
117
|
+
this.version = ref.version;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
getJson(path: string, priority?: 'high'): Promise<unknown | undefined> {
|
|
121
|
+
const high = priority === 'high';
|
|
122
|
+
if (path === 'versions.json') {
|
|
123
|
+
return fetchJson(
|
|
124
|
+
dumpStorageUrl(`${this.packId}/versions.json`),
|
|
125
|
+
high,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
return this.location().then((loc) => {
|
|
129
|
+
const key = loc.files?.[path];
|
|
130
|
+
if (key) {
|
|
131
|
+
return key.endsWith('.gz')
|
|
132
|
+
? fetchJsonGz(dumpStorageUrl(`${key}`), high)
|
|
133
|
+
: fetchJson(dumpStorageUrl(`${key}`), high);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (loc.files) return undefined;
|
|
137
|
+
const rel = `${loc.root}/${path}`;
|
|
138
|
+
return COMPRESSED_TREE.test(path)
|
|
139
|
+
? fetchJsonGz(dumpStorageUrl(`${rel}.gz`), high)
|
|
140
|
+
: fetchJson(dumpStorageUrl(`${rel}`), high);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
getLang(lang: string): Promise<Record<string, string>> {
|
|
145
|
+
const code = cleanLangCode(lang);
|
|
146
|
+
return code ? this.langCache(code) : Promise.resolve({});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
getLangRaw(lang: string): Promise<Record<string, string>> {
|
|
150
|
+
const code = cleanLangCode(lang);
|
|
151
|
+
return code ? this.langRawCache(code) : Promise.resolve({});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private location(): Promise<DumpLocation> {
|
|
155
|
+
this.locationCache ??= this.resolveLocation();
|
|
156
|
+
return this.locationCache;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private async resolveLocation(): Promise<DumpLocation> {
|
|
160
|
+
if (this.version) {
|
|
161
|
+
const root = `${this.packId}/${this.version}`;
|
|
162
|
+
const loc: DumpLocation = { root, files: await this.objectsIndex(root) };
|
|
163
|
+
registerDumpLocation(this.dataset, loc);
|
|
164
|
+
return loc;
|
|
165
|
+
}
|
|
166
|
+
const versions = (await fetchJson(
|
|
167
|
+
dumpStorageUrl(`${this.packId}/versions.json`),
|
|
168
|
+
true,
|
|
169
|
+
)) as { latest?: string } | undefined;
|
|
170
|
+
if (versions?.latest) {
|
|
171
|
+
const root = `${this.packId}/${versions.latest}`;
|
|
172
|
+
|
|
173
|
+
const exists = await fetchJson(
|
|
174
|
+
dumpStorageUrl(`${root}/manifest.json`),
|
|
175
|
+
false,
|
|
176
|
+
);
|
|
177
|
+
if (exists) {
|
|
178
|
+
const loc: DumpLocation = { root, files: await this.objectsIndex(root) };
|
|
179
|
+
registerDumpLocation(this.dataset, loc);
|
|
180
|
+
registerDumpLocation(`${this.packId}@${versions.latest}`, loc);
|
|
181
|
+
return loc;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const loc: DumpLocation = { root: this.packId };
|
|
185
|
+
registerDumpLocation(this.dataset, loc);
|
|
186
|
+
return loc;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private async objectsIndex(
|
|
190
|
+
root: string,
|
|
191
|
+
): Promise<Record<string, string> | undefined> {
|
|
192
|
+
const raw = await fetchJson(dumpStorageUrl(`${root}/objects.json`), false);
|
|
193
|
+
const files =
|
|
194
|
+
raw && typeof raw === 'object' && !Array.isArray(raw)
|
|
195
|
+
? (raw as Record<string, unknown>).files
|
|
196
|
+
: undefined;
|
|
197
|
+
if (!files || typeof files !== 'object' || Array.isArray(files)) {
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
const out: Record<string, string> = {};
|
|
201
|
+
for (const [rel, key] of Object.entries(files)) {
|
|
202
|
+
if (typeof key === 'string') out[rel] = key;
|
|
203
|
+
}
|
|
204
|
+
return out;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private manifest() {
|
|
208
|
+
this.manifestCache ??= this.getJson('manifest.json').then((raw) =>
|
|
209
|
+
parseDumpManifest(raw),
|
|
210
|
+
);
|
|
211
|
+
return this.manifestCache;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private async loadLang(lang: string): Promise<Record<string, string>> {
|
|
215
|
+
const codes = lang === 'en_us' ? ['en_us'] : ['en_us', lang];
|
|
216
|
+
const loc = await this.location();
|
|
217
|
+
const base = loc.files ? 'lang' : 'meta/lang';
|
|
218
|
+
const metaMaps = await Promise.all(
|
|
219
|
+
codes.map((code) =>
|
|
220
|
+
this.getJson(`${base}/${code}.json`).then(toLangMap),
|
|
221
|
+
),
|
|
222
|
+
);
|
|
223
|
+
return Object.assign({}, ...metaMaps);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private async loadLangRaw(code: string): Promise<Record<string, string>> {
|
|
227
|
+
const loc = await this.location();
|
|
228
|
+
const base = loc.files ? 'lang' : 'meta/lang';
|
|
229
|
+
return this.getJson(`${base}/${code}.json`).then(toLangMap);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import {
|
|
2
|
+
asNumber,
|
|
3
|
+
asRecord,
|
|
4
|
+
asString,
|
|
5
|
+
asStringList,
|
|
6
|
+
} from '@terrafirmagreg-team/game/lib/dump/contract/read';
|
|
7
|
+
import type {
|
|
8
|
+
DumpIndexes,
|
|
9
|
+
DumpManifest,
|
|
10
|
+
DumpProducer,
|
|
11
|
+
DumpText,
|
|
12
|
+
NamespaceDisplay,
|
|
13
|
+
PackInfo,
|
|
14
|
+
RecipeTypeRow,
|
|
15
|
+
} from '@terrafirmagreg-team/game/lib/dump/manifest/dto';
|
|
16
|
+
|
|
17
|
+
function asProducer(value: unknown): DumpProducer | undefined {
|
|
18
|
+
const row = asRecord(value);
|
|
19
|
+
if (!row) return undefined;
|
|
20
|
+
const name = asString(row.name);
|
|
21
|
+
const platform = asString(row.platform);
|
|
22
|
+
const minecraft = asString(row.minecraft);
|
|
23
|
+
if (!name || !platform || !minecraft) return undefined;
|
|
24
|
+
return { name, platform, minecraft };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function asPackInfo(value: unknown): PackInfo | undefined {
|
|
28
|
+
const row = asRecord(value);
|
|
29
|
+
if (!row) return undefined;
|
|
30
|
+
const id = asString(row.id);
|
|
31
|
+
if (!id) return undefined;
|
|
32
|
+
return { id, version: typeof row.version === 'string' ? row.version : null };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function asDumpText(value: unknown): DumpText | undefined {
|
|
36
|
+
const row = asRecord(value);
|
|
37
|
+
if (!row) return undefined;
|
|
38
|
+
const out: DumpText = {};
|
|
39
|
+
const text = asString(row.text);
|
|
40
|
+
const translate = asString(row.translate);
|
|
41
|
+
const keybind = asString(row.keybind);
|
|
42
|
+
if (text !== undefined) out.text = text;
|
|
43
|
+
if (translate !== undefined) out.translate = translate;
|
|
44
|
+
if (keybind !== undefined) out.keybind = keybind;
|
|
45
|
+
if (Array.isArray(row.with)) {
|
|
46
|
+
out.with = row.with.flatMap((entry) => {
|
|
47
|
+
const part = asDumpText(entry);
|
|
48
|
+
return part ? [part] : [];
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (Array.isArray(row.extra)) {
|
|
52
|
+
out.extra = row.extra.flatMap((entry) => {
|
|
53
|
+
const part = asDumpText(entry);
|
|
54
|
+
return part ? [part] : [];
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function asDisplay(value: unknown): NamespaceDisplay['display'] | undefined {
|
|
61
|
+
const row = asRecord(value);
|
|
62
|
+
if (!row) return undefined;
|
|
63
|
+
const name = asString(row.name);
|
|
64
|
+
if (!name) return undefined;
|
|
65
|
+
const tooltipMods: Record<string, DumpText[]> = {};
|
|
66
|
+
const modsRow = asRecord(row.tooltipMods);
|
|
67
|
+
if (modsRow) {
|
|
68
|
+
for (const [mod, entries] of Object.entries(modsRow)) {
|
|
69
|
+
if (!Array.isArray(entries)) continue;
|
|
70
|
+
tooltipMods[mod] = entries.flatMap((entry) => {
|
|
71
|
+
const text = asDumpText(entry);
|
|
72
|
+
return text ? [text] : [];
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
name,
|
|
78
|
+
tooltip: Array.isArray(row.tooltip)
|
|
79
|
+
? row.tooltip.flatMap((entry) => {
|
|
80
|
+
const text = asDumpText(entry);
|
|
81
|
+
return text ? [text] : [];
|
|
82
|
+
})
|
|
83
|
+
: [],
|
|
84
|
+
tooltipMods,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function asNamespaces(
|
|
89
|
+
value: unknown,
|
|
90
|
+
): Record<string, NamespaceDisplay> | undefined {
|
|
91
|
+
const row = asRecord(value);
|
|
92
|
+
if (!row) return undefined;
|
|
93
|
+
const out: Record<string, NamespaceDisplay> = {};
|
|
94
|
+
for (const [ns, entry] of Object.entries(row)) {
|
|
95
|
+
if (!ns || ns.includes('/') || ns.includes('\\') || ns.includes('..')) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const nsRow = asRecord(entry);
|
|
99
|
+
const display = asDisplay(nsRow?.display);
|
|
100
|
+
if (!display) continue;
|
|
101
|
+
out[ns] = {
|
|
102
|
+
display,
|
|
103
|
+
registry: asStringList(nsRow?.registry) ?? [],
|
|
104
|
+
guides: asStringList(nsRow?.guides) ?? [],
|
|
105
|
+
languages: asStringList(nsRow?.languages) ?? [],
|
|
106
|
+
quests: nsRow?.quests === true,
|
|
107
|
+
keybinds: nsRow?.keybinds === true,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function asRecipeTypeRow(value: unknown): RecipeTypeRow | undefined {
|
|
114
|
+
const row = asRecord(value);
|
|
115
|
+
if (!row) return undefined;
|
|
116
|
+
const id = asString(row.id);
|
|
117
|
+
const count = asNumber(row.count);
|
|
118
|
+
const filesRow = asRecord(row.files);
|
|
119
|
+
if (!id || count === undefined || !filesRow) return undefined;
|
|
120
|
+
const files: Record<string, string> = {};
|
|
121
|
+
for (const [ns, path] of Object.entries(filesRow)) {
|
|
122
|
+
if (typeof path === 'string' && path) files[ns] = path;
|
|
123
|
+
}
|
|
124
|
+
return { id, count, files };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function asIndexes(value: unknown): DumpIndexes | undefined {
|
|
128
|
+
const row = asRecord(value);
|
|
129
|
+
if (!row) return undefined;
|
|
130
|
+
const recipesRow = asRecord(row.recipes);
|
|
131
|
+
if (!recipesRow || !Array.isArray(recipesRow.types)) return undefined;
|
|
132
|
+
const types = recipesRow.types.flatMap((entry) => {
|
|
133
|
+
const typeRow = asRecipeTypeRow(entry);
|
|
134
|
+
return typeRow ? [typeRow] : [];
|
|
135
|
+
});
|
|
136
|
+
const quests: Record<string, string> = {};
|
|
137
|
+
const questsRow = asRecord(row.quests);
|
|
138
|
+
if (questsRow) {
|
|
139
|
+
for (const [ns, path] of Object.entries(questsRow)) {
|
|
140
|
+
if (typeof path === 'string' && path) quests[ns] = path;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
recipes: { types },
|
|
145
|
+
quests,
|
|
146
|
+
used: asStringList(row.used) ?? [],
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function parseDumpManifest(value: unknown): DumpManifest | undefined {
|
|
151
|
+
const row = asRecord(value);
|
|
152
|
+
if (!row) return undefined;
|
|
153
|
+
if (asString(row.schema) !== 'manifest') return undefined;
|
|
154
|
+
const producer = asProducer(row.producer);
|
|
155
|
+
const indexes = asIndexes(row.indexes);
|
|
156
|
+
const namespaces = asNamespaces(row.namespaces);
|
|
157
|
+
if (!producer || !indexes || !namespaces) return undefined;
|
|
158
|
+
return {
|
|
159
|
+
schema: 'manifest',
|
|
160
|
+
producer,
|
|
161
|
+
pack: asPackInfo(row.pack) ?? null,
|
|
162
|
+
languages: asStringList(row.languages),
|
|
163
|
+
layout: asString(row.layout) ?? 'v1',
|
|
164
|
+
namespaces,
|
|
165
|
+
indexes,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { DumpManifest } from '@terrafirmagreg-team/game/lib/dump/manifest/dto';
|
|
2
|
+
|
|
3
|
+
export type GuideBookRef = {
|
|
4
|
+
namespace: string;
|
|
5
|
+
modId: string;
|
|
6
|
+
bookPath: string;
|
|
7
|
+
bookId: string;
|
|
8
|
+
dir: string;
|
|
9
|
+
locales: string[];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type ClientDumpMod = {
|
|
13
|
+
namespace: string;
|
|
14
|
+
books: GuideBookRef[];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type DumpMod = {
|
|
18
|
+
namespace: string;
|
|
19
|
+
languages: Record<string, string>;
|
|
20
|
+
keybindsFile: string | null;
|
|
21
|
+
items: string | null;
|
|
22
|
+
blocks: string | null;
|
|
23
|
+
fluids: string | null;
|
|
24
|
+
entities: string | null;
|
|
25
|
+
tabs: string | null;
|
|
26
|
+
groups: string | null;
|
|
27
|
+
recipeCategories: string | null;
|
|
28
|
+
tags: string | null;
|
|
29
|
+
recipeIndex: string | null;
|
|
30
|
+
modelIndex: string | null;
|
|
31
|
+
questData: string | null;
|
|
32
|
+
guides: Record<string, string>;
|
|
33
|
+
books: GuideBookRef[];
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type DumpIndex = {
|
|
37
|
+
root: string;
|
|
38
|
+
manifest: DumpManifest;
|
|
39
|
+
mods: DumpMod[];
|
|
40
|
+
};
|