@minecraft/creator-tools
Version:
Minecraft Creator Tools command line and libraries.
231 lines (230 loc) • 9.88 kB
JavaScript
"use strict";
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const ste_events_1 = require("ste-events");
const StorageUtilities_1 = __importDefault(require("../storage/StorageUtilities"));
const IProjectItemData_1 = require("../app/IProjectItemData");
const ItemTypeDefinition_1 = __importDefault(require("./ItemTypeDefinition"));
const Utilities_1 = __importDefault(require("../core/Utilities"));
const Database_1 = __importDefault(require("./Database"));
const Log_1 = __importDefault(require("../core/Log"));
class LootTableBehaviorDefinition {
_file;
_isLoaded = false;
_loadedWithComments = false;
data;
_onLoaded = new ste_events_1.EventDispatcher();
get isLoaded() {
return this._isLoaded;
}
get file() {
return this._file;
}
get onLoaded() {
return this._onLoaded.asEvent();
}
set file(newFile) {
this._file = newFile;
}
_ensureDataInitialized() {
if (this.data === undefined) {
this.data = { pools: [] };
}
}
static async ensureOnFile(file, loadHandler) {
let ltb;
if (file.manager === undefined) {
ltb = new LootTableBehaviorDefinition();
ltb.file = file;
file.manager = ltb;
}
if (file.manager !== undefined && file.manager instanceof LootTableBehaviorDefinition) {
ltb = file.manager;
if (!ltb.isLoaded) {
if (loadHandler) {
ltb.onLoaded.subscribe(loadHandler);
}
await ltb.load();
}
}
return ltb;
}
getTargetItemTypeIdList() {
if (!this.data || !this.data.pools || !Array.isArray(this.data.pools)) {
return;
}
const targetItems = [];
for (const pool of this.data.pools) {
if (pool.entries && Array.isArray(pool.entries)) {
for (const entry of pool.entries) {
if (entry.type === "item" && entry.name) {
targetItems.push(entry.name);
}
}
}
}
return targetItems;
}
getTargetLootTablePathList() {
if (!this.data || !this.data.pools || !Array.isArray(this.data.pools)) {
return;
}
const targetLootTablePaths = [];
for (const pool of this.data.pools) {
if (pool.entries && Array.isArray(pool.entries)) {
for (const entry of pool.entries) {
if (entry.type === "loot_table" && entry.name) {
targetLootTablePaths.push(this.canonicalizeLootTablePath(entry.name));
}
}
}
}
return targetLootTablePaths;
}
persist() {
if (this._file === undefined) {
return false;
}
if (!this.data) {
Log_1.default.unexpectedUndefined("ITRDP");
return false;
}
return this._file.setObjectContentIfSemanticallyDifferent(this.data);
}
/**
* Loads the definition from the file.
* @param preserveComments If true, uses comment-preserving JSON parsing for edit/save cycles.
* If false (default), uses efficient standard JSON parsing.
* Can be called again with true to "upgrade" a read-only load to read/write.
*/
async load(preserveComments = false) {
// If already loaded with comments, we have the "best" version - nothing more to do
if (this._isLoaded && this._loadedWithComments) {
return;
}
// If already loaded without comments and caller doesn't need comments, we're done
if (this._isLoaded && !preserveComments) {
return;
}
if (this._file === undefined) {
return;
}
if (!this._file.isContentLoaded) {
await this._file.loadContent();
}
if (this._file.content === null || this._file.content instanceof Uint8Array) {
this._isLoaded = true;
this._loadedWithComments = preserveComments;
this._onLoaded.dispatch(this, this);
return;
}
// Use comment-preserving parser only when needed for editing
this.data = preserveComments
? StorageUtilities_1.default.getJsonObjectWithComments(this._file)
: StorageUtilities_1.default.getJsonObject(this._file);
this._isLoaded = true;
this._loadedWithComments = preserveComments;
this._onLoaded.dispatch(this, this);
}
canonicalizeLootTablePath(lootTablePath) {
lootTablePath = Utilities_1.default.ensureNotStartsWithSlash(lootTablePath);
const lastPeriod = lootTablePath.lastIndexOf(".");
if (lastPeriod > 0) {
lootTablePath = lootTablePath.substring(0, lastPeriod);
}
return lootTablePath.toLowerCase();
}
async addChildItems(project, item, index) {
let itemList = this.getTargetItemTypeIdList();
let lootTableList = this.getTargetLootTablePathList();
if (index) {
// Use pre-built index for O(1) lookups
if (itemList) {
for (const itemTypeId of itemList) {
if (typeof itemTypeId === "string") {
const matchingItems = index.getItemsById(index.itemTypesById, itemTypeId);
if (matchingItems.length > 0) {
for (const candItem of matchingItems) {
item.addChildItem(candItem);
}
itemList = Utilities_1.default.removeItemInArray(itemTypeId, itemList);
}
}
}
}
// TODO: lootTablesByPath index stores by exact projectPath, but lookup needs
// endsWith() matching. Fall back to scanning for now until a suffix-based index is added.
if (lootTableList) {
const lootTableItems = project.getItemsByType(IProjectItemData_1.ProjectItemType.lootTableBehavior);
for (const candItem of lootTableItems) {
if (candItem.primaryFile) {
let lootTablePath = await candItem.getPackRelativePath();
if (lootTablePath) {
lootTablePath = this.canonicalizeLootTablePath(lootTablePath);
if (lootTableList.includes(lootTablePath)) {
item.addChildItem(candItem);
lootTableList = Utilities_1.default.removeItemInArray(lootTablePath, lootTableList);
}
}
}
}
}
}
else {
// Fallback: scan all items
const itemsCopy = project.getItemsCopy();
for (const candItem of itemsCopy) {
if (candItem.itemType === IProjectItemData_1.ProjectItemType.itemTypeBehavior && itemList) {
if (!candItem.isContentLoaded) {
await candItem.loadContent();
}
if (candItem.primaryFile) {
const itemType = await ItemTypeDefinition_1.default.ensureOnFile(candItem.primaryFile);
if (itemType) {
if (itemList.includes(itemType.id)) {
item.addChildItem(candItem);
itemList = Utilities_1.default.removeItemInArray(itemType.id, itemList);
continue;
}
}
}
}
else if (candItem.itemType === IProjectItemData_1.ProjectItemType.lootTableBehavior && lootTableList) {
if (!candItem.isContentLoaded) {
await candItem.loadContent();
}
if (candItem.primaryFile) {
let lootTablePath = await candItem.getPackRelativePath();
if (lootTablePath) {
lootTablePath = this.canonicalizeLootTablePath(lootTablePath);
if (lootTableList.includes(lootTablePath)) {
item.addChildItem(candItem);
lootTableList = Utilities_1.default.removeItemInArray(lootTablePath, lootTableList);
continue;
}
}
}
}
}
}
if (itemList && Array.isArray(itemList)) {
for (const itemTypeId of itemList) {
if (typeof itemTypeId === "string") {
const isVanilla = await Database_1.default.isVanillaToken(itemTypeId);
item.addUnfulfilledRelationship(itemTypeId, IProjectItemData_1.ProjectItemType.itemTypeBehavior, isVanilla);
}
}
}
if (lootTableList && Array.isArray(lootTableList)) {
for (const lootTablePath of lootTableList) {
const isVanilla = await Database_1.default.matchesVanillaPathFromIndex(lootTablePath);
item.addUnfulfilledRelationship(lootTablePath, IProjectItemData_1.ProjectItemType.lootTableBehavior, isVanilla);
}
}
}
}
exports.default = LootTableBehaviorDefinition;