diff --git a/app/public/data/forms/convert.form.json b/app/public/data/forms/convert.form.json new file mode 100644 index 00000000..00072891 --- /dev/null +++ b/app/public/data/forms/convert.form.json @@ -0,0 +1,73 @@ +{ + "title": "Conversion", + "fields": [ + { + "id": "name", + "title": "Name", + "dataType": 2 + }, + { + "id": "targetJavaVersion", + "title": "Target Java Version", + "dataType": 5, + "choices": [ + { + "id": 0, + "title": "1.8.8" + }, + { + "id": 1, + "title": "1.9.3" + }, + { + "id": 2, + "title": "1.10.2" + }, + { + "id": 3, + "title": "1.11.2" + }, + { + "id": 4, + "title": "1.12.2" + }, + { + "id": 5, + "title": "1.13.2" + }, + { + "id": 6, + "title": "1.14.4" + }, + { + "id": 7, + "title": "1.15.2" + }, + { + "id": 8, + "title": "1.16.5" + }, + { + "id": 9, + "title": "1.17.1" + }, + { + "id": 10, + "title": "1.18.2" + }, + { + "id": 11, + "title": "1.19.4" + }, + { + "id": 12, + "title": "1.20.6" + }, + { + "id": 13, + "title": "1.21.0" + } + ] + } + ] +} diff --git a/app/public/data/forms/dataform.form.json b/app/public/data/forms/dataform.form.json index 69b9471d..c12d9543 100644 --- a/app/public/data/forms/dataform.form.json +++ b/app/public/data/forms/dataform.form.json @@ -156,7 +156,7 @@ "visibility": [ { "field": "dataType", - "comparison": 0, + "comparison": "=", "anyValues": [0, 3] } ] @@ -168,7 +168,7 @@ "visibility": [ { "field": "dataType", - "comparison": 0, + "comparison": "=", "anyValues": [0, 3] } ] diff --git a/app/public/data/forms/entity_minecraft_ageable.form.json b/app/public/data/forms/entity_minecraft_ageable.form.json index 75fe9ab3..2c203b40 100644 --- a/app/public/data/forms/entity_minecraft_ageable.form.json +++ b/app/public/data/forms/entity_minecraft_ageable.form.json @@ -1,4 +1,5 @@ { + "title": "Ageable", "description": "Adds a timer for the entity to grow up. It can be accelerated by giving the entity the items it likes as defined by feed_items.", "fields": [ { diff --git a/app/public/data/forms/entity_minecraft_anger_level.form.json b/app/public/data/forms/entity_minecraft_anger_level.form.json new file mode 100644 index 00000000..c1771259 --- /dev/null +++ b/app/public/data/forms/entity_minecraft_anger_level.form.json @@ -0,0 +1,66 @@ +{ + "title": "Anger Level", + "description": "Compels the entity to track anger towards a set of nuisances.", + "fields": [ + { + "id": "anger_decrement_interval", + "description": "Anger level will decay over time. Defines how often anger towards all nuisances will decrease by on.", + "dataType": 2 + }, + { + "id": "sound_interval", + "description": "Anger boost applied to angry threshold when the entity gets angry.", + "dataType": 18 + }, + { + "id": "duration", + "description": "The amount of time in seconds that the entity will be angry.", + "dataType": 0 + }, + { + "id": "duration_delta", + "description": "Variance in seconds added to the duration [-delta, delta].", + "dataType": 0 + }, + { + "id": "filters", + "title": "Anger exemption filters", + "description": "Filter out mob types that it should not attack while angry (other Piglins)", + "dataType": 20 + }, + { + "id": "broadcast_anger", + "description": "If set, other entities of the same entity definition within the broadcastRange will also become angry", + "dataType": 1, + "defaultValue": false + }, + { + "id": "broadcast_anger_on_attack", + "description": "If set, other entities of the same entity definition within the broadcastRange will also become angry whenever this mob attacks", + "dataType": 1, + "defaultValue": false + }, + { + "id": "broadcast_range", + "description": "Distance in blocks within which other entities of the same entity type will become angry", + "dataType": 0, + "defaultValue": 20 + }, + { + "id": "broadcast_targets", + "description": "A list of entity families to broadcast anger to", + "dataType": 17 + }, + { + "id": "broadcast_filters", + "description": "Conditions that make this entry in the list valid", + "dataType": 20 + }, + { + "id": "calm_event", + "description": "Event to fire when this entity is calmed down", + "dataType": 8, + "lookupId": "entityTypeEvents" + } + ] +} diff --git a/app/public/data/forms/entity_minecraft_balloonable.form.json b/app/public/data/forms/entity_minecraft_balloonable.form.json index f1959317..4b5b9d76 100644 --- a/app/public/data/forms/entity_minecraft_balloonable.form.json +++ b/app/public/data/forms/entity_minecraft_balloonable.form.json @@ -1,10 +1,12 @@ { + "title": "Balloonable", "description": "Allows this entity to have a balloon attached and defines the conditions and events for this entity when is ballooned.", "fields": [ { "id": "soft_distance", "description": "Distance in blocks at which the 'spring' effect that lifts it.", "dataType": 3, + "experienceType": 3, "minValue": 0, "maxValue": 30 }, @@ -12,6 +14,7 @@ "id": "max_distance", "description": "Distance in blocks at which the balloon breaks.", "dataType": 3, + "experienceType": 3, "minValue": 0, "maxValue": 30 }, @@ -31,6 +34,7 @@ "id": "mass", "description": "Mass that this entity will have when computing balloon pull forces.", "dataType": 3, + "experienceType": 3, "minValue": 0, "maxValue": 30 } diff --git a/app/public/data/forms/entity_minecraft_breathable.form.json b/app/public/data/forms/entity_minecraft_breathable.form.json index ba659365..85b05bc4 100644 --- a/app/public/data/forms/entity_minecraft_breathable.form.json +++ b/app/public/data/forms/entity_minecraft_breathable.form.json @@ -1,4 +1,5 @@ { + "title": "Breathable", "description": "Defines what blocks an entity can breathe in and gives them the ability to suffocate.", "fields": [ { diff --git a/app/public/data/forms/entity_minecraft_leashable.form.json b/app/public/data/forms/entity_minecraft_leashable.form.json new file mode 100644 index 00000000..13541bf1 --- /dev/null +++ b/app/public/data/forms/entity_minecraft_leashable.form.json @@ -0,0 +1,38 @@ +{ + "title": "Leashable", + "description": "Describes how this mob can be leashed to other items", + "fields": [ + { + "id": "can_be_stolen", + "dataType": 1, + "defaultValue": false + }, + { + "id": "hard_distance", + "description": "Distance in blocks at which the leash stiffens, restricting movement.", + "dataType": 0, + "experienceType": 3, + "minValue": 0, + "suggestedMaxValue": 20, + "defaultValue": 6 + }, + { + "id": "soft_distance", + "description": "Distance in blocks at which the 'spring' effect starts acting to keep this entity close to the entity that leashed it.", + "dataType": 0, + "experienceType": 3, + "minValue": 0, + "suggestedMaxValue": 20, + "defaultValue": 4 + }, + { + "id": "max_distance", + "description": "Distance in blocks it which the leash breaks.", + "dataType": 0, + "experienceType": 3, + "minValue": 0, + "suggestedMaxValue": 20, + "defaultValue": 0 + } + ] +} diff --git a/app/public/data/forms/entity_type_resource.form.json b/app/public/data/forms/entity_type_resource.form.json index 8552ec41..5c72fe3b 100644 --- a/app/public/data/forms/entity_type_resource.form.json +++ b/app/public/data/forms/entity_type_resource.form.json @@ -7,27 +7,22 @@ }, { "id": "materials", - "title": "Materials", "dataType": 24 }, { "id": "textures", - "title": "Textures", "dataType": 24 }, { "id": "geometry", - "title": "Geometry", "dataType": 24 }, { "id": "animations", - "title": "Animations", "dataType": 24 }, { "id": "render_controllers", - "title": "Render Controllers", "dataType": 17 } ] diff --git a/app/public/preload.js b/app/public/preload.js index 6c25d325..4c2d2243 100644 --- a/app/public/preload.js +++ b/app/public/preload.js @@ -124,6 +124,9 @@ contextBridge.exposeInMainWorld("api", { ipcRenderer.invoke("asyncselectDirectory", position + "|" + data); return; + case "asyncconvertFile": + return ipcRenderer.invoke("asyncconvertFile", position + "|" + data); + case "asyncstartWebSocketServer": return ipcRenderer.invoke("asyncstartWebSocketServer", position + "|" + data); @@ -187,7 +190,6 @@ contextBridge.exposeInMainWorld("api", { case "asyncwindowRightSide": case "asyncupdateIAgree": case "asyncgetWindowState": - case "asyncfsFolderExists": case "asyncgetDirname": case "asyncaugerLogin": return ipcRenderer.invoke(commandName, position + "|" + data); @@ -203,6 +205,7 @@ contextBridge.exposeInMainWorld("api", { return ipcRenderer.invoke(commandName, position + "|" + data); + case "asyncfsFolderExists": case "asyncfsMkdir": case "asyncfsReaddir": _validateFolderPath(data); diff --git a/app/reslist/packs.resources.json b/app/reslist/packs.resources.json index 561ad770..087b61e1 100644 --- a/app/reslist/packs.resources.json +++ b/app/reslist/packs.resources.json @@ -1,5 +1,5 @@ { - "url": "https://github.com/Mojang/bedrock-samples/archive/1c48a87a35864df286f5a8403a88706cdede963d.zip", + "url": "https://github.com/Mojang/bedrock-samples/archive/14404383059147f3f19aae34836c1e8f209bd2df.zip", "ignoreFirstFolder": true, "exclude": [ "documentation/", diff --git a/app/site/index.body.html b/app/site/index.body.html index 147fc1a5..de31d8a5 100644 --- a/app/site/index.body.html +++ b/app/site/index.body.html @@ -9,6 +9,19 @@ var siteConsent = null; +window.getUserConsentDetails = function() { + if (siteConsent) { + return siteConsent.getConsent(); + } + + return { + Required: true, + Analytics: true, + SocialMedia: false, + Advertising: false + }; +} + if (WcpConsent) { WcpConsent.init("en-US", "cookie-banner", function (err, _siteConsent) { if (err != undefined) { @@ -16,15 +29,16 @@ } else { siteConsent = _siteConsent; - if (oneDS && oneDS.ApplicationInsights) { + if (oneDS && oneDS.ApplicationInsights && !navigator.globalPrivacyControl) { const analytics = new oneDS.ApplicationInsights(); analytics.initialize({ instrumentationKey: "1e1425454dbc4c25b1be2762598df0b6-7f4669a7-d9f8-4e51-9bba-266dcfc0dc00-7638", disableCookiesUsage: !siteConsent || !siteConsent.getConsent || (siteConsent.isConsentRequired && !siteConsent.getConsent().Analytics), propertyConfiguration: { + gpcDataSharingOptIn: false, callback: { - userConsentDetails: siteConsent ? siteConsent.getConsent : null + userConsentDetails: window.getUserConsentDetails }, }, webAnalyticsConfiguration: { diff --git a/app/src/UX/App.tsx b/app/src/UX/App.tsx index 4d57d0ea..2879be1a 100644 --- a/app/src/UX/App.tsx +++ b/app/src/UX/App.tsx @@ -17,7 +17,7 @@ import MCWorld from "../minecraft/MCWorld"; import ProjectItem from "../app/ProjectItem"; import ZipStorage from "../storage/ZipStorage"; import ProjectUtilities from "../app/ProjectUtilities"; -import { LocalFolderType, LocalGalleryCommand } from "./LocalGalleryCommand"; +import { LocalFolderType } from "./LocalGalleryCommand"; import WebUtilities from "./WebUtilities"; import ProjectEditorUtilities, { ProjectEditorMode } from "./ProjectEditorUtilities"; import HttpStorage from "../storage/HttpStorage"; @@ -215,8 +215,6 @@ export default class App extends Component { } else { this.state = newState; } - - // Log.debug("Setting state with new project '" + newProject.name + "'"); } } } @@ -372,9 +370,15 @@ export default class App extends Component { if (firstSlash > 1) { const openToken = openQuery.substring(0, firstSlash).toLowerCase(); - const openData = openQuery.substring(firstSlash + 1, openQuery.length); + let openData = openQuery.substring(firstSlash + 1, openQuery.length); if (openToken === "gp") { + const lastPeriod = openData.lastIndexOf("."); + + if (lastPeriod > 0) { + openData = openData.substring(lastPeriod); + } + this._ensureProjectFromGalleryId(openData, updateContent); } } @@ -708,6 +712,7 @@ export default class App extends Component { proj.originalGitHubRepoName === gitHubRepoName && proj.originalGitHubBranch === gitHubBranch && proj.originalGitHubFolder === gitHubFolder && + (sampleId === undefined || proj.originalSampleId === sampleId) && updateContent === undefined ) { await proj.ensureInflated(); @@ -1231,14 +1236,6 @@ export default class App extends Component { } } - private _handleLocalGalleryCommand(command: LocalGalleryCommand, folderType: LocalFolderType, folder: IFolder) { - switch (command) { - case LocalGalleryCommand.ensureAndOpenProjectFromFolder: - this._newProjectFromMinecraftFolder(folderType, folder); - break; - } - } - private async _handleProjectSelected(project: Project) { await project.ensureLoadedFromFile(); await project.ensureInflated(); diff --git a/app/src/UX/ComponentSetEditor.css b/app/src/UX/ComponentSetEditor.css index fa832fb8..195dea9a 100644 --- a/app/src/UX/ComponentSetEditor.css +++ b/app/src/UX/ComponentSetEditor.css @@ -23,6 +23,15 @@ min-height: 220px; } +.cose-componentWrapper { + margin-top: 1px; + margin-bottom: 1px; + + padding: 10px 6px 10px 6px; + border: 2px outset; + font-size: small; +} + .cose-componentForm .ui-form__input { padding-top: 3px; margin-bottom: 4px; @@ -58,21 +67,36 @@ .cose-extraArea { margin-bottom: 5px; } + .cose-componentList { grid-column: 1; overflow-y: scroll; overflow-x: hidden; + border-left: solid 1px; + border-bottom: solid 1px; + border-top: solid 1px; +} + +.cose-componentList li { + padding-left: 5px; + padding-right: 2px; +} + +.cose-componentList .ui-list__itemcontent { + margin-right: 3px; } .cose-componentBin { padding: 4px; - border-radius: 4px; height: 100%; min-height: 200px; vertical-align: top; grid-column: 2; overflow-y: scroll; overflow-x: hidden; + border-right: solid 1px; + border-bottom: solid 1px; + border-top: solid 1px; } .cose-componentBin:first-child { @@ -84,17 +108,22 @@ padding: 3px; grid-column-start: 1; grid-column-end: 3; - grid-template-columns: 7px 7px 1fr; + grid-template-columns: 190px 1fr 100px; } .cose-titleArea { - grid-column: 1; + grid-column: 2; + grid-row: 1; + padding-top: 5px; } .cose-toolBarArea { - grid-column: 2; + grid-column: 3; + grid-row: 1; } .cose-extraArea { - grid-column: 3; + grid-column: 1; + grid-row: 1; + padding-left: 5px; } diff --git a/app/src/UX/ComponentSetEditor.tsx b/app/src/UX/ComponentSetEditor.tsx index f71f05a2..2894b128 100644 --- a/app/src/UX/ComponentSetEditor.tsx +++ b/app/src/UX/ComponentSetEditor.tsx @@ -11,6 +11,7 @@ interface IComponentSetEditorProps { componentSetItem: IManagedComponentSetItem; isDefault: boolean; heightOffset: number; + title?: string; theme: ThemeInput; } @@ -170,9 +171,17 @@ export default class ComponentSetEditor extends Component + {Utilities.humanifyMinecraftName(component.id)} + + ), }); if (component && component.id) { @@ -234,6 +243,10 @@ export default class ComponentSetEditor extends Component; + if (this.props.title) { + title = {this.props.title}; + } + const areaHeight = "calc(100vh - " + String(this.props.heightOffset + 34) + "px)"; return ( @@ -262,6 +275,7 @@ export default class ComponentSetEditor extends Component {componentForms} diff --git a/app/src/UX/DataFormEditor.tsx b/app/src/UX/DataFormEditor.tsx index 3da4dfd8..417373c9 100644 --- a/app/src/UX/DataFormEditor.tsx +++ b/app/src/UX/DataFormEditor.tsx @@ -110,11 +110,12 @@ export default class DataFormEditor extends Component
{dform.id}
diff --git a/app/src/UX/EntityTypeEditor.css b/app/src/UX/EntityTypeEditor.css index 0424ba49..2d4e50a3 100644 --- a/app/src/UX/EntityTypeEditor.css +++ b/app/src/UX/EntityTypeEditor.css @@ -18,7 +18,7 @@ .ete-mainArea { display: grid; grid-template-columns: 1fr; - grid-template-rows: 55px 1fr; + grid-template-rows: 45px 1fr; } .ete-componentEditorInterior { @@ -81,8 +81,7 @@ .ete-header { font-size: 16pt; padding-top: 10px; - padding-left: 30px; - padding-bottom: 13px; + padding-left: 36px; } .ete-componentHeader { @@ -107,7 +106,7 @@ .ete-toolBarArea { grid-column: 1; width: 100%; - padding-top: 24px; + padding-top: 13px; padding-left: 29px; } diff --git a/app/src/UX/EntityTypeEditor.tsx b/app/src/UX/EntityTypeEditor.tsx index 66b30357..836d3c04 100644 --- a/app/src/UX/EntityTypeEditor.tsx +++ b/app/src/UX/EntityTypeEditor.tsx @@ -306,12 +306,13 @@ export default class EntityTypeEditor extends Component} - text={"Properties & Behavior"} + text={"Properties"} isCompact={isButtonCompact} isSelected={this.state.mode === EntityTypeEditorMode.properties} theme={this.props.theme} @@ -390,7 +391,7 @@ export default class EntityTypeEditor extends Component} - text={"Loot Table"} + text={"Loot"} isCompact={isButtonCompact} isSelected={this.state.mode === EntityTypeEditorMode.loot} theme={this.props.theme} @@ -411,9 +412,20 @@ export default class EntityTypeEditor extends Component; + if (this.state) { let selItem = this.state.selectedItem; if ( @@ -431,6 +443,7 @@ export default class EntityTypeEditor extends Component @@ -458,7 +471,8 @@ export default class EntityTypeEditor extends Component @@ -492,6 +506,7 @@ export default class EntityTypeEditor extends Component {itemInterior} @@ -618,7 +634,7 @@ export default class EntityTypeEditor extends Component ); diff --git a/app/src/UX/EntityTypeResourceEditor.tsx b/app/src/UX/EntityTypeResourceEditor.tsx index 93a4e328..9ed61283 100644 --- a/app/src/UX/EntityTypeResourceEditor.tsx +++ b/app/src/UX/EntityTypeResourceEditor.tsx @@ -160,7 +160,7 @@ export default class EntityTypeResourceEditor extends Component< } const definitionFile = this.state.fileToEdit.manager as EntityTypeResourceDefinition; - const def = definitionFile.dataWrapper; + const def = definitionFile._dataWrapper; if (def === undefined) { return
Loading definition...
; diff --git a/app/src/UX/EventActionDesign.css b/app/src/UX/EventActionDesign.css index 47c1f833..44650d53 100644 --- a/app/src/UX/EventActionDesign.css +++ b/app/src/UX/EventActionDesign.css @@ -3,6 +3,7 @@ min-width: calc(100% - 2px); max-width: calc(100% - 2px); overflow-y: auto; + padding: 4px 20px 4px 20px; } .ead-headerTransition { @@ -12,35 +13,86 @@ } .ead-header { - font-size: 16pt; padding-top: 15px; - padding-left: 30px; padding-bottom: 8px; - background: #242425; + font-weight: bold; + font-size: large; } -.ead-componentHeader { - padding-top: 20px; - font-size: 14pt; - padding: 12px; - padding-top: 40px; - padding-bottom: 14px; +.ead-codeSnippet { + background-color: black; + font-family: "Consolas", "Courier New", Courier, monospace; + padding: 3px; + text-wrap: nowrap; } -.ead-extraArea { - margin-bottom: 5px; +.ead-cgToggle { + border: outset 1px; + display: inline-block; + margin-right: 3px; + margin-bottom: 3px; + box-shadow: none !important; + text-wrap: wrap; + padding: 0px 8px 0px 2px !important; } -.ead-commands { +.ead-addTriggerArea { + margin-top: 10px; display: grid; - padding: 3px; - grid-template-columns: 200px 1fr; + grid-template-columns: 100px 230px 1fr; + margin-bottom: 10px; } -.ead-toolBarArea { +.ead-addTriggerInstruction { grid-column: 1; + padding-top: 6px; } -.ead-extraArea { +.ead-addTriggerArea .ui-dropdown__container { + width: 220px; +} + +.ead-addTriggerDropdown { grid-column: 2; } + +.ead-addTriggerButton { + grid-column: 3; +} + +.ead-cgAdd { + background-color: green !important; +} + +.ead-cgRemove { + background-color: red !important; +} + +.ead-icon { + min-width: 18px; + display: inline-block; +} + +.ead-componentGroupsHeaderInfo { + padding-bottom: 4px; +} + +.ead-componentGroupsBin { + padding: 4px; + border: inset 1px; +} +.ead-triggerTitle { + padding-bottom: 10px; + padding-top: 15px; + font-weight: bold; +} + +.ead-actionsTitle { + padding-bottom: 10px; + padding-top: 20px; + font-weight: bold; +} + +.ead-instruction { + padding-bottom: 4px; +} diff --git a/app/src/UX/EventActionDesign.tsx b/app/src/UX/EventActionDesign.tsx index bb802889..09ec5d37 100644 --- a/app/src/UX/EventActionDesign.tsx +++ b/app/src/UX/EventActionDesign.tsx @@ -1,4 +1,4 @@ -import { Component } from "react"; +import { Component, SyntheticEvent } from "react"; import IFileProps from "./IFileProps"; import IFile from "../storage/IFile"; import "./EventActionDesign.css"; @@ -7,11 +7,19 @@ import { ThemeInput } from "@fluentui/styles"; import EntityTypeDefinition from "../minecraft/EntityTypeDefinition"; import ManagedEvent from "../minecraft/ManagedEvent"; import BlockTypeBehaviorDefinition from "../minecraft/BlockTypeBehaviorDefinition"; +import FunctionEditor from "./FunctionEditor"; +import ProjectItem from "../app/ProjectItem"; +import Utilities from "../core/Utilities"; +import { Button, Dropdown, DropdownProps } from "@fluentui/react-northstar"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faCheck, faRemove } from "@fortawesome/free-solid-svg-icons"; interface IEventActionDesignProps extends IFileProps { heightOffset: number; readOnly: boolean; entityType: EntityTypeDefinition; + + item: ProjectItem; event: ManagedEvent; theme: ThemeInput; } @@ -19,6 +27,7 @@ interface IEventActionDesignProps extends IFileProps { interface IEventActionDesignState { fileToEdit: IFile; isLoaded: boolean; + state: string; } export default class EventActionDesign extends Component { @@ -30,8 +39,11 @@ export default class EventActionDesign extends Component | React.KeyboardEvent | null) { + if (!event?.target) { + return; + } + + const elt = event.target as HTMLElement; + + if (!elt.className) { + return; + } + + const classNames = elt.className.split(" "); + + for (const className of classNames) { + if (className.startsWith("cg.")) { + const cgKey = className.split("."); + + if (cgKey.length === 2) { + const cgId = cgKey[1]; + + if (this.props.event.hasAddComponentGroup(cgId)) { + this.props.event.removeAddComponentGroup(cgId); + this.props.event.ensureRemoveComponentGroup(cgId); + } else if (this.props.event.hasRemoveComponentGroup(cgId)) { + this.props.event.removeRemoveComponentGroup(cgId); + } else { + this.props.event.ensureAddComponentGroup(cgId); + } + + this._doUpdate(true); + } + } + } + } + + async _handleAddTriggerChange( + event: React.MouseEvent | React.KeyboardEvent | null, + data: DropdownProps + ) {} + render() { const height = "calc(100vh - " + this.props.heightOffset + "px)"; @@ -126,23 +181,140 @@ export default class EventActionDesign extends ComponentThis action is automatically fired when the mob is spawned. + ); + } else if (this.props.event.id === "minecraft:entity_born") { + defaultTriggers.push( +
This action is automatically fired when the mob is born via breeding.
+ ); + } else if (this.props.event.id === "minecraft:transformed") { + defaultTriggers.push( +
+ This action is automatically fired when the mob is changed into a different type of mob. +
+ ); + } else if (this.props.event.id === "minecraft:on_prime") { + defaultTriggers.push( +
This action is automatically fired when the mob is set to explode.
+ ); + } + + defaultTriggers.push( +
+ This can be triggered via /event command. For example,{" "} + + /event @e[type={this.props.entityType.id}] {this.props.event.id} + +
+ ); + const et = this.state.fileToEdit.manager as EntityTypeDefinition; if (et.data === undefined) { return
Loading behavior pack...
; } + const groupToggleButtons = []; + + const groups = et.getComponentGroups(); + + const groupIds = []; + + for (const etg of groups) { + groupIds.push(etg.id); + } + + groupIds.sort(); + + for (const groupId of groupIds) { + let icon =  ; + + let cssAdjust = ""; + if (this.props.event.hasAddComponentGroup(groupId)) { + cssAdjust = " ead-cgAdd"; + icon = ( + + + + ); + } else if (this.props.event.hasRemoveComponentGroup(groupId)) { + cssAdjust = " ead-cgRemove"; + icon = ( + + + + ); + } + + groupToggleButtons.push( + + ); + } + return (
-
Action Editor{et.id}
-
default components:
-
+
{this.props.event.id}
+
Triggers
+
{defaultTriggers}
+
+
Add a trigger:
+
+ +
+
+ +
+
+ +
Actions
+ +
Add or remove component groups
+
(click to toggle add/remove/neutral)
+
+ {groupToggleButtons} +
+ +
+ +
); } diff --git a/app/src/UX/FunctionEditor.css b/app/src/UX/FunctionEditor.css index 7c80aab8..ad464b0b 100644 --- a/app/src/UX/FunctionEditor.css +++ b/app/src/UX/FunctionEditor.css @@ -32,7 +32,7 @@ padding: 2px; padding-top: 10px; display: grid; - grid-template-columns: 140px 1fr; + grid-template-columns: 190px 1fr; } .mcfe-accessoryToolBarAreaSingle { diff --git a/app/src/UX/FunctionEditor.tsx b/app/src/UX/FunctionEditor.tsx index 39f6ccb2..b20bc0f7 100644 --- a/app/src/UX/FunctionEditor.tsx +++ b/app/src/UX/FunctionEditor.tsx @@ -16,6 +16,7 @@ import Project from "../app/Project"; interface IFunctionEditorProps { file?: IFile; project?: Project; + title?: string; theme: ThemeInput; isCommandEditor: boolean; initialContent?: string; @@ -28,7 +29,7 @@ interface IFunctionEditorProps { preferredTextSize: number; carto: Carto; onFilterTextChanged?: (newFilterText: string) => void; - onUpdatePreferredTextSize: (newSize: number) => void; + onUpdatePreferredTextSize?: (newSize: number) => void; onUpdateContent?: (newContent: string) => void; } @@ -236,7 +237,11 @@ export default class FunctionEditor extends Component
- {this.props.isCommandEditor ? "Commands" : "Function"} + {this.props.title ? this.props.title : this.props.isCommandEditor ? "Commands" : "Function"}
diff --git a/app/src/UX/Home.css b/app/src/UX/Home.css index e574eaf2..62af75f9 100644 --- a/app/src/UX/Home.css +++ b/app/src/UX/Home.css @@ -171,11 +171,14 @@ .home-uploadFile { display: inline-block; border: 0px !important; - grid-column: 2; max-width: calc(100vw - 40px); box-shadow: none !important; } +.home-tools-bin-upload { + grid-row: 1; +} + .home-inspectFileUpload { max-width: calc(100vw - 100px); margin-left: 0px !important; @@ -368,7 +371,11 @@ padding-top: 1px; font-size: small; display: grid; - grid-template-columns: 1fr 196px; + grid-template-rows: 32px 32px; +} + +.home-tools-bin-upload-label { + padding-left: 7px; } .home-tools-bin-inner BUTTON DIV SPAN { diff --git a/app/src/UX/Home.tsx b/app/src/UX/Home.tsx index d16c6c14..80225377 100644 --- a/app/src/UX/Home.tsx +++ b/app/src/UX/Home.tsx @@ -1056,6 +1056,7 @@ export default class Home extends Component { { key="homeProjectsList" className="home-projects-list" style={{ - height: browserWidth >= 800 ? "calc(100vh - " + (300 + this.props.heightOffset) + "px)" : "", + height: browserWidth >= 800 ? "calc(100vh - " + (332 + this.props.heightOffset) + "px)" : "", }} > { borderBottom: "outset 1.5px " + this.props.theme.siteVariables?.colorScheme.brand.background4, }} > +
+ + From MC/Zip File: + + +
-
diff --git a/app/src/UX/JavaScriptEditor.tsx b/app/src/UX/JavaScriptEditor.tsx index ed6e3935..721e78df 100644 --- a/app/src/UX/JavaScriptEditor.tsx +++ b/app/src/UX/JavaScriptEditor.tsx @@ -383,10 +383,10 @@ export default class JavaScriptEditor extends Component & IS ? props.theme.siteVariables?.colorScheme.brand.background2 : props.theme.siteVariables?.colorScheme.brand.background1, color: props.isSelected - ? props.theme.siteVariables?.colorScheme.brand.background + ? props.theme.siteVariables?.colorScheme.brand.foreground : props.theme.siteVariables?.colorScheme.brand.foreground6, + fontWeight: props.isSelected ? "bold" : "normal", + textDecoration: props.isSelected ? "underline" : "none", backgroundColor: props.isSelected ? props.theme.siteVariables?.colorScheme.brand.background2 : props.theme.siteVariables?.colorScheme.brand.background1, @@ -348,7 +350,7 @@ export const ExportBackupLabel: React.FC & ) => ( - {!props.isCompact ? Backup : <>} + {!props.isCompact ? Download Backup : <>} ); @@ -357,7 +359,7 @@ export const LocalFolderLabel: React.FC & ) => ( - {!props.isCompact ? Folder : <>} + {!props.isCompact ? Edit Folder : <>} ); diff --git a/app/src/UX/LootTableEditor.css b/app/src/UX/LootTableEditor.css index 80018357..c191149e 100644 --- a/app/src/UX/LootTableEditor.css +++ b/app/src/UX/LootTableEditor.css @@ -19,6 +19,5 @@ } .ltb-form { - padding-top: 20px; padding-left: 20px; } diff --git a/app/src/UX/ProjectEditor.tsx b/app/src/UX/ProjectEditor.tsx index 2eec4fd3..21b49586 100644 --- a/app/src/UX/ProjectEditor.tsx +++ b/app/src/UX/ProjectEditor.tsx @@ -82,6 +82,7 @@ import IntegrateItem from "./IntegrateItem"; import IProjectItemSeed, { ProjectItemSeedAction } from "../app/IProjectItemSeed"; import ProjectStandard from "../app/ProjectStandard"; import ProjectAutogeneration from "../app/ProjectAutogeneration"; +import BrowserFolder from "../storage/BrowserFolder"; interface IProjectEditorProps extends IAppProps { onModeChangeRequested?: (mode: AppMode) => void; @@ -1380,17 +1381,21 @@ export default class ProjectEditor extends Component, - key: "add", - active: true, - onClick: this._handleVscAddClick, - });*/ - - // const viewMenuItems: any[] = []; toolbarItems.push({ key: "itemsFocus", content: "Items", diff --git a/app/src/UX/ProjectEditorUtilities.ts b/app/src/UX/ProjectEditorUtilities.ts index de0cddeb..963dc823 100644 --- a/app/src/UX/ProjectEditorUtilities.ts +++ b/app/src/UX/ProjectEditorUtilities.ts @@ -118,22 +118,22 @@ export default class ProjectEditorUtilities { const name = nameCore + " Flat GameTest"; const fileName = nameCore + "-flatpack.mcworld"; - carto.notifyStatusUpdate("Packing " + fileName); + await carto.notifyStatusUpdate("Packing " + fileName); const newBytes = await ProjectExporter.generateFlatBetaApisWorldWithPacksZipBytes(carto, project, name); if (!newBytes) { - carto.notifyOperationEnded(operId); + await carto.notifyOperationEnded(operId); return; } - carto.notifyStatusUpdate("Now downloading " + fileName); + await carto.notifyStatusUpdate("Now downloading " + fileName); if (newBytes !== undefined) { saveAs(new Blob([newBytes], { type: "application/octet-stream" }), fileName); } - carto.notifyOperationEnded(operId, "Done with save " + fileName); + await carto.notifyOperationEnded(operId, "Done with save " + fileName); } public static async launchWorldWithPacksDownload(carto: Carto, project: Project) { @@ -153,7 +153,7 @@ export default class ProjectEditorUtilities { saveAs(new Blob([newBytes], { type: "application/octet-stream" }), fileName); } - carto.notifyStatusUpdate("Downloading mcworld with packs embedded '" + project.name + "'."); + await carto.notifyStatusUpdate("Downloading mcworld with packs embedded '" + project.name + "'."); } static getIntegrateBrowserFileDefaultActionDescription( @@ -523,7 +523,7 @@ export default class ProjectEditorUtilities { saveAs(new Blob([newBytes], { type: "application/octet-stream" }), fileName); } - carto.notifyStatusUpdate("Downloading mcworld with packs embedded '" + project.name + "'."); + await carto.notifyStatusUpdate("Downloading mcworld with packs embedded '" + project.name + "'."); } public static async launchLocalExport(carto: Carto, project: Project) { diff --git a/app/src/UX/ProjectItemEditor.tsx b/app/src/UX/ProjectItemEditor.tsx index b5c3acc2..3754f06c 100644 --- a/app/src/UX/ProjectItemEditor.tsx +++ b/app/src/UX/ProjectItemEditor.tsx @@ -282,7 +282,7 @@ export default class ProjectItemEditor extends Component, data?: any | undefined) { if (data !== undefined && data.tag !== undefined && this.props.project !== null) { const projectItem = this.props.project.getItemByProjectPath(data.tag); @@ -769,6 +798,9 @@ export default class ProjectItemList extends Component + ), key: "pil-hideShowSlash", kind: "toggle", diff --git a/app/src/UX/ProjectPropertyEditor.tsx b/app/src/UX/ProjectPropertyEditor.tsx index 0bd317c8..f4d05747 100644 --- a/app/src/UX/ProjectPropertyEditor.tsx +++ b/app/src/UX/ProjectPropertyEditor.tsx @@ -237,7 +237,7 @@ export default class ProjectPropertyEditor extends Component 4) { + await ResourceManifestDefinition.setNewResourcePackId(this, newId, oldId); + } + + this._onPropertyChanged.dispatch(this, "defaultResourcePackUniqueId"); + } + } + get defaultResourcePackVersion(): number[] { if (this.#data.defaultResourcePackVersion === undefined) { const vMajor = this.versionMajor ? this.versionMajor : 0; @@ -1209,6 +1225,19 @@ export default class Project { } } + async setDefaultBehaviorPackUniqueIdAndUpdateDependencies(newId: string) { + if (this.#data.defaultBehaviorPackUniqueId !== newId) { + const oldId = this.#data.defaultBehaviorPackUniqueId; + this.#data.defaultBehaviorPackUniqueId = newId; + + if (oldId && oldId.length > 4) { + await BehaviorManifestDefinition.setNewBehaviorPackId(this, newId, oldId); + } + + this._onPropertyChanged.dispatch(this, "defaultBehaviorPackUniqueId"); + } + } + get defaultBehaviorPackVersion(): number[] { if (this.#data.defaultBehaviorPackVersion === undefined) { const vMajor = this.versionMajor ? this.versionMajor : 0; @@ -1714,7 +1743,7 @@ export default class Project { } else if ( (folderPathA.indexOf("/docs/") >= 0 || folderPathA.indexOf("/@minecraft/") >= 0 || - folderPathA.indexOf("/mojang-commands/") >= 0) && + folderPathA.indexOf("/mojang-commands") >= 0) && folderContext === FolderContext.unknown ) { folderContext = FolderContext.docs; @@ -2370,7 +2399,7 @@ export default class Project { (folderContext === FolderContext.resourcePack || folderContext === FolderContext.resourcePackSubPack) && folderPathLower.indexOf("/textures/ui/") >= 0 ) { - newJsonType = ProjectItemType.uiTextureJson; + newJsonType = ProjectItemType.ninesliceJson; } else if ( folderContext === FolderContext.resourcePack && folderPathLower.indexOf("/texture_sets/") >= 0 @@ -2432,7 +2461,7 @@ export default class Project { folderContext === FolderContext.behaviorPack && (folderPathLower.indexOf("/entities/") >= 0 || folderPathLower.indexOf("/entity/") >= 0) ) { - newJsonType = ProjectItemType.entityTypeBehaviorJson; + newJsonType = ProjectItemType.entityTypeBehavior; } else if (folderContext === FolderContext.behaviorPack && folderPathLower.indexOf("/items/") >= 0) { newJsonType = ProjectItemType.itemTypeBehaviorJson; } else if (folderContext === FolderContext.behaviorPack && folderPathLower.indexOf("/blocks/") >= 0) { @@ -2442,6 +2471,11 @@ export default class Project { this.role = ProjectRole.documentation; itemName = StorageUtilities.getLeafName(folderPath); + } else if ( + (folderContext === FolderContext.resourcePack || folderContext === FolderContext.resourcePackSubPack) && + folderPathLower.indexOf("/textures/") >= 0 + ) { + newJsonType = ProjectItemType.ninesliceJson; } else if ( folderContext === FolderContext.docs && (baseName === "_example_files" || baseName === "example_files") @@ -2453,7 +2487,10 @@ export default class Project { } else if (folderContext === FolderContext.typeDefs && folderPathLower.indexOf("/command_modules") >= 0) { newJsonType = ProjectItemType.commandSetDefinitionJson; this.role = ProjectRole.documentation; - } else if (folderContext === FolderContext.metaData && folderPathLower.indexOf("/forms") >= 0) { + } else if ( + (folderContext === FolderContext.metaData && folderPathLower.indexOf("/forms") >= 0) || + projectPath.endsWith(".form.json") + ) { newJsonType = ProjectItemType.dataFormJson; this.role = ProjectRole.meta; } else if (folderContext === FolderContext.typeDefs && folderPathLower.indexOf("/script_modules") >= 0) { @@ -3297,31 +3334,63 @@ export default class Project { if (this.#accessoryFilePaths && this.#projectFolder) { for (let i = 0; i < this.#accessoryFilePaths.length; i++) { - const addFile = containingFolder.ensureFile(this.#accessoryFilePaths[i]); + if ( + (this.#accessoryFilePaths[i].startsWith("\\") || this.#accessoryFilePaths[i].indexOf(":") >= 0) && + this.carto.localFileExists && + this.carto.ensureLocalFolder + ) { + if (StorageUtilities.isUsableFile(this.#accessoryFilePaths[i])) { + const exists = await this.carto.localFileExists(this.#accessoryFilePaths[i]); - const additionalFileExists = await addFile.exists(); - if (additionalFileExists) { - let isChildOfExistingFolder = false; + if (exists) { + const folder = this.carto.ensureLocalFolder( + StorageUtilities.getFolderPath(this.#accessoryFilePaths[i]) + ); - if (this.#accessoryFoldersForFilePaths === null) { - this.#accessoryFoldersForFilePaths = []; - } + if (folder) { + folder.storage.readOnly = true; - for (let j = 0; j < this.#accessoryFoldersForFilePaths.length; j++) { - let addFileStoragePath = addFile.getFolderRelativePath(this.#accessoryFoldersForFilePaths[j]); + const fileName = StorageUtilities.getLeafName(this.#accessoryFilePaths[i]); + await folder.load(); + const file = folder.files[fileName]; - if (addFileStoragePath) { - isChildOfExistingFolder = true; - this._inferProjectItemFromFile(addFile, this.#accessoryFoldersForFilePaths[j], addFileStoragePath); + if (file && (await file.exists())) { + if (this.#accessoryFoldersForFilePaths === null) { + this.#accessoryFoldersForFilePaths = []; + } + this.#accessoryFoldersForFilePaths.push(file.parentFolder); + this._inferProjectItemFromFile(file, file.parentFolder, file.storageRelativePath); + } + } } } + } else { + const addFile = containingFolder.ensureFile(this.#accessoryFilePaths[i]); + + const additionalFileExists = await addFile.exists(); + if (additionalFileExists) { + let isChildOfExistingFolder = false; + + if (this.#accessoryFoldersForFilePaths === null) { + this.#accessoryFoldersForFilePaths = []; + } + + for (let j = 0; j < this.#accessoryFoldersForFilePaths.length; j++) { + let addFileStoragePath = addFile.getFolderRelativePath(this.#accessoryFoldersForFilePaths[j]); - if (!isChildOfExistingFolder) { - if (addFile.parentFolder) { - let addFileStoragePath = addFile.getFolderRelativePath(addFile.parentFolder); if (addFileStoragePath) { - this.#accessoryFoldersForFilePaths.push(addFile.parentFolder); - this._inferProjectItemFromFile(addFile, addFile.parentFolder, addFileStoragePath); + isChildOfExistingFolder = true; + this._inferProjectItemFromFile(addFile, this.#accessoryFoldersForFilePaths[j], addFileStoragePath); + } + } + + if (!isChildOfExistingFolder) { + if (addFile.parentFolder) { + let addFileStoragePath = addFile.getFolderRelativePath(addFile.parentFolder); + if (addFileStoragePath) { + this.#accessoryFoldersForFilePaths.push(addFile.parentFolder); + this._inferProjectItemFromFile(addFile, addFile.parentFolder, addFileStoragePath); + } } } } @@ -3372,15 +3441,29 @@ export default class Project { } async _inferProjectItemFromFile(file: IFile, folder: IFolder, fileStoragePath: string) { - this.ensureItemByProjectPath( - fileStoragePath, - ProjectItemStorageType.singleFile, - file.name, - ProjectItemType.projectSummaryMetadata, - undefined, - ProjectItemCreationType.normal, - file - ); + const fileName = StorageUtilities.canonicalizeName(file.name); + + if (fileName.endsWith(".data.json")) { + this.ensureItemByProjectPath( + fileStoragePath, + ProjectItemStorageType.singleFile, + file.name, + ProjectItemType.projectSummaryMetadata, + undefined, + ProjectItemCreationType.normal, + file + ); + } else if (fileName.endsWith(".tags.json")) { + this.ensureItemByProjectPath( + fileStoragePath, + ProjectItemStorageType.singleFile, + file.name, + ProjectItemType.tagsMetadata, + undefined, + ProjectItemCreationType.normal, + file + ); + } } _handleProjectFolderMoved(folder: IFolder, folderMove: IFolderMove) { diff --git a/app/src/app/ProjectAutogeneration.ts b/app/src/app/ProjectAutogeneration.ts index b7ad51b5..d19dca1f 100644 --- a/app/src/app/ProjectAutogeneration.ts +++ b/app/src/app/ProjectAutogeneration.ts @@ -373,7 +373,7 @@ export default class ProjectAutogeneration { const candItem = items[i]; if ( - candItem.itemType === ProjectItemType.entityTypeBehaviorJson && + candItem.itemType === ProjectItemType.entityTypeBehavior && candItem.projectPath !== null && candItem.projectPath !== undefined ) { diff --git a/app/src/app/ProjectExporter.ts b/app/src/app/ProjectExporter.ts index ec673bdc..d2894b7d 100644 --- a/app/src/app/ProjectExporter.ts +++ b/app/src/app/ProjectExporter.ts @@ -724,11 +724,11 @@ export default class ProjectExporter { } static async deployAsFlatPackRefWorld(carto: Carto, project: Project) { - carto.notifyStatusUpdate("Saving..."); + await carto.notifyStatusUpdate("Saving..."); await ProjectExporter.updateProjects(project); await project.save(); - carto.notifyStatusUpdate("Saved"); + await carto.notifyStatusUpdate("Saved"); // only do an explicit deploy here autodeployment is not turned on; otherwise, deployment should happen in the save() above. if ( @@ -737,9 +737,9 @@ export default class ProjectExporter { carto.deployBehaviorPacksFolder !== null && carto.activeMinecraft ) { - carto.notifyStatusUpdate("Deploying pack add-ons"); + await carto.notifyStatusUpdate("Deploying pack add-ons"); carto.activeMinecraft.syncWithDeployment(); - carto.notifyStatusUpdate("Deployed"); + await carto.notifyStatusUpdate("Deployed"); } const hash = project.defaultBehaviorPackUniqueId + "|"; diff --git a/app/src/app/ProjectItem.ts b/app/src/app/ProjectItem.ts index 7c5b6b2b..361e01f5 100644 --- a/app/src/app/ProjectItem.ts +++ b/app/src/app/ProjectItem.ts @@ -311,7 +311,7 @@ export default class ProjectItem { return "behavior/blocks/blocks.json"; case ProjectItemType.dialogueBehaviorJson: return "behavior/dialogue/dialogue.json"; - case ProjectItemType.entityTypeBehaviorJson: + case ProjectItemType.entityTypeBehavior: return "behavior/entities/entities.json"; case ProjectItemType.blocksCatalogResourceJson: return "resource/blocks.json"; @@ -887,7 +887,7 @@ export default class ProjectItem { this.errorStatus = ProjectItemErrorStatus.none; } } - } else if (this.itemType === ProjectItemType.entityTypeBehaviorJson) { + } else if (this.itemType === ProjectItemType.entityTypeBehavior) { await EntityTypeDefinition.ensureOnFile(this._file); this._fireLoadedEvent(); diff --git a/app/src/app/ProjectItemRelations.ts b/app/src/app/ProjectItemRelations.ts index 7d97258a..8372e969 100644 --- a/app/src/app/ProjectItemRelations.ts +++ b/app/src/app/ProjectItemRelations.ts @@ -1,4 +1,5 @@ import EntityTypeDefinition from "../minecraft/EntityTypeDefinition"; +import EntityTypeResourceDefinition from "../minecraft/EntityTypeResourceDefinition"; import { ProjectItemType } from "./IProjectItemData"; import Project from "./Project"; @@ -13,7 +14,7 @@ export default class ProjectItemRelations { } for (const item of items) { - if (item.itemType === ProjectItemType.entityTypeBehaviorJson) { + if (item.itemType === ProjectItemType.entityTypeBehavior) { await item.ensureStorage(); if (item.file) { @@ -23,6 +24,16 @@ export default class ProjectItemRelations { entityTypeBehavior.addChildItems(project, item); } } + } else if (item.itemType === ProjectItemType.entityTypeResource) { + await item.ensureStorage(); + + if (item.file) { + const entityTypeResource = await EntityTypeResourceDefinition.ensureOnFile(item.file); + + if (entityTypeResource) { + entityTypeResource.addChildItems(project, item); + } + } } } } diff --git a/app/src/app/ProjectItemUtilities.ts b/app/src/app/ProjectItemUtilities.ts index 46fc2fca..34fe8e82 100644 --- a/app/src/app/ProjectItemUtilities.ts +++ b/app/src/app/ProjectItemUtilities.ts @@ -53,7 +53,7 @@ export default class ProjectItemUtilities { ) { return { itemType: ProjectItemType.recipeBehaviorJson }; } else if (firstHundred.indexOf('"minecraft:entity"') >= 0) { - return { itemType: ProjectItemType.entityTypeBehaviorJson }; + return { itemType: ProjectItemType.entityTypeBehavior }; } else if (firstHundred.indexOf('"minecraft:item"') >= 0) { return { itemType: ProjectItemType.itemTypeBehaviorJson }; } else if (firstHundred.indexOf('"pools"') >= 0) { @@ -111,7 +111,7 @@ export default class ProjectItemUtilities { case ProjectItemType.resourcePackManifestJson: // sort next to .behaviorPackManifestJson return 510; - case ProjectItemType.entityTypeBehaviorJson: + case ProjectItemType.entityTypeBehavior: return 1851; case ProjectItemType.blockTypeBehaviorJson: @@ -187,12 +187,12 @@ export default class ProjectItemUtilities { case ProjectItemType.uiJson: case ProjectItemType.lang: case ProjectItemType.languagesCatalogResourceJson: - case ProjectItemType.uiTextureJson: + case ProjectItemType.ninesliceJson: case ProjectItemType.attachableResourceJson: case ProjectItemType.audio: return ProjectItemCategory.assets; - case ProjectItemType.entityTypeBehaviorJson: + case ProjectItemType.entityTypeBehavior: case ProjectItemType.entityTypeResource: case ProjectItemType.entityTypeBaseJs: case ProjectItemType.entityTypeBaseTs: @@ -312,7 +312,7 @@ export default class ProjectItemUtilities { case ProjectItemType.json: case ProjectItemType.behaviorPackManifestJson: case ProjectItemType.resourcePackManifestJson: - case ProjectItemType.entityTypeBehaviorJson: + case ProjectItemType.entityTypeBehavior: case ProjectItemType.tickJson: case ProjectItemType.cameraJson: case ProjectItemType.actionSetJson: @@ -336,7 +336,7 @@ export default class ProjectItemUtilities { case ProjectItemType.modelGeometryJson: case ProjectItemType.particleJson: case ProjectItemType.renderControllerJson: - case ProjectItemType.uiTextureJson: + case ProjectItemType.ninesliceJson: case ProjectItemType.uiJson: case ProjectItemType.languagesCatalogResourceJson: case ProjectItemType.biomeBehaviorJson: @@ -443,7 +443,7 @@ export default class ProjectItemUtilities { return "Entity type JavaScript"; case ProjectItemType.entityTypeBaseTs: return "Entity type TypeScript"; - case ProjectItemType.entityTypeBehaviorJson: + case ProjectItemType.entityTypeBehavior: return "Entity type"; case ProjectItemType.MCTemplate: return "Minecraft template"; @@ -527,8 +527,6 @@ export default class ProjectItemUtilities { return "Particle"; case ProjectItemType.renderControllerJson: return "Render controller"; - case ProjectItemType.uiTextureJson: - return "UI texture"; case ProjectItemType.uiJson: return "User interface"; case ProjectItemType.languagesCatalogResourceJson: @@ -584,11 +582,13 @@ export default class ProjectItemUtilities { case ProjectItemType.env: return "Environment File"; case ProjectItemType.esLintConfigMjs: - return "ESLint Config"; + return "ESLint config"; case ProjectItemType.justConfigTs: - return "Just Config"; + return "Just config"; case ProjectItemType.docInfoJson: return "Doc info json"; + case ProjectItemType.ninesliceJson: + return "Nine-slice scaling config"; case ProjectItemType.scriptTypesJson: return "Script types definition"; case ProjectItemType.vanillaDataJson: @@ -742,7 +742,7 @@ export default class ProjectItemUtilities { return scriptsFolder; - case ProjectItemType.entityTypeBehaviorJson: + case ProjectItemType.entityTypeBehavior: case ProjectItemType.MCFunction: const defaultBpFolder = await project.getDefaultBehaviorPackFolder(); @@ -819,7 +819,7 @@ export default class ProjectItemUtilities { return ["render_controllers"]; case ProjectItemType.attachableResourceJson: return ["attachables"]; - case ProjectItemType.entityTypeBehaviorJson: + case ProjectItemType.entityTypeBehavior: return ["entities"]; case ProjectItemType.itemTypeBehaviorJson: case ProjectItemType.itemTypeResourceJson: diff --git a/app/src/app/ProjectUtilities.ts b/app/src/app/ProjectUtilities.ts index 45d06e15..1f39c719 100644 --- a/app/src/app/ProjectUtilities.ts +++ b/app/src/app/ProjectUtilities.ts @@ -340,7 +340,7 @@ export default class ProjectUtilities { static async applyBehaviorPackUniqueId(project: Project, newBehaviorPackId: string) { const oldBehaviorPackId = project.defaultBehaviorPackUniqueId; - project.defaultBehaviorPackUniqueId = newBehaviorPackId; + await project.setDefaultBehaviorPackUniqueIdAndUpdateDependencies(newBehaviorPackId); if (project.editPreference === ProjectEditPreference.summarized && project.defaultBehaviorPackUniqueId) { let bpackCount = 0; @@ -382,7 +382,7 @@ export default class ProjectUtilities { static async applyResourcePackUniqueId(project: Project, newResourcePackId: string) { const oldResourcePackId = project.defaultResourcePackUniqueId; - project.defaultResourcePackUniqueId = newResourcePackId; + await project.setDefaultResourcePackUniqueIdAndUpdateDependencies(newResourcePackId); if (project.editPreference === ProjectEditPreference.summarized && project.defaultResourcePackUniqueId) { let rpackCount = 0; @@ -509,18 +509,115 @@ export default class ProjectUtilities { await project.save(true); } - static async randomizeAllUids(project: Project) { - const uids: { [name: string]: string } = {}; - let setBehaviorPack = false; + static async setNewModuleId(project: Project, newModuleId: string, oldModuleId: string) { + const itemsCopy = project.getItemsCopy(); let setResourcePack = false; - uids["defaultResourcePack"] = project.defaultResourcePackUniqueId; - uids["defaultBehaviorPack"] = project.defaultBehaviorPackUniqueId; - uids["defaultDataPack"] = project.defaultDataUniqueId; - uids["defaultScriptModulePack"] = project.defaultScriptModuleUniqueId; + for (let i = 0; i < itemsCopy.length; i++) { + const pi = itemsCopy[i]; + + if (pi.file) { + if (pi.itemType === ProjectItemType.resourcePackManifestJson && !setResourcePack) { + const rpManifestJson = await ResourceManifestDefinition.ensureOnFile(pi.file); + + if (rpManifestJson) { + if (rpManifestJson.definition && rpManifestJson.definition.modules) { + const mods = rpManifestJson.definition.modules; + + for (const mod of mods) { + if (mod.uuid === oldModuleId) { + mod.uuid = newModuleId; + } + } + } + } + } else if (pi.itemType === ProjectItemType.behaviorPackManifestJson) { + const bpManifestJson = await ResourceManifestDefinition.ensureOnFile(pi.file); + + if (bpManifestJson) { + if (bpManifestJson.definition && bpManifestJson.definition.modules) { + const mods = bpManifestJson.definition.modules; + + for (const mod of mods) { + if (mod.uuid === oldModuleId) { + mod.uuid = newModuleId; + } + } + } + } + } + } + } + } + + static async getIsAddon(project: Project) { + const itemsCopy = project.getItemsCopy(); + let rpCount = 0; + let bpCount = 0; + + for (let i = 0; i < itemsCopy.length; i++) { + const pi = itemsCopy[i]; + + if (pi.file) { + if (pi.itemType === ProjectItemType.resourcePackManifestJson) { + rpCount++; + const rpManifestJson = await ResourceManifestDefinition.ensureOnFile(pi.file); + + if (rpManifestJson) { + if (!rpManifestJson.hasAddonProperties()) { + return false; + } + } + } else if (pi.itemType === ProjectItemType.behaviorPackManifestJson) { + bpCount++; + const bpManifestJson = await BehaviorManifestDefinition.ensureOnFile(pi.file); + + if (bpManifestJson) { + if (!bpManifestJson.hasAddonProperties()) { + return false; + } + } + } + } + } + + return bpCount === 1 && rpCount === 1; + } - project.defaultResourcePackUniqueId = Utilities.createUuid(); - project.defaultBehaviorPackUniqueId = Utilities.createUuid(); + static async setIsAddon(project: Project) { + const itemsCopy = project.getItemsCopy(); + + for (let i = 0; i < itemsCopy.length; i++) { + const pi = itemsCopy[i]; + + if (pi.file) { + if (pi.itemType === ProjectItemType.resourcePackManifestJson) { + const rpManifestJson = await ResourceManifestDefinition.ensureOnFile(pi.file); + + if (rpManifestJson) { + rpManifestJson.setAddonProperties(); + } + } else if (pi.itemType === ProjectItemType.behaviorPackManifestJson) { + const bpManifestJson = await BehaviorManifestDefinition.ensureOnFile(pi.file); + + if (bpManifestJson) { + bpManifestJson.setAddonProperties(); + } + } + } + } + } + + static async randomizeAllUids(project: Project) { + const oldUids: { [name: string]: string } = {}; + + oldUids["defaultBehaviorPack"] = project.defaultBehaviorPackUniqueId; + oldUids["defaultResourcePack"] = project.defaultResourcePackUniqueId; + oldUids["defaultDataPack"] = project.defaultDataUniqueId; + oldUids["defaultScriptModulePack"] = project.defaultScriptModuleUniqueId; + + await project.setDefaultResourcePackUniqueIdAndUpdateDependencies(Utilities.createUuid()); + await project.setDefaultBehaviorPackUniqueIdAndUpdateDependencies(Utilities.createUuid()); project.defaultDataUniqueId = Utilities.createUuid(); project.defaultScriptModuleUniqueId = Utilities.createUuid(); @@ -530,26 +627,33 @@ export default class ProjectUtilities { const pi = itemsCopy[i]; if (pi.file) { - if (pi.itemType === ProjectItemType.behaviorPackManifestJson && !setBehaviorPack) { + if (pi.itemType === ProjectItemType.behaviorPackManifestJson) { const bpManifestJson = await BehaviorManifestDefinition.ensureOnFile(pi.file); if (bpManifestJson) { - bpManifestJson.randomizeModuleUuids(); + bpManifestJson.randomizeModuleUuids( + project.defaultScriptModuleUniqueId, + oldUids["defaultScriptModulePack"] + ); - if (bpManifestJson.uuid && Utilities.uuidEqual(bpManifestJson.uuid, uids["defaultBehaviorPack"])) { - bpManifestJson.uuid = project.defaultBehaviorPackUniqueId; - setBehaviorPack = true; - await bpManifestJson.save(); + if ( + bpManifestJson.uuid !== oldUids["defaultBehaviorPack"] && + bpManifestJson.uuid !== project.defaultBehaviorPackUniqueId + ) { + await bpManifestJson.setUuid(Utilities.createUuid(), project); } } - } else if (pi.itemType === ProjectItemType.resourcePackManifestJson && !setResourcePack) { + } else if (pi.itemType === ProjectItemType.resourcePackManifestJson) { const rpManifestJson = await ResourceManifestDefinition.ensureOnFile(pi.file); if (rpManifestJson) { - if (rpManifestJson.uuid && Utilities.uuidEqual(rpManifestJson.uuid, uids["defaultResourcePack"])) { - rpManifestJson.uuid = project.defaultResourcePackUniqueId; - setResourcePack = true; - await rpManifestJson.save(); + rpManifestJson.randomizeModuleUuids(project.defaultDataUniqueId, oldUids["defaultDataModulePack"]); + + if ( + rpManifestJson.uuid !== oldUids["defaultResourcePack"] && + rpManifestJson.uuid !== project.defaultResourcePackUniqueId + ) { + await rpManifestJson.setUuid(Utilities.createUuid(), project); } } } diff --git a/app/src/cli/ClUtils.ts b/app/src/cli/ClUtils.ts index 57bef57b..b2d9f38d 100644 --- a/app/src/cli/ClUtils.ts +++ b/app/src/cli/ClUtils.ts @@ -103,7 +103,7 @@ export default class ClUtils { } static getIsWriteCommand(taskType: TaskType) { - return taskType === TaskType.create || taskType === TaskType.add; + return taskType === TaskType.world || taskType === TaskType.create || taskType === TaskType.add; } static async getMainWorkFolder(taskType: TaskType, inputFolder?: string, outputFolder?: string) { @@ -128,9 +128,7 @@ export default class ClUtils { const exists = await workFolder.exists(); if (!exists) { - throw new Error( - "Specified folder path '" + workFolder.fullPath + "' does not exist within '" + process.cwd() + "'." - ); + throw new Error("Specified folder path '" + workFolder.fullPath + "' does not exist within '" + __dirname + "'."); } await workFolder.load(); @@ -144,27 +142,27 @@ export default class ClUtils { CartoApp.ensureLocalFolder = ClUtils.ensureLocalFolder; CartoApp.prefsStorage = new NodeStorage( - localEnv.utilities.cliWorkingPath + "prefs" + NodeStorage.folderDelimiter, + localEnv.utilities.cliWorkingPath + "prefs" + NodeStorage.platformFolderDelimiter, "" ); CartoApp.projectsStorage = new NodeStorage( - localEnv.utilities.cliWorkingPath + "projects" + NodeStorage.folderDelimiter, + localEnv.utilities.cliWorkingPath + "projects" + NodeStorage.platformFolderDelimiter, "" ); CartoApp.packStorage = new NodeStorage( - localEnv.utilities.cliWorkingPath + "packs" + NodeStorage.folderDelimiter, + localEnv.utilities.cliWorkingPath + "packs" + NodeStorage.platformFolderDelimiter, "" ); CartoApp.deploymentStorage = new NodeStorage( - localEnv.utilities.cliWorkingPath + "deployment" + NodeStorage.folderDelimiter, + localEnv.utilities.cliWorkingPath + "deployment" + NodeStorage.platformFolderDelimiter, "" ); CartoApp.workingStorage = new NodeStorage( - localEnv.utilities.cliWorkingPath + "working" + NodeStorage.folderDelimiter, + localEnv.utilities.cliWorkingPath + "working" + NodeStorage.platformFolderDelimiter, "" ); diff --git a/app/src/cli/IProjectMetaState.ts b/app/src/cli/IProjectMetaState.ts index 49b9efd4..2ca2481f 100644 --- a/app/src/cli/IProjectMetaState.ts +++ b/app/src/cli/IProjectMetaState.ts @@ -1,4 +1,4 @@ -import IProjectInfoData from "../info/IProjectInfoData"; +import IProjectInfoData, { ProjectInfoSuite } from "../info/IProjectInfoData"; export default interface IProjectMetaState { projectContainerName: string; @@ -6,4 +6,5 @@ export default interface IProjectMetaState { projectName?: string; projectTitle: string; infoSetData: IProjectInfoData; + suite: ProjectInfoSuite | undefined; } diff --git a/app/src/cli/TaskWorker.ts b/app/src/cli/TaskWorker.ts index 9f4a2fa5..4347a187 100644 --- a/app/src/cli/TaskWorker.ts +++ b/app/src/cli/TaskWorker.ts @@ -6,7 +6,7 @@ import Carto from "../app/Carto"; import NodeFile from "../local/NodeFile"; import StorageUtilities from "../storage/StorageUtilities"; import NodeStorage from "../local/NodeStorage"; -import IProjectInfoData from "../info/IProjectInfoData"; +import IProjectInfoData, { ProjectInfoSuite } from "../info/IProjectInfoData"; import Project from "../app/Project"; import IProjectMetaState from "./IProjectMetaState"; import { expose } from "threads/worker"; @@ -14,6 +14,7 @@ import CartoApp, { HostType } from "../app/CartoApp"; import ProjectInfoSet from "../info/ProjectInfoSet"; import { InfoItemType } from "../info/IInfoItemData"; import LocalEnvironment from "../local/LocalEnvironment"; +import ProjectUtilities from "../app/ProjectUtilities"; let carto: Carto | undefined; let localEnv: LocalEnvironment | undefined; @@ -122,7 +123,7 @@ async function validate( project.dispose(); - return metaState; + return [metaState]; } } } @@ -143,33 +144,39 @@ async function validateAndDisposeProject( suite?: string, outputMci?: boolean, outputType?: OutputType -): Promise { +): Promise { Log.verbose("Validating '" + project.name + "'" + (suite ? " with suite '" + suite + "'" : "") + "."); - const csvHeader = ProjectInfoSet.CommonCsvHeader; - await project.inferProjectItemsFromFiles(); let pis: ProjectInfoSet | undefined; + let suiteInst: ProjectInfoSuite | undefined; + if (!suite) { pis = project.infoSet; } else { - pis = new ProjectInfoSet(project, ProjectInfoSet.getSuiteFromString(suite)); + suiteInst = ProjectInfoSet.getSuiteFromString(suite); + pis = new ProjectInfoSet(project, suiteInst); } await pis.generateForProject(); const pisData = pis.getDataObject(); + const metaStates: IProjectMetaState[] = []; + const projectSet = { projectContainerName: project.containerName, projectPath: project.projectFolder?.storageRelativePath, projectName: project.name, projectTitle: project.title, infoSetData: pisData, + suite: suiteInst, }; + metaStates.push(projectSet); + pis.disconnectFromProject(); if (localEnv?.displayInfo || localEnv?.displayVerbose) { @@ -192,10 +199,76 @@ async function validateAndDisposeProject( } } + try { + await outputResults(projectSet, pis, "", outputStorage, mcrJsonFile, outputMci, outputType); + } catch (e) { + Log.error(e); + } + + // run derivative suites if no specific suite specified + if (!suite || suite === "all") { + const isAddon = await ProjectUtilities.getIsAddon(project); + + if (isAddon) { + pis = new ProjectInfoSet(project, ProjectInfoSuite.addOn); + + await pis.generateForProject(); + + const projectSet = { + projectContainerName: project.containerName, + projectPath: project.projectFolder?.storageRelativePath, + projectName: project.name, + projectTitle: project.title, + infoSetData: pis.getDataObject(), + suite: ProjectInfoSuite.addOn, + }; + + metaStates.push(projectSet); + + await outputResults(projectSet, pis, "addon", outputStorage, undefined); + } + + const shouldRunPlatformVersion = (pisData.info as any)["CWave"] !== undefined; + + if (shouldRunPlatformVersion) { + pis = new ProjectInfoSet(project, ProjectInfoSuite.currentPlatformVersions); + + await pis.generateForProject(); + + const projectSet = { + projectContainerName: project.containerName, + projectPath: project.projectFolder?.storageRelativePath, + projectName: project.name, + projectTitle: project.title, + infoSetData: pis.getDataObject(), + suite: ProjectInfoSuite.currentPlatformVersions, + }; + + metaStates.push(projectSet); + + await outputResults(projectSet, pis, "currentplatform", outputStorage, undefined); + } + } + + project.dispose(); + + return metaStates; +} + +async function outputResults( + projectSet: IProjectMetaState, + pis: ProjectInfoSet, + fileNameModifier: string, + outputStorage: NodeStorage | undefined, + mcrJsonFile: NodeFile | undefined, + outputMci?: boolean, + outputType?: OutputType +) { if (outputStorage) { if (outputType !== OutputType.noReports) { const reportHtmlFile = outputStorage.rootFolder.ensureFile( StorageUtilities.ensureFileNameIsSafe(StorageUtilities.getBaseFromName(projectSet.projectContainerName)) + + fileNameModifier + ".report.html" ); @@ -213,6 +286,7 @@ async function validateAndDisposeProject( const mciContentFile = indexFolder.ensureFile( StorageUtilities.ensureFileNameIsSafe(StorageUtilities.getBaseFromName(projectSet.projectContainerName)) + + fileNameModifier + ".mci.json" ); @@ -228,12 +302,13 @@ async function validateAndDisposeProject( if (outputType !== OutputType.noReports) { const csvFile = outputStorage.rootFolder.ensureFile( StorageUtilities.ensureFileNameIsSafe(StorageUtilities.getBaseFromName(projectSet.projectContainerName)) + + fileNameModifier + ".csv" ); const pisLines = pis.getItemCsvLines(); - const csvContent = csvHeader + "\r\n" + pisLines.join("\n"); + const csvContent = ProjectInfoSet.CommonCsvHeader + "\r\n" + pisLines.join("\n"); csvFile.setContent(csvContent); @@ -241,19 +316,15 @@ async function validateAndDisposeProject( } if (mcrJsonFile) { - if (pisData.index) { - pisData.index = undefined; + if (projectSet.infoSetData.index) { + projectSet.infoSetData.index = undefined; } - const mcrContent = JSON.stringify(pisData, null, 2); + const mcrContent = JSON.stringify(projectSet.infoSetData, null, 2); mcrJsonFile.setContent(mcrContent); mcrJsonFile.saveContent(); } } - - project.dispose(); - - return projectSet; } diff --git a/app/src/cli/index.ts b/app/src/cli/index.ts index d36110b8..6fc49c87 100644 --- a/app/src/cli/index.ts +++ b/app/src/cli/index.ts @@ -25,6 +25,9 @@ import IGalleryItem, { GalleryItemType } from "../app/IGalleryItem.js"; import ProjectUtilities, { NewEntityTypeAddMode } from "../app/ProjectUtilities.js"; import Project, { ProjectAutoDeploymentMode } from "../app/Project.js"; import ProjectExporter from "../app/ProjectExporter.js"; +import Utilities from "../core/Utilities.js"; +import { ProjectInfoSuite } from "../info/IProjectInfoData.js"; +import NodeFolder from "../local/NodeFolder.js"; if (typeof btoa === "undefined") { // @ts-ignore @@ -788,29 +791,33 @@ async function validate() { Log.error(ps.ctorProjectName + " error: " + result); } } else { - // clear out icons since the aggregation won't need them, and it should save memory. - if (result.infoSetData && result.infoSetData.info && result.infoSetData.info["defaultIcon"]) { - result.infoSetData.info["defaultIcon"] = undefined; - } - - projectList.push(result as IProjectMetaState); - - const infoSet = (result as IProjectMetaState).infoSetData; - - if (infoSet) { - const items = infoSet.items; + for (const metaState of result) { + // clear out icons since the aggregation won't need them, and it should save memory. + if (metaState.infoSetData && metaState.infoSetData.info && metaState.infoSetData.info["defaultIcon"]) { + metaState.infoSetData.info["defaultIcon"] = undefined; + } - if (items) { - for (const item of items) { - if (item.iTp === InfoItemType.internalProcessingError) { - console.error( - "Internal Processing Error: " + ProjectInfoSet.getEffectiveMessageFromData(infoSet, item) - ); - setErrorLevel(ERROR_VALIDATION_INTERNALPROCESSINGERROR); - } else if (item.iTp === InfoItemType.testCompleteFail && !options.outputFolder) { - setErrorLevel(ERROR_VALIDATION_TESTFAIL); - } else if (item.iTp === InfoItemType.error && !options.outputFolder) { - setErrorLevel(ERROR_VALIDATION_ERROR); + projectList.push(metaState as IProjectMetaState); + + const infoSet = (metaState as IProjectMetaState).infoSetData; + + if (infoSet) { + const items = infoSet.items; + + if (items) { + for (const item of items) { + if (item.iTp === InfoItemType.internalProcessingError) { + console.error( + "Internal Processing Error: " + ProjectInfoSet.getEffectiveMessageFromData(infoSet, item) + ); + setErrorLevel(ERROR_VALIDATION_INTERNALPROCESSINGERROR); + } else if (item.iTp === InfoItemType.testCompleteFail && !options.outputFolder) { + console.error("Test Fail: " + ProjectInfoSet.getEffectiveMessageFromData(infoSet, item)); + setErrorLevel(ERROR_VALIDATION_TESTFAIL); + } else if (item.iTp === InfoItemType.error && !options.outputFolder) { + console.error("Error: " + ProjectInfoSet.getEffectiveMessageFromData(infoSet, item)); + setErrorLevel(ERROR_VALIDATION_ERROR); + } } } } @@ -862,12 +869,24 @@ async function aggregateReports() { if (jsonO.info && jsonO.items && jsonO.generatorName !== undefined && jsonO.generatorVersion !== undefined) { const pis = new ProjectInfoSet(undefined, undefined, undefined, jsonO.info, jsonO.items); + let suite: ProjectInfoSuite | undefined = undefined; + let baseName = StorageUtilities.getBaseFromName(fileName); if (baseName.endsWith(".mcr")) { baseName = baseName.substring(0, baseName.length - 4); } + if (baseName.endsWith("addon")) { + suite = ProjectInfoSuite.addOn; + baseName = baseName.substring(0, baseName.length - 6); + } + + if (baseName.endsWith("currentplatform")) { + suite = ProjectInfoSuite.currentPlatformVersions; + baseName = baseName.substring(0, baseName.length - 16); + } + let title = StorageUtilities.getBaseFromName(fileName); let firstDash = title.indexOf("-"); @@ -894,6 +913,7 @@ async function aggregateReports() { projectName: baseName, projectTitle: title, infoSetData: jsonO, + suite: suite, }); projectsLoaded++; @@ -912,12 +932,18 @@ async function saveAggregatedReports(projectList: IProjectMetaState[]) { let outputStorage: NodeStorage | undefined; const csvHeader = ProjectInfoSet.CommonCsvHeader; - let samplePis: ProjectInfoSet | undefined; - const allFeatureSets: { [setName: string]: { [measureName: string]: number | undefined } | undefined } = {}; - const allFields: { [featureName: string]: boolean | undefined } = {}; + let sampleProjectInfoSets: { + [suiteName: string]: ProjectInfoSet | undefined; + } = {}; - const allIssueLines: string[] = []; - const allSummaryLines: string[] = []; + const featureSetsByName: { + [suiteName: string]: { [setName: string]: { [measureName: string]: number | undefined } | undefined }; + } = {}; + + const fieldsByName: { [suiteName: string]: { [featureName: string]: boolean | undefined } } = {}; + + const issueLines: { [name: string]: string[] } = {}; + const summaryLines: { [name: string]: string[] } = {}; const mciFileList: IIndexJson = { files: [], folders: [] }; const megaContentIndex: ContentIndex = new ContentIndex(); const measures: { [featureSetName: string]: { name: string; items: { [featureName: string]: any } } } = {}; @@ -928,7 +954,14 @@ async function saveAggregatedReports(projectList: IProjectMetaState[]) { } let projectsConsidered = 0; + for (const projectSet of projectList) { + let suiteName = "all"; + + if (projectSet.suite !== undefined) { + suiteName = ProjectInfoSet.getSuiteString(projectSet.suite); + } + const pisData = projectSet.infoSetData; const contentIndex = new ContentIndex(); @@ -953,90 +986,157 @@ async function saveAggregatedReports(projectList: IProjectMetaState[]) { console.warn("Processed " + projectsConsidered + " reports, @ " + projectBaseName); } - pis.mergeFeatureSetsAndFieldsTo(allFeatureSets, allFields); + if (featureSetsByName[suiteName] === undefined) { + featureSetsByName[suiteName] = {}; + } + + const featureSets = featureSetsByName[suiteName]; + + if (fieldsByName[suiteName] === undefined) { + fieldsByName[suiteName] = {}; + } + + const fields = fieldsByName[suiteName]; + + pis.mergeFeatureSetsAndFieldsTo(featureSets, fields); projectsConsidered++; } - for (const featureSetName in allFeatureSets) { - const featureSet = allFeatureSets[featureSetName]; + for (const setName in featureSetsByName) { + const featureSets = featureSetsByName[setName]; - if (featureSet) { - measures[featureSetName] = { - name: featureSetName, - items: {}, - }; + if (featureSets) { + for (const featureSetName in featureSets) { + const featureSet = featureSets[featureSetName]; + + if (featureSet) { + measures[featureSetName] = { + name: featureSetName, + items: {}, + }; + } + } } } for (const projectSet of projectList) { + let suiteName = "all"; + + if (projectSet.suite !== undefined) { + suiteName = ProjectInfoSet.getSuiteString(projectSet.suite); + } + const pisData = projectSet.infoSetData; - const pis = new ProjectInfoSet(undefined, undefined, undefined, pisData.info, pisData.items); const projectBaseName = StorageUtilities.removeContainerExtension(projectSet.projectContainerName); + const pis = new ProjectInfoSet(undefined, undefined, undefined, pisData.info, pisData.items); - pis.mergeFeatureSetsAndFieldsTo(allFeatureSets, allFields); + if (featureSetsByName[suiteName] === undefined) { + featureSetsByName[suiteName] = {}; + } + + const featureSets = featureSetsByName[suiteName]; - samplePis = pis; + if (fieldsByName[suiteName] === undefined) { + fieldsByName[suiteName] = {}; + } - if (projectSet.infoSetData.info) { - for (const memberName in projectSet.infoSetData.info) { - if (ProjectInfoSet.isAggregableFieldName(memberName)) { - let data: { name: string; items: { [featureName: string]: any } } = dataMeasures[memberName]; + const fields = fieldsByName[suiteName]; - if (data === undefined) { - data = { name: memberName, items: {} }; - dataMeasures[memberName] = data; - } + pis.mergeFeatureSetsAndFieldsTo(featureSets, fields); - if ((projectSet.infoSetData.info as any)[memberName] !== undefined) { - data.items[projectBaseName] = (projectSet.infoSetData.info as any)[memberName]; + sampleProjectInfoSets[suiteName] = pis; + + if (projectSet.suite === undefined || projectSet.suite === ProjectInfoSuite.allExceptAddOn) { + if (projectSet.infoSetData.info) { + for (const memberName in projectSet.infoSetData.info) { + if (ProjectInfoSet.isAggregableFieldName(memberName)) { + let data: { name: string; items: { [featureName: string]: any } } = dataMeasures[memberName]; + + if (data === undefined) { + data = { name: memberName, items: {} }; + dataMeasures[memberName] = data; + } + + if ((projectSet.infoSetData.info as any)[memberName] !== undefined) { + data.items[projectBaseName] = (projectSet.infoSetData.info as any)[memberName]; + } } } } - } - if (projectSet.infoSetData.info && projectSet.infoSetData.info.featureSets) { - for (const featureSetName in allFeatureSets) { - const featureSet = projectSet.infoSetData.info.featureSets[featureSetName]; + if (projectSet.infoSetData.info && projectSet.infoSetData.info.featureSets) { + for (const featureSetName in featureSets) { + const featureSet = projectSet.infoSetData.info.featureSets[featureSetName]; - if (featureSet) { - measures[featureSetName].items[projectBaseName] = featureSet; + if (featureSet) { + measures[featureSetName].items[projectBaseName] = featureSet; + } } } } - if (allIssueLines.length <= MAX_LINES_PER_CSV_FILE) { + if (issueLines[suiteName] === undefined) { + issueLines[suiteName] = []; + } + + if (issueLines[suiteName].length <= MAX_LINES_PER_CSV_FILE) { const pisLines = pis.getItemCsvLines(); for (let j = 0; j < pisLines.length; j++) { - allIssueLines.push('"' + projectBaseName + '",' + pisLines[j]); + issueLines[suiteName].push('"' + projectBaseName + '",' + pisLines[j]); } } + if (summaryLines[suiteName] === undefined) { + summaryLines[suiteName] = []; + } + if (outputStorage) { - allSummaryLines.push(pis.getSummaryCsvLine(projectBaseName, projectSet.projectTitle, allFeatureSets)); + summaryLines[suiteName].push(pis.getSummaryCsvLine(projectBaseName, projectSet.projectTitle, featureSets)); } } - if (outputStorage && samplePis) { - if (allIssueLines.length < MAX_LINES_PER_CSV_FILE) { - let allCsvFile = outputStorage.rootFolder.ensureFile("all.csv"); + if (outputStorage) { + for (const issueLinesName in issueLines) { + if (sampleProjectInfoSets[issueLinesName]) { + const issueLinesSet = issueLines[issueLinesName]; + + if (issueLinesSet.length < MAX_LINES_PER_CSV_FILE) { + let allCsvFile = outputStorage.rootFolder.ensureFile(issueLinesName + ".csv"); - let allCsvContent = "Project," + csvHeader + "\r\n" + allIssueLines.join("\n"); + let allCsvContent = "Project," + csvHeader + "\r\n" + issueLinesSet.join("\n"); - allCsvFile.setContent(allCsvContent); + allCsvFile.setContent(allCsvContent); - await allCsvFile.saveContent(); + await allCsvFile.saveContent(); + } + } } - const projectsCsvFile = outputStorage.rootFolder.ensureFile("projects.csv"); + for (const summaryLinesName in summaryLines) { + if (featureSetsByName[summaryLinesName] === undefined) { + featureSetsByName[summaryLinesName] = {}; + } + + if (sampleProjectInfoSets[summaryLinesName]) { + const featureSets = featureSetsByName[summaryLinesName]; + + const summaryLinesSet = summaryLines[summaryLinesName]; - let projectsCsvContent = ProjectInfoSet.getSummaryCsvHeaderLine(samplePis.info, allFeatureSets); + const projectsCsvFile = outputStorage.rootFolder.ensureFile(summaryLinesName + "projects.csv"); - projectsCsvContent += "\r\n" + allSummaryLines.join("\n"); + let projectsCsvContent = ProjectInfoSet.getSummaryCsvHeaderLine( + sampleProjectInfoSets[summaryLinesName].info, + featureSets + ); - projectsCsvFile.setContent(projectsCsvContent); + projectsCsvContent += "\r\n" + summaryLinesSet.join("\n"); - await projectsCsvFile.saveContent(); + projectsCsvFile.setContent(projectsCsvContent); + + await projectsCsvFile.saveContent(); + } + } } } diff --git a/app/src/core/StandardInit.ts b/app/src/core/StandardInit.ts index 49a5ad7d..808b9c6e 100644 --- a/app/src/core/StandardInit.ts +++ b/app/src/core/StandardInit.ts @@ -7,13 +7,13 @@ export const minecraftToolDarkTheme = { colorScheme: { brand: { background: "#3c8527", // accent background for things like buttons. complemented with fore*4 - background1: "#313233", // main outermost background. in a dark theme, this should be dark. complemented with fore*1 - background2: "#48494a", - background3: "#5a5b5c", + background1: "#312f2d", // main outermost background. in a dark theme, this should be dark. complemented with fore*1 + background2: "#484644", + background3: "#5a5856", background4: "#8f8b89", background5: "#5d7850", // subtle accent background background6: "#040404", // subtle theme background. complemented with fore*6 - foreground: "red", + foreground: "#5ca547", foreground1: "#f8f8f8", // main foreground color. in a dark theme, this should be light. foreground2: "#ffffff", foreground3: "#ffffff", @@ -103,7 +103,7 @@ export const minecraftToolLightTheme = { background4: "#7b7876", background5: "#52a435", // subtle accent background background6: "#b3b3b3", // subtle theme background. complemented with fore*6 - foreground: "red", + foreground: "#3b9329", foreground1: "#1a1a1a", // main foreground color. in a dark theme, this should be light. foreground2: "#1a1a1a", // complement foreground color foreground3: "#1a1a1a", diff --git a/app/src/core/Utilities.ts b/app/src/core/Utilities.ts index a0a1fa2c..dbd3707c 100644 --- a/app/src/core/Utilities.ts +++ b/app/src/core/Utilities.ts @@ -111,8 +111,10 @@ export default class Utilities { name = name.substring(0, name.length - 7); } - if (name.startsWith("minecraft:")) { - name = name.substring(10, name.length); + const colon = name.indexOf(":"); + + if (colon >= 0) { + name = name.substring(colon + 1); } name = name.replace(/[_]/gi, " "); @@ -252,9 +254,6 @@ export default class Utilities { throw new TypeError(`Expected argument \`jsonString\` to be a \`string\`, got \`${typeof jsonString}\``); } - jsonString = jsonString.replace(/,\s*]/g, "]"); // remove trailing commas - jsonString = jsonString.replace(/,\s*}/g, "}"); // remove trailing commas - const strip = whitespace ? Utilities.stripWithWhitespace : Utilities.stripWithoutWhitespace; let isInsideString = false; @@ -277,6 +276,10 @@ export default class Utilities { } if (isInsideString) { + // fix control characters inside of strings, if they exist + if (currentCharacter === "\r" || currentCharacter === "\n" || currentCharacter === "\t") { + jsonString = jsonString.substring(0, index) + " " + jsonString.substring(index + 1); + } continue; } @@ -342,11 +345,15 @@ export default class Utilities { } } - return ( + let results = result + buffer + - (isInsideComment ? strip(jsonString.slice(offset), undefined, undefined) : jsonString.slice(offset)) - ); + (isInsideComment ? strip(jsonString.slice(offset), undefined, undefined) : jsonString.slice(offset)); + + results = results.replace(/,(\s*)]/g, "]"); // ["a", "b", ] => ["a", "b"] + results = results.replace(/,(\s*)}/g, "}"); // { "foo": "bar", } => { "foo": "bar"} + + return results; } static setIsDebug(boolVal: boolean) { diff --git a/app/src/dataform/DataForm.css b/app/src/dataform/DataForm.css index c5208fd0..3b0aec7d 100644 --- a/app/src/dataform/DataForm.css +++ b/app/src/dataform/DataForm.css @@ -74,10 +74,6 @@ padding-bottom: 8px; } -.df-fieldWrap .ui-form__checkbox { - padding-left: 35px; -} - .df-fieldWrap .ui-form__label { padding-bottom: 4px; vertical-align: bottom; @@ -86,10 +82,6 @@ min-width: 136px; } -.df-fieldWrap input { - max-width: 136px; -} - .df-fieldWrap .ui-dropdown { display: inline-block; } @@ -98,9 +90,13 @@ width: 235px; } +.df-elementBinTitle { + padding-bottom: 5px; +} + .df-cardWrapper { padding: 5px 10px 10px 10px; - border: solid 1px black; + border: outset 1px black; margin-bottom: 4px; margin-top: 4px; box-shadow: rgba(0, 0, 0, 0.25) 0px 0.2rem 0.4rem -0.075rem; @@ -115,10 +111,40 @@ grid-template-columns: 180px 80px; } +.df-sliderSet .ui-form__input { + margin-left: 10px; +} + .df-elementTitle { padding-bottom: 3px; } +.df-elementBin { + padding: 10px; + margin-left: 10px; + border: inset 1px; +} + +.df-stringArray { + display: grid; + grid-template-columns: 115px 1fr; +} + +.df-stringArrayTitle { + grid-column: 1; + padding-top: 6px; +} + +.df-stringArrayData { + grid-column: 2; +} + +.df-stringArrayData .ui-form__input { + width: 300px; + max-width: 300px; + margin-bottom: 1px; +} + .df-elementTitleInvalid { color: red; } diff --git a/app/src/dataform/DataForm.tsx b/app/src/dataform/DataForm.tsx index aaa2884d..5b79224c 100644 --- a/app/src/dataform/DataForm.tsx +++ b/app/src/dataform/DataForm.tsx @@ -1068,35 +1068,17 @@ export default class DataForm extends Component let contents = <>; if (!this.props.readOnly) { - contents = ( - /*
, data?: FormProps) => { - event.stopPropagation(); - event.preventDefault(); - event.nativeEvent.stopImmediatePropagation(); - event.nativeEvent.preventDefault(); - return false; - }} - >*/ -
{formInterior}
- //
- ); + contents =
{formInterior}
; } else { contents =
{formInterior}
; } - let paddingLevel = 0; - - if (this.props.indentLevel) { - paddingLevel = this.props.indentLevel; - } - if (header.length > 0) { headerOuter =
{header}
; } return ( -
+
addKeyedObjectArrayComponent(field: IField, formInterior: any[], descriptionElement: JSX.Element) { const val = this._getProperty(field.id, {}); const fieldInterior = []; + const childElements = []; Log.assert(val !== undefined, "Keyed object is not available in DataForm."); if (val && field.subForm && field.subFields) { const keys = []; - const headerElement =
{FieldUtilities.getFieldTitle(field)}
; + const headerElement =
{FieldUtilities.getFieldTitle(field)}
; this.formComponentNames.push(field.id); this.formComponents.push(headerElement); fieldInterior.push(headerElement); @@ -1220,9 +1203,22 @@ export default class DataForm extends Component this.formComponentNames.push(propertyId); this.formComponents.push(subForm); - fieldInterior.push(subForm); + childElements.push(subForm); } + + fieldInterior.push( +
+ {childElements} +
+ ); } + formInterior.push(
{fieldInterior} @@ -1234,29 +1230,23 @@ export default class DataForm extends Component addKeyedStringArrayComponent(field: IField, formInterior: any[], descriptionElement: JSX.Element) { const val = this._getProperty(field.id, {}); const fieldInterior = []; + const childElements = []; + Log.assert(val !== undefined, "Keyed string array not available in data form."); - if (val && field.subForm && field.subFields) { + if (val) { const keys = []; - const headerElement =
{FieldUtilities.getFieldTitle(field)}
; + const headerElement =
{FieldUtilities.getFieldTitle(field)}
; this.formComponentNames.push(field.id); this.formComponents.push(headerElement); fieldInterior.push(headerElement); - for (const key in field.subFields) { + for (const key in val) { keys.push(key); let title = key; - if (field.subFields && field.subFields[key]) { - const subField = field.subFields[key]; - - if (subField.title) { - title = subField.title; - } - } - let objKey = field.id; if (this.props.objectKey) { @@ -1285,14 +1275,27 @@ export default class DataForm extends Component this.formComponentNames.push(propertyId); this.formComponents.push(subForm); - fieldInterior.push( -
-
{title}
-
{subForm}
+ childElements.push( +
+
{title}
+
{subForm}
); } } + + fieldInterior.push( +
+ {childElements} +
+ ); + formInterior.push(
{fieldInterior} @@ -1346,11 +1349,12 @@ export default class DataForm extends Component const arrayOfDataVal = this._getProperty(field.id, []); const fieldTopper = []; const fieldInterior = []; + const childElements = []; Log.assert(arrayOfDataVal, "DFAOAC"); if (arrayOfDataVal !== undefined && field.subForm && arrayOfDataVal instanceof Array) { - const headerElement =
{FieldUtilities.getFieldTitle(field)}
; + const headerElement =
{FieldUtilities.getFieldTitle(field)}
; this.formComponentNames.push(field.id); this.formComponents.push(headerElement); fieldTopper.push(headerElement); @@ -1445,7 +1449,7 @@ export default class DataForm extends Component this.formComponentNames.push(fieldName); this.formComponents.push(subForm); - fieldInterior.push(subForm); + childElements.push(subForm); } } else { for (const index in arrayOfDataVal) { @@ -1526,10 +1530,23 @@ export default class DataForm extends Component this.formComponentNames.push(propertyId); this.formComponents.push(subForm); - fieldInterior.push(subForm); + childElements.push(subForm); } } } + + fieldInterior.push( +
+ {childElements} +
+ ); + formInterior.push(
{fieldTopper} @@ -1635,12 +1652,6 @@ export default class DataForm extends Component const propertyId = field.id; - let indentLevel = 0; - - if (this.props.indentLevel) { - indentLevel = this.props.indentLevel; - } - const subForm = ( parentField={field} theme={this.props.theme} title={FieldUtilities.getFieldTitle(field)} - defaultVisualExperience={field.visualExperience} + defaultVisualExperience={FieldVisualExperience.normal} displayTitle={true} - indentLevel={indentLevel} + indentLevel={0} onPropertyChanged={this._handleObjectSubFormPropertyChange} definition={field.subForm} readOnly={this.props.readOnly} diff --git a/app/src/dataform/FieldUtilities.ts b/app/src/dataform/FieldUtilities.ts index bcac53c9..b6999326 100644 --- a/app/src/dataform/FieldUtilities.ts +++ b/app/src/dataform/FieldUtilities.ts @@ -131,33 +131,36 @@ export default class FieldUtilities { } const actualVal = FieldUtilities.getFieldValue(field, container); - const comp = condition.comparison.toLowerCase(); - if (comp === ComparisonType.equals) { - if (condition.value !== undefined && actualVal !== condition.value) { - return false; - } + if (condition.comparison) { + const comp = condition.comparison.toLowerCase(); + + if (comp === ComparisonType.equals) { + if (condition.value !== undefined && actualVal !== condition.value) { + return false; + } - if (condition.anyValues !== undefined) { - let foundMatch = false; + if (condition.anyValues !== undefined) { + let foundMatch = false; - for (const val of condition.anyValues) { - if (val === actualVal) { - foundMatch = true; + for (const val of condition.anyValues) { + if (val === actualVal) { + foundMatch = true; + } } - } - if (!foundMatch) { - return false; + if (!foundMatch) { + return false; + } } + } else if (comp === ComparisonType.isDefined && (actualVal === undefined || actualVal === null)) { + return false; + } else if ( + comp === ComparisonType.isNonEmpty && + (actualVal === undefined || actualVal === null || (typeof actualVal === "string" && actualVal.length <= 0)) + ) { + return false; } - } else if (comp === ComparisonType.isDefined && (actualVal === undefined || actualVal === null)) { - return false; - } else if ( - comp === ComparisonType.isNonEmpty && - (actualVal === undefined || actualVal === null || (typeof actualVal === "string" && actualVal.length <= 0)) - ) { - return false; } } diff --git a/app/src/dataform/Range.css b/app/src/dataform/Range.css index 7c7f4cbb..09939504 100644 --- a/app/src/dataform/Range.css +++ b/app/src/dataform/Range.css @@ -24,6 +24,18 @@ padding-right: 6px !important; } +.rng-intro { + display: table-cell; + padding-right: 9px; + text-wrap: nowrap; +} + +.rng-joiner { + display: table-cell; + padding-left: 4px; + padding-right: 9px; +} + .rng-ambient { display: table-cell; width: 50px; diff --git a/app/src/dataform/Range.tsx b/app/src/dataform/Range.tsx index 5c3b7d7d..fec0f653 100644 --- a/app/src/dataform/Range.tsx +++ b/app/src/dataform/Range.tsx @@ -168,6 +168,7 @@ export default class Range extends Component { {header}
+ Random number between
{ onChange={this._handleMinChange} />
+ and
{ + async generate(project: Project): Promise { const items: ProjectInfoItem[] = []; return items; diff --git a/app/src/info/ProjectInfoSet.ts b/app/src/info/ProjectInfoSet.ts index b85e6ed2..ee12e2b4 100644 --- a/app/src/info/ProjectInfoSet.ts +++ b/app/src/info/ProjectInfoSet.ts @@ -50,10 +50,73 @@ export default class ProjectInfoSet { } } + static getSuiteString(suite: ProjectInfoSuite) { + switch (suite) { + case ProjectInfoSuite.addOn: + return "addon"; + + case ProjectInfoSuite.currentPlatformVersions: + return "currentplatform"; + + default: + return "all"; + } + } + get completedGeneration() { return this._completedGeneration; } + get errorAndFailCount() { + let count = 0; + + for (const item of this.items) { + if ( + item.itemType === InfoItemType.error || + item.itemType === InfoItemType.internalProcessingError || + item.itemType === InfoItemType.testCompleteFail + ) { + count++; + } + } + + return count; + } + + get errorFailWarnCount() { + let count = 0; + + for (const item of this.items) { + if ( + item.itemType === InfoItemType.error || + item.itemType === InfoItemType.warning || + item.itemType === InfoItemType.internalProcessingError || + item.itemType === InfoItemType.testCompleteFail + ) { + count++; + } + } + + return count; + } + + get errorFailWarnString() { + let str: string[] = []; + + for (const item of this.items) { + if ( + item.itemType === InfoItemType.error || + item.itemType === InfoItemType.warning || + item.itemType === InfoItemType.internalProcessingError || + item.itemType === InfoItemType.testCompleteFail + ) { + str.push(item.toString()); + } + } + + return str.join("\r\n"); + } + constructor( project?: Project, suite?: ProjectInfoSuite, @@ -142,6 +205,30 @@ export default class ProjectInfoSet { return undefined; } + getCountByType(itemType: InfoItemType) { + let count = 0; + + for (const item of this.items) { + if (item.itemType === itemType) { + count++; + } + } + + return count; + } + + getSummaryByType(itemType: InfoItemType) { + let str: string[] = []; + + for (const item of this.items) { + if (str.length < 50 && item.itemType === itemType) { + str.push(item.toString()); + } + } + + return str.join("\r\n"); + } + matchesSuite( generator: IProjectFileInfoGenerator | IProjectInfoGenerator | IProjectItemInfoGenerator | IProjectInfoGeneratorBase ) { @@ -298,6 +385,16 @@ export default class ProjectInfoSet { this._pendingGenerateRequests = []; this._isGenerating = false; + this.info.errorCount = this.getCountByType(InfoItemType.error); + this.info.internalProcessingErrorCount = this.getCountByType(InfoItemType.internalProcessingError); + this.info.warningCount = this.getCountByType(InfoItemType.warning); + this.info.testSuccessCount = this.getCountByType(InfoItemType.testCompleteSuccess); + this.info.testFailCount = this.getCountByType(InfoItemType.testCompleteFail); + + this.info.errorSummary = this.getSummaryByType(InfoItemType.error); + this.info.internalProcessingErrorSummary = this.getSummaryByType(InfoItemType.internalProcessingError); + this.info.testFailSummary = this.getSummaryByType(InfoItemType.testCompleteFail); + if (valOperId !== undefined) { await this.project?.carto.notifyOperationEnded( valOperId, diff --git a/app/src/info/StrictPlatformInfoGenerator.ts b/app/src/info/StrictPlatformInfoGenerator.ts index bfeea38e..869bf405 100644 --- a/app/src/info/StrictPlatformInfoGenerator.ts +++ b/app/src/info/StrictPlatformInfoGenerator.ts @@ -43,7 +43,7 @@ export default class StrictPlatformInfoGenerator implements IProjectInfoGenerato for (let i = 0; i < itemsCopy.length; i++) { const pi = itemsCopy[i]; - if (pi.itemType === ProjectItemType.entityTypeBehaviorJson) { + if (pi.itemType === ProjectItemType.entityTypeBehavior) { await pi.ensureFileStorage(); if (pi.file) { diff --git a/app/src/info/TextureInfoGenerator.ts b/app/src/info/TextureInfoGenerator.ts index 63e4171d..e0030f2b 100644 --- a/app/src/info/TextureInfoGenerator.ts +++ b/app/src/info/TextureInfoGenerator.ts @@ -365,11 +365,11 @@ export default class TextureInfoGenerator implements IProjectInfoGenerator { const entityTypeResourceDef = await EntityTypeResourceDefinition.ensureOnFile(projectItem.file); if ( - entityTypeResourceDef?.dataWrapper && - entityTypeResourceDef?.dataWrapper["minecraft:client_entity"] && - entityTypeResourceDef?.dataWrapper["minecraft:client_entity"].description + entityTypeResourceDef?._dataWrapper && + entityTypeResourceDef?._dataWrapper["minecraft:client_entity"] && + entityTypeResourceDef?._dataWrapper["minecraft:client_entity"].description ) { - const desc = entityTypeResourceDef.dataWrapper["minecraft:client_entity"].description; + const desc = entityTypeResourceDef._dataWrapper["minecraft:client_entity"].description; const textures = desc.textures; if (textures) { diff --git a/app/src/integrations/BlockbenchModel.ts b/app/src/integrations/BlockbenchModel.ts new file mode 100644 index 00000000..98727309 --- /dev/null +++ b/app/src/integrations/BlockbenchModel.ts @@ -0,0 +1,379 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import IFile from "../storage/IFile"; +import { EventDispatcher, IEventHandler } from "ste-events"; +import StorageUtilities from "../storage/StorageUtilities"; +import IBlockbenchModel, { IBlockbenchTexture } from "./IBlockbenchModel"; +import ProjectItem from "../app/ProjectItem"; +import ModelGeometryDefinition from "../minecraft/ModelGeometryDefinition"; +import { ProjectItemType } from "../app/IProjectItemData"; +import EntityTypeResourceDefinition from "../minecraft/EntityTypeResourceDefinition"; +import Utilities from "../core/Utilities"; +import { IGeometryBoneCube } from "../minecraft/IModelGeometry"; +import { Exifr } from "exifr"; + +export default class BlockbenchModel { + private _file?: IFile; + private _id?: string; + private _isLoaded: boolean = false; + + public definition?: IBlockbenchModel; + + private _onLoaded = new EventDispatcher(); + + public get isLoaded() { + return this._isLoaded; + } + + public get file() { + return this._file; + } + + public set file(newFile: IFile | undefined) { + this._file = newFile; + } + + public get onLoaded() { + return this._onLoaded.asEvent(); + } + + public get name() { + if (this.definition) { + return this.definition.name; + } + + return undefined; + } + + public get id() { + return this._id; + } + + public set id(newId: string | undefined) { + this._id = newId; + } + + static async ensureOnFile(file: IFile, loadHandler?: IEventHandler) { + let bd: BlockbenchModel | undefined; + + if (file.manager === undefined) { + bd = new BlockbenchModel(); + + bd.file = file; + + file.manager = bd; + } + + if (file.manager !== undefined && file.manager instanceof BlockbenchModel) { + bd = file.manager as BlockbenchModel; + + if (!bd.isLoaded && loadHandler) { + bd.onLoaded.subscribe(loadHandler); + } + + await bd.load(); + + return bd; + } + + return bd; + } + + async persist() { + if (this._file === undefined) { + return; + } + + const pjString = JSON.stringify(this.definition, null, 2); + + this._file.setContent(pjString); + } + + async save() { + if (this._file === undefined) { + return; + } + + this.persist(); + + await this._file.saveContent(false); + } + + async load() { + if (this._file === undefined || this._isLoaded) { + return; + } + + await this._file.loadContent(); + + if (this._file.content === null || this._file.content instanceof Uint8Array) { + return; + } + + this.id = this._file.name; + + this.definition = StorageUtilities.getJsonObject(this._file); + + this._isLoaded = true; + } + + static createEmptyModel(name: string, identifier: string): IBlockbenchModel { + return { + meta: { + format_version: "4.10", + model_format: "bedrock", + box_uv: true, + }, + name: name, + model_identifier: identifier, + variable_placeholder_buttons: [], + variable_placeholders: "", + visible_box: [1, 1, 1], + bedrock_animation_mode: "entity", + timeline_setups: [], + unhandled_root_fields: {}, + resolution: { width: 64, height: 32 }, + elements: [], + outliner: [], + }; + } + + static async exportModel(modelProjectItem: ProjectItem, modelIndex?: number): Promise { + if (modelIndex === undefined) { + modelIndex = 0; + } + + await modelProjectItem.ensureFileStorage(); + + let clientEntityItem: ProjectItem | undefined = undefined; + let clientEntity: EntityTypeResourceDefinition | undefined = undefined; + let model: ModelGeometryDefinition | undefined = undefined; + + if (modelProjectItem.file) { + model = await ModelGeometryDefinition.ensureOnFile(modelProjectItem.file); + } + + if (modelProjectItem.parentItems) { + for (const parentItemOuter of modelProjectItem.parentItems) { + if (parentItemOuter.parentItem.itemType === ProjectItemType.entityTypeResource) { + clientEntityItem = parentItemOuter.parentItem; + if (clientEntityItem && clientEntityItem.file) { + clientEntity = await EntityTypeResourceDefinition.ensureOnFile(clientEntityItem.file); + } + } + } + } + + if (!model || model.identifiers.length === 0 || !model.file || !model.wrapper || model.definitions.length === 0) { + return undefined; + } + + const bbmodel = this.createEmptyModel( + StorageUtilities.getBaseFromName(model.file.name), + model.identifiers[modelIndex] + ); + + const textureWidth = model.getTextureWidth(modelIndex); + const textureHeight = model.getTextureHeight(modelIndex); + + if (textureWidth !== undefined && textureHeight !== undefined) { + bbmodel.resolution = { + width: textureWidth, + height: textureHeight, + }; + } + + const visibleBoundsWidth = model.getVisibleBoundsWidth(modelIndex); + const visibleBoundsHeight = model.getVisibleBoundsHeight(modelIndex); + const visibleBoundsOffset = model.getVisibleBoundsOffset(modelIndex); + + if (visibleBoundsWidth && visibleBoundsHeight && visibleBoundsOffset && visibleBoundsOffset.length > 1) { + bbmodel.visible_box = [visibleBoundsWidth, visibleBoundsHeight, visibleBoundsOffset[1]]; + } + + const def = model.definitions[modelIndex]; + + for (const bone of def.bones) { + if (bone.cubes.length >= 0) { + const childrenIds = []; + + for (const cube of bone.cubes) { + const id = Utilities.createUuid(); + + if (cube.origin && cube.origin.length === 3 && cube.size && cube.size.length === 3) { + const cubeTo = new Array(3); + + cubeTo[0] = cube.origin[0] + cube.size[0]; + cubeTo[1] = cube.origin[1] + cube.size[1]; + cubeTo[2] = cube.origin[2] + cube.size[2]; + + bbmodel.elements?.push({ + name: bone.name, + box_uv: true, + rescale: false, + locked: false, + light_emission: 0, + render_order: "default", + allow_mirror_modeling: true, + from: cube.origin, + to: cubeTo, + autouv: 0, + color: 0, + rotation: bone.bind_pose_rotation ? bone.bind_pose_rotation : [0, 0, 0], + origin: bone.pivot, + uv_offset: cube.uv, + type: "cube", + faces: { + north: { uv: BlockbenchModel.getNorthBoxUvCoordinates(cube), texture: 0 }, + east: { uv: BlockbenchModel.getEastBoxUvCoordinates(cube), texture: 0 }, + south: { uv: BlockbenchModel.getSouthBoxUvCoordinates(cube), texture: 0 }, + west: { uv: BlockbenchModel.getWestBoxUvCoordinates(cube), texture: 0 }, + up: { uv: BlockbenchModel.getUpBoxUvCoordinates(cube), texture: 0 }, + down: { uv: BlockbenchModel.getDownBoxUvCoordinates(cube), texture: 0 }, + }, + uuid: id, + }); + + childrenIds.push(id); + } + } + + bbmodel.outliner?.push({ + name: bone.name, + origin: bone.pivot, + bedrock_binding: "", + color: 0, + uuid: Utilities.createUuid(), + export: true, + mirror_uv: false, + isOpen: false, + locked: false, + visibility: true, + autouv: 0, + children: childrenIds, + }); + } + } + + let textureList: IBlockbenchTexture[] = []; + bbmodel.textures = []; + + if (clientEntity && clientEntityItem && clientEntityItem.file) { + const textures = clientEntity.getTextureItems(clientEntityItem); + + if (textures) { + for (const textureName in textures) { + const textureItem = textures[textureName]; + + if (textureName && textureItem && textureItem.file) { + await textureItem.file.loadContent(); + const exifr = new Exifr({}); + + if (textureItem.file.content) { + try { + await exifr.read(textureItem.file.content); + + const results = await exifr.parse(); + + const relativePath = clientEntityItem.file.getRelativePathFor(textureItem.file); + const contentStr = StorageUtilities.getContentAsString(textureItem.file); + + if (relativePath && contentStr) { + textureList.push({ + path: textureItem.file.storageRelativePath, + name: textureItem.file.name, + folder: "", + namespace: "", + id: textureList.length.toString(), + group: "", + width: results.ImageWidth, + height: results.ImageHeight, + uv_width: results.ImageWidth, + uv_height: results.ImageHeight, + particle: false, + use_as_default: false, + layers_enabled: false, + sync_to_project: "", + render_mode: "default", + render_sides: "auto", + frame_time: 1, + frame_order_type: "loop", + frame_order: "", + frame_interpolate: false, + visible: true, + internal: true, + saved: true, + uuid: Utilities.createUuid(), + relative_path: relativePath, + source: contentStr, + }); + + bbmodel.textures?.push(textureList[textureList.length - 1]); + } + } catch (e) {} + } + } + } + } + } + + return bbmodel; + } + + /* + Standard Box UV Mapping: + + +s0-+s0-+ + | u | d | < s2 + +s2-+s0-+s0-+s2-+ + | e | n | w | s | < s1 + +---+---+---+---+ + + bb coordinates are: x1, y1, x2, y2 + */ + + static getUpBoxUvCoordinates(cube: IGeometryBoneCube) { + return [cube.uv[0] + cube.size[2], cube.uv[1], cube.uv[0] + cube.size[2] + cube.size[0], cube.uv[1] + cube.size[2]]; + } + + static getDownBoxUvCoordinates(cube: IGeometryBoneCube) { + return [ + cube.uv[0] + cube.size[0] + cube.size[2], + cube.uv[1], + cube.uv[0] + cube.size[2] + cube.size[0] * 2, + cube.uv[1] + cube.size[2], + ]; + } + + static getEastBoxUvCoordinates(cube: IGeometryBoneCube) { + return [cube.uv[0], cube.uv[1] + cube.size[2], cube.uv[0] + cube.size[2], cube.uv[1] + cube.size[2] + cube.size[1]]; + } + + static getNorthBoxUvCoordinates(cube: IGeometryBoneCube) { + return [ + cube.uv[0] + cube.size[2], + cube.uv[1] + cube.size[2], + cube.uv[0] + cube.size[2] + cube.size[0], + cube.uv[1] + cube.size[2] + cube.size[1], + ]; + } + + static getWestBoxUvCoordinates(cube: IGeometryBoneCube) { + return [ + cube.uv[0] + cube.size[2] + cube.size[0], + cube.uv[1] + cube.size[2], + cube.uv[0] + cube.size[2] * 2 + cube.size[0], + cube.uv[1] + cube.size[2] + cube.size[1], + ]; + } + + static getSouthBoxUvCoordinates(cube: IGeometryBoneCube) { + return [ + cube.uv[0] + cube.size[2] * 2 + cube.size[0], + cube.uv[1] + cube.size[2], + cube.uv[0] + cube.size[2] * 2 + cube.size[0] * 2, + cube.uv[1] + cube.size[2] + cube.size[1], + ]; + } +} diff --git a/app/src/integrations/IBlockbenchModel.ts b/app/src/integrations/IBlockbenchModel.ts new file mode 100644 index 00000000..96f110f7 --- /dev/null +++ b/app/src/integrations/IBlockbenchModel.ts @@ -0,0 +1,148 @@ +export default interface IBlockbenchModel { + meta: IBlockbenchModelMetadata; + name: string; + model_identifier: string; + elements?: IBlockbenchElement[]; + outliner?: IBlockbenchOutlineItem[]; + textures?: IBlockbenchTexture[]; + visible_box: number[]; + variable_placeholders: string; + variable_placeholder_buttons: string[]; + bedrock_animation_mode: string; + timeline_setups: string[]; + unhandled_root_fields: {}; + resolution: IBlockbench2DSize; +} + +export interface IBlockbenchModelMetadata { + format_version: string; + model_format: string; + box_uv: boolean; +} + +export interface IBlockbench2DSize { + width: number; + height: number; +} + +export interface IBlockbenchOutlineItem { + name: string; + origin: number[]; + bedrock_binding: string; + color: number; + uuid: string; + export: boolean; + mirror_uv: boolean; + isOpen: boolean; + locked: boolean; + visibility: boolean; + autouv: number; + children: string[]; +} + +export interface IBlockbenchTexture { + path: string; + name: string; + folder: string; + namespace: string; + id: string; + group: string; + width: number; + height: number; + uv_width: number; + uv_height: number; + particle: boolean; + use_as_default: boolean; + layers_enabled: boolean; + sync_to_project: string; + render_mode: string; + render_sides: string; + frame_time: number; + frame_order_type: string; + frame_order: string; + frame_interpolate: boolean; + visible: boolean; + internal: boolean; + saved: boolean; + uuid: string; + relative_path: string; + source: string; +} + +export interface IBlockbenchElement { + name: string; + box_uv: boolean; + rescale: boolean; + locked: boolean; + render_order: string; + allow_mirror_modeling: boolean; + from: number[]; + to: number[]; + autouv: number; + color: number; + origin: number[]; + uv_offset: number[]; + rotation: number[]; + light_emission?: number; + faces: IBlockbenchFaceSet; + type: string; + uuid: string; +} + +export interface IBlockbenchFaceSet { + north: IBlockbenchFace; + east: IBlockbenchFace; + south: IBlockbenchFace; + west: IBlockbenchFace; + up: IBlockbenchFace; + down: IBlockbenchFace; +} + +export interface IBlockbenchFace { + uv: number[]; + texture: number; +} + +export interface IBlockbenchTexture { + path: string; + name: string; + folder: string; + namespace: string; + id: string; + width: number; + height: number; + uv_width: number; + uv_height: number; + particle: boolean; + use_as_default: boolean; + layers_enabled: boolean; + sync_to_project: string; + render_mode: string; + render_sides: string; + frame_time: number; + frame_order_type: string; + frame_order: string; + frame_interpolate: boolean; + visible: boolean; + internal: boolean; + saved: boolean; + uuid: string; + relative_path: string; + source: string; +} + +export interface IBlockbenchOutliner { + name: string; + origin: number[]; + rotation: number[]; + bedrock_binding: string; + color: number; + uuid: string; + export: boolean; + mirror_uv: boolean; + isOpen: boolean; + locked: boolean; + visibility: boolean; + autouv: number; + children: string[]; +} diff --git a/app/src/local/LocalUtilities.ts b/app/src/local/LocalUtilities.ts index 4317cf39..9d3d3043 100644 --- a/app/src/local/LocalUtilities.ts +++ b/app/src/local/LocalUtilities.ts @@ -32,11 +32,11 @@ export default class LocalUtilities implements ILocalUtilities { if (this.isWindows) { return ( this.userDataPath + - NodeStorage.folderDelimiter + + NodeStorage.platformFolderDelimiter + "AppData" + - NodeStorage.folderDelimiter + + NodeStorage.platformFolderDelimiter + "Local" + - NodeStorage.folderDelimiter + NodeStorage.platformFolderDelimiter ); } else { return this.userDataPath; @@ -47,16 +47,16 @@ export default class LocalUtilities implements ILocalUtilities { if (this.isWindows) { return ( this.userDataPath + - NodeStorage.folderDelimiter + + NodeStorage.platformFolderDelimiter + "AppData" + - NodeStorage.folderDelimiter + + NodeStorage.platformFolderDelimiter + "Roaming" + - NodeStorage.folderDelimiter + + NodeStorage.platformFolderDelimiter + "logs" + - NodeStorage.folderDelimiter + NodeStorage.platformFolderDelimiter ); } else { - return "." + NodeStorage.folderDelimiter; + return "." + NodeStorage.platformFolderDelimiter; } } @@ -64,15 +64,15 @@ export default class LocalUtilities implements ILocalUtilities { return ( this.localAppDataPath + "Packages" + - NodeStorage.folderDelimiter + + NodeStorage.platformFolderDelimiter + "Microsoft.MinecraftUWP_8wekyb3d8bbwe" + - NodeStorage.folderDelimiter + + NodeStorage.platformFolderDelimiter + "LocalState" + - NodeStorage.folderDelimiter + + NodeStorage.platformFolderDelimiter + "games" + - NodeStorage.folderDelimiter + + NodeStorage.platformFolderDelimiter + "com.mojang" + - NodeStorage.folderDelimiter + NodeStorage.platformFolderDelimiter ); } @@ -80,15 +80,15 @@ export default class LocalUtilities implements ILocalUtilities { return ( this.localAppDataPath + "Packages" + - NodeStorage.folderDelimiter + + NodeStorage.platformFolderDelimiter + "Microsoft.MinecraftWindowsBeta_8wekyb3d8bbwe" + - NodeStorage.folderDelimiter + + NodeStorage.platformFolderDelimiter + "LocalState" + - NodeStorage.folderDelimiter + + NodeStorage.platformFolderDelimiter + "games" + - NodeStorage.folderDelimiter + + NodeStorage.platformFolderDelimiter + "com.mojang" + - NodeStorage.folderDelimiter + NodeStorage.platformFolderDelimiter ); } @@ -100,7 +100,7 @@ export default class LocalUtilities implements ILocalUtilities { (this.isWindows ? "" : ".") + this.#productNameSeed + "_test" + - NodeStorage.folderDelimiter; + NodeStorage.platformFolderDelimiter; return path; } @@ -113,7 +113,7 @@ export default class LocalUtilities implements ILocalUtilities { (this.isWindows ? "" : ".") + this.#productNameSeed + "_cli" + - NodeStorage.folderDelimiter; + NodeStorage.platformFolderDelimiter; return path; } @@ -126,7 +126,7 @@ export default class LocalUtilities implements ILocalUtilities { (this.isWindows ? "" : ".") + this.#productNameSeed + "_server" + - NodeStorage.folderDelimiter; + NodeStorage.platformFolderDelimiter; return path; } @@ -139,7 +139,7 @@ export default class LocalUtilities implements ILocalUtilities { (this.isWindows ? "" : ".") + this.#productNameSeed + "_worlds" + - NodeStorage.folderDelimiter; + NodeStorage.platformFolderDelimiter; return path; } @@ -147,7 +147,7 @@ export default class LocalUtilities implements ILocalUtilities { get serversPath() { let path = this.serverWorkingPath; - path = NodeStorage.ensureEndsWithDelimiter(path) + "servers" + NodeStorage.folderDelimiter; + path = NodeStorage.ensureEndsWithDelimiter(path) + "servers" + NodeStorage.platformFolderDelimiter; return path; } @@ -155,7 +155,7 @@ export default class LocalUtilities implements ILocalUtilities { get sourceServersPath() { let path = this.serverWorkingPath; - path = NodeStorage.ensureEndsWithDelimiter(path) + "serverSources" + NodeStorage.folderDelimiter; + path = NodeStorage.ensureEndsWithDelimiter(path) + "serverSources" + NodeStorage.platformFolderDelimiter; return path; } @@ -163,7 +163,7 @@ export default class LocalUtilities implements ILocalUtilities { get packCachePath() { let path = this.serverWorkingPath; - path = NodeStorage.ensureEndsWithDelimiter(path) + "packCache" + NodeStorage.folderDelimiter; + path = NodeStorage.ensureEndsWithDelimiter(path) + "packCache" + NodeStorage.platformFolderDelimiter; return path; } @@ -171,7 +171,7 @@ export default class LocalUtilities implements ILocalUtilities { get envPrefsPath() { let path = this.serverWorkingPath; - path = NodeStorage.ensureEndsWithDelimiter(path) + "envprefs" + NodeStorage.folderDelimiter; + path = NodeStorage.ensureEndsWithDelimiter(path) + "envprefs" + NodeStorage.platformFolderDelimiter; return path; } @@ -262,7 +262,7 @@ export default class LocalUtilities implements ILocalUtilities { if (this.isWindows) { fullPath += path.replace(/\//g, "\\"); } else { - fullPath += path.replace(/\\/g, NodeStorage.folderDelimiter); + fullPath += path.replace(/\\/g, NodeStorage.platformFolderDelimiter); } return fullPath; diff --git a/app/src/local/NodeFile.ts b/app/src/local/NodeFile.ts index e775389f..5b8f6bbf 100644 --- a/app/src/local/NodeFile.ts +++ b/app/src/local/NodeFile.ts @@ -20,8 +20,8 @@ export default class NodeFile extends FileBase implements IFile { get fullPath() { let path = this._parentFolder.fullPath; - if (!path.endsWith(NodeStorage.folderDelimiter)) { - path += NodeStorage.folderDelimiter; + if (!path.endsWith(NodeStorage.platformFolderDelimiter)) { + path += NodeStorage.platformFolderDelimiter; } return path + this.name; diff --git a/app/src/local/NodeFolder.ts b/app/src/local/NodeFolder.ts index 0a0c27fa..5f2ac7fb 100644 --- a/app/src/local/NodeFolder.ts +++ b/app/src/local/NodeFolder.ts @@ -51,8 +51,8 @@ export default class NodeFolder extends FolderBase implements IFolder { get fullPath() { let path = this._path; - if (!path.endsWith(NodeStorage.folderDelimiter)) { - path += NodeStorage.folderDelimiter; + if (!path.endsWith(NodeStorage.platformFolderDelimiter)) { + path += NodeStorage.platformFolderDelimiter; } return path + this.name; @@ -403,7 +403,7 @@ export default class NodeFolder extends FolderBase implements IFolder { addFilesToInclusionList, listings, destStorageRelativePath, - copyPath + folderName + NodeStorage.folderDelimiter + copyPath + folderName + NodeStorage.platformFolderDelimiter ); } } @@ -483,8 +483,8 @@ export default class NodeFolder extends FolderBase implements IFolder { results.forEach((fileName: string) => { let filePath = this.fullPath; - if (!filePath.endsWith(NodeStorage.folderDelimiter)) { - filePath += NodeStorage.folderDelimiter; + if (!filePath.endsWith(NodeStorage.platformFolderDelimiter)) { + filePath += NodeStorage.platformFolderDelimiter; } filePath += fileName; diff --git a/app/src/local/NodeStorage.ts b/app/src/local/NodeStorage.ts index a0e2b5e5..ba0d386f 100644 --- a/app/src/local/NodeStorage.ts +++ b/app/src/local/NodeStorage.ts @@ -12,17 +12,21 @@ export default class NodeStorage extends StorageBase implements IStorage { rootFolder: NodeFolder; - static folderDelimiter = path.sep; + static platformFolderDelimiter = path.sep; + + get folderDelimiter() { + return path.sep; + } constructor(incomingPath: string, name: string) { super(); - if (NodeStorage.folderDelimiter === "\\") { - incomingPath = incomingPath.replace(/\//gi, NodeStorage.folderDelimiter); + if (NodeStorage.platformFolderDelimiter === "\\") { + incomingPath = incomingPath.replace(/\//gi, NodeStorage.platformFolderDelimiter); incomingPath = incomingPath.replace(/\\\\/gi, "\\"); - } else if (NodeStorage.folderDelimiter === "/") { - incomingPath = incomingPath.replace(/\\/gi, NodeStorage.folderDelimiter); - incomingPath = incomingPath.replace(/\/\//gi, NodeStorage.folderDelimiter); + } else if (NodeStorage.platformFolderDelimiter === "/") { + incomingPath = incomingPath.replace(/\\/gi, NodeStorage.platformFolderDelimiter); + incomingPath = incomingPath.replace(/\/\//gi, NodeStorage.platformFolderDelimiter); } this.rootPath = incomingPath; @@ -34,8 +38,8 @@ export default class NodeStorage extends StorageBase implements IStorage { joinPath(pathA: string, pathB: string) { let fullPath = pathA; - if (!fullPath.endsWith(NodeStorage.folderDelimiter)) { - fullPath += NodeStorage.folderDelimiter; + if (!fullPath.endsWith(NodeStorage.platformFolderDelimiter)) { + fullPath += NodeStorage.platformFolderDelimiter; } fullPath += pathB; @@ -44,7 +48,7 @@ export default class NodeStorage extends StorageBase implements IStorage { } static getParentFolderPath(parentPath: string) { - const lastDelim = parentPath.lastIndexOf(this.folderDelimiter); + const lastDelim = parentPath.lastIndexOf(this.platformFolderDelimiter); if (lastDelim < 0) { return parentPath; @@ -54,16 +58,16 @@ export default class NodeStorage extends StorageBase implements IStorage { } public static ensureEndsWithDelimiter(pth: string) { - if (!pth.endsWith(NodeStorage.folderDelimiter)) { - pth = pth + NodeStorage.folderDelimiter; + if (!pth.endsWith(NodeStorage.platformFolderDelimiter)) { + pth = pth + NodeStorage.platformFolderDelimiter; } return pth; } public static ensureStartsWithDelimiter(pth: string) { - if (!pth.startsWith(NodeStorage.folderDelimiter)) { - pth = NodeStorage.folderDelimiter + pth; + if (!pth.startsWith(NodeStorage.platformFolderDelimiter)) { + pth = NodeStorage.platformFolderDelimiter + pth; } return pth; diff --git a/app/src/manager/BehaviorPackEntityTypeManager.ts b/app/src/manager/BehaviorPackEntityTypeManager.ts index 40ea222a..428dd175 100644 --- a/app/src/manager/BehaviorPackEntityTypeManager.ts +++ b/app/src/manager/BehaviorPackEntityTypeManager.ts @@ -151,7 +151,7 @@ export default class BehaviorPackEntityTypeManager implements IProjectInfoGenera for (let i = 0; i < itemsCopy.length; i++) { const pi = itemsCopy[i]; - if (pi.itemType === ProjectItemType.entityTypeBehaviorJson) { + if (pi.itemType === ProjectItemType.entityTypeBehavior) { await pi.ensureFileStorage(); if (pi.file) { diff --git a/app/src/minecraft/BehaviorManifestDefinition.ts b/app/src/minecraft/BehaviorManifestDefinition.ts index 279b153e..8f40de2a 100644 --- a/app/src/minecraft/BehaviorManifestDefinition.ts +++ b/app/src/minecraft/BehaviorManifestDefinition.ts @@ -3,12 +3,13 @@ import IFile from "../storage/IFile"; import { EventDispatcher, IEventHandler } from "ste-events"; -import IAddonManifest, { IAddonDependency, IAddonManifestHeader, IAddonModule } from "./IAddonManifest"; +import IAddonManifest, { IAddonDependency, IAddonManifestHeader, IAddonMetadata, IAddonModule } from "./IAddonManifest"; import Utilities from "../core/Utilities"; import Project, { AUTOGENERATED_WHOLEFILE_GENERAL_SEPARATOR, minecraftScriptModules } from "../app/Project"; import StorageUtilities from "../storage/StorageUtilities"; import { ProjectItemType } from "../app/IProjectItemData"; import { ProjectFocus } from "../app/IProjectData"; +import ResourceManifestDefinition from "./ResourceManifestDefinition"; export default class BehaviorManifestDefinition { private _file?: IFile; @@ -43,6 +44,16 @@ export default class BehaviorManifestDefinition { return this.definition.metadata.product_type; } + public set productType(value: "" | "addon" | undefined) { + this.ensureMetadata(); + + if (!this.definition || !this.definition.metadata) { + return; + } + + this.definition.metadata.product_type = value; + } + public get description() { if (!this.definition || !this.definition.header) { return undefined; @@ -87,7 +98,77 @@ export default class BehaviorManifestDefinition { this._id = newId; } - public randomizeModuleUuids() { + public async setUuid(newId: string | undefined, project?: Project) { + const oldId = this.uuid; + + this.uuid = newId; + + if (newId && oldId && project) { + await BehaviorManifestDefinition.setNewBehaviorPackId(project, newId, oldId); + } + } + + static async setNewBehaviorPackId(project: Project, newBehaviorPackId: string, oldBehaviorPackId: string) { + const itemsCopy = project.getItemsCopy(); + let setBehaviorPack = false; + + for (let i = 0; i < itemsCopy.length; i++) { + const pi = itemsCopy[i]; + + if (pi.file) { + if (pi.itemType === ProjectItemType.behaviorPackManifestJson && !setBehaviorPack) { + const bpManifestJson = await BehaviorManifestDefinition.ensureOnFile(pi.file); + + if (bpManifestJson) { + if (bpManifestJson.uuid && Utilities.uuidEqual(bpManifestJson.uuid, oldBehaviorPackId)) { + bpManifestJson.uuid = newBehaviorPackId; + setBehaviorPack = true; + + await bpManifestJson.save(); + } else if (bpManifestJson.definition && bpManifestJson.definition.dependencies) { + const deps = bpManifestJson.definition?.dependencies; + + for (const dep of deps) { + if (dep.uuid === oldBehaviorPackId) { + dep.uuid = newBehaviorPackId; + } + } + + await bpManifestJson.save(); + } + } + } else if (pi.itemType === ProjectItemType.resourcePackManifestJson) { + const rpManifestJson = await ResourceManifestDefinition.ensureOnFile(pi.file); + + if (rpManifestJson) { + if (rpManifestJson.definition && rpManifestJson.definition.dependencies) { + const deps = rpManifestJson.definition?.dependencies; + + for (const dep of deps) { + if (dep.uuid === oldBehaviorPackId) { + dep.uuid = newBehaviorPackId; + } + } + + await rpManifestJson.save(); + } + } + } + } + } + } + + public hasAddonProperties(): boolean { + return this.productType === "addon"; + } + + public async setAddonProperties() { + this.productType = "addon"; + + await this.save(); + } + + public randomizeModuleUuids(newScriptModuleId?: string, oldScriptModuleId?: string) { if (!this.definition) { return; } @@ -96,7 +177,15 @@ export default class BehaviorManifestDefinition { const mod = this.definition.modules[i]; if (mod.uuid) { - mod.uuid = Utilities.createUuid(); + if ( + oldScriptModuleId && + newScriptModuleId && + (mod.uuid === oldScriptModuleId || mod.uuid === newScriptModuleId) + ) { + mod.uuid = newScriptModuleId; + } else { + mod.uuid = Utilities.createUuid(); + } } } } @@ -245,6 +334,18 @@ export default class BehaviorManifestDefinition { return this.ensureHeader(project.title, project.description); } + public ensureMetadata(): IAddonMetadata | undefined { + if (!this.definition) { + return undefined; + } + + if (!this.definition.metadata) { + this.definition.metadata = {}; + } + + return this.definition.metadata; + } + public ensureHeader(name: string, description: string): IAddonManifestHeader { this.ensureDefinition(name, description); @@ -334,8 +435,6 @@ export default class BehaviorManifestDefinition { return; } - this.uuid = this._file.name; - this.definition = StorageUtilities.getJsonObject(this._file); this._isLoaded = true; diff --git a/app/src/minecraft/EntityTypeResourceDefinition.ts b/app/src/minecraft/EntityTypeResourceDefinition.ts index a5204a4b..e1ea3cff 100644 --- a/app/src/minecraft/EntityTypeResourceDefinition.ts +++ b/app/src/minecraft/EntityTypeResourceDefinition.ts @@ -5,12 +5,17 @@ import IFile from "../storage/IFile"; import Log from "../core/Log"; import { EventDispatcher, IEventHandler } from "ste-events"; import StorageUtilities from "../storage/StorageUtilities"; -import { IEntityTypeResourceWrapper } from "./IEntityTypeResource"; +import { IEntityTypeResourceDescription, IEntityTypeResourceWrapper } from "./IEntityTypeResource"; +import Project from "../app/Project"; +import ProjectItem from "../app/ProjectItem"; +import { ProjectItemType } from "../app/IProjectItemData"; +import ModelGeometryDefinition from "./ModelGeometryDefinition"; export default class EntityTypeResourceDefinition { - public dataWrapper?: IEntityTypeResourceWrapper; + public _dataWrapper?: IEntityTypeResourceWrapper; private _file?: IFile; private _isLoaded: boolean = false; + private _data?: IEntityTypeResourceDescription; private _onLoaded = new EventDispatcher(); @@ -30,30 +35,111 @@ export default class EntityTypeResourceDefinition { } public get id() { - if ( - !this.dataWrapper || - !this.dataWrapper["minecraft:client_entity"] || - !this.dataWrapper["minecraft:client_entity"].description - ) { + if (!this._data) { return undefined; } - return this.dataWrapper["minecraft:client_entity"].description.identifier; + return this._data.identifier; + } + + public get textures() { + if (!this._data) { + return undefined; + } + + return this._data.textures; + } + + public get texturesList() { + if (!this._data || !this._data.textures) { + return undefined; + } + + const textureList = []; + + for (const key in this._data.textures) { + const texturePath = this._data.textures[key]; + + if (texturePath) { + textureList.push(texturePath); + } + } + + return textureList; + } + + public get geometry() { + if (!this._data) { + return undefined; + } + + return this._data.geometry; + } + + public get geometryList() { + if (!this._data || !this._data.geometry) { + return undefined; + } + + const geometryList = []; + + for (const key in this._data.geometry) { + const geometryPath = this._data.geometry[key]; + + if (geometryPath) { + geometryList.push(geometryPath); + } + } + + return geometryList; + } + + public getTextureItems(entityTypeResourceProjectItem: ProjectItem): { [name: string]: ProjectItem } | undefined { + if (!this._data || !this._data.geometry || !entityTypeResourceProjectItem.childItems) { + return undefined; + } + + const results: { [name: string]: ProjectItem } = {}; + + for (const key in this._data.textures) { + let texturePath = this._data.textures[key]; + + if (texturePath) { + texturePath = StorageUtilities.canonicalizePath(texturePath); + + for (const projectItemRel of entityTypeResourceProjectItem.childItems) { + if (projectItemRel.childItem.itemType === ProjectItemType.texture && projectItemRel.childItem.projectPath) { + let texturePathCand = StorageUtilities.canonicalizePath(projectItemRel.childItem.projectPath); + const lastPeriod = texturePathCand.lastIndexOf("."); + + if (lastPeriod >= 0) { + texturePathCand = texturePathCand.substring(0, lastPeriod).toLowerCase(); + } + + if (texturePathCand.endsWith(texturePath)) { + results[key] = projectItemRel.childItem; + } + } + } + } + } + + return results; } public getFormatVersion(): number[] | undefined { - if (!this.dataWrapper) { + if (!this._dataWrapper) { return undefined; } - const fv = this.dataWrapper.format_version; + const fv = this._dataWrapper.format_version; if (typeof fv === "number") { return [fv]; } if (typeof fv === "string") { - let fvarr = this.dataWrapper.format_version.split("."); + let fvarr = this._dataWrapper.format_version.split("."); let fvarrInt: number[] = []; for (let i = 0; i < fvarr.length; i++) { @@ -69,11 +155,11 @@ export default class EntityTypeResourceDefinition { } get formatVersion() { - if (!this.dataWrapper || !this.dataWrapper.format_version) { + if (!this._dataWrapper || !this._dataWrapper.format_version) { return undefined; } - return this.dataWrapper.format_version; + return this._dataWrapper.format_version; } static async ensureOnFile( @@ -108,7 +194,7 @@ export default class EntityTypeResourceDefinition { return; } - const defString = JSON.stringify(this.dataWrapper, null, 2); + const defString = JSON.stringify(this._dataWrapper, null, 2); this._file.setContent(defString); } @@ -137,10 +223,76 @@ export default class EntityTypeResourceDefinition { data = result; } - this.dataWrapper = data; + this._dataWrapper = data; + + if (this._dataWrapper && this._dataWrapper["minecraft:client_entity"]) { + this._data = this._dataWrapper["minecraft:client_entity"].description; + } this._isLoaded = true; this._onLoaded.dispatch(this, this); } + + async addChildItems(project: Project, item: ProjectItem) { + const itemsCopy = project.getItemsCopy(); + + let packRootFolder = undefined; + if (this.file && this.file.parentFolder) { + let parentFolder = this.file.parentFolder; + + while (parentFolder.name !== "entity" && parentFolder.parentFolder) { + parentFolder = parentFolder.parentFolder; + } + + if (parentFolder.parentFolder) { + packRootFolder = parentFolder.parentFolder; + } + } + + const textureList = this.texturesList; + const geometryList = this.geometryList; + + for (const candItem of itemsCopy) { + if (candItem.itemType === ProjectItemType.texture && packRootFolder && textureList) { + await candItem.ensureStorage(); + + if (candItem.file) { + let relativePath = candItem.file.getFolderRelativePath(packRootFolder); + + if (relativePath) { + const lastPeriod = relativePath?.lastIndexOf("."); + if (lastPeriod >= 0) { + relativePath = relativePath?.substring(0, lastPeriod).toLowerCase(); + } + + relativePath = StorageUtilities.ensureNotStartsWithDelimiter(relativePath); + + if (this.texturesList?.includes(relativePath)) { + item.addChildItem(candItem); + } + } + } + } else if (candItem.itemType === ProjectItemType.modelGeometryJson && geometryList) { + await candItem.ensureStorage(); + + if (candItem.file) { + const model = await ModelGeometryDefinition.ensureOnFile(candItem.file); + + if (model) { + let doAddModel = false; + for (const modelId of model.identifiers) { + if (this.geometryList?.includes(modelId)) { + doAddModel = true; + } + } + + if (doAddModel) { + item.addChildItem(candItem); + } + } + } + } + } + } } diff --git a/app/src/minecraft/IEntityEvent.ts b/app/src/minecraft/IEntityEvent.ts index b9748690..5f2fd352 100644 --- a/app/src/minecraft/IEntityEvent.ts +++ b/app/src/minecraft/IEntityEvent.ts @@ -5,11 +5,11 @@ import { IEntityTrigger } from "./IEntityTrigger"; import { IFilter } from "./IFilter"; export default interface IEntityEvent { - sequence: IEntityEvent[] | undefined; - add: { component_groups: string[] } | undefined; - remove: { component_groups: string[] } | undefined; - randomize: IEntityEvent[] | undefined; - weight: number; - filters: IFilter | undefined; - trigger: IEntityTrigger | undefined; + sequence?: IEntityEvent[] | undefined; + add?: { component_groups: string[] } | undefined; + remove?: { component_groups: string[] } | undefined; + randomize?: IEntityEvent[] | undefined; + weight?: number; + filters?: IFilter | undefined; + trigger?: IEntityTrigger | undefined; } diff --git a/app/src/minecraft/IModelGeometry.ts b/app/src/minecraft/IModelGeometry.ts index 0fa37de5..726c2007 100644 --- a/app/src/minecraft/IModelGeometry.ts +++ b/app/src/minecraft/IModelGeometry.ts @@ -4,12 +4,17 @@ export default interface IModelGeometry { format_version: string; __comment__?: string; - "minecraft:geometry": IGeometry[]; + "minecraft:geometry": IGeometry[]; // note this is a 1.10.0+ definition thing } export interface IGeometry { description: IGeometryDescription; bones: IGeometryBone[]; + texturewidth?: number; // geometry 1.8.0 prop + textureheight?: number; // geometry 1.8.0 prop + visible_bounds_width?: number; // geometry 1.8.0 prop + visible_bounds_height?: number; // geometry 1.8.0 prop + visible_bounds_offset?: number[]; // geometry 1.8.0 prop } export interface IGeometryDescription { @@ -24,11 +29,12 @@ export interface IGeometryDescription { export interface IGeometryBone { name: string; pivot: number[]; - cubes: IGeometryBoneCubes[]; + bind_pose_rotation?: number[]; + cubes: IGeometryBoneCube[]; locators: { [name: string]: number[] }; } -export interface IGeometryBoneCubes { +export interface IGeometryBoneCube { origin: number[]; size: number[]; uv: number[]; diff --git a/app/src/minecraft/ManagedEvent.ts b/app/src/minecraft/ManagedEvent.ts index c5fe4347..4cb75627 100644 --- a/app/src/minecraft/ManagedEvent.ts +++ b/app/src/minecraft/ManagedEvent.ts @@ -10,10 +10,6 @@ export default class ManagedEvent { id: string; - //private _onComponentGroupAddAdded = new EventDispatcher(); - //private _onComponentGroupAddRemoved = new EventDispatcher(); - //private _onComponentGroupAddChanged = new EventDispatcher(); - public constructor(data: IEntityEvent, id: string) { this._data = data; @@ -21,4 +17,141 @@ export default class ManagedEvent { this.id = id; } + + public toString() { + if (this._data === undefined) { + return "undefined"; + } + + return JSON.stringify(this._data); + } + + public hasAddComponentGroup(id: string) { + if (!this._data || !this._data.add || !this._data.add.component_groups) { + return false; + } + + if (this._data.add.component_groups.includes(id)) { + return true; + } + + return false; + } + + public ensureData() { + if (this._data === undefined) { + this._data = {}; + } + } + + public ensureAddComponentGroup(id: string) { + if (this.hasAddComponentGroup(id)) { + return; + } + + if (!this._data) { + this.ensureData(); + } + + if (!this._data) { + return; + } + + if (!this._data.add) { + this._data.add = { component_groups: [] }; + } + + if (!this._data.add.component_groups) { + this._data.add.component_groups = []; + } + + if (this._data.add.component_groups.includes(id)) { + return; + } + + this._data.add.component_groups.push(id); + } + + public removeAddComponentGroup(id: string) { + if (!this._data || !this._data.add || !this._data.add.component_groups) { + return false; + } + + if (this._data.add.component_groups.includes(id)) { + const newarr = []; + + for (const elt of this._data.add.component_groups) { + if (elt !== id) { + newarr.push(elt); + } + } + + this._data.add.component_groups = newarr; + return true; + } + + return false; + } + + public hasRemoveComponentGroup(id: string) { + if (!this._data || !this._data.remove || !this._data.remove.component_groups) { + return false; + } + + if (this._data.remove.component_groups.includes(id)) { + return true; + } + + return false; + } + + public ensureRemoveComponentGroup(id: string) { + if (this.hasRemoveComponentGroup(id)) { + return; + } + + if (!this._data) { + this.ensureData(); + } + + if (!this._data) { + return; + } + + if (!this._data.remove) { + this._data.remove = { component_groups: [] }; + } + + if (!this._data.remove.component_groups) { + this._data.remove.component_groups = []; + } + + if (this._data.remove.component_groups.includes(id)) { + return; + } + + this._data.remove.component_groups.push(id); + } + + public removeRemoveComponentGroup(id: string) { + if (!this._data || !this._data.remove || !this._data.remove.component_groups) { + return false; + } + + if (this._data.remove.component_groups.includes(id)) { + const newarr = []; + + for (const elt of this._data.remove.component_groups) { + if (elt !== id) { + newarr.push(elt); + } + } + + this._data.remove.component_groups = newarr; + + return true; + } + + return false; + } } diff --git a/app/src/minecraft/MinecraftDefinitions.ts b/app/src/minecraft/MinecraftDefinitions.ts index cef5c7c4..c2d0917b 100644 --- a/app/src/minecraft/MinecraftDefinitions.ts +++ b/app/src/minecraft/MinecraftDefinitions.ts @@ -18,7 +18,7 @@ export default class MinecraftDefinitions { } switch (projectItem.itemType) { - case ProjectItemType.entityTypeBehaviorJson: + case ProjectItemType.entityTypeBehavior: return await EntityTypeDefinition.ensureOnFile(projectItem.file); case ProjectItemType.itemTypeBehaviorJson: return await ItemTypeBehaviorDefinition.ensureOnFile(projectItem.file); diff --git a/app/src/minecraft/ModelGeometryDefinition.ts b/app/src/minecraft/ModelGeometryDefinition.ts index 18affef7..f8dde9fe 100644 --- a/app/src/minecraft/ModelGeometryDefinition.ts +++ b/app/src/minecraft/ModelGeometryDefinition.ts @@ -4,7 +4,7 @@ import IFile from "../storage/IFile"; import { EventDispatcher, IEventHandler } from "ste-events"; import StorageUtilities from "../storage/StorageUtilities"; -import IModelGeometry from "./IModelGeometry"; +import IModelGeometry, { IGeometry } from "./IModelGeometry"; import Database from "./Database"; import MinecraftUtilities from "./MinecraftUtilities"; @@ -12,7 +12,10 @@ export default class ModelGeometryDefinition { private _file?: IFile; private _isLoaded: boolean = false; - public definition?: IModelGeometry; + public wrapper?: IModelGeometry; + public definitions: IGeometry[] = []; + + private _identifiers: string[] = []; private _onLoaded = new EventDispatcher(); @@ -32,17 +35,109 @@ export default class ModelGeometryDefinition { return this._onLoaded.asEvent(); } - public get identifier(): string | undefined { + public get identifiers(): string[] { + if (this._identifiers) { + return this._identifiers; + } + if ( - !this.definition || - !this.definition["minecraft:geometry"] || - this.definition["minecraft:geometry"].length !== 1 || - !this.definition["minecraft:geometry"][0].description + !this.wrapper || + !this.wrapper["minecraft:geometry"] || + this.wrapper["minecraft:geometry"].length !== 1 || + !this.wrapper["minecraft:geometry"][0].description ) { + return []; + } + + const ids: string[] = []; + + for (const def of this.definitions) { + if (def.description && def.description.identifier) { + ids.push(def.description.identifier); + } + } + + return ids; + } + + public getVisibleBoundsWidth(defIndex: number): number | undefined { + if (defIndex < 0 || defIndex >= this.definitions.length) { + return; + } + + if (!this.definitions[defIndex]) { return undefined; } - return this.definition["minecraft:geometry"][0].description.identifier; + if (this.definitions[defIndex].description) { + return this.definitions[defIndex].description.visible_bounds_width; + } + + return this.definitions[defIndex].visible_bounds_width; + } + + public getVisibleBoundsHeight(defIndex: number): number | undefined { + if (defIndex < 0 || defIndex >= this.definitions.length) { + return; + } + + if (!this.definitions[defIndex]) { + return undefined; + } + + if (this.definitions[defIndex].description) { + return this.definitions[defIndex].description.visible_bounds_height; + } + + return this.definitions[defIndex].visible_bounds_height; + } + + public getVisibleBoundsOffset(defIndex: number): number[] | undefined { + if (defIndex < 0 || defIndex >= this.definitions.length) { + return; + } + + if (!this.definitions[defIndex]) { + return undefined; + } + + if (this.definitions[defIndex].description) { + return this.definitions[defIndex].description.visible_bounds_offset; + } + + return this.definitions[defIndex].visible_bounds_offset; + } + + public getTextureWidth(defIndex: number): number | undefined { + if (defIndex < 0 || defIndex >= this.definitions.length) { + return; + } + + if (!this.definitions[defIndex]) { + return undefined; + } + + if (this.definitions[defIndex].description) { + return this.definitions[defIndex].description.texture_width; + } + + return this.definitions[defIndex].texturewidth; + } + + public getTextureHeight(defIndex: number): number | undefined { + if (defIndex < 0 || defIndex >= this.definitions.length) { + return; + } + + if (!this.definitions[defIndex]) { + return undefined; + } + + if (this.definitions[defIndex].description) { + return this.definitions[defIndex].description.texture_height; + } + + return this.definitions[defIndex].textureheight; } static async ensureOnFile( @@ -83,11 +178,11 @@ export default class ModelGeometryDefinition { } public getFormatVersion(): number[] | undefined { - if (!this.definition || !this.definition.format_version) { + if (!this.wrapper || !this.wrapper.format_version) { return undefined; } - return MinecraftUtilities.getVersionArrayFrom(this.definition.format_version); + return MinecraftUtilities.getVersionArrayFrom(this.wrapper.format_version); } persist() { @@ -95,14 +190,14 @@ export default class ModelGeometryDefinition { return; } - const pjString = JSON.stringify(this.definition, null, 2); + const pjString = JSON.stringify(this.wrapper, null, 2); this._file.setContent(pjString); } public ensureDefinition(name: string) { - if (!this.definition) { - this.definition = { + if (!this.wrapper) { + this.wrapper = { format_version: "1.12.0", "minecraft:geometry": [ { @@ -142,7 +237,29 @@ export default class ModelGeometryDefinition { return; } - this.definition = StorageUtilities.getJsonObject(this._file); + this.wrapper = StorageUtilities.getJsonObject(this._file); + + this.definitions = []; + + if (this.wrapper && this.wrapper["minecraft:geometry"]) { + for (const def of this.wrapper["minecraft:geometry"]) { + this.definitions.push(def); + } + } else if (this.wrapper) { + // look for 1.8.0 style geometries: + // { + // "format_version": ... + // "geometry.foobar": {} + // } + + for (const elt in this.wrapper) { + if (elt !== "format_version" && elt.startsWith("geometry.") && (this.wrapper as any)[elt]) { + this._identifiers.push(elt); + + this.definitions.push((this.wrapper as any)[elt]); + } + } + } this._isLoaded = true; } diff --git a/app/src/minecraft/ResourceManifestDefinition.ts b/app/src/minecraft/ResourceManifestDefinition.ts index da33797e..19b15a11 100644 --- a/app/src/minecraft/ResourceManifestDefinition.ts +++ b/app/src/minecraft/ResourceManifestDefinition.ts @@ -3,10 +3,12 @@ import IFile from "../storage/IFile"; import { EventDispatcher, IEventHandler } from "ste-events"; -import { IAddonManifestHeader, IResourcePackManifest } from "./IAddonManifest"; +import { IAddonManifestHeader, IAddonMetadata, IResourcePackManifest } from "./IAddonManifest"; import Utilities from "../core/Utilities"; import Project from "../app/Project"; import StorageUtilities from "../storage/StorageUtilities"; +import { ProjectItemType } from "../app/IProjectItemData"; +import BehaviorManifestDefinition from "./BehaviorManifestDefinition"; export default class ResourceManifestDefinition { private _file?: IFile; @@ -55,6 +57,14 @@ export default class ResourceManifestDefinition { return this.definition.header.pack_scope; } + public set packScope(newValue: "world" | "global" | "any" | undefined) { + if (!this.definition || !this.definition.header) { + return; + } + + this.definition.header.pack_scope = newValue; + } + public get productType() { if (!this.definition || !this.definition.metadata) { return undefined; @@ -63,6 +73,16 @@ export default class ResourceManifestDefinition { return this.definition.metadata.product_type; } + public set productType(value: "" | "addon" | undefined) { + this.ensureMetadata(); + + if (!this.definition || !this.definition.metadata) { + return; + } + + this.definition.metadata.product_type = value; + } + public get name() { if (this.definition && this.definition.header) { return this.definition.header.name; @@ -93,6 +113,16 @@ export default class ResourceManifestDefinition { this._id = newId; } + public async setUuid(newId: string | undefined, project?: Project) { + const oldUuid = this.uuid; + + this.uuid = newId; + + if (newId && oldUuid && project) { + await ResourceManifestDefinition.setNewResourcePackId(project, newId, oldUuid); + } + } + public ensureHeaderForProject(project: Project): IAddonManifestHeader { return this.ensureHeader(project.title, project.description); } @@ -111,6 +141,83 @@ export default class ResourceManifestDefinition { header.min_engine_version = versionArray; } + static async setNewResourcePackId(project: Project, newResourcePackId: string, oldResourcePackId: string) { + const itemsCopy = project.getItemsCopy(); + let setResourcePack = false; + + for (let i = 0; i < itemsCopy.length; i++) { + const pi = itemsCopy[i]; + + if (pi.file) { + if (pi.itemType === ProjectItemType.resourcePackManifestJson && !setResourcePack) { + const rpManifestJson = await ResourceManifestDefinition.ensureOnFile(pi.file); + + if (rpManifestJson) { + if (rpManifestJson.uuid && Utilities.uuidEqual(rpManifestJson.uuid, oldResourcePackId)) { + rpManifestJson.uuid = newResourcePackId; + setResourcePack = true; + await rpManifestJson.save(); + } else if (rpManifestJson.definition && rpManifestJson.definition.dependencies) { + const deps = rpManifestJson.definition?.dependencies; + + for (const dep of deps) { + if (dep.uuid === oldResourcePackId) { + dep.uuid = newResourcePackId; + } + } + await rpManifestJson.save(); + } + } + } else if (pi.itemType === ProjectItemType.behaviorPackManifestJson) { + const bpManifestJson = await BehaviorManifestDefinition.ensureOnFile(pi.file); + + if (bpManifestJson) { + if (bpManifestJson.definition && bpManifestJson.definition.dependencies) { + const deps = bpManifestJson.definition?.dependencies; + + for (const dep of deps) { + if (dep.uuid === oldResourcePackId) { + dep.uuid = newResourcePackId; + } + } + + await bpManifestJson.save(); + } + } + } + } + } + } + + public hasAddonProperties(): boolean { + return this.productType === "addon" && this.packScope === "world"; + } + + public async setAddonProperties() { + this.productType = "addon"; + this.packScope = "world"; + + await this.save(); + } + + public randomizeModuleUuids(newDataModuleId?: string, oldDataModuleId?: string) { + if (!this.definition) { + return; + } + + for (let i = 0; i < this.definition.modules.length; i++) { + const mod = this.definition.modules[i]; + + if (mod.uuid) { + if (oldDataModuleId && newDataModuleId && (mod.uuid === oldDataModuleId || mod.uuid === newDataModuleId)) { + mod.uuid = newDataModuleId; + } else { + mod.uuid = Utilities.createUuid(); + } + } + } + } + static async ensureOnFile( file: IFile, loadHandler?: IEventHandler @@ -180,6 +287,18 @@ export default class ResourceManifestDefinition { return this.definition.header; } + public ensureMetadata(): IAddonMetadata | undefined { + if (!this.definition) { + return undefined; + } + + if (!this.definition.metadata) { + this.definition.metadata = {}; + } + + return this.definition.metadata; + } + public getDefaultHeader(name: string, description: string) { return { name: name, @@ -211,8 +330,6 @@ export default class ResourceManifestDefinition { return; } - this.uuid = this._file.name; - this.definition = StorageUtilities.getJsonObject(this._file); this._isLoaded = true; diff --git a/app/src/minecraft/WorldChunk.ts b/app/src/minecraft/WorldChunk.ts index 885208ef..2b8345b1 100644 --- a/app/src/minecraft/WorldChunk.ts +++ b/app/src/minecraft/WorldChunk.ts @@ -311,7 +311,7 @@ export default class WorldChunk { Log.assert(false, "Unexpected type 119 data."); break; default: - throw new Error("Unsupported chunk type: " + val); + Log.debugAlert("Unsupported chunk type: " + val); } } } diff --git a/app/src/storage/BrowserFile.ts b/app/src/storage/BrowserFile.ts index a4a6f3e2..491ec9ca 100644 --- a/app/src/storage/BrowserFile.ts +++ b/app/src/storage/BrowserFile.ts @@ -24,7 +24,7 @@ export default class BrowserFile extends FileBase implements IFile { } get fullPath(): string { - return this._parentFolder.fullPath + BrowserStorage.folderDelimiter + this.name; + return this._parentFolder.fullPath + BrowserStorage.slashFolderDelimiter + this.name; } get size(): number { diff --git a/app/src/storage/BrowserFolder.ts b/app/src/storage/BrowserFolder.ts index aaa65501..9cb552f7 100644 --- a/app/src/storage/BrowserFolder.ts +++ b/app/src/storage/BrowserFolder.ts @@ -42,7 +42,7 @@ export default class BrowserFolder extends FolderBase implements IFolder { } get fullPath() { - return this._parentPath + BrowserStorage.folderDelimiter + StorageUtilities.canonicalizeName(this.name); + return this._parentPath + BrowserStorage.slashFolderDelimiter + StorageUtilities.canonicalizeName(this.name); } constructor(storage: BrowserStorage, parentFolder: BrowserFolder | null, parentPath: string, folderName: string) { @@ -92,7 +92,7 @@ export default class BrowserFolder extends FolderBase implements IFolder { let result = await this.recursiveDeleteThisFolder(); - await localforage.removeItem(this.fullPath + BrowserStorage.folderDelimiter); + await localforage.removeItem(this.fullPath + BrowserStorage.slashFolderDelimiter); return result; } @@ -209,8 +209,8 @@ export default class BrowserFolder extends FolderBase implements IFolder { return this.lastLoadedOrSaved; } - this._lastLoadedPath = this.fullPath + BrowserStorage.folderDelimiter; - const listingContent = await localforage.getItem(this.fullPath + BrowserStorage.folderDelimiter); + this._lastLoadedPath = this.fullPath + BrowserStorage.slashFolderDelimiter; + const listingContent = await localforage.getItem(this.fullPath + BrowserStorage.slashFolderDelimiter); if (listingContent != null) { this._lastSavedContent = listingContent; @@ -339,8 +339,8 @@ export default class BrowserFolder extends FolderBase implements IFolder { if (this._lastSavedContent !== saveContent || force) { this._lastSavedContent = saveContent; - this._lastLoadedPath = this.fullPath + BrowserStorage.folderDelimiter; - await localforage.setItem(this.fullPath + BrowserStorage.folderDelimiter, saveContent); + this._lastLoadedPath = this.fullPath + BrowserStorage.slashFolderDelimiter; + await localforage.setItem(this.fullPath + BrowserStorage.slashFolderDelimiter, saveContent); } return this.lastLoadedOrSaved as Date; diff --git a/app/src/storage/BrowserStorage.ts b/app/src/storage/BrowserStorage.ts index a7868e32..977ab42c 100644 --- a/app/src/storage/BrowserStorage.ts +++ b/app/src/storage/BrowserStorage.ts @@ -10,8 +10,6 @@ export default class BrowserStorage extends StorageBase implements IStorage { rootFolder: BrowserFolder; static isConfigured: boolean = false; - static readonly folderDelimiter = "/"; - static ensureConfigured() { if (!BrowserStorage.isConfigured) { localforage.config({ @@ -37,26 +35,4 @@ export default class BrowserStorage extends StorageBase implements IStorage { this.rootFolder = new BrowserFolder(this, null, "fs" + name, "root"); } - - joinPath(pathA: string, pathB: string) { - let fullPath = pathA; - - if (!fullPath.endsWith(BrowserStorage.folderDelimiter)) { - fullPath += BrowserStorage.folderDelimiter; - } - - fullPath += pathB; - - return fullPath; - } - - static getParentFolderPath(path: string) { - const lastDelim = path.lastIndexOf(this.folderDelimiter); - - if (lastDelim < 0) { - return path; - } - - return path.substring(0, lastDelim); - } } diff --git a/app/src/storage/FileBase.ts b/app/src/storage/FileBase.ts index 32591ada..a9521c72 100644 --- a/app/src/storage/FileBase.ts +++ b/app/src/storage/FileBase.ts @@ -179,6 +179,40 @@ export default abstract class FileBase implements IFile { return this._content !== null; } + getRelativePathFor(file: IFile): string | undefined { + if (file.parentFolder.storage !== this.parentFolder.storage) { + return undefined; + } + const foldersByPath: { [path: string]: IFolder } = {}; + let targetParentFolder: IFolder | null = file.parentFolder; + + while (targetParentFolder) { + foldersByPath[targetParentFolder.storageRelativePath] = targetParentFolder; + targetParentFolder = targetParentFolder.parentFolder; + } + + let myParentFolder: IFolder | null = this.parentFolder; + + let relativePath = "." + myParentFolder.storage.folderDelimiter; + while (myParentFolder && foldersByPath[myParentFolder.storageRelativePath] === undefined) { + relativePath += ".." + myParentFolder.storage.folderDelimiter; + + myParentFolder = myParentFolder.parentFolder; + } + + if (!myParentFolder) { + return undefined; + } + + const folderRelativePath = file.getFolderRelativePath(myParentFolder); + + if (!folderRelativePath) { + return undefined; + } + + return relativePath + StorageUtilities.ensureNotStartsWithDelimiter(folderRelativePath); + } + abstract deleteThisFile(skipRemoveFromParent?: boolean): Promise; abstract moveTo(newStorageRelativePath: string): Promise; abstract loadContent(force?: boolean): Promise; diff --git a/app/src/storage/FileSystemFile.ts b/app/src/storage/FileSystemFile.ts index 88135c47..057c0c2a 100644 --- a/app/src/storage/FileSystemFile.ts +++ b/app/src/storage/FileSystemFile.ts @@ -41,7 +41,7 @@ export default class FileSystemFile extends FileBase implements IFile { } get fullPath(): string { - return this._parentFolder.fullPath + FileSystemStorage.folderDelimiter + this.name; + return this._parentFolder.fullPath + FileSystemStorage.fileSystemFolderDelimiter + this.name; } get size(): number { diff --git a/app/src/storage/FileSystemFolder.ts b/app/src/storage/FileSystemFolder.ts index 56fdc245..41b4fe42 100644 --- a/app/src/storage/FileSystemFolder.ts +++ b/app/src/storage/FileSystemFolder.ts @@ -58,7 +58,9 @@ export default class FileSystemFolder extends FolderBase implements IFolder { } get fullPath() { - return this._parentPath + FileSystemStorage.folderDelimiter + StorageUtilities.canonicalizeName(this.name); + return ( + this._parentPath + FileSystemStorage.fileSystemFolderDelimiter + StorageUtilities.canonicalizeName(this.name) + ); } constructor( diff --git a/app/src/storage/FileSystemStorage.ts b/app/src/storage/FileSystemStorage.ts index 947ec2cb..b2413804 100644 --- a/app/src/storage/FileSystemStorage.ts +++ b/app/src/storage/FileSystemStorage.ts @@ -8,7 +8,11 @@ import IStorage from "./IStorage"; export default class FileSystemStorage extends StorageBase implements IStorage { rootFolder: FileSystemFolder; - static readonly folderDelimiter = "/"; + static readonly fileSystemFolderDelimiter = "/"; + + get folderDelimiter() { + return FileSystemStorage.fileSystemFolderDelimiter; + } constructor(handle: FileSystemDirectoryHandle, name?: string) { super(); @@ -19,8 +23,8 @@ export default class FileSystemStorage extends StorageBase implements IStorage { joinPath(pathA: string, pathB: string) { let fullPath = pathA; - if (!fullPath.endsWith(FileSystemStorage.folderDelimiter)) { - fullPath += FileSystemStorage.folderDelimiter; + if (!fullPath.endsWith(FileSystemStorage.fileSystemFolderDelimiter)) { + fullPath += FileSystemStorage.fileSystemFolderDelimiter; } fullPath += pathB; @@ -29,7 +33,7 @@ export default class FileSystemStorage extends StorageBase implements IStorage { } static getParentFolderPath(path: string) { - const lastDelim = path.lastIndexOf(this.folderDelimiter); + const lastDelim = path.lastIndexOf(this.fileSystemFolderDelimiter); if (lastDelim < 0) { return path; diff --git a/app/src/storage/HttpFolder.ts b/app/src/storage/HttpFolder.ts index 2419e21f..48053909 100644 --- a/app/src/storage/HttpFolder.ts +++ b/app/src/storage/HttpFolder.ts @@ -40,7 +40,7 @@ export default class HttpFolder extends FolderBase implements IFolder { return this._storage.baseUrl; } - return this._parentFolder.fullPath + this.name + HttpStorage.folderDelimiter; + return this._parentFolder.fullPath + this.name + HttpStorage.slashFolderDelimiter; } constructor(storage: HttpStorage, parentFolder: HttpFolder | null, folderName: string) { diff --git a/app/src/storage/HttpStorage.ts b/app/src/storage/HttpStorage.ts index 3b21ab65..6ca0814c 100644 --- a/app/src/storage/HttpStorage.ts +++ b/app/src/storage/HttpStorage.ts @@ -10,39 +10,15 @@ export default class HttpStorage extends StorageBase implements IStorage { baseUrl: string; - static readonly folderDelimiter = "/"; - constructor(newUrl: string) { super(); this.baseUrl = newUrl; - if (!this.baseUrl.endsWith(HttpStorage.folderDelimiter)) { - this.baseUrl += HttpStorage.folderDelimiter; + if (!this.baseUrl.endsWith(StorageBase.slashFolderDelimiter)) { + this.baseUrl += StorageBase.slashFolderDelimiter; } this.rootFolder = new HttpFolder(this, null, ""); } - - joinPath(pathA: string, pathB: string) { - let fullPath = pathA; - - if (!fullPath.endsWith(HttpStorage.folderDelimiter)) { - fullPath += HttpStorage.folderDelimiter; - } - - fullPath += pathB; - - return fullPath; - } - - static getParentFolderPath(path: string) { - const lastDelim = path.lastIndexOf(this.folderDelimiter); - - if (lastDelim < 0) { - return path; - } - - return path.substring(0, lastDelim); - } } diff --git a/app/src/storage/IFile.ts b/app/src/storage/IFile.ts index 6a5c2f1d..64f32da4 100644 --- a/app/src/storage/IFile.ts +++ b/app/src/storage/IFile.ts @@ -28,7 +28,7 @@ export default interface IFile extends IStorageObject { dispose(): void; getHash(): Promise; - + getRelativePathFor(file: IFile): string | undefined; deleteThisFile(skipRemoveFromParent?: boolean): Promise; moveTo(newStorageRelativePath: string): Promise; exists(): Promise; diff --git a/app/src/storage/IStorage.ts b/app/src/storage/IStorage.ts index 65c1f824..4d8292ab 100644 --- a/app/src/storage/IStorage.ts +++ b/app/src/storage/IStorage.ts @@ -21,6 +21,8 @@ export default interface IStorage { rootFolder: IFolder; storagePath: string | undefined; + readonly folderDelimiter: string; + onFileAdded: IEvent; onFileRemoved: IEvent; onFileContentsUpdated: IEvent; diff --git a/app/src/storage/StorageBase.ts b/app/src/storage/StorageBase.ts index 99326fce..ff244687 100644 --- a/app/src/storage/StorageBase.ts +++ b/app/src/storage/StorageBase.ts @@ -11,6 +11,12 @@ export default abstract class StorageBase implements IStorage { isContentUpdated: boolean = false; readOnly: boolean = false; + static readonly slashFolderDelimiter = "/"; + + get folderDelimiter() { + return StorageBase.slashFolderDelimiter; + } + #onFileAdded = new EventDispatcher(); #onFileRemoved = new EventDispatcher(); #onFileContentsUpdated = new EventDispatcher(); @@ -31,8 +37,6 @@ export default abstract class StorageBase implements IStorage { this.#storagePath = newStoragePath; } - abstract joinPath(pathA: string, pathB: string): string; - public resetContentUpdated() { this.isContentUpdated = false; } @@ -78,4 +82,26 @@ export default abstract class StorageBase implements IStorage { notifyFileRemoved(fileName: string) { this.#onFileRemoved.dispatch(this, fileName); } + + joinPath(pathA: string, pathB: string) { + let fullPath = pathA; + + if (!fullPath.endsWith(StorageBase.slashFolderDelimiter)) { + fullPath += StorageBase.slashFolderDelimiter; + } + + fullPath += pathB; + + return fullPath; + } + + static getParentFolderPath(path: string) { + const lastDelim = path.lastIndexOf(StorageBase.slashFolderDelimiter); + + if (lastDelim < 0) { + return path; + } + + return path.substring(0, lastDelim); + } } diff --git a/app/src/storage/StorageUtilities.ts b/app/src/storage/StorageUtilities.ts index 50593266..0c9b3e4e 100644 --- a/app/src/storage/StorageUtilities.ts +++ b/app/src/storage/StorageUtilities.ts @@ -232,6 +232,68 @@ export default class StorageUtilities { return fullPath; } + static getMimeType(file: IFile) { + switch (StorageUtilities.getTypeFromName(file.name)) { + case "js": + return "application/javascript"; + + case "ts": + return "application/typescript"; + + case "json": + return "application/json"; + + case "mcworld": + case "mctemplate": + case "mcproject": + case "mcaddon": + case "mcpack": + case "zip": + return "application/zip"; + + case "mcstucture": + return "application/octet-stream"; + + case "mcfunction": + case "material": + case "env": + case "lang": + return "text/plain"; + + case "wav": + return "audio/wav"; + + case "mp3": + return "audio/mp3"; + case "ogg": + return "audio/ogg"; + + case "jpg": + case "jpeg": + return "image/jpg"; + + case "png": + return "image/png"; + + case "tiff": + case "tga": + return "image/tiff"; + + default: + return "application/octet-stream"; + } + } + + public static getContentAsString(file: IFile) { + if (typeof file.content === "string") { + return file.content; + } else if (file.content instanceof Uint8Array) { + return "data:" + StorageUtilities.getMimeType(file) + ";base64," + Utilities.uint8ArrayToBase64(file.content); + } + + return undefined; + } + public static async getFileStorageFolder(file: IFile) { let zipStorage = file.fileContainerStorage as ZipStorage; diff --git a/app/src/storage/ZipFolder.ts b/app/src/storage/ZipFolder.ts index 792f0073..aa08c548 100644 --- a/app/src/storage/ZipFolder.ts +++ b/app/src/storage/ZipFolder.ts @@ -160,9 +160,9 @@ export default class ZipFolder extends FolderBase implements IFolder { this._jsz.forEach((relativePath: string, file: JSZip.JSZipObject) => { // some zip files use \ as a delimiter (??) - relativePath = relativePath.replace(/\\/gi, ZipStorage.folderDelimiter); + relativePath = relativePath.replace(/\\/gi, ZipStorage.slashFolderDelimiter); - const countDelim = Utilities.countChar(relativePath, ZipStorage.folderDelimiter); + const countDelim = Utilities.countChar(relativePath, ZipStorage.slashFolderDelimiter); if (countDelim === 0) { const nameCanon = StorageUtilities.canonicalizeName(relativePath); @@ -182,13 +182,13 @@ export default class ZipFolder extends FolderBase implements IFolder { subPath = subPath.substring(1); } - let nextDelim = subPath.indexOf(ZipStorage.folderDelimiter); + let nextDelim = subPath.indexOf(ZipStorage.slashFolderDelimiter); while (nextDelim > 0) { lastFolder = lastFolder.ensureFolder(subPath.substring(0, nextDelim)); subPath = subPath.substring(nextDelim + 1); - nextDelim = subPath.indexOf(ZipStorage.folderDelimiter); + nextDelim = subPath.indexOf(ZipStorage.slashFolderDelimiter); } if (subPath.length > 0 && file) { diff --git a/app/src/storage/ZipStorage.ts b/app/src/storage/ZipStorage.ts index e6df9dc2..d79816f4 100644 --- a/app/src/storage/ZipStorage.ts +++ b/app/src/storage/ZipStorage.ts @@ -16,8 +16,6 @@ export default class ZipStorage extends StorageBase implements IStorage { modified: Date | null = null; lastLoadedOrSaved: Date | null = null; - static readonly folderDelimiter = "/"; - get updatedSinceLoad() { if (this.modified === null || (this.lastLoadedOrSaved === null && this.modified === null)) { return false; @@ -113,8 +111,8 @@ export default class ZipStorage extends StorageBase implements IStorage { joinPath(pathA: string, pathB: string) { let fullPath = pathA; - if (!fullPath.endsWith(ZipStorage.folderDelimiter)) { - fullPath += ZipStorage.folderDelimiter; + if (!fullPath.endsWith(StorageBase.slashFolderDelimiter)) { + fullPath += StorageBase.slashFolderDelimiter; } fullPath += pathB; diff --git a/app/src/test/CartoTest.ts b/app/src/test/CartoTest.ts index 57bc458a..0b432a83 100644 --- a/app/src/test/CartoTest.ts +++ b/app/src/test/CartoTest.ts @@ -50,31 +50,31 @@ localEnv = new LocalEnvironment(false); await resultsFolder.ensureExists(); CartoApp.prefsStorage = new NodeStorage( - localEnv.utilities.testWorkingPath + "prefs" + NodeStorage.folderDelimiter, + localEnv.utilities.testWorkingPath + "prefs" + NodeStorage.platformFolderDelimiter, "" ); CartoApp.projectsStorage = new NodeStorage( - localEnv.utilities.testWorkingPath + "projects" + NodeStorage.folderDelimiter, + localEnv.utilities.testWorkingPath + "projects" + NodeStorage.platformFolderDelimiter, "" ); CartoApp.packStorage = new NodeStorage( - localEnv.utilities.testWorkingPath + "packs" + NodeStorage.folderDelimiter, + localEnv.utilities.testWorkingPath + "packs" + NodeStorage.platformFolderDelimiter, "" ); CartoApp.worldStorage = new NodeStorage( - localEnv.utilities.testWorkingPath + "worlds" + NodeStorage.folderDelimiter, + localEnv.utilities.testWorkingPath + "worlds" + NodeStorage.platformFolderDelimiter, "" ); CartoApp.deploymentStorage = new NodeStorage( - localEnv.utilities.testWorkingPath + "deployment" + NodeStorage.folderDelimiter, + localEnv.utilities.testWorkingPath + "deployment" + NodeStorage.platformFolderDelimiter, "" ); CartoApp.workingStorage = new NodeStorage( - localEnv.utilities.testWorkingPath + "working" + NodeStorage.folderDelimiter, + localEnv.utilities.testWorkingPath + "working" + NodeStorage.platformFolderDelimiter, "" ); @@ -135,12 +135,19 @@ function removeResultFolder(scenarioName: string) { const path = StorageUtilities.ensureEndsWithDelimiter(resultsFolder.fullPath) + StorageUtilities.ensureEndsWithDelimiter(scenarioName); + // guard against being called at a "more root" file path - if (fs.existsSync(path) && Utilities.countChar(path, NodeStorage.folderDelimiter) > 5) - // @ts-ignore - fs.rmSync(path, { - recursive: true, - }); + if (fs.existsSync(path) && Utilities.countChar(path, NodeStorage.platformFolderDelimiter) > 5) { + try { + fs.rmSync(path, { + recursive: true, + }); + } catch (e) { + console.log("Error occurred during rmSync on '" + path + "'"); + + throw e; + } + } } } diff --git a/app/src/test/CommandLineTest.ts b/app/src/test/CommandLineTest.ts index f590e715..8375956d 100644 --- a/app/src/test/CommandLineTest.ts +++ b/app/src/test/CommandLineTest.ts @@ -12,6 +12,9 @@ import { chunksToLinesAsync } from "@rauschma/stringio"; import { Readable } from "stream"; import * as fs from "fs"; import Utilities from "../core/Utilities"; +import ProjectInfoSet from "../info/ProjectInfoSet"; +import { ProjectInfoSuite } from "../info/IProjectInfoData"; +import ProjectUtilities from "../app/ProjectUtilities"; CartoApp.hostType = HostType.testLocal; @@ -50,31 +53,31 @@ localEnv = new LocalEnvironment(false); await sampleFolder.ensureExists(); CartoApp.prefsStorage = new NodeStorage( - localEnv.utilities.testWorkingPath + "prefs" + NodeStorage.folderDelimiter, + localEnv.utilities.testWorkingPath + "prefs" + NodeStorage.platformFolderDelimiter, "" ); CartoApp.projectsStorage = new NodeStorage( - localEnv.utilities.testWorkingPath + "projects" + NodeStorage.folderDelimiter, + localEnv.utilities.testWorkingPath + "projects" + NodeStorage.platformFolderDelimiter, "" ); CartoApp.packStorage = new NodeStorage( - localEnv.utilities.testWorkingPath + "packs" + NodeStorage.folderDelimiter, + localEnv.utilities.testWorkingPath + "packs" + NodeStorage.platformFolderDelimiter, "" ); CartoApp.worldStorage = new NodeStorage( - localEnv.utilities.testWorkingPath + "worlds" + NodeStorage.folderDelimiter, + localEnv.utilities.testWorkingPath + "worlds" + NodeStorage.platformFolderDelimiter, "" ); CartoApp.deploymentStorage = new NodeStorage( - localEnv.utilities.testWorkingPath + "deployment" + NodeStorage.folderDelimiter, + localEnv.utilities.testWorkingPath + "deployment" + NodeStorage.platformFolderDelimiter, "" ); CartoApp.workingStorage = new NodeStorage( - localEnv.utilities.testWorkingPath + "working" + NodeStorage.folderDelimiter, + localEnv.utilities.testWorkingPath + "working" + NodeStorage.platformFolderDelimiter, "" ); @@ -83,6 +86,14 @@ localEnv = new LocalEnvironment(false); await CartoApp.init(); // carto app init does something here that, if removed, causes these tests to fail. Also, await needs to be here. + carto = CartoApp.carto; + + if (!carto) { + return; + } + + await carto.load(); + run(); })(); @@ -105,7 +116,7 @@ function removeResultFolder(scenarioName: string) { StorageUtilities.ensureEndsWithDelimiter(scenarioName); // guard against being called at a "more root" file path - if (fs.existsSync(path) && Utilities.countChar(path, NodeStorage.folderDelimiter) > 5) + if (fs.existsSync(path) && Utilities.countChar(path, NodeStorage.platformFolderDelimiter) > 5) try { fs.rmSync(path, { recursive: true, @@ -179,6 +190,9 @@ describe("createCommandAddonStarter", async () => { let exitCode: number | null = null; const stdoutLines: string[] = []; const stderrLines: string[] = []; + let project: Project | null = null; + let allProjectInfoSet: ProjectInfoSet | null = null; + let addonProjectInfoSet: ProjectInfoSet | null = null; before(function (done) { this.timeout(10000); @@ -201,7 +215,35 @@ describe("createCommandAddonStarter", async () => { process.on("exit", (code) => { exitCode = code; - done(); + + assert(carto, "Carto is not properly initialized"); + + project = new Project(carto, "createCommandAddonStarter", null); + + // exclude eslint because we know the .ts comes with some warnings due to + // the starter TS having some unused variables. + allProjectInfoSet = new ProjectInfoSet(project, ProjectInfoSuite.allExceptAddOn, ["ESLINT"]); + + addonProjectInfoSet = new ProjectInfoSet(project, ProjectInfoSuite.addOn); + + project.autoDeploymentMode = ProjectAutoDeploymentMode.noAutoDeployment; + project.localFolderPath = __dirname + "/../../test/results/createCommandAddonStarter/"; + + project.inferProjectItemsFromFiles().then(() => { + assert(project); + + ProjectUtilities.setIsAddon(project).then(() => { + assert(allProjectInfoSet); + + allProjectInfoSet.generateForProject().then(() => { + assert(addonProjectInfoSet); + + addonProjectInfoSet.generateForProject().then(() => { + done(); + }); + }); + }); + }); }); }); @@ -213,8 +255,23 @@ describe("createCommandAddonStarter", async () => { assert.equal(exitCode, 0); }).timeout(10000); + it("should have 17 project items", async () => { + assert(project); + assert.equal(project.items.length, 17); + }).timeout(10000); + + it("main validation should have 0 errors, failures, or warnings", async () => { + assert(allProjectInfoSet); + assert.equal(allProjectInfoSet.errorFailWarnCount, 0, allProjectInfoSet.errorFailWarnString); + }).timeout(10000); + + it("addon validation should have 0 errors, failures, or warnings", async () => { + assert(addonProjectInfoSet); + assert.equal(addonProjectInfoSet.errorFailWarnCount, 0, addonProjectInfoSet.errorFailWarnString); + }).timeout(10000); + it("output matches", async () => { - await folderMatches("createCommandAddonStarter"); + await folderMatches("createCommandAddonStarter", ["manifest.json"]); }); }); diff --git a/app/test/scenarios/createCommandAddonStarter/behavior_packs/cn_test/manifest.json b/app/test/scenarios/createCommandAddonStarter/behavior_packs/cn_test/manifest.json index 683da7d9..91fdb90a 100644 --- a/app/test/scenarios/createCommandAddonStarter/behavior_packs/cn_test/manifest.json +++ b/app/test/scenarios/createCommandAddonStarter/behavior_packs/cn_test/manifest.json @@ -3,7 +3,7 @@ "header": { "name": "testerName", "description": "testerDescription", - "uuid": "25ae3291-9d7a-4a21-a1f5-2c70e39ede34", + "uuid": "6d0ccf49-bcb7-49f5-a894-72c7e2b66320", "version": [ 1, 0, @@ -12,7 +12,7 @@ "min_engine_version": [ 1, 21, - 20 + 30 ] }, "modules": [ @@ -23,14 +23,14 @@ 0, 0 ], - "uuid": "8e67648f-9b4d-42cd-aa5c-9f1681c74b1e", + "uuid": "a46ebb3c-b824-4091-aac8-e93897601df1", "type": "data" }, { "description": "Script resources", "language": "javascript", "type": "script", - "uuid": "e1bb6ca5-2ed5-4e7a-8a75-248c7f31383a", + "uuid": "fc08504c-36ed-4541-b308-c319560cceed", "version": [ 0, 0, @@ -41,7 +41,7 @@ ], "dependencies": [ { - "uuid": "9d6dad89-6e29-46ba-83d5-490a4901caef", + "uuid": "e0a6c016-1d1f-480d-86af-8f240f7b8fbc", "version": [ 1, 0, @@ -52,7 +52,7 @@ "module_name": "@minecraft/server", "version": [ 1, - 13, + 14, 0 ] }, @@ -60,7 +60,7 @@ "module_name": "@minecraft/server-ui", "version": [ 1, - 2, + 3, 0 ] } diff --git a/app/test/scenarios/simple/report.json b/app/test/scenarios/simple/report.json index 7710ec9b..b262f91b 100644 --- a/app/test/scenarios/simple/report.json +++ b/app/test/scenarios/simple/report.json @@ -84,6 +84,14 @@ "Average": 9872 } }, + "errorCount": 0, + "internalProcessingErrorCount": 0, + "warningCount": 0, + "testSuccessCount": 9, + "testFailCount": 1, + "errorSummary": "", + "internalProcessingErrorSummary": "", + "testFailSummary": "TESTFAIL: [PATHLENGTH003] File path contains more than 80 characters, and may not run on all devices: /behavior_packs/StarterTestsTutorial/structures/starttests/mediumglass.mcstructure", "summary": { "ITEMS": { "2": { @@ -304,12 +312,6 @@ "testCompleteSuccesses": 1 } }, - "PACKMETADATA": { - "1": { - "defaultMessage": "Test General info completed successfully.", - "testCompleteSuccesses": 1 - } - }, "WORLD": { "1": { "defaultMessage": "Test World Validation completed successfully.", @@ -789,11 +791,6 @@ "gId": "UNKJSON", "gIx": 1 }, - { - "iTp": 0, - "gId": "PACKMETADATA", - "gIx": 1 - }, { "iTp": 0, "gId": "WORLD", diff --git a/app/test/scenarios/validateAddons1WellFormedCommand/content1.mcr.json b/app/test/scenarios/validateAddons1WellFormedCommand/content1.mcr.json index b502a1c7..b547d3ce 100644 --- a/app/test/scenarios/validateAddons1WellFormedCommand/content1.mcr.json +++ b/app/test/scenarios/validateAddons1WellFormedCommand/content1.mcr.json @@ -56,6 +56,14 @@ "Count": 1 } }, + "errorCount": 0, + "internalProcessingErrorCount": 0, + "warningCount": 0, + "testSuccessCount": 2, + "testFailCount": 3, + "errorSummary": "", + "internalProcessingErrorSummary": "", + "testFailSummary": "TESTFAIL: [ADDONREQ102] Found an add-on-blocked folder 'entities' in a parent folder pack\\loot_tables. Should be named 'creatorshortname' and not a common term: entities\r\nTESTFAIL: [ADDONREQ104] Found a loose file 'sheepomelon.json' in loot_tables\\entities. Files should only be in the folder loot_tables\\entities\\: sheepomelon.json\r\nTESTFAIL: [ADDONREQ104] Found a loose file 'sheepomelon_shear.json' in loot_tables\\entities. Files should only be in the folder loot_tables\\entities\\: sheepomelon_shear.json", "summary": { "PACKSIZE": { "1": { diff --git a/app/test/scenarios/validateAddons1WellFormedCommand/content1.report.html b/app/test/scenarios/validateAddons1WellFormedCommand/content1.report.html index eb2e32af..8106a06d 100644 --- a/app/test/scenarios/validateAddons1WellFormedCommand/content1.report.html +++ b/app/test/scenarios/validateAddons1WellFormedCommand/content1.report.html @@ -6,7 +6,7 @@ _reportObjects.push(data); } -_addReportJson({"info":{"behaviorPackManifestCount":0,"unknownJsonCount":0,"entityTypeManifestCount":0,"itemTypeManifestCount":0,"blockTypeManifestCount":0,"resourcePackManifestCount":0,"worldCount":0,"entityTypeResourceCount":0,"behaviorPackAnimationCount":0,"behaviorPackAnimationControllerCount":0,"overallSize":54260,"fileCounts":37,"folderCounts":23,"contentSize":54260,"contentFileCounts":37,"contentFolderCounts":18,"animationCount":0,"textureCount":0,"chunkCount":0,"subchunkLessChunkCount":0,"worldLoadErrors":0,"featureSets":{"Textures Entity Resource Count":{"Count":3},"Textures Texture References":{"Count":3},"Textures Entity References":{"Count":3},"Textures File Count":{"Count":4},"Textures Unique Texture Handles (estimated)":{"Count":3},"Textures Unique Texture Paths":{"Count":3},"Textures Unique Particle Texture Paths":{"Count":0},"Textures Unique Particle Texture Vanilla Paths":{"Count":1},"Textures Unique Entity Texture Paths":{"Count":3},"Command say":{"Count":1},"Command summon":{"Count":1}},"summary":{"PACKSIZE":{"1":{"title":"Overall Size"},"2":{"title":"Overall File Count","defaultMessage":"File Counts"},"3":{"title":"Overall Folder Count","defaultMessage":"Folder Counts"},"4":{"title":"Content Size"},"5":{"title":"Content File Count","defaultMessage":"Content File Counts"},"6":{"title":"Content Folder Count","defaultMessage":"Content Folder Counts"}},"ADDONREQ":{"102":{"title":"102","defaultMessage":"Found an add-on-blocked folder 'entities' in a parent folder pack\\loot_tables. Should be named 'creatorshortname' and not a common term","testCompleteFails":1},"104":{"title":"104","defaultMessage":"Found a loose file 'sheepomelon.json' in loot_tables\\entities. Files should only be in the folder loot_tables\\entities\\","testCompleteFails":2}},"TEXTURE":{"1":{"title":"Textures"}},"WORLDDATA":{"1":{"defaultMessage":"Blocks","testCompleteSuccesses":1},"2":{"defaultMessage":"Block Data"},"3":{"defaultMessage":"Commands"},"4":{"defaultMessage":"Execute Sub Commands"},"5":{"defaultMessage":"NBT Tags"},"6":{"defaultMessage":"NBT Experiment Tags"}},"ADDONIREQ":{"1":{"defaultMessage":"Test Addon Item Requirements Generator completed successfully.","testCompleteSuccesses":1}}}},"items":[{"iTp":7,"gId":"PACKSIZE","gIx":1,"d":54260},{"iTp":7,"gId":"PACKSIZE","gIx":2,"d":37},{"iTp":7,"gId":"PACKSIZE","gIx":3,"d":23},{"iTp":7,"gId":"PACKSIZE","gIx":4,"d":54260},{"iTp":7,"gId":"PACKSIZE","gIx":5,"d":37},{"iTp":7,"gId":"PACKSIZE","gIx":6,"d":18},{"iTp":1,"gId":"ADDONREQ","gIx":102,"d":"entities"},{"iTp":1,"gId":"ADDONREQ","gIx":104,"d":"sheepomelon.json"},{"iTp":1,"gId":"ADDONREQ","gIx":104,"m":"Found a loose file 'sheepomelon_shear.json' in loot_tables\\entities. Files should only be in the folder loot_tables\\entities\\","d":"sheepomelon_shear.json"},{"iTp":7,"gId":"TEXTURE","gIx":1,"fs":{"Entity Resource Count":{"Count":3},"Texture References":{"Count":3},"Entity References":{"Count":3},"File Count":{"Count":4},"Unique Texture Handles (estimated)":{"Count":3},"Unique Texture Paths":{"Count":3},"Unique Particle Texture Paths":{"Count":0},"Unique Particle Texture Vanilla Paths":{"Count":1},"Unique Entity Texture Paths":{"Count":3}}},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/behavior_packs/aop_mobsbp/"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/behavior_packs/aop_mobsbp/"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/behavior_packs/aop_mobsbp/"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/behavior_packs/aop_mobsbp/"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/behavior_packs/aop_mobsbp/"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/behavior_packs/aop_mobsbp/"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/behavior_packs/aop_mobsbp/manifest.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/behavior_packs/aop_mobsbp/manifest.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/behavior_packs/aop_mobsbp/manifest.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/behavior_packs/aop_mobsbp/manifest.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/behavior_packs/aop_mobsbp/manifest.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/behavior_packs/aop_mobsbp/manifest.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/behavior_packs/aop_mobsbp/animations/sheepomelon.bp_anims.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/behavior_packs/aop_mobsbp/animations/sheepomelon.bp_anims.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/behavior_packs/aop_mobsbp/animations/sheepomelon.bp_anims.json","fs":{"say":{"Count":1},"summon":{"Count":1}}},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/behavior_packs/aop_mobsbp/animations/sheepomelon.bp_anims.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/behavior_packs/aop_mobsbp/animations/sheepomelon.bp_anims.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/behavior_packs/aop_mobsbp/animations/sheepomelon.bp_anims.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/behavior_packs/aop_mobsbp/animation_controllers/sheepomelon.bp_ac.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/behavior_packs/aop_mobsbp/animation_controllers/sheepomelon.bp_ac.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/behavior_packs/aop_mobsbp/animation_controllers/sheepomelon.bp_ac.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/behavior_packs/aop_mobsbp/animation_controllers/sheepomelon.bp_ac.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/behavior_packs/aop_mobsbp/animation_controllers/sheepomelon.bp_ac.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/behavior_packs/aop_mobsbp/animation_controllers/sheepomelon.bp_ac.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/behavior_packs/aop_mobsbp/entities/biceson.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/behavior_packs/aop_mobsbp/entities/biceson.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/behavior_packs/aop_mobsbp/entities/biceson.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/behavior_packs/aop_mobsbp/entities/biceson.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/behavior_packs/aop_mobsbp/entities/biceson.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/behavior_packs/aop_mobsbp/entities/biceson.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/behavior_packs/aop_mobsbp/entities/nardolphle.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/behavior_packs/aop_mobsbp/entities/nardolphle.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/behavior_packs/aop_mobsbp/entities/nardolphle.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/behavior_packs/aop_mobsbp/entities/nardolphle.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/behavior_packs/aop_mobsbp/entities/nardolphle.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/behavior_packs/aop_mobsbp/entities/nardolphle.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/behavior_packs/aop_mobsbp/entities/sheepomelon.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/behavior_packs/aop_mobsbp/entities/sheepomelon.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/behavior_packs/aop_mobsbp/entities/sheepomelon.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/behavior_packs/aop_mobsbp/entities/sheepomelon.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/behavior_packs/aop_mobsbp/entities/sheepomelon.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/behavior_packs/aop_mobsbp/entities/sheepomelon.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/behavior_packs/aop_mobsbp/loot_tables/entities/sheepomelon.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/behavior_packs/aop_mobsbp/loot_tables/entities/sheepomelon.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/behavior_packs/aop_mobsbp/loot_tables/entities/sheepomelon.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/behavior_packs/aop_mobsbp/loot_tables/entities/sheepomelon.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/behavior_packs/aop_mobsbp/loot_tables/entities/sheepomelon.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/behavior_packs/aop_mobsbp/loot_tables/entities/sheepomelon.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/behavior_packs/aop_mobsbp/loot_tables/entities/sheepomelon_shear.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/behavior_packs/aop_mobsbp/loot_tables/entities/sheepomelon_shear.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/behavior_packs/aop_mobsbp/loot_tables/entities/sheepomelon_shear.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/behavior_packs/aop_mobsbp/loot_tables/entities/sheepomelon_shear.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/behavior_packs/aop_mobsbp/loot_tables/entities/sheepomelon_shear.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/behavior_packs/aop_mobsbp/loot_tables/entities/sheepomelon_shear.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/biceson.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/biceson.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/biceson.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/biceson.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/biceson.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/biceson.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/nardolphle.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/nardolphle.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/nardolphle.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/nardolphle.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/nardolphle.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/nardolphle.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/sheepomelon.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/sheepomelon.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/sheepomelon.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/sheepomelon.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/sheepomelon.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/sheepomelon.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/behavior_packs/aop_mobsbp/texts/en_US.lang"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/behavior_packs/aop_mobsbp/texts/en_US.lang"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/behavior_packs/aop_mobsbp/texts/en_US.lang"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/behavior_packs/aop_mobsbp/texts/en_US.lang"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/behavior_packs/aop_mobsbp/texts/en_US.lang"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/behavior_packs/aop_mobsbp/texts/en_US.lang"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/behavior_packs/aop_mobsbp/texts/languages.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/behavior_packs/aop_mobsbp/texts/languages.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/behavior_packs/aop_mobsbp/texts/languages.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/behavior_packs/aop_mobsbp/texts/languages.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/behavior_packs/aop_mobsbp/texts/languages.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/behavior_packs/aop_mobsbp/texts/languages.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/manifest.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/manifest.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/manifest.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/manifest.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/manifest.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/manifest.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/sounds.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/sounds.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/sounds.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/sounds.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/sounds.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/sounds.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/animations/biceson.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/animations/biceson.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/animations/biceson.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/animations/biceson.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/animations/biceson.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/animations/biceson.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/animations/nardolphle.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/animations/nardolphle.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/animations/nardolphle.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/animations/nardolphle.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/animations/nardolphle.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/animations/nardolphle.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/animations/sheepomelon.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/animations/sheepomelon.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/animations/sheepomelon.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/animations/sheepomelon.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/animations/sheepomelon.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/animations/sheepomelon.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/entity/biceson.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/entity/biceson.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/entity/biceson.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/entity/biceson.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/entity/biceson.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/entity/biceson.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/entity/nardolphle.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/entity/nardolphle.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/entity/nardolphle.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/entity/nardolphle.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/entity/nardolphle.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/entity/nardolphle.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/entity/sheepomelon.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/entity/sheepomelon.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/entity/sheepomelon.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/entity/sheepomelon.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/entity/sheepomelon.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/entity/sheepomelon.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/materials/entity.material"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/materials/entity.material"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/materials/entity.material"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/materials/entity.material"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/materials/entity.material"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/materials/entity.material"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/models/entity/biceson.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/models/entity/biceson.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/models/entity/biceson.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/models/entity/biceson.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/models/entity/biceson.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/models/entity/biceson.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/models/entity/nardolphle.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/models/entity/nardolphle.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/models/entity/nardolphle.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/models/entity/nardolphle.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/models/entity/nardolphle.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/models/entity/nardolphle.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/models/entity/sheepomelon.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/models/entity/sheepomelon.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/models/entity/sheepomelon.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/models/entity/sheepomelon.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/models/entity/sheepomelon.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/models/entity/sheepomelon.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/particles/fireworks_pop.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/particles/fireworks_pop.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/particles/fireworks_pop.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/particles/fireworks_pop.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/particles/fireworks_pop.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/particles/fireworks_pop.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/particles/smoke_puff.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/particles/smoke_puff.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/particles/smoke_puff.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/particles/smoke_puff.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/particles/smoke_puff.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/particles/smoke_puff.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/texts/en_US.lang"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/texts/en_US.lang"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/texts/en_US.lang"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/texts/en_US.lang"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/texts/en_US.lang"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/texts/en_US.lang"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/texts/languages.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/texts/languages.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/texts/languages.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/texts/languages.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/texts/languages.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/texts/languages.json"},{"iTp":0,"gId":"WORLDDATA","gIx":1,"m":"Test World Data Validation completed successfully."},{"iTp":0,"gId":"ADDONIREQ","gIx":1}],"generatorName":"Minecraft Creator Tools","suite":2,"generatorVersion":"0.2.9","sourceName":"content1","sourcePath":"/"}); +_addReportJson({"info":{"behaviorPackManifestCount":0,"unknownJsonCount":0,"entityTypeManifestCount":0,"itemTypeManifestCount":0,"blockTypeManifestCount":0,"resourcePackManifestCount":0,"worldCount":0,"entityTypeResourceCount":0,"behaviorPackAnimationCount":0,"behaviorPackAnimationControllerCount":0,"overallSize":54260,"fileCounts":37,"folderCounts":23,"contentSize":54260,"contentFileCounts":37,"contentFolderCounts":18,"animationCount":0,"textureCount":0,"chunkCount":0,"subchunkLessChunkCount":0,"worldLoadErrors":0,"featureSets":{"Textures Entity Resource Count":{"Count":3},"Textures Texture References":{"Count":3},"Textures Entity References":{"Count":3},"Textures File Count":{"Count":4},"Textures Unique Texture Handles (estimated)":{"Count":3},"Textures Unique Texture Paths":{"Count":3},"Textures Unique Particle Texture Paths":{"Count":0},"Textures Unique Particle Texture Vanilla Paths":{"Count":1},"Textures Unique Entity Texture Paths":{"Count":3},"Command say":{"Count":1},"Command summon":{"Count":1}},"errorCount":0,"internalProcessingErrorCount":0,"warningCount":0,"testSuccessCount":2,"testFailCount":3,"errorSummary":"","internalProcessingErrorSummary":"","testFailSummary":"TESTFAIL: [ADDONREQ102] Found an add-on-blocked folder 'entities' in a parent folder pack\\loot_tables. Should be named 'creatorshortname' and not a common term: entities\r\nTESTFAIL: [ADDONREQ104] Found a loose file 'sheepomelon.json' in loot_tables\\entities. Files should only be in the folder loot_tables\\entities\\: sheepomelon.json\r\nTESTFAIL: [ADDONREQ104] Found a loose file 'sheepomelon_shear.json' in loot_tables\\entities. Files should only be in the folder loot_tables\\entities\\: sheepomelon_shear.json","summary":{"PACKSIZE":{"1":{"title":"Overall Size"},"2":{"title":"Overall File Count","defaultMessage":"File Counts"},"3":{"title":"Overall Folder Count","defaultMessage":"Folder Counts"},"4":{"title":"Content Size"},"5":{"title":"Content File Count","defaultMessage":"Content File Counts"},"6":{"title":"Content Folder Count","defaultMessage":"Content Folder Counts"}},"ADDONREQ":{"102":{"title":"102","defaultMessage":"Found an add-on-blocked folder 'entities' in a parent folder pack\\loot_tables. Should be named 'creatorshortname' and not a common term","testCompleteFails":1},"104":{"title":"104","defaultMessage":"Found a loose file 'sheepomelon.json' in loot_tables\\entities. Files should only be in the folder loot_tables\\entities\\","testCompleteFails":2}},"TEXTURE":{"1":{"title":"Textures"}},"WORLDDATA":{"1":{"defaultMessage":"Blocks","testCompleteSuccesses":1},"2":{"defaultMessage":"Block Data"},"3":{"defaultMessage":"Commands"},"4":{"defaultMessage":"Execute Sub Commands"},"5":{"defaultMessage":"NBT Tags"},"6":{"defaultMessage":"NBT Experiment Tags"}},"ADDONIREQ":{"1":{"defaultMessage":"Test Addon Item Requirements Generator completed successfully.","testCompleteSuccesses":1}}}},"items":[{"iTp":7,"gId":"PACKSIZE","gIx":1,"d":54260},{"iTp":7,"gId":"PACKSIZE","gIx":2,"d":37},{"iTp":7,"gId":"PACKSIZE","gIx":3,"d":23},{"iTp":7,"gId":"PACKSIZE","gIx":4,"d":54260},{"iTp":7,"gId":"PACKSIZE","gIx":5,"d":37},{"iTp":7,"gId":"PACKSIZE","gIx":6,"d":18},{"iTp":1,"gId":"ADDONREQ","gIx":102,"d":"entities"},{"iTp":1,"gId":"ADDONREQ","gIx":104,"d":"sheepomelon.json"},{"iTp":1,"gId":"ADDONREQ","gIx":104,"m":"Found a loose file 'sheepomelon_shear.json' in loot_tables\\entities. Files should only be in the folder loot_tables\\entities\\","d":"sheepomelon_shear.json"},{"iTp":7,"gId":"TEXTURE","gIx":1,"fs":{"Entity Resource Count":{"Count":3},"Texture References":{"Count":3},"Entity References":{"Count":3},"File Count":{"Count":4},"Unique Texture Handles (estimated)":{"Count":3},"Unique Texture Paths":{"Count":3},"Unique Particle Texture Paths":{"Count":0},"Unique Particle Texture Vanilla Paths":{"Count":1},"Unique Entity Texture Paths":{"Count":3}}},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/behavior_packs/aop_mobsbp/"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/behavior_packs/aop_mobsbp/"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/behavior_packs/aop_mobsbp/"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/behavior_packs/aop_mobsbp/"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/behavior_packs/aop_mobsbp/"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/behavior_packs/aop_mobsbp/"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/behavior_packs/aop_mobsbp/manifest.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/behavior_packs/aop_mobsbp/manifest.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/behavior_packs/aop_mobsbp/manifest.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/behavior_packs/aop_mobsbp/manifest.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/behavior_packs/aop_mobsbp/manifest.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/behavior_packs/aop_mobsbp/manifest.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/behavior_packs/aop_mobsbp/animations/sheepomelon.bp_anims.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/behavior_packs/aop_mobsbp/animations/sheepomelon.bp_anims.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/behavior_packs/aop_mobsbp/animations/sheepomelon.bp_anims.json","fs":{"say":{"Count":1},"summon":{"Count":1}}},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/behavior_packs/aop_mobsbp/animations/sheepomelon.bp_anims.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/behavior_packs/aop_mobsbp/animations/sheepomelon.bp_anims.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/behavior_packs/aop_mobsbp/animations/sheepomelon.bp_anims.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/behavior_packs/aop_mobsbp/animation_controllers/sheepomelon.bp_ac.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/behavior_packs/aop_mobsbp/animation_controllers/sheepomelon.bp_ac.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/behavior_packs/aop_mobsbp/animation_controllers/sheepomelon.bp_ac.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/behavior_packs/aop_mobsbp/animation_controllers/sheepomelon.bp_ac.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/behavior_packs/aop_mobsbp/animation_controllers/sheepomelon.bp_ac.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/behavior_packs/aop_mobsbp/animation_controllers/sheepomelon.bp_ac.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/behavior_packs/aop_mobsbp/entities/biceson.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/behavior_packs/aop_mobsbp/entities/biceson.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/behavior_packs/aop_mobsbp/entities/biceson.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/behavior_packs/aop_mobsbp/entities/biceson.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/behavior_packs/aop_mobsbp/entities/biceson.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/behavior_packs/aop_mobsbp/entities/biceson.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/behavior_packs/aop_mobsbp/entities/nardolphle.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/behavior_packs/aop_mobsbp/entities/nardolphle.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/behavior_packs/aop_mobsbp/entities/nardolphle.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/behavior_packs/aop_mobsbp/entities/nardolphle.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/behavior_packs/aop_mobsbp/entities/nardolphle.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/behavior_packs/aop_mobsbp/entities/nardolphle.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/behavior_packs/aop_mobsbp/entities/sheepomelon.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/behavior_packs/aop_mobsbp/entities/sheepomelon.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/behavior_packs/aop_mobsbp/entities/sheepomelon.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/behavior_packs/aop_mobsbp/entities/sheepomelon.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/behavior_packs/aop_mobsbp/entities/sheepomelon.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/behavior_packs/aop_mobsbp/entities/sheepomelon.behavior.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/behavior_packs/aop_mobsbp/loot_tables/entities/sheepomelon.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/behavior_packs/aop_mobsbp/loot_tables/entities/sheepomelon.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/behavior_packs/aop_mobsbp/loot_tables/entities/sheepomelon.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/behavior_packs/aop_mobsbp/loot_tables/entities/sheepomelon.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/behavior_packs/aop_mobsbp/loot_tables/entities/sheepomelon.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/behavior_packs/aop_mobsbp/loot_tables/entities/sheepomelon.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/behavior_packs/aop_mobsbp/loot_tables/entities/sheepomelon_shear.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/behavior_packs/aop_mobsbp/loot_tables/entities/sheepomelon_shear.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/behavior_packs/aop_mobsbp/loot_tables/entities/sheepomelon_shear.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/behavior_packs/aop_mobsbp/loot_tables/entities/sheepomelon_shear.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/behavior_packs/aop_mobsbp/loot_tables/entities/sheepomelon_shear.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/behavior_packs/aop_mobsbp/loot_tables/entities/sheepomelon_shear.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/biceson.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/biceson.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/biceson.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/biceson.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/biceson.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/biceson.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/nardolphle.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/nardolphle.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/nardolphle.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/nardolphle.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/nardolphle.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/nardolphle.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/sheepomelon.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/sheepomelon.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/sheepomelon.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/sheepomelon.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/sheepomelon.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/behavior_packs/aop_mobsbp/spawn_rules/sheepomelon.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/behavior_packs/aop_mobsbp/texts/en_US.lang"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/behavior_packs/aop_mobsbp/texts/en_US.lang"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/behavior_packs/aop_mobsbp/texts/en_US.lang"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/behavior_packs/aop_mobsbp/texts/en_US.lang"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/behavior_packs/aop_mobsbp/texts/en_US.lang"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/behavior_packs/aop_mobsbp/texts/en_US.lang"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/behavior_packs/aop_mobsbp/texts/languages.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/behavior_packs/aop_mobsbp/texts/languages.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/behavior_packs/aop_mobsbp/texts/languages.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/behavior_packs/aop_mobsbp/texts/languages.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/behavior_packs/aop_mobsbp/texts/languages.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/behavior_packs/aop_mobsbp/texts/languages.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/manifest.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/manifest.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/manifest.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/manifest.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/manifest.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/manifest.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/sounds.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/sounds.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/sounds.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/sounds.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/sounds.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/sounds.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/animations/biceson.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/animations/biceson.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/animations/biceson.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/animations/biceson.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/animations/biceson.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/animations/biceson.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/animations/nardolphle.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/animations/nardolphle.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/animations/nardolphle.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/animations/nardolphle.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/animations/nardolphle.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/animations/nardolphle.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/animations/sheepomelon.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/animations/sheepomelon.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/animations/sheepomelon.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/animations/sheepomelon.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/animations/sheepomelon.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/animations/sheepomelon.animation.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/entity/biceson.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/entity/biceson.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/entity/biceson.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/entity/biceson.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/entity/biceson.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/entity/biceson.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/entity/nardolphle.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/entity/nardolphle.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/entity/nardolphle.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/entity/nardolphle.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/entity/nardolphle.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/entity/nardolphle.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/entity/sheepomelon.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/entity/sheepomelon.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/entity/sheepomelon.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/entity/sheepomelon.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/entity/sheepomelon.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/entity/sheepomelon.entity.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/materials/entity.material"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/materials/entity.material"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/materials/entity.material"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/materials/entity.material"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/materials/entity.material"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/materials/entity.material"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/models/entity/biceson.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/models/entity/biceson.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/models/entity/biceson.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/models/entity/biceson.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/models/entity/biceson.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/models/entity/biceson.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/models/entity/nardolphle.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/models/entity/nardolphle.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/models/entity/nardolphle.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/models/entity/nardolphle.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/models/entity/nardolphle.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/models/entity/nardolphle.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/models/entity/sheepomelon.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/models/entity/sheepomelon.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/models/entity/sheepomelon.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/models/entity/sheepomelon.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/models/entity/sheepomelon.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/models/entity/sheepomelon.geometry.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/particles/fireworks_pop.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/particles/fireworks_pop.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/particles/fireworks_pop.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/particles/fireworks_pop.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/particles/fireworks_pop.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/particles/fireworks_pop.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/particles/smoke_puff.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/particles/smoke_puff.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/particles/smoke_puff.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/particles/smoke_puff.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/particles/smoke_puff.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/particles/smoke_puff.json"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/texts/en_US.lang"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/texts/en_US.lang"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/texts/en_US.lang"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/texts/en_US.lang"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/texts/en_US.lang"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/texts/en_US.lang"},{"iTp":7,"gId":"WORLDDATA","gIx":1,"p":"/Content/resource_packs/aop_mobsrp/texts/languages.json"},{"iTp":7,"gId":"WORLDDATA","gIx":2,"p":"/Content/resource_packs/aop_mobsrp/texts/languages.json"},{"iTp":7,"gId":"WORLDDATA","gIx":3,"p":"/Content/resource_packs/aop_mobsrp/texts/languages.json"},{"iTp":7,"gId":"WORLDDATA","gIx":4,"p":"/Content/resource_packs/aop_mobsrp/texts/languages.json"},{"iTp":7,"gId":"WORLDDATA","gIx":5,"p":"/Content/resource_packs/aop_mobsrp/texts/languages.json"},{"iTp":7,"gId":"WORLDDATA","gIx":6,"p":"/Content/resource_packs/aop_mobsrp/texts/languages.json"},{"iTp":0,"gId":"WORLDDATA","gIx":1,"m":"Test World Data Validation completed successfully."},{"iTp":0,"gId":"ADDONIREQ","gIx":1}],"generatorName":"Minecraft Creator Tools","suite":2,"generatorVersion":"0.2.9","sourceName":"content1","sourcePath":"/"});