@gmvjs/dunia1 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/LICENSE +21 -0
- package/README.md +66 -2
- package/dist/fat.d.ts +57 -0
- package/dist/fat.js +226 -0
- package/dist/fat.js.map +1 -0
- package/dist/fcb.d.ts +14 -0
- package/dist/fcb.js +86 -0
- package/dist/fcb.js.map +1 -0
- package/dist/games/farcry2.d.ts +7 -0
- package/dist/games/farcry2.js +11 -0
- package/dist/games/farcry2.js.map +1 -0
- package/dist/i18n.d.ts +2 -0
- package/dist/i18n.js +12 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +68 -0
- package/dist/index.js.map +1 -0
- package/dist/level.d.ts +120 -0
- package/dist/level.js +751 -0
- package/dist/level.js.map +1 -0
- package/dist/mab.d.ts +40 -0
- package/dist/mab.js +228 -0
- package/dist/mab.js.map +1 -0
- package/dist/realtree.d.ts +27 -0
- package/dist/realtree.js +127 -0
- package/dist/realtree.js.map +1 -0
- package/dist/rml.d.ts +15 -0
- package/dist/rml.js +77 -0
- package/dist/rml.js.map +1 -0
- package/dist/xbg.d.ts +115 -0
- package/dist/xbg.js +524 -0
- package/dist/xbg.js.map +1 -0
- package/dist/xbt.d.ts +3 -0
- package/dist/xbt.js +14 -0
- package/dist/xbt.js.map +1 -0
- package/package.json +54 -4
package/dist/index.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { defineEngine } from "@gmvjs/core/plugin";
|
|
2
|
+
import { EN } from "./i18n.js";
|
|
3
|
+
const ui = { archives: true, archiveExts: ["fat", "dat"], archiveLabel: "Открыть *.fat + *.dat…" };
|
|
4
|
+
const dunia1Engine = defineEngine({
|
|
5
|
+
id: "dunia1",
|
|
6
|
+
title: "Dunia Engine 1 (Far Cry 2)",
|
|
7
|
+
i18n: { en: EN },
|
|
8
|
+
codecs: [{ id: "xbt", extensions: ["xbt"], decode: async (buf) => (await import("./xbt.js")).decodeXbt(buf) }],
|
|
9
|
+
formats: [
|
|
10
|
+
{
|
|
11
|
+
id: "dunia-xbg",
|
|
12
|
+
title: "Far Cry 2: модель .xbg",
|
|
13
|
+
engine: "dunia1",
|
|
14
|
+
extensions: ["xbg"],
|
|
15
|
+
motionExtensions: ["mab"],
|
|
16
|
+
status: "beta",
|
|
17
|
+
sniff: (h) => String.fromCharCode(...h.subarray(0, 4)) === "HSEM" ? 1 : 0,
|
|
18
|
+
load: async (main, ctx) => (await import("./xbg.js")).loadXbg(main, ctx),
|
|
19
|
+
afterLoad: async (ctx) => {
|
|
20
|
+
const arc = ctx.main.archive;
|
|
21
|
+
if (!arc || !ctx.model.loadMotions) return;
|
|
22
|
+
const list = (await import("./xbg.js")).listMabMotions(ctx.model, arc);
|
|
23
|
+
if (list.length) ctx.addMotions(list, "animations");
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "dunia-level",
|
|
28
|
+
title: "Far Cry 2: уровень (worlds/<мир>/maps/*.fc2level — карта 1×1 км с объектами; <мир>.fc2level — весь мир)",
|
|
29
|
+
engine: "dunia1",
|
|
30
|
+
extensions: ["fc2level"],
|
|
31
|
+
status: "beta",
|
|
32
|
+
sniff: (h) => String.fromCharCode(...h.subarray(0, 12)) === "DUNIA-LEVEL\n" ? 1 : 0,
|
|
33
|
+
load: async (main, ctx) => {
|
|
34
|
+
const text = new TextDecoder().decode(await main.arrayBuffer());
|
|
35
|
+
return (await import("./level.js")).loadDuniaLevel(text, main, ctx);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
archives: [
|
|
40
|
+
{
|
|
41
|
+
id: "dunia-fat",
|
|
42
|
+
title: "Far Cry 2: архивы .fat + .dat",
|
|
43
|
+
engine: "dunia1",
|
|
44
|
+
extensions: ["fat"],
|
|
45
|
+
companionExtensions: ["dat"],
|
|
46
|
+
openFromFolder: true,
|
|
47
|
+
sniff: (h) => String.fromCharCode(...h.subarray(0, 4)) === "2TAF" ? 1 : 0,
|
|
48
|
+
open: async (file, siblings) => (await import("./fat.js")).openDuniaArchive(file, siblings)
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
games: [
|
|
52
|
+
{
|
|
53
|
+
id: "farcry2",
|
|
54
|
+
group: "Игры",
|
|
55
|
+
title: "Far Cry 2 (2008)",
|
|
56
|
+
engine: "dunia1",
|
|
57
|
+
formats: ["dunia-xbg", "dunia-level"],
|
|
58
|
+
hint: "Откройте папку игры (или Data_Win32): архивы .fat/.dat подключатся сами, имена файлов берутся из списков ресурсов игры. Модели — graphics/…/*.xbg, уровни — worlds/<мир>/maps/*.fc2level (карта 1×1 км: ландшафт и объекты) и worlds/<мир>/<мир>.fc2level (весь мир — ландшафт).",
|
|
59
|
+
ui,
|
|
60
|
+
status: "beta",
|
|
61
|
+
markers: ["common.fat", "worlds.fat"]
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
});
|
|
65
|
+
export {
|
|
66
|
+
dunia1Engine
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["// Dunia Engine (Far Cry 2): архивы .fat/.dat (имена по хешу, из XML-списков ресурсов игры), модели .xbg, материалы .xbm,\n// текстуры .xbt, анимации .mab (скелет — *_ref.skeleton в папке модели или выше).\nimport { defineEngine, type GamePreset } from '@gmvjs/core/plugin';\nimport { EN } from './i18n';\n\nconst ui: GamePreset['ui'] = { archives: true, archiveExts: ['fat', 'dat'], archiveLabel: 'Открыть *.fat + *.dat…' };\n\nexport const dunia1Engine = defineEngine({\n id: 'dunia1',\n title: 'Dunia Engine 1 (Far Cry 2)',\n i18n: { en: EN },\n codecs: [{ id: 'xbt', extensions: ['xbt'], decode: async (buf) => (await import('./xbt')).decodeXbt(buf) }],\n formats: [\n {\n id: 'dunia-xbg',\n title: 'Far Cry 2: модель .xbg',\n engine: 'dunia1',\n extensions: ['xbg'],\n motionExtensions: ['mab'],\n status: 'beta',\n sniff: (h) => (String.fromCharCode(...h.subarray(0, 4)) === 'HSEM' ? 1 : 0),\n load: async (main, ctx) => (await import('./xbg')).loadXbg(main, ctx),\n afterLoad: async (ctx) => {\n const arc = ctx.main.archive;\n if (!arc || !ctx.model.loadMotions) return;\n const list = (await import('./xbg')).listMabMotions(ctx.model, arc);\n if (list.length) ctx.addMotions(list, 'animations');\n },\n },\n {\n id: 'dunia-level',\n title: 'Far Cry 2: уровень (worlds/<мир>/maps/*.fc2level — карта 1×1 км с объектами; <мир>.fc2level — весь мир)',\n engine: 'dunia1',\n extensions: ['fc2level'],\n status: 'beta',\n sniff: (h) => (String.fromCharCode(...h.subarray(0, 12)) === 'DUNIA-LEVEL\\n' ? 1 : 0),\n load: async (main, ctx) => {\n const text = new TextDecoder().decode(await main.arrayBuffer());\n return (await import('./level')).loadDuniaLevel(text, main, ctx);\n },\n },\n ],\n archives: [\n {\n id: 'dunia-fat',\n title: 'Far Cry 2: архивы .fat + .dat',\n engine: 'dunia1',\n extensions: ['fat'],\n companionExtensions: ['dat'],\n openFromFolder: true,\n sniff: (h) => (String.fromCharCode(...h.subarray(0, 4)) === '2TAF' ? 1 : 0),\n open: async (file, siblings) => (await import('./fat')).openDuniaArchive(file, siblings),\n },\n ],\n games: [\n {\n id: 'farcry2',\n group: 'Игры',\n title: 'Far Cry 2 (2008)',\n engine: 'dunia1',\n formats: ['dunia-xbg', 'dunia-level'],\n hint: 'Откройте папку игры (или Data_Win32): архивы .fat/.dat подключатся сами, имена файлов берутся из списков ресурсов игры. Модели — graphics/…/*.xbg, уровни — worlds/<мир>/maps/*.fc2level (карта 1×1 км: ландшафт и объекты) и worlds/<мир>/<мир>.fc2level (весь мир — ландшафт).',\n ui,\n status: 'beta',\n markers: ['common.fat', 'worlds.fat'],\n },\n ],\n});\n"],"names":[],"mappings":";;AAKA,MAAM,KAAuB,EAAE,UAAU,MAAM,aAAa,CAAC,OAAO,KAAK,GAAG,cAAc,yBAAA;AAEnF,MAAM,eAAe,aAAa;AAAA,EACvC,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,MAAM,EAAE,IAAI,GAAA;AAAA,EACZ,QAAQ,CAAC,EAAE,IAAI,OAAO,YAAY,CAAC,KAAK,GAAG,QAAQ,OAAO,SAAS,MAAM,OAAO,UAAO,GAAG,UAAU,GAAG,GAAG;AAAA,EAC1G,SAAS;AAAA,IACP;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,YAAY,CAAC,KAAK;AAAA,MAClB,kBAAkB,CAAC,KAAK;AAAA,MACxB,QAAQ;AAAA,MACR,OAAO,CAAC,MAAO,OAAO,aAAa,GAAG,EAAE,SAAS,GAAG,CAAC,CAAC,MAAM,SAAS,IAAI;AAAA,MACzE,MAAM,OAAO,MAAM,SAAS,MAAM,OAAO,UAAO,GAAG,QAAQ,MAAM,GAAG;AAAA,MACpE,WAAW,OAAO,QAAQ;AACxB,cAAM,MAAM,IAAI,KAAK;AACrB,YAAI,CAAC,OAAO,CAAC,IAAI,MAAM,YAAa;AACpC,cAAM,QAAQ,MAAM,OAAO,UAAO,GAAG,eAAe,IAAI,OAAO,GAAG;AAClE,YAAI,KAAK,OAAQ,KAAI,WAAW,MAAM,YAAY;AAAA,MACpD;AAAA,IAAA;AAAA,IAEF;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,YAAY,CAAC,UAAU;AAAA,MACvB,QAAQ;AAAA,MACR,OAAO,CAAC,MAAO,OAAO,aAAa,GAAG,EAAE,SAAS,GAAG,EAAE,CAAC,MAAM,kBAAkB,IAAI;AAAA,MACnF,MAAM,OAAO,MAAM,QAAQ;AACzB,cAAM,OAAO,IAAI,YAAA,EAAc,OAAO,MAAM,KAAK,aAAa;AAC9D,gBAAQ,MAAM,OAAO,YAAS,GAAG,eAAe,MAAM,MAAM,GAAG;AAAA,MACjE;AAAA,IAAA;AAAA,EACF;AAAA,EAEF,UAAU;AAAA,IACR;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,YAAY,CAAC,KAAK;AAAA,MAClB,qBAAqB,CAAC,KAAK;AAAA,MAC3B,gBAAgB;AAAA,MAChB,OAAO,CAAC,MAAO,OAAO,aAAa,GAAG,EAAE,SAAS,GAAG,CAAC,CAAC,MAAM,SAAS,IAAI;AAAA,MACzE,MAAM,OAAO,MAAM,cAAc,MAAM,OAAO,UAAO,GAAG,iBAAiB,MAAM,QAAQ;AAAA,IAAA;AAAA,EACzF;AAAA,EAEF,OAAO;AAAA,IACL;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,SAAS,CAAC,aAAa,aAAa;AAAA,MACpC,MAAM;AAAA,MACN;AAAA,MACA,QAAQ;AAAA,MACR,SAAS,CAAC,cAAc,YAAY;AAAA,IAAA;AAAA,EACtC;AAEJ,CAAC;"}
|
package/dist/level.d.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { LoadContext } from '@gmvjs/core/plugin';
|
|
2
|
+
import type { Mesh, Model, VFile } from '@gmvjs/core/types';
|
|
3
|
+
import { type DuniaArchiveApi, type DuniaWorld, type FatRef } from './fat';
|
|
4
|
+
import { type FcbObject } from './fcb';
|
|
5
|
+
export interface TerrainLayer {
|
|
6
|
+
name: string;
|
|
7
|
+
texture: string;
|
|
8
|
+
normal: string;
|
|
9
|
+
tiling: number;
|
|
10
|
+
}
|
|
11
|
+
/** Слои ландшафта и сетка мира из game.xml. */
|
|
12
|
+
export declare function worldDescriptor(u: Uint8Array): {
|
|
13
|
+
layers: TerrainLayer[];
|
|
14
|
+
countX: number;
|
|
15
|
+
countY: number;
|
|
16
|
+
};
|
|
17
|
+
export interface TerrainSector {
|
|
18
|
+
id: number;
|
|
19
|
+
min: number;
|
|
20
|
+
max: number;
|
|
21
|
+
layers: number[];
|
|
22
|
+
height: Float32Array;
|
|
23
|
+
mask: Uint8Array;
|
|
24
|
+
water: number;
|
|
25
|
+
}
|
|
26
|
+
/** Ландшафт сектора (см. заголовок файла). */
|
|
27
|
+
export declare function parseTerrainSector(u: Uint8Array): TerrainSector;
|
|
28
|
+
/**
|
|
29
|
+
* Слой вершины: старший установленный бит младшей тетрады маски, чей слот занят (верхний нарисованный слой);
|
|
30
|
+
* пустой слот — 255; без бит (край сектора) — первый занятый слот.
|
|
31
|
+
*/
|
|
32
|
+
export declare function vertexLayer(mask: number, slots: readonly number[]): number;
|
|
33
|
+
/**
|
|
34
|
+
* Углы объекта (градусы) -> кватернион [w, x, y, z]: R = Rz(z)·Ry(y)·Rx(x) (сначала X, затем Y, затем Z — рыскание).
|
|
35
|
+
* Проверено по объектам на склонах: ось «вверх» объекта совпадает с нормалью земли (см. тест уровней).
|
|
36
|
+
*/
|
|
37
|
+
export declare function anglesQuat(a: readonly number[]): number[];
|
|
38
|
+
/** Объект уровня: модель, видимые части (пусто — все), поза. */
|
|
39
|
+
export interface LevelEntity {
|
|
40
|
+
name: string;
|
|
41
|
+
model: string;
|
|
42
|
+
parts: string[];
|
|
43
|
+
pos: number[];
|
|
44
|
+
angles: number[];
|
|
45
|
+
layer: string;
|
|
46
|
+
archetype?: string;
|
|
47
|
+
}
|
|
48
|
+
/** Архетипы entitylibrary.fcb: hidName -> модель и части. */
|
|
49
|
+
export declare function entityLibrary(root: FcbObject): Map<string, {
|
|
50
|
+
model: string;
|
|
51
|
+
parts: string[];
|
|
52
|
+
}>;
|
|
53
|
+
/** Объекты сектора (WorldSector) с моделями; архетипы — по библиотеке. */
|
|
54
|
+
export interface LevelMarker {
|
|
55
|
+
kind: 'particle' | 'light';
|
|
56
|
+
name: string;
|
|
57
|
+
pos: number[];
|
|
58
|
+
}
|
|
59
|
+
export declare function sectorEntities(root: FcbObject, lib: Map<string, {
|
|
60
|
+
model: string;
|
|
61
|
+
parts: string[];
|
|
62
|
+
}>): {
|
|
63
|
+
list: LevelEntity[];
|
|
64
|
+
noModel: number;
|
|
65
|
+
unknownArchetype: Set<string>;
|
|
66
|
+
markers: LevelMarker[];
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Коллекции сектора (CCollectionComponent у CSectorEntity — камни, мусор, растительность, расставленные кистью редактора):
|
|
70
|
+
* 0x114CFEE7 — набор (рамка 0x17E7C62E–0x2BEAF977), его данные 0xA18C8484: 0x89C8C095 — хеши путей моделей (как имена
|
|
71
|
+
* в .fat), 0x58AD43E2 — число групп каждой модели, 0x798C33D5 — группы (u8 число экземпляров, далее номер первого),
|
|
72
|
+
* 0xB0484974 — центры и радиусы групп; экземпляры: 0xD28100C0 — u16 x, u16 y (дециметры), 0x8E2752CA — f32 z,
|
|
73
|
+
* 0x98C678BD — u32 (младшие 16 бит — поворот вокруг Z, полный круг = 65536; старшие — не разобраны), 0x9D1CAE67 — цвета.
|
|
74
|
+
* Все массивы — u32 число, затем элементы.
|
|
75
|
+
*/
|
|
76
|
+
export declare function sectorCollections(root: FcbObject): Array<{
|
|
77
|
+
hash: number;
|
|
78
|
+
pos: number[];
|
|
79
|
+
yaw: number;
|
|
80
|
+
}>;
|
|
81
|
+
/** Шаг сетки ландшафта (м): степень двойки, чтобы вершин было не больше бюджета. */
|
|
82
|
+
export declare const terrainStep: (w: number, h: number, budget?: number) => number;
|
|
83
|
+
/** Меши ландшафта области: по мешу на слой (треугольник — по слою его угла), UV — плитка слоя (Tiling повторов на сектор). */
|
|
84
|
+
export declare function terrainMeshes(spec: {
|
|
85
|
+
x0: number;
|
|
86
|
+
y0: number;
|
|
87
|
+
w: number;
|
|
88
|
+
h: number;
|
|
89
|
+
}, sectors: Map<number, TerrainSector>, widthInSectors: number, layers: TerrainLayer[], step: number): Mesh[];
|
|
90
|
+
/** Вода: по сектору с уровнем воды — клетки сетки, где земля ниже уровня, плоскостью на уровне; полупрозрачная. */
|
|
91
|
+
export declare function waterMesh(spec: {
|
|
92
|
+
x0: number;
|
|
93
|
+
y0: number;
|
|
94
|
+
w: number;
|
|
95
|
+
h: number;
|
|
96
|
+
}, sectors: Map<number, TerrainSector>, widthInSectors: number, step: number): Mesh | null;
|
|
97
|
+
/** Указатель мира: файлы мира, ландшафт по номеру сектора, файлы объектов (WorldSector) по X, Y. Строится один раз на архив. */
|
|
98
|
+
interface WorldIndex {
|
|
99
|
+
files: FatRef[];
|
|
100
|
+
terrain: Map<number, FatRef>;
|
|
101
|
+
terrainEnd: number;
|
|
102
|
+
objects?: Promise<Map<number, FatRef[]>>;
|
|
103
|
+
full?: boolean;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Указатель мира. Обычно файлы мира — участок .dat между .sectorsdep.fcb предыдущего мира и своим (worlds.dat). Если там
|
|
107
|
+
* нет ландшафта или номера секторов повторяются (dlc1.dat: сначала сектора всех миров, потом их именованные файлы), k-й блок ландшафта .dat — k-го мира
|
|
108
|
+
* по порядку .sectorsdep.fcb, а файлы мира — от начала его блока до начала следующего.
|
|
109
|
+
*/
|
|
110
|
+
export declare function worldIndex(api: DuniaArchiveApi, world: DuniaWorld): Promise<WorldIndex>;
|
|
111
|
+
/**
|
|
112
|
+
* Файлы объектов мира: .fcb с корнем WorldSector. У больших миров — после блока ландшафта (объекты слоёв миссий; рядом
|
|
113
|
+
* с ландшафтом — только сектора-коллекции растительности), у малых — вперемешку с ландшафтом. X, Y — первые значения
|
|
114
|
+
* корня, читаются из начала файла.
|
|
115
|
+
*/
|
|
116
|
+
export declare function worldObjects(api: DuniaArchiveApi, world: DuniaWorld, idx: WorldIndex): Promise<Map<number, FatRef[]>>;
|
|
117
|
+
/** X, Y из начала .fcb с корнем WorldSector (иначе null). */
|
|
118
|
+
export declare function worldSectorXY(h: Uint8Array): [number, number] | null;
|
|
119
|
+
export declare function loadDuniaLevel(text: string, main: VFile, ctx: LoadContext): Promise<Model>;
|
|
120
|
+
export {};
|