UNPKG

@minecraft/creator-tools

Version:

Minecraft Creator Tools command line and libraries.

188 lines (187 loc) 10.3 kB
"use strict"; // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.CustomDimensionWorldDataTest = void 0; const ProjectInfoItem_1 = __importDefault(require("./ProjectInfoItem")); const IProjectItemData_1 = require("../app/IProjectItemData"); const MCWorld_1 = __importDefault(require("../minecraft/MCWorld")); const IInfoItemData_1 = require("./IInfoItemData"); const ProjectUtilities_1 = __importStar(require("../app/ProjectUtilities")); const CUSTOM_DIMENSION_ID_START = 1000; const VanillaIdToString = { 0: "Overworld", 1: "Nether", 2: "The End", }; var CustomDimensionWorldDataTest; (function (CustomDimensionWorldDataTest) { CustomDimensionWorldDataTest[CustomDimensionWorldDataTest["nameIdMappingTableMissing"] = 101] = "nameIdMappingTableMissing"; CustomDimensionWorldDataTest[CustomDimensionWorldDataTest["vanillaDimensionChunkData"] = 102] = "vanillaDimensionChunkData"; CustomDimensionWorldDataTest[CustomDimensionWorldDataTest["unclaimedDimensionMappings"] = 103] = "unclaimedDimensionMappings"; })(CustomDimensionWorldDataTest || (exports.CustomDimensionWorldDataTest = CustomDimensionWorldDataTest = {})); class CustomDimensionWorldDataInfoGenerator { id = "CDWORLDDATA"; title = "Custom Dimension World Data Validation"; /** * Per-project cache for {@link isAddOnContextForWorld}. Avoids re-running * the (manifest-reading) meta-category detection on every world item in a * multi-world project. */ #addOnContextCache = new WeakMap(); /** * Rule 102 ("Vanilla Dimension Chunk Data") is meaningful only when the * surrounding project is shaped like an add-on. For a standalone world * template — or any "general world data" project — Overworld/Nether/End * chunk data is normal and expected, not a defect. * * Delegates to the canonical {@link ProjectUtilities.getMetaCategory}, the * shared classifier used elsewhere (e.g. {@link TextureImageInfoGenerator}) * to gate add-on-only validations. We use `getMetaCategory` rather than * `getIsAddon` because the latter excludes any project containing world * items — but this generator only ever runs on world items, so that check * would always fail. `getMetaCategory` evaluates pack shape independently * and returns `worldTemplate` (not `addOn`) when a template manifest is * present, which gives us the correct gating for free. * * Result is cached per Project for the lifetime of the generator instance. */ async isAddOnContextForWorld(project) { if (!project) { return false; } const cached = this.#addOnContextCache.get(project); if (cached) { return cached; } const promise = ProjectUtilities_1.default.getMetaCategory(project).then((cat) => cat === ProjectUtilities_1.ProjectMetaCategory.addOn); this.#addOnContextCache.set(project, promise); return promise; } getTopicData(topicId) { switch (topicId) { case CustomDimensionWorldDataTest.nameIdMappingTableMissing: return { title: "Name-ID Mapping Table Missing", description: "The LevelDB contains custom dimension chunk data but is missing the required DimensionNameIdTable key. " + "This table is needed to map dimension names to IDs for correct chunk data association on load.", }; case CustomDimensionWorldDataTest.vanillaDimensionChunkData: return { title: "Vanilla Dimension Chunk Data", description: "The LevelDB contains chunk data for vanilla dimensions (Overworld, Nether, or The End). " + "Creator content must only include chunk data for custom dimensions (ID >= 1000).", }; case CustomDimensionWorldDataTest.unclaimedDimensionMappings: return { title: "Unclaimed Dimension Mappings", description: "The DimensionNameIdTable contains name-ID mappings for dimensions that have no corresponding " + "chunk data in the LevelDB. This may indicate incomplete cleanup or leftover development data.", }; default: return undefined; } } summarize(info, infoSet) { const customDimensionErrors = infoSet.getCount(this.id, CustomDimensionWorldDataTest.vanillaDimensionChunkData); const nameIdTableMissing = infoSet.getCount(this.id, CustomDimensionWorldDataTest.nameIdMappingTableMissing); const unclaimedMappings = infoSet.getCount(this.id, CustomDimensionWorldDataTest.unclaimedDimensionMappings); // Only add summary fields when there are actual findings. // This avoids adding zero-value fields to validation output for content // that has no world data (e.g., add-on packs), keeping existing baselines stable. if (customDimensionErrors > 0 || nameIdTableMissing > 0 || unclaimedMappings > 0) { info.customDimensionErrors = customDimensionErrors; info.nameIdTableMissing = nameIdTableMissing; info.unclaimedMappings = unclaimedMappings; } } async generate(projectItem, contentIndex, options) { const items = []; if (projectItem.itemType !== IProjectItemData_1.ProjectItemType.MCWorld && projectItem.itemType !== IProjectItemData_1.ProjectItemType.MCTemplate && projectItem.itemType !== IProjectItemData_1.ProjectItemType.worldFolder) { return items; } const mcworld = await MCWorld_1.default.ensureOnItem(projectItem); if (!mcworld) { return items; } await mcworld.loadLevelDb(false); if (!mcworld.levelDb) { return items; } const dimensionIds = mcworld.dimensionIdsInChunks; // No chunk data at all — nothing to validate if (dimensionIds.size === 0) { return items; } const hasCustomDimChunks = Array.from(dimensionIds).some((id) => id >= CUSTOM_DIMENSION_ID_START); const vanillaDimIds = Array.from(dimensionIds).filter((id) => id < CUSTOM_DIMENSION_ID_START); // Rule 102: Vanilla dimension chunk data found in LevelDB. // Only meaningful when this project is shaped like an add-on that bundles // a starter world. World templates and "general world data" projects are // expected to contain vanilla chunk data — emitting an error there is a // false positive. const inAddOnContext = vanillaDimIds.length > 0 ? await this.isAddOnContextForWorld(projectItem.project) : false; if (inAddOnContext) { for (const dimId of vanillaDimIds) { const dimName = VanillaIdToString[dimId] ?? `ID ${dimId}`; items.push(new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.error, this.id, CustomDimensionWorldDataTest.vanillaDimensionChunkData, `LevelDB contains chunk data for vanilla dimension '${dimName}' (ID ${dimId}). ` + "Creator content must only include chunk data for custom dimensions (ID >= 1000).", projectItem, dimId)); } } // Rule 101: DimensionNameIdTable required when custom dimension chunk data is present if (hasCustomDimChunks && !mcworld.hasDimensionNameIdTable) { items.push(new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.error, this.id, CustomDimensionWorldDataTest.nameIdMappingTableMissing, "LevelDB contains custom dimension chunk data but is missing the required DimensionNameIdTable key. " + "This table is needed by Chunker to correctly associate dimension names with their chunk data.", projectItem)); } // Rule 103: Unclaimed dimension mappings (name-ID entries with no corresponding chunk data) const nameIdTable = mcworld.dimensionNameIdTable; if (nameIdTable && nameIdTable.size > 0) { for (const [dimName, dimId] of nameIdTable) { if (!dimensionIds.has(dimId)) { items.push(new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.warning, this.id, CustomDimensionWorldDataTest.unclaimedDimensionMappings, `DimensionNameIdTable contains mapping '${dimName}' -> ID ${dimId} ` + "but no corresponding chunk data exists in the LevelDB.", projectItem, dimName)); } } } return items; } } exports.default = CustomDimensionWorldDataInfoGenerator;