@hato810424/mc-resources-plugin 0.0.0-beta.11

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.
@@ -0,0 +1,2333 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) {
13
+ __defProp(to, key, {
14
+ get: ((k) => from[k]).bind(null, key),
15
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
+ });
17
+ }
18
+ }
19
+ }
20
+ return to;
21
+ };
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
23
+ value: mod,
24
+ enumerable: true
25
+ }) : target, mod));
26
+
27
+ //#endregion
28
+ let find_cache_directory = require("find-cache-directory");
29
+ find_cache_directory = __toESM(find_cache_directory);
30
+ let zod = require("zod");
31
+ zod = __toESM(zod);
32
+ let node_fs = require("node:fs");
33
+ let node_path = require("node:path");
34
+ node_path = __toESM(node_path);
35
+ let node_util = require("node:util");
36
+ let chalk = require("chalk");
37
+ chalk = __toESM(chalk);
38
+ let fs = require("fs");
39
+ let path = require("path");
40
+ let node_stream_zip = require("node-stream-zip");
41
+ node_stream_zip = __toESM(node_stream_zip);
42
+ let fs_promises = require("fs/promises");
43
+ let canvas = require("canvas");
44
+ let sharp = require("sharp");
45
+ sharp = __toESM(sharp);
46
+ let node_crypto = require("node:crypto");
47
+
48
+ //#region src/env.ts
49
+ const CONFIG = {
50
+ OUTPUT_DIR: "./mcpacks",
51
+ EMPTY_OUT_DIR: false,
52
+ INCLUDE: [
53
+ "**/*.ts",
54
+ "**/*.tsx",
55
+ "**/*.js",
56
+ "**/*.jsx"
57
+ ],
58
+ EXCLUDE: [],
59
+ CACHE_DIR: (0, find_cache_directory.default)({
60
+ name: "@hato810424/mc-resources-plugin",
61
+ create: true
62
+ }),
63
+ START_UP_RENDER_CACHE_REFRESH: false,
64
+ TEXTURE_SIZE: 16,
65
+ WIDTH: 128,
66
+ HEIGHT: 128,
67
+ ROTATION: [
68
+ -30,
69
+ 45,
70
+ 0
71
+ ],
72
+ LOG_LEVEL: "info"
73
+ };
74
+
75
+ //#endregion
76
+ //#region src/types.ts
77
+ const PluginOptionsSchema = zod.object({
78
+ mcVersion: zod.string().regex(/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/, "有効なバージョン形式 (X.Y.Z) を入力してください"),
79
+ resourcePackPath: zod.string(),
80
+ outputPath: zod.string().optional(),
81
+ emptyOutDir: zod.boolean().optional(),
82
+ include: zod.array(zod.string()).optional(),
83
+ exclude: zod.array(zod.string()).optional(),
84
+ cacheDir: zod.string().optional(),
85
+ startUpRenderCacheRefresh: zod.boolean().optional(),
86
+ logLevel: zod.enum([
87
+ "info",
88
+ "debug",
89
+ "error"
90
+ ]).optional()
91
+ });
92
+
93
+ //#endregion
94
+ //#region src/filesystem.ts
95
+ /**
96
+ * resourcePackPath/assets/minecraft 内のすべての画像ファイルを再帰的に取得
97
+ */
98
+ function getAllImages(resourcePackPath) {
99
+ const images = [];
100
+ const minecraftPath = (0, node_path.join)(resourcePackPath, "assets", "minecraft");
101
+ function walkDir(currentPath, basePath) {
102
+ try {
103
+ const entries = (0, node_fs.readdirSync)(currentPath);
104
+ for (const entry of entries) {
105
+ const fullPath = (0, node_path.join)(currentPath, entry);
106
+ if ((0, node_fs.statSync)(fullPath).isDirectory()) walkDir(fullPath, basePath);
107
+ else {
108
+ const ext = (0, node_path.extname)(entry).toLowerCase();
109
+ if ([
110
+ ".png",
111
+ ".jpg",
112
+ ".jpeg",
113
+ ".gif",
114
+ ".webp"
115
+ ].includes(ext)) {
116
+ const relativePath = (0, node_path.relative)(basePath, fullPath).replace(/\\/g, "/");
117
+ images.push({
118
+ path: `/${relativePath}`,
119
+ relativePath
120
+ });
121
+ }
122
+ }
123
+ }
124
+ } catch {}
125
+ }
126
+ walkDir(minecraftPath, minecraftPath);
127
+ return images;
128
+ }
129
+ /**
130
+ * 出力ディレクトリを初期化
131
+ */
132
+ function initializeOutputDirectory(outputPath, emptyOutDir) {
133
+ if (emptyOutDir && (0, node_fs.existsSync)(outputPath)) {
134
+ (0, node_fs.rmSync)(outputPath, { recursive: true });
135
+ (0, node_fs.mkdirSync)(outputPath, { recursive: true });
136
+ }
137
+ try {
138
+ (0, node_fs.mkdirSync)(outputPath, { recursive: true });
139
+ } catch {}
140
+ }
141
+ /**
142
+ * ファイルをディスクに書き込む
143
+ */
144
+ function writeFiles(outputPath, jsCode, tsCode) {
145
+ (0, node_fs.writeFileSync)((0, node_path.join)(outputPath, "resourcepack.mjs"), jsCode, "utf-8");
146
+ (0, node_fs.writeFileSync)((0, node_path.join)(outputPath, "resourcepack.d.ts"), tsCode, "utf-8");
147
+ }
148
+
149
+ //#endregion
150
+ //#region src/codeGenerator.ts
151
+ /**
152
+ * 画像情報から getMcResources 関数を生成
153
+ */
154
+ async function generateGetResourcePackCode({ images, usedIds, itemManager, versionId, itemsUrlMap }) {
155
+ let filteredImages = usedIds ? images.filter((img) => {
156
+ const itemId = "minecraft:" + img.path.split("/").pop()?.replace(/\.[^.]+$/, "");
157
+ return usedIds.has(itemId);
158
+ }) : images;
159
+ const items = /* @__PURE__ */ new Set();
160
+ if (itemManager) {
161
+ (await itemManager.get3DItemsLazy(versionId)).forEach((id) => items.add(id));
162
+ (await itemManager.getItemIds(versionId)).forEach((id) => items.add(id));
163
+ }
164
+ let itemsImports = "";
165
+ const itemHashMap = /* @__PURE__ */ new Map();
166
+ if (itemsUrlMap) {
167
+ let importIndex = 0;
168
+ for (const [key, renderedPath] of itemsUrlMap) {
169
+ const lastUnderscore = key.lastIndexOf("_");
170
+ let itemId;
171
+ let optionHash;
172
+ if (lastUnderscore > 0) {
173
+ const possibleHash = key.substring(lastUnderscore + 1);
174
+ if (!possibleHash.includes(":")) {
175
+ itemId = key.substring(0, lastUnderscore);
176
+ optionHash = possibleHash;
177
+ } else {
178
+ itemId = key;
179
+ optionHash = "default";
180
+ }
181
+ } else {
182
+ itemId = key;
183
+ optionHash = "default";
184
+ }
185
+ const importVarName = `_r${importIndex}`;
186
+ itemsImports += `import ${importVarName} from "${renderedPath}";\n`;
187
+ if (!itemHashMap.has(itemId)) itemHashMap.set(itemId, /* @__PURE__ */ new Map());
188
+ itemHashMap.get(itemId).set(optionHash, importVarName);
189
+ importIndex++;
190
+ }
191
+ }
192
+ const mapEntries = [];
193
+ items.forEach((itemId) => {
194
+ const hashMap = itemHashMap.get(itemId);
195
+ if (hashMap && hashMap.has("default")) mapEntries.push(` "${itemId}": ${hashMap.get("default")}`);
196
+ else mapEntries.push(` "${itemId}": "/@hato810424:mc-resources-plugin/minecraft:${itemId.replace("minecraft:", "")}"`);
197
+ });
198
+ if (itemHashMap.size > 0) {
199
+ for (const [itemId, hashMap] of itemHashMap) if (!filteredImages.some((img) => `minecraft:${img.path.split("/").pop()?.replace(/\.[^.]+$/, "")}` === itemId)) for (const [optionHash, importVar] of hashMap) if (optionHash === "default") mapEntries.push(` "${itemId}": ${importVar}`);
200
+ else mapEntries.push(` "${itemId}_${optionHash}": ${importVar}`);
201
+ }
202
+ const finalMap = mapEntries.join(",\n");
203
+ return `${itemsImports}
204
+
205
+ const resourcePack = {
206
+ ${finalMap}
207
+ };
208
+
209
+ function buildQueryString(params) {
210
+ return new URLSearchParams(
211
+ Object.entries(params).reduce((acc, [key, value]) => {
212
+ if (value !== undefined && value !== null) {
213
+ acc[key] = String(value);
214
+ }
215
+ return acc;
216
+ }, {})
217
+ ).toString();
218
+ }
219
+
220
+ function generateOptionHash(width, height, scale) {
221
+ const parts = [];
222
+ if (width !== undefined) parts.push(\`w\${width}\`);
223
+ if (height !== undefined) parts.push(\`h\${height}\`);
224
+ if (scale !== undefined) parts.push(\`s\${scale}\`);
225
+ return parts.join('_');
226
+ }
227
+
228
+ export function getResourcePack(itemId, options = {}) {
229
+ // ビルド時にレンダリングされた画像を優先的に使用
230
+ if (options.width || options.height || options.scale) {
231
+ const optionHash = generateOptionHash(options.width, options.height, options.scale);
232
+ const hashKey = \`\${itemId}_\${optionHash}\`;
233
+ const hashedUrl = resourcePack[hashKey];
234
+ if (hashedUrl) {
235
+ return hashedUrl;
236
+ }
237
+ }
238
+
239
+ const resourceUrl = resourcePack[itemId] ?? null;
240
+ if (!resourceUrl) return null;
241
+
242
+ const queryString = buildQueryString(options);
243
+ return queryString ? \`\${resourceUrl}?\${queryString}\` : resourceUrl;
244
+ }
245
+
246
+ export default resourcePack;`;
247
+ }
248
+ /**
249
+ * TypeScript型定義を生成
250
+ */
251
+ async function generateTypeDefinitions({ images, itemManager, versionId }) {
252
+ const filteredImages = images;
253
+ const FunctionOptions = `
254
+ type FunctionOptions = {
255
+ width: number;
256
+ height?: number;
257
+ scale?: number;
258
+ };
259
+ `.replace(/^\n/, "").replace(/[ \t]+$/, "");
260
+ if (itemManager && versionId) {
261
+ let itemMap = /* @__PURE__ */ new Set();
262
+ let items = /* @__PURE__ */ new Set();
263
+ (await itemManager.get3DItemsLazy(versionId)).forEach((id) => items.add(id));
264
+ (await itemManager.getItemIds(versionId)).forEach((id) => items.add(id));
265
+ const itemMapPromises = filteredImages.map(async (img) => {
266
+ const itemId = "minecraft:" + img.path.split("/").pop()?.replace(/\.[^.]+$/, "");
267
+ if (itemId) try {
268
+ if (await itemManager.getItemTexturePath(versionId, itemId)) return itemId;
269
+ } catch (error) {}
270
+ return null;
271
+ });
272
+ const results = await Promise.all(itemMapPromises);
273
+ for (const itemId of results) if (itemId) itemMap.add(itemId);
274
+ const allItems = new Set([...itemMap, ...items]);
275
+ const Items = itemMap;
276
+ const renderItems = items;
277
+ const hasFunctionSignature = allItems.size > 0;
278
+ return (0, node_util.format)(`
279
+ type ItemId = %s;
280
+ type RenderingItemId = %s;
281
+ %s
282
+ %s
283
+ export const resourcePack: Readonly<Record<ItemId | RenderingItemId, string>>;
284
+ export default resourcePack;
285
+ `.replace(/^\n/, "").replace(/[ \t]+$/, ""), Items.size > 0 ? Array.from(Items).map((item) => `"${item}"`).join(" | ") : "\"\"", renderItems.size > 0 ? Array.from(renderItems).map((item) => `"${item}"`).join(" | ") : "\"\"", FunctionOptions, (hasFunctionSignature ? `
286
+ export function getResourcePack(itemId: ItemId): string;
287
+ export function getResourcePack(itemId: RenderingItemId, options?: FunctionOptions): string;
288
+ ` : "").replace(/^\n/, "").replace(/[ \t]+$/, ""));
289
+ } else return (0, node_util.format)(`
290
+ type ItemId = %s;
291
+ %s
292
+ %s
293
+ export const resourcePack: Readonly<Record<ItemId, string>>;
294
+ export default resourcePack;
295
+ `.replace(/^\n/, "").replace(/[ \t]+$/, ""), filteredImages.length > 0 ? filteredImages.map((img) => `"${img.path}"`).join(" | ") : "\"\"", FunctionOptions, filteredImages.length > 0 ? "export function getResourcePack(path: ItemId): string;" : "");
296
+ }
297
+
298
+ //#endregion
299
+ //#region package.json
300
+ var package_default = {
301
+ name: "@hato810424/mc-resources-plugin",
302
+ type: "module",
303
+ scripts: {
304
+ "build": "tsdown",
305
+ "dev": "concurrently -c cyan,yellow --names esm,cjs \"tsdown --watch --format esm\" \"tsdown --watch --format cjs\"",
306
+ "test": "vitest",
307
+ "typecheck": "tsc --noEmit"
308
+ },
309
+ devDependencies: {
310
+ "@docusaurus/types": "^3.9.2",
311
+ "@types/node": "^25.0.10",
312
+ "concurrently": "^9.2.1",
313
+ "tsdown": "^0.18.4",
314
+ "tsx": "^4.21.0",
315
+ "typescript": "^5.9.3",
316
+ "vite": "^7.3.1",
317
+ "vitest": "^4.0.16",
318
+ "webpack": "^5.104.1",
319
+ "webpack-dev-server": "^5.2.3"
320
+ },
321
+ dependencies: {
322
+ "canvas": "^3.2.1",
323
+ "chalk": "^5.6.2",
324
+ "find-cache-directory": "^6.0.0",
325
+ "node-stream-zip": "^1.15.0",
326
+ "sharp": "^0.34.5",
327
+ "zod": "^4.3.6"
328
+ }
329
+ };
330
+
331
+ //#endregion
332
+ //#region src/logger.ts
333
+ const PREFIX = chalk.default.cyan(`[mc-resources-plugin ${package_default.version}]`) + " ";
334
+ let logLevel = CONFIG.LOG_LEVEL;
335
+ const logger = {
336
+ setLogLevel: (level) => {
337
+ logLevel = level === void 0 ? CONFIG.LOG_LEVEL : level;
338
+ },
339
+ info: (message) => {
340
+ if (logLevel === "error") return;
341
+ console.log(PREFIX + message);
342
+ },
343
+ warn: (message) => {
344
+ console.warn(PREFIX + chalk.default.bgRed("WARN") + " " + message);
345
+ },
346
+ debug: (message) => {
347
+ if (logLevel === "debug") console.debug(PREFIX + chalk.default.bgBlue("DEBUG") + " " + message);
348
+ },
349
+ error: (message) => {
350
+ console.error(PREFIX + chalk.default.bgRed("ERROR") + " " + message);
351
+ }
352
+ };
353
+ var logger_default = logger;
354
+
355
+ //#endregion
356
+ //#region src/mojang/minecraftVersionManager.ts
357
+ const MOJANG_PATHS = {
358
+ manifest: "https://launchermeta.mojang.com/mc/game/version_manifest.json",
359
+ versionDetails: "version_details",
360
+ clientJars: "version_details",
361
+ langFiles: "lang_files"
362
+ };
363
+ const CACHE_EXPIRY_MS = 1e3 * 60 * 60 * 24 * 30;
364
+ var MinecraftVersionManager = class {
365
+ cacheDir;
366
+ assetsFetchingTasks = /* @__PURE__ */ new Map();
367
+ constructor(cacheDir) {
368
+ this.cacheDir = cacheDir;
369
+ this.ensureCacheDir();
370
+ }
371
+ ensureCacheDir() {
372
+ if (!(0, fs.existsSync)(this.cacheDir)) (0, fs.mkdirSync)(this.cacheDir, { recursive: true });
373
+ const detailsDir = (0, path.join)(this.cacheDir, MOJANG_PATHS.versionDetails);
374
+ if (!(0, fs.existsSync)(detailsDir)) (0, fs.mkdirSync)(detailsDir, { recursive: true });
375
+ const clientJarsDir = (0, path.join)(this.cacheDir, MOJANG_PATHS.clientJars);
376
+ if (!(0, fs.existsSync)(clientJarsDir)) (0, fs.mkdirSync)(clientJarsDir, { recursive: true });
377
+ }
378
+ getManifestCachePath() {
379
+ return (0, path.join)(this.cacheDir, "version_manifest.json");
380
+ }
381
+ isCacheExpired(filePath) {
382
+ try {
383
+ const stats = require("fs").statSync(filePath);
384
+ return Date.now() - stats.mtimeMs > CACHE_EXPIRY_MS;
385
+ } catch {
386
+ return true;
387
+ }
388
+ }
389
+ async getVersionManifest(forceRefresh = false) {
390
+ const cachePath = this.getManifestCachePath();
391
+ if (!forceRefresh && (0, fs.existsSync)(cachePath) && !this.isCacheExpired(cachePath)) try {
392
+ const cachedData = (0, fs.readFileSync)(cachePath, "utf-8");
393
+ return JSON.parse(cachedData);
394
+ } catch (error) {
395
+ logger_default.warn(`Failed to read version manifest cache: ${error}`);
396
+ }
397
+ try {
398
+ logger_default.info("Fetching version manifest from Mojang...");
399
+ const response = await fetch(MOJANG_PATHS.manifest);
400
+ if (!response.ok) throw new Error(`Failed to fetch manifest: ${response.statusText}`);
401
+ const manifest = await response.json();
402
+ (0, fs.writeFileSync)(cachePath, JSON.stringify(manifest, null, 2));
403
+ logger_default.info("Version manifest cached successfully");
404
+ return manifest;
405
+ } catch (error) {
406
+ logger_default.error(`Failed to fetch version manifest: ${error}`);
407
+ if ((0, fs.existsSync)(cachePath)) try {
408
+ const cachedData = (0, fs.readFileSync)(cachePath, "utf-8");
409
+ logger_default.warn("Using stale cache due to fetch failure");
410
+ return JSON.parse(cachedData);
411
+ } catch {
412
+ throw new Error("Failed to fetch manifest and no valid cache available");
413
+ }
414
+ throw error;
415
+ }
416
+ }
417
+ async getVersionDetails(versionId, forceRefresh = false) {
418
+ const manifest = await this.getVersionManifest();
419
+ if (versionId === "latest") versionId = manifest.latest.release;
420
+ const versionInfo = manifest.versions.find((v) => v.id === versionId);
421
+ if (!versionInfo) throw new Error(`Version ${versionId} not found in manifest`);
422
+ const cachePath = (0, path.join)(this.cacheDir, MOJANG_PATHS.versionDetails, `${versionId}.json`);
423
+ if (!forceRefresh && (0, fs.existsSync)(cachePath) && !this.isCacheExpired(cachePath)) try {
424
+ const cachedData = (0, fs.readFileSync)(cachePath, "utf-8");
425
+ return JSON.parse(cachedData);
426
+ } catch (error) {
427
+ logger_default.warn(`Failed to read version details cache: ${error}`);
428
+ }
429
+ try {
430
+ logger_default.info(`Fetching details for version ${versionId}...`);
431
+ const response = await fetch(versionInfo.url);
432
+ if (!response.ok) throw new Error(`Failed to fetch version details: ${response.statusText}`);
433
+ const details = await response.json();
434
+ (0, fs.writeFileSync)(cachePath, JSON.stringify(details, null, 2));
435
+ logger_default.info(`Version details for ${versionId} cached successfully`);
436
+ return details;
437
+ } catch (error) {
438
+ logger_default.error(`Failed to fetch version details for ${versionId}: ${error}`);
439
+ if ((0, fs.existsSync)(cachePath)) try {
440
+ const cachedData = (0, fs.readFileSync)(cachePath, "utf-8");
441
+ logger_default.warn(`Using stale cache for ${versionId} due to fetch failure`);
442
+ return JSON.parse(cachedData);
443
+ } catch {
444
+ throw new Error(`Failed to fetch version details and no valid cache available for ${versionId}`);
445
+ }
446
+ throw error;
447
+ }
448
+ }
449
+ async getClientJar(versionId, forceRefresh = false) {
450
+ const versionDetails = await this.getVersionDetails(versionId);
451
+ const clientJarPath = (0, path.join)(this.cacheDir, MOJANG_PATHS.clientJars, `${versionDetails.id}.jar`);
452
+ if (!forceRefresh && (0, fs.existsSync)(clientJarPath) && !this.isCacheExpired(clientJarPath)) return clientJarPath;
453
+ try {
454
+ logger_default.info(`Downloading client jar for version ${versionDetails.id}...`);
455
+ const clientJarUrl = versionDetails.downloads.client.url;
456
+ const response = await fetch(clientJarUrl);
457
+ if (!response.ok) throw new Error(`Failed to download client jar: ${response.statusText}`);
458
+ const arrayBuffer = await response.arrayBuffer();
459
+ (0, fs.writeFileSync)(clientJarPath, Buffer.from(arrayBuffer));
460
+ logger_default.info(`Client jar for version ${versionDetails.id} cached successfully`);
461
+ return clientJarPath;
462
+ } catch (error) {
463
+ logger_default.error(`Failed to download client jar for ${versionDetails.id}: ${error}`);
464
+ if ((0, fs.existsSync)(clientJarPath)) {
465
+ logger_default.warn(`Using stale cache for ${versionDetails.id} due to download failure`);
466
+ return clientJarPath;
467
+ }
468
+ throw error;
469
+ }
470
+ }
471
+ async getAssets(versionId, forceRefresh = false) {
472
+ const taskKey = `${versionId}:${forceRefresh}`;
473
+ if (this.assetsFetchingTasks.has(taskKey)) return this.assetsFetchingTasks.get(taskKey);
474
+ const assetsPromise = (async () => {
475
+ const versionDetails = await this.getVersionDetails(versionId);
476
+ const assetsDirPath = (0, path.join)(this.cacheDir, MOJANG_PATHS.versionDetails, versionDetails.id);
477
+ if (!forceRefresh && (0, fs.existsSync)(assetsDirPath) && !this.isCacheExpired(assetsDirPath)) return assetsDirPath;
478
+ try {
479
+ const jarPath = await this.getClientJar(versionId, forceRefresh);
480
+ const zip = new node_stream_zip.default.async({ file: jarPath });
481
+ if ((0, fs.existsSync)(assetsDirPath)) (0, fs.rmSync)(assetsDirPath, { recursive: true });
482
+ (0, fs.mkdirSync)(assetsDirPath, { recursive: true });
483
+ logger_default.info(`Extracting assets for version ${versionDetails.id}...`);
484
+ const entries = await zip.entries();
485
+ for (const entry of Object.values(entries)) if (entry.name.startsWith("assets/minecraft")) {
486
+ const destPath = (0, path.join)(assetsDirPath, entry.name);
487
+ if (entry.isDirectory) (0, fs.mkdirSync)(destPath, { recursive: true });
488
+ else {
489
+ (0, fs.mkdirSync)((0, path.dirname)(destPath), { recursive: true });
490
+ await zip.extract(entry.name, destPath);
491
+ }
492
+ }
493
+ await zip.close();
494
+ logger_default.info(`Assets for version ${versionDetails.id} extracted successfully`);
495
+ return assetsDirPath;
496
+ } catch (error) {
497
+ logger_default.error(`Failed to extract assets for ${versionDetails.id}: ${error}`);
498
+ if ((0, fs.existsSync)(assetsDirPath)) {
499
+ logger_default.warn(`Using stale assets cache for ${versionDetails.id} due to extraction failure`);
500
+ return assetsDirPath;
501
+ }
502
+ throw error;
503
+ }
504
+ })().finally(() => {
505
+ this.assetsFetchingTasks.delete(taskKey);
506
+ });
507
+ this.assetsFetchingTasks.set(taskKey, assetsPromise);
508
+ return assetsPromise;
509
+ }
510
+ async getLangFile(versionId, lang) {
511
+ if (lang === "en_us") return (0, path.join)(await this.getAssets(versionId), `assets/minecraft/lang/en_us.json`);
512
+ const langFilePath = (0, path.join)(this.cacheDir, MOJANG_PATHS.langFiles, `${versionId}_${lang}.json`);
513
+ if ((0, fs.existsSync)(langFilePath) && !this.isCacheExpired(langFilePath)) return langFilePath;
514
+ try {
515
+ logger_default.info(`Downloading language file: ${versionId}/${lang}`);
516
+ const assetIndexUrl = (await this.getVersionDetails(versionId)).assetIndex.url;
517
+ const assetIndexResponse = await fetch(assetIndexUrl);
518
+ if (!assetIndexResponse.ok) throw new Error(`Failed to fetch asset index: ${assetIndexResponse.statusText}`);
519
+ const assetIndex = await assetIndexResponse.json();
520
+ const langKey = `minecraft/lang/${lang}.json`;
521
+ const langObject = assetIndex.objects[langKey];
522
+ if (!langObject) throw new Error(`Language file not found in asset index: ${langKey}`);
523
+ const langHash = langObject.hash;
524
+ const langFileUrl = `https://resources.download.minecraft.net/${langHash.substring(0, 2)}/${langHash}`;
525
+ const langResponse = await fetch(langFileUrl);
526
+ if (!langResponse.ok) throw new Error(`Failed to download language file: ${langResponse.statusText}`);
527
+ const langDir = (0, path.join)(this.cacheDir, "lang_files");
528
+ if (!(0, fs.existsSync)(langDir)) (0, fs.mkdirSync)(langDir, { recursive: true });
529
+ (0, fs.writeFileSync)(langFilePath, await langResponse.text());
530
+ logger_default.info(`Language file cached: ${versionId}/${lang}`);
531
+ return langFilePath;
532
+ } catch (error) {
533
+ logger_default.error(`Failed to download language file ${lang} for ${versionId}: ${error}`);
534
+ throw error;
535
+ }
536
+ }
537
+ async getLatestRelease() {
538
+ return (await this.getVersionManifest()).latest.release;
539
+ }
540
+ async getLatestSnapshot() {
541
+ return (await this.getVersionManifest()).latest.snapshot;
542
+ }
543
+ clearCache() {
544
+ const cachePath = this.getManifestCachePath();
545
+ if ((0, fs.existsSync)(cachePath)) {
546
+ require("fs").unlinkSync(cachePath);
547
+ logger_default.info("Version manifest cache cleared");
548
+ }
549
+ }
550
+ };
551
+ function createVersionManager(cacheDir) {
552
+ return new MinecraftVersionManager(cacheDir);
553
+ }
554
+
555
+ //#endregion
556
+ //#region src/render/paths.ts
557
+ /**
558
+ * Minecraft リソースパックの標準ディレクトリ構造定義
559
+ */
560
+ const MINECRAFT_PATHS = {
561
+ assets: "assets",
562
+ minecraft: "assets/minecraft",
563
+ models: "assets/minecraft/models",
564
+ modelBlocks: "assets/minecraft/models/block",
565
+ textures: "assets/minecraft/textures",
566
+ textureBlocks: "assets/minecraft/textures/block",
567
+ textureItems: "assets/minecraft/textures/item",
568
+ items: "assets/minecraft/items"
569
+ };
570
+ const TEXTURE_EXTENSIONS = [
571
+ ".png",
572
+ ".jpg",
573
+ ".jpeg"
574
+ ];
575
+ /**
576
+ * Minecraft パス正規化・解析のユーティリティ
577
+ */
578
+ var MinecraftPathResolver = class {
579
+ constructor(resourcePackPath) {
580
+ this.resourcePackPath = resourcePackPath;
581
+ }
582
+ /**
583
+ * モデルパスを正規化
584
+ * 例: minecraft:block/cube -> block/cube
585
+ * block/cube -> block/cube
586
+ * /path/to/block/cube.json -> block/cube
587
+ */
588
+ normalizeModelPath(modelPath) {
589
+ let normalized = modelPath.replace(/^minecraft:/, "").replace(/\.json$/, "");
590
+ if (!normalized.startsWith("block/") && !normalized.startsWith("item/")) normalized = `block/${normalized}`;
591
+ return normalized;
592
+ }
593
+ /**
594
+ * テクスチャ参照を正規化(相対パスのみ返す)
595
+ * 例: minecraft:block/stone -> block/stone.png
596
+ * stone -> block/stone.png
597
+ * item/apple -> item/apple.png
598
+ */
599
+ normalizeTexturePath(texturePath) {
600
+ let normalized = texturePath.replace(/^minecraft:/, "").replace(/^textures\//, "");
601
+ if (!normalized.startsWith("block/") && !normalized.startsWith("item/")) normalized = `block/${normalized}`;
602
+ if (!TEXTURE_EXTENSIONS.some((ext) => normalized.endsWith(ext))) normalized += ".png";
603
+ return normalized;
604
+ }
605
+ /**
606
+ * リソースパック内の完全なモデルファイルパスを取得
607
+ */
608
+ getModelFilePath(modelPath) {
609
+ const normalized = this.normalizeModelPath(modelPath);
610
+ return (0, path.join)(this.resourcePackPath, MINECRAFT_PATHS.models, `${normalized}.json`);
611
+ }
612
+ /**
613
+ * リソースパック内の完全なテクスチャファイルパスを取得
614
+ */
615
+ getTextureFilePath(texturePath) {
616
+ const normalized = this.normalizeTexturePath(texturePath);
617
+ return (0, path.join)(this.resourcePackPath, MINECRAFT_PATHS.textures, normalized);
618
+ }
619
+ /**
620
+ * ブロックモデルディレクトリの完全パスを取得
621
+ */
622
+ getBlockModelsDir() {
623
+ return (0, path.join)(this.resourcePackPath, MINECRAFT_PATHS.modelBlocks);
624
+ }
625
+ /**
626
+ * アイテム定義ディレクトリの完全パスを取得
627
+ */
628
+ getItemsDir() {
629
+ return (0, path.join)(this.resourcePackPath, MINECRAFT_PATHS.items);
630
+ }
631
+ /**
632
+ * モデルの基本ディレクトリを取得
633
+ */
634
+ getModelsBaseDir() {
635
+ return (0, path.join)(this.resourcePackPath, MINECRAFT_PATHS.models);
636
+ }
637
+ /**
638
+ * テクスチャの基本ディレクトリを取得
639
+ */
640
+ getTexturesBaseDir() {
641
+ return (0, path.join)(this.resourcePackPath, MINECRAFT_PATHS.textures);
642
+ }
643
+ };
644
+
645
+ //#endregion
646
+ //#region src/render/Renderer.ts
647
+ /**
648
+ * Minecraft のティントカラーマップ(ブロック別)
649
+ * tintindex に対応するRGB値を定義
650
+ */
651
+ const TINT_COLORS = {
652
+ grass_block: { 0: [
653
+ 127,
654
+ 178,
655
+ 56
656
+ ] },
657
+ grass: { 0: [
658
+ 127,
659
+ 178,
660
+ 56
661
+ ] },
662
+ tall_grass: { 0: [
663
+ 127,
664
+ 178,
665
+ 56
666
+ ] },
667
+ seagrass: { 0: [
668
+ 127,
669
+ 178,
670
+ 56
671
+ ] },
672
+ vine: { 0: [
673
+ 127,
674
+ 178,
675
+ 56
676
+ ] },
677
+ oak_leaves: { 0: [
678
+ 127,
679
+ 178,
680
+ 56
681
+ ] },
682
+ birch_leaves: { 0: [
683
+ 128,
684
+ 168,
685
+ 63
686
+ ] },
687
+ spruce_leaves: { 0: [
688
+ 95,
689
+ 130,
690
+ 60
691
+ ] },
692
+ jungle_leaves: { 0: [
693
+ 97,
694
+ 163,
695
+ 43
696
+ ] },
697
+ acacia_leaves: { 0: [
698
+ 155,
699
+ 178,
700
+ 33
701
+ ] },
702
+ dark_oak_leaves: { 0: [
703
+ 103,
704
+ 117,
705
+ 53
706
+ ] },
707
+ cocoa: { 0: [
708
+ 128,
709
+ 92,
710
+ 63
711
+ ] },
712
+ cactus: { 0: [
713
+ 95,
714
+ 160,
715
+ 54
716
+ ] },
717
+ water: { 0: [
718
+ 63,
719
+ 127,
720
+ 255
721
+ ] },
722
+ water_cauldron: { 0: [
723
+ 63,
724
+ 127,
725
+ 255
726
+ ] },
727
+ potion: { 0: [
728
+ 127,
729
+ 178,
730
+ 56
731
+ ] },
732
+ tipped_arrow: { 0: [
733
+ 127,
734
+ 178,
735
+ 56
736
+ ] },
737
+ spawn_egg: { 0: [
738
+ 78,
739
+ 78,
740
+ 78
741
+ ] }
742
+ };
743
+ var MinecraftBlockRenderer = class {
744
+ modelsCache = /* @__PURE__ */ new Map();
745
+ texturesCache = /* @__PURE__ */ new Map();
746
+ tintedTexturesCache = /* @__PURE__ */ new Map();
747
+ resourcePackPathResolver;
748
+ modelPathResolver;
749
+ constructor(resourcePackPath, modelPath) {
750
+ this.resourcePackPathResolver = new MinecraftPathResolver(resourcePackPath);
751
+ this.modelPathResolver = new MinecraftPathResolver(modelPath ?? resourcePackPath);
752
+ }
753
+ /**
754
+ * ブロック名からモデルパスを解決
755
+ */
756
+ async resolveBlockModelPath(blockName) {
757
+ return `block/${blockName.replace(/^minecraft:/, "")}`;
758
+ }
759
+ /**
760
+ * モデルファイルを読み込んで、parent継承を解決する
761
+ */
762
+ async loadModel(modelPath) {
763
+ const fullPath = this.modelPathResolver.getModelFilePath(modelPath);
764
+ if (modelPath === "builtin/generated" || modelPath === "builtin/entity") return {};
765
+ if (this.modelsCache.has(fullPath)) return this.modelsCache.get(fullPath);
766
+ const content = await (0, fs_promises.readFile)(fullPath, "utf-8");
767
+ const model = JSON.parse(content);
768
+ if (model.parent) {
769
+ if (model.parent === "builtin/generated" || model.parent === "builtin/entity") {
770
+ this.modelsCache.set(fullPath, model);
771
+ return model;
772
+ }
773
+ const parentModel = await this.loadModel(model.parent);
774
+ const merged = {
775
+ ...parentModel,
776
+ ...model,
777
+ textures: {
778
+ ...parentModel.textures,
779
+ ...model.textures
780
+ },
781
+ elements: model.elements || parentModel.elements
782
+ };
783
+ this.modelsCache.set(fullPath, merged);
784
+ return merged;
785
+ }
786
+ this.modelsCache.set(fullPath, model);
787
+ return model;
788
+ }
789
+ /**
790
+ * テクスチャ参照を解決(#texture_nameのような参照を実際のパスに変換)
791
+ */
792
+ resolveTexture(texture, textures, visited = /* @__PURE__ */ new Set()) {
793
+ if (texture.startsWith("#")) {
794
+ const key = texture.slice(1);
795
+ if (visited.has(key)) return texture;
796
+ visited.add(key);
797
+ if (textures[key]) return this.resolveTexture(textures[key], textures, visited);
798
+ }
799
+ return this.resourcePackPathResolver.normalizeTexturePath(texture);
800
+ }
801
+ /**
802
+ * テクスチャ画像を読み込む(上下面は水平反転、壁面は反転なし)
803
+ */
804
+ async loadTexture(texturePath, faceName) {
805
+ const shouldFlipX = faceName === "up" || faceName === "down";
806
+ const cacheKey = shouldFlipX ? `${texturePath}:flipX` : texturePath;
807
+ if (this.texturesCache.has(cacheKey)) return this.texturesCache.get(cacheKey);
808
+ const fullPath = this.resourcePackPathResolver.getTextureFilePath(texturePath);
809
+ try {
810
+ const image = await (0, canvas.loadImage)(fullPath);
811
+ const canvas$1 = (0, canvas.createCanvas)(image.width, image.height);
812
+ const ctx = canvas$1.getContext("2d");
813
+ if (shouldFlipX) {
814
+ ctx.scale(-1, 1);
815
+ ctx.drawImage(image, -image.width, 0);
816
+ } else ctx.drawImage(image, 0, 0);
817
+ this.texturesCache.set(cacheKey, canvas$1);
818
+ return canvas$1;
819
+ } catch (error) {
820
+ console.warn(`Failed to load texture: ${fullPath}`);
821
+ const canvas$1 = (0, canvas.createCanvas)(16, 16);
822
+ const ctx = canvas$1.getContext("2d");
823
+ ctx.fillStyle = "#f800f8";
824
+ ctx.fillRect(0, 0, 16, 16);
825
+ ctx.fillStyle = "#000000";
826
+ ctx.fillRect(0, 0, 8, 8);
827
+ ctx.fillRect(8, 8, 8, 8);
828
+ this.texturesCache.set(cacheKey, canvas$1);
829
+ return canvas$1;
830
+ }
831
+ }
832
+ /**
833
+ * 3D座標を2D画面座標に投影(アイソメトリック風)
834
+ */
835
+ project(point, cosX, sinX, cosY, sinY, cosZ, sinZ, scale) {
836
+ let { x, y, z } = point;
837
+ const x1 = x * cosY - z * sinY;
838
+ const z1 = x * sinY + z * cosY;
839
+ const y1 = y * cosX - z1 * sinX;
840
+ const z2 = y * sinX + z1 * cosX;
841
+ const x2 = x1 * cosZ - y1 * sinZ;
842
+ const y2 = x1 * sinZ + y1 * cosZ;
843
+ return {
844
+ x: x2 * scale,
845
+ y: y2 * scale,
846
+ z: z2
847
+ };
848
+ }
849
+ /**
850
+ * テクスチャ付きの四角形を描画する(4頂点パス)
851
+ * @param ctx Canvas 2D コンテキスト
852
+ * @param centerX キャンバス中心X
853
+ * @param centerY キャンバス中心Y
854
+ * @param texture ロード済みのImageオブジェクト
855
+ * @param vertices 投影済みの2D頂点 4個 (z含む)
856
+ * @param uvCoords UV座標 4個 (0-16)
857
+ * @param passes テクスチャ貼り付けのパス数(1 or 2)
858
+ */
859
+ drawTexturedQuad(ctx, centerX, centerY, texture, vertices, uvCoords, passes = 1) {
860
+ if (vertices.length !== 4 || uvCoords.length !== 4) return;
861
+ ctx.save();
862
+ const offset = .3;
863
+ const quadCenterX = vertices.reduce((sum, p) => sum + p.x, 0) / 4;
864
+ const quadCenterY = vertices.reduce((sum, p) => sum + p.y, 0) / 4;
865
+ const expand = (p) => {
866
+ const dx = p.x - quadCenterX;
867
+ const dy = p.y - quadCenterY;
868
+ const mag = Math.sqrt(dx * dx + dy * dy);
869
+ return {
870
+ x: p.x + dx / mag * offset,
871
+ y: p.y + dy / mag * offset * 3
872
+ };
873
+ };
874
+ const expandedVertices = vertices.map(expand);
875
+ ctx.beginPath();
876
+ ctx.moveTo(centerX + expandedVertices[0].x, centerY - expandedVertices[0].y);
877
+ ctx.lineTo(centerX + expandedVertices[1].x, centerY - expandedVertices[1].y);
878
+ ctx.lineTo(centerX + expandedVertices[2].x, centerY - expandedVertices[2].y);
879
+ ctx.lineTo(centerX + expandedVertices[3].x, centerY - expandedVertices[3].y);
880
+ ctx.closePath();
881
+ ctx.clip();
882
+ const p0 = vertices[0], p1 = vertices[1], p2 = vertices[2];
883
+ const u0 = uvCoords[0].u, v0 = uvCoords[0].v;
884
+ const u1 = uvCoords[1].u, v1 = uvCoords[1].v;
885
+ const u2 = uvCoords[2].u, v2 = uvCoords[2].v;
886
+ const delta = (u1 - u0) * (v2 - v0) - (u2 - u0) * (v1 - v0);
887
+ if (Math.abs(delta) > 1e-4) {
888
+ const m11 = ((p1.x - p0.x) * (v2 - v0) - (p2.x - p0.x) * (v1 - v0)) / delta;
889
+ const m12 = -((p1.y - p0.y) * (v2 - v0) - (p2.y - p0.y) * (v1 - v0)) / delta;
890
+ const m21 = ((p2.x - p0.x) * (u1 - u0) - (p1.x - p0.x) * (u2 - u0)) / delta;
891
+ const m22 = -((p2.y - p0.y) * (u1 - u0) - (p1.y - p0.y) * (u2 - u0)) / delta;
892
+ const dx = centerX + p0.x - (m11 * u0 + m21 * v0);
893
+ const dy = centerY - p0.y - (m12 * u0 + m22 * v0);
894
+ ctx.setTransform(m11, m12, m21, m22, dx, dy);
895
+ ctx.drawImage(texture, 0, 0);
896
+ if (passes === 2) {
897
+ const p3 = vertices[3];
898
+ const u3 = uvCoords[3].u, v3 = uvCoords[3].v;
899
+ const delta2 = (u2 - u0) * (v3 - v0) - (u3 - u0) * (v2 - v0);
900
+ if (Math.abs(delta2) > 1e-4) {
901
+ const m11_2 = ((p2.x - p0.x) * (v3 - v0) - (p3.x - p0.x) * (v2 - v0)) / delta2;
902
+ const m12_2 = -((p2.y - p0.y) * (v3 - v0) - (p3.y - p0.y) * (v2 - v0)) / delta2;
903
+ const m21_2 = ((p3.x - p0.x) * (u2 - u0) - (p2.x - p0.x) * (u3 - u0)) / delta2;
904
+ const m22_2 = -((p3.y - p0.y) * (u2 - u0) - (p2.y - p0.y) * (u3 - u0)) / delta2;
905
+ const dx2 = centerX + p0.x - (m11_2 * u0 + m21_2 * v0);
906
+ const dy2 = centerY - p0.y - (m12_2 * u0 + m22_2 * v0);
907
+ ctx.setTransform(m11_2, m12_2, m21_2, m22_2, dx2, dy2);
908
+ ctx.drawImage(texture, 0, 0);
909
+ }
910
+ }
911
+ }
912
+ ctx.restore();
913
+ }
914
+ /**
915
+ * テクスチャにティント色を適用(透明部分は保護)
916
+ */
917
+ async applyTint(texture, tintColor) {
918
+ const [r, g, b] = tintColor;
919
+ const tintedCanvas = (0, canvas.createCanvas)(texture.width, texture.height);
920
+ const tintCtx = tintedCanvas.getContext("2d");
921
+ tintCtx.fillStyle = `rgb(${r},${g},${b})`;
922
+ tintCtx.fillRect(0, 0, texture.width, texture.height);
923
+ tintCtx.globalCompositeOperation = "multiply";
924
+ tintCtx.drawImage(texture, 0, 0);
925
+ const finalCanvas = (0, canvas.createCanvas)(texture.width, texture.height);
926
+ const finalCtx = finalCanvas.getContext("2d");
927
+ finalCtx.drawImage(texture, 0, 0);
928
+ finalCtx.globalCompositeOperation = "source-in";
929
+ finalCtx.drawImage(tintedCanvas, 0, 0);
930
+ return finalCanvas;
931
+ }
932
+ /**
933
+ * モデル名からティントカラーを取得
934
+ * 未知のブロックの場合はデフォルト緑色を返す
935
+ */
936
+ getTintColor(modelPath, tintindex) {
937
+ if (tintindex === void 0) return null;
938
+ const nameParts = modelPath.split("/");
939
+ const type$1 = nameParts[0];
940
+ const baseName = nameParts.pop()?.replace(/\.json$/, "");
941
+ if (!baseName) return null;
942
+ const colorMap = TINT_COLORS[baseName];
943
+ if (!colorMap) {
944
+ logger_default.debug(`[Tint] Unknown ${type$1} "${baseName}" with tintindex ${tintindex}, using default green tint`);
945
+ return [
946
+ 127,
947
+ 178,
948
+ 56
949
+ ];
950
+ }
951
+ return colorMap[tintindex] || null;
952
+ }
953
+ /**
954
+ * ブロックモデルをレンダリング
955
+ */
956
+ async renderBlock(modelPath, outputPath, options) {
957
+ const { width, height, rotation = CONFIG.ROTATION } = options;
958
+ const scale = options.scale ?? Math.round((height > width ? width : height) / 25.6);
959
+ let resolvedModelPath = modelPath;
960
+ if (modelPath.startsWith("block/")) {
961
+ const blockName = modelPath.replace(/^block\//, "");
962
+ const resolvedPath = await this.resolveBlockModelPath(blockName);
963
+ if (resolvedPath && resolvedPath !== `block/${blockName}`) resolvedModelPath = resolvedPath;
964
+ }
965
+ const normalizedModelPath = this.modelPathResolver.normalizeModelPath(resolvedModelPath);
966
+ const model = await this.loadModel(normalizedModelPath);
967
+ if (!model.elements) throw new Error("Model has no elements to render");
968
+ const canvas$1 = (0, canvas.createCanvas)(width, height);
969
+ const ctx = canvas$1.getContext("2d");
970
+ ctx.imageSmoothingEnabled = false;
971
+ ctx.clearRect(0, 0, width, height);
972
+ const centerX = width / 2;
973
+ const centerY = height / 2;
974
+ const [rotX, rotY, rotZ] = rotation.map((deg) => deg * Math.PI / 180);
975
+ const cosX = Math.cos(rotX), sinX = Math.sin(rotX);
976
+ const cosY = Math.cos(rotY), sinY = Math.sin(rotY);
977
+ const cosZ = Math.cos(rotZ), sinZ = Math.sin(rotZ);
978
+ const allFacesToRender = [];
979
+ for (const element of model.elements) for (const faceName of [
980
+ "down",
981
+ "up",
982
+ "north",
983
+ "south",
984
+ "west",
985
+ "east"
986
+ ]) {
987
+ const face = element.faces[faceName];
988
+ if (!face) continue;
989
+ const from = element.from.map((v) => v - 8);
990
+ const to = element.to.map((v) => v - 8);
991
+ let vertices = [];
992
+ switch (faceName) {
993
+ case "up":
994
+ vertices = [
995
+ {
996
+ x: from[0],
997
+ y: to[1],
998
+ z: to[2]
999
+ },
1000
+ {
1001
+ x: to[0],
1002
+ y: to[1],
1003
+ z: to[2]
1004
+ },
1005
+ {
1006
+ x: to[0],
1007
+ y: to[1],
1008
+ z: from[2]
1009
+ },
1010
+ {
1011
+ x: from[0],
1012
+ y: to[1],
1013
+ z: from[2]
1014
+ }
1015
+ ];
1016
+ break;
1017
+ case "down":
1018
+ vertices = [
1019
+ {
1020
+ x: from[0],
1021
+ y: from[1],
1022
+ z: from[2]
1023
+ },
1024
+ {
1025
+ x: to[0],
1026
+ y: from[1],
1027
+ z: from[2]
1028
+ },
1029
+ {
1030
+ x: to[0],
1031
+ y: from[1],
1032
+ z: to[2]
1033
+ },
1034
+ {
1035
+ x: from[0],
1036
+ y: from[1],
1037
+ z: to[2]
1038
+ }
1039
+ ];
1040
+ break;
1041
+ case "north":
1042
+ vertices = [
1043
+ {
1044
+ x: to[0],
1045
+ y: from[1],
1046
+ z: from[2]
1047
+ },
1048
+ {
1049
+ x: from[0],
1050
+ y: from[1],
1051
+ z: from[2]
1052
+ },
1053
+ {
1054
+ x: from[0],
1055
+ y: to[1],
1056
+ z: from[2]
1057
+ },
1058
+ {
1059
+ x: to[0],
1060
+ y: to[1],
1061
+ z: from[2]
1062
+ }
1063
+ ];
1064
+ break;
1065
+ case "south":
1066
+ vertices = [
1067
+ {
1068
+ x: from[0],
1069
+ y: from[1],
1070
+ z: to[2]
1071
+ },
1072
+ {
1073
+ x: to[0],
1074
+ y: from[1],
1075
+ z: to[2]
1076
+ },
1077
+ {
1078
+ x: to[0],
1079
+ y: to[1],
1080
+ z: to[2]
1081
+ },
1082
+ {
1083
+ x: from[0],
1084
+ y: to[1],
1085
+ z: to[2]
1086
+ }
1087
+ ];
1088
+ break;
1089
+ case "west":
1090
+ vertices = [
1091
+ {
1092
+ x: from[0],
1093
+ y: from[1],
1094
+ z: from[2]
1095
+ },
1096
+ {
1097
+ x: from[0],
1098
+ y: from[1],
1099
+ z: to[2]
1100
+ },
1101
+ {
1102
+ x: from[0],
1103
+ y: to[1],
1104
+ z: to[2]
1105
+ },
1106
+ {
1107
+ x: from[0],
1108
+ y: to[1],
1109
+ z: from[2]
1110
+ }
1111
+ ];
1112
+ break;
1113
+ case "east":
1114
+ vertices = [
1115
+ {
1116
+ x: to[0],
1117
+ y: from[1],
1118
+ z: to[2]
1119
+ },
1120
+ {
1121
+ x: to[0],
1122
+ y: from[1],
1123
+ z: from[2]
1124
+ },
1125
+ {
1126
+ x: to[0],
1127
+ y: to[1],
1128
+ z: from[2]
1129
+ },
1130
+ {
1131
+ x: to[0],
1132
+ y: to[1],
1133
+ z: to[2]
1134
+ }
1135
+ ];
1136
+ break;
1137
+ }
1138
+ const projected = vertices.map((v) => this.project(v, cosX, sinX, cosY, sinY, cosZ, sinZ, scale));
1139
+ const v0 = projected[0], v1 = projected[1], v2 = projected[2];
1140
+ if ((v1.x - v0.x) * (v2.y - v0.y) - (v1.y - v0.y) * (v2.x - v0.x) >= 0) continue;
1141
+ const avgZ = projected.reduce((sum, p) => sum + p.z, 0) / 4;
1142
+ const minZ = Math.min(...projected.map((p) => p.z));
1143
+ allFacesToRender.push({
1144
+ faceName,
1145
+ face,
1146
+ projected,
1147
+ avgZ,
1148
+ minZ
1149
+ });
1150
+ }
1151
+ allFacesToRender.sort((a, b) => b.avgZ - a.avgZ);
1152
+ for (const renderData of allFacesToRender) {
1153
+ const { faceName, face, projected } = renderData;
1154
+ const texturePath = this.resolveTexture(face.texture, model.textures || {});
1155
+ let texture = await this.loadTexture(texturePath, faceName);
1156
+ if (face.tintindex !== void 0) {
1157
+ const tintColor = this.getTintColor(normalizedModelPath, face.tintindex);
1158
+ if (tintColor) {
1159
+ const tintCacheKey = `${texturePath}:${faceName}:${tintColor.join(",")}`;
1160
+ if (this.tintedTexturesCache.has(tintCacheKey)) texture = this.tintedTexturesCache.get(tintCacheKey);
1161
+ else {
1162
+ texture = await this.applyTint(texture, tintColor);
1163
+ this.tintedTexturesCache.set(tintCacheKey, texture);
1164
+ }
1165
+ }
1166
+ }
1167
+ ctx.save();
1168
+ const uv = face.uv || [
1169
+ 0,
1170
+ 0,
1171
+ 16,
1172
+ 16
1173
+ ];
1174
+ const uMin = uv[0], vMin = uv[1];
1175
+ const uMax = uv[2], vMax = uv[3];
1176
+ let uvCoords = [];
1177
+ const rotateUVCoords = (coords, rotation$1) => {
1178
+ const steps = ((rotation$1 || 0) % 360 + 360) % 360 / 90;
1179
+ let result = [...coords];
1180
+ for (let i = 0; i < steps; i++) result = [
1181
+ result[3],
1182
+ result[0],
1183
+ result[1],
1184
+ result[2]
1185
+ ];
1186
+ return result;
1187
+ };
1188
+ switch (faceName) {
1189
+ case "up":
1190
+ case "down":
1191
+ uvCoords = [
1192
+ {
1193
+ u: uMin,
1194
+ v: vMax
1195
+ },
1196
+ {
1197
+ u: uMax,
1198
+ v: vMax
1199
+ },
1200
+ {
1201
+ u: uMax,
1202
+ v: vMin
1203
+ },
1204
+ {
1205
+ u: uMin,
1206
+ v: vMin
1207
+ }
1208
+ ];
1209
+ break;
1210
+ default:
1211
+ uvCoords = [
1212
+ {
1213
+ u: uMax,
1214
+ v: vMax
1215
+ },
1216
+ {
1217
+ u: uMin,
1218
+ v: vMax
1219
+ },
1220
+ {
1221
+ u: uMin,
1222
+ v: vMin
1223
+ },
1224
+ {
1225
+ u: uMax,
1226
+ v: vMin
1227
+ }
1228
+ ];
1229
+ break;
1230
+ }
1231
+ uvCoords = rotateUVCoords(uvCoords, face.rotation);
1232
+ this.drawTexturedQuad(ctx, centerX, centerY, texture, [
1233
+ {
1234
+ x: projected[0].x,
1235
+ y: projected[0].y
1236
+ },
1237
+ {
1238
+ x: projected[1].x,
1239
+ y: projected[1].y
1240
+ },
1241
+ {
1242
+ x: projected[2].x,
1243
+ y: projected[2].y
1244
+ },
1245
+ {
1246
+ x: projected[3].x,
1247
+ y: projected[3].y
1248
+ }
1249
+ ], uvCoords, 2);
1250
+ ctx.save();
1251
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
1252
+ ctx.beginPath();
1253
+ ctx.moveTo(centerX + projected[0].x, centerY - projected[0].y);
1254
+ ctx.lineTo(centerX + projected[1].x, centerY - projected[1].y);
1255
+ ctx.lineTo(centerX + projected[2].x, centerY - projected[2].y);
1256
+ ctx.lineTo(centerX + projected[3].x, centerY - projected[3].y);
1257
+ ctx.closePath();
1258
+ let shade = 1;
1259
+ switch (faceName) {
1260
+ case "up":
1261
+ shade = 1;
1262
+ break;
1263
+ case "down":
1264
+ shade = .5;
1265
+ break;
1266
+ case "north":
1267
+ case "south":
1268
+ shade = .4;
1269
+ break;
1270
+ case "west":
1271
+ case "east":
1272
+ shade = .7;
1273
+ break;
1274
+ }
1275
+ const shadowColor = `rgba(0, 0, 0, ${1 - shade})`;
1276
+ ctx.globalCompositeOperation = "source-atop";
1277
+ ctx.fillStyle = shadowColor;
1278
+ ctx.fill();
1279
+ ctx.strokeStyle = shadowColor;
1280
+ ctx.lineWidth = .4;
1281
+ ctx.lineJoin = "round";
1282
+ ctx.stroke();
1283
+ ctx.restore();
1284
+ }
1285
+ const buffer = canvas$1.toBuffer("image/png");
1286
+ await (0, fs_promises.mkdir)((0, path.dirname)(outputPath), { recursive: true });
1287
+ await (0, sharp.default)(buffer).png().toFile(outputPath);
1288
+ return outputPath;
1289
+ }
1290
+ /**
1291
+ * アイテムモデルをレンダリング
1292
+ */
1293
+ async renderItem(modelId, outputPath, options) {
1294
+ const { width = CONFIG.TEXTURE_SIZE, height = CONFIG.TEXTURE_SIZE, scale = CONFIG.TEXTURE_SIZE } = options ?? {};
1295
+ await (0, fs_promises.mkdir)((0, path.dirname)(outputPath), { recursive: true });
1296
+ const model = await this.loadModel(modelId);
1297
+ if (!model) throw new Error(`Item model not found: ${modelId}`);
1298
+ const resolvedScale = scale / 16;
1299
+ const textures = [];
1300
+ let currentModel = model;
1301
+ let depth = 0;
1302
+ while (currentModel && depth < 10) {
1303
+ if (currentModel.textures) {
1304
+ for (const key in currentModel.textures) if (key.startsWith("layer")) textures.push({
1305
+ layer: currentModel.textures[key].replace("minecraft:", ""),
1306
+ tintindex: currentModel.overrides?.[0]?.predicate?.custom_model_data !== void 0 ? 0 : void 0
1307
+ });
1308
+ }
1309
+ if (currentModel.parent) {
1310
+ const parentId = currentModel.parent.replace("minecraft:", "");
1311
+ if (parentId === "builtin/entity") currentModel = null;
1312
+ else currentModel = await this.loadModel(parentId);
1313
+ } else currentModel = null;
1314
+ depth++;
1315
+ }
1316
+ if (textures.length === 0) throw new Error(`No textures found for item model: ${modelId}`);
1317
+ let composedImage;
1318
+ for (const { layer, tintindex } of textures) {
1319
+ const texturePath = `textures/${layer}.png`;
1320
+ let textureCanvas = await this.loadTexture(texturePath);
1321
+ const tintColor = TINT_COLORS[modelId.replace("item/", "")]?.[tintindex ?? 0] || TINT_COLORS[modelId]?.[tintindex ?? 0];
1322
+ if (tintColor) {
1323
+ const tintCacheKey = `${texturePath}:item:${tintColor.join(",")}`;
1324
+ if (this.tintedTexturesCache.has(tintCacheKey)) textureCanvas = this.tintedTexturesCache.get(tintCacheKey);
1325
+ else {
1326
+ textureCanvas = await this.applyTint(textureCanvas, tintColor);
1327
+ this.tintedTexturesCache.set(tintCacheKey, textureCanvas);
1328
+ }
1329
+ }
1330
+ let sharpImage = (0, sharp.default)(textureCanvas.toBuffer("image/png"));
1331
+ if (resolvedScale !== 1) sharpImage = sharpImage.resize(Math.round(16 * resolvedScale), Math.round(16 * resolvedScale), { kernel: sharp.default.kernel.nearest });
1332
+ const processedBuffer = await sharpImage.toBuffer();
1333
+ if (!composedImage) composedImage = (0, sharp.default)(processedBuffer);
1334
+ else composedImage = composedImage.composite([{ input: processedBuffer }]);
1335
+ }
1336
+ if (!composedImage) throw new Error(`Failed to compose image for item model: ${modelId}`);
1337
+ (0, fs.writeFileSync)(outputPath, await composedImage.resize(width, height, {
1338
+ fit: "contain",
1339
+ background: {
1340
+ r: 0,
1341
+ g: 0,
1342
+ b: 0,
1343
+ alpha: 0
1344
+ }
1345
+ }).toBuffer());
1346
+ return outputPath;
1347
+ }
1348
+ /**
1349
+ * modelsディレクトリ内のすべてのモデルをレンダリング
1350
+ */
1351
+ async renderAllModels(outputDir, options) {
1352
+ const files = await (0, fs_promises.readdir)(this.modelPathResolver.getBlockModelsDir());
1353
+ for (const file of files) {
1354
+ if (!file.endsWith(".json")) continue;
1355
+ const modelPath = file.replace(".json", "");
1356
+ const outputPath = (0, path.dirname)(outputDir) + "/" + modelPath.split("/").pop() + ".png";
1357
+ console.log(`Rendering ${file}...`);
1358
+ try {
1359
+ await this.renderBlock(modelPath, outputPath, options);
1360
+ console.log(`✓ Saved to ${outputPath}`);
1361
+ } catch (error) {
1362
+ console.error(`✗ Failed to render ${file}:`, error);
1363
+ }
1364
+ }
1365
+ }
1366
+ };
1367
+
1368
+ //#endregion
1369
+ //#region src/mojang/itemManager.ts
1370
+ var ItemManager = class {
1371
+ resourcePackPath;
1372
+ versionManager;
1373
+ items3dCache = /* @__PURE__ */ new Map();
1374
+ itemIdsCache = /* @__PURE__ */ new Map();
1375
+ displayTypeCache = /* @__PURE__ */ new Map();
1376
+ constructor(resourcePackPath, versionManager) {
1377
+ this.resourcePackPath = resourcePackPath;
1378
+ this.versionManager = versionManager;
1379
+ }
1380
+ /**
1381
+ * en_us.json からアイテムID一覧を取得
1382
+ */
1383
+ async getItemIds(versionId) {
1384
+ if (this.itemIdsCache.has(versionId)) return this.itemIdsCache.get(versionId);
1385
+ try {
1386
+ const langData = await this.getLangFile(versionId, "en_us");
1387
+ const itemIds = [];
1388
+ for (const key in langData) {
1389
+ const parts = key.split(".");
1390
+ if (parts.length === 3) {
1391
+ const [category, namespace, id] = parts;
1392
+ if ([
1393
+ "item",
1394
+ "block",
1395
+ "entity"
1396
+ ].includes(category) && namespace === "minecraft") {
1397
+ const fullId = `minecraft:${id}`;
1398
+ itemIds.push(fullId);
1399
+ }
1400
+ }
1401
+ }
1402
+ logger_default.debug(`Extracted ${itemIds.length} item IDs from ${versionId}`);
1403
+ this.itemIdsCache.set(versionId, itemIds);
1404
+ return itemIds;
1405
+ } catch (error) {
1406
+ logger_default.error(`Failed to get item IDs for ${versionId}: ${error}`);
1407
+ throw error;
1408
+ }
1409
+ }
1410
+ /**
1411
+ * アイテムIDからテクスチャパスを取得
1412
+ */
1413
+ async getItemTexturePath(versionId, itemId) {
1414
+ const [namespace, id] = itemId.includes(":") ? itemId.split(":") : ["minecraft", itemId];
1415
+ if (namespace !== "minecraft") throw new Error(`Invalid item ID: ${itemId}`);
1416
+ const assetsDir = await this.versionManager.getAssets(versionId);
1417
+ let currentModelPath = (0, path.join)(assetsDir, "assets", "minecraft", "models", "item", `${id}.json`);
1418
+ const visited = /* @__PURE__ */ new Set();
1419
+ while ((0, fs.existsSync)(currentModelPath) && !visited.has(currentModelPath)) {
1420
+ visited.add(currentModelPath);
1421
+ const content = await (0, fs_promises.readFile)(currentModelPath, "utf8");
1422
+ const model = JSON.parse(content);
1423
+ if (model.textures && model.textures.layer0) {
1424
+ let textureId = model.textures.layer0;
1425
+ const [, texPath] = textureId.includes(":") ? textureId.split(":") : ["minecraft", textureId];
1426
+ return (0, path.join)(this.resourcePackPath, "assets", "minecraft", "textures", `${texPath}.png`);
1427
+ }
1428
+ if (model.parent) {
1429
+ const [, parentPath] = model.parent.split(":");
1430
+ currentModelPath = (0, path.join)(assetsDir, "assets", "minecraft", "models", parentPath.startsWith("block/") ? "block" : "item", `${parentPath.replace(/^(block|item)\//, "")}.json`);
1431
+ } else break;
1432
+ }
1433
+ return null;
1434
+ }
1435
+ /**
1436
+ * アイテムがティントを必要とするか判定
1437
+ */
1438
+ async needsTint(versionId, itemId) {
1439
+ const [namespace, id] = itemId.includes(":") ? itemId.split(":") : ["minecraft", itemId];
1440
+ const cleanId = id.replace(/^item\//, "").replace(/^block\//, "");
1441
+ if (!!TINT_COLORS[cleanId]) return true;
1442
+ const modelPath = (0, path.join)(await this.versionManager.getAssets(versionId), "assets", "minecraft", "models", "item", `${cleanId}.json`);
1443
+ if ((0, fs.existsSync)(modelPath)) try {
1444
+ const content = await (0, fs_promises.readFile)(modelPath, "utf8");
1445
+ const model = JSON.parse(content);
1446
+ if (model.overrides && model.overrides.length > 0) return true;
1447
+ } catch {}
1448
+ return false;
1449
+ }
1450
+ /**
1451
+ * 指定言語でアイテムの表示名を取得
1452
+ */
1453
+ async getItemLabel(versionId, itemId, lang = "en_us") {
1454
+ try {
1455
+ const langData = await this.getLangFile(versionId, lang);
1456
+ const labelKey = `.minecraft.${itemId.replace("minecraft:", "")}`;
1457
+ return langData["item" + labelKey] || langData["block" + labelKey] || langData["entity" + labelKey] || itemId;
1458
+ } catch (error) {
1459
+ logger_default.warn(`Failed to get label for ${itemId} in ${lang}: ${error}`);
1460
+ return itemId;
1461
+ }
1462
+ }
1463
+ /**
1464
+ * 言語ファイルを取得(en_us はアセットから、他言語はAsset Indexから)
1465
+ */
1466
+ async getLangFile(versionId, lang = "en_us") {
1467
+ try {
1468
+ logger_default.debug(`Loading language file: ${versionId}/${lang}`);
1469
+ const langFilePath = await this.versionManager.getLangFile(versionId, lang);
1470
+ if (!(0, fs.existsSync)(langFilePath)) throw new Error(`Language file not found: ${lang}.json`);
1471
+ const langData = JSON.parse((0, fs.readFileSync)(langFilePath, "utf-8"));
1472
+ logger_default.debug(`Language file loaded: ${versionId}/${lang}`);
1473
+ return langData;
1474
+ } catch (error) {
1475
+ logger_default.error(`Failed to load language file ${lang} for ${versionId}: ${error}`);
1476
+ throw error;
1477
+ }
1478
+ }
1479
+ /**
1480
+ * 複数の言語でアイテムラベルを取得
1481
+ */
1482
+ async getItemLabelsByLangs(versionId, itemId, langs = ["en_us"]) {
1483
+ const result = {};
1484
+ for (const lang of langs) try {
1485
+ result[lang] = await this.getItemLabel(versionId, itemId, lang);
1486
+ } catch (error) {
1487
+ logger_default.warn(`Failed to get label for ${itemId} in ${lang}`);
1488
+ result[lang] = itemId;
1489
+ }
1490
+ return result;
1491
+ }
1492
+ async isItem2DModel(modelId, assetsDir) {
1493
+ return await this.getModelDisplayType(modelId, assetsDir) === "2d";
1494
+ }
1495
+ /**
1496
+ * モデルの表示タイプを判定 ('2d' または '3d')
1497
+ */
1498
+ async getModelDisplayType(modelId, assetsDir) {
1499
+ const cacheKey = `${assetsDir}:${modelId}`;
1500
+ if (this.displayTypeCache.has(cacheKey)) return this.displayTypeCache.get(cacheKey);
1501
+ const BASE_PATH = (0, path.join)(assetsDir, "assets", "minecraft", "models");
1502
+ let [, rawPath] = modelId.includes(":") ? modelId.split(":") : ["minecraft", modelId];
1503
+ let modelPath = rawPath.startsWith("item/") || rawPath.startsWith("block/") ? rawPath : `item/${rawPath}`;
1504
+ let depth = 0;
1505
+ let currentPath = (0, path.join)(BASE_PATH, `${modelPath}.json`);
1506
+ const visited = /* @__PURE__ */ new Set();
1507
+ while ((0, fs.existsSync)(currentPath) && depth < 10 && !visited.has(currentPath)) {
1508
+ visited.add(currentPath);
1509
+ try {
1510
+ const content = await (0, fs_promises.readFile)(currentPath, "utf8");
1511
+ const parent = JSON.parse(content).parent;
1512
+ if (!parent) {
1513
+ this.displayTypeCache.set(cacheKey, "3d");
1514
+ return "3d";
1515
+ }
1516
+ if (parent === "minecraft:item/generated" || parent === "item/generated" || parent === "minecraft:item/handheld" || parent === "item/handheld") {
1517
+ this.displayTypeCache.set(cacheKey, "2d");
1518
+ return "2d";
1519
+ }
1520
+ if (parent.startsWith("minecraft:block/") || parent.startsWith("block/")) {
1521
+ this.displayTypeCache.set(cacheKey, "3d");
1522
+ return "3d";
1523
+ }
1524
+ [, modelPath] = parent.includes(":") ? parent.split(":") : ["minecraft", parent];
1525
+ currentPath = (0, path.join)(BASE_PATH, `${modelPath}.json`);
1526
+ depth++;
1527
+ } catch {
1528
+ this.displayTypeCache.set(cacheKey, "3d");
1529
+ return "3d";
1530
+ }
1531
+ }
1532
+ this.displayTypeCache.set(cacheKey, "3d");
1533
+ return "3d";
1534
+ }
1535
+ /**
1536
+ * minecraftId からレンダーパスを取得
1537
+ * ブロックモデルなら block/{id}、アイテムモデルなら item/{id}
1538
+ */
1539
+ async getItemRenderPath(versionId, minecraftId) {
1540
+ const assetsDir = await this.versionManager.getAssets(versionId);
1541
+ const baseId = minecraftId.replace(/^minecraft:/, "");
1542
+ let itemModelPath = (0, path.join)(assetsDir, "assets", "minecraft", "models", "item", `${baseId}.json`);
1543
+ if ((0, fs.existsSync)(itemModelPath)) {
1544
+ if (await this.getModelDisplayType(`item/${baseId}`, assetsDir) === "2d") return `item/${baseId}`;
1545
+ try {
1546
+ const itemModelContent = await (0, fs_promises.readFile)(itemModelPath, "utf8");
1547
+ const itemModelJson = JSON.parse(itemModelContent);
1548
+ if (itemModelJson.parent && itemModelJson.parent.startsWith("block/")) return itemModelJson.parent;
1549
+ } catch (e) {
1550
+ logger_default.warn(`Failed to parse item model ${itemModelPath}: ${e}`);
1551
+ }
1552
+ return `item/${baseId}`;
1553
+ }
1554
+ if ((0, fs.existsSync)((0, path.join)(assetsDir, "assets", "minecraft", "models", "block", `${baseId}.json`))) return `block/${baseId}`;
1555
+ logger_default.warn(`Could not find render path for ${minecraftId}. Defaulting to item/${baseId}.`);
1556
+ return `item/${baseId}`;
1557
+ }
1558
+ /**
1559
+ * 3Dアイテムのリストを取得
1560
+ */
1561
+ async get3DItems(versionId) {
1562
+ if (this.items3dCache.has(versionId)) return this.items3dCache.get(versionId);
1563
+ try {
1564
+ const itemIds = await this.getItemIds(versionId);
1565
+ const assetsDir = await this.versionManager.getAssets(versionId);
1566
+ const items3d = [];
1567
+ const batchSize = 30;
1568
+ for (let i = 0; i < itemIds.length; i += batchSize) {
1569
+ const batchPromises = itemIds.slice(i, i + batchSize).map(async (itemId) => ({
1570
+ itemId,
1571
+ type: await this.getModelDisplayType(itemId, assetsDir)
1572
+ }));
1573
+ const batchResults = await Promise.all(batchPromises);
1574
+ for (const result of batchResults) if (result.type === "3d") items3d.push(result.itemId);
1575
+ }
1576
+ logger_default.debug(`Found ${items3d.length} 3D items out of ${itemIds.length} total items`);
1577
+ this.items3dCache.set(versionId, items3d);
1578
+ return items3d;
1579
+ } catch (error) {
1580
+ logger_default.error(`Failed to get 3D items for ${versionId}: ${error}`);
1581
+ throw error;
1582
+ }
1583
+ }
1584
+ /**
1585
+ * 3Dアイテムのリストを段階的に取得(dev用:キャッシュからの読み込みのみ)
1586
+ * ファイルシステム操作を最小限に抑える
1587
+ */
1588
+ async get3DItemsLazy(versionId) {
1589
+ if (this.items3dCache.has(versionId)) return this.items3dCache.get(versionId);
1590
+ return [];
1591
+ }
1592
+ };
1593
+ function createItemManager(resourcePackPath, versionManager) {
1594
+ return new ItemManager(resourcePackPath, versionManager);
1595
+ }
1596
+
1597
+ //#endregion
1598
+ //#region src/render/Builder.ts
1599
+ const clone = (value) => JSON.parse(JSON.stringify(value));
1600
+ /**
1601
+ * テクスチャ参照を解決(#texture_nameのような参照を実際のパスに変換)
1602
+ */
1603
+ function resolveTexturePath(textures = {}, ref, visited = /* @__PURE__ */ new Set()) {
1604
+ if (!ref) return null;
1605
+ if (visited.has(ref)) return null;
1606
+ if (ref.startsWith("#")) {
1607
+ const key = ref.slice(1);
1608
+ visited.add(ref);
1609
+ return resolveTexturePath(textures, textures[key], visited);
1610
+ }
1611
+ const texturePath = new MinecraftPathResolver("").normalizeTexturePath(ref);
1612
+ return `${MINECRAFT_PATHS.minecraft}/${texturePath}`;
1613
+ }
1614
+ /**
1615
+ * モデル参照を再帰的に検索
1616
+ */
1617
+ function findModelReference(node) {
1618
+ if (!node || typeof node !== "object") return null;
1619
+ if (node.type === "minecraft:model" && typeof node.model === "string") return node.model;
1620
+ if (node.fallback) {
1621
+ const fallbackFound = findModelReference(node.fallback);
1622
+ if (fallbackFound) return fallbackFound;
1623
+ }
1624
+ if (Array.isArray(node.cases)) for (const entry of node.cases) {
1625
+ const found = findModelReference(entry?.model ?? entry);
1626
+ if (found) return found;
1627
+ }
1628
+ if (Array.isArray(node.entries)) for (const entry of node.entries) {
1629
+ const found = findModelReference(entry?.model ?? entry);
1630
+ if (found) return found;
1631
+ }
1632
+ if (Array.isArray(node)) {
1633
+ for (const entry of node) {
1634
+ const found = findModelReference(entry);
1635
+ if (found) return found;
1636
+ }
1637
+ return null;
1638
+ }
1639
+ for (const value of Object.values(node)) {
1640
+ const found = findModelReference(value);
1641
+ if (found) return found;
1642
+ }
1643
+ return null;
1644
+ }
1645
+ var ResourcePackBuilder = class {
1646
+ modelsCache = /* @__PURE__ */ new Map();
1647
+ resolvedModelCache = /* @__PURE__ */ new Map();
1648
+ pathResolver;
1649
+ constructor(resourcePackPath) {
1650
+ this.pathResolver = new MinecraftPathResolver(resourcePackPath);
1651
+ }
1652
+ /**
1653
+ * リソースパック内のすべてのブロックモデルを構築
1654
+ */
1655
+ async buildAllModels() {
1656
+ const modelsDir = this.pathResolver.getBlockModelsDir();
1657
+ if (!(0, fs.existsSync)(modelsDir)) {
1658
+ console.warn(`Models directory not found: ${modelsDir}`);
1659
+ return [];
1660
+ }
1661
+ const modelFiles = (await (0, fs_promises.readdir)(modelsDir)).filter((f) => f.endsWith(".json"));
1662
+ for (const file of modelFiles) {
1663
+ const name$1 = file.slice(0, -5);
1664
+ const filePath = (0, path.join)(modelsDir, file);
1665
+ try {
1666
+ const content = await (0, fs_promises.readFile)(filePath, "utf-8");
1667
+ this.modelsCache.set(name$1, JSON.parse(content));
1668
+ } catch (error) {
1669
+ console.warn(`Failed to load model ${name$1}:`, error);
1670
+ }
1671
+ }
1672
+ const resolvedModels = [];
1673
+ for (const name$1 of this.modelsCache.keys()) {
1674
+ const resolvedModel = this.resolveModel(name$1);
1675
+ if (!resolvedModel) continue;
1676
+ const elements = resolvedModel.elements || [];
1677
+ const usedTextures = /* @__PURE__ */ new Map();
1678
+ for (const element of elements) {
1679
+ const faces = element.faces || {};
1680
+ for (const [, faceDef] of Object.entries(faces)) {
1681
+ const texturePath = resolveTexturePath(resolvedModel.textures || {}, faceDef?.texture);
1682
+ if (texturePath && !usedTextures.has(texturePath)) usedTextures.set(texturePath, {
1683
+ path: texturePath,
1684
+ dataUrl: null,
1685
+ mcmeta: null
1686
+ });
1687
+ }
1688
+ }
1689
+ resolvedModels.push({
1690
+ name: name$1,
1691
+ model: resolvedModel,
1692
+ sourceModel: clone(this.modelsCache.get(name$1)),
1693
+ usedTextures: Array.from(usedTextures.values())
1694
+ });
1695
+ }
1696
+ resolvedModels.sort((a, b) => a.name.localeCompare(b.name));
1697
+ return resolvedModels;
1698
+ }
1699
+ /**
1700
+ * すべてのアイテムを構築
1701
+ */
1702
+ async buildAllItems() {
1703
+ const itemsDir = this.pathResolver.getItemsDir();
1704
+ if (!(0, fs.existsSync)(itemsDir)) {
1705
+ console.warn(`Items directory not found: ${itemsDir}`);
1706
+ return [];
1707
+ }
1708
+ const itemFiles = (await (0, fs_promises.readdir)(itemsDir)).filter((f) => f.endsWith(".json"));
1709
+ const resolvedItems = [];
1710
+ for (const file of itemFiles) {
1711
+ const name$1 = file.slice(0, -5);
1712
+ const filePath = (0, path.join)(itemsDir, file);
1713
+ try {
1714
+ const content = await (0, fs_promises.readFile)(filePath, "utf-8");
1715
+ const definition = JSON.parse(content);
1716
+ const normalized = findModelReference(definition?.model ?? definition)?.replace(/^minecraft:/, "");
1717
+ const isBlockModel = normalized?.startsWith("block/");
1718
+ const texturePath = `assets/minecraft/textures/item/${normalized?.startsWith("item/") ? normalized.slice(5) : name$1}.png`;
1719
+ resolvedItems.push({
1720
+ name: name$1,
1721
+ definition,
1722
+ modelReference: normalized ?? null,
1723
+ textureInfo: {
1724
+ texturePath: isBlockModel ? null : texturePath,
1725
+ dataUrl: null,
1726
+ mcmeta: null
1727
+ }
1728
+ });
1729
+ } catch (error) {
1730
+ console.warn(`Failed to load item ${name$1}:`, error);
1731
+ }
1732
+ }
1733
+ resolvedItems.sort((a, b) => a.name.localeCompare(b.name));
1734
+ return resolvedItems;
1735
+ }
1736
+ /**
1737
+ * モデルとその親の継承を解決
1738
+ */
1739
+ resolveModel(name$1, chain = /* @__PURE__ */ new Set()) {
1740
+ if (this.resolvedModelCache.has(name$1)) return this.resolvedModelCache.get(name$1);
1741
+ if (chain.has(name$1)) return null;
1742
+ chain.add(name$1);
1743
+ const baseModel = this.modelsCache.get(name$1);
1744
+ if (!baseModel) return null;
1745
+ const model = clone(baseModel);
1746
+ if (model.parent) {
1747
+ let parentName = model.parent.replace(/^minecraft:/, "");
1748
+ if (parentName.startsWith("block/")) parentName = parentName.slice(6);
1749
+ const parentModel = this.resolveModel(parentName, chain);
1750
+ if (parentModel) {
1751
+ model.textures = {
1752
+ ...parentModel.textures || {},
1753
+ ...model.textures || {}
1754
+ };
1755
+ if (!model.elements && parentModel.elements) model.elements = clone(parentModel.elements);
1756
+ }
1757
+ }
1758
+ this.resolvedModelCache.set(name$1, model);
1759
+ return model;
1760
+ }
1761
+ /**
1762
+ * キャッシュをクリア
1763
+ */
1764
+ clearCache() {
1765
+ this.modelsCache.clear();
1766
+ this.resolvedModelCache.clear();
1767
+ }
1768
+ };
1769
+
1770
+ //#endregion
1771
+ //#region src/render/ResourcePack.ts
1772
+ /**
1773
+ * Minecraft リソースパック統合システム
1774
+ * リソースパック解析とレンダリングの統合ファサード
1775
+ */
1776
+ var MinecraftResourcePack = class {
1777
+ builder;
1778
+ renderer;
1779
+ constructor(resourcePackPath, modelPath) {
1780
+ this.builder = new ResourcePackBuilder(resourcePackPath);
1781
+ this.renderer = new MinecraftBlockRenderer(resourcePackPath, modelPath);
1782
+ }
1783
+ /**
1784
+ * レンダラーを取得(内部使用)
1785
+ */
1786
+ getRenderer() {
1787
+ return this.renderer;
1788
+ }
1789
+ /**
1790
+ * すべてのブロックモデルを取得
1791
+ */
1792
+ async getAllBlockModels() {
1793
+ return this.builder.buildAllModels();
1794
+ }
1795
+ /**
1796
+ * すべてのアイテムを取得
1797
+ */
1798
+ async getAllItems() {
1799
+ return this.builder.buildAllItems();
1800
+ }
1801
+ /**
1802
+ * ブロックモデルの詳細情報を取得
1803
+ */
1804
+ async getBlockModel(blockName) {
1805
+ return (await this.builder.buildAllModels()).find((m) => m.name === blockName) || null;
1806
+ }
1807
+ /**
1808
+ * 複数のブロックをレンダリング
1809
+ */
1810
+ async renderBlocks(blockNames, options = {}) {
1811
+ const { outputDir = "./renders", width = CONFIG.WIDTH, height = options.width ?? CONFIG.HEIGHT, scale, rotation = CONFIG.ROTATION, dryRun = false } = options;
1812
+ const renderOptions = {
1813
+ width,
1814
+ height,
1815
+ scale,
1816
+ rotation
1817
+ };
1818
+ const result = {
1819
+ success: [],
1820
+ failed: []
1821
+ };
1822
+ const renderTasks = blockNames.map(async (blockName) => {
1823
+ const normalizedName = blockName.replace(/^minecraft:/, "");
1824
+ const modelPath = `block/${normalizedName}`;
1825
+ const outputPath = (0, path.join)(outputDir, `${normalizedName}.png`);
1826
+ try {
1827
+ if (dryRun) {
1828
+ console.log(`[DRY-RUN] Would render: ${normalizedName} -> ${outputPath}`);
1829
+ return {
1830
+ type: "success",
1831
+ name: normalizedName
1832
+ };
1833
+ } else {
1834
+ await this.renderer.renderBlock(modelPath, outputPath, renderOptions);
1835
+ return {
1836
+ type: "success",
1837
+ name: normalizedName
1838
+ };
1839
+ }
1840
+ } catch (error) {
1841
+ console.error(`❌ Failed to render ${blockName}:`, error);
1842
+ return {
1843
+ type: "failed",
1844
+ name: normalizedName
1845
+ };
1846
+ }
1847
+ });
1848
+ const results = await Promise.allSettled(renderTasks);
1849
+ for (const settledResult of results) if (settledResult.status === "fulfilled") {
1850
+ const { type: type$1, name: name$1 } = settledResult.value;
1851
+ if (type$1 === "success") result.success.push(name$1);
1852
+ else result.failed.push(name$1);
1853
+ } else result.failed.push("unknown");
1854
+ return result;
1855
+ }
1856
+ /**
1857
+ * すべてのブロックモデルをレンダリング
1858
+ */
1859
+ async renderAllBlocks(options = {}) {
1860
+ const blockNames = (await this.builder.buildAllModels()).map((m) => m.name);
1861
+ console.log(`🎨 Rendering ${blockNames.length} block models...`);
1862
+ const result = await this.renderBlocks(blockNames, options);
1863
+ console.log(`\n📊 Render Summary:`);
1864
+ console.log(` ✅ Success: ${result.success.length}`);
1865
+ console.log(` ❌ Failed: ${result.failed.length}`);
1866
+ return {
1867
+ success: result.success.length,
1868
+ failed: result.failed.length
1869
+ };
1870
+ }
1871
+ /**
1872
+ * モデルの使用テクスチャを取得
1873
+ */
1874
+ async getModelTextures(blockName) {
1875
+ const model = await this.getBlockModel(blockName);
1876
+ if (!model) return [];
1877
+ return model.usedTextures.map((t) => t.path);
1878
+ }
1879
+ /**
1880
+ * キャッシュをクリア
1881
+ */
1882
+ clearCache() {
1883
+ this.builder.clearCache();
1884
+ }
1885
+ };
1886
+ function createResourcePack(resourcePackPath, modelPath) {
1887
+ return new MinecraftResourcePack(resourcePackPath, modelPath);
1888
+ }
1889
+
1890
+ //#endregion
1891
+ //#region src/patternMatcher.ts
1892
+ /**
1893
+ * より正確なパターンマッチング
1894
+ */
1895
+ function matchesPattern(path$2, patterns) {
1896
+ const pathParts = path$2.split("/");
1897
+ return patterns.some((pattern) => {
1898
+ const regex = /* @__PURE__ */ new RegExp("^" + pattern.replace(/\./g, "\\.").replace(/\*\*/g, ".+").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]") + "$");
1899
+ if (!pattern.includes("/")) return pathParts.some((part) => regex.test(part));
1900
+ return regex.test(path$2);
1901
+ });
1902
+ }
1903
+
1904
+ //#endregion
1905
+ //#region src/codeScanner.ts
1906
+ /**
1907
+ * ソースコードをスキャンして、使用されているMinecraft IDとレンダリングオプションを検出
1908
+ */
1909
+ function scanSourceCode(root, options = {}) {
1910
+ const { include = [
1911
+ "**/*.ts",
1912
+ "**/*.tsx",
1913
+ "**/*.js",
1914
+ "**/*.jsx"
1915
+ ], exclude = [], outputPath = "./mcpacks", distDir = "./dist" } = options;
1916
+ const usedIds = /* @__PURE__ */ new Set();
1917
+ const renderingOptions = /* @__PURE__ */ new Map();
1918
+ const alwaysExclude = [
1919
+ "node_modules",
1920
+ ".git",
1921
+ "*.d.ts"
1922
+ ];
1923
+ const normalizedOutputPath = outputPath.replace(/^\.\//, "").replace(/\/$/, "");
1924
+ const normalizedDistDir = distDir.replace(/^\.\//, "").replace(/\/$/, "");
1925
+ const finalExclude = [
1926
+ ...alwaysExclude,
1927
+ normalizedOutputPath,
1928
+ normalizedDistDir,
1929
+ ...exclude
1930
+ ];
1931
+ const scanDir = (dir, relativeBase = "") => {
1932
+ try {
1933
+ const entries = (0, node_fs.readdirSync)(dir);
1934
+ for (const entry of entries) {
1935
+ const fullPath = (0, node_path.join)(dir, entry);
1936
+ const relativePath = relativeBase ? `${relativeBase}/${entry}` : entry;
1937
+ const stat = (0, node_fs.statSync)(fullPath);
1938
+ if (matchesPattern(relativePath, finalExclude)) continue;
1939
+ if (stat.isDirectory()) scanDir(fullPath, relativePath);
1940
+ else if (stat.isFile()) {
1941
+ if (matchesPattern(relativePath, include)) extractResourceIds(fullPath, usedIds, renderingOptions);
1942
+ }
1943
+ }
1944
+ } catch {}
1945
+ };
1946
+ scanDir(root);
1947
+ return {
1948
+ usedIds,
1949
+ renderingOptions
1950
+ };
1951
+ }
1952
+ /**
1953
+ * オプションのハッシュキーを生成
1954
+ */
1955
+ function generateOptionHash(width, height, scale) {
1956
+ const parts = [];
1957
+ if (width !== void 0) parts.push(`w${width}`);
1958
+ if (height !== void 0) parts.push(`h${height}`);
1959
+ if (scale !== void 0) parts.push(`s${scale}`);
1960
+ return parts.join("_");
1961
+ }
1962
+ /**
1963
+ * ファイルからMinecraft IDを抽出
1964
+ */
1965
+ function extractResourceIds(filePath, usedIds, renderingOptions) {
1966
+ try {
1967
+ const content = (0, node_fs.readFileSync)(filePath, "utf-8");
1968
+ const resourcePackRegex = /getResourcePack\s*\(\s*["']minecraft:([a-z0-9/_\-\.]+)["']\s*,\s*\{\s*([^}]*)\s*\}\s*\)/gi;
1969
+ let match;
1970
+ const processedItems = /* @__PURE__ */ new Set();
1971
+ while ((match = resourcePackRegex.exec(content)) !== null) {
1972
+ const itemId = `minecraft:${match[1]}`;
1973
+ usedIds.add(itemId);
1974
+ const optionsStr = match[2];
1975
+ let width;
1976
+ let height;
1977
+ let scale;
1978
+ const widthMatch = /width\s*:\s*(\d+)/.exec(optionsStr);
1979
+ const heightMatch = /height\s*:\s*(\d+)/.exec(optionsStr);
1980
+ const scaleMatch = /scale\s*:\s*([\d.]+)/.exec(optionsStr);
1981
+ if (widthMatch) width = parseInt(widthMatch[1], 10);
1982
+ if (heightMatch) height = parseInt(heightMatch[1], 10);
1983
+ if (scaleMatch) scale = parseFloat(scaleMatch[1]);
1984
+ if (width || height || scale) {
1985
+ const optionHash = generateOptionHash(width, height, scale);
1986
+ const uniqueKey = `${itemId}:${optionHash}`;
1987
+ if (!renderingOptions.has(uniqueKey)) renderingOptions.set(uniqueKey, {
1988
+ itemId,
1989
+ optionHash,
1990
+ width,
1991
+ height,
1992
+ scale
1993
+ });
1994
+ }
1995
+ processedItems.add(itemId);
1996
+ }
1997
+ const resourcePackNoOptionRegex = /getResourcePack\s*\(\s*["']minecraft:([a-z0-9/_\-\.]+)["']\s*(?!\s*,\s*\{)(?=\s*\))/gi;
1998
+ while ((match = resourcePackNoOptionRegex.exec(content)) !== null) {
1999
+ const itemId = `minecraft:${match[1]}`;
2000
+ usedIds.add(itemId);
2001
+ const uniqueKey = `${itemId}:default`;
2002
+ if (!renderingOptions.has(uniqueKey)) renderingOptions.set(uniqueKey, {
2003
+ itemId,
2004
+ optionHash: "default"
2005
+ });
2006
+ processedItems.add(itemId);
2007
+ }
2008
+ } catch {}
2009
+ }
2010
+
2011
+ //#endregion
2012
+ //#region src/plugin/core.ts
2013
+ const parseConfig = (options) => {
2014
+ const validatedOptions = PluginOptionsSchema.parse(options);
2015
+ return {
2016
+ mcVersion: validatedOptions.mcVersion,
2017
+ resourcePackPath: validatedOptions.resourcePackPath,
2018
+ outputPath: validatedOptions.outputPath ?? CONFIG.OUTPUT_DIR,
2019
+ emptyOutDir: validatedOptions.emptyOutDir ?? CONFIG.EMPTY_OUT_DIR,
2020
+ include: validatedOptions.include ?? CONFIG.INCLUDE,
2021
+ exclude: validatedOptions.exclude ?? CONFIG.EXCLUDE,
2022
+ cacheDir: validatedOptions.cacheDir ?? CONFIG.CACHE_DIR,
2023
+ startUpRenderCacheRefresh: validatedOptions.startUpRenderCacheRefresh ?? CONFIG.START_UP_RENDER_CACHE_REFRESH,
2024
+ logLevel: validatedOptions.logLevel ?? CONFIG.LOG_LEVEL
2025
+ };
2026
+ };
2027
+ var McResourcesCore = class {
2028
+ config;
2029
+ renderingTasks = /* @__PURE__ */ new Map();
2030
+ fileGenerationStarted = false;
2031
+ fileGenerationPromise = null;
2032
+ isGenerated = false;
2033
+ resourcePack = null;
2034
+ versionManager;
2035
+ itemManager;
2036
+ constructor(config) {
2037
+ this.config = parseConfig(config);
2038
+ if (!this.config.cacheDir) throw new Error("Cache directory is not set. Please set the cache directory in the configuration.");
2039
+ this.versionManager = createVersionManager(this.config.cacheDir);
2040
+ this.itemManager = createItemManager(this.config.resourcePackPath, this.versionManager);
2041
+ logger_default.setLogLevel(this.config.logLevel);
2042
+ }
2043
+ /**
2044
+ * ItemManagerを取得
2045
+ */
2046
+ getItemManager() {
2047
+ if (!this.itemManager) throw new Error("ItemManager is not initialized. Call initializeManagers() first.");
2048
+ return this.itemManager;
2049
+ }
2050
+ /**
2051
+ * VersionManagerを取得
2052
+ */
2053
+ getVersionManager() {
2054
+ if (!this.versionManager) throw new Error("VersionManager is not initialized. Call initializeManagers() first.");
2055
+ return this.versionManager;
2056
+ }
2057
+ /**
2058
+ * Dev Modeのアセット取得
2059
+ */
2060
+ async getAssetsInDevMode() {
2061
+ setTimeout(() => {
2062
+ this.versionManager.getAssets(this.config.mcVersion).catch((err) => {
2063
+ logger_default.warn(`Failed to pre-fetch assets: ${err}`);
2064
+ });
2065
+ }, 500);
2066
+ setTimeout(() => {
2067
+ this.itemManager.get3DItems(this.config.mcVersion).catch((err) => {
2068
+ logger_default.warn(`Failed to preload 3D items: ${err}`);
2069
+ });
2070
+ }, 1e3);
2071
+ }
2072
+ /**
2073
+ * Build Modeのアセット取得
2074
+ */
2075
+ async getAssetsInBuildMode() {
2076
+ try {
2077
+ return await this.versionManager.getAssets(this.config.mcVersion);
2078
+ } catch (err) {
2079
+ logger_default.warn(`Failed to pre-fetch assets: ${err}`);
2080
+ throw err;
2081
+ }
2082
+ }
2083
+ /**
2084
+ * ファイル生成関数(遅延生成対応)
2085
+ */
2086
+ async generateFiles(options = {}) {
2087
+ const { isBuild = false, usedIds = void 0, ensureItems3d = false, itemsUrlMap = void 0 } = options;
2088
+ if (this.isGenerated) return;
2089
+ if (this.fileGenerationPromise) return this.fileGenerationPromise;
2090
+ this.fileGenerationPromise = (async () => {
2091
+ let itemManager;
2092
+ try {
2093
+ itemManager = this.getItemManager();
2094
+ } catch {}
2095
+ if (ensureItems3d && !isBuild && itemManager) try {
2096
+ await itemManager.get3DItems(this.config.mcVersion);
2097
+ } catch (err) {
2098
+ logger_default.warn(`Failed to fetch 3D items: ${err}`);
2099
+ }
2100
+ const images = getAllImages(this.config.resourcePackPath);
2101
+ const jsCode = await generateGetResourcePackCode({
2102
+ images,
2103
+ usedIds,
2104
+ itemManager,
2105
+ versionId: this.config.mcVersion,
2106
+ itemsUrlMap
2107
+ });
2108
+ const tsCode = await generateTypeDefinitions({
2109
+ images,
2110
+ itemManager,
2111
+ versionId: this.config.mcVersion
2112
+ });
2113
+ writeFiles(this.config.outputPath, jsCode, tsCode);
2114
+ logger_default.info(chalk.default.bgGreen("Generated") + " TypeScript and JavaScript files");
2115
+ this.isGenerated = true;
2116
+ })();
2117
+ return this.fileGenerationPromise;
2118
+ }
2119
+ /**
2120
+ * ビルド時にアイテムをレンダリング(outputPath配下に保存)
2121
+ */
2122
+ async renderItemsForBuildWithEmit(detectedIds, renderingOptions) {
2123
+ const itemUrlMap = /* @__PURE__ */ new Map();
2124
+ if (detectedIds.size === 0) return itemUrlMap;
2125
+ try {
2126
+ const assetsDirPath = await this.versionManager.getAssets(this.config.mcVersion);
2127
+ if (!this.resourcePack) this.resourcePack = createResourcePack(this.config.resourcePackPath, assetsDirPath);
2128
+ const renderedItemsDir = (0, node_path.join)(this.config.outputPath, "rendered-items");
2129
+ if ((0, node_fs.existsSync)(renderedItemsDir)) {
2130
+ const files = (0, node_fs.readdirSync)(renderedItemsDir);
2131
+ for (const file of files) (0, node_fs.rmSync)((0, node_path.join)(renderedItemsDir, file), {
2132
+ recursive: true,
2133
+ force: true
2134
+ });
2135
+ }
2136
+ (0, node_fs.mkdirSync)(renderedItemsDir, { recursive: true });
2137
+ const renderTargets = [];
2138
+ const processedItems = /* @__PURE__ */ new Set();
2139
+ if (renderingOptions && renderingOptions.size > 0) {
2140
+ for (const [, opt] of renderingOptions) if (detectedIds.has(opt.itemId)) {
2141
+ renderTargets.push({
2142
+ itemId: opt.itemId,
2143
+ optionHash: opt.optionHash,
2144
+ width: opt.width,
2145
+ height: opt.height,
2146
+ scale: opt.scale
2147
+ });
2148
+ processedItems.add(opt.itemId);
2149
+ }
2150
+ }
2151
+ for (const itemId of detectedIds) if (!processedItems.has(itemId)) renderTargets.push({ itemId });
2152
+ logger_default.info(`Rendering ${renderTargets.length} items for build...`);
2153
+ const renderPromises = renderTargets.map(async (target) => {
2154
+ try {
2155
+ const cleanId = target.itemId.replace("minecraft:", "");
2156
+ const fileName = target.optionHash && target.optionHash !== "default" ? `${cleanId}_${target.optionHash}.png` : `${cleanId}.png`;
2157
+ const outputFile = (0, node_path.join)(renderedItemsDir, fileName);
2158
+ const isItemModel = await this.itemManager.isItem2DModel(target.itemId, assetsDirPath);
2159
+ const modelPath = isItemModel ? `item/${cleanId}` : `block/${cleanId}`;
2160
+ const renderOptions = {
2161
+ width: target.width ?? (isItemModel ? CONFIG.TEXTURE_SIZE : CONFIG.WIDTH),
2162
+ height: target.height ?? target.width ?? (isItemModel ? CONFIG.TEXTURE_SIZE : CONFIG.WIDTH),
2163
+ ...target.scale !== void 0 && { scale: target.scale }
2164
+ };
2165
+ if (isItemModel) await this.resourcePack.getRenderer().renderItem(modelPath, outputFile, renderOptions);
2166
+ else await this.resourcePack.getRenderer().renderBlock(modelPath, outputFile, renderOptions);
2167
+ logger_default.info(`Rendered: ${target.itemId} with options: ${JSON.stringify(renderOptions)}`);
2168
+ return {
2169
+ mapKey: target.optionHash ? `${target.itemId}_${target.optionHash}` : target.itemId,
2170
+ relativePath: `./rendered-items/${fileName}`
2171
+ };
2172
+ } catch (err) {
2173
+ logger_default.warn(`Failed to render ${target.itemId} with options ${target.optionHash || "default"}: ${err}`);
2174
+ return null;
2175
+ }
2176
+ });
2177
+ const results = await Promise.all(renderPromises);
2178
+ for (const result of results) if (result) {
2179
+ itemUrlMap.set(result.mapKey, result.relativePath);
2180
+ logger_default.info(`Rendered item saved: ${result.mapKey} -> ${result.relativePath}`);
2181
+ }
2182
+ } catch (error) {
2183
+ logger_default.error(`Failed to render items for build: ${error}`);
2184
+ }
2185
+ return itemUrlMap;
2186
+ }
2187
+ /**
2188
+ * Build
2189
+ */
2190
+ async build(options) {
2191
+ initializeOutputDirectory(this.config.outputPath, this.config.emptyOutDir);
2192
+ try {
2193
+ await this.getAssetsInBuildMode();
2194
+ } catch (err) {
2195
+ logger_default.warn(`Failed to get assets: ${err}`);
2196
+ }
2197
+ const root = process.cwd();
2198
+ const scanResult = scanSourceCode(root, {
2199
+ include: this.config.include,
2200
+ exclude: this.config.exclude,
2201
+ outputPath: this.config.outputPath,
2202
+ distDir: node_path.default.relative(root, options.distDir)
2203
+ });
2204
+ const detectedIds = scanResult.usedIds;
2205
+ const renderingOptions = scanResult.renderingOptions;
2206
+ logger_default.debug(`Rendering options: ${JSON.stringify(Array.from(renderingOptions?.entries() ?? []))}`);
2207
+ const itemsUrlMap = await this.renderItemsForBuildWithEmit(detectedIds, renderingOptions);
2208
+ await this.generateFiles({
2209
+ usedIds: detectedIds.size > 0 ? detectedIds : void 0,
2210
+ itemsUrlMap
2211
+ });
2212
+ }
2213
+ /**
2214
+ * Dev Server Start
2215
+ */
2216
+ async devServerStart() {
2217
+ if (this.config.startUpRenderCacheRefresh) (0, node_fs.rmSync)((0, node_path.join)(this.config.cacheDir, "renders"), {
2218
+ recursive: true,
2219
+ force: true
2220
+ });
2221
+ this.fileGenerationStarted = true;
2222
+ logger_default.debug("Starting file generation in background...");
2223
+ this.generateFiles({ ensureItems3d: true }).catch((err) => {
2224
+ logger_default.warn(`Failed to generate files: ${err}`);
2225
+ });
2226
+ }
2227
+ /**
2228
+ * Dev Server Middleware
2229
+ */
2230
+ async devServerMiddleware(options) {
2231
+ const { next, req, res, isBuild, isGenerated } = options;
2232
+ const { url, headers } = req;
2233
+ if (!isBuild && !isGenerated && !this.fileGenerationStarted) {
2234
+ this.fileGenerationStarted = true;
2235
+ logger_default.debug("Starting file generation in background...");
2236
+ this.generateFiles({ ensureItems3d: true }).catch((err) => {
2237
+ logger_default.warn(`Failed to generate files: ${err}`);
2238
+ });
2239
+ }
2240
+ if (!url?.startsWith("/@hato810424:mc-resources-plugin/minecraft:")) {
2241
+ next();
2242
+ return;
2243
+ }
2244
+ try {
2245
+ const urlObj = new URL(url, `http://${headers.host}`);
2246
+ const minecraftId = urlObj.pathname.replace("/@hato810424:mc-resources-plugin/minecraft:", "");
2247
+ if (!minecraftId) {
2248
+ res.setStatus(400);
2249
+ res.send("Invalid minecraft ID");
2250
+ return;
2251
+ }
2252
+ const assetsDirPath = await this.versionManager.getAssets(this.config.mcVersion);
2253
+ if (!this.resourcePack) this.resourcePack = createResourcePack(this.config.resourcePackPath, assetsDirPath);
2254
+ if (!this.itemManager) this.itemManager = createItemManager(this.config.resourcePackPath, this.versionManager);
2255
+ const isItemModel = await this.itemManager.isItem2DModel(minecraftId, assetsDirPath);
2256
+ const baseSize = isItemModel ? CONFIG.TEXTURE_SIZE : CONFIG.WIDTH;
2257
+ const width = parseInt(urlObj.searchParams.get("width") ?? String(baseSize), 10);
2258
+ const height = parseInt(urlObj.searchParams.get("height") ?? String(width), 10);
2259
+ const scaleParam = urlObj.searchParams.get("scale");
2260
+ const scale = scaleParam ? parseFloat(scaleParam) : void 0;
2261
+ if (isItemModel && width === 16 && height === 16 && scale === void 0) {
2262
+ if (!await this.itemManager.needsTint(this.config.mcVersion, minecraftId)) {
2263
+ const texturePath = await this.itemManager.getItemTexturePath(this.config.mcVersion, minecraftId);
2264
+ if (texturePath && (0, node_fs.existsSync)(texturePath)) {
2265
+ const imageBuffer = (0, node_fs.readFileSync)(texturePath);
2266
+ res.setHeader("Content-Type", "image/png");
2267
+ res.send(imageBuffer);
2268
+ return;
2269
+ }
2270
+ }
2271
+ }
2272
+ const sendResponse = (imageBuffer) => {
2273
+ res.setHeader("Content-Type", "image/png");
2274
+ res.send(imageBuffer);
2275
+ };
2276
+ const cacheKey = `${minecraftId}_${width}x${height}${scale !== void 0 ? `_${scale}` : ""}.png`;
2277
+ const hash = (0, node_crypto.createHash)("md5").update(this.config.resourcePackPath).digest("hex").substring(0, 10);
2278
+ const cacheFile = (0, node_path.join)(this.config.cacheDir, "renders", hash, cacheKey);
2279
+ logger_default.debug(`Processing request: id=${minecraftId}, width=${width}, height=${height}, scale=${scale}, cacheKey=${cacheKey}`);
2280
+ if ((0, node_fs.existsSync)(cacheFile)) {
2281
+ sendResponse((0, node_fs.readFileSync)(cacheFile));
2282
+ logger_default.info(`File cache hit: ${minecraftId}`);
2283
+ return;
2284
+ }
2285
+ if (this.renderingTasks.has(cacheKey)) {
2286
+ sendResponse(await this.renderingTasks.get(cacheKey));
2287
+ return;
2288
+ }
2289
+ const renderPromise = (async () => {
2290
+ logger_default.info(`Rendering ${minecraftId}...`);
2291
+ if (this.fileGenerationPromise) await this.fileGenerationPromise;
2292
+ const renderPath = await this.itemManager.getItemRenderPath(this.config.mcVersion, minecraftId);
2293
+ const renderOptions = {
2294
+ width,
2295
+ height
2296
+ };
2297
+ if (scale !== void 0) renderOptions.scale = scale;
2298
+ let outputPath = cacheFile;
2299
+ if (isItemModel) outputPath = await this.resourcePack.getRenderer().renderItem(renderPath, cacheFile, renderOptions);
2300
+ else outputPath = await this.resourcePack.getRenderer().renderBlock(renderPath, cacheFile, renderOptions);
2301
+ const imageBuffer = (0, node_fs.readFileSync)(outputPath);
2302
+ logger_default.info(`Rendered: ${minecraftId} with options: ${JSON.stringify(renderOptions)}`);
2303
+ return imageBuffer;
2304
+ })();
2305
+ this.renderingTasks.set(cacheKey, renderPromise);
2306
+ try {
2307
+ sendResponse(await renderPromise);
2308
+ } finally {
2309
+ this.renderingTasks.delete(cacheKey);
2310
+ }
2311
+ } catch (error) {
2312
+ logger_default.error(`Failed to render minecraft item: ${error}`);
2313
+ const urlObj = new URL(url, `http://${headers.host}`);
2314
+ const extractedId = urlObj.pathname.replace("/@hato810424:mc-resources-plugin/minecraft:", "");
2315
+ const width = parseInt(urlObj.searchParams.get("width") ?? String(CONFIG.WIDTH), 10);
2316
+ const height = parseInt(urlObj.searchParams.get("height") ?? String(width), 10);
2317
+ const scaleParam = urlObj.searchParams.get("scale");
2318
+ const scale = scaleParam ? parseFloat(scaleParam) : void 0;
2319
+ const errorCacheKey = `${extractedId}_${width}x${height}${scale !== void 0 ? `_${scale}` : ""}.png`;
2320
+ this.renderingTasks.delete(errorCacheKey);
2321
+ res.setStatus(500);
2322
+ res.send("Failed to render minecraft item");
2323
+ }
2324
+ }
2325
+ };
2326
+
2327
+ //#endregion
2328
+ Object.defineProperty(exports, 'McResourcesCore', {
2329
+ enumerable: true,
2330
+ get: function () {
2331
+ return McResourcesCore;
2332
+ }
2333
+ });