diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index e04e6db0..b3c21c90 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -113,21 +113,12 @@ body: Provide details about your system and setup to help diagnose the issue. - - type: dropdown + - type: input id: mc-version attributes: label: 🟩 Minecraft Version - description: >- - The version of Minecraft you're using. (Only versions supported by the - latest version of Animated Java are listed). - options: - - 1.20.4 - - 1.20.5 - - 1.20.6 - - 1.21.1 - - 1.21.2 - - 1.21.3 - - 1.21.4 + description: The version of Minecraft you're using. + placeholder: E.g. 1.20.4, 26.1.1 validations: required: true diff --git a/bun.lock b/bun.lock index e8bf283a..ed4cbd80 100644 --- a/bun.lock +++ b/bun.lock @@ -25,7 +25,7 @@ "@typescript-eslint/eslint-plugin": "^5.54.0", "@typescript-eslint/parser": "^5.54.0", "blockbench-patch-manager": "^1.1.0", - "book-and-quill": "^1.0.9", + "book-and-quill": "^1.0.10", "esbuild": "^0.17.10", "esbuild-plugin-import-folder": "^1.0.1", "esbuild-plugin-import-glob": "^0.1.1", @@ -37,7 +37,7 @@ "firebase": "^9.19.0", "jiti": "^2.6.1", "js-yaml": "^4.1.0", - "mc-build": "^4.0.4", + "mc-build": "^4.1.2", "node-modules-vscode-problems-patch": "^1.0.9", "octokit": "^5.0.3", "prettier": "^3.8.2", @@ -503,7 +503,7 @@ "blockbench-types": ["blockbench-types@5.1.0", "", { "dependencies": { "@babel/types": "^7.20.7", "@types/dompurify": "^3.0.5", "@types/jquery": "^3.5.32", "@types/prismjs": "^1.26.0", "@types/three": "^0.129.2", "@types/tinycolor2": "^1.4.6", "dompurify": "^3.0.1", "electron": "^40.8.0", "prismjs": "^1.29.0", "tinycolor2": "^1.6.0", "typescript": "^5.8.3", "vue": "2.7.14", "wintersky": "^1.3.0" } }, "sha512-JLMHJZ+2J7aFQwSacfW4N/rvd56JRqvaBsHNH2R6sjyxkwKPuNPKYIeav/WtRXnq3dmlsEoH3rtg09s7yhe3sg=="], - "book-and-quill": ["book-and-quill@1.0.9", "", { "dependencies": { "@types/node": "^25.3.3", "tinycolor2": "^1.6.0" }, "peerDependencies": { "typescript": "^5" } }, "sha512-5jpj2ziHTMzeS7YTPqV8IcvaRkRn71A+kcvhSsxruK6JBM14jnqIjgdiKlDfuA/YzV5P6APwH9lNcC5yWOBn5A=="], + "book-and-quill": ["book-and-quill@1.0.10", "", { "dependencies": { "@types/node": "^25.3.3", "tinycolor2": "^1.6.0" }, "peerDependencies": { "typescript": "^5" } }, "sha512-9NaTEOmnJM8GPJVsdvl8Th9peju97KgvAtWPhya3v+IGI4hAafpdpuVoIlCK7TjHkHjIqKJ2HLYmcJX2qd4kEQ=="], "boolean": ["boolean@3.2.0", "", {}, "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw=="], @@ -829,7 +829,7 @@ "matcher": ["matcher@3.0.0", "", { "dependencies": { "escape-string-regexp": "^4.0.0" } }, "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng=="], - "mc-build": ["mc-build@4.0.4", "", { "bin": { "mcb3": "dist/mcb.js", "mc-build": "dist/mcb.js", "mcb": "dist/mcb.js" } }, "sha512-3f+tAQLann/dYBBlHD6itL7A3OZrPOAqayMtqOvVTikBnzKtemdhTY2w4Nkv1CDrVqLwHZ8GVnbVawYV6ATIWQ=="], + "mc-build": ["mc-build@4.1.2", "", { "bin": { "mcb3": "dist/mcb.js", "mc-build": "dist/mcb.js", "mcb": "dist/mcb.js" } }, "sha512-C/Su40vSUn2IPtbtLPzkQXUUiv2ATCOwvkE4dE4JhORIWM6qTWHwCZlABeqpegcjRvR6PuqWQeyqd2cVeCW8BQ=="], "md5": ["md5@2.3.0", "", { "dependencies": { "charenc": "0.0.2", "crypt": "0.0.2", "is-buffer": "~1.1.6" } }, "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g=="], diff --git a/package.json b/package.json index 0df6b361..7b084fc2 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "title": "Animated Java", "icon": "icon.svg", "description": "Effortlessly craft complex animations for Minecraft: Java Edition", - "version": "1.10.0-beta.6", + "version": "1.10.0-beta.7", "min_blockbench_version": "5.1.4", "variant": "desktop", "tags": [ @@ -93,7 +93,7 @@ "@typescript-eslint/eslint-plugin": "^5.54.0", "@typescript-eslint/parser": "^5.54.0", "blockbench-patch-manager": "^1.1.0", - "book-and-quill": "^1.0.9", + "book-and-quill": "^1.0.10", "esbuild": "^0.17.10", "esbuild-plugin-import-folder": "^1.0.1", "esbuild-plugin-import-glob": "^0.1.1", @@ -105,7 +105,7 @@ "firebase": "^9.19.0", "jiti": "^2.6.1", "js-yaml": "^4.1.0", - "mc-build": "^4.0.4", + "mc-build": "^4.1.2", "node-modules-vscode-problems-patch": "^1.0.9", "octokit": "^5.0.3", "prettier": "^3.8.2", diff --git a/schemas/blueprint-project.schema.json b/schemas/blueprint-project.schema.json index b5a8f754..e5b2b4c3 100644 --- a/schemas/blueprint-project.schema.json +++ b/schemas/blueprint-project.schema.json @@ -517,7 +517,8 @@ "glow_color", "shadow_radius", "shadow_strength", - "brightness_override", + "sky_brightness", + "block_brightness", "enchanted", "invisible", "nbt" @@ -556,6 +557,18 @@ "maximum": 15, "default": 0 }, + "sky_brightness": { + "type": "number", + "minimum": 0, + "maximum": 15, + "default": 0 + }, + "block_brightness": { + "type": "number", + "minimum": 0, + "maximum": 15, + "default": 0 + }, "enchanted": { "type": "boolean", "default": false diff --git a/schemas/plugin-blueprint.schema.json b/schemas/plugin-blueprint.schema.json index 9d0f98e0..33804ef9 100644 --- a/schemas/plugin-blueprint.schema.json +++ b/schemas/plugin-blueprint.schema.json @@ -450,8 +450,20 @@ "default": "fixed" }, "custom_brightness": { - "type": "number", - "default": 0 + "type": "object", + "required": ["sky", "block"], + "properties": { + "sky": { + "type": "number", + "minimum": 0, + "maximum": 15 + }, + "block": { + "type": "number", + "minimum": 0, + "maximum": 15 + } + } }, "custom_name": { "$ref": "#/definitions/optional_adventure_component", diff --git a/src/dialogs/displayEntityConfig/displayEntityConfig.svelte b/src/dialogs/displayEntityConfig/displayEntityConfig.svelte index 70eb3da6..fc69db0e 100644 --- a/src/dialogs/displayEntityConfig/displayEntityConfig.svelte +++ b/src/dialogs/displayEntityConfig/displayEntityConfig.svelte @@ -33,7 +33,8 @@ let billboard = observable(DEFAULT_CONFIG.billboard) let overrideBrightness = observable(DEFAULT_CONFIG.overrideBrightness) - let brightnessOverride = observable(DEFAULT_CONFIG.brightnessOverride) + let skyBrightness = observable(DEFAULT_CONFIG.skyBrightness) + let blockBrightness = observable(DEFAULT_CONFIG.blockBrightness) let enchanted = observable(DEFAULT_CONFIG.enchanted) let glowing = observable(DEFAULT_CONFIG.glowing) let overrideGlowColor = observable(DEFAULT_CONFIG.overrideGlowColor) @@ -58,7 +59,8 @@ $onApplyFunction = config.onApplyFunction $billboard = config.billboard $overrideBrightness = config.overrideBrightness - $brightnessOverride = config.brightnessOverride + $skyBrightness = config.skyBrightness + $blockBrightness = config.blockBrightness $enchanted = config.enchanted $glowing = config.glowing $overrideGlowColor = config.overrideGlowColor @@ -95,7 +97,8 @@ config.onApplyFunction = $onApplyFunction config.billboard = $billboard as BillboardMode config.overrideBrightness = $overrideBrightness - config.brightnessOverride = $brightnessOverride + config.skyBrightness = $skyBrightness + config.blockBrightness = $blockBrightness config.enchanted = $enchanted config.glowing = $glowing config.overrideGlowColor = $overrideGlowColor @@ -172,10 +175,20 @@ {#if $overrideBrightness} + + { + return { + icon: 'bubble_chart', + color: color.standard, + // @ts-expect-error - Broken BB types + name: color.name ?? 'cube.color.' + color.id, + click(element: any) { + // @ts-expect-error - any + element.forSelected(obj => obj.setColor(i), 'Change color') + }, + } + }) + }, + }, + 'randomize_marker_colors', + '_', 'rename', 'delete', ]) diff --git a/src/dialogs/interactionConfig/interactionConfig.svelte b/src/dialogs/interactionConfig/interactionConfig.svelte index 1f9ba930..13ad6b9d 100644 --- a/src/dialogs/interactionConfig/interactionConfig.svelte +++ b/src/dialogs/interactionConfig/interactionConfig.svelte @@ -10,7 +10,7 @@ export let response: Observable export let onSummonFunction: Observable - export let onInteractionFunction: Observable + export let onInteractFunction: Observable export let onAttackFunction: Observable export let onRemoveFunction: Observable export let onTickFunction: Observable @@ -38,9 +38,9 @@ /> diff --git a/src/dialogs/interactionConfig/interactionConfig.ts b/src/dialogs/interactionConfig/interactionConfig.ts index 95ade895..5c010e05 100644 --- a/src/dialogs/interactionConfig/interactionConfig.ts +++ b/src/dialogs/interactionConfig/interactionConfig.ts @@ -7,24 +7,22 @@ import { SvelteDialog } from 'svelte-patching-tools/blockbench' import { PACKAGE } from '../../constants' import { activeProjectIsBlueprintFormat } from '../../formats/blueprint' import { InteractionConfig } from '../../nodeConfigs' +import { Interaction } from '../../outliner/interaction' import { localize } from '../../util/lang' import LocatorConfigDialog from './interactionConfig.svelte' -// TODO - Make on-tick function work without requiring use-entity. - -export function openInteractionConfigDialog(interaction: BoundingBox) { - // Blockbench's JSON stringifier doesn't handle custom toJSON functions, so I'm storing the config JSON in the bounding box instead of the actual BoundingBoxConfig object - const boundingBoxConfig = InteractionConfig.fromJSON( - // @ts-expect-error - Broken BB types +export function openInteractionConfigDialog(interaction: Interaction) { + // Blockbench's JSON stringifier doesn't handle custom toJSON functions, so I'm storing the config JSON in the interaction instead of the actual InteractionConfig object + const interactionConfig = InteractionConfig.fromJSON( (interaction.config ??= new InteractionConfig().toJSON()) ) - const response = observable(boundingBoxConfig.response) - const onSummonFunction = observable(boundingBoxConfig.onSummonFunction) - const onInteractionFunction = observable(boundingBoxConfig.onInteractionFunction) - const onAttackFunction = observable(boundingBoxConfig.onAttackFunction) - const onRemoveFunction = observable(boundingBoxConfig.onRemoveFunction) - const onTickFunction = observable(boundingBoxConfig.onTickFunction) + const response = observable(interactionConfig.response) + const onSummonFunction = observable(interactionConfig.onSummonFunction) + const onInteractFunction = observable(interactionConfig.onInteractFunction) + const onAttackFunction = observable(interactionConfig.onAttackFunction) + const onRemoveFunction = observable(interactionConfig.onRemoveFunction) + const onTickFunction = observable(interactionConfig.onTickFunction) new SvelteDialog({ id: `${PACKAGE.name}:interactionConfig`, @@ -34,28 +32,30 @@ export function openInteractionConfigDialog(interaction: BoundingBox) { props: { response, onSummonFunction, - onInteractionFunction, + onInteractFunction, onAttackFunction, onRemoveFunction, onTickFunction, }, disableKeybinds: true, onConfirm() { - boundingBoxConfig.response = response.get() - boundingBoxConfig.onSummonFunction = onSummonFunction.get() - boundingBoxConfig.onInteractionFunction = onInteractionFunction.get() - boundingBoxConfig.onAttackFunction = onAttackFunction.get() - boundingBoxConfig.onRemoveFunction = onRemoveFunction.get() - boundingBoxConfig.onTickFunction = onTickFunction.get() + interactionConfig.response = response.get() + interactionConfig.onSummonFunction = onSummonFunction.get() + interactionConfig.onInteractFunction = onInteractFunction.get() + interactionConfig.onAttackFunction = onAttackFunction.get() + interactionConfig.onRemoveFunction = onRemoveFunction.get() + interactionConfig.onTickFunction = onTickFunction.get() + + interaction.config = interactionConfig.toJSON() - // @ts-expect-error - Broken BB types - interaction.config = boundingBoxConfig.toJSON() + Project!.saved = false }, }).show() } -const OPEN_INTERACTION_CONFIG = registerDeletableHandlerPatch({ +registerDeletableHandlerPatch({ id: `animated_java:action/interaction-config`, + dependencies: ['animated_java:action/create-interaction'], create() { // @ts-expect-error - Broken BB types const action = new Blockbench.Action(`animated_java:action/interaction-config`, { @@ -63,76 +63,61 @@ const OPEN_INTERACTION_CONFIG = registerDeletableHandlerPatch({ name: localize('action.open_interaction_config.name'), condition: () => activeProjectIsBlueprintFormat(), click: () => { - const interaction = BoundingBox.selected.at(0) + const interaction = Interaction.selected.at(0) if (!interaction) return - // @ts-expect-error - Broken BB types openInteractionConfigDialog(interaction) }, }) + + Interaction.prototype.menu = new Menu([ + ...Outliner.control_menu_group, + '_', + action, + '_', + { + name: 'menu.cube.color', + icon: 'color_lens', + children() { + return markerColors.map((color, i) => { + return { + icon: 'bubble_chart', + color: color.standard, + // @ts-expect-error - Broken BB types + name: color.name ?? 'cube.color.' + color.id, + click(element: Interaction) { + // @ts-expect-error - Broken BB types + element.forSelected((obj: Interaction) => { + obj.setColor(i) + }, 'Change color') + }, + } + }) + }, + }, + 'randomize_marker_colors', + '_', + 'rename', + 'toggle_visibility', + 'delete', + ]) + return action }, }) registerPropertyOverridePatch({ id: `animated_java:bounding-box/extend`, - target: BoundingBox.prototype, + target: Interaction.prototype, key: 'extend', get: function (this, value) { if (activeProjectIsBlueprintFormat()) { - return function (this: BoundingBox, ...args) { + return function (this: Interaction, ...args) { const result = value.apply(this, args) - this.menu = BoundingBox.prototype.menu + this.menu = Interaction.prototype.menu return result } } return value }, }) - -registerPropertyOverridePatch({ - id: `animated_java:bounding-box/menu`, - dependencies: [`animated_java:action/interaction-config`], - target: BoundingBox.prototype, - key: 'menu', - - get: function (this, value) { - if (activeProjectIsBlueprintFormat()) { - return new Menu([ - ...Outliner.control_menu_group, - new MenuSeparator('export'), - 'generate_bedrock_block_box', - 'generate_bedrock_entity_box', - new MenuSeparator('interaction'), - OPEN_INTERACTION_CONFIG.get(), - new MenuSeparator('settings'), - { - name: 'menu.cube.color', - icon: 'color_lens', - children() { - return markerColors.map((color, i) => { - return { - icon: 'bubble_chart', - color: color.standard, - // @ts-expect-error - Broken BB types - name: color.name ?? 'cube.color.' + color.id, - click(element: BoundingBox) { - // @ts-expect-error - Broken BB types - element.forSelected((obj: BoundingBox) => { - obj.setColor(i) - }, 'Change color') - }, - } - }) - }, - }, - 'randomize_marker_colors', - new MenuSeparator('manage'), - 'rename', - 'toggle_visibility', - 'delete', - ]) - } - return value - }, -}) diff --git a/src/dialogs/itemModelProperties/itemModelProperties.svelte b/src/dialogs/itemModelProperties/itemModelProperties.svelte new file mode 100644 index 00000000..68fbaa51 --- /dev/null +++ b/src/dialogs/itemModelProperties/itemModelProperties.svelte @@ -0,0 +1,137 @@ + + +
+
+

{localize('dialog.item_properties.tint_constants')}

+ +
+

{@html localize('dialog.item_properties.description')}

+ + {#if tintValues.length === 0} +

{localize('dialog.item_properties.empty_state')}

+ {:else} +
+ {#each tintValues as _, index} +
+ + normalizeTintValue(index)} + /> + +
+ {/each} +
+ {/if} +
+ + diff --git a/src/dialogs/itemModelProperties/itemModelProperties.ts b/src/dialogs/itemModelProperties/itemModelProperties.ts new file mode 100644 index 00000000..fe5ec3cc --- /dev/null +++ b/src/dialogs/itemModelProperties/itemModelProperties.ts @@ -0,0 +1,60 @@ +import { registerDeletableHandlerPatch } from 'blockbench-patch-manager' +import { SvelteDialog } from 'svelte-patching-tools/blockbench' +import { PACKAGE } from '../../constants' +import { + activeProjectIsBlueprintFormat, + projectTargetVersionIsAtLeast, +} from '../../formats/blueprint' +import { localize } from '../../util/lang' +import ItemModelProperties from './itemModelProperties.svelte' + +export function openItemPropertiesDialog(group: Group) { + const itemTints = group.itemModelProperties?.tints ?? [] + + new SvelteDialog({ + id: `${PACKAGE.name}:itemProperties`, + title: localize('dialog.item_properties.title'), + width: 800, + component: ItemModelProperties, + props: { + itemTints, + } as any, + disableKeybinds: true, + onConfirm() { + if (itemTints.length === 0) { + delete group.itemModelProperties + return + } + + group.itemModelProperties = { + tints: [...itemTints], + } + }, + }).show() +} + +registerDeletableHandlerPatch({ + id: `animated_java:action/item-model-properties`, + dependencies: [`animated_java:action/open-display-entity-config`], + create() { + // @ts-expect-error - Broken BB types + const action = new Blockbench.Action(`animated_java:action/item-model-properties`, { + icon: 'settings', + name: localize('action.open_item_model_properties.name'), + condition: () => + activeProjectIsBlueprintFormat() && + projectTargetVersionIsAtLeast('1.21.4') && + Group.first_selected?.children.some(child => child instanceof Cube), + click: () => { + const group = Group.first_selected + if (!group) return + openItemPropertiesDialog(group) + }, + }) + + Group.prototype.menu!.structure.splice(6, 0, '_') + Group.prototype.menu!.addAction(action, 7) + + return action + }, +}) diff --git a/src/formats/blueprint/dfu.ts b/src/formats/blueprint/dfu.ts index e506c609..15cc458f 100644 --- a/src/formats/blueprint/dfu.ts +++ b/src/formats/blueprint/dfu.ts @@ -16,6 +16,7 @@ import v1_0_0_pre7 from './versions/1.0.0-pre7' import v1_0_0_pre8 from './versions/1.0.0-pre8' import v1_10_0_beta_1 from './versions/1.10.0-beta.1' import v1_10_0_beta_4 from './versions/1.10.0-beta.4' +import v1_10_0_beta_7 from './versions/1.10.0-beta.7' import v1_4_0 from './versions/1.4.0' import v1_6_3 from './versions/1.6.3' import v1_6_5 from './versions/1.6.5' @@ -99,6 +100,8 @@ export function upgradeAnimatedJavaBlueprint(model: any): IBlueprintFormatJSON { model = v1_10_0_beta_1(model) case VersionUtil.compare(model.meta.format_version, '<', '1.10.0-beta.4'): model = v1_10_0_beta_4(model) + case VersionUtil.compare(model.meta.format_version, '<', '1.10.0-beta.7'): + model = v1_10_0_beta_7(model) } // Remove unknown blueprint settings diff --git a/src/formats/blueprint/index.ts b/src/formats/blueprint/index.ts index 32163e5d..4264b1cf 100644 --- a/src/formats/blueprint/index.ts +++ b/src/formats/blueprint/index.ts @@ -6,6 +6,7 @@ import { observable, type Observable } from 'svelte-observable-store' import { injectComponent } from 'svelte-patching-tools' import AnimatedJavaIcon from '../../assets/icons/animated_java_fancy_icon_centered.svg' import { DisplayEntityConfig, InteractionConfig, LocatorConfig } from '../../nodeConfigs' +import type { Interaction } from '../../outliner/interaction' import { type TextDisplay } from '../../outliner/textDisplay' import { type VanillaBlockDisplay } from '../../outliner/vanillaBlockDisplay' import { type VanillaItemDisplay } from '../../outliner/vanillaItemDisplay' @@ -31,6 +32,7 @@ declare module '@blockbench-types/generated/io/project' { textDisplays: TextDisplay[] vanillaItemDisplays: VanillaItemDisplay[] vanillaBlockDisplays: VanillaBlockDisplay[] + interactions: Interaction[] } } @@ -43,7 +45,9 @@ export interface IBlueprintDisplayEntityConfigJSON { on_apply_function?: DisplayEntityConfig['__onApplyFunction'] billboard?: DisplayEntityConfig['billboard'] override_brightness?: DisplayEntityConfig['overrideBrightness'] - brightness_override?: DisplayEntityConfig['brightnessOverride'] + sky_brightness?: DisplayEntityConfig['skyBrightness'] + block_brightness?: DisplayEntityConfig['blockBrightness'] + brightness_override?: number enchanted?: DisplayEntityConfig['enchanted'] glowing?: DisplayEntityConfig['glowing'] override_glow_color?: DisplayEntityConfig['overrideGlowColor'] @@ -71,7 +75,7 @@ export interface IBlueprintLocatorConfigJSON { export interface IBlueprintInteractionConfigJSON { response?: InteractionConfig['__response'] on_summon_function?: InteractionConfig['__onSummonFunction'] - on_interaction_function?: InteractionConfig['__onInteractionFunction'] + on_interact_function?: InteractionConfig['__onInteractFunction'] on_attack_function?: InteractionConfig['__onAttackFunction'] on_remove_function?: InteractionConfig['__onRemoveFunction'] on_tick_function?: InteractionConfig['__onTickFunction'] @@ -373,7 +377,7 @@ export const BLUEPRINT_FORMAT = registerDeletableHandlerPatch({ single_texture: false, texture_folder: false, texture_meshes: false, - // bounding_boxes: true, + bounding_boxes: false, uv_rotation: true, vertex_color_ambient_occlusion: true, java_cube_shading_properties: true, diff --git a/src/formats/blueprint/settings.ts b/src/formats/blueprint/settings.ts index f58f3885..4cdd5f52 100644 --- a/src/formats/blueprint/settings.ts +++ b/src/formats/blueprint/settings.ts @@ -78,7 +78,7 @@ export const defaultValues: BlueprintSettings = { custom_rig_entity_tags: '', auto_update_rig_orientation: true, use_storage_for_animation: false, - use_entity_stacking: false, + use_entity_stacking: true, // Plugin Settings baked_animations: true, json_file: '', diff --git a/src/formats/blueprint/versions/1.10.0-beta.1.ts b/src/formats/blueprint/versions/1.10.0-beta.1.ts index b4f47758..71dc5b8a 100644 --- a/src/formats/blueprint/versions/1.10.0-beta.1.ts +++ b/src/formats/blueprint/versions/1.10.0-beta.1.ts @@ -6,25 +6,16 @@ export default function upgrade(model: any): IBlueprintFormatJSON { // Invert rotation xy and position x for Blockbench 5.0 for (const animation of fixed.animations ?? []) { - for (const [uuid, animator] of Object.entries>( - animation.animators ?? {} - )) { - console.log('Processing animator', uuid, JSON.parse(JSON.stringify(animator))) + for (const animator of Object.values>(animation.animators ?? {})) { for (const keyframe of animator.keyframes ?? []) { - console.log('Processing keyframe', JSON.parse(JSON.stringify(keyframe))) if (keyframe.channel === 'rotation') { for (const datapoint of keyframe.data_points ?? []) { - console.log('Processing datapoint', JSON.parse(JSON.stringify(datapoint))) datapoint.x = invertMolang(datapoint.x) datapoint.y = invertMolang(datapoint.y) } } if (keyframe.channel === 'position') { for (const datapoint of keyframe.data_points ?? []) { - console.log( - 'Processing position datapoint', - JSON.parse(JSON.stringify(datapoint)) - ) datapoint.x = invertMolang(datapoint.x) } } diff --git a/src/formats/blueprint/versions/1.10.0-beta.7.ts b/src/formats/blueprint/versions/1.10.0-beta.7.ts new file mode 100644 index 00000000..56503676 --- /dev/null +++ b/src/formats/blueprint/versions/1.10.0-beta.7.ts @@ -0,0 +1,30 @@ +import type { IBlueprintFormatJSON } from '..' + +export default function upgrade(model: any): IBlueprintFormatJSON { + console.log('Processing model format 1.10.0-beta.7', JSON.parse(JSON.stringify(model))) + const fixed: IBlueprintFormatJSON = JSON.parse(JSON.stringify(model)) + + // Split brightness override into separate sky and block brightness values. + for (const group of fixed.groups ?? []) { + if (group.configs?.default) upgradeConfig(group.configs?.default) + for (const config of Object.values(group.configs?.variants ?? {}) as any) { + upgradeConfig(config) + } + } + for (const element of fixed.elements ?? []) { + if (element.configs?.default) upgradeConfig(element.configs?.default) + for (const config of Object.values(element.configs?.variants ?? {}) as any) { + upgradeConfig(config) + } + } + + return fixed +} + +function upgradeConfig(config: any) { + if (config?.brightness_override !== undefined) { + config.sky_brightness ??= config.brightness_override + config.block_brightness ??= config.brightness_override + delete config.brightness_override + } +} diff --git a/src/index.ts b/src/index.ts index a3b75830..3110184c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,6 +11,7 @@ import 'import_folder_recursive:./popups' // Blockbench Mods import 'import_folder_recursive:./mods' // Outliner +import './outliner/interaction' import './outliner/textDisplay' import './outliner/vanillaBlockDisplay' import './outliner/vanillaItemDisplay' diff --git a/src/lang/en.yml b/src/lang/en.yml index 66bf43ab..98f7a64b 100644 --- a/src/lang/en.yml +++ b/src/lang/en.yml @@ -12,6 +12,8 @@ animated_java: outliner: structure_group: title: Structure Group (Will not create an entity) + interaction: + title: Interaction action: open_blueprint_settings: @@ -28,6 +30,8 @@ animated_java: name: Locator Config open_interaction_config: name: Interaction Config + open_item_model_properties: + name: Item Model Properties export: name: Export export_debug: @@ -49,6 +53,8 @@ animated_java: title: Add Item Display create_block_display: title: Add Block Display + create_interaction: + title: Add Interaction copy_display_entity_config: name: Copy Display Entity Config message: Copied Display Entity Config from "%s" @@ -99,6 +105,16 @@ animated_java: close_button: Close changelog_dialog: title: Animated Java Changelog + item_properties: + title: Item Model Properties + tint_constants: Tint Constants + description: |- + [Markdown] + Constant tints apply fixed color values to an item's tint layers, so the model always uses the same tint colors in-game. + add_tint: Add Tint + empty_state: No tint constants configured. Add a tint to start. + tint_label: Tint Index %s + remove_tint: Remove unexpected_error: title: An Unexpected Error Occurred! close_button: Close @@ -450,6 +466,12 @@ animated_java: brightness_override: title: Brightness description: The brightness of the node. This should be a value between 0 and 15. + sky_brightness: + title: Sky Brightness + description: The sky light brightness of the node. This should be a value between 0 and 15. + block_brightness: + title: Block Brightness + description: The block light brightness of the node. This should be a value between 0 and 15. use_custom_brightness: title: Use Custom Brightness description: Whether or not to enable the custom brightness override for the node. @@ -550,8 +572,8 @@ animated_java: Supports [MC-Build](https://mcbuild.dev) syntax. - on_interaction_function: - title: On-Interaction Function + on_interact_function: + title: On-Interact Function description: |- [Markdown] Commands to run `as` and `at` this Interaction when a player interacts with it. @@ -616,6 +638,12 @@ animated_java: brightness_override: title: Brightness description: The brightness of the text display. This should be a value between 0 and 15. + sky_brightness: + title: Sky Brightness + description: The sky light brightness of the text display. This should be a value between 0 and 15. + block_brightness: + title: Block Brightness + description: The block light brightness of the text display. This should be a value between 0 and 15. use_custom_brightness: title: Use Custom Brightness description: Whether or not to enable the custom brightness override for the bone. diff --git a/src/mods/boneAnimatorMod.ts b/src/mods/boneAnimatorMod.ts index db0f4530..b53517d4 100644 --- a/src/mods/boneAnimatorMod.ts +++ b/src/mods/boneAnimatorMod.ts @@ -15,6 +15,7 @@ registerPropertyOverridePatch({ get: original => { return function (this: BoneAnimator, channel, allowExpression, axis) { + if (this.channels?.[channel] == undefined) return false if ( !BONE_INTERPOLATION_ENABLED.get() || !activeProjectIsBlueprintFormat() || diff --git a/src/mods/boundingBox.ts b/src/mods/boundingBox.ts deleted file mode 100644 index 7cc5fdab..00000000 --- a/src/mods/boundingBox.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { registerPatch, registerPropertyOverridePatch } from 'blockbench-patch-manager' -import { - activeProjectIsBlueprintFormat, - type IBlueprintInteractionConfigJSON, -} from '../formats/blueprint' -import { DisplayEntityConfig } from '../nodeConfigs' -import { sanitizeOutlinerElementName } from '../outliner/util' -import { DeepClonedObjectProperty } from '../util/property' - -declare global { - // @ts-expect-error - Broken BB Types - interface BoundingBox { - onSummonFunction: string - config: IBlueprintInteractionConfigJSON - } -} - -registerPropertyOverridePatch({ - id: 'animated_java:bounding_box/square_horizontal_size/resize', - target: BoundingBox.prototype, - key: 'resize', - - get: function (this, value) { - if (activeProjectIsBlueprintFormat()) { - return function ( - this: BoundingBox, - val: number | ((offset: number) => number), - axis: axisNumber, - negative?: boolean, - allowNegative?: boolean, - bidirectional?: boolean - ) { - const result = value.call(this, val, axis, negative, allowNegative, bidirectional) - - if (axis === 0) { - this.from = [this.from[0], this.from[1], this.from[0]] - this.to = [this.to[0], this.to[1], this.to[0]] - } else if (axis === 2) { - this.from = [this.from[2], this.from[1], this.from[2]] - this.to = [this.to[2], this.to[1], this.to[2]] - } - - this.preview_controller.updateGeometry(this) - TickUpdates.selection = true - - return result - } - } - return value - }, -}) - -registerPropertyOverridePatch({ - id: 'animated_java:bounding_box/preview_controller/update_transform', - target: BoundingBox.prototype.preview_controller, - key: 'updateTransform', - - get: function (this, value) { - if (activeProjectIsBlueprintFormat()) { - console.log('Applying bounding box preview controller patch') - return function (this: NodePreviewController, el: BoundingBox) { - const mesh = el.mesh - if (el.getTypeBehavior('movable')) { - mesh.position.set(el.origin[0], el.origin[1], el.origin[2]) - } - if (mesh.parent !== Project.model_3d) { - Project.model_3d.add(mesh) - } - - if (el.mesh.fix_position) { - el.mesh.fix_position.set(...el.origin) - if (el.parent instanceof Group) { - el.mesh.fix_position.x -= el.parent.mesh.position.x - el.mesh.fix_position.y -= el.parent.mesh.position.y - el.mesh.fix_position.z -= el.parent.mesh.position.z - } - } - if (el.mesh.fix_rotation) { - el.mesh.fix_rotation.copy(el.mesh.rotation) - } - - mesh.updateMatrixWorld() - this.dispatchEvent('update_transform', { element: el }) - return - } - } - return value - }, -}) - -registerPropertyOverridePatch({ - id: 'animated_java:bounding_box/preview_controller/setup', - target: BoundingBox.prototype.preview_controller, - key: 'setup', - - get: function (this, value) { - if (activeProjectIsBlueprintFormat()) { - return function (this: NodePreviewController, el: BoundingBox) { - const result = value.call(this, el) - - const mesh = el.mesh - mesh.fix_rotation = new THREE.Euler(0, 0, 0, 'ZYX') - mesh.fix_position = new THREE.Vector3(...el.position) - - return result - } - } - return value - }, -}) - -registerPatch({ - id: `animated_java:bounding_box/custom_properties`, - - apply: () => { - const properties = [ - new Property(BoundingBox, 'string', 'onSummonFunction', { - condition: activeProjectIsBlueprintFormat, - default: '', - }), - new DeepClonedObjectProperty(BoundingBox, 'config', { - condition: activeProjectIsBlueprintFormat, - default: () => { - return { default: new DisplayEntityConfig().toJSON(), variants: {} } - }, - }), - ] - - return { properties } - }, - - revert: ({ properties }) => { - properties.forEach(prop => prop.delete()) - }, -}) - -registerPropertyOverridePatch({ - id: `animated_java:override_function/bounding_box/save_name`, - target: BoundingBox.prototype, - key: 'saveName', - - condition: () => activeProjectIsBlueprintFormat(), - - get: original => { - return function (this: BoundingBox, save?: boolean) { - this.name = sanitizeOutlinerElementName(this.name, this.uuid) - return original.call(this, save) - } - }, -}) - -registerPropertyOverridePatch({ - id: `animated_java:override_function/bounding_box/sanitize_name`, - target: BoundingBox.prototype, - key: 'sanitizeName', - - condition: () => activeProjectIsBlueprintFormat(), - - get: original => { - return function (this: BoundingBox) { - this.name = sanitizeOutlinerElementName(this.name, this.uuid) - return original.call(this) - } - }, -}) diff --git a/src/mods/displayFramePatch.ts b/src/mods/displayFramePatch.ts new file mode 100644 index 00000000..b6b89c0e --- /dev/null +++ b/src/mods/displayFramePatch.ts @@ -0,0 +1,32 @@ +import { registerPropertyOverridePatch } from 'blockbench-patch-manager' + +registerPropertyOverridePatch({ + id: `animated_java:blockbench-fix/bone-animator/display-frame`, + target: BoneAnimator.prototype, + key: 'displayFrame', + + get: original => { + return function (this: BoneAnimator, multiplier?: number) { + if (!this.doRender()) return + this.getGroup() + // @ts-expect-error - Broken BB types + Animator.MolangParser.context.animation = this.animation + + if (this.channels.rotation && !this.muted.rotation) + this.displayRotation(this.interpolate('rotation') || undefined, multiplier) + if (this.channels.position && !this.muted.position) + this.displayPosition(this.interpolate('position') || undefined, multiplier) + if (this.channels.scale && !this.muted.scale) + this.displayScale(this.interpolate('scale') || undefined, multiplier) + + for (const channel in this.channels) { + const channelConfig = this.channels[channel] + // @ts-expect-error - Broken BB types + if (channelConfig.displayFrame && !this.muted[channel]) { + // @ts-expect-error - Broken BB types + channelConfig.displayFrame(this, multiplier) + } + } + } + }, +}) diff --git a/src/mods/groupMod.ts b/src/mods/groupMod.ts index 196b3568..3fb44c5f 100644 --- a/src/mods/groupMod.ts +++ b/src/mods/groupMod.ts @@ -2,6 +2,7 @@ import { registerPatch, registerPropertyOverridePatch } from 'blockbench-patch-m import { activeProjectIsBlueprintFormat } from '../formats/blueprint' import { DisplayEntityConfig } from '../nodeConfigs' import { sanitizeOutlinerElementName } from '../outliner/util' +import type { TintSource } from '../systems/minecraft/itemDefinitions' import type { IDisplayEntityConfigs } from '../systems/rigRenderer' import { localize } from '../util/lang' import { DeepClonedObjectProperty } from '../util/property' @@ -11,6 +12,9 @@ declare global { interface Group { onSummonFunction: string configs: IDisplayEntityConfigs + itemModelProperties?: { + tints: TintSource[] + } } } @@ -62,6 +66,12 @@ registerPatch({ return { default: new DisplayEntityConfig().toJSON(), variants: {} } }, }), + new DeepClonedObjectProperty(Group, 'itemModelProperties', { + condition: activeProjectIsBlueprintFormat, + default: () => { + return { tints: [] } + }, + }), ] return { properties } diff --git a/src/mods/showDefaultPoseMod.ts b/src/mods/showDefaultPoseMod.ts index eec30245..c1149881 100644 --- a/src/mods/showDefaultPoseMod.ts +++ b/src/mods/showDefaultPoseMod.ts @@ -1,15 +1,15 @@ -import { registerPatch } from 'blockbench-patch-manager' +import { registerPropertyOverridePatch } from 'blockbench-patch-manager' import { activeProjectIsBlueprintFormat } from '../formats/blueprint' -registerPatch({ - id: `animated_java:show-default-pose`, +registerPropertyOverridePatch({ + id: `animated_java:animator/show-default-pose`, + target: Animator, + key: 'showDefaultPose', - apply: () => { - const original = Animator.showDefaultPose - - Animator.showDefaultPose = function (noMatrixUpdate?: boolean) { - if (!activeProjectIsBlueprintFormat()) return original(noMatrixUpdate) + condition: () => activeProjectIsBlueprintFormat(), + get: () => { + return function (noMatrixUpdate?: boolean) { const nodes = [...Group.all, ...Outliner.elements] for (const node of nodes) { // @ts-expect-error Constructor type is Function @@ -25,13 +25,7 @@ registerPatch({ mesh.scale.x = mesh.scale.y = mesh.scale.z = 1 } } - if (!noMatrixUpdate) scene.updateMatrixWorld() + if (!noMatrixUpdate) Canvas.scene.updateMatrixWorld() } - - return { original } - }, - - revert: ({ original }) => { - Animator.showDefaultPose = original }, }) diff --git a/src/nodeConfigs.ts b/src/nodeConfigs.ts index 5f2a4d1a..25ad2f57 100644 --- a/src/nodeConfigs.ts +++ b/src/nodeConfigs.ts @@ -13,7 +13,8 @@ export class DisplayEntityConfig { private __onApplyFunction?: string private __billboard?: BillboardMode private __overrideBrightness?: boolean - private __brightnessOverride?: number + private __skyBrightness?: number + private __blockBrightness?: number private __enchanted?: boolean private __glowing?: boolean private __overrideGlowColor?: boolean @@ -27,7 +28,8 @@ export class DisplayEntityConfig { on_apply_function: '', billboard: 'fixed', override_brightness: false, - brightness_override: 0, + sky_brightness: 0, + block_brightness: 0, enchanted: false, glowing: false, override_glow_color: false, @@ -64,13 +66,22 @@ export class DisplayEntityConfig { this.__overrideBrightness = value } - get brightnessOverride(): NonNullable { - if (this.__brightnessOverride !== undefined) return this.__brightnessOverride + get skyBrightness(): NonNullable { + if (this.__skyBrightness !== undefined) return this.__skyBrightness const defaultConfig = DisplayEntityConfig.getDefault() - return defaultConfig.brightnessOverride + return defaultConfig.skyBrightness } - set brightnessOverride(value: DisplayEntityConfig['__brightnessOverride']) { - this.__brightnessOverride = value + set skyBrightness(value: DisplayEntityConfig['__skyBrightness']) { + this.__skyBrightness = value + } + + get blockBrightness(): NonNullable { + if (this.__blockBrightness !== undefined) return this.__blockBrightness + const defaultConfig = DisplayEntityConfig.getDefault() + return defaultConfig.blockBrightness + } + set blockBrightness(value: DisplayEntityConfig['__blockBrightness']) { + this.__blockBrightness = value } get enchanted(): NonNullable { @@ -141,7 +152,8 @@ export class DisplayEntityConfig { this.__onApplyFunction === other.__onApplyFunction && this.__billboard === other.__billboard && this.__overrideBrightness === other.__overrideBrightness && - this.__brightnessOverride === other.__brightnessOverride && + this.__skyBrightness === other.__skyBrightness && + this.__blockBrightness === other.__blockBrightness && this.__enchanted === other.__enchanted && this.__glowing === other.__glowing && this.__overrideGlowColor === other.__overrideGlowColor && @@ -161,7 +173,8 @@ export class DisplayEntityConfig { on_apply_function: this.__onApplyFunction, billboard: this.__billboard, override_brightness: this.__overrideBrightness, - brightness_override: this.__brightnessOverride, + sky_brightness: this.__skyBrightness, + block_brightness: this.__blockBrightness, enchanted: this.__enchanted, glowing: this.__glowing, override_glow_color: this.__overrideGlowColor, @@ -177,8 +190,8 @@ export class DisplayEntityConfig { if (other.__billboard !== undefined) this.billboard = other.billboard if (other.__overrideBrightness !== undefined) this.overrideBrightness = other.overrideBrightness - if (other.__brightnessOverride !== undefined) - this.brightnessOverride = other.brightnessOverride + if (other.__skyBrightness !== undefined) this.skyBrightness = other.skyBrightness + if (other.__blockBrightness !== undefined) this.blockBrightness = other.blockBrightness if (other.__enchanted !== undefined) this.enchanted = other.enchanted if (other.__glowing !== undefined) this.glowing = other.glowing if (other.__overrideGlowColor !== undefined) @@ -195,8 +208,12 @@ export class DisplayEntityConfig { if (json.billboard !== undefined) config.__billboard = json.billboard if (json.override_brightness !== undefined) config.__overrideBrightness = json.override_brightness - if (json.brightness_override !== undefined) - config.__brightnessOverride = json.brightness_override + if (json.sky_brightness !== undefined) config.__skyBrightness = json.sky_brightness + if (json.block_brightness !== undefined) config.__blockBrightness = json.block_brightness + if (json.brightness_override !== undefined) { + config.__skyBrightness ??= json.brightness_override + config.__blockBrightness ??= json.brightness_override + } if (json.enchanted !== undefined) config.__enchanted = json.enchanted if (json.glowing !== undefined) config.__glowing = json.glowing if (json.override_glow_color !== undefined) @@ -217,8 +234,8 @@ export class DisplayEntityConfig { compound.set( 'brightness', new NbtCompound() - .set('block', new NbtFloat(this.brightnessOverride)) - .set('sky', new NbtFloat(this.brightnessOverride)) + .set('block', new NbtInt(this.blockBrightness)) + .set('sky', new NbtInt(this.skyBrightness)) ) } @@ -401,7 +418,7 @@ export class LocatorConfig { export class InteractionConfig { private __response?: boolean - private __onInteractionFunction?: string + private __onInteractFunction?: string private __onAttackFunction?: string private __onSummonFunction?: string private __onRemoveFunction?: string @@ -410,7 +427,7 @@ export class InteractionConfig { getDefault(): InteractionConfig { return InteractionConfig.fromJSON({ response: false, - on_interaction_function: '', + on_interact_function: '', on_attack_function: '', on_summon_function: '', on_remove_function: '', @@ -427,13 +444,13 @@ export class InteractionConfig { this.__response = value } - get onInteractionFunction(): NonNullable { - if (this.__onInteractionFunction !== undefined) return this.__onInteractionFunction + get onInteractFunction(): NonNullable { + if (this.__onInteractFunction !== undefined) return this.__onInteractFunction const defaultConfig = this.getDefault() - return defaultConfig.onInteractionFunction + return defaultConfig.onInteractFunction } - set onInteractionFunction(value: NonNullable) { - this.__onInteractionFunction = value + set onInteractFunction(value: NonNullable) { + this.__onInteractFunction = value } get onAttackFunction(): NonNullable { @@ -475,7 +492,7 @@ export class InteractionConfig { toJSON(): IBlueprintInteractionConfigJSON { return scrubUndefined({ response: this.__response, - on_interaction_function: this.__onInteractionFunction, + on_interact_function: this.__onInteractFunction, on_attack_function: this.__onAttackFunction, on_summon_function: this.__onSummonFunction, on_remove_function: this.__onRemoveFunction, @@ -486,8 +503,8 @@ export class InteractionConfig { static fromJSON(json: IBlueprintInteractionConfigJSON): InteractionConfig { const config = new InteractionConfig() if (json.response !== undefined) config.__response = json.response - if (json.on_interaction_function !== undefined) - config.__onInteractionFunction = json.on_interaction_function + if (json.on_interact_function !== undefined) + config.__onInteractFunction = json.on_interact_function if (json.on_attack_function !== undefined) config.__onAttackFunction = json.on_attack_function if (json.on_summon_function !== undefined) @@ -505,7 +522,7 @@ export class InteractionConfig { checkIfEqual(other: InteractionConfig) { return ( this.response === other.response && - this.onInteractionFunction === other.onInteractionFunction && + this.onInteractFunction === other.onInteractFunction && this.onAttackFunction === other.onAttackFunction && this.onSummonFunction === other.onSummonFunction && this.onRemoveFunction === other.onRemoveFunction && diff --git a/src/outliner/interaction.ts b/src/outliner/interaction.ts new file mode 100644 index 00000000..a525a521 --- /dev/null +++ b/src/outliner/interaction.ts @@ -0,0 +1,462 @@ +import { registerDeletableHandlerPatch, registerPatch } from 'blockbench-patch-manager' +import { observable } from 'svelte-observable-store' +import { PACKAGE } from '../constants' +import { + activeProjectIsBlueprintFormat, + type IBlueprintInteractionConfigJSON, +} from '../formats/blueprint' +import EVENTS from '../util/events' +import { localize as translate } from '../util/lang' +import { DeepClonedObjectProperty, fixClassPropertyInheritance } from '../util/property' +import { ResizableOutlinerElement } from './resizableOutlinerElement' +import { sanitizeOutlinerElementName } from './util' + +interface InteractionOptions { + name?: string + block?: string + position?: ArrayVector3 + rotation?: ArrayVector3 + scale?: ArrayVector3 + visibility?: boolean + color?: number +} + +@fixClassPropertyInheritance +export class Interaction extends ResizableOutlinerElement { + static type = `${PACKAGE.name}:interaction` + static icon = 'fa-computer-mouse' + static selected: Interaction[] = [] + static all: Interaction[] = [] + + static behavior = { + select_faces: false, + cube_faces: false, + movable: true, + resizable: true, + rotatable: false, + unique_name: true, + } + + type = Interaction.type + icon = Interaction.icon + needsUniqueName = true + + // Properties + onSummonFunction = Interaction.properties.onSummonFunction.default as string + config!: IBlueprintInteractionConfigJSON + + error = observable('') + + buttons = [Outliner.buttons.export, Outliner.buttons.locked, Outliner.buttons.visibility] + // eslint-disable-next-line @typescript-eslint/naming-convention + preview_controller = PREVIEW_CONTROLLER + + constructor(data: InteractionOptions, uuid = guid()) { + super(data, uuid) + Interaction.all.push(this) + + for (const key in Interaction.properties) { + Interaction.properties[key].reset(this) + } + + this.name = 'interaction' + this.extend(data) + + this.sanitizeName() + } + + sanitizeName(): string { + this.name = sanitizeOutlinerElementName(this.name, this.uuid) + return this.name + } + + getUndoCopy() { + const copy = {} as InteractionOptions & { uuid: string; type: string } + + for (const key in Interaction.properties) { + Interaction.properties[key].copy(this, copy) + } + + copy.uuid = this.uuid + copy.type = this.type + return copy + } + + getSaveCopy() { + const save = super.getSaveCopy?.() ?? {} + for (const key in Interaction.properties) { + Interaction.properties[key].copy(this, save) + } + return save + } + + resize( + val: number | ((n: number) => number), + axis: number, + negative: boolean, + _allowNegative: boolean, + _bidirectional: boolean + ) { + let before = this.temp_data.old_size ?? this.size(axis) + if (Array.isArray(before)) before = before[axis] + + let beforeOrigin = this.temp_data.old_origin ?? this.origin + if (Array.isArray(beforeOrigin)) beforeOrigin = beforeOrigin[axis] + + // For some unknown reason scale is not inverted on the y axis + let sign = before < 0 && axis !== 1 ? -1 : 1 + if (negative) sign *= -1 + + const modify = typeof val === 'function' ? val : (n: number) => n + val * sign + + let value = modify(before) + + value = Math.max(value, 0) + + if (axis === 1) { + this.scale[axis] = value + } else { + this.scale[0] = value + this.scale[2] = value + } + + this.preview_controller.updateGeometry?.(this) + this.preview_controller.updateTransform(this) + + return this + } + + select() { + if (Group.first_selected) { + Group.first_selected.unselect() + } + if (!Pressing.ctrl && !Pressing.shift) { + if (Cube.selected.length) { + Cube.selected.forEachReverse(el => el.unselect()) + } + if (selected.length) { + selected.forEachReverse(el => el !== this && el.unselect()) + } + } + + Interaction.selected.safePush(this) + this.selectLow() + this.showInOutliner() + updateSelection() + if (Animator.open && Blockbench.Animation.selected) { + Blockbench.Animation.selected.getBoneAnimator(this).select() + } + return this + } + + unselect(_unselectParent?: boolean) { + if (!this.selected) return this + if ( + Animator.open && + Timeline.selected_animator && + Timeline.selected_animator.element === this && + Timeline.selected + ) { + Timeline.selected.empty() + } + Project!.selected_elements.remove(this) + Interaction.selected.remove(this) + this.selected = false + TickUpdates.selection = true + this.preview_controller.updateHighlight(this) + return this + } +} +Interaction.prototype.icon = Interaction.icon +new Property(Interaction, 'string', 'onSummonFunction', { default: '' }) +new DeepClonedObjectProperty(Interaction, 'config', { + default: () => ({ tints: [] }), +}) +OutlinerElement.registerType(Interaction, Interaction.type) + +interface MaterialSet { + default: THREE.LineBasicMaterial + selected: THREE.LineBasicMaterial +} +const MATERIALS: Record = {} +function getBoundingBoxMaterial(interaction: Interaction): MaterialSet { + if (!MATERIALS[interaction.color]) { + const markerColor = markerColors[interaction.color % markerColors.length] + MATERIALS[interaction.color] = { + default: new THREE.LineBasicMaterial({ + color: new THREE.Color().set(markerColor.standard), + }), + selected: new THREE.LineBasicMaterial({ + color: new THREE.Color().set(markerColor.pastel), + }), + } + } + return MATERIALS[interaction.color] +} + +export const PREVIEW_CONTROLLER: NodePreviewController = new NodePreviewController(Interaction, { + setup(el: Interaction) { + ResizableOutlinerElement.prototype.preview_controller.setup(el) + + const mesh = new THREE.LineSegments( + new THREE.BufferGeometry(), + getBoundingBoxMaterial(el).default + ) + mesh.name = el.uuid + mesh.type = Interaction.type + mesh.no_export = true + mesh.visible = el.visibility + mesh.renderOrder = 100 + + mesh.isElement = true + mesh.fix_rotation = new THREE.Euler(0, 0, 0, 'ZYX') + mesh.fix_rotation.x = Math.degToRad(el.rotation[0]) + mesh.fix_rotation.y = Math.degToRad(el.rotation[1]) + mesh.fix_rotation.z = Math.degToRad(el.rotation[2]) + mesh.fix_position = new THREE.Vector3(...el.position) + mesh.fix_scale = new THREE.Vector3(...el.scale) + Project!.nodes_3d[el.uuid] = mesh + + el.preview_controller.updateGeometry?.(el) + el.preview_controller.dispatchEvent('setup', { element: el }) + }, + updateGeometry(el: Interaction) { + if (!el.mesh) return + const mesh = el.mesh as THREE.LineSegments + // Create a box geometry with the size of 1, centered horizontally and aligned to the bottom + const geometry = new THREE.BoxGeometry(1, 1, 1) + geometry.translate(0, 0.5, 0) + const edges = new THREE.EdgesGeometry(geometry) + mesh.geometry.dispose() + mesh.geometry = edges + + mesh.geometry.computeBoundingBox() + mesh.geometry.computeBoundingSphere() + + const materialSet = getBoundingBoxMaterial(el) + mesh.material = el.selected ? materialSet.selected : materialSet.default + + this.dispatchEvent('update_geometry', { element: el }) + }, + updateTransform(el: Interaction) { + ResizableOutlinerElement.prototype.preview_controller.updateTransform(el) + if (el.mesh.parent) { + Reusable.euler1.setFromQuaternion( + el.mesh.parent.getWorldQuaternion(Reusable.quat1), + 'ZYX' + ) + el.mesh.rotation.x = -Reusable.euler1.x + el.mesh.rotation.y = -Reusable.euler1.y + el.mesh.rotation.z = -Reusable.euler1.z + el.mesh.fix_rotation!.copy(el.mesh.rotation) + } + }, + updateHighlight(el: Interaction, force?: boolean | Interaction) { + if (!activeProjectIsBlueprintFormat() || !el?.mesh) return + const highlighted = Modes.edit && (force === true || force === el || el.selected) ? 1 : 0 + + const blockModel = el.mesh.children.at(0) as THREE.Mesh + if (!blockModel) return + for (const child of blockModel.children) { + if (!(child instanceof THREE.Mesh)) continue + const highlight = child.geometry.attributes.highlight + + if (highlight.array[0] != highlighted) { + highlight.array.set(Array(highlight.count).fill(highlighted)) + highlight.needsUpdate = true + } + } + }, +}) + +class InteractionAnimator extends BoneAnimator { + uuid: string + element: Interaction | undefined + + constructor(uuid: string, animation: _Animation, name: string) { + super(uuid, animation, name) + this.uuid = uuid + } + + getElement() { + this.element = OutlinerNode.uuids[this.uuid] as Interaction + return this.element + } + + select() { + this.getElement() + if (!this.element) { + unselectAllElements() + return this + } + + if (this.element.locked) { + return this + } + + if (!this.element.selected && this.element) { + this.element.select() + } + GeneralAnimator.prototype.select.call(this) + + if ( + // @ts-expect-error - Broken BB types + this[Toolbox.selected.animation_channel] && + ((Timeline.selected && Timeline.selected.length === 0) || + (Timeline.selected && (Timeline.selected[0].animator as any)) !== this) + ) { + let nearest: _Keyframe | undefined + // @ts-expect-error - Broken BB types + this[Toolbox.selected.animation_channel].forEach((kf: _Keyframe) => { + if (Math.abs(kf.time - Timeline.time) < 0.002) { + nearest = kf + } + }) + if (nearest) { + nearest.select() + } + } + + if (this.element?.parent && this.element.parent !== 'root') { + this.element.parent.openUp() + } + + return this + } + + doRender() { + this.getElement() + return !!this.element?.mesh + } + + displayRotation(arr: ArrayVector3 | ArrayVector4, multiplier = 1) { + const bone = this.getElement().mesh + + if (bone.parent) { + Reusable.euler1.setFromQuaternion(bone.parent.getWorldQuaternion(Reusable.quat1), 'ZYX') + bone.rotation.x = -Reusable.euler1.x + bone.rotation.y = -Reusable.euler1.y + bone.rotation.z = -Reusable.euler1.z + return + } + + bone.rotation.x = 0 + bone.rotation.y = 0 + bone.rotation.z = 0 + + return this + } + + displayPosition(arr: ArrayVector3, multiplier = 1) { + const bone = this.getElement().mesh + if (bone.fix_position) { + bone.position.copy(bone.fix_position as THREE.Vector3) + } + if (arr) { + bone.position.x += arr[0] * multiplier + bone.position.y += arr[1] * multiplier + bone.position.z += arr[2] * multiplier + } + return this + } + + displayScale() { + const el = this.getElement() + + if (el.mesh.parent) { + // prevent parent scale from affecting the size of the element + Reusable.vec3.copy(el.mesh.parent.scale) + el.mesh.scale.set(...el.scale).divide(Reusable.vec3) + el.mesh.fix_scale!.copy(el.mesh.scale) + return this + } + + el.mesh.scale.set(...el.scale) + + return this + } +} +InteractionAnimator.prototype.type = Interaction.type +Interaction.animator = InteractionAnimator as any +InteractionAnimator.prototype.channels = { + position: InteractionAnimator.prototype.channels.position, + function: { + name: translate('effect_animator.timeline.function'), + mutable: true, + transform: true, + max_data_points: 1, + }, +} + +export const CREATE_ACTION = registerDeletableHandlerPatch({ + id: `animated_java:action/create-interaction`, + create() { + const action = new Blockbench.Action(`animated_java:action/create-interaction`, { + name: translate('action.create_interaction.title'), + icon: Interaction.icon, + category: 'animated_java', + condition() { + return ( + activeProjectIsBlueprintFormat() && Mode.selected.id === Modes.options.edit.id + ) + }, + click() { + Undo.initEdit({ outliner: true, elements: [], selection: true }) + + const interaction = new Interaction({}).init() + const group = getCurrentGroup() + + if (group instanceof Group) { + interaction.addTo(group) + interaction.extend({ position: group.origin.slice() as ArrayVector3 }) + } + + selected.forEachReverse(el => el.unselect()) + Group.first_selected?.unselect() + interaction.select() + + Undo.finishEdit('Create Interaction', { + outliner: true, + elements: selected, + selection: true, + }) + + return interaction + }, + }) + + // @ts-expect-error - Broken BB types + BarItems.add_element.side_menu.addAction(action, 3) + + return action + }, +}) + +registerPatch({ + id: `animated_java:interaction-project-sync`, + + apply: () => { + const callbacks: Array<() => void> = [] + + callbacks.push( + EVENTS.SELECT_PROJECT.subscribe(project => { + project.interactions ??= [] + Interaction.all.empty() + Interaction.all.push(...project.interactions) + }), + + EVENTS.UNSELECT_PROJECT.subscribe(project => { + project.interactions = [...Interaction.all] + Interaction.all.empty() + }) + ) + return { callbacks } + }, + + revert: ({ callbacks }) => { + // @ts-expect-error - Broken BB types + BarItems.add_element.side_menu.removeAction(`animated_java:action/create-interaction`) + + callbacks.forEach(unsub => unsub()) + }, +}) diff --git a/src/outliner/resizableOutlinerElement.ts b/src/outliner/resizableOutlinerElement.ts index 00d25e82..0a5ca7e1 100644 --- a/src/outliner/resizableOutlinerElement.ts +++ b/src/outliner/resizableOutlinerElement.ts @@ -10,6 +10,7 @@ export class ResizableOutlinerElement extends OutlinerElement { rotation: ArrayVector3 scale: ArrayVector3 visibility: boolean + color!: number // @ts-expect-error - Is defined externally // eslint-disable-next-line @typescript-eslint/naming-convention preview_controller = PREVIEW_CONTROLLER @@ -65,14 +66,21 @@ export class ResizableOutlinerElement extends OutlinerElement { return this } - getSize() { - return this.size() + setColor(color: number) { + this.color = color + this.preview_controller.updateGeometry?.(this) + this.preview_controller.updateHighlight?.(this) + return this } - size(axis?: number, floored?: boolean) { + getSize(axis?: number) { + return this.size(axis) + } + + size(axis?: number, floored?: boolean): number | ArrayVector3 { if (axis === undefined) { - if (floored) return this.scale.map(n => Math.floor(n)) - return this.scale.slice() + if (floored) return this.scale.map(n => Math.floor(n)) as ArrayVector3 + return this.scale.slice() as ArrayVector3 } if (floored) return Math.floor(this.scale[axis]) return this.scale[axis] @@ -80,22 +88,37 @@ export class ResizableOutlinerElement extends OutlinerElement { resize( val: number | ((n: number) => number), - axis: number - // negative: boolean, - // allowNegative: boolean, - // bidirectional: boolean + axis: number, + negative: boolean, + _allowNegative: boolean, + _bidirectional: boolean ) { let before = this.temp_data.old_size ?? this.size(axis) if (before instanceof Array) before = before[axis] // For some unknown reason scale is not inverted on the y axis - const sign = before < 0 && axis !== 1 ? -1 : 1 + let sign = before < 0 && axis !== 1 ? -1 : 1 + if (negative) sign *= -1 const modify = typeof val === 'function' ? val : (n: number) => n + (val * sign) / 16 this.scale[axis] = modify(before) this.preview_controller.updateGeometry?.(this) - this.preview_controller.updateTransform(this) + this.preview_controller.updateTransform?.(this) + + return this + } + + flip(axis: number, center: number) { + console.log('Flipping', this.name, 'on axis', axis, 'with center', center) + this.rotation[(axis + 1) % 3] *= -1 + this.rotation[(axis + 2) % 3] *= -1 + this.origin[axis] = center * 2 - this.origin[axis] + // @ts-expect-error - Incorrectly required arguments + flipNameOnAxis(this, axis) + + this.preview_controller.updateTransform?.(this) + this.preview_controller.updateGeometry?.(this) return this } @@ -112,6 +135,9 @@ new Property(ResizableOutlinerElement, 'vector', 'position', { default: [0, 0, 0 new Property(ResizableOutlinerElement, 'vector', 'rotation', { default: [0, 0, 0] }) new Property(ResizableOutlinerElement, 'vector', 'pivotOffset', { default: [0, 0, 0] }) new Property(ResizableOutlinerElement, 'vector', 'scale', { default: [1, 1, 1] }) +new Property(ResizableOutlinerElement, 'number', 'color', { + default: () => Math.floor(Math.random() * markerColors.length), +}) new Property(ResizableOutlinerElement, 'boolean', 'visibility', { default: true }) new Property(ResizableOutlinerElement, 'boolean', 'locked', { default: false }) new Property(ResizableOutlinerElement, 'boolean', 'export', { default: true }) @@ -148,6 +174,7 @@ export const PREVIEW_CONTROLLER: NodePreviewController = new NodePreviewControll el.mesh.fix_rotation.copy(el.mesh.rotation) } el.mesh.scale.set(...el.scale) + makeNotZero(el.mesh.scale) if (el.mesh.fix_scale) { el.mesh.fix_scale.set(...el.scale) makeNotZero(el.mesh.fix_scale) diff --git a/src/pluginPackage/changelog.json b/src/pluginPackage/changelog.json index b1e20b56..be84e196 100644 --- a/src/pluginPackage/changelog.json +++ b/src/pluginPackage/changelog.json @@ -722,5 +722,37 @@ ] } ] + }, + "1.10.0-beta.7": { + "title": "v1.10.0-beta.7", + "author": "Titus Evans (SnaveSutit)", + "date": "2026-06-04", + "categories": [ + { + "title": "Changes", + "list": [ + "[BREAKING] The play function no longer automatically pauses all other animations. Use the new `play_exclusive` function to pause all other animations and play the desired animation.", + "Added Interaction element", + "Added functions for interacting with Interactions, similar to the locator functions, such as `as_interaction` and `as_all_interactions`.", + "Split Display Entity config option `brightnessOverride` into two separate properties: `blockBrightness` and `skyBrightness`.", + "Added marker color support to Display entity elements.", + "Made `Use Entity Stacking` default to true.", + "Added `Item Model Config` to Groups with cube children.", + "Added support for adding custom constant tints to item models." + ] + }, + { + "title": "Fixes", + "list": [ + "Fixed Item Display, Block Display, and Text Display elements ignoring flip actions.", + "Fixed custom element size not being rendered in element panel.", + "Fixed [#509](https://github.com/Animated-Java/animated-java/issues/509)", + "Fixed [#507](https://github.com/Animated-Java/animated-java/issues/507)", + "Fixed [#510](https://github.com/Animated-Java/animated-java/issues/510)", + "Fixed [#505](https://github.com/Animated-Java/animated-java/issues/505)", + "Fixed [#495](https://github.com/Animated-Java/animated-java/issues/495)" + ] + } + ] } } diff --git a/src/svelteComponents/dialogItems/collection.svelte b/src/svelteComponents/dialogItems/collection.svelte index 30781fb7..bbfe7c04 100644 --- a/src/svelteComponents/dialogItems/collection.svelte +++ b/src/svelteComponents/dialogItems/collection.svelte @@ -1,9 +1,5 @@ @@ -33,41 +29,48 @@ includedItems = $bindable(), }: Props = $props() + function sortByName(items: T[]): T[] { + return [...items].sort((a, b) => + a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }) + ) + } + let availableItemsList = $derived.by(() => { - let result: Array<{ id: number; title: string; [key: string]: any }> = [] - for (const item of availableItems) { + let result: Array<{ id: string; title: string; value: string; icon?: string }> = [] + for (const item of sortByName(availableItems)) { if ($includedItems.find(i => i.value === item.value)) continue - result.push({ id: result.length, title: item.name, icon: item.icon }) + result.push({ id: item.value, title: item.name, value: item.value, icon: item.icon }) } return result }) let includedItemsList = $derived.by(() => { - let result: Array<{ id: number; title: string; [key: string]: any }> = [] - for (const item of $includedItems) { - result.push({ id: result.length, title: item.name, icon: item.icon }) + let result: Array<{ id: string; title: string; value: string; icon?: string }> = [] + for (const item of sortByName($includedItems)) { + result.push({ id: item.value, title: item.name, value: item.value, icon: item.icon }) } return result }) - function handleSortAvailableItems(e: any) { - availableItemsList = e.detail.items - } + function includeItem(value: string) { + const itemToInclude = availableItems.find(item => item.value === value) + if (!itemToInclude) return - function handleSortIncludedItems(e: any) { - includedItemsList = e.detail.items + includedItems.update(items => { + if (items.find(item => item.value === value)) return items + return sortByName([...items, itemToInclude]) + }) } - function finalizeSort() { - includedItems.update(() => - includedItemsList.map(i => availableItems.find(a => a.name === i.title)!) - ) + function excludeItem(value: string) { + includedItems.update(items => items.filter(item => item.value !== value)) } function swapColumns() { - const temp = availableItemsList - availableItemsList = includedItemsList - includedItemsList = temp - finalizeSort() + includedItems.set( + sortByName( + availableItems.filter(item => !$includedItems.some(i => i.value === item.value)) + ) + ) } function onReset() { @@ -79,31 +82,16 @@

{availableItemsColumnLable}

-
{ - handleSortAvailableItems(e) - finalizeSort() - }} - > +
{#each availableItemsList as item (item.id)} -
- {#if item[SHADOW_ITEM_MARKER_PROPERTY_NAME]} -
- {/if} +
includeItem(item.value)}> + {item.icon ?? 'folder'} + class="fas fa-angle-right icon swap-item-button" + title="Move to {includedItemsColumnLable}" + > + {@html Blockbench.getIconNode(item.icon ?? 'folder', 'rgb(162, 235, 255)') + .outerHTML} {item.title}
{/each} @@ -111,38 +99,23 @@

{includedItemsColumnLable}

-
{ - handleSortIncludedItems(e) - finalizeSort() - }} - > +
{#each includedItemsList as item (item.id)} -
- {#if item[SHADOW_ITEM_MARKER_PROPERTY_NAME]} -
- {/if} +
excludeItem(item.value)}> + {item.icon ?? 'folder'} + class="fas fa-angle-left icon swap-item-button" + title="Move to {availableItemsColumnLable}" + > + {@html Blockbench.getIconNode(item.icon ?? 'folder', 'rgb(162, 235, 255)') + .outerHTML} {item.title}
{/each} @@ -183,19 +156,27 @@ } .list-item { display: flex; - cursor: default !important; + align-items: center; + gap: 8px; width: 100%; + cursor: pointer; } span { - /* background-color: var(--color-button); */ - /* border-bottom: 2px solid var(--color-dark); */ - /* margin: 0 8px 6px 8px; */ - padding: 0 8px; + flex: 1; } span:hover { color: var(--color-light); } - .fa-icon { + .swap-item-button { + font-size: 16px; + line-height: 1; + padding: 2px; + color: var(--color-text); + } + .swap-item-button:hover { + color: var(--color-light); + } + .swap-icon { display: flex; align-items: center; justify-content: center; diff --git a/src/svelteComponents/sidebarDialogItems/numberSlider.svelte b/src/svelteComponents/sidebarDialogItems/numberSlider.svelte index 3ac1bd81..33661a63 100644 --- a/src/svelteComponents/sidebarDialogItems/numberSlider.svelte +++ b/src/svelteComponents/sidebarDialogItems/numberSlider.svelte @@ -50,6 +50,19 @@ let input = $state() + function setValue(newValue: number) { + const divisor = 1 / (step ?? 1) + return ( + Math.round( + Math.clamp( + Animator.MolangParser.parse(newValue), + min ?? -Infinity, + max ?? Infinity + ) * divisor + ) / divisor + ) + } + function onMousedown(input: HTMLInputElement, event: any) { event.preventDefault() convertTouchEvent(event) @@ -59,11 +72,8 @@ convertTouchEvent(e2) let difference = Math.trunc((e2.clientX - event.clientX) / 10) * (step ?? 1) if (difference != lastDifference) { - input.value = Math.clamp( - parseFloat(input.value) + (difference - lastDifference), - min ?? -Infinity, - max ?? Infinity - ).toString() + value = setValue(parseFloat(input.value) + (difference - lastDifference)) + input.value = value.toString() lastDifference = difference } } @@ -75,12 +85,9 @@ addEventListeners(document, 'mouseup touchend', stop) } - function onFocusOut(input: HTMLInputElement, min?: number, max?: number) { - input.value = Math.clamp( - Animator.MolangParser.parse(input.value), - min ?? -Infinity, - max ?? Infinity - ).toString() + function onFocusOut(input: HTMLInputElement) { + value = setValue(Animator.MolangParser.parse(input.value)) + input.value = value.toString() } @@ -98,7 +105,7 @@ bind:this={input} class="dark_bordered focusable_input" bind:value - onfocusout={() => onFocusOut(input!, min, max)} + onfocusout={() => onFocusOut(input!)} />
() let inputY = $state() + function setValue(newValue: number, min?: number, max?: number) { + const divisor = 1 / (step ?? 1) + return ( + Math.round( + Math.clamp( + Animator.MolangParser.parse(newValue), + min ?? -Infinity, + max ?? Infinity + ) * divisor + ) / divisor + ) + } + function onMousedown(input: HTMLInputElement, event: any) { event.preventDefault() convertTouchEvent(event) @@ -68,11 +81,21 @@ convertTouchEvent(e2) let difference = Math.trunc((e2.clientX - event.clientX) / 10) * (step ?? 1) if (difference != lastDifference) { - input.value = Math.clamp( - parseFloat(input.value) + (difference - lastDifference), - input === inputX ? (minX ?? -Infinity) : (minY ?? -Infinity), - input === inputX ? (maxX ?? Infinity) : (maxY ?? Infinity) - ).toString() + if (input === inputX) { + valueX = setValue( + parseFloat(input.value) + (difference - lastDifference), + minX, + maxX + ) + input.value = valueX.toString() + } else { + valueY = setValue( + parseFloat(input.value) + (difference - lastDifference), + minY, + maxY + ) + input.value = valueY.toString() + } lastDifference = difference } } @@ -84,12 +107,14 @@ addEventListeners(document, 'mouseup touchend', stop) } - function onFocusOut(input: HTMLInputElement, min?: number, max?: number) { - input.value = Math.clamp( - Animator.MolangParser.parse(input.value), - min ?? -Infinity, - max ?? Infinity - ).toString() + function onFocusOut(input: HTMLInputElement) { + if (input === inputX) { + valueX = setValue(Animator.MolangParser.parse(input.value), minX, maxX) + input.value = valueX.toString() + } else { + valueY = setValue(Animator.MolangParser.parse(input.value), minY, maxY) + input.value = valueY.toString() + } } @@ -107,7 +132,7 @@ bind:this={inputX} class="dark_bordered focusable_input" bind:value={valueX} - onfocusout={() => onFocusOut(inputX!, minX, maxX)} + onfocusout={() => onFocusOut(inputX!)} />
onFocusOut(inputY!, minY, maxY)} + onfocusout={() => onFocusOut(inputY!)} />
args.uuid set from storage <%data_storage%> "$(id)".uuid scoreboard players set #success <%OBJECTIVES.I()%> 0 block { with storage <%temp_storage%> args - $execute store success score #success <%OBJECTIVES.I()%> if score $(uuid) matches 1.. + $execute store success score #success <%OBJECTIVES.I()%> if score $(uuid) <%OBJECTIVES.ID()%> matches 1.. $execute if score #success <%OBJECTIVES.I()%> matches 0 run data remove storage <%data_storage%> "$(id)" } } @@ -254,4 +254,78 @@ dir global { $data modify storage <%data_storage%> "$(id)" set from storage <%temp_storage%> entry } } + + dir interactions { + REPEAT (['interaction', 'attack']) as direction { + dir <%direction%> { + advancement trigger { + "criteria": { + "player_<%direction%>_clicked_interaction_entity": { + "trigger": "<%direction === 'interaction' ? 'minecraft:player_interacted_with_entity' : 'minecraft:player_hurt_entity'%>", + "conditions": { + "entity": { + "type": "minecraft:interaction" + } + } + } + }, + "rewards": { + "function": "<%context.namespace + ':' + context.path.join('/')%>/on" + } + } + + function on { + advancement revoke @s only <%context.namespace + ':' + context.path.join('/')%>/trigger + tag @s add <%TAGS.INTERACTING_PLAYER()%> + execute \ + as @e[type=interaction, tag=<%TAGS.GLOBAL_INTERACTION()%>, distance=..8] \ + if data entity @s <%direction%> \ + run function ./check + tag @s remove <%TAGS.INTERACTING_PLAYER()%> + } + + function check { + execute store result score #gametime <%OBJECTIVES.I()%> run time query gametime + execute \ + store result score #timestamp <%OBJECTIVES.I()%> \ + run data get entity @s <%direction%>.timestamp + + # Check that the interaction has a command to run, and + # if this interaction is from the player being processed. + scoreboard players set #check <%OBJECTIVES.I()%> 0 + execute \ + if score #timestamp <%OBJECTIVES.I()%> = #gametime <%OBJECTIVES.I()%> \ + if data entity @s data.animated_java.on_<%direction === 'interaction' ? 'interact' : 'attack'%>_function \ + store success score #check <%OBJECTIVES.I()%> \ + on <%direction === 'interaction' ? 'target' : 'attacker'%> \ + if entity @s[tag=<%TAGS.INTERACTING_PLAYER()%>] + + execute \ + if score #check <%OBJECTIVES.I()%> matches 1 \ + run function ./do + } + + function do { + scoreboard players set #check <%OBJECTIVES.I()%> 0 + block execute { with entity @s data.animated_java + $$(on_<%direction === 'interaction' ? 'interact' : 'attack'%>_function) + scoreboard players set #check <%OBJECTIVES.I()%> 1 + } + execute if score #check <%OBJECTIVES.I()%> matches 0 run tellraw @a [ \ + {text: '', color: red}, \ + {text:'ɪɴᴛᴇʀᴀᴄᴛɪᴏɴꜱ', color:'light_purple'}, \ + {text:'\n(<%global.root%>.mcb)', color:'dark_gray'}, \ + '\n→ ᴇʀʀᴏʀ:\n\sFailed to handle <%direction === 'interaction' ? 'right' : 'left'%> click interaction from ', \ + {selector: '@p[tag=<%TAGS.INTERACTING_PLAYER()%>]', color: gold}, \ + ':\n\s\sInvalid command: ', \ + {nbt: 'data.animated_java.on_<%direction === 'interaction' ? 'interact' : 'attack'%>_function', entity: '@s', color: aqua}, \ + '\n\s\sfrom ', \ + {text: 'data.animated_java.on_<%direction === 'interaction' ? 'interact' : 'attack'%>_function ', color: yellow}, \ + ] + # Only remove the interaction if it's consumed. + data remove entity @s <%direction%> + } + } + } + } } diff --git a/src/systems/datapackCompiler/1.20.4/global.mcbt b/src/systems/datapackCompiler/1.20.4/global.mcbt index 27667765..645e989b 100644 --- a/src/systems/datapackCompiler/1.20.4/global.mcbt +++ b/src/systems/datapackCompiler/1.20.4/global.mcbt @@ -52,16 +52,4 @@ template data_manager { emit.mcb(result.join('\n')) %%> } - - # with rw:literal block:block { - # template data_manager read - - # <%% - # console.log(arguments) - # console.log(context) - # console.log(block) - # %%> - - # template data_manager write - # } } \ No newline at end of file diff --git a/src/systems/datapackCompiler/1.20.4/main.mcb b/src/systems/datapackCompiler/1.20.4/main.mcb index 53a96ade..fc06bfd9 100644 --- a/src/systems/datapackCompiler/1.20.4/main.mcb +++ b/src/systems/datapackCompiler/1.20.4/main.mcb @@ -63,7 +63,7 @@ dir root { } IF (auto_update_rig_orientation) { - IF (has_locators || has_cameras) { + IF (has_locators || has_cameras || has_interactions) { function <%blueprint_id%>/root/on_tick/transform_floating_entities } execute on passengers run tp @s ~ ~ ~ ~ ~ @@ -71,6 +71,19 @@ dir root { function <%blueprint_id%>/root/on_tick/transform_locators } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction' && node.config?.on_tick_function)) as interaction { + block tick_interactions { with storage <%temp_storage%> entry.data.uuids_by_name + $execute \ + as $(<%interaction.storage_name%>) \ + at @s \ + run block <%interaction.storage_name%> { + <%% + emit.mcb(interaction.config.on_tick_function) + %%> + } + } + } + # Custom post-tick function IF (on_post_tick_function) { <%% @@ -79,7 +92,7 @@ dir root { } } - IF (has_locators || has_cameras) { + IF (has_locators || has_cameras || has_interactions) { dir on_tick { function transform_locators { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { @@ -114,6 +127,24 @@ dir root { } } } + + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + block select_interaction_<%interaction.storage_name%> { with storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%> + $execute \ + as $(uuid) \ + positioned ^$(px) ^$(py) ^$(pz) \ + rotated ~$(ry) ~$(rx) \ + run block as_interaction_<%interaction.storage_name%> { + tp @s ~ ~ ~ ~ ~ + + IF (interaction.config?.on_tick_function) { + <%% + emit.mcb(interaction.config.on_tick_function) + %%> + } + } + } + } } function transform_floating_entities { @@ -139,8 +170,8 @@ IF (!auto_update_rig_orientation) { tp @s ~ ~ ~ ~ ~ + data_manager prep read IF (has_locators || has_cameras) { - data_manager prep read function <%blueprint_id%>/root/on_tick/transform_floating_entities } execute at @s on passengers run tp @s ~ ~ ~ ~ ~ @@ -158,6 +189,19 @@ IF (has_animations) { function play { debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + data_manager prep read + + tag @s add <%TAGS.ANIMATION_PLAYING(blueprint_id, animation.storage_name)%> + scoreboard players set @s <%OBJECTIVES.FRAME(animation.storage_name)%> 0 + tag @s add <%TAGS.TRANSFORMS_ONLY()%> + execute at @s run function ./zzz/set_frame {frame: 0} + tag @s remove <%TAGS.TRANSFORMS_ONLY()%> + } + + function play_exclusive { + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data_manager prep read function <%blueprint_id%>/animations/pause_all tag @s add <%TAGS.ANIMATION_PLAYING(blueprint_id, animation.storage_name)%> @@ -170,6 +214,7 @@ IF (has_animations) { function stop { debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + data_manager prep read function <%blueprint_id%>/animations/pause_all scoreboard players set @s <%OBJECTIVES.FRAME(animation.storage_name)%> 0 @@ -193,6 +238,7 @@ IF (has_animations) { function next_frame { debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + data_manager prep read execute if score @s <%OBJECTIVES.FRAME(animation.storage_name)%> matches <%animation.duration%>.. run scoreboard players set @s <%OBJECTIVES.FRAME(animation.storage_name)%> 1 data remove storage <%temp_storage%> args execute store result storage <%temp_storage%> args.frame int 1 run scoreboard players get @s <%OBJECTIVES.FRAME(animation.storage_name)%> @@ -206,7 +252,6 @@ IF (has_animations) { debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> data_manager prep read - data remove storage <%temp_storage%> args $execute store result storage <%temp_storage%> args.frame int 1 run scoreboard players set @s <%OBJECTIVES.FRAME(animation.storage_name)%> $(frame) execute at @s run function ./zzz/set_frame with storage <%temp_storage%> args @@ -228,6 +273,7 @@ IF (has_animations) { #ARGS: {duration: int, to_frame: int} debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + data_manager prep read function <%blueprint_id%>/animations/pause_all tag @s add <%TAGS.ANIMATION_PLAYING(blueprint_id, animation.storage_name)%> @@ -354,7 +400,6 @@ IF (has_animations) { global.modified_effect_nodes = Object.values(animation.modified_nodes).filter( node => node.type === 'locator' && global.frame.node_transforms[node.uuid] ) - console.log(global.modified_effect_nodes) %%> IF(Object.keys(global.modified_effect_nodes).length > 0) { function last_frame_effects { @@ -410,7 +455,7 @@ IF (has_animations) { const lastActiveFrame = {} const modifiedNodes = Object.values(animation.modified_nodes).filter(n => n.type !== 'struct').sort(nodeSorter); for (const [frameIndex, frame] of animation.frames.entries()) { - const to_merge = {cameras: {}, locators: {}} + const to_merge = {cameras: {}, locators: {}, interactions: {}} let frameFunc = ``; for (const node of modifiedNodes) { const transform = frame.node_transforms[node.uuid] @@ -481,6 +526,32 @@ IF (has_animations) { } break } + case 'interaction': { + const lastFrame = lastActiveFrame[node.uuid] + lastActiveFrame[node.uuid] = transform + ;if (!lastFrame || matrixToNbtFloatArray(transform.matrix).toString() !== matrixToNbtFloatArray(lastFrame.matrix).toString()) { + to_merge.interactions[node.storage_name] = { + px: roundTo(transform.pos[0], 10), + py: roundTo(transform.pos[1], 10), + pz: roundTo(transform.pos[2], 10), + ry: roundTo(transform.head_rot[1], 10), + rx: roundTo(transform.head_rot[0], 10) + }; + } + + if (transform.function) { + frameFunc += + `\n$execute unless entity @s[tag=${TAGS.TRANSFORMS_ONLY()}] as $(${node.storage_name}) ` + + `positioned ^${roundTo(transform.pos[0], 10)} ^${roundTo(transform.pos[1], 10)} ^${roundTo(transform.pos[2], 10)} ` + + `rotated ~${roundTo(transform.head_rot[1], 10)} ~${roundTo(transform.head_rot[0], 10)} ` + + `${transform.function_execute_condition ? transform.function_execute_condition + ' ' : ''}run ` + + `block ${frameIndex}_interaction_${node.storage_name}%NEWLINE_PATCH%{\n` + + `tp @s ~ ~ ~ ~ ~\n` + + `${transform.function}` + + `\n}` + } + break + } case 'camera': { const lastFrame = lastActiveFrame[node.uuid] lastActiveFrame[node.uuid] = transform @@ -498,9 +569,17 @@ IF (has_animations) { } } - if (Object.keys(to_merge.locators).length > 0 || Object.keys(to_merge.cameras).length > 0) { - frameFunc += `\ndata modify storage <%temp_storage%> entry.data merge value ${JSON.stringify(to_merge)}` - frameFunc += `\ndata_manager write` + const updatedLocators = Object.keys(to_merge.locators).length > 0; + const updatedCameras = Object.keys(to_merge.cameras).length > 0; + const updatedInteractions = Object.keys(to_merge.interactions).length > 0; + + if (!updatedCameras) delete to_merge.cameras; + if (!updatedLocators) delete to_merge.locators; + if (!updatedInteractions) delete to_merge.interactions; + + if (updatedLocators || updatedCameras || updatedInteractions) { + frameFunc += `\ndata modify storage ${temp_storage} entry.data merge value ${JSON.stringify(to_merge)}` + frameFunc += `\ndata_manager prep write` if (!auto_update_rig_orientation) { frameFunc += `\nfunction ${blueprint_id}/root/on_tick/transform_floating_entities` } @@ -585,6 +664,42 @@ function summon { # Align the position and rotation of the root with the command context. tp @s ~ ~ ~ ~ ~ + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + summon minecraft:interaction \ + ^<%roundTo(interaction.default_transform.pos[0], 10)%> \ + ^<%roundTo(interaction.default_transform.pos[1], 10)%> \ + ^<%roundTo(interaction.default_transform.pos[2], 10)%> \ + { \ + Tags:<%getNodeTags(interaction, rig)%>, \ + response: <%interaction.config.response ?? false%>, \ + width: <%interaction.width%>f, \ + height: <%interaction.height%>f, \ + } + execute \ + as @n[ \ + type=minecraft:interaction, \ + tag=<%TAGS.PROJECT_INTERACTION_NAMED(blueprint_id, interaction.storage_name)%>, \ + tag=<%TAGS.NEW()%>, \ + distance=..<%Math.ceil(interaction.max_distance + 0.5)%> \ + ] \ + run block as_interaction/<%interaction.storage_name%> { + # run block ../as_interaction/<%interaction.storage_name%> { + tag @s remove <%TAGS.NEW()%> + data modify entity @s CustomName set value '<%ENTITY_NAMES.NODE(blueprint_id, interaction.type, interaction.storage_name)%>' + function animated_java:global/gu/get_entity_uuid_string + scoreboard players operation @s <%OBJECTIVES.ID()%> = aj.last_id <%OBJECTIVES.ID()%> + IF (interaction.config?.on_interact_function) { + data modify entity @s data.animated_java.on_interact_function set value "function <%blueprint_id%>/interactions/<%interaction.storage_name%>_on_interact" + } + IF (interaction.config?.on_attack_function) { + data modify entity @s data.animated_java.on_attack_function set value "function <%blueprint_id%>/interactions/<%interaction.storage_name%>_on_attack" + } + } + data modify storage <%temp_storage%> entry.data.uuids append from storage <%gu_storage%> out + data modify storage <%temp_storage%> entry.data.uuids_by_name.<%interaction.storage_name%> set from storage <%gu_storage%> out + data modify storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%>.uuid set from storage <%gu_storage%> out + } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { summon <%locator.config.entity_type%> \ ^<%roundTo(locator.default_transform.pos[0], 10)%> \ @@ -767,6 +882,16 @@ function summon { } } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction' && node.config.on_summon_function)) as interaction { + block { with storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%> + $execute at @s as $(uuid) at @s run block on_summon/custom_<%interaction.type + '_' + interaction.storage_name%> { + <%% + emit.mcb(interaction.config.on_summon_function) + %%> + } + } + } + REPEAT (Object.values(rig.nodes).filter(node => BONE_TYPES.includes(node.type) && node.on_summon_function?.trim())) as node { execute \ on passengers if entity @s[tag=<%TAGS.PROJECT_NODE_NAMED(blueprint_id, node.storage_name)%>] \ @@ -791,6 +916,25 @@ function summon { } } +dir interactions { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + IF (interaction.config.on_interact_function) { + function <%interaction.storage_name%>_on_interact { + <%% + emit.mcb(interaction.config.on_interact_function) + %%> + } + } + IF (interaction.config.on_attack_function) { + function <%interaction.storage_name%>_on_attack { + <%% + emit.mcb(interaction.config.on_attack_function) + %%> + } + } + } +} + function as_node { #ARGS: {name: string, command: string} debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> @@ -826,6 +970,234 @@ function as_node { } } +IF (has_interactions) { + function as_interaction { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {name:'$(name)', command:'$(command)', uuid:'+MISSING_UUID+'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{name:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('name')%> + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + block zzz/as_interaction/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from storage <%temp_storage%> entry.data.interactions.$(name).uuid + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + $execute as $(uuid) run return run $(command) + IF (debug_mode) { + # If the entity with the provided UUID doesn't exist, the interaction wasn't found. + tellraw @a <%TELLRAW.INTERACTION_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function as_at_interaction { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {name:'$(name)', command:'$(command)', uuid:'+MISSING_UUID+'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{name:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('name')%> + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + block zzz/as_interaction/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from storage <%temp_storage%> entry.data.interactions.$(name).uuid + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + $execute as $(uuid) at @s run return run $(command) + IF (debug_mode) { + # If the entity with the provided UUID doesn't exist, the interaction wasn't found. + tellraw @a <%TELLRAW.INTERACTION_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function as_all_interactions { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {command:'$(command)'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + block zzz/as_all_interactions/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + data modify storage <%temp_storage%> args.uuid set from storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + + block execute_as_uuid { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + + $execute as $(uuid) run return run $(command) + } + + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE({text: interaction.storage_name, color: 'aqua'})%> + } + } + } + } + + function as_at_all_interactions { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {command:'$(command)'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + block zzz/as_all_interactions/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + data modify storage <%temp_storage%> args.uuid set from storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + + block execute_as_uuid { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + + $execute as $(uuid) at @s run return run $(command) + } + + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE({text: interaction.storage_name, color: 'aqua'})%> + } + } + } + } + + function at_interaction { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {name:'$(name)', command:'$(command)'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{name:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('name')%> + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + execute at @s run block zzz/at_interaction/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge from storage <%temp_storage%> entry.data.interactions.$(name) + + IF (debug_mode) { + execute unless data storage <%temp_storage%> args.px run return run tellraw @a <%TELLRAW.INTERACTION_NOT_FOUND()%> + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + + block execute_at_transform { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + + $execute \ + positioned ^$(px) ^$(py) ^$(pz) \ + rotated ~$(ry) ~$(rx) \ + run $(command) + } + + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function at_all_interactions { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {command:'$(command)'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + block zzz/at_all_interactions/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + data modify storage <%temp_storage%> args merge from storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%> + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + + block execute_at_transform { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + + $execute \ + positioned ^$(px) ^$(py) ^$(pz) \ + rotated ~$(ry) ~$(rx) \ + run $(command) + } + + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE({text: interaction.storage_name, color: 'aqua'})%> + } + } + } + } +} + IF (has_entity_locators) { function as_locator { #ARGS: {name: string, command: string} @@ -1144,8 +1516,9 @@ dir remove { if (on_remove_function) emit.mcb(on_remove_function) %%> + data_manager prep read + IF (has_entity_locators) { - data_manager prep read REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { IF (locator.config?.on_remove_function) { IF (locator.config.use_entity) { @@ -1172,6 +1545,18 @@ dir remove { } } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + IF (interaction.config?.on_remove_function) { + block as_interaction_<%interaction.storage_name%> { with storage <%temp_storage%> entry.data.uuids_by_name + $execute as $(<%interaction.storage_name%>) at @s run block interaction_<%interaction.storage_name%>_on_remove { + <%% + emit.mcb(interaction.config.on_remove_function) + %%> + } + } + } + } + function ./this/without_on_remove_function } @@ -1190,6 +1575,11 @@ dir remove { } } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + data modify storage <%temp_storage%> args.uuid set from storage <%temp_storage%> entry.data.uuids_by_name.<%interaction.storage_name%> + function animated_java:global/remove/entity_stack_by_uuid with storage <%temp_storage%> args + } + # Remove the rig using the more expensive & thorough method if the rig_hash doesn't match. execute \ unless data storage <%temp_storage%> {entry:{data:{rig_hash: '<%rig_hash%>'}}} \ @@ -1385,4 +1775,4 @@ function set_default_pose { data_manager prep read } function ./zzz/set_default_pose -} \ No newline at end of file +} diff --git a/src/systems/datapackCompiler/1.20.5/main.mcb b/src/systems/datapackCompiler/1.20.5/main.mcb index 20636c01..a5214f05 100644 --- a/src/systems/datapackCompiler/1.20.5/main.mcb +++ b/src/systems/datapackCompiler/1.20.5/main.mcb @@ -66,7 +66,7 @@ dir root { } IF (auto_update_rig_orientation) { - IF (has_locators || has_cameras) { + IF (has_locators || has_cameras || has_interactions) { function <%blueprint_id%>/root/on_tick/transform_floating_entities } execute on passengers run tp @s ~ ~ ~ ~ ~ @@ -74,6 +74,19 @@ dir root { function <%blueprint_id%>/root/on_tick/transform_locators } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction' && node.config?.on_tick_function)) as interaction { + block tick_interactions { with storage <%temp_storage%> entry.data.uuids_by_name + $execute \ + as $(<%interaction.storage_name%>) \ + at @s \ + run block <%interaction.storage_name%> { + <%% + emit.mcb(interaction.config.on_tick_function) + %%> + } + } + } + # Custom post-tick function IF (on_post_tick_function) { <%% @@ -82,7 +95,7 @@ dir root { } } - IF (has_locators || has_cameras) { + IF (has_locators || has_cameras || has_interactions) { dir on_tick { function transform_locators { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { @@ -117,6 +130,24 @@ dir root { } } } + + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + block select_interaction_<%interaction.storage_name%> { with storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%> + $execute \ + as $(uuid) \ + positioned ^$(px) ^$(py) ^$(pz) \ + rotated ~$(ry) ~$(rx) \ + run block as_interaction_<%interaction.storage_name%> { + tp @s ~ ~ ~ ~ ~ + + IF (interaction.config?.on_tick_function) { + <%% + emit.mcb(interaction.config.on_tick_function) + %%> + } + } + } + } } function transform_floating_entities { @@ -142,8 +173,8 @@ IF (!auto_update_rig_orientation) { tp @s ~ ~ ~ ~ ~ + data_manager prep read IF (has_locators || has_cameras) { - data_manager prep read function <%blueprint_id%>/root/on_tick/transform_floating_entities } execute at @s on passengers run tp @s ~ ~ ~ ~ ~ @@ -161,6 +192,19 @@ IF (has_animations) { function play { debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + data_manager prep read + + tag @s add <%TAGS.ANIMATION_PLAYING(blueprint_id, animation.storage_name)%> + scoreboard players set @s <%OBJECTIVES.FRAME(animation.storage_name)%> 0 + tag @s add <%TAGS.TRANSFORMS_ONLY()%> + execute at @s run function ./zzz/set_frame {frame: 0} + tag @s remove <%TAGS.TRANSFORMS_ONLY()%> + } + + function play_exclusive { + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data_manager prep read function <%blueprint_id%>/animations/pause_all tag @s add <%TAGS.ANIMATION_PLAYING(blueprint_id, animation.storage_name)%> @@ -173,6 +217,7 @@ IF (has_animations) { function stop { debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + data_manager prep read function <%blueprint_id%>/animations/pause_all scoreboard players set @s <%OBJECTIVES.FRAME(animation.storage_name)%> 0 @@ -196,6 +241,7 @@ IF (has_animations) { function next_frame { debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + data_manager prep read execute if score @s <%OBJECTIVES.FRAME(animation.storage_name)%> matches <%animation.duration%>.. run scoreboard players set @s <%OBJECTIVES.FRAME(animation.storage_name)%> 1 data remove storage <%temp_storage%> args execute store result storage <%temp_storage%> args.frame int 1 run scoreboard players get @s <%OBJECTIVES.FRAME(animation.storage_name)%> @@ -209,7 +255,6 @@ IF (has_animations) { debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> data_manager prep read - data remove storage <%temp_storage%> args $execute store result storage <%temp_storage%> args.frame int 1 run scoreboard players set @s <%OBJECTIVES.FRAME(animation.storage_name)%> $(frame) execute at @s run function ./zzz/set_frame with storage <%temp_storage%> args @@ -231,6 +276,7 @@ IF (has_animations) { #ARGS: {duration: int, to_frame: int} debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + data_manager prep read function <%blueprint_id%>/animations/pause_all tag @s add <%TAGS.ANIMATION_PLAYING(blueprint_id, animation.storage_name)%> @@ -357,7 +403,6 @@ IF (has_animations) { global.modified_effect_nodes = Object.values(animation.modified_nodes).filter( node => node.type === 'locator' && global.frame.node_transforms[node.uuid] ) - console.log(global.modified_effect_nodes) %%> IF(Object.keys(global.modified_effect_nodes).length > 0) { function last_frame_effects { @@ -413,7 +458,7 @@ IF (has_animations) { const lastActiveFrame = {} const modifiedNodes = Object.values(animation.modified_nodes).filter(n => n.type !== 'struct').sort(nodeSorter); for (const [frameIndex, frame] of animation.frames.entries()) { - const to_merge = {cameras: {}, locators: {}} + const to_merge = {cameras: {}, locators: {}, interactions: {}} let frameFunc = ``; for (const node of modifiedNodes) { const transform = frame.node_transforms[node.uuid] @@ -484,6 +529,32 @@ IF (has_animations) { } break } + case 'interaction': { + const lastFrame = lastActiveFrame[node.uuid] + lastActiveFrame[node.uuid] = transform + ;if (!lastFrame || matrixToNbtFloatArray(transform.matrix).toString() !== matrixToNbtFloatArray(lastFrame.matrix).toString()) { + to_merge.interactions[node.storage_name] = { + px: roundTo(transform.pos[0], 10), + py: roundTo(transform.pos[1], 10), + pz: roundTo(transform.pos[2], 10), + ry: roundTo(transform.head_rot[1], 10), + rx: roundTo(transform.head_rot[0], 10) + }; + } + + if (transform.function) { + frameFunc += + `\n$execute unless entity @s[tag=${TAGS.TRANSFORMS_ONLY()}] as $(${node.storage_name}) ` + + `positioned ^${roundTo(transform.pos[0], 10)} ^${roundTo(transform.pos[1], 10)} ^${roundTo(transform.pos[2], 10)} ` + + `rotated ~${roundTo(transform.head_rot[1], 10)} ~${roundTo(transform.head_rot[0], 10)} ` + + `${transform.function_execute_condition ? transform.function_execute_condition + ' ' : ''}run ` + + `block ${frameIndex}_interaction_${node.storage_name}%NEWLINE_PATCH%{\n` + + `tp @s ~ ~ ~ ~ ~\n` + + `${transform.function}` + + `\n}` + } + break + } case 'camera': { const lastFrame = lastActiveFrame[node.uuid] lastActiveFrame[node.uuid] = transform @@ -501,9 +572,17 @@ IF (has_animations) { } } - if (Object.keys(to_merge.locators).length > 0 || Object.keys(to_merge.cameras).length > 0) { - frameFunc += `\ndata modify storage <%temp_storage%> entry.data merge value ${JSON.stringify(to_merge)}` - frameFunc += `\ndata_manager write` + const updatedLocators = Object.keys(to_merge.locators).length > 0; + const updatedCameras = Object.keys(to_merge.cameras).length > 0; + const updatedInteractions = Object.keys(to_merge.interactions).length > 0; + + if (!updatedCameras) delete to_merge.cameras; + if (!updatedLocators) delete to_merge.locators; + if (!updatedInteractions) delete to_merge.interactions; + + if (updatedLocators || updatedCameras || updatedInteractions) { + frameFunc += `\ndata modify storage ${temp_storage} entry.data merge value ${JSON.stringify(to_merge)}` + frameFunc += `\ndata_manager prep write` if (!auto_update_rig_orientation) { frameFunc += `\nfunction ${blueprint_id}/root/on_tick/transform_floating_entities` } @@ -588,6 +667,42 @@ function summon { # Align the position and rotation of the root with the command context. tp @s ~ ~ ~ ~ ~ + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + summon minecraft:interaction \ + ^<%roundTo(interaction.default_transform.pos[0], 10)%> \ + ^<%roundTo(interaction.default_transform.pos[1], 10)%> \ + ^<%roundTo(interaction.default_transform.pos[2], 10)%> \ + { \ + Tags:<%getNodeTags(interaction, rig)%>, \ + response: <%interaction.config.response ?? false%>, \ + width: <%interaction.width%>f, \ + height: <%interaction.height%>f, \ + } + execute \ + as @n[ \ + type=minecraft:interaction, \ + tag=<%TAGS.PROJECT_INTERACTION_NAMED(blueprint_id, interaction.storage_name)%>, \ + tag=<%TAGS.NEW()%>, \ + distance=..<%Math.ceil(interaction.max_distance + 0.5)%> \ + ] \ + run block as_interaction/<%interaction.storage_name%> { + # run block ../as_interaction/<%interaction.storage_name%> { + tag @s remove <%TAGS.NEW()%> + data modify entity @s CustomName set value '<%ENTITY_NAMES.NODE(blueprint_id, interaction.type, interaction.storage_name)%>' + function animated_java:global/gu/get_entity_uuid_string + scoreboard players operation @s <%OBJECTIVES.ID()%> = aj.last_id <%OBJECTIVES.ID()%> + IF (interaction.config?.on_interact_function) { + data modify entity @s data.animated_java.on_interact_function set value "function <%blueprint_id%>/interactions/<%interaction.storage_name%>_on_interact" + } + IF (interaction.config?.on_attack_function) { + data modify entity @s data.animated_java.on_attack_function set value "function <%blueprint_id%>/interactions/<%interaction.storage_name%>_on_attack" + } + } + data modify storage <%temp_storage%> entry.data.uuids append from storage <%gu_storage%> out + data modify storage <%temp_storage%> entry.data.uuids_by_name.<%interaction.storage_name%> set from storage <%gu_storage%> out + data modify storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%>.uuid set from storage <%gu_storage%> out + } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { summon <%locator.config.entity_type%> \ ^<%roundTo(locator.default_transform.pos[0], 10)%> \ @@ -770,6 +885,16 @@ function summon { } } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction' && node.config.on_summon_function)) as interaction { + block { with storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%> + $execute at @s as $(uuid) at @s run block on_summon/custom_<%interaction.type + '_' + interaction.storage_name%> { + <%% + emit.mcb(interaction.config.on_summon_function) + %%> + } + } + } + REPEAT (Object.values(rig.nodes).filter(node => BONE_TYPES.includes(node.type) && node.on_summon_function?.trim())) as node { execute \ on passengers if entity @s[tag=<%TAGS.PROJECT_NODE_NAMED(blueprint_id, node.storage_name)%>] \ @@ -794,6 +919,25 @@ function summon { } } +dir interactions { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + IF (interaction.config.on_interact_function) { + function <%interaction.storage_name%>_on_interact { + <%% + emit.mcb(interaction.config.on_interact_function) + %%> + } + } + IF (interaction.config.on_attack_function) { + function <%interaction.storage_name%>_on_attack { + <%% + emit.mcb(interaction.config.on_attack_function) + %%> + } + } + } +} + function as_node { #ARGS: {name: string, command: string} debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> @@ -829,6 +973,234 @@ function as_node { } } +IF (has_interactions) { + function as_interaction { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {name:'$(name)', command:'$(command)', uuid:'+MISSING_UUID+'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{name:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('name')%> + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + block zzz/as_interaction/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from storage <%temp_storage%> entry.data.interactions.$(name).uuid + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + $execute as $(uuid) run return run $(command) + IF (debug_mode) { + # If the entity with the provided UUID doesn't exist, the interaction wasn't found. + tellraw @a <%TELLRAW.INTERACTION_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function as_at_interaction { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {name:'$(name)', command:'$(command)', uuid:'+MISSING_UUID+'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{name:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('name')%> + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + block zzz/as_interaction/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from storage <%temp_storage%> entry.data.interactions.$(name).uuid + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + $execute as $(uuid) at @s run return run $(command) + IF (debug_mode) { + # If the entity with the provided UUID doesn't exist, the interaction wasn't found. + tellraw @a <%TELLRAW.INTERACTION_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function as_all_interactions { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {command:'$(command)'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + block zzz/as_all_interactions/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + data modify storage <%temp_storage%> args.uuid set from storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + + block execute_as_uuid { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + + $execute as $(uuid) run return run $(command) + } + + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE({text: interaction.storage_name, color: 'aqua'})%> + } + } + } + } + + function as_at_all_interactions { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {command:'$(command)'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + block zzz/as_all_interactions/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + data modify storage <%temp_storage%> args.uuid set from storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + + block execute_as_uuid { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + + $execute as $(uuid) at @s run return run $(command) + } + + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE({text: interaction.storage_name, color: 'aqua'})%> + } + } + } + } + + function at_interaction { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {name:'$(name)', command:'$(command)'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{name:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('name')%> + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + execute at @s run block zzz/at_interaction/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge from storage <%temp_storage%> entry.data.interactions.$(name) + + IF (debug_mode) { + execute unless data storage <%temp_storage%> args.px run return run tellraw @a <%TELLRAW.INTERACTION_NOT_FOUND()%> + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + + block execute_at_transform { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + + $execute \ + positioned ^$(px) ^$(py) ^$(pz) \ + rotated ~$(ry) ~$(rx) \ + run $(command) + } + + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function at_all_interactions { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {command:'$(command)'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + block zzz/at_all_interactions/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + data modify storage <%temp_storage%> args merge from storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%> + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + + block execute_at_transform { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + + $execute \ + positioned ^$(px) ^$(py) ^$(pz) \ + rotated ~$(ry) ~$(rx) \ + run $(command) + } + + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE({text: interaction.storage_name, color: 'aqua'})%> + } + } + } + } +} + IF (has_entity_locators) { function as_locator { #ARGS: {name: string, command: string} @@ -1147,8 +1519,9 @@ dir remove { if (on_remove_function) emit.mcb(on_remove_function) %%> + data_manager prep read + IF (has_entity_locators) { - data_manager prep read REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { IF (locator.config?.on_remove_function) { IF (locator.config.use_entity) { @@ -1175,6 +1548,18 @@ dir remove { } } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + IF (interaction.config?.on_remove_function) { + block as_interaction_<%interaction.storage_name%> { with storage <%temp_storage%> entry.data.uuids_by_name + $execute as $(<%interaction.storage_name%>) at @s run block interaction_<%interaction.storage_name%>_on_remove { + <%% + emit.mcb(interaction.config.on_remove_function) + %%> + } + } + } + } + function ./this/without_on_remove_function } @@ -1193,6 +1578,11 @@ dir remove { } } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + data modify storage <%temp_storage%> args.uuid set from storage <%temp_storage%> entry.data.uuids_by_name.<%interaction.storage_name%> + function animated_java:global/remove/entity_stack_by_uuid with storage <%temp_storage%> args + } + # Remove the rig using the more expensive & thorough method if the rig_hash doesn't match. execute \ unless data storage <%temp_storage%> {entry:{data:{rig_hash: '<%rig_hash%>'}}} \ @@ -1388,4 +1778,4 @@ function set_default_pose { data_manager prep read } function ./zzz/set_default_pose -} \ No newline at end of file +} diff --git a/src/systems/datapackCompiler/1.21.0/global.mcb b/src/systems/datapackCompiler/1.21.0/global.mcb index 318b3258..ce13c472 100644 --- a/src/systems/datapackCompiler/1.21.0/global.mcb +++ b/src/systems/datapackCompiler/1.21.0/global.mcb @@ -234,7 +234,7 @@ dir global { $data modify storage <%temp_storage%> args.uuid set from storage <%data_storage%> "$(id)".uuid scoreboard players set #success <%OBJECTIVES.I()%> 0 block { with storage <%temp_storage%> args - $execute store success score #success <%OBJECTIVES.I()%> if score $(uuid) matches 1.. + $execute store success score #success <%OBJECTIVES.I()%> if score $(uuid) <%OBJECTIVES.ID()%> matches 1.. $execute if score #success <%OBJECTIVES.I()%> matches 0 run data remove storage <%data_storage%> "$(id)" } } @@ -257,4 +257,78 @@ dir global { $data modify storage <%data_storage%> "$(id)" set from storage <%temp_storage%> entry } } + + dir interactions { + REPEAT (['interaction', 'attack']) as direction { + dir <%direction%> { + advancement trigger { + "criteria": { + "player_<%direction%>_clicked_interaction_entity": { + "trigger": "<%direction === 'interaction' ? 'minecraft:player_interacted_with_entity' : 'minecraft:player_hurt_entity'%>", + "conditions": { + "entity": { + "type": "minecraft:interaction" + } + } + } + }, + "rewards": { + "function": "<%context.namespace + ':' + context.path.join('/')%>/on" + } + } + + function on { + advancement revoke @s only <%context.namespace + ':' + context.path.join('/')%>/trigger + tag @s add <%TAGS.INTERACTING_PLAYER()%> + execute \ + as @e[type=interaction, tag=<%TAGS.GLOBAL_INTERACTION()%>, distance=..8] \ + if data entity @s <%direction%> \ + run function ./check + tag @s remove <%TAGS.INTERACTING_PLAYER()%> + } + + function check { + execute store result score #gametime <%OBJECTIVES.I()%> run time query gametime + execute \ + store result score #timestamp <%OBJECTIVES.I()%> \ + run data get entity @s <%direction%>.timestamp + + # Check that the interaction has a command to run, and + # if this interaction is from the player being processed. + scoreboard players set #check <%OBJECTIVES.I()%> 0 + execute \ + if score #timestamp <%OBJECTIVES.I()%> = #gametime <%OBJECTIVES.I()%> \ + if data entity @s data.animated_java.on_<%direction === 'interaction' ? 'interact' : 'attack'%>_function \ + store success score #check <%OBJECTIVES.I()%> \ + on <%direction === 'interaction' ? 'target' : 'attacker'%> \ + if entity @s[tag=<%TAGS.INTERACTING_PLAYER()%>] + + execute \ + if score #check <%OBJECTIVES.I()%> matches 1 \ + run function ./do + } + + function do { + scoreboard players set #check <%OBJECTIVES.I()%> 0 + block execute { with entity @s data.animated_java + $$(on_<%direction === 'interaction' ? 'interact' : 'attack'%>_function) + scoreboard players set #check <%OBJECTIVES.I()%> 1 + } + execute if score #check <%OBJECTIVES.I()%> matches 0 run tellraw @a [ \ + {text: '', color: red}, \ + {text:'ɪɴᴛᴇʀᴀᴄᴛɪᴏɴꜱ', color:'light_purple'}, \ + {text:'\n(<%global.root%>.mcb)', color:'dark_gray'}, \ + '\n→ ᴇʀʀᴏʀ:\n\sFailed to handle <%direction === 'interaction' ? 'right' : 'left'%> click interaction from ', \ + {selector: '@p[tag=<%TAGS.INTERACTING_PLAYER()%>]', color: gold}, \ + ':\n\s\sInvalid command: ', \ + {nbt: 'data.animated_java.on_<%direction === 'interaction' ? 'interact' : 'attack'%>_function', entity: '@s', color: aqua}, \ + '\n\s\sfrom ', \ + {text: 'data.animated_java.on_<%direction === 'interaction' ? 'interact' : 'attack'%>_function ', color: yellow}, \ + ] + # Only remove the interaction if it's consumed. + data remove entity @s <%direction%> + } + } + } + } } diff --git a/src/systems/datapackCompiler/1.21.0/main.mcb b/src/systems/datapackCompiler/1.21.0/main.mcb index 82d9014a..9eb1f89e 100644 --- a/src/systems/datapackCompiler/1.21.0/main.mcb +++ b/src/systems/datapackCompiler/1.21.0/main.mcb @@ -67,7 +67,7 @@ dir root { } IF (auto_update_rig_orientation) { - IF (has_locators || has_cameras) { + IF (has_locators || has_cameras || has_interactions) { function <%blueprint_id%>/root/on_tick/transform_floating_entities } execute on passengers run tp @s ~ ~ ~ ~ ~ @@ -75,6 +75,19 @@ dir root { function <%blueprint_id%>/root/on_tick/transform_locators } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction' && node.config?.on_tick_function)) as interaction { + block tick_interactions { with storage <%temp_storage%> entry.data.uuids_by_name + $execute \ + as $(<%interaction.storage_name%>) \ + at @s \ + run block <%interaction.storage_name%> { + <%% + emit.mcb(interaction.config.on_tick_function) + %%> + } + } + } + # Custom post-tick function IF (on_post_tick_function) { <%% @@ -83,7 +96,7 @@ dir root { } } - IF (has_locators || has_cameras) { + IF (has_locators || has_cameras || has_interactions) { dir on_tick { function transform_locators { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { @@ -118,6 +131,24 @@ dir root { } } } + + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + block select_interaction_<%interaction.storage_name%> { with storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%> + $execute \ + as $(uuid) \ + positioned ^$(px) ^$(py) ^$(pz) \ + rotated ~$(ry) ~$(rx) \ + run block as_interaction_<%interaction.storage_name%> { + tp @s ~ ~ ~ ~ ~ + + IF (interaction.config?.on_tick_function) { + <%% + emit.mcb(interaction.config.on_tick_function) + %%> + } + } + } + } } function transform_floating_entities { @@ -143,8 +174,8 @@ IF (!auto_update_rig_orientation) { tp @s ~ ~ ~ ~ ~ + data_manager prep read IF (has_locators || has_cameras) { - data_manager prep read function <%blueprint_id%>/root/on_tick/transform_floating_entities } execute at @s on passengers run tp @s ~ ~ ~ ~ ~ @@ -162,6 +193,19 @@ IF (has_animations) { function play { debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + data_manager prep read + + tag @s add <%TAGS.ANIMATION_PLAYING(blueprint_id, animation.storage_name)%> + scoreboard players set @s <%OBJECTIVES.FRAME(animation.storage_name)%> 0 + tag @s add <%TAGS.TRANSFORMS_ONLY()%> + execute at @s run function ./zzz/set_frame {frame: 0} + tag @s remove <%TAGS.TRANSFORMS_ONLY()%> + } + + function play_exclusive { + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data_manager prep read function <%blueprint_id%>/animations/pause_all tag @s add <%TAGS.ANIMATION_PLAYING(blueprint_id, animation.storage_name)%> @@ -174,6 +218,7 @@ IF (has_animations) { function stop { debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + data_manager prep read function <%blueprint_id%>/animations/pause_all scoreboard players set @s <%OBJECTIVES.FRAME(animation.storage_name)%> 0 @@ -197,6 +242,7 @@ IF (has_animations) { function next_frame { debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + data_manager prep read execute if score @s <%OBJECTIVES.FRAME(animation.storage_name)%> matches <%animation.duration%>.. run scoreboard players set @s <%OBJECTIVES.FRAME(animation.storage_name)%> 1 data remove storage <%temp_storage%> args execute store result storage <%temp_storage%> args.frame int 1 run scoreboard players get @s <%OBJECTIVES.FRAME(animation.storage_name)%> @@ -210,7 +256,6 @@ IF (has_animations) { debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> data_manager prep read - data remove storage <%temp_storage%> args $execute store result storage <%temp_storage%> args.frame int 1 run scoreboard players set @s <%OBJECTIVES.FRAME(animation.storage_name)%> $(frame) execute at @s run function ./zzz/set_frame with storage <%temp_storage%> args @@ -232,6 +277,7 @@ IF (has_animations) { #ARGS: {duration: int, to_frame: int} debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + data_manager prep read function <%blueprint_id%>/animations/pause_all tag @s add <%TAGS.ANIMATION_PLAYING(blueprint_id, animation.storage_name)%> @@ -358,7 +404,6 @@ IF (has_animations) { global.modified_effect_nodes = Object.values(animation.modified_nodes).filter( node => node.type === 'locator' && global.frame.node_transforms[node.uuid] ) - console.log(global.modified_effect_nodes) %%> IF(Object.keys(global.modified_effect_nodes).length > 0) { function last_frame_effects { @@ -414,7 +459,7 @@ IF (has_animations) { const lastActiveFrame = {} const modifiedNodes = Object.values(animation.modified_nodes).filter(n => n.type !== 'struct').sort(nodeSorter); for (const [frameIndex, frame] of animation.frames.entries()) { - const to_merge = {cameras: {}, locators: {}} + const to_merge = {cameras: {}, locators: {}, interactions: {}} let frameFunc = ``; for (const node of modifiedNodes) { const transform = frame.node_transforms[node.uuid] @@ -485,6 +530,32 @@ IF (has_animations) { } break } + case 'interaction': { + const lastFrame = lastActiveFrame[node.uuid] + lastActiveFrame[node.uuid] = transform + ;if (!lastFrame || matrixToNbtFloatArray(transform.matrix).toString() !== matrixToNbtFloatArray(lastFrame.matrix).toString()) { + to_merge.interactions[node.storage_name] = { + px: roundTo(transform.pos[0], 10), + py: roundTo(transform.pos[1], 10), + pz: roundTo(transform.pos[2], 10), + ry: roundTo(transform.head_rot[1], 10), + rx: roundTo(transform.head_rot[0], 10) + }; + } + + if (transform.function) { + frameFunc += + `\n$execute unless entity @s[tag=${TAGS.TRANSFORMS_ONLY()}] as $(${node.storage_name}) ` + + `positioned ^${roundTo(transform.pos[0], 10)} ^${roundTo(transform.pos[1], 10)} ^${roundTo(transform.pos[2], 10)} ` + + `rotated ~${roundTo(transform.head_rot[1], 10)} ~${roundTo(transform.head_rot[0], 10)} ` + + `${transform.function_execute_condition ? transform.function_execute_condition + ' ' : ''}run ` + + `block ${frameIndex}_interaction_${node.storage_name}%NEWLINE_PATCH%{\n` + + `tp @s ~ ~ ~ ~ ~\n` + + `${transform.function}` + + `\n}` + } + break + } case 'camera': { const lastFrame = lastActiveFrame[node.uuid] lastActiveFrame[node.uuid] = transform @@ -502,9 +573,17 @@ IF (has_animations) { } } - if (Object.keys(to_merge.locators).length > 0 || Object.keys(to_merge.cameras).length > 0) { - frameFunc += `\ndata modify storage <%temp_storage%> entry.data merge value ${JSON.stringify(to_merge)}` - frameFunc += `\ndata_manager write` + const updatedLocators = Object.keys(to_merge.locators).length > 0; + const updatedCameras = Object.keys(to_merge.cameras).length > 0; + const updatedInteractions = Object.keys(to_merge.interactions).length > 0; + + if (!updatedCameras) delete to_merge.cameras; + if (!updatedLocators) delete to_merge.locators; + if (!updatedInteractions) delete to_merge.interactions; + + if (updatedLocators || updatedCameras || updatedInteractions) { + frameFunc += `\ndata modify storage ${temp_storage} entry.data merge value ${JSON.stringify(to_merge)}` + frameFunc += `\ndata_manager prep write` if (!auto_update_rig_orientation) { frameFunc += `\nfunction ${blueprint_id}/root/on_tick/transform_floating_entities` } @@ -589,6 +668,42 @@ function summon { # Align the position and rotation of the root with the command context. tp @s ~ ~ ~ ~ ~ + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + summon minecraft:interaction \ + ^<%roundTo(interaction.default_transform.pos[0], 10)%> \ + ^<%roundTo(interaction.default_transform.pos[1], 10)%> \ + ^<%roundTo(interaction.default_transform.pos[2], 10)%> \ + { \ + Tags:<%getNodeTags(interaction, rig)%>, \ + response: <%interaction.config.response ?? false%>, \ + width: <%interaction.width%>f, \ + height: <%interaction.height%>f, \ + } + execute \ + as @n[ \ + type=minecraft:interaction, \ + tag=<%TAGS.PROJECT_INTERACTION_NAMED(blueprint_id, interaction.storage_name)%>, \ + tag=<%TAGS.NEW()%>, \ + distance=..<%Math.ceil(interaction.max_distance + 0.5)%> \ + ] \ + run block as_interaction/<%interaction.storage_name%> { + # run block ../as_interaction/<%interaction.storage_name%> { + tag @s remove <%TAGS.NEW()%> + data modify entity @s CustomName set value '<%ENTITY_NAMES.NODE(blueprint_id, interaction.type, interaction.storage_name)%>' + function animated_java:global/gu/get_entity_uuid_string + scoreboard players operation @s <%OBJECTIVES.ID()%> = aj.last_id <%OBJECTIVES.ID()%> + IF (interaction.config?.on_interact_function) { + data modify entity @s data.animated_java.on_interact_function set value "function <%blueprint_id%>/interactions/<%interaction.storage_name%>_on_interact" + } + IF (interaction.config?.on_attack_function) { + data modify entity @s data.animated_java.on_attack_function set value "function <%blueprint_id%>/interactions/<%interaction.storage_name%>_on_attack" + } + } + data modify storage <%temp_storage%> entry.data.uuids append from storage <%gu_storage%> out + data modify storage <%temp_storage%> entry.data.uuids_by_name.<%interaction.storage_name%> set from storage <%gu_storage%> out + data modify storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%>.uuid set from storage <%gu_storage%> out + } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { summon <%locator.config.entity_type%> \ ^<%roundTo(locator.default_transform.pos[0], 10)%> \ @@ -771,6 +886,16 @@ function summon { } } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction' && node.config.on_summon_function)) as interaction { + block { with storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%> + $execute at @s as $(uuid) at @s run block on_summon/custom_<%interaction.type + '_' + interaction.storage_name%> { + <%% + emit.mcb(interaction.config.on_summon_function) + %%> + } + } + } + REPEAT (Object.values(rig.nodes).filter(node => BONE_TYPES.includes(node.type) && node.on_summon_function?.trim())) as node { execute \ on passengers if entity @s[tag=<%TAGS.PROJECT_NODE_NAMED(blueprint_id, node.storage_name)%>] \ @@ -795,6 +920,25 @@ function summon { } } +dir interactions { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + IF (interaction.config.on_interact_function) { + function <%interaction.storage_name%>_on_interact { + <%% + emit.mcb(interaction.config.on_interact_function) + %%> + } + } + IF (interaction.config.on_attack_function) { + function <%interaction.storage_name%>_on_attack { + <%% + emit.mcb(interaction.config.on_attack_function) + %%> + } + } + } +} + function as_node { #ARGS: {name: string, command: string} debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> @@ -830,6 +974,234 @@ function as_node { } } +IF (has_interactions) { + function as_interaction { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {name:'$(name)', command:'$(command)', uuid:'+MISSING_UUID+'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{name:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('name')%> + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + block zzz/as_interaction/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from storage <%temp_storage%> entry.data.interactions.$(name).uuid + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + $execute as $(uuid) run return run $(command) + IF (debug_mode) { + # If the entity with the provided UUID doesn't exist, the interaction wasn't found. + tellraw @a <%TELLRAW.INTERACTION_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function as_at_interaction { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {name:'$(name)', command:'$(command)', uuid:'+MISSING_UUID+'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{name:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('name')%> + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + block zzz/as_interaction/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from storage <%temp_storage%> entry.data.interactions.$(name).uuid + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + $execute as $(uuid) at @s run return run $(command) + IF (debug_mode) { + # If the entity with the provided UUID doesn't exist, the interaction wasn't found. + tellraw @a <%TELLRAW.INTERACTION_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function as_all_interactions { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {command:'$(command)'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + block zzz/as_all_interactions/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + data modify storage <%temp_storage%> args.uuid set from storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + + block execute_as_uuid { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + + $execute as $(uuid) run return run $(command) + } + + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE({text: interaction.storage_name, color: 'aqua'})%> + } + } + } + } + + function as_at_all_interactions { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {command:'$(command)'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + block zzz/as_all_interactions/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + data modify storage <%temp_storage%> args.uuid set from storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + + block execute_as_uuid { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + + $execute as $(uuid) at @s run return run $(command) + } + + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE({text: interaction.storage_name, color: 'aqua'})%> + } + } + } + } + + function at_interaction { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {name:'$(name)', command:'$(command)'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{name:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('name')%> + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + execute at @s run block zzz/at_interaction/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge from storage <%temp_storage%> entry.data.interactions.$(name) + + IF (debug_mode) { + execute unless data storage <%temp_storage%> args.px run return run tellraw @a <%TELLRAW.INTERACTION_NOT_FOUND()%> + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + + block execute_at_transform { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + + $execute \ + positioned ^$(px) ^$(py) ^$(pz) \ + rotated ~$(ry) ~$(rx) \ + run $(command) + } + + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function at_all_interactions { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {command:'$(command)'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + block zzz/at_all_interactions/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + data modify storage <%temp_storage%> args merge from storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%> + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + + block execute_at_transform { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + + $execute \ + positioned ^$(px) ^$(py) ^$(pz) \ + rotated ~$(ry) ~$(rx) \ + run $(command) + } + + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE({text: interaction.storage_name, color: 'aqua'})%> + } + } + } + } +} + IF (has_entity_locators) { function as_locator { #ARGS: {name: string, command: string} @@ -1148,8 +1520,9 @@ dir remove { if (on_remove_function) emit.mcb(on_remove_function) %%> + data_manager prep read + IF (has_entity_locators) { - data_manager prep read REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { IF (locator.config?.on_remove_function) { IF (locator.config.use_entity) { @@ -1176,6 +1549,18 @@ dir remove { } } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + IF (interaction.config?.on_remove_function) { + block as_interaction_<%interaction.storage_name%> { with storage <%temp_storage%> entry.data.uuids_by_name + $execute as $(<%interaction.storage_name%>) at @s run block interaction_<%interaction.storage_name%>_on_remove { + <%% + emit.mcb(interaction.config.on_remove_function) + %%> + } + } + } + } + function ./this/without_on_remove_function } @@ -1194,6 +1579,11 @@ dir remove { } } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + data modify storage <%temp_storage%> args.uuid set from storage <%temp_storage%> entry.data.uuids_by_name.<%interaction.storage_name%> + function animated_java:global/remove/entity_stack_by_uuid with storage <%temp_storage%> args + } + # Remove the rig using the more expensive & thorough method if the rig_hash doesn't match. execute \ unless data storage <%temp_storage%> {entry:{data:{rig_hash: '<%rig_hash%>'}}} \ @@ -1389,4 +1779,4 @@ function set_default_pose { data_manager prep read } function ./zzz/set_default_pose -} \ No newline at end of file +} diff --git a/src/systems/datapackCompiler/1.21.2/main.mcb b/src/systems/datapackCompiler/1.21.2/main.mcb index 9aacd4ac..2b4e92c8 100644 --- a/src/systems/datapackCompiler/1.21.2/main.mcb +++ b/src/systems/datapackCompiler/1.21.2/main.mcb @@ -66,7 +66,7 @@ dir root { } IF (auto_update_rig_orientation) { - IF (has_locators || has_cameras) { + IF (has_locators || has_cameras || has_interactions) { function <%blueprint_id%>/root/on_tick/transform_floating_entities } execute on passengers run rotate @s ~ ~ @@ -74,6 +74,19 @@ dir root { function <%blueprint_id%>/root/on_tick/transform_locators } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction' && node.config?.on_tick_function)) as interaction { + block tick_interactions { with storage <%temp_storage%> entry.data.uuids_by_name + $execute \ + as $(<%interaction.storage_name%>) \ + at @s \ + run block <%interaction.storage_name%> { + <%% + emit.mcb(interaction.config.on_tick_function) + %%> + } + } + } + # Custom post-tick function IF (on_post_tick_function) { <%% @@ -82,7 +95,7 @@ dir root { } } - IF (has_locators || has_cameras) { + IF (has_locators || has_cameras || has_interactions) { dir on_tick { function transform_locators { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { @@ -117,6 +130,24 @@ dir root { } } } + + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + block select_interaction_<%interaction.storage_name%> { with storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%> + $execute \ + as $(uuid) \ + positioned ^$(px) ^$(py) ^$(pz) \ + rotated ~$(ry) ~$(rx) \ + run block as_interaction_<%interaction.storage_name%> { + tp @s ~ ~ ~ ~ ~ + + IF (interaction.config?.on_tick_function) { + <%% + emit.mcb(interaction.config.on_tick_function) + %%> + } + } + } + } } function transform_floating_entities { @@ -142,8 +173,8 @@ IF (!auto_update_rig_orientation) { tp @s ~ ~ ~ ~ ~ + data_manager prep read IF (has_locators || has_cameras) { - data_manager prep read function <%blueprint_id%>/root/on_tick/transform_floating_entities } execute at @s on passengers run rotate @s ~ ~ @@ -161,6 +192,19 @@ IF (has_animations) { function play { debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + data_manager prep read + + tag @s add <%TAGS.ANIMATION_PLAYING(blueprint_id, animation.storage_name)%> + scoreboard players set @s <%OBJECTIVES.FRAME(animation.storage_name)%> 0 + tag @s add <%TAGS.TRANSFORMS_ONLY()%> + execute at @s run function ./zzz/set_frame {frame: 0} + tag @s remove <%TAGS.TRANSFORMS_ONLY()%> + } + + function play_exclusive { + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data_manager prep read function <%blueprint_id%>/animations/pause_all tag @s add <%TAGS.ANIMATION_PLAYING(blueprint_id, animation.storage_name)%> @@ -173,6 +217,7 @@ IF (has_animations) { function stop { debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + data_manager prep read function <%blueprint_id%>/animations/pause_all scoreboard players set @s <%OBJECTIVES.FRAME(animation.storage_name)%> 0 @@ -196,6 +241,7 @@ IF (has_animations) { function next_frame { debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + data_manager prep read execute if score @s <%OBJECTIVES.FRAME(animation.storage_name)%> matches <%animation.duration%>.. run scoreboard players set @s <%OBJECTIVES.FRAME(animation.storage_name)%> 1 data remove storage <%temp_storage%> args execute store result storage <%temp_storage%> args.frame int 1 run scoreboard players get @s <%OBJECTIVES.FRAME(animation.storage_name)%> @@ -209,7 +255,6 @@ IF (has_animations) { debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> data_manager prep read - data remove storage <%temp_storage%> args $execute store result storage <%temp_storage%> args.frame int 1 run scoreboard players set @s <%OBJECTIVES.FRAME(animation.storage_name)%> $(frame) execute at @s run function ./zzz/set_frame with storage <%temp_storage%> args @@ -231,6 +276,7 @@ IF (has_animations) { #ARGS: {duration: int, to_frame: int} debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + data_manager prep read function <%blueprint_id%>/animations/pause_all tag @s add <%TAGS.ANIMATION_PLAYING(blueprint_id, animation.storage_name)%> @@ -357,7 +403,6 @@ IF (has_animations) { global.modified_effect_nodes = Object.values(animation.modified_nodes).filter( node => node.type === 'locator' && global.frame.node_transforms[node.uuid] ) - console.log(global.modified_effect_nodes) %%> IF(Object.keys(global.modified_effect_nodes).length > 0) { function last_frame_effects { @@ -413,7 +458,7 @@ IF (has_animations) { const lastActiveFrame = {} const modifiedNodes = Object.values(animation.modified_nodes).filter(n => n.type !== 'struct').sort(nodeSorter); for (const [frameIndex, frame] of animation.frames.entries()) { - const to_merge = {cameras: {}, locators: {}} + const to_merge = {cameras: {}, locators: {}, interactions: {}} let frameFunc = ``; for (const node of modifiedNodes) { const transform = frame.node_transforms[node.uuid] @@ -484,6 +529,32 @@ IF (has_animations) { } break } + case 'interaction': { + const lastFrame = lastActiveFrame[node.uuid] + lastActiveFrame[node.uuid] = transform + ;if (!lastFrame || matrixToNbtFloatArray(transform.matrix).toString() !== matrixToNbtFloatArray(lastFrame.matrix).toString()) { + to_merge.interactions[node.storage_name] = { + px: roundTo(transform.pos[0], 10), + py: roundTo(transform.pos[1], 10), + pz: roundTo(transform.pos[2], 10), + ry: roundTo(transform.head_rot[1], 10), + rx: roundTo(transform.head_rot[0], 10) + }; + } + + if (transform.function) { + frameFunc += + `\n$execute unless entity @s[tag=${TAGS.TRANSFORMS_ONLY()}] as $(${node.storage_name}) ` + + `positioned ^${roundTo(transform.pos[0], 10)} ^${roundTo(transform.pos[1], 10)} ^${roundTo(transform.pos[2], 10)} ` + + `rotated ~${roundTo(transform.head_rot[1], 10)} ~${roundTo(transform.head_rot[0], 10)} ` + + `${transform.function_execute_condition ? transform.function_execute_condition + ' ' : ''}run ` + + `block ${frameIndex}_interaction_${node.storage_name}%NEWLINE_PATCH%{\n` + + `tp @s ~ ~ ~ ~ ~\n` + + `${transform.function}` + + `\n}` + } + break + } case 'camera': { const lastFrame = lastActiveFrame[node.uuid] lastActiveFrame[node.uuid] = transform @@ -501,9 +572,17 @@ IF (has_animations) { } } - if (Object.keys(to_merge.locators).length > 0 || Object.keys(to_merge.cameras).length > 0) { - frameFunc += `\ndata modify storage <%temp_storage%> entry.data merge value ${JSON.stringify(to_merge)}` - frameFunc += `\ndata_manager write` + const updatedLocators = Object.keys(to_merge.locators).length > 0; + const updatedCameras = Object.keys(to_merge.cameras).length > 0; + const updatedInteractions = Object.keys(to_merge.interactions).length > 0; + + if (!updatedCameras) delete to_merge.cameras; + if (!updatedLocators) delete to_merge.locators; + if (!updatedInteractions) delete to_merge.interactions; + + if (updatedLocators || updatedCameras || updatedInteractions) { + frameFunc += `\ndata modify storage ${temp_storage} entry.data merge value ${JSON.stringify(to_merge)}` + frameFunc += `\ndata_manager prep write` if (!auto_update_rig_orientation) { frameFunc += `\nfunction ${blueprint_id}/root/on_tick/transform_floating_entities` } @@ -588,6 +667,42 @@ function summon { # Align the position and rotation of the root with the command context. tp @s ~ ~ ~ ~ ~ + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + summon minecraft:interaction \ + ^<%roundTo(interaction.default_transform.pos[0], 10)%> \ + ^<%roundTo(interaction.default_transform.pos[1], 10)%> \ + ^<%roundTo(interaction.default_transform.pos[2], 10)%> \ + { \ + Tags:<%getNodeTags(interaction, rig)%>, \ + response: <%interaction.config.response ?? false%>, \ + width: <%interaction.width%>f, \ + height: <%interaction.height%>f, \ + } + execute \ + as @n[ \ + type=minecraft:interaction, \ + tag=<%TAGS.PROJECT_INTERACTION_NAMED(blueprint_id, interaction.storage_name)%>, \ + tag=<%TAGS.NEW()%>, \ + distance=..<%Math.ceil(interaction.max_distance + 0.5)%> \ + ] \ + run block as_interaction/<%interaction.storage_name%> { + # run block ../as_interaction/<%interaction.storage_name%> { + tag @s remove <%TAGS.NEW()%> + data modify entity @s CustomName set value '<%ENTITY_NAMES.NODE(blueprint_id, interaction.type, interaction.storage_name)%>' + function animated_java:global/gu/get_entity_uuid_string + scoreboard players operation @s <%OBJECTIVES.ID()%> = aj.last_id <%OBJECTIVES.ID()%> + IF (interaction.config?.on_interact_function) { + data modify entity @s data.animated_java.on_interact_function set value "function <%blueprint_id%>/interactions/<%interaction.storage_name%>_on_interact" + } + IF (interaction.config?.on_attack_function) { + data modify entity @s data.animated_java.on_attack_function set value "function <%blueprint_id%>/interactions/<%interaction.storage_name%>_on_attack" + } + } + data modify storage <%temp_storage%> entry.data.uuids append from storage <%gu_storage%> out + data modify storage <%temp_storage%> entry.data.uuids_by_name.<%interaction.storage_name%> set from storage <%gu_storage%> out + data modify storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%>.uuid set from storage <%gu_storage%> out + } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { summon <%locator.config.entity_type%> \ ^<%roundTo(locator.default_transform.pos[0], 10)%> \ @@ -770,6 +885,16 @@ function summon { } } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction' && node.config.on_summon_function)) as interaction { + block { with storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%> + $execute at @s as $(uuid) at @s run block on_summon/custom_<%interaction.type + '_' + interaction.storage_name%> { + <%% + emit.mcb(interaction.config.on_summon_function) + %%> + } + } + } + REPEAT (Object.values(rig.nodes).filter(node => BONE_TYPES.includes(node.type) && node.on_summon_function?.trim())) as node { execute \ on passengers if entity @s[tag=<%TAGS.PROJECT_NODE_NAMED(blueprint_id, node.storage_name)%>] \ @@ -794,6 +919,25 @@ function summon { } } +dir interactions { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + IF (interaction.config.on_interact_function) { + function <%interaction.storage_name%>_on_interact { + <%% + emit.mcb(interaction.config.on_interact_function) + %%> + } + } + IF (interaction.config.on_attack_function) { + function <%interaction.storage_name%>_on_attack { + <%% + emit.mcb(interaction.config.on_attack_function) + %%> + } + } + } +} + function as_node { #ARGS: {name: string, command: string} debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> @@ -829,6 +973,234 @@ function as_node { } } +IF (has_interactions) { + function as_interaction { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {name:'$(name)', command:'$(command)', uuid:'+MISSING_UUID+'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{name:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('name')%> + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + block zzz/as_interaction/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from storage <%temp_storage%> entry.data.interactions.$(name).uuid + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + $execute as $(uuid) run return run $(command) + IF (debug_mode) { + # If the entity with the provided UUID doesn't exist, the interaction wasn't found. + tellraw @a <%TELLRAW.INTERACTION_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function as_at_interaction { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {name:'$(name)', command:'$(command)', uuid:'+MISSING_UUID+'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{name:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('name')%> + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + block zzz/as_interaction/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from storage <%temp_storage%> entry.data.interactions.$(name).uuid + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + $execute as $(uuid) at @s run return run $(command) + IF (debug_mode) { + # If the entity with the provided UUID doesn't exist, the interaction wasn't found. + tellraw @a <%TELLRAW.INTERACTION_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function as_all_interactions { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {command:'$(command)'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + block zzz/as_all_interactions/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + data modify storage <%temp_storage%> args.uuid set from storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + + block execute_as_uuid { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + + $execute as $(uuid) run return run $(command) + } + + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE({text: interaction.storage_name, color: 'aqua'})%> + } + } + } + } + + function as_at_all_interactions { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {command:'$(command)'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + block zzz/as_all_interactions/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + data modify storage <%temp_storage%> args.uuid set from storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + + block execute_as_uuid { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + + $execute as $(uuid) at @s run return run $(command) + } + + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE({text: interaction.storage_name, color: 'aqua'})%> + } + } + } + } + + function at_interaction { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {name:'$(name)', command:'$(command)'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{name:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('name')%> + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + execute at @s run block zzz/at_interaction/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge from storage <%temp_storage%> entry.data.interactions.$(name) + + IF (debug_mode) { + execute unless data storage <%temp_storage%> args.px run return run tellraw @a <%TELLRAW.INTERACTION_NOT_FOUND()%> + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + + block execute_at_transform { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + + $execute \ + positioned ^$(px) ^$(py) ^$(pz) \ + rotated ~$(ry) ~$(rx) \ + run $(command) + } + + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function at_all_interactions { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {command:'$(command)'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + block zzz/at_all_interactions/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + data modify storage <%temp_storage%> args merge from storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%> + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + + block execute_at_transform { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + + $execute \ + positioned ^$(px) ^$(py) ^$(pz) \ + rotated ~$(ry) ~$(rx) \ + run $(command) + } + + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE({text: interaction.storage_name, color: 'aqua'})%> + } + } + } + } +} + IF (has_entity_locators) { function as_locator { #ARGS: {name: string, command: string} @@ -1147,8 +1519,9 @@ dir remove { if (on_remove_function) emit.mcb(on_remove_function) %%> + data_manager prep read + IF (has_entity_locators) { - data_manager prep read REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { IF (locator.config?.on_remove_function) { IF (locator.config.use_entity) { @@ -1175,6 +1548,18 @@ dir remove { } } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + IF (interaction.config?.on_remove_function) { + block as_interaction_<%interaction.storage_name%> { with storage <%temp_storage%> entry.data.uuids_by_name + $execute as $(<%interaction.storage_name%>) at @s run block interaction_<%interaction.storage_name%>_on_remove { + <%% + emit.mcb(interaction.config.on_remove_function) + %%> + } + } + } + } + function ./this/without_on_remove_function } @@ -1193,6 +1578,11 @@ dir remove { } } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + data modify storage <%temp_storage%> args.uuid set from storage <%temp_storage%> entry.data.uuids_by_name.<%interaction.storage_name%> + function animated_java:global/remove/entity_stack_by_uuid with storage <%temp_storage%> args + } + # Remove the rig using the more expensive & thorough method if the rig_hash doesn't match. execute \ unless data storage <%temp_storage%> {entry:{data:{rig_hash: '<%rig_hash%>'}}} \ @@ -1388,4 +1778,4 @@ function set_default_pose { data_manager prep read } function ./zzz/set_default_pose -} \ No newline at end of file +} diff --git a/src/systems/datapackCompiler/1.21.4/main.mcb b/src/systems/datapackCompiler/1.21.4/main.mcb index 2b14954f..84795994 100644 --- a/src/systems/datapackCompiler/1.21.4/main.mcb +++ b/src/systems/datapackCompiler/1.21.4/main.mcb @@ -67,7 +67,7 @@ dir root { } IF (auto_update_rig_orientation) { - IF (has_locators || has_cameras) { + IF (has_locators || has_cameras || has_interactions) { function <%blueprint_id%>/root/on_tick/transform_floating_entities } IF (use_entity_stacking) { @@ -90,6 +90,19 @@ dir root { function <%blueprint_id%>/root/on_tick/transform_locators } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction' && node.config?.on_tick_function)) as interaction { + block tick_interactions { with storage <%temp_storage%> entry.data.uuids_by_name + $execute \ + as $(<%interaction.storage_name%>) \ + at @s \ + run block <%interaction.storage_name%> { + <%% + emit.mcb(interaction.config.on_tick_function) + %%> + } + } + } + # Custom post-tick function IF (on_post_tick_function) { <%% @@ -98,7 +111,7 @@ dir root { } } - IF (has_locators || has_cameras) { + IF (has_locators || has_cameras || has_interactions) { dir on_tick { function transform_locators { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { @@ -143,6 +156,24 @@ dir root { } } } + + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + block select_interaction_<%interaction.storage_name%> { with storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%> + $execute \ + as $(uuid) \ + positioned ^$(px) ^$(py) ^$(pz) \ + rotated ~$(ry) ~$(rx) \ + run block as_interaction_<%interaction.storage_name%> { + tp @s ~ ~ ~ ~ ~ + + IF (interaction.config?.on_tick_function) { + <%% + emit.mcb(interaction.config.on_tick_function) + %%> + } + } + } + } } function transform_floating_entities { @@ -209,6 +240,19 @@ IF (has_animations) { function play { debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + data_manager prep read + + tag @s add <%TAGS.ANIMATION_PLAYING(blueprint_id, animation.storage_name)%> + scoreboard players set @s <%OBJECTIVES.FRAME(animation.storage_name)%> 0 + tag @s add <%TAGS.TRANSFORMS_ONLY()%> + execute at @s run function ./zzz/set_frame {frame: 0} + tag @s remove <%TAGS.TRANSFORMS_ONLY()%> + } + + function play_exclusive { + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data_manager prep read function <%blueprint_id%>/animations/pause_all tag @s add <%TAGS.ANIMATION_PLAYING(blueprint_id, animation.storage_name)%> @@ -221,6 +265,7 @@ IF (has_animations) { function stop { debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + data_manager prep read function <%blueprint_id%>/animations/pause_all scoreboard players set @s <%OBJECTIVES.FRAME(animation.storage_name)%> 0 @@ -244,6 +289,7 @@ IF (has_animations) { function next_frame { debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + data_manager prep read execute if score @s <%OBJECTIVES.FRAME(animation.storage_name)%> matches <%animation.duration%>.. run scoreboard players set @s <%OBJECTIVES.FRAME(animation.storage_name)%> 1 data remove storage <%temp_storage%> args execute store result storage <%temp_storage%> args.frame int 1 run scoreboard players get @s <%OBJECTIVES.FRAME(animation.storage_name)%> @@ -257,7 +303,6 @@ IF (has_animations) { debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> data_manager prep read - data remove storage <%temp_storage%> args $execute store result storage <%temp_storage%> args.frame int 1 run scoreboard players set @s <%OBJECTIVES.FRAME(animation.storage_name)%> $(frame) execute at @s run function ./zzz/set_frame with storage <%temp_storage%> args @@ -279,6 +324,7 @@ IF (has_animations) { #ARGS: {duration: int, to_frame: int} debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + data_manager prep read function <%blueprint_id%>/animations/pause_all tag @s add <%TAGS.ANIMATION_PLAYING(blueprint_id, animation.storage_name)%> @@ -294,7 +340,6 @@ IF (has_animations) { IF (use_entity_stacking) { execute on passengers store result entity @s interpolation_duration int 1 run scoreboard players get #this <%OBJECTIVES.I()%> } ELSE { - data_manager prep read block { with storage <%temp_storage%> entry.data.uuids_by_name REPEAT (Object.values(rig.nodes).filter(node => BONE_TYPES.includes(node.type))) as node { $execute \ @@ -446,7 +491,6 @@ IF (has_animations) { global.modified_effect_nodes = Object.values(animation.modified_nodes).filter( node => node.type === 'locator' && global.frame.node_transforms[node.uuid] ) - console.log(global.modified_effect_nodes) %%> IF(Object.keys(global.modified_effect_nodes).length > 0) { function last_frame_effects { @@ -502,7 +546,7 @@ IF (has_animations) { const lastActiveFrame = {} const modifiedNodes = Object.values(animation.modified_nodes).filter(n => n.type !== 'struct').sort(nodeSorter); for (const [frameIndex, frame] of animation.frames.entries()) { - const to_merge = {cameras: {}, locators: {}} + const to_merge = {cameras: {}, locators: {}, interactions: {}} let frameFunc = ``; for (const node of modifiedNodes) { const transform = frame.node_transforms[node.uuid] @@ -573,6 +617,32 @@ IF (has_animations) { } break } + case 'interaction': { + const lastFrame = lastActiveFrame[node.uuid] + lastActiveFrame[node.uuid] = transform + ;if (!lastFrame || matrixToNbtFloatArray(transform.matrix).toString() !== matrixToNbtFloatArray(lastFrame.matrix).toString()) { + to_merge.interactions[node.storage_name] = { + px: roundTo(transform.pos[0], 10), + py: roundTo(transform.pos[1], 10), + pz: roundTo(transform.pos[2], 10), + ry: roundTo(transform.head_rot[1], 10), + rx: roundTo(transform.head_rot[0], 10) + }; + } + + if (transform.function) { + frameFunc += + `\n$execute unless entity @s[tag=${TAGS.TRANSFORMS_ONLY()}] as $(${node.storage_name}) ` + + `positioned ^${roundTo(transform.pos[0], 10)} ^${roundTo(transform.pos[1], 10)} ^${roundTo(transform.pos[2], 10)} ` + + `rotated ~${roundTo(transform.head_rot[1], 10)} ~${roundTo(transform.head_rot[0], 10)} ` + + `${transform.function_execute_condition ? transform.function_execute_condition + ' ' : ''}run ` + + `block ${frameIndex}_interaction_${node.storage_name}%NEWLINE_PATCH%{\n` + + `tp @s ~ ~ ~ ~ ~\n` + + `${transform.function}` + + `\n}` + } + break + } case 'camera': { const lastFrame = lastActiveFrame[node.uuid] lastActiveFrame[node.uuid] = transform @@ -590,9 +660,17 @@ IF (has_animations) { } } - if (Object.keys(to_merge.locators).length > 0 || Object.keys(to_merge.cameras).length > 0) { - frameFunc += `\ndata modify storage <%temp_storage%> entry.data merge value ${JSON.stringify(to_merge)}` - frameFunc += `\ndata_manager write` + const updatedLocators = Object.keys(to_merge.locators).length > 0; + const updatedCameras = Object.keys(to_merge.cameras).length > 0; + const updatedInteractions = Object.keys(to_merge.interactions).length > 0; + + if (!updatedCameras) delete to_merge.cameras; + if (!updatedLocators) delete to_merge.locators; + if (!updatedInteractions) delete to_merge.interactions; + + if (updatedLocators || updatedCameras || updatedInteractions) { + frameFunc += `\ndata modify storage ${temp_storage} entry.data merge value ${JSON.stringify(to_merge)}` + frameFunc += `\ndata_manager prep write` if (!auto_update_rig_orientation) { frameFunc += `\nfunction ${blueprint_id}/root/on_tick/transform_floating_entities` } @@ -677,6 +755,42 @@ function summon { # Align the position and rotation of the root with the command context. tp @s ~ ~ ~ ~ ~ + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + summon minecraft:interaction \ + ^<%roundTo(interaction.default_transform.pos[0], 10)%> \ + ^<%roundTo(interaction.default_transform.pos[1], 10)%> \ + ^<%roundTo(interaction.default_transform.pos[2], 10)%> \ + { \ + Tags:<%getNodeTags(interaction, rig)%>, \ + response: <%interaction.config.response ?? false%>, \ + width: <%interaction.width%>f, \ + height: <%interaction.height%>f, \ + } + execute \ + as @n[ \ + type=minecraft:interaction, \ + tag=<%TAGS.PROJECT_INTERACTION_NAMED(blueprint_id, interaction.storage_name)%>, \ + tag=<%TAGS.NEW()%>, \ + distance=..<%Math.ceil(interaction.max_distance + 0.5)%> \ + ] \ + run block as_interaction/<%interaction.storage_name%> { + # run block ../as_interaction/<%interaction.storage_name%> { + tag @s remove <%TAGS.NEW()%> + data modify entity @s CustomName set value '<%ENTITY_NAMES.NODE(blueprint_id, interaction.type, interaction.storage_name)%>' + function animated_java:global/gu/get_entity_uuid_string + scoreboard players operation @s <%OBJECTIVES.ID()%> = aj.last_id <%OBJECTIVES.ID()%> + IF (interaction.config?.on_interact_function) { + data modify entity @s data.animated_java.on_interact_function set value "function <%blueprint_id%>/interactions/<%interaction.storage_name%>_on_interact" + } + IF (interaction.config?.on_attack_function) { + data modify entity @s data.animated_java.on_attack_function set value "function <%blueprint_id%>/interactions/<%interaction.storage_name%>_on_attack" + } + } + data modify storage <%temp_storage%> entry.data.uuids append from storage <%gu_storage%> out + data modify storage <%temp_storage%> entry.data.uuids_by_name.<%interaction.storage_name%> set from storage <%gu_storage%> out + data modify storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%>.uuid set from storage <%gu_storage%> out + } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { summon <%locator.config.entity_type%> \ ^<%roundTo(locator.default_transform.pos[0], 10)%> \ @@ -859,6 +973,16 @@ function summon { } } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction' && node.config.on_summon_function)) as interaction { + block { with storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%> + $execute at @s as $(uuid) at @s run block on_summon/custom_<%interaction.type + '_' + interaction.storage_name%> { + <%% + emit.mcb(interaction.config.on_summon_function) + %%> + } + } + } + REPEAT (Object.values(rig.nodes).filter(node => BONE_TYPES.includes(node.type) && node.on_summon_function?.trim())) as node { execute \ on passengers if entity @s[tag=<%TAGS.PROJECT_NODE_NAMED(blueprint_id, node.storage_name)%>] \ @@ -880,8 +1004,29 @@ function summon { # Remove the NEW tag from the root entity, and it's passengers. tag @s remove <%TAGS.NEW()%> execute on passengers run tag @s remove <%TAGS.NEW()%> - # Dismount bone entities from the root entity. - execute on passengers run ride @s dismount + IF (!use_entity_stacking) { + # Dismount bone entities from the root entity. + execute on passengers run ride @s dismount + } + } +} + +dir interactions { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + IF (interaction.config.on_interact_function) { + function <%interaction.storage_name%>_on_interact { + <%% + emit.mcb(interaction.config.on_interact_function) + %%> + } + } + IF (interaction.config.on_attack_function) { + function <%interaction.storage_name%>_on_attack { + <%% + emit.mcb(interaction.config.on_attack_function) + %%> + } + } } } @@ -920,6 +1065,234 @@ function as_node { } } +IF (has_interactions) { + function as_interaction { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {name:'$(name)', command:'$(command)', uuid:'+MISSING_UUID+'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{name:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('name')%> + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + block zzz/as_interaction/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from storage <%temp_storage%> entry.data.interactions.$(name).uuid + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + $execute as $(uuid) run return run $(command) + IF (debug_mode) { + # If the entity with the provided UUID doesn't exist, the interaction wasn't found. + tellraw @a <%TELLRAW.INTERACTION_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function as_at_interaction { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {name:'$(name)', command:'$(command)', uuid:'+MISSING_UUID+'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{name:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('name')%> + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + block zzz/as_interaction/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from storage <%temp_storage%> entry.data.interactions.$(name).uuid + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + $execute as $(uuid) at @s run return run $(command) + IF (debug_mode) { + # If the entity with the provided UUID doesn't exist, the interaction wasn't found. + tellraw @a <%TELLRAW.INTERACTION_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function as_all_interactions { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {command:'$(command)'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + block zzz/as_all_interactions/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + data modify storage <%temp_storage%> args.uuid set from storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + + block execute_as_uuid { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + + $execute as $(uuid) run return run $(command) + } + + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE({text: interaction.storage_name, color: 'aqua'})%> + } + } + } + } + + function as_at_all_interactions { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {command:'$(command)'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + block zzz/as_all_interactions/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + data modify storage <%temp_storage%> args.uuid set from storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + + block execute_as_uuid { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + + $execute as $(uuid) at @s run return run $(command) + } + + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE({text: interaction.storage_name, color: 'aqua'})%> + } + } + } + } + + function at_interaction { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {name:'$(name)', command:'$(command)'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{name:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('name')%> + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + execute at @s run block zzz/at_interaction/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge from storage <%temp_storage%> entry.data.interactions.$(name) + + IF (debug_mode) { + execute unless data storage <%temp_storage%> args.px run return run tellraw @a <%TELLRAW.INTERACTION_NOT_FOUND()%> + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + + block execute_at_transform { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + + $execute \ + positioned ^$(px) ^$(py) ^$(pz) \ + rotated ~$(ry) ~$(rx) \ + run $(command) + } + + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function at_all_interactions { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {command:'$(command)'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + block zzz/at_all_interactions/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + data modify storage <%temp_storage%> args merge from storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%> + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + + block execute_at_transform { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + + $execute \ + positioned ^$(px) ^$(py) ^$(pz) \ + rotated ~$(ry) ~$(rx) \ + run $(command) + } + + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE({text: interaction.storage_name, color: 'aqua'})%> + } + } + } + } +} + IF (has_entity_locators) { function as_locator { #ARGS: {name: string, command: string} @@ -1238,8 +1611,9 @@ dir remove { if (on_remove_function) emit.mcb(on_remove_function) %%> + data_manager prep read + IF (has_entity_locators) { - data_manager prep read REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { IF (locator.config?.on_remove_function) { IF (locator.config.use_entity) { @@ -1266,6 +1640,18 @@ dir remove { } } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + IF (interaction.config?.on_remove_function) { + block as_interaction_<%interaction.storage_name%> { with storage <%temp_storage%> entry.data.uuids_by_name + $execute as $(<%interaction.storage_name%>) at @s run block interaction_<%interaction.storage_name%>_on_remove { + <%% + emit.mcb(interaction.config.on_remove_function) + %%> + } + } + } + } + function ./this/without_on_remove_function } @@ -1284,6 +1670,11 @@ dir remove { } } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + data modify storage <%temp_storage%> args.uuid set from storage <%temp_storage%> entry.data.uuids_by_name.<%interaction.storage_name%> + function animated_java:global/remove/entity_stack_by_uuid with storage <%temp_storage%> args + } + # Remove the rig using the more expensive & thorough method if the rig_hash doesn't match. execute \ unless data storage <%temp_storage%> {entry:{data:{rig_hash: '<%rig_hash%>'}}} \ @@ -1474,4 +1865,4 @@ function set_default_pose { data_manager prep read } function ./zzz/set_default_pose -} \ No newline at end of file +} diff --git a/src/systems/datapackCompiler/1.21.5/global.mcb b/src/systems/datapackCompiler/1.21.5/global.mcb index ec2afdfb..09bc8ed6 100644 --- a/src/systems/datapackCompiler/1.21.5/global.mcb +++ b/src/systems/datapackCompiler/1.21.5/global.mcb @@ -234,7 +234,7 @@ dir global { $data modify storage <%temp_storage%> args.uuid set from storage <%data_storage%> "$(id)".uuid scoreboard players set #success <%OBJECTIVES.I()%> 0 block { with storage <%temp_storage%> args - $execute store success score #success <%OBJECTIVES.I()%> if score $(uuid) matches 1.. + $execute store success score #success <%OBJECTIVES.I()%> if score $(uuid) <%OBJECTIVES.ID()%> matches 1.. $execute if score #success <%OBJECTIVES.I()%> matches 0 run data remove storage <%data_storage%> "$(id)" } } @@ -257,4 +257,78 @@ dir global { $data modify storage <%data_storage%> "$(id)" set from storage <%temp_storage%> entry } } + + dir interactions { + REPEAT (['interaction', 'attack']) as direction { + dir <%direction%> { + advancement trigger { + "criteria": { + "player_<%direction%>_clicked_interaction_entity": { + "trigger": "<%direction === 'interaction' ? 'minecraft:player_interacted_with_entity' : 'minecraft:player_hurt_entity'%>", + "conditions": { + "entity": { + "type": "minecraft:interaction" + } + } + } + }, + "rewards": { + "function": "<%context.namespace + ':' + context.path.join('/')%>/on" + } + } + + function on { + advancement revoke @s only <%context.namespace + ':' + context.path.join('/')%>/trigger + tag @s add <%TAGS.INTERACTING_PLAYER()%> + execute \ + as @e[type=interaction, tag=<%TAGS.GLOBAL_INTERACTION()%>, distance=..8] \ + if data entity @s <%direction%> \ + run function ./check + tag @s remove <%TAGS.INTERACTING_PLAYER()%> + } + + function check { + execute store result score #gametime <%OBJECTIVES.I()%> run time query gametime + execute \ + store result score #timestamp <%OBJECTIVES.I()%> \ + run data get entity @s <%direction%>.timestamp + + # Check that the interaction has a command to run, and + # if this interaction is from the player being processed. + scoreboard players set #check <%OBJECTIVES.I()%> 0 + execute \ + if score #timestamp <%OBJECTIVES.I()%> = #gametime <%OBJECTIVES.I()%> \ + if data entity @s data.animated_java.on_<%direction === 'interaction' ? 'interact' : 'attack'%>_function \ + store success score #check <%OBJECTIVES.I()%> \ + on <%direction === 'interaction' ? 'target' : 'attacker'%> \ + if entity @s[tag=<%TAGS.INTERACTING_PLAYER()%>] + + execute \ + if score #check <%OBJECTIVES.I()%> matches 1 \ + run function ./do + } + + function do { + scoreboard players set #check <%OBJECTIVES.I()%> 0 + block execute { with entity @s data.animated_java + $$(on_<%direction === 'interaction' ? 'interact' : 'attack'%>_function) + scoreboard players set #check <%OBJECTIVES.I()%> 1 + } + execute if score #check <%OBJECTIVES.I()%> matches 0 run tellraw @a [ \ + {text: '', color: red}, \ + {text:'ɪɴᴛᴇʀᴀᴄᴛɪᴏɴꜱ', color:'light_purple'}, \ + {text:'\n(<%global.root%>.mcb)', color:'dark_gray'}, \ + '\n→ ᴇʀʀᴏʀ:\n\sFailed to handle <%direction === 'interaction' ? 'right' : 'left'%> click interaction from ', \ + {selector: '@p[tag=<%TAGS.INTERACTING_PLAYER()%>]', color: gold}, \ + ':\n\s\sInvalid command: ', \ + {nbt: 'data.animated_java.on_<%direction === 'interaction' ? 'interact' : 'attack'%>_function', entity: '@s', color: aqua}, \ + '\n\s\sfrom ', \ + {text: 'data.animated_java.on_<%direction === 'interaction' ? 'interact' : 'attack'%>_function ', color: yellow}, \ + ] + # Only remove the interaction if it's consumed. + data remove entity @s <%direction%> + } + } + } + } } diff --git a/src/systems/datapackCompiler/1.21.5/main.mcb b/src/systems/datapackCompiler/1.21.5/main.mcb index 6a80dac0..bf7cddf0 100644 --- a/src/systems/datapackCompiler/1.21.5/main.mcb +++ b/src/systems/datapackCompiler/1.21.5/main.mcb @@ -1,5 +1,5 @@ # TODO - Move all internal functions into an internal namespace, and only have user-facing functions in the main `<%blueprint_id%>` namespace. - +# FetchBot was here - May 5 2026 # DIFFERENCES FROM 1.21.4: # - JSON text components are now stored as NBT in entities. @@ -66,7 +66,7 @@ dir root { } IF (auto_update_rig_orientation) { - IF (has_locators || has_cameras) { + IF (has_locators || has_cameras || has_interactions) { function <%blueprint_id%>/root/on_tick/transform_floating_entities } IF (use_entity_stacking) { @@ -89,6 +89,19 @@ dir root { function <%blueprint_id%>/root/on_tick/transform_locators } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction' && node.config?.on_tick_function)) as interaction { + block tick_interactions { with storage <%temp_storage%> entry.data.uuids_by_name + $execute \ + as $(<%interaction.storage_name%>) \ + at @s \ + run block <%interaction.storage_name%> { + <%% + emit.mcb(interaction.config.on_tick_function) + %%> + } + } + } + # Custom post-tick function IF (on_post_tick_function) { <%% @@ -97,7 +110,7 @@ dir root { } } - IF (has_locators || has_cameras) { + IF (has_locators || has_cameras || has_interactions) { dir on_tick { function transform_locators { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { @@ -142,6 +155,24 @@ dir root { } } } + + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + block select_interaction_<%interaction.storage_name%> { with storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%> + $execute \ + as $(uuid) \ + positioned ^$(px) ^$(py) ^$(pz) \ + rotated ~$(ry) ~$(rx) \ + run block as_interaction_<%interaction.storage_name%> { + tp @s ~ ~ ~ ~ ~ + + IF (interaction.config?.on_tick_function) { + <%% + emit.mcb(interaction.config.on_tick_function) + %%> + } + } + } + } } function transform_floating_entities { @@ -208,6 +239,19 @@ IF (has_animations) { function play { debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + data_manager prep read + + tag @s add <%TAGS.ANIMATION_PLAYING(blueprint_id, animation.storage_name)%> + scoreboard players set @s <%OBJECTIVES.FRAME(animation.storage_name)%> 0 + tag @s add <%TAGS.TRANSFORMS_ONLY()%> + execute at @s run function ./zzz/set_frame {frame: 0} + tag @s remove <%TAGS.TRANSFORMS_ONLY()%> + } + + function play_exclusive { + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data_manager prep read function <%blueprint_id%>/animations/pause_all tag @s add <%TAGS.ANIMATION_PLAYING(blueprint_id, animation.storage_name)%> @@ -220,6 +264,7 @@ IF (has_animations) { function stop { debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + data_manager prep read function <%blueprint_id%>/animations/pause_all scoreboard players set @s <%OBJECTIVES.FRAME(animation.storage_name)%> 0 @@ -243,6 +288,7 @@ IF (has_animations) { function next_frame { debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + data_manager prep read execute if score @s <%OBJECTIVES.FRAME(animation.storage_name)%> matches <%animation.duration%>.. run scoreboard players set @s <%OBJECTIVES.FRAME(animation.storage_name)%> 1 data remove storage <%temp_storage%> args execute store result storage <%temp_storage%> args.frame int 1 run scoreboard players get @s <%OBJECTIVES.FRAME(animation.storage_name)%> @@ -256,7 +302,6 @@ IF (has_animations) { debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> data_manager prep read - data remove storage <%temp_storage%> args $execute store result storage <%temp_storage%> args.frame int 1 run scoreboard players set @s <%OBJECTIVES.FRAME(animation.storage_name)%> $(frame) execute at @s run function ./zzz/set_frame with storage <%temp_storage%> args @@ -278,6 +323,7 @@ IF (has_animations) { #ARGS: {duration: int, to_frame: int} debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + data_manager prep read function <%blueprint_id%>/animations/pause_all tag @s add <%TAGS.ANIMATION_PLAYING(blueprint_id, animation.storage_name)%> @@ -293,7 +339,6 @@ IF (has_animations) { IF (use_entity_stacking) { execute on passengers store result entity @s interpolation_duration int 1 run scoreboard players get #this <%OBJECTIVES.I()%> } ELSE { - data_manager prep read block { with storage <%temp_storage%> entry.data.uuids_by_name REPEAT (Object.values(rig.nodes).filter(node => BONE_TYPES.includes(node.type))) as node { $execute \ @@ -445,7 +490,6 @@ IF (has_animations) { global.modified_effect_nodes = Object.values(animation.modified_nodes).filter( node => node.type === 'locator' && global.frame.node_transforms[node.uuid] ) - console.log(global.modified_effect_nodes) %%> IF(Object.keys(global.modified_effect_nodes).length > 0) { function last_frame_effects { @@ -501,7 +545,7 @@ IF (has_animations) { const lastActiveFrame = {} const modifiedNodes = Object.values(animation.modified_nodes).filter(n => n.type !== 'struct').sort(nodeSorter); for (const [frameIndex, frame] of animation.frames.entries()) { - const to_merge = {cameras: {}, locators: {}} + const to_merge = {cameras: {}, locators: {}, interactions: {}} let frameFunc = ``; for (const node of modifiedNodes) { const transform = frame.node_transforms[node.uuid] @@ -572,6 +616,32 @@ IF (has_animations) { } break } + case 'interaction': { + const lastFrame = lastActiveFrame[node.uuid] + lastActiveFrame[node.uuid] = transform + ;if (!lastFrame || matrixToNbtFloatArray(transform.matrix).toString() !== matrixToNbtFloatArray(lastFrame.matrix).toString()) { + to_merge.interactions[node.storage_name] = { + px: roundTo(transform.pos[0], 10), + py: roundTo(transform.pos[1], 10), + pz: roundTo(transform.pos[2], 10), + ry: roundTo(transform.head_rot[1], 10), + rx: roundTo(transform.head_rot[0], 10) + }; + } + + if (transform.function) { + frameFunc += + `\n$execute unless entity @s[tag=${TAGS.TRANSFORMS_ONLY()}] as $(${node.storage_name}) ` + + `positioned ^${roundTo(transform.pos[0], 10)} ^${roundTo(transform.pos[1], 10)} ^${roundTo(transform.pos[2], 10)} ` + + `rotated ~${roundTo(transform.head_rot[1], 10)} ~${roundTo(transform.head_rot[0], 10)} ` + + `${transform.function_execute_condition ? transform.function_execute_condition + ' ' : ''}run ` + + `block ${frameIndex}_interaction_${node.storage_name}%NEWLINE_PATCH%{\n` + + `tp @s ~ ~ ~ ~ ~\n` + + `${transform.function}` + + `\n}` + } + break + } case 'camera': { const lastFrame = lastActiveFrame[node.uuid] lastActiveFrame[node.uuid] = transform @@ -589,9 +659,17 @@ IF (has_animations) { } } - if (Object.keys(to_merge.locators).length > 0 || Object.keys(to_merge.cameras).length > 0) { - frameFunc += `\ndata modify storage <%temp_storage%> entry.data merge value ${JSON.stringify(to_merge)}` - frameFunc += `\ndata_manager write` + const updatedLocators = Object.keys(to_merge.locators).length > 0; + const updatedCameras = Object.keys(to_merge.cameras).length > 0; + const updatedInteractions = Object.keys(to_merge.interactions).length > 0; + + if (!updatedCameras) delete to_merge.cameras; + if (!updatedLocators) delete to_merge.locators; + if (!updatedInteractions) delete to_merge.interactions; + + if (updatedLocators || updatedCameras || updatedInteractions) { + frameFunc += `\ndata modify storage ${temp_storage} entry.data merge value ${JSON.stringify(to_merge)}` + frameFunc += `\ndata_manager prep write` if (!auto_update_rig_orientation) { frameFunc += `\nfunction ${blueprint_id}/root/on_tick/transform_floating_entities` } @@ -676,6 +754,42 @@ function summon { # Align the position and rotation of the root with the command context. tp @s ~ ~ ~ ~ ~ + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + summon minecraft:interaction \ + ^<%roundTo(interaction.default_transform.pos[0], 10)%> \ + ^<%roundTo(interaction.default_transform.pos[1], 10)%> \ + ^<%roundTo(interaction.default_transform.pos[2], 10)%> \ + { \ + Tags:<%getNodeTags(interaction, rig)%>, \ + response: <%interaction.config.response ?? false%>, \ + width: <%interaction.width%>f, \ + height: <%interaction.height%>f, \ + } + execute \ + as @n[ \ + type=minecraft:interaction, \ + tag=<%TAGS.PROJECT_INTERACTION_NAMED(blueprint_id, interaction.storage_name)%>, \ + tag=<%TAGS.NEW()%>, \ + distance=..<%Math.ceil(interaction.max_distance + 0.5)%> \ + ] \ + run block as_interaction/<%interaction.storage_name%> { + # run block ../as_interaction/<%interaction.storage_name%> { + tag @s remove <%TAGS.NEW()%> + data modify entity @s CustomName set value <%ENTITY_NAMES.NODE(blueprint_id, interaction.type, interaction.storage_name)%> + function animated_java:global/gu/get_entity_uuid_string + scoreboard players operation @s <%OBJECTIVES.ID()%> = aj.last_id <%OBJECTIVES.ID()%> + IF (interaction.config?.on_interact_function) { + data modify entity @s data.animated_java.on_interact_function set value "function <%blueprint_id%>/interactions/<%interaction.storage_name%>_on_interact" + } + IF (interaction.config?.on_attack_function) { + data modify entity @s data.animated_java.on_attack_function set value "function <%blueprint_id%>/interactions/<%interaction.storage_name%>_on_attack" + } + } + data modify storage <%temp_storage%> entry.data.uuids append from storage <%gu_storage%> out + data modify storage <%temp_storage%> entry.data.uuids_by_name.<%interaction.storage_name%> set from storage <%gu_storage%> out + data modify storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%>.uuid set from storage <%gu_storage%> out + } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { summon <%locator.config.entity_type%> \ ^<%roundTo(locator.default_transform.pos[0], 10)%> \ @@ -858,6 +972,16 @@ function summon { } } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction' && node.config.on_summon_function)) as interaction { + block { with storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%> + $execute at @s as $(uuid) at @s run block on_summon/custom_<%interaction.type + '_' + interaction.storage_name%> { + <%% + emit.mcb(interaction.config.on_summon_function) + %%> + } + } + } + REPEAT (Object.values(rig.nodes).filter(node => BONE_TYPES.includes(node.type) && node.on_summon_function?.trim())) as node { execute \ on passengers if entity @s[tag=<%TAGS.PROJECT_NODE_NAMED(blueprint_id, node.storage_name)%>] \ @@ -879,8 +1003,29 @@ function summon { # Remove the NEW tag from the root entity, and it's passengers. tag @s remove <%TAGS.NEW()%> execute on passengers run tag @s remove <%TAGS.NEW()%> - # Dismount bone entities from the root entity. - execute on passengers run ride @s dismount + IF (!use_entity_stacking) { + # Dismount bone entities from the root entity. + execute on passengers run ride @s dismount + } + } +} + +dir interactions { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + IF (interaction.config.on_interact_function) { + function <%interaction.storage_name%>_on_interact { + <%% + emit.mcb(interaction.config.on_interact_function) + %%> + } + } + IF (interaction.config.on_attack_function) { + function <%interaction.storage_name%>_on_attack { + <%% + emit.mcb(interaction.config.on_attack_function) + %%> + } + } } } @@ -919,6 +1064,234 @@ function as_node { } } +IF (has_interactions) { + function as_interaction { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {name:'$(name)', command:'$(command)', uuid:'+MISSING_UUID+'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{name:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('name')%> + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + block zzz/as_interaction/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from storage <%temp_storage%> entry.data.interactions.$(name).uuid + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + $execute as $(uuid) run return run $(command) + IF (debug_mode) { + # If the entity with the provided UUID doesn't exist, the interaction wasn't found. + tellraw @a <%TELLRAW.INTERACTION_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function as_at_interaction { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {name:'$(name)', command:'$(command)', uuid:'+MISSING_UUID+'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{name:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('name')%> + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + block zzz/as_interaction/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from storage <%temp_storage%> entry.data.interactions.$(name).uuid + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + $execute as $(uuid) at @s run return run $(command) + IF (debug_mode) { + # If the entity with the provided UUID doesn't exist, the interaction wasn't found. + tellraw @a <%TELLRAW.INTERACTION_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function as_all_interactions { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {command:'$(command)'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + block zzz/as_all_interactions/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + data modify storage <%temp_storage%> args.uuid set from storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + + block execute_as_uuid { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + + $execute as $(uuid) run return run $(command) + } + + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE({text: interaction.storage_name, color: 'aqua'})%> + } + } + } + } + + function as_at_all_interactions { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {command:'$(command)'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + block zzz/as_all_interactions/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + data modify storage <%temp_storage%> args.uuid set from storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + + block execute_as_uuid { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + + $execute as $(uuid) at @s run return run $(command) + } + + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE({text: interaction.storage_name, color: 'aqua'})%> + } + } + } + } + + function at_interaction { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {name:'$(name)', command:'$(command)'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{name:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('name')%> + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + execute at @s run block zzz/at_interaction/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge from storage <%temp_storage%> entry.data.interactions.$(name) + + IF (debug_mode) { + execute unless data storage <%temp_storage%> args.px run return run tellraw @a <%TELLRAW.INTERACTION_NOT_FOUND()%> + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + + block execute_at_transform { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + + $execute \ + positioned ^$(px) ^$(py) ^$(pz) \ + rotated ~$(ry) ~$(rx) \ + run $(command) + } + + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function at_all_interactions { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(blueprint_id)%> + + data remove storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge value {command:'$(command)'} + + IF (debug_mode) { + execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> + } + + data_manager prep read + + block zzz/at_all_interactions/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + data modify storage <%temp_storage%> args merge from storage <%temp_storage%> entry.data.interactions.<%interaction.storage_name%> + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + + block execute_at_transform { with storage <%temp_storage%> args + IF (debug_mode) { + # If the function successfully instantiated, the provided command is valid. + scoreboard players set #aj.check <%OBJECTIVES.I()%> 1 + } + + $execute \ + positioned ^$(px) ^$(py) ^$(pz) \ + rotated ~$(ry) ~$(rx) \ + run $(command) + } + + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.INTERACTION_COMMAND_FAILED_TO_EXECUTE({text: interaction.storage_name, color: 'aqua'})%> + } + } + } + } +} + IF (has_entity_locators) { function as_locator { #ARGS: {name: string, command: string} @@ -1237,8 +1610,9 @@ dir remove { if (on_remove_function) emit.mcb(on_remove_function) %%> + data_manager prep read + IF (has_entity_locators) { - data_manager prep read REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { IF (locator.config?.on_remove_function) { IF (locator.config.use_entity) { @@ -1265,6 +1639,18 @@ dir remove { } } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + IF (interaction.config?.on_remove_function) { + block as_interaction_<%interaction.storage_name%> { with storage <%temp_storage%> entry.data.uuids_by_name + $execute as $(<%interaction.storage_name%>) at @s run block interaction_<%interaction.storage_name%>_on_remove { + <%% + emit.mcb(interaction.config.on_remove_function) + %%> + } + } + } + } + function ./this/without_on_remove_function } @@ -1283,6 +1669,11 @@ dir remove { } } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'interaction')) as interaction { + data modify storage <%temp_storage%> args.uuid set from storage <%temp_storage%> entry.data.uuids_by_name.<%interaction.storage_name%> + function animated_java:global/remove/entity_stack_by_uuid with storage <%temp_storage%> args + } + # Remove the rig using the more expensive & thorough method if the rig_hash doesn't match. execute \ unless data storage <%temp_storage%> {entry:{data:{rig_hash: '<%rig_hash%>'}}} \ @@ -1473,4 +1864,4 @@ function set_default_pose { data_manager prep read } function ./zzz/set_default_pose -} \ No newline at end of file +} diff --git a/src/systems/datapackCompiler/index.ts b/src/systems/datapackCompiler/index.ts index 1dde6841..a0eba207 100644 --- a/src/systems/datapackCompiler/index.ts +++ b/src/systems/datapackCompiler/index.ts @@ -250,7 +250,7 @@ async function createAnimationStorage(rig: IRenderedRig, animations: IRenderedAn PROGRESS_DESCRIPTION.set(`Creating Animation Storage for '${animation.storage_name}'`) let frames = new NbtCompound() const addFrameDataCommand = () => { - const str = `data modify storage animated_java:${ + const str = `data modify storage ${ Project!.animated_java.blueprint_id }/animations ${animation.storage_name} merge value ${frames.toString()}` dataCommands.push(str) @@ -586,6 +586,7 @@ const dataPackCompiler: DataPackCompiler = async ({ nodeSorter, getRotationFromQuaternion: eulerFromQuaternion, has_locators: Object.values(rig.nodes).filter(n => n.type === 'locator').length > 0, + has_interactions: Object.values(rig.nodes).filter(n => n.type === 'interaction').length > 0, has_entity_locators: Object.values(rig.nodes).filter(n => n.type === 'locator' && n.config?.use_entity) .length > 0, diff --git a/src/systems/datapackCompiler/tags.ts b/src/systems/datapackCompiler/tags.ts index 57810978..47533948 100644 --- a/src/systems/datapackCompiler/tags.ts +++ b/src/systems/datapackCompiler/tags.ts @@ -21,6 +21,7 @@ namespace TAGS { export const GLOBAL_ROOT_CHILD_TEXT_DISPLAY = () => 'aj.global.root.child.text_display' export const GLOBAL_ROOT_CHILD_LOCATOR = () => 'aj.global.root.child.locator' export const GLOBAL_ROOT_CHILD_CAMERA = () => 'aj.global.root.child.camera' + export const GLOBAL_ROOT_CHILD_INTERACTION = () => 'aj.global.root.child.interaction' export const GLOBAL_NODE = () => 'aj.global.node' export const GLOBAL_DISPLAY_NODE = () => 'aj.global.display_node' @@ -31,6 +32,11 @@ namespace TAGS { export const GLOBAL_TEXT_DISPLAY = () => 'aj.global.text_display' export const GLOBAL_CAMERA = () => 'aj.global.camera' export const GLOBAL_LOCATOR = () => 'aj.global.locator' + export const GLOBAL_INTERACTION = () => 'aj.global.interaction' + + // -------------------------------- + // region Global Named Tags + // -------------------------------- export const GLOBAL_NODE_NAMED = (nodeName: string) => `aj.global.node.${nodeName}` export const GLOBAL_DISPLAY_NODE_NAMED = (nodeName: string) => @@ -48,6 +54,8 @@ namespace TAGS { `aj.global.bone.${boneName}.child.locator` export const GLOBAL_BONE_CHILD_CAMERA = (boneName: string) => `aj.global.bone.${boneName}.child.camera` + export const GLOBAL_BONE_CHILD_INTERACTION = (boneName: string) => + `aj.global.bone.${boneName}.child.interaction` export const GLOBAL_BONE_DECENDANT = (boneName: string) => `aj.global.bone.${boneName}.decendant` @@ -63,6 +71,8 @@ namespace TAGS { `aj.global.bone.${boneName}.decendant.locator` export const GLOBAL_BONE_DECENDANT_CAMERA = (boneName: string) => `aj.global.bone.${boneName}.decendant.camera` + export const GLOBAL_BONE_DECENDANT_INTERACTION = (boneName: string) => + `aj.global.bone.${boneName}.decendant.interaction` export const GLOBAL_BONE_TREE = (boneName: string) => `aj.global.bone.${boneName}.tree` export const GLOBAL_BONE_TREE_BONE = (boneName: string) => @@ -88,6 +98,8 @@ namespace TAGS { `${makeTagSafe(blueprintId)}.root.child.locator` export const PROJECT_ROOT_CHILD_CAMERA = (blueprintId: string) => `${makeTagSafe(blueprintId)}.root.child.camera` + export const PROJECT_ROOT_CHILD_INTERACTION = (blueprintId: string) => + `${makeTagSafe(blueprintId)}.root.child.interaction` export const PROJECT_NODE = (blueprintId: string) => `${makeTagSafe(blueprintId)}.node` export const PROJECT_DISPLAY_NODE = (blueprintId: string) => @@ -103,6 +115,12 @@ namespace TAGS { `${makeTagSafe(blueprintId)}.text_display` export const PROJECT_CAMERA = (blueprintId: string) => `${makeTagSafe(blueprintId)}.camera` export const PROJECT_LOCATOR = (blueprintId: string) => `${makeTagSafe(blueprintId)}.locator` + export const PROJECT_INTERACTION = (blueprintId: string) => + `${makeTagSafe(blueprintId)}.interaction` + + // -------------------------------- + // region Project Named Tags + // -------------------------------- export const PROJECT_NODE_NAMED = (blueprintId: string, nodeName: string) => `${makeTagSafe(blueprintId)}.node.${nodeName}` @@ -120,6 +138,8 @@ namespace TAGS { `${makeTagSafe(blueprintId)}.camera.${cameraName}` export const PROJECT_LOCATOR_NAMED = (blueprintId: string, locatorName: string) => `${makeTagSafe(blueprintId)}.locator.${locatorName}` + export const PROJECT_INTERACTION_NAMED = (blueprintId: string, interactionName: string) => + `${makeTagSafe(blueprintId)}.interaction.${interactionName}` export const PROJECT_BONE_CHILD = (blueprintId: string, boneName: string) => `${makeTagSafe(blueprintId)}.bone.${boneName}.child` @@ -135,6 +155,8 @@ namespace TAGS { `${makeTagSafe(blueprintId)}.bone.${boneName}.child.locator` export const PROJECT_BONE_CHILD_CAMERA = (blueprintId: string, boneName: string) => `${makeTagSafe(blueprintId)}.bone.${boneName}.child.camera` + export const PROJECT_BONE_CHILD_INTERACTION = (blueprintId: string, boneName: string) => + `${makeTagSafe(blueprintId)}.bone.${boneName}.child.interaction` export const PROJECT_BONE_DECENDANT = (blueprintId: string, boneName: string) => `${makeTagSafe(blueprintId)}.bone.${boneName}.decendant` @@ -150,6 +172,8 @@ namespace TAGS { `${makeTagSafe(blueprintId)}.bone.${boneName}.decendant.locator` export const PROJECT_BONE_DECENDANT_CAMERA = (blueprintId: string, boneName: string) => `${makeTagSafe(blueprintId)}.bone.${boneName}.decendant.camera` + export const PROJECT_BONE_DECENDANT_INTERACTION = (blueprintId: string, boneName: string) => + `${makeTagSafe(blueprintId)}.bone.${boneName}.decendant.interaction` export const PROJECT_BONE_TREE = (blueprintId: string, boneName: string) => `${makeTagSafe(blueprintId)}.bone.${boneName}.tree` @@ -169,6 +193,7 @@ namespace TAGS { export const TRANSFORMS_ONLY = () => 'aj.transforms_only' export const EFFECTS_ONLY = () => 'aj.effects_only' export const OUTDATED_RIG_TEXT_DISPLAY = () => 'aj.outdated_rig_text_display' + export const INTERACTING_PLAYER = () => 'aj.interacting_player' } export default TAGS @@ -209,6 +234,8 @@ export function getNodeTags(node: AnyRenderedNode, rig: IRenderedRig): NbtList { const hasParent = node.parent && node.parent !== 'root' + const aj = Project!.animated_java + tags.push( // Global TAGS.NEW(), @@ -216,9 +243,9 @@ export function getNodeTags(node: AnyRenderedNode, rig: IRenderedRig): NbtList { TAGS.GLOBAL_NODE(), TAGS.GLOBAL_NODE_NAMED(node.storage_name), // Project - TAGS.PROJECT_ENTITY(Project!.animated_java.blueprint_id), - TAGS.PROJECT_NODE(Project!.animated_java.blueprint_id), - TAGS.PROJECT_NODE_NAMED(Project!.animated_java.blueprint_id, node.storage_name) + TAGS.PROJECT_ENTITY(aj.blueprint_id), + TAGS.PROJECT_NODE(aj.blueprint_id), + TAGS.PROJECT_NODE_NAMED(aj.blueprint_id, node.storage_name) ) if (!hasParent) { @@ -233,14 +260,11 @@ export function getNodeTags(node: AnyRenderedNode, rig: IRenderedRig): NbtList { TAGS.GLOBAL_BONE_TREE(node.storage_name), // Tree includes self TAGS.GLOBAL_BONE_TREE_BONE(node.storage_name), // Tree includes self // Project - TAGS.PROJECT_DISPLAY_NODE_NAMED( - Project!.animated_java.blueprint_id, - node.storage_name - ), - TAGS.PROJECT_BONE(Project!.animated_java.blueprint_id), - TAGS.PROJECT_BONE_NAMED(Project!.animated_java.blueprint_id, node.storage_name), - TAGS.PROJECT_BONE_TREE(Project!.animated_java.blueprint_id, node.storage_name), // Tree includes self - TAGS.PROJECT_BONE_TREE_BONE(Project!.animated_java.blueprint_id, node.storage_name) // Tree includes self + TAGS.PROJECT_DISPLAY_NODE_NAMED(aj.blueprint_id, node.storage_name), + TAGS.PROJECT_BONE(aj.blueprint_id), + TAGS.PROJECT_BONE_NAMED(aj.blueprint_id, node.storage_name), + TAGS.PROJECT_BONE_TREE(aj.blueprint_id, node.storage_name), // Tree includes self + TAGS.PROJECT_BONE_TREE_BONE(aj.blueprint_id, node.storage_name) // Tree includes self ) if (!hasParent) { // Nodes without parents are assumed to be root nodes @@ -251,14 +275,8 @@ export function getNodeTags(node: AnyRenderedNode, rig: IRenderedRig): NbtList { TAGS.GLOBAL_BONE_CHILD(parentNames[0].name), TAGS.GLOBAL_BONE_CHILD_BONE(parentNames[0].name), // Project - TAGS.PROJECT_BONE_CHILD( - Project!.animated_java.blueprint_id, - parentNames[0].name - ), - TAGS.PROJECT_BONE_CHILD_BONE( - Project!.animated_java.blueprint_id, - parentNames[0].name - ) + TAGS.PROJECT_BONE_CHILD(aj.blueprint_id, parentNames[0].name), + TAGS.PROJECT_BONE_CHILD_BONE(aj.blueprint_id, parentNames[0].name) ) } for (const { name } of parentNames) { @@ -269,10 +287,10 @@ export function getNodeTags(node: AnyRenderedNode, rig: IRenderedRig): NbtList { TAGS.GLOBAL_BONE_TREE(name), TAGS.GLOBAL_BONE_TREE_BONE(name), // Project - TAGS.PROJECT_BONE_DECENDANT(Project!.animated_java.blueprint_id, name), - TAGS.PROJECT_BONE_DECENDANT_BONE(Project!.animated_java.blueprint_id, name), - TAGS.PROJECT_BONE_TREE(Project!.animated_java.blueprint_id, name), - TAGS.PROJECT_BONE_TREE_BONE(Project!.animated_java.blueprint_id, name) + TAGS.PROJECT_BONE_DECENDANT(aj.blueprint_id, name), + TAGS.PROJECT_BONE_DECENDANT_BONE(aj.blueprint_id, name), + TAGS.PROJECT_BONE_TREE(aj.blueprint_id, name), + TAGS.PROJECT_BONE_TREE_BONE(aj.blueprint_id, name) ) } break @@ -283,15 +301,9 @@ export function getNodeTags(node: AnyRenderedNode, rig: IRenderedRig): NbtList { TAGS.GLOBAL_DISPLAY_NODE_NAMED(node.storage_name), TAGS.GLOBAL_ITEM_DISPLAY(), // Project - TAGS.PROJECT_DISPLAY_NODE_NAMED( - Project!.animated_java.blueprint_id, - node.storage_name - ), - TAGS.PROJECT_ITEM_DISPLAY(Project!.animated_java.blueprint_id), - TAGS.PROJECT_ITEM_DISPLAY_NAMED( - Project!.animated_java.blueprint_id, - node.storage_name - ) + TAGS.PROJECT_DISPLAY_NODE_NAMED(aj.blueprint_id, node.storage_name), + TAGS.PROJECT_ITEM_DISPLAY(aj.blueprint_id), + TAGS.PROJECT_ITEM_DISPLAY_NAMED(aj.blueprint_id, node.storage_name) ) if (!hasParent) { // Nodes without parents are assumed to be root nodes @@ -302,14 +314,8 @@ export function getNodeTags(node: AnyRenderedNode, rig: IRenderedRig): NbtList { TAGS.GLOBAL_BONE_CHILD(parentNames[0].name), TAGS.GLOBAL_BONE_CHILD_ITEM_DISPLAY(parentNames[0].name), // Project - TAGS.PROJECT_BONE_CHILD( - Project!.animated_java.blueprint_id, - parentNames[0].name - ), - TAGS.PROJECT_BONE_CHILD_ITEM_DISPLAY( - Project!.animated_java.blueprint_id, - parentNames[0].name - ) + TAGS.PROJECT_BONE_CHILD(aj.blueprint_id, parentNames[0].name), + TAGS.PROJECT_BONE_CHILD_ITEM_DISPLAY(aj.blueprint_id, parentNames[0].name) ) } for (const { name } of parentNames) { @@ -319,12 +325,9 @@ export function getNodeTags(node: AnyRenderedNode, rig: IRenderedRig): NbtList { TAGS.GLOBAL_BONE_DECENDANT_ITEM_DISPLAY(name), TAGS.GLOBAL_BONE_TREE(name), // Project - TAGS.PROJECT_BONE_DECENDANT(Project!.animated_java.blueprint_id, name), - TAGS.PROJECT_BONE_DECENDANT_ITEM_DISPLAY( - Project!.animated_java.blueprint_id, - name - ), - TAGS.PROJECT_BONE_TREE(Project!.animated_java.blueprint_id, name) + TAGS.PROJECT_BONE_DECENDANT(aj.blueprint_id, name), + TAGS.PROJECT_BONE_DECENDANT_ITEM_DISPLAY(aj.blueprint_id, name), + TAGS.PROJECT_BONE_TREE(aj.blueprint_id, name) ) } break @@ -335,15 +338,9 @@ export function getNodeTags(node: AnyRenderedNode, rig: IRenderedRig): NbtList { TAGS.GLOBAL_DISPLAY_NODE_NAMED(node.storage_name), TAGS.GLOBAL_BLOCK_DISPLAY(), // Project - TAGS.PROJECT_DISPLAY_NODE_NAMED( - Project!.animated_java.blueprint_id, - node.storage_name - ), - TAGS.PROJECT_BLOCK_DISPLAY(Project!.animated_java.blueprint_id), - TAGS.PROJECT_BLOCK_DISPLAY_NAMED( - Project!.animated_java.blueprint_id, - node.storage_name - ) + TAGS.PROJECT_DISPLAY_NODE_NAMED(aj.blueprint_id, node.storage_name), + TAGS.PROJECT_BLOCK_DISPLAY(aj.blueprint_id), + TAGS.PROJECT_BLOCK_DISPLAY_NAMED(aj.blueprint_id, node.storage_name) ) if (!hasParent) { // Nodes without parents are assumed to be root nodes @@ -354,14 +351,8 @@ export function getNodeTags(node: AnyRenderedNode, rig: IRenderedRig): NbtList { TAGS.GLOBAL_BONE_CHILD(parentNames[0].name), TAGS.GLOBAL_BONE_CHILD_BLOCK_DISPLAY(parentNames[0].name), // Project - TAGS.PROJECT_BONE_CHILD( - Project!.animated_java.blueprint_id, - parentNames[0].name - ), - TAGS.PROJECT_BONE_CHILD_BLOCK_DISPLAY( - Project!.animated_java.blueprint_id, - parentNames[0].name - ) + TAGS.PROJECT_BONE_CHILD(aj.blueprint_id, parentNames[0].name), + TAGS.PROJECT_BONE_CHILD_BLOCK_DISPLAY(aj.blueprint_id, parentNames[0].name) ) } for (const { name } of parentNames) { @@ -371,12 +362,9 @@ export function getNodeTags(node: AnyRenderedNode, rig: IRenderedRig): NbtList { TAGS.GLOBAL_BONE_DECENDANT_BLOCK_DISPLAY(name), TAGS.GLOBAL_BONE_TREE(name), // Project - TAGS.PROJECT_BONE_DECENDANT(Project!.animated_java.blueprint_id, name), - TAGS.PROJECT_BONE_DECENDANT_BLOCK_DISPLAY( - Project!.animated_java.blueprint_id, - name - ), - TAGS.PROJECT_BONE_TREE(Project!.animated_java.blueprint_id, name) + TAGS.PROJECT_BONE_DECENDANT(aj.blueprint_id, name), + TAGS.PROJECT_BONE_DECENDANT_BLOCK_DISPLAY(aj.blueprint_id, name), + TAGS.PROJECT_BONE_TREE(aj.blueprint_id, name) ) } break @@ -387,15 +375,9 @@ export function getNodeTags(node: AnyRenderedNode, rig: IRenderedRig): NbtList { TAGS.GLOBAL_DISPLAY_NODE_NAMED(node.storage_name), TAGS.GLOBAL_TEXT_DISPLAY(), // Project - TAGS.PROJECT_DISPLAY_NODE_NAMED( - Project!.animated_java.blueprint_id, - node.storage_name - ), - TAGS.PROJECT_TEXT_DISPLAY(Project!.animated_java.blueprint_id), - TAGS.PROJECT_TEXT_DISPLAY_NAMED( - Project!.animated_java.blueprint_id, - node.storage_name - ) + TAGS.PROJECT_DISPLAY_NODE_NAMED(aj.blueprint_id, node.storage_name), + TAGS.PROJECT_TEXT_DISPLAY(aj.blueprint_id), + TAGS.PROJECT_TEXT_DISPLAY_NAMED(aj.blueprint_id, node.storage_name) ) if (!hasParent) { // Nodes without parents are assumed to be root nodes @@ -406,14 +388,8 @@ export function getNodeTags(node: AnyRenderedNode, rig: IRenderedRig): NbtList { TAGS.GLOBAL_BONE_CHILD(parentNames[0].name), TAGS.GLOBAL_BONE_CHILD_TEXT_DISPLAY(parentNames[0].name), // Project - TAGS.PROJECT_BONE_CHILD( - Project!.animated_java.blueprint_id, - parentNames[0].name - ), - TAGS.PROJECT_BONE_CHILD_TEXT_DISPLAY( - Project!.animated_java.blueprint_id, - parentNames[0].name - ) + TAGS.PROJECT_BONE_CHILD(aj.blueprint_id, parentNames[0].name), + TAGS.PROJECT_BONE_CHILD_TEXT_DISPLAY(aj.blueprint_id, parentNames[0].name) ) } for (const { name } of parentNames) { @@ -423,12 +399,9 @@ export function getNodeTags(node: AnyRenderedNode, rig: IRenderedRig): NbtList { TAGS.GLOBAL_BONE_DECENDANT_TEXT_DISPLAY(name), TAGS.GLOBAL_BONE_TREE(name), // Project - TAGS.PROJECT_BONE_DECENDANT(Project!.animated_java.blueprint_id, name), - TAGS.PROJECT_BONE_DECENDANT_TEXT_DISPLAY( - Project!.animated_java.blueprint_id, - name - ), - TAGS.PROJECT_BONE_TREE(Project!.animated_java.blueprint_id, name) + TAGS.PROJECT_BONE_DECENDANT(aj.blueprint_id, name), + TAGS.PROJECT_BONE_DECENDANT_TEXT_DISPLAY(aj.blueprint_id, name), + TAGS.PROJECT_BONE_TREE(aj.blueprint_id, name) ) } break @@ -438,8 +411,8 @@ export function getNodeTags(node: AnyRenderedNode, rig: IRenderedRig): NbtList { // Global TAGS.GLOBAL_LOCATOR(), // Project - TAGS.PROJECT_LOCATOR(Project!.animated_java.blueprint_id), - TAGS.PROJECT_LOCATOR_NAMED(Project!.animated_java.blueprint_id, node.storage_name) + TAGS.PROJECT_LOCATOR(aj.blueprint_id), + TAGS.PROJECT_LOCATOR_NAMED(aj.blueprint_id, node.storage_name) ) if (!hasParent) { // Nodes without parents are assumed to be root nodes @@ -450,14 +423,8 @@ export function getNodeTags(node: AnyRenderedNode, rig: IRenderedRig): NbtList { TAGS.GLOBAL_BONE_CHILD(parentNames[0].name), TAGS.GLOBAL_BONE_CHILD_LOCATOR(parentNames[0].name), // Project - TAGS.PROJECT_BONE_CHILD( - Project!.animated_java.blueprint_id, - parentNames[0].name - ), - TAGS.PROJECT_BONE_CHILD_LOCATOR( - Project!.animated_java.blueprint_id, - parentNames[0].name - ) + TAGS.PROJECT_BONE_CHILD(aj.blueprint_id, parentNames[0].name), + TAGS.PROJECT_BONE_CHILD_LOCATOR(aj.blueprint_id, parentNames[0].name) ) } for (const { name } of parentNames) { @@ -467,9 +434,9 @@ export function getNodeTags(node: AnyRenderedNode, rig: IRenderedRig): NbtList { TAGS.GLOBAL_BONE_DECENDANT_LOCATOR(name), TAGS.GLOBAL_BONE_TREE(name), // Project - TAGS.PROJECT_BONE_DECENDANT(Project!.animated_java.blueprint_id, name), - TAGS.PROJECT_BONE_DECENDANT_LOCATOR(Project!.animated_java.blueprint_id, name), - TAGS.PROJECT_BONE_TREE(Project!.animated_java.blueprint_id, name) + TAGS.PROJECT_BONE_DECENDANT(aj.blueprint_id, name), + TAGS.PROJECT_BONE_DECENDANT_LOCATOR(aj.blueprint_id, name), + TAGS.PROJECT_BONE_TREE(aj.blueprint_id, name) ) } break @@ -479,8 +446,8 @@ export function getNodeTags(node: AnyRenderedNode, rig: IRenderedRig): NbtList { // Global TAGS.GLOBAL_CAMERA(), // Project - TAGS.PROJECT_CAMERA(Project!.animated_java.blueprint_id), - TAGS.PROJECT_CAMERA_NAMED(Project!.animated_java.blueprint_id, node.storage_name) + TAGS.PROJECT_CAMERA(aj.blueprint_id), + TAGS.PROJECT_CAMERA_NAMED(aj.blueprint_id, node.storage_name) ) if (!hasParent) { // Nodes without parents are assumed to be root nodes @@ -491,14 +458,8 @@ export function getNodeTags(node: AnyRenderedNode, rig: IRenderedRig): NbtList { TAGS.GLOBAL_BONE_CHILD(parentNames[0].name), TAGS.GLOBAL_BONE_CHILD_CAMERA(parentNames[0].name), // Project - TAGS.PROJECT_BONE_CHILD( - Project!.animated_java.blueprint_id, - parentNames[0].name - ), - TAGS.PROJECT_BONE_CHILD_CAMERA( - Project!.animated_java.blueprint_id, - parentNames[0].name - ) + TAGS.PROJECT_BONE_CHILD(aj.blueprint_id, parentNames[0].name), + TAGS.PROJECT_BONE_CHILD_CAMERA(aj.blueprint_id, parentNames[0].name) ) } for (const { name } of parentNames) { @@ -508,9 +469,44 @@ export function getNodeTags(node: AnyRenderedNode, rig: IRenderedRig): NbtList { TAGS.GLOBAL_BONE_DECENDANT_CAMERA(name), TAGS.GLOBAL_BONE_TREE(name), // Project - TAGS.PROJECT_BONE_DECENDANT(Project!.animated_java.blueprint_id, name), - TAGS.PROJECT_BONE_DECENDANT_CAMERA(Project!.animated_java.blueprint_id, name), - TAGS.PROJECT_BONE_TREE(Project!.animated_java.blueprint_id, name) + TAGS.PROJECT_BONE_DECENDANT(aj.blueprint_id, name), + TAGS.PROJECT_BONE_DECENDANT_CAMERA(aj.blueprint_id, name), + TAGS.PROJECT_BONE_TREE(aj.blueprint_id, name) + ) + } + break + } + case 'interaction': { + tags.push( + // Global + TAGS.GLOBAL_INTERACTION(), + // Project + TAGS.PROJECT_INTERACTION(aj.blueprint_id), + TAGS.PROJECT_INTERACTION_NAMED(aj.blueprint_id, node.storage_name) + ) + if (!hasParent) { + // Nodes without parents are assumed to be root nodes + tags.push(TAGS.GLOBAL_ROOT_CHILD_INTERACTION()) + } else { + tags.push( + // Global + TAGS.GLOBAL_BONE_CHILD(parentNames[0].name), + TAGS.GLOBAL_BONE_CHILD_INTERACTION(parentNames[0].name), + // Project + TAGS.PROJECT_BONE_CHILD(aj.blueprint_id, parentNames[0].name), + TAGS.PROJECT_BONE_CHILD_INTERACTION(aj.blueprint_id, parentNames[0].name) + ) + } + for (const { name } of parentNames) { + tags.push( + // Global + TAGS.GLOBAL_BONE_DECENDANT(name), + TAGS.GLOBAL_BONE_DECENDANT_INTERACTION(name), + TAGS.GLOBAL_BONE_TREE(name), + // Project + TAGS.PROJECT_BONE_DECENDANT(aj.blueprint_id, name), + TAGS.PROJECT_BONE_DECENDANT_INTERACTION(aj.blueprint_id, name), + TAGS.PROJECT_BONE_TREE(aj.blueprint_id, name) ) } break diff --git a/src/systems/datapackCompiler/tellraw.ts b/src/systems/datapackCompiler/tellraw.ts index c6779c2b..dba763bd 100644 --- a/src/systems/datapackCompiler/tellraw.ts +++ b/src/systems/datapackCompiler/tellraw.ts @@ -289,6 +289,34 @@ namespace TELLRAW { { text: ' cannot be an empty string.' }, ]) + export const INTERACTION_NOT_FOUND = () => + TELLRAW_ERROR('Interaction Not Found', [ + 'Interaction ', + { nbt: 'args.name', storage: 'animated_java:temp', color: 'aqua' }, + ' not found!', + '\n Please ensure that the name is spelled correctly.', + ]) + + export const INTERACTION_ENTITY_NOT_FOUND = () => + TELLRAW_ERROR('Interaction Not Found', [ + 'Interaction ', + { nbt: 'args.name', storage: 'animated_java:temp', color: 'aqua' }, + ' does not exist!', + { text: '\n Please ensure that the name is spelled correctly, and ' }, + { text: '"Use Entity"', color: 'yellow' }, + " is enabled in the interaction's config.", + ]) + + export const INTERACTION_COMMAND_FAILED_TO_EXECUTE = (name?: TextElement) => + TELLRAW_ERROR('Failed to Execute Command as Interaction', [ + 'Failed to execute command ', + { nbt: 'args.command', storage: 'animated_java:temp', color: 'yellow' }, + ' as Interaction ', + name ?? { nbt: 'args.name', storage: 'animated_java:temp', color: 'aqua' }, + '.', + '\n Please ensure the command is valid.', + ]) + export const LOCATOR_NOT_FOUND = () => TELLRAW_ERROR('Locator Not Found', [ 'Locator ', diff --git a/src/systems/minecraft/itemDefinitions.d.ts b/src/systems/minecraft/itemDefinitions.d.ts index af1b01f1..f6a41aca 100644 --- a/src/systems/minecraft/itemDefinitions.d.ts +++ b/src/systems/minecraft/itemDefinitions.d.ts @@ -12,7 +12,7 @@ type DisplayContext = type TintSource = | { type: 'minecraft:constant' - value: [number, number, number] + value: number | [number, number, number] } | { type: diff --git a/src/systems/pluginCompiler.ts b/src/systems/pluginCompiler.ts index d7530025..f286d2b9 100644 --- a/src/systems/pluginCompiler.ts +++ b/src/systems/pluginCompiler.ts @@ -20,6 +20,8 @@ import type { IRenderedRig, } from './rigRenderer' +type StringVector3 = [string, string, string] + type TextureAnimationFrame = | number | { @@ -143,8 +145,8 @@ type TransformationKeyframeInterpolation = | { type: 'step' } interface TransformationKeyframe { - value: [string, string, string] - post?: [string, string, string] + value: StringVector3 + post?: StringVector3 interpolation: TransformationKeyframeInterpolation } @@ -262,7 +264,10 @@ function serializeDisplayProperties( const overrideBrightness = config?.override_brightness ?? false if (overrideBrightness) { props.is_custom_brightness_enabled = true - props.custom_brightness = config?.brightness_override ?? 0 + props.custom_brightness = { + sky: config?.sky_brightness ?? 0, + block: config?.block_brightness ?? 0, + } } if (config?.glowing !== undefined) props.is_glowing = config.glowing @@ -546,12 +551,8 @@ function keyframeInterpolation(kf: _Keyframe): TransformationKeyframeInterpolati } } -function keyframeDataPoint(kf: _Keyframe, index: number): [string, string, string] { - return [ - String(kf.get('x', index)), - String(kf.get('y', index)), - String(kf.get('z', index)), - ] +function keyframeDataPoint(kf: _Keyframe, index: number): StringVector3 { + return [String(kf.get('x', index)), String(kf.get('y', index)), String(kf.get('z', index))] } function serializeRawAnimation(options: { @@ -640,9 +641,18 @@ function serializeBakedAnimation(options: { const rotation = (channels.rotation ??= {}) const scale = (channels.scale ??= {}) - position[timeKey] = { value: transform.pos.map(toMolangNumber) as [string, string, string], interpolation } - rotation[timeKey] = { value: transform.rot.map(toMolangNumber) as [string, string, string], interpolation } - scale[timeKey] = { value: transform.scale.map(toMolangNumber) as [string, string, string], interpolation } + position[timeKey] = { + value: transform.pos.map(toMolangNumber) as StringVector3, + interpolation, + } + rotation[timeKey] = { + value: transform.rot.map(toMolangNumber) as StringVector3, + interpolation, + } + scale[timeKey] = { + value: transform.scale.map(toMolangNumber) as StringVector3, + interpolation, + } } if (paletteIds.length && frame.variants?.length) { diff --git a/src/systems/resourcepackCompiler/1.21.4.ts b/src/systems/resourcepackCompiler/1.21.4.ts index 9890cb24..9f721163 100644 --- a/src/systems/resourcepackCompiler/1.21.4.ts +++ b/src/systems/resourcepackCompiler/1.21.4.ts @@ -7,7 +7,7 @@ import { sanitizeStorageKey, } from '../../util/minecraftUtil' import { Variant } from '../../variants' -import type { IItemDefinition } from '../minecraft/itemDefinitions' +import type { IItemDefinition, TintSource } from '../minecraft/itemDefinitions' import { type ITextureAtlas } from '../minecraft/textureAtlas' import type { IRenderedNodes, IRenderedRig, IRenderedVariantModel } from '../rigRenderer' @@ -119,9 +119,14 @@ const compileResourcePack: ResourcePackCompiler = async ({ let itemDefinition: IItemDefinition if (Object.values(rig.variants).length === 1) { - itemDefinition = createSingleVariantItemDefinition(model) + itemDefinition = createSingleVariantItemDefinition(model, bone.itemModelProperties) } else { - itemDefinition = createMultiVariantItemDefinition(boneUuid, model, rig) + itemDefinition = createMultiVariantItemDefinition( + boneUuid, + model, + rig, + bone.itemModelProperties + ) } versionedFiles.set(exportPath, { content: autoStringify(itemDefinition) }) @@ -146,12 +151,22 @@ const compileResourcePack: ResourcePackCompiler = async ({ export default compileResourcePack -function createSingleVariantItemDefinition(model: IRenderedVariantModel): IItemDefinition { +function createSingleVariantItemDefinition( + model: IRenderedVariantModel, + itemModelProperties?: { tints: TintSource[] } +): IItemDefinition { + let tints: TintSource[] + if (itemModelProperties?.tints.length) { + tints = itemModelProperties.tints + } else { + tints = [new oneLiner({ type: 'minecraft:dye', default: [1, 1, 1] })] + } + return { model: { type: 'minecraft:model', model: model.resource_location, - tints: [new oneLiner({ type: 'minecraft:dye', default: [1, 1, 1] })], + tints, }, } } @@ -159,8 +174,16 @@ function createSingleVariantItemDefinition(model: IRenderedVariantModel): IItemD function createMultiVariantItemDefinition( boneUUID: string, model: IRenderedVariantModel, - rig: IRenderedRig + rig: IRenderedRig, + itemModelProperties?: { tints: TintSource[] } ): IItemDefinition { + let tints: TintSource[] + if (itemModelProperties?.tints.length) { + tints = itemModelProperties.tints + } else { + tints = [new oneLiner({ type: 'minecraft:dye', default: [1, 1, 1] })] + } + const itemDefinition: IItemDefinition & { model: { type: 'minecraft:select'; property: 'minecraft:custom_model_data' } } = { @@ -176,7 +199,7 @@ function createMultiVariantItemDefinition( fallback: { type: 'minecraft:model', model: model.resource_location, - tints: [new oneLiner({ type: 'minecraft:dye', default: [1, 1, 1] })], + tints, }, }, } @@ -189,13 +212,13 @@ function createMultiVariantItemDefinition( model: { type: 'minecraft:model', model: variantModel.resource_location, - tints: [new oneLiner({ type: 'minecraft:dye', default: [1, 1, 1] })], + tints, }, } as (typeof itemDefinition.model.cases)[0]) } if (itemDefinition.model.cases.length === 0) { - return createSingleVariantItemDefinition(model) + return createSingleVariantItemDefinition(model, itemModelProperties) } return itemDefinition diff --git a/src/systems/rigRenderer.ts b/src/systems/rigRenderer.ts index 6a5945fd..30c75325 100644 --- a/src/systems/rigRenderer.ts +++ b/src/systems/rigRenderer.ts @@ -7,6 +7,7 @@ import type { IBlueprintLocatorConfigJSON, IBlueprintVariantJSON, } from '../formats/blueprint' +import { Interaction } from '../outliner/interaction' import { type Alignment, TextDisplay } from '../outliner/textDisplay' import { VanillaBlockDisplay } from '../outliner/vanillaBlockDisplay' import { type ItemDisplayMode, VanillaItemDisplay } from '../outliner/vanillaItemDisplay' @@ -24,6 +25,7 @@ import { updatePreview, } from './animationRenderer' import { IntentionalExportError } from './errors' +import type { TintSource } from './minecraft/itemDefinitions' export interface IRenderedFace { uv: number[] @@ -117,6 +119,9 @@ export interface IDisplayEntityConfigs { export interface IRenderedNodes { Bone: IRenderedDisplayEntityNode & { type: 'bone' + itemModelProperties?: { + tints: TintSource[] + } } TextDisplay: IRenderedDisplayEntityNode & { type: 'text_display' @@ -228,8 +233,8 @@ function renderCube(cube: Cube, rig: IRenderedRig, model: IRenderedModel) { if (cube.shade === false) element.shade = false - // If target version is 1.21.9 or higher, we can use free rotation - if (!compareVersions('1.21.9', rig.target_minecraft_version)) { + // If target version is 1.21.11 or higher, we can use free rotation + if (!compareVersions('1.21.11', rig.target_minecraft_version)) { element.rotation = { x: cube.rotation[0], y: cube.rotation[1], @@ -395,6 +400,9 @@ function renderGroup( base_scale: 1, configs: structuredClone(group.configs), on_summon_function: group.onSummonFunction?.trim(), + itemModelProperties: group.itemModelProperties + ? structuredClone(group.itemModelProperties) + : undefined, // This is a placeholder value that will be updated later once the animation renderer is run. default_transform: {} as INodeTransform, } @@ -440,6 +448,10 @@ function renderGroup( renderBlockDisplay(node, rig) break } + case node instanceof Interaction: { + renderInteraction(node, rig) + break + } case node instanceof Cube: { renderCube(node, rig, groupModel.model!) rig.includes_custom_models = true @@ -602,7 +614,7 @@ function renderLocator(locator: Locator, rig: IRenderedRig) { rig.nodes[locator.uuid] = renderedLocator } -function renderInteraction(box: BoundingBox, rig: IRenderedRig) { +function renderInteraction(box: Interaction, rig: IRenderedRig) { if (!box.export) return const parentId = box.parent instanceof Group ? box.parent.uuid : undefined @@ -612,12 +624,11 @@ function renderInteraction(box: BoundingBox, rig: IRenderedRig) { storage_name: sanitizeStorageKey(box.name), uuid: box.uuid, parent: parentId, - // @ts-expect-error - Broken BB types config: structuredClone(box.config), max_distance: 0, default_transform: {} as INodeTransform, - width: box.to[0] - box.from[0], - height: box.to[1] - box.from[1], + width: box.scale[0] / 16, + height: box.scale[1] / 16, } rig.nodes[box.uuid] = renderedInteraction @@ -820,7 +831,7 @@ export function renderRig(modelExportFolder: string, textureExportFolder: string renderBlockDisplay(node, rig) break } - case node instanceof BoundingBox: { + case node instanceof Interaction: { renderInteraction(node, rig) break } diff --git a/src/tests/pass.test.ts b/src/tests/pass.test.ts new file mode 100644 index 00000000..982a17e2 --- /dev/null +++ b/src/tests/pass.test.ts @@ -0,0 +1,3 @@ +import { test } from 'vitest' + +test.todo('should pass') diff --git a/src/util/excludedNodes.ts b/src/util/excludedNodes.ts index 831418fd..5d549c82 100644 --- a/src/util/excludedNodes.ts +++ b/src/util/excludedNodes.ts @@ -1,3 +1,4 @@ +import { Interaction } from '../outliner/interaction' import { TextDisplay } from '../outliner/textDisplay' import { VanillaBlockDisplay } from '../outliner/vanillaBlockDisplay' import { VanillaItemDisplay } from '../outliner/vanillaItemDisplay' @@ -22,6 +23,7 @@ export function getAvailableNodes( allNodes.push( ...Locator.all, ...TextDisplay.all, + ...Interaction.all, ...VanillaItemDisplay.all, ...VanillaBlockDisplay.all, // @ts-expect-error - Broken BB types @@ -34,28 +36,8 @@ export function getAvailableNodes( entry.name = node.name } - let icon: string - switch (true) { - case node instanceof Group: - icon = 'folder' - break - case node instanceof Locator: - icon = 'anchor' - break - case node instanceof TextDisplay: - case node instanceof VanillaItemDisplay: - case node instanceof VanillaBlockDisplay: - icon = node.icon - break - case node instanceof OutlinerElement.types.camera: - icon = 'videocam' - break - default: - icon = 'close' - break - } - - return { icon, name: node.name, value: node.uuid } + // @ts-expect-error - Broken BB types + return { icon: node.icon, name: node.name, value: node.uuid } }) return availableNodes