From 1f8f037d374027837c008c759c24c79cb38361c1 Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Tue, 11 Nov 2025 11:22:55 -0500 Subject: [PATCH 01/23] =?UTF-8?q?=F0=9F=90=9B=20Fix=20translation=20keys?= =?UTF-8?q?=20being=20used=20when=20a=20language=20other=20than=20English?= =?UTF-8?q?=20is=20missing=20a=20translation=20that=20is=20available=20in?= =?UTF-8?q?=20English.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/translation.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/util/translation.ts b/src/util/translation.ts index 5016fac3..cd75b5ae 100644 --- a/src/util/translation.ts +++ b/src/util/translation.ts @@ -17,6 +17,19 @@ export function translate(key: string, ...args: string[]) { if (translation) { return translation.replace(/\{(\d+)\}/g, (str, index) => args[index] || '') } else { + const enIndex = FILE_NAMES.indexOf('en') + if (languageIndex !== enIndex) { + const langEn = LANGUAGES[enIndex] as Record + const translationEn = langEn[key] + if (translationEn) { + console.warn( + `Missing translation for '${key}' in '${ + settings.language.value as string + }', falling back to English` + ) + return langEn[key].replace(/\{(\d+)\}/g, (str, index) => args[index] || '') + } + } console.warn(`Could not find translation for '${key}'`) return key } From 5fe68937709d2128dc1ab1a6fb47140e798a8445 Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Tue, 11 Nov 2025 11:29:00 -0500 Subject: [PATCH 02/23] =?UTF-8?q?=E2=9C=A8=20Add=20support=20for=201.21.11?= =?UTF-8?q?=20rotation=20freedom?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/formats/blueprint/index.ts | 9 +++++---- src/formats/blueprint/settings.ts | 2 +- src/lang/en.yml | 2 +- src/systems/datapackCompiler/mcbFiles.ts | 6 ++++++ src/systems/global.ts | 1 + src/systems/resourcepackCompiler/index.ts | 1 + src/systems/util.ts | 5 +++++ src/util/minecraftUtil.ts | 4 ++++ 8 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/formats/blueprint/index.ts b/src/formats/blueprint/index.ts index 435c3e1b..869d5502 100644 --- a/src/formats/blueprint/index.ts +++ b/src/formats/blueprint/index.ts @@ -405,10 +405,11 @@ export function updateRotationConstraints() { return } - // Rotation is always limited when selecting an element - format.rotation_limit = !hasNonElementSelection() - if (!projectTargetVersionIsAtLeast('1.21.6') /* < 1.21.6 */) { - // But only snaps to 22.5 degree increments on versions before 1.21.6 + if (!projectTargetVersionIsAtLeast('1.21.11')) { + // Rotation is limited to one axis, and between -45 and 45 degrees on versions before 1.21.11 + format.rotation_limit = !hasNonElementSelection() + } else if (!projectTargetVersionIsAtLeast('1.21.6') /* < 1.21.6 */) { + // Rotation is snapped to 22.5 degree increments on versions before 1.21.6 format.rotation_snap = format.rotation_limit } } diff --git a/src/formats/blueprint/settings.ts b/src/formats/blueprint/settings.ts index e3442945..223d8da5 100644 --- a/src/formats/blueprint/settings.ts +++ b/src/formats/blueprint/settings.ts @@ -46,7 +46,7 @@ export const defaultValues: BlueprintSettings = { enable_plugin_mode: false, resource_pack_export_mode: 'folder' as ExportMode, data_pack_export_mode: 'folder' as ExportMode, - target_minecraft_version: SUPPORTED_MINECRAFT_VERSIONS['1.21.9'], + target_minecraft_version: SUPPORTED_MINECRAFT_VERSIONS['1.21.11'], // Resource Pack Settings display_item: 'minecraft:white_dye', diff --git a/src/lang/en.yml b/src/lang/en.yml index e958af20..cd291c11 100644 --- a/src/lang/en.yml +++ b/src/lang/en.yml @@ -718,7 +718,7 @@ animated_java.misc.failed_to_export.invalid_rotation.message: |- animated_java.misc.failed_to_export.invalid_rotation.message_post_1_21_6: |- Some cubes in your model have an invalid rotations! - Cubes can only be rotated on a single axis at a time when targeting Minecraft 1.21.6 or later. + Cubes can only be rotated on a single axis at a time when targeting Minecraft 1.21.6 through 1.21.10. If you want to rotate a cube on multiple axes, put it in a bone and rotate the bone instead. diff --git a/src/systems/datapackCompiler/mcbFiles.ts b/src/systems/datapackCompiler/mcbFiles.ts index a6a2b85d..d214e373 100644 --- a/src/systems/datapackCompiler/mcbFiles.ts +++ b/src/systems/datapackCompiler/mcbFiles.ts @@ -28,6 +28,12 @@ interface MCBFiles { } const MCB_FILES: Record = { + '1.21.11': { + animation: ANIMATION_1_21_4, + static: STATIC_1_21_4, + global: GLOBAL_1_21_5, + globalTemplates: GLOBAL_TEMPLATES_1_20_4, + }, '1.21.9': { animation: ANIMATION_1_21_4, static: STATIC_1_21_4, diff --git a/src/systems/global.ts b/src/systems/global.ts index 8e322dbf..58131e97 100644 --- a/src/systems/global.ts +++ b/src/systems/global.ts @@ -10,6 +10,7 @@ export enum SUPPORTED_MINECRAFT_VERSIONS { '1.21.5' = '1.21.5', '1.21.6' = '1.21.6', '1.21.9' = '1.21.9', + '1.21.11' = '1.21.11', } type OldSerializedAJMeta = Record< diff --git a/src/systems/resourcepackCompiler/index.ts b/src/systems/resourcepackCompiler/index.ts index d80d0f10..6afb9d25 100644 --- a/src/systems/resourcepackCompiler/index.ts +++ b/src/systems/resourcepackCompiler/index.ts @@ -13,6 +13,7 @@ const VERSIONED_RESOURCE_PACK_COMPILERS: Record< SUPPORTED_MINECRAFT_VERSIONS, ResourcePackCompiler > = { + '1.21.11': EXPORT_1_21_4, '1.21.9': EXPORT_1_21_4, '1.21.6': EXPORT_1_21_4, '1.21.5': EXPORT_1_21_4, diff --git a/src/systems/util.ts b/src/systems/util.ts index f11f25c0..3fc03434 100644 --- a/src/systems/util.ts +++ b/src/systems/util.ts @@ -93,6 +93,11 @@ export const unzip = (data: Uint8Array, options: AsyncUnzipOptions) => { } export function isCubeValid(cube: Cube): '1.21.6+' | 'valid' | 'invalid' { + if (projectTargetVersionIsAtLeast('1.21.11')) { + // Rotations are unrestricted on 1.21.11+ + return 'valid' + } + const totalRotation = cube.rotation[0] + cube.rotation[1] + cube.rotation[2] if (totalRotation === 0) return 'valid' diff --git a/src/util/minecraftUtil.ts b/src/util/minecraftUtil.ts index ca94a908..ca28e262 100644 --- a/src/util/minecraftUtil.ts +++ b/src/util/minecraftUtil.ts @@ -345,6 +345,8 @@ export function getDataPackFormat(version: SUPPORTED_MINECRAFT_VERSIONS): number return 80 case SUPPORTED_MINECRAFT_VERSIONS['1.21.9']: return 88.0 + case SUPPORTED_MINECRAFT_VERSIONS['1.21.11']: + return 93.1 default: console.warn(`Unknown Minecraft version: ${version}`) return Infinity @@ -367,6 +369,8 @@ export function getResourcePackFormat(version: SUPPORTED_MINECRAFT_VERSIONS): nu return 63 case SUPPORTED_MINECRAFT_VERSIONS['1.21.9']: return 69.0 + case SUPPORTED_MINECRAFT_VERSIONS['1.21.11']: + return 74.0 default: console.warn(`Unknown Minecraft version: ${version}`) return Infinity From b5414f87595b8442e742bd3ee13e6921e8064e25 Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Thu, 18 Dec 2025 09:30:37 -0500 Subject: [PATCH 03/23] =?UTF-8?q?=F0=9F=A9=B9=20Fixed=20incorrect=20target?= =?UTF-8?q?=20version=20description?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #475 --- src/lang/en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/en.yml b/src/lang/en.yml index cd291c11..d2649f4f 100644 --- a/src/lang/en.yml +++ b/src/lang/en.yml @@ -133,7 +133,7 @@ animated_java.dialog.blueprint_settings.target_minecraft_version.description: |- If the version you are using is not available, select the closest version below it. - E.g If you are using `1.21.8`, select `1.21.5`. + E.g If you are using `1.21.10`, select `1.21.9`. Some features may be altered, or unavailable depending on the selected version. From 5925a7812f9a5e7154cb0115505630ae591ce1c2 Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Thu, 18 Dec 2025 09:33:30 -0500 Subject: [PATCH 04/23] =?UTF-8?q?=E2=9C=A8=20Added=20back=20support=20for?= =?UTF-8?q?=201.21.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #472 --- src/blockbenchTypeMods.d.ts | 4 + .../datapackCompiler/1.21.0/animation.mcb | 1136 +++++++++++++++++ .../datapackCompiler/1.21.0/static.mcb | 693 ++++++++++ src/systems/datapackCompiler/mcbFiles.ts | 9 + src/systems/global.ts | 1 + src/systems/resourcepackCompiler/index.ts | 1 + src/util/minecraftUtil.ts | 10 +- 7 files changed, 1848 insertions(+), 6 deletions(-) create mode 100644 src/systems/datapackCompiler/1.21.0/animation.mcb create mode 100644 src/systems/datapackCompiler/1.21.0/static.mcb diff --git a/src/blockbenchTypeMods.d.ts b/src/blockbenchTypeMods.d.ts index 47416cf5..a7b8df22 100644 --- a/src/blockbenchTypeMods.d.ts +++ b/src/blockbenchTypeMods.d.ts @@ -1,3 +1,7 @@ +declare global { + const fs: typeof import('fs') +} + declare module 'three' { interface Object3D { isVanillaItemModel?: boolean diff --git a/src/systems/datapackCompiler/1.21.0/animation.mcb b/src/systems/datapackCompiler/1.21.0/animation.mcb new file mode 100644 index 00000000..f1de4b3d --- /dev/null +++ b/src/systems/datapackCompiler/1.21.0/animation.mcb @@ -0,0 +1,1136 @@ +# TODO - Move all internal functions into an internal namespace, and only have user-facing functions in the main `animated_java:<%export_namespace%>` namespace. +import ../global.mcbt + +function on_load { + data modify storage <%project_storage%> rig_hash set value <%"'" + rig_hash + "'"%> + + IF (use_storage_for_animation) { + REPEAT (animations) as animation { + data remove storage <%project_storage%>/animations <%animation.storage_name%> + } + <%animationStorage.join('\n')%> + } + <%% + animations.forEach(animation => { + emit(`scoreboard objectives add ${OBJECTIVES.FRAME(animation.storage_name)} dummy`) + }) + %%> +} + +function remove_animation_objectives { + <%% + animations.forEach(animation => { + emit(`scoreboard objectives remove ${OBJECTIVES.FRAME(animation.storage_name)}`) + }) + %%> + tellraw @a <%TELLRAW.UNINSTALL()%> +} + +function invalid_version_warning { + # This function will contain a tellraw if the datapack is loaded in the wrong version. +} + +dir root { + function on_tick { + execute unless entity @s[tag=<%TAGS.PROJECT_ROOT(export_namespace)%>] run return 0 + + # Custom pre-tick function + IF (on_pre_tick_function) { + <%% + emit.mcb(on_pre_tick_function) + %%> + } + + # Once we have more than 8 animations, calling a function only if at least one animation is playing is more efficient. + IF (animations.length > 8) { + # If no animations are playing, we can skip all animation logic. + # This helps reduce ticking commands for rigs that are idle. + execute \ + unless entity @s[<%animations.map(anim => 'tag=!' + TAGS.ANIMATION_PLAYING(export_namespace, anim.storage_name)).join(',')%>] \ + run block tick_animations { + REPEAT (animations) as animation { + execute if entity @s[tag=<%TAGS.ANIMATION_PLAYING(export_namespace, animation.storage_name)%>] run \ + function *<%export_namespace%>/animations/<%animation.storage_name%>/zzz/on_tick + } + } + } ELSE { + REPEAT (animations) as animation { + execute if entity @s[tag=<%TAGS.ANIMATION_PLAYING(export_namespace, animation.storage_name)%>] run \ + function *<%export_namespace%>/animations/<%animation.storage_name%>/zzz/on_tick + } + } + + IF (auto_update_rig_orientation) { + IF (has_locators || has_cameras) { + execute \ + on passengers \ + if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ + run function ./on_tick/transform_floating_entities + } + execute on passengers run tp @s ~ ~ ~ ~ ~ + } ELSE IF (has_ticking_locators) { + execute \ + on passengers \ + if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ + run function ./on_tick/transform_locators + } + + # Custom post-tick function + IF (on_post_tick_function) { + <%% + emit.mcb(on_post_tick_function) + %%> + } + } + + IF (has_locators || has_cameras) { + dir on_tick { + function transform_locators { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { + block select_locator_<%locator.storage_name%> { with entity @s data.locators.<%locator.storage_name%> + IF (locator.config?.use_entity) { + $execute \ + as $(uuid) \ + positioned ^$(px) ^$(py) ^$(pz) \ + rotated ~$(ry) ~$(rx) \ + run block as_locator_<%locator.storage_name%> { + tp @s ~ ~ ~ ~ ~ + + IF (locator.config?.sync_passenger_rotation) { + execute on passengers run tp @s ~ ~ ~ ~ ~ + } + + IF (locator.config?.on_tick_function) { + <%% + emit.mcb(locator.config.on_tick_function) + %%> + } + } + } ELSE IF (locator.config?.on_tick_function) { + $execute \ + positioned ^$(px) ^$(py) ^$(pz) \ + rotated ~$(ry) ~$(rx) \ + run block at_locator_<%locator.storage_name%> { + <%% + emit.mcb(locator.config.on_tick_function) + %%> + } + } + } + } + } + + function transform_floating_entities { + function ./transform_locators + + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'camera')) as camera { + block select_camera_<%camera.storage_name%> { with entity @s data.cameras.<%camera.storage_name%> + $execute \ + as $(uuid) \ + positioned ^$(px) ^$(py) ^$(pz) \ + rotated ~$(ry) ~$(rx) \ + run tp @s ~ ~ ~ ~ ~ + } + } + } + } + } +} + +IF (!auto_update_rig_orientation) { + function move { + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + tp @s ~ ~ ~ ~ ~ + + IF (has_locators || has_cameras) { + execute \ + at @s on passengers \ + if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ + run function ./on_tick/transform_floating_entities + } + execute at @s on passengers run tp @s ~ ~ ~ ~ ~ + } +} ELSE { + function move { + tellraw @a <%TELLRAW.AUTO_UPDATE_RIG_ORIENTATION_MOVE_WARNING()%> + } +} + +dir animations { + REPEAT (animations) as animation { + dir <%animation.storage_name%> { + function play { + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + function *<%export_namespace%>/animations/pause_all + + tag @s add <%TAGS.ANIMATION_PLAYING(export_namespace, 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 stop { + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + function *<%export_namespace%>/animations/pause_all + + 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 pause { + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + tag @s remove <%TAGS.ANIMATION_PLAYING(export_namespace, animation.storage_name)%> + } + + function resume { + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + tag @s add <%TAGS.ANIMATION_PLAYING(export_namespace, animation.storage_name)%> + } + + function next_frame { + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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)%> + execute at @s run function ./zzz/apply_frame with storage <%temp_storage%> args + scoreboard players add @s <%OBJECTIVES.FRAME(animation.storage_name)%> 1 + } + + function set_frame { + # Sets the frame without interpolation + #ARGS: {frame: int} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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 + } + + function apply_frame { + #ARGS: {frame: int} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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/apply_frame with storage <%temp_storage%> args + } + + function tween { + # Attempts to smoothly transition from the currently playing animation into this one. + #ARGS: {duration: int, to_frame: int} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + function *<%export_namespace%>/animations/pause_all + + tag @s add <%TAGS.ANIMATION_PLAYING(export_namespace, animation.storage_name)%> + $scoreboard players set @s <%OBJECTIVES.TWEEN_DURATION()%> $(duration) + $scoreboard players set @s <%OBJECTIVES.FRAME(animation.storage_name)%> $(to_frame) + + scoreboard players operation #this <%OBJECTIVES.I()%> = @s <%OBJECTIVES.TWEEN_DURATION()%> + tag @s add <%TAGS.TRANSFORMS_ONLY()%> + execute at @s run function ./zzz/apply_frame {frame: 0} + $execute at @s run function ./zzz/apply_frame {frame: $(to_frame)} + tag @s remove <%TAGS.TRANSFORMS_ONLY()%> + execute on passengers store result entity @s interpolation_duration int 1 run scoreboard players get #this <%OBJECTIVES.I()%> + } + + dir zzz { + function on_tick { + # Tweening logic + scoreboard players remove @s <%OBJECTIVES.TWEEN_DURATION()%> 1 + execute if score @s <%OBJECTIVES.TWEEN_DURATION()%> matches 1.. run return 1 + execute if score @s <%OBJECTIVES.TWEEN_DURATION()%> matches 0 on passengers run \ + data modify entity @s interpolation_duration set value <%interpolation_duration%> + # Animation logic + IF (animation.loop_mode === 'loop' && animation.loop_delay === 0) { + # Makes sure function keyframes in the last frame of the animation are activated. + execute if score @s <%OBJECTIVES.FRAME(animation.storage_name)%> matches -1 run \ + block function_keyframe_loop_patch { + function ./apply_frame {frame: <%animation.duration-1%>} + scoreboard players add @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)%> + function ./apply_frame with storage <%temp_storage%> args + IF (animation.loop_mode === 'loop') { + # Loop the animation back to the start once it reaches the last frame. + # If loop_delay is 0, the animation will loop instantly, otherwise, it will wait for the specified amount of ticks. + execute \ + if score @s <%OBJECTIVES.FRAME(animation.storage_name)%> \ + matches <%animation.duration-2 + animation.loop_delay%>.. \ + run return run \ + scoreboard players set @s <%OBJECTIVES.FRAME(animation.storage_name)%> <%animation.loop_delay === 0 ? -1 : 0%> + } ELSE IF (animation.loop_mode === 'hold') { + # Pause the animation at the last frame. + execute \ + if score @s <%OBJECTIVES.FRAME(animation.storage_name)%> \ + matches <%animation.duration-1%>.. \ + run return run \ + function ../pause + } ELSE IF (animation.loop_mode === 'once') { + # Stop the animation once it reaches the last frame. + execute \ + if score @s <%OBJECTIVES.FRAME(animation.storage_name)%> \ + matches <%animation.duration-1%> \ + run return run block loop_mode_stop { + 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()%> + } + } + scoreboard players add @s <%OBJECTIVES.FRAME(animation.storage_name)%> 1 + } + + IF (use_storage_for_animation) { + function set_frame { + #ARGS: {frame: int} + $function ./apply_frame {frame: $(frame)} + execute on passengers run data modify entity @s[tag=!<%TAGS.GLOBAL_DATA()%>] start_interpolation set value -1 + return 1 + } + + function apply_frame { + #ARGS: {frame: int} + REPEAT (Object.values(animation.modified_nodes).sort(nodeSorter)) as node { + IF (BONE_TYPES.includes(node.type)) { + $execute on passengers if entity @s[tag=<%TAGS.PROJECT_NODE_NAMED(export_namespace, node.storage_name)%>] run \ + data modify entity @s {} merge from \ + storage <%project_storage%>/animations <%animation.storage_name%>.$(frame).<%node.type.charAt(0) + '_' + node.storage_name%> + } ELSE IF (node.type === 'locator' || node.type === 'camera') { + $execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run \ + $data modify entity @s data.uuids_by_name.<%node.type + '_' + node.storage_name%> merge from \ + storage <%project_storage%>/animations <%animation.storage_name%>.$(frame).<%node.type.charAt(0) + '_' + node.storage_name%> + } + } + + IF (animation.frames.some(anim => anim.variant)) { + $execute \ + if data storage <%project_storage%>/animations <%animation.storage_name%>.$(frame).variant \ + unless entity @s[tag=<%TAGS.TRANSFORMS_ONLY()%>] \ + run { with storage <%project_storage%>/animations <%animation.storage_name%>.$(frame).variant + #ARGS: {name: string, condition: string} + $execute $(condition)run function *<%export_namespace%>/variants/$(name)/apply + } + } + + return 1 + } + } ELSE { + function set_frame { + # Sets the frame without interpolation + #ARGS: {frame: int} + $execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run \ + function ./frames/$(frame) with entity @s data.uuids_by_name + + execute on passengers if entity @s[tag=!<%TAGS.GLOBAL_DATA()%>] run \ + data modify entity @s start_interpolation set value -1 + + return 1 + } + + function apply_frame { + #ARGS: {frame: int} + $execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run \ + function ./frames/$(frame) with entity @s data.uuids_by_name + + return 1 + } + + # FIXME - %NEWLINE_PATCH% is a temporary solution to temporarily fix an MCB bug where extra newlines are being added to the output. + dir frames { + <%% + // A record of node uuid to INodeTransform. + // Keeps track of the last time a bone was updated. + // Only used for step keyframe interpolation. + let hasFunction = false + 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: {}} + let frameFunc = ``; + for (const node of modifiedNodes) { + const transform = frame.node_transforms[node.uuid] + // Skip if the node doesn't have a transform for this frame. + if (!transform) continue + switch (node.type) { + case 'bone': + case 'text_display': + case 'item_display': + case 'block_display': { + const lastFrame = lastActiveFrame[node.uuid] + const isStepInterpolation = !!(lastFrame?.interpolation === 'step') + lastActiveFrame[node.uuid] = transform + + if (transform.interpolation === 'pre-post' || isStepInterpolation) { + frameFunc += + `\n$data merge entity $(${node.type + '_' + node.storage_name})%NEWLINE_PATCH%{` + + `transformation: ${matrixToNbtFloatArray(transform.matrix).toString()},` + + `start_interpolation: ${isStepInterpolation ? -1 : 0},` + + `interpolation_duration: ${isStepInterpolation ? 0 : interpolation_duration}` + + `}` + } else { + frameFunc += + `\n$data merge entity $(${node.type + '_' + node.storage_name})%NEWLINE_PATCH%{` + + `transformation: ${matrixToNbtFloatArray(transform.matrix).toString()},` + + `start_interpolation: 0,` + + `interpolation_duration: ${interpolation_duration}` + + `}` + } + ;hasFunction = true + break + } + case 'locator': { + const lastFrame = lastActiveFrame[node.uuid] + lastActiveFrame[node.uuid] = transform + ;if (!lastFrame || matrixToNbtFloatArray(transform.matrix).toString() !== matrixToNbtFloatArray(lastFrame.matrix).toString()) { + to_merge.locators[node.storage_name] = { + px: transform.pos[0], + py: transform.pos[1], + pz: transform.pos[2], + ry: transform.head_rot[1], + rx: transform.head_rot[0] + }; + } + + if (transform.function) { + if (node.config?.use_entity) { + frameFunc += + `\n$execute on vehicle unless entity @s[tag=${TAGS.TRANSFORMS_ONLY()}] as $(${node.type + '_' + 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}_locator_${node.storage_name}%NEWLINE_PATCH%{\n` + + `tp @s ~ ~ ~ ~ ~\n` + + `${transform.function}` + + `\n}` + } else { + frameFunc += + `\nexecute on vehicle unless entity @s[tag=${TAGS.TRANSFORMS_ONLY()}] ` + + `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}_locator_${node.storage_name}%NEWLINE_PATCH%{\n` + + `${transform.function}` + + `\n}` + } + } + break + } + case 'camera': { + const lastFrame = lastActiveFrame[node.uuid] + lastActiveFrame[node.uuid] = transform + ;if (!lastFrame || matrixToNbtFloatArray(transform.matrix).toString() !== matrixToNbtFloatArray(lastFrame.matrix).toString()) { + to_merge.cameras[node.storage_name] = { + px: transform.pos[0], + py: transform.pos[1], + pz: transform.pos[2], + ry: transform.head_rot[1], + rx: transform.head_rot[0] + }; + } + ;break + } + } + } + + if (Object.keys(to_merge.locators).length > 0 || Object.keys(to_merge.cameras).length > 0) { + frameFunc += `\ndata modify entity @s data merge value ${JSON.stringify(to_merge)}` + if (!auto_update_rig_orientation) { + frameFunc += `\nfunction ./on_tick/transform_floating_entities` + } + hasFunction = true + } + + if (frame.variants?.length) { + const variant = rig.variants[frame.variants[0]] + if (!variant) { + throw new Error(`Could not find Variant with uuid "${frame.variants[0]}" while generating frame "${frameIndex}" of animation "${animation.name}".`) + } + const execute_condition = frame.variants_execute_condition ? frame.variants_execute_condition + ' ' : '' + frameFunc += `\nexecute on vehicle unless entity @s[tag=${TAGS.TRANSFORMS_ONLY()}] ${execute_condition}run function *${export_namespace}/variants/${variant.name}/apply` + ;hasFunction = true + } + + // Root function keyframes. + if (frame.function) { + const execute_condition = frame.function_execute_condition ? frame.function_execute_condition + ' ' : '' + frameFunc += `\nexecute on vehicle unless entity @s[tag=${TAGS.TRANSFORMS_ONLY()}] at @s ${execute_condition}run block ${frameIndex}_root_function%NEWLINE_PATCH%{\n${frame.function}\n}` + ;hasFunction = true + } + ;if (frameFunc.length > 0) { + frameFunc = `function ${frameIndex}%NEWLINE_PATCH%{${frameFunc}\n}` + emit.mcb(frameFunc.replaceAll(/%NEWLINE_PATCH%\n?/g, ' ')) + } + } + %%> + } + } + } + } + } + function pause_all { + # Pauses all animations + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + REPEAT (animations) as animation { + tag @s remove <%TAGS.ANIMATION_PLAYING(export_namespace, animation.storage_name)%> + } + } +} + +function summon { + #Args: {args:{variant: string, animation: string, frame: int, start_animation: boolean}} + # frame is ignored unless animation is specified. + + data modify storage <%temp_storage%> args set value {variant:'', animation:'', frame: 0} + $execute store success score #success <%OBJECTIVES.I()%> run data modify storage <%temp_storage%> args set value $(args) + + summon minecraft:item_display ~ ~ ~ { \ + Tags:[ \ + '<%TAGS.NEW()%>', \ + '<%TAGS.GLOBAL_ENTITY()%>', \ + '<%TAGS.GLOBAL_ROOT()%>', \ + '<%TAGS.PROJECT_ENTITY(export_namespace)%>', \ + '<%TAGS.PROJECT_ROOT(export_namespace)%>' \ + ], \ + teleport_duration: 0, \ + interpolation_duration: <%interpolation_duration%>, \ + Passengers:<%root_entity_passengers%>, \ + } + execute as @n[ \ + type=minecraft:item_display, \ + tag=<%TAGS.PROJECT_ROOT(export_namespace)%>, \ + tag=<%TAGS.NEW()%>, \ + distance=..0.01 \ + ] run block zzz/summon/as_root_entity { + execute store result score @s <%OBJECTIVES.ID()%> run scoreboard players add aj.last_id <%OBJECTIVES.ID()%> 1 + # Align the position and rotation of the root with the command context. + tp @s ~ ~ ~ ~ ~ + + function *global/gu/get_entity_uuid_string + execute at @s on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block as_data_entity { + # Rig Root UUID + data modify entity @s data.uuids append from storage <%gu_storage%> out + # Data Entity UUID + function *global/gu/get_entity_uuid_string + data modify entity @s data.uuids append from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.data_data 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)%> \ + ^<%roundTo(locator.default_transform.pos[1], 10)%> \ + ^<%roundTo(locator.default_transform.pos[2], 10)%> \ + {Tags:<%getNodeTags(locator, rig)%>} + execute \ + as @n[ \ + type=<%locator.config.entity_type%>, \ + tag=<%TAGS.PROJECT_LOCATOR_NAMED(export_namespace, locator.storage_name)%>, \ + tag=<%TAGS.NEW()%>, \ + distance=..<%Math.ceil(locator.max_distance)%> \ + ] \ + run block as_locator/<%locator.storage_name%> { + # run block ../as_locator/<%locator.storage_name%> { + tag @s remove <%TAGS.NEW()%> + function *global/gu/get_entity_uuid_string + } + data modify entity @s data.uuids append from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%locator.type + '_' + locator.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.locators.<%locator.storage_name%>.uuid set from storage <%gu_storage%> out + } + + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'camera')) as camera { + summon minecraft:item_display \ + ^<%roundTo(camera.default_transform.pos[0], 10)%> \ + ^<%roundTo(camera.default_transform.pos[1], 10)%> \ + ^<%roundTo(camera.default_transform.pos[2], 10)%> \ + {Tags:<%getNodeTags(camera, rig)%>, teleport_duration: 2} + execute \ + as @n[ \ + type=minecraft:item_display, \ + tag=<%TAGS.PROJECT_CAMERA_NAMED(export_namespace, camera.storage_name)%>, \ + tag=<%TAGS.NEW()%>, \ + distance=..<%Math.ceil(camera.max_distance)%> \ + ] \ + run block as_camera/<%camera.storage_name%> { + # run block ../as_camera/<%camera.storage_name%> { + tag @s remove <%TAGS.NEW()%> + function *global/gu/get_entity_uuid_string + } + data modify entity @s data.uuids append from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%camera.type + '_' + camera.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.cameras.<%camera.storage_name%>.uuid set from storage <%gu_storage%> out + } + + REPEAT (Object.values(rig.nodes).filter(node => BONE_TYPES.includes(node.type))) as node { + execute \ + on vehicle \ + on passengers \ + if entity @s[tag=<%TAGS.PROJECT_NODE_NAMED(export_namespace, node.storage_name)%>] \ + run \ + function *global/gu/get_entity_uuid_string + + data modify entity @s data.uuids append from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%node.type + '_' + node.storage_name%> set from storage <%gu_storage%> out + } + } + + # Variant Arguement + IF (Object.keys(rig.variants).length > 1) { + execute if data storage <%temp_storage%> args.variant run block variant_arg/process { with storage <%temp_storage%> args + scoreboard players set #success <%OBJECTIVES.I()%> 0 + # If the variant argument is *explicitly* set to an empty string, return an error. + execute if data storage <%temp_storage%> {args:{variant:''}} run return run block if_empty { + # Tell the user that the variant cannot be empty. + tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('variant')%> + function *<%export_namespace%>/remove/this/without_on_remove_function + } + # Attempt to apply the requested variant. + # We get the success of the `try_apply` function in just in case the user's arguments are *very* wrong. + execute store success score #success <%OBJECTIVES.I()%> run block try_apply { with storage <%temp_storage%> args + $execute if function animated_java:<%export_namespace%>/variants/$(variant)/apply run return 1 + # If the apply function fails, the variant doesn't exist, so we return an error. + return fail + } + # If the apply function failed, return an error. + execute unless score #success <%OBJECTIVES.I()%> matches 1 run return run block invalid_variant { + # Tell the user that the provided variant doesn't exist, remove the rig, and list all available variants for this rig. + tellraw @a <%TELLRAW.INVALID_VARIANT(rig.variants)%> + function *<%export_namespace%>/remove/this/without_on_remove_function + } + scoreboard players set #success <%OBJECTIVES.I()%> 1 + } + } ELSE { + execute if data storage <%temp_storage%> args.variant run block zzz/variant_arg/no_variants_warning { + tellraw @a <%TELLRAW.NO_VARIANTS()%> + function *<%export_namespace%>/remove/this/without_on_remove_function + scoreboard players set #success <%OBJECTIVES.I()%> 0 + } + } + execute if score #success <%OBJECTIVES.I()%> matches 0 run return fail + + function *<%export_namespace%>/set_default_pose + + # Animation Argument + # If the animation argument is provided, attempt to apply the animation. + execute if data storage <%temp_storage%> args.animation run block animation_arg/process { with storage <%temp_storage%> args + scoreboard players set #success <%OBJECTIVES.I()%> 0 + # If the animation argument is *explicitly* set to an empty string, return an error. + execute if data storage <%temp_storage%> {args:{animation:''}} run return run block if_empty { + tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('animation')%> + function *<%export_namespace%>/remove/this/without_on_remove_function + } + # Automatically set the frame argument to 0 if the frame argument is not provided. + # Takes advantage of `store result score` setting the score to 0 if the command fails. + execute \ + store result storage <%temp_storage%> args.frame int 1 \ + store result score #frame <%OBJECTIVES.I()%> \ + run \ + data get storage <%temp_storage%> args.frame + # If the frame argument is negative, return an error. + execute if score #frame <%OBJECTIVES.I()%> matches ..-1 run return run block no_negative { + # Tell the user that the frame argument cannot be negative. + tellraw @a <%TELLRAW.FRAME_CANNOT_BE_NEGATIVE()%> + function *<%export_namespace%>/remove/this/without_on_remove_function + } + # Attempt to apply the animation frame. + execute store success score #success <%OBJECTIVES.I()%> run block try_set_frame { with storage <%temp_storage%> args + # Make sure we're only applying transforms when setting the summon pose. + tag @s add <%TAGS.TRANSFORMS_ONLY()%> + $execute store success score #success <%OBJECTIVES.I()%> run function *<%export_namespace%>/animations/$(animation)/zzz/set_frame with storage <%temp_storage%> args + tag @s remove <%TAGS.TRANSFORMS_ONLY()%> + execute if score #success <%OBJECTIVES.I()%> matches 1 run return 1 + # If the set_frame function fails, the animation doesn't exist, so we return an error. + return fail + } + # If the set_frame function failed, return an error. + execute unless score #success <%OBJECTIVES.I()%> matches 1 run return run block invalid_animation { + # Tell the user that the provided animation doesn't exist, remove the rig, and list all available animations for this rig. + tellraw @a <%TELLRAW.INVALID_ANIMATION(animations)%> + function *<%export_namespace%>/remove/this/without_on_remove_function + } + + # If the animation is successfully applied, and the start_animation argument is set to true, start the animation. + execute if data storage <%temp_storage%> {args:{start_animation: true}} run block start_animation { with storage <%temp_storage%> args + $function *<%export_namespace%>/animations/$(animation)/resume + } + scoreboard players set #success <%OBJECTIVES.I()%> 1 + } + execute if score #success <%OBJECTIVES.I()%> matches 0 run return fail + + IF (has_locators || has_cameras) { + execute \ + on passengers \ + if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ + run function ./on_tick/transform_floating_entities + } + execute on passengers run tp @s ~ ~ ~ ~ ~ + + # Apply teleport duration + data modify entity @s teleport_duration set value <%teleportation_duration%> + execute on passengers run data modify entity @s teleport_duration set value <%teleportation_duration%> + + IF (has_entity_locators) { + execute at @s on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block locators_on_summon { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity && node.config.on_summon_function)) as locator { + block { with entity @s data.locators.<%locator.storage_name%> + $execute as $(uuid) at @s run block <%locator.storage_name%> { + <%% + emit.mcb(locator.config.on_summon_function) + %%> + # Track any custom entities on the locator. + function *global/util/get_entity_stack_uuids + } + } + data modify entity @s data.uuids append from storage <%temp_storage%> uuids + } + } + } + + 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(export_namespace, node.storage_name)%>] \ + run block node_on_summon_<%node.storage_name%> { + <%% + emit.mcb(node.on_summon_function.trim()) + %%> + } + } + + IF (on_summon_function) { + execute at @s run block rig_on_summon { + <%% + emit.mcb(on_summon_function) + %%> + } + } + + # 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()%> + } +} + +IF (has_entity_locators) { + function as_locator { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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')%> + } + + execute \ + on passengers \ + if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ + run block zzz/as_locator/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.$(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 locator wasn't found. + tellraw @a <%TELLRAW.LOCATOR_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.LOCATOR_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function as_all_locators { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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')%> + } + + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { + data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + block execute_as_uuid { with storage <%temp_storage%> args + $execute as $(uuid) at @s run $(command) + } + } + } + } +} + +IF (has_locators) { + function at_locator { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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')%> + } + + execute \ + at @s \ + on passengers \ + if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ + run block zzz/at_locator/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge from entity @s data.locators.$(name) + + IF (debug_mode) { + execute unless data storage <%temp_storage%> args.uuid run return run tellraw @a <%TELLRAW.LOCATOR_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.LOCATOR_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function at_all_locators { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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')%> + } + + execute at @s on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/at_all_locators/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { + data modify storage <%temp_storage%> args merge from entity @s data.locators.<%locator.storage_name%> + block execute_at_transform { with storage <%temp_storage%> args + $execute \ + positioned ^$(px) ^$(py) ^$(pz) \ + rotated ~$(ry) ~$(rx) \ + run $(command) + } + } + } + } +} + +IF (has_cameras) { + function as_camera { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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')%> + } + + execute \ + on passengers \ + if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ + run block zzz/as_camera/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from entity @s data.cameras.$(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 camera wasn't found. + tellraw @a <%TELLRAW.CAMERA_ENTITY_NOT_FOUND()%> + } + } + + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.CAMERA_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } +} + +dir remove { + function all { + # Removes all instances of this rig from the world. + execute as @e[type=minecraft:item_display,tag=<%TAGS.PROJECT_ROOT(export_namespace)%>] run function *<%export_namespace%>/remove/this + } + + function entities { + # Removes all entities related to this rig from the world. + kill @e[tag=<%TAGS.PROJECT_ENTITY(export_namespace)%>] + } + + function this { + # Removes the rig this function is executed as. + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + <%% + if (on_remove_function) emit.mcb(on_remove_function) + %%> + + IF (has_entity_locators) { + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { + IF (locator.config?.on_remove_function) { + IF (locator.config.use_entity) { + block as_locator_<%locator.storage_name%> { with entity @s data.locators.<%locator.storage_name%> + $execute as $(uuid) at @s run block locator_<%locator.storage_name%>_on_remove { + <%% + emit.mcb(locator.config.on_remove_function) + %%> + } + } + } ELSE { + block at_locator_<%locator.storage_name%> { with entity @s data.locators.<%locator.storage_name%> + $execute \ + positioned ^$(px) ^$(py) ^$(pz) \ + rotated ~$(ry) ~$(rx) \ + run block locator_<%locator.storage_name%>_on_remove { + <%% + emit.mcb(locator.config.on_remove_function) + %%> + } + } + } + } + } + } + } + + function ./this/without_on_remove_function + } + + dir this { + function without_on_remove_function { + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + IF (has_entity_locators || has_cameras) { + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { + function animated_java:global/remove/entity_stack_by_uuid with entity @s data.locators.<%locator.storage_name%> + } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'camera')) as camera { + function animated_java:global/remove/entity_stack_by_uuid with entity @s data.cameras.<%camera.storage_name%> + } + } + } + + # Remove the rig using the more expensive & thorough method if the rig_hash doesn't match. + execute \ + on passengers \ + if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ + unless data entity @s {data:{rig_hash: '<%rig_hash%>'}} \ + on vehicle \ + run function animated_java:global/remove/outdated_rig + + function animated_java:global/remove/entity_stack + } + } +} + +IF (Object.keys(rig.variants).length > 1) { + dir variants { + REPEAT (Object.values(rig.variants)) as variant { + dir <%variant.name%> { + function apply { + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + <%% + global.filteredNodes = Object.values(rig.nodes).filter( + node => ( + node.type === 'bone' && + !variant.excluded_nodes.includes(node.uuid) && + ( // Variant has a model override or a config override for this bone. + variant.models[node.uuid] !== undefined || + node.configs.variants[variant.uuid] !== undefined + ) + ) || ( + BONE_TYPES.includes(node.type) && + !variant.excluded_nodes.includes(node.uuid) && + // Variant has a config override for this node. + node.configs.variants[variant.uuid] !== undefined + ) + ) + %%> + + REPEAT (global.filteredNodes) as node { + execute \ + on passengers \ + if entity @s[tag=<%TAGS.PROJECT_NODE_NAMED(export_namespace, node.storage_name)%>] \ + at @s \ + run \ + block zzz/apply_to_node_<%node.storage_name%> { + IF (node.type === 'bone' && variant.models[node.uuid] !== undefined) { + # Special case for `animated_java:empty` model. + IF (variant.models[node.uuid].model === null) { + data modify entity @s item.components."minecraft:item_model" set value "animated_java:empty" + } ELSE { + data modify entity @s item.components."minecraft:item_model" set value "<%variant.models[node.uuid].item_model%>" + } + } + IF (node.configs.variants[variant.uuid]) { + <%% + global.config = DisplayEntityConfig.fromJSON(node.configs.variants[variant.uuid]) + %%> + IF (!global.config.isDefault()) { + data merge entity @s <%global.config.toNBT(undefined, variant.is_default)%> + } + IF (global.config.onApplyFunction) { + <%% + emit.mcb(global.config.onApplyFunction) + %%> + } + } + } + } + # Return success to allow this function to be used in function conditions. + return 1 + } + } + } + } +} + +IF (has_locators || has_cameras) { + dir zzz { + function reset_floating_entities { + IF (has_locators) { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { + execute \ + at @s \ + on passengers \ + if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ + run block zzz/set_default_pose/as_locator_<%locator.storage_name%> { with entity @s data.locators.<%locator.storage_name%> + IF (locator.config?.use_entity) { + $tp $(uuid) \ + ^<%roundTo(locator.default_transform.pos[0], 10)%> \ + ^<%roundTo(locator.default_transform.pos[1], 10)%> \ + ^<%roundTo(locator.default_transform.pos[2], 10)%> \ + ~<%roundTo(locator.default_transform.head_rot[1], 10)%> \ + ~<%roundTo(locator.default_transform.head_rot[0], 10)%> + } + + data modify entity @s data.locators.<%locator.storage_name%> merge value { \ + px: <%roundTo(locator.default_transform.pos[0], 10)%>, \ + py: <%roundTo(locator.default_transform.pos[1], 10)%>, \ + pz: <%roundTo(locator.default_transform.pos[2], 10)%>, \ + ry: <%roundTo(locator.default_transform.head_rot[1], 10)%>, \ + rx: <%roundTo(locator.default_transform.head_rot[0], 10)%> \ + } + } + } + } + + IF (has_cameras) { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'camera')) as camera { + execute \ + at @s \ + on passengers \ + if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ + run block zzz/set_default_pose/as_camera_<%camera.storage_name%> { with entity @s data.cameras.<%camera.storage_name%> + $tp $(uuid) \ + ^<%roundTo(camera.default_transform.pos[0], 10)%> \ + ^<%roundTo(camera.default_transform.pos[1], 10)%> \ + ^<%roundTo(camera.default_transform.pos[2], 10)%> \ + ~<%roundTo(camera.default_transform.head_rot[1], 10)%> \ + ~<%roundTo(camera.default_transform.head_rot[0], 10)%> + + data modify entity @s data.cameras.<%camera.storage_name%> merge value { \ + px: <%roundTo(camera.default_transform.pos[0], 10)%>, \ + py: <%roundTo(camera.default_transform.pos[1], 10)%>, \ + pz: <%roundTo(camera.default_transform.pos[2], 10)%>, \ + ry: <%roundTo(camera.default_transform.head_rot[1], 10)%>, \ + rx: <%roundTo(camera.default_transform.head_rot[0], 10)%> \ + } + } + } + } + } + } +} + +function apply_default_pose { + # Changes the pose of the rig to the the default pose with interpolation + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + IF (has_locators || has_cameras) { + function ./zzz/reset_floating_entities + } + + REPEAT (Object.values(rig.nodes).filter(node => BONE_TYPES.includes(node.type))) as node { + execute \ + on passengers \ + if entity @s[tag=<%TAGS.PROJECT_NODE_NAMED(export_namespace, node.storage_name)%>] \ + run \ + data merge entity @s { \ + transformation: <%matrixToNbtFloatArray(node.default_transform.matrix).toString()%>, \ + start_interpolation: 0 \ + } + } +} + +function set_default_pose { + # Changes the pose of the rig to the the default pose without interpolation + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + IF (has_locators || has_cameras) { + function ./zzz/reset_floating_entities + } + + REPEAT (Object.values(rig.nodes).filter(node => node.type !== 'locator' && node.type !== 'camera')) as node { + execute \ + on passengers \ + if entity @s[tag=<%TAGS.PROJECT_NODE_NAMED(export_namespace, node.storage_name)%>] \ + run \ + data merge entity @s { \ + transformation: <%matrixToNbtFloatArray(node.default_transform.matrix).toString()%>, \ + start_interpolation: -1 \ + } + } +} diff --git a/src/systems/datapackCompiler/1.21.0/static.mcb b/src/systems/datapackCompiler/1.21.0/static.mcb new file mode 100644 index 00000000..37337da0 --- /dev/null +++ b/src/systems/datapackCompiler/1.21.0/static.mcb @@ -0,0 +1,693 @@ +# TODO - Move all internal functions into an internal namespace, and only have user-facing functions in the main `animated_java:<%export_namespace%>` namespace. +import ../global.mcbt +function on_load { + data modify storage <%project_storage%> rig_hash set value <%"'" + rig_hash + "'"%> +} + +function invalid_version_warning { + # This function will contain a tellraw if the datapack is loaded in the wrong version. +} + +dir root { + function on_tick { + execute unless entity @s[tag=<%TAGS.PROJECT_ROOT(export_namespace)%>] run return 0 + + # Custom pre-tick function + IF (on_pre_tick_function) { + <%% + emit.mcb(on_pre_tick_function) + %%> + } + + IF (auto_update_rig_orientation) { + IF (has_locators || has_cameras) { + execute \ + on passengers \ + if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ + run function ./on_tick/transform_floating_entities + } + execute on passengers run tp @s ~ ~ ~ ~ ~ + } ELSE IF (has_ticking_locators) { + execute \ + on passengers \ + if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ + run function ./on_tick/transform_locators + } + + # Custom post-tick function + IF (on_post_tick_function) { + <%% + emit.mcb(on_post_tick_function) + %%> + } + } + + IF (has_locators || has_cameras) { + dir on_tick { + function transform_locators { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { + block select_locator_<%locator.storage_name%> { with entity @s data.locators.<%locator.storage_name%> + IF (locator.config?.use_entity) { + $execute \ + as $(uuid) \ + positioned ^$(px) ^$(py) ^$(pz) \ + rotated ~$(ry) ~$(rx) \ + run block as_locator_<%locator.storage_name%> { + tp @s ~ ~ ~ ~ ~ + + IF (locator.config?.sync_passenger_rotation) { + execute on passengers run tp @s ~ ~ ~ ~ ~ + } + + IF (locator.config?.on_tick_function) { + <%% + emit.mcb(locator.config.on_tick_function) + %%> + } + } + } ELSE IF (locator.config?.on_tick_function) { + $execute \ + positioned ^$(px) ^$(py) ^$(pz) \ + rotated ~$(ry) ~$(rx) \ + run block at_locator_<%locator.storage_name%> { + <%% + emit.mcb(locator.config.on_tick_function) + %%> + } + } + } + } + } + + function transform_floating_entities { + function ./transform_locators + + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'camera')) as camera { + block select_camera_<%camera.storage_name%> { with entity @s data.cameras.<%camera.storage_name%> + $execute \ + as $(uuid) \ + positioned ^$(px) ^$(py) ^$(pz) \ + rotated ~$(ry) ~$(rx) \ + run tp @s ~ ~ ~ ~ ~ + } + } + } + } + } +} + +IF (!auto_update_rig_orientation) { + function move { + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + tp @s ~ ~ ~ ~ ~ + + IF (has_locators || has_cameras) { + execute \ + at @s on passengers \ + if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ + run function ./on_tick/transform_floating_entities + } + execute at @s on passengers run tp @s ~ ~ ~ ~ ~ + } +} ELSE { + function move { + tellraw @a <%TELLRAW.AUTO_UPDATE_RIG_ORIENTATION_MOVE_WARNING()%> + } +} + +function summon { + #Args: {args:{variant: string}} + + data modify storage <%temp_storage%> args set value {variant:''} + $execute store success score #success <%OBJECTIVES.I()%> run data modify storage <%temp_storage%> args set value $(args) + + summon minecraft:item_display ~ ~ ~ { \ + Tags:[ \ + '<%TAGS.NEW()%>', \ + '<%TAGS.GLOBAL_ENTITY()%>', \ + '<%TAGS.GLOBAL_ROOT()%>', \ + '<%TAGS.PROJECT_ENTITY(export_namespace)%>', \ + '<%TAGS.PROJECT_ROOT(export_namespace)%>' \ + ], \ + teleport_duration: 0, \ + interpolation_duration: <%interpolation_duration%>, \ + Passengers:<%root_entity_passengers%>, \ + } + execute as @n[ \ + type=minecraft:item_display, \ + tag=<%TAGS.PROJECT_ROOT(export_namespace)%>, \ + tag=<%TAGS.NEW()%>, \ + distance=..0.01 \ + ] run block zzz/summon/as_root_entity { + execute store result score @s <%OBJECTIVES.ID()%> run scoreboard players add aj.last_id <%OBJECTIVES.ID()%> 1 + # Align the position and rotation of the root with the command context. + tp @s ~ ~ ~ ~ ~ + + function *global/gu/get_entity_uuid_string + execute at @s on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block as_data_entity { + # Rig Root UUID + data modify entity @s data.uuids append from storage <%gu_storage%> out + # Data Entity UUID + function *global/gu/get_entity_uuid_string + data modify entity @s data.uuids append from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.data_data 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)%> \ + ^<%roundTo(locator.default_transform.pos[1], 10)%> \ + ^<%roundTo(locator.default_transform.pos[2], 10)%> \ + {Tags:<%getNodeTags(locator, rig)%>} + execute \ + as @n[ \ + type=<%locator.config.entity_type%>, \ + tag=<%TAGS.PROJECT_LOCATOR_NAMED(export_namespace, locator.storage_name)%>, \ + tag=<%TAGS.NEW()%>, \ + distance=..<%Math.ceil(locator.max_distance)%> \ + ] \ + run block as_locator/<%locator.storage_name%> { + # run block ../as_locator/<%locator.storage_name%> { + tag @s remove <%TAGS.NEW()%> + function *global/gu/get_entity_uuid_string + } + data modify entity @s data.uuids append from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%locator.type + '_' + locator.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.locators.<%locator.storage_name%>.uuid set from storage <%gu_storage%> out + } + + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'camera')) as camera { + summon minecraft:item_display \ + ^<%roundTo(camera.default_transform.pos[0], 10)%> \ + ^<%roundTo(camera.default_transform.pos[1], 10)%> \ + ^<%roundTo(camera.default_transform.pos[2], 10)%> \ + {Tags:<%getNodeTags(camera, rig)%>, teleport_duration: 2} + execute \ + as @n[ \ + type=minecraft:item_display, \ + tag=<%TAGS.PROJECT_CAMERA_NAMED(export_namespace, camera.storage_name)%>, \ + tag=<%TAGS.NEW()%>, \ + distance=..<%Math.ceil(camera.max_distance)%> \ + ] \ + run block as_camera/<%camera.storage_name%> { + # run block ../as_camera/<%camera.storage_name%> { + tag @s remove <%TAGS.NEW()%> + function *global/gu/get_entity_uuid_string + } + data modify entity @s data.uuids append from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%camera.type + '_' + camera.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.cameras.<%camera.storage_name%>.uuid set from storage <%gu_storage%> out + } + + REPEAT (Object.values(rig.nodes).filter(node => BONE_TYPES.includes(node.type))) as node { + execute \ + on vehicle \ + on passengers \ + if entity @s[tag=<%TAGS.PROJECT_NODE_NAMED(export_namespace, node.storage_name)%>] \ + run \ + function *global/gu/get_entity_uuid_string + + data modify entity @s data.uuids append from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%node.type + '_' + node.storage_name%> set from storage <%gu_storage%> out + } + } + + # Variant Arguement + IF (Object.keys(rig.variants).length > 1) { + execute if data storage <%temp_storage%> args.variant run block variant_arg/process { with storage <%temp_storage%> args + scoreboard players set #success <%OBJECTIVES.I()%> 0 + # If the variant argument is *explicitly* set to an empty string, return an error. + execute if data storage <%temp_storage%> {args:{variant:''}} run return run block if_empty { + # Tell the user that the variant cannot be empty. + tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('variant')%> + function *<%export_namespace%>/remove/this/without_on_remove_function + } + # Attempt to apply the requested variant. + # We get the success of the `try_apply` function in just in case the user's arguments are *very* wrong. + execute store success score #success <%OBJECTIVES.I()%> run block try_apply { with storage <%temp_storage%> args + $execute if function animated_java:<%export_namespace%>/variants/$(variant)/apply run return 1 + # If the apply function fails, the variant doesn't exist, so we return an error. + return fail + } + # If the apply function failed, return an error. + execute unless score #success <%OBJECTIVES.I()%> matches 1 run return run block invalid_variant { + # Tell the user that the provided variant doesn't exist, remove the rig, and list all available variants for this rig. + tellraw @a <%TELLRAW.INVALID_VARIANT(rig.variants)%> + function *<%export_namespace%>/remove/this/without_on_remove_function + } + scoreboard players set #success <%OBJECTIVES.I()%> 1 + } + } ELSE { + execute if data storage <%temp_storage%> args.variant run block zzz/variant_arg/no_variants_warning { + tellraw @a <%TELLRAW.NO_VARIANTS()%> + function *<%export_namespace%>/remove/this/without_on_remove_function + scoreboard players set #success <%OBJECTIVES.I()%> 0 + } + } + execute if score #success <%OBJECTIVES.I()%> matches 0 run return fail + + function *<%export_namespace%>/set_default_pose + IF (has_locators || has_cameras) { + execute \ + on passengers \ + if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ + run function ./on_tick/transform_floating_entities + } + execute on passengers run tp @s ~ ~ ~ ~ ~ + + # Apply teleport duration + data modify entity @s teleport_duration set value <%teleportation_duration%> + execute on passengers run data modify entity @s teleport_duration set value <%teleportation_duration%> + + IF (has_entity_locators) { + execute at @s on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block locators_on_summon { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity && node.config.on_summon_function)) as locator { + block { with entity @s data.locators.<%locator.storage_name%> + $execute as $(uuid) at @s run block <%locator.storage_name%> { + <%% + emit.mcb(locator.config.on_summon_function) + %%> + # Track any custom entities on the locator. + function *global/util/get_entity_stack_uuids + } + } + data modify entity @s data.uuids append from storage <%temp_storage%> uuids + } + } + } + + 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(export_namespace, node.storage_name)%>] \ + run block node_on_summon_<%node.storage_name%> { + <%% + emit.mcb(node.on_summon_function.trim()) + %%> + } + } + + IF (on_summon_function) { + execute at @s run block rig_on_summon { + <%% + emit.mcb(on_summon_function) + %%> + } + } + + # 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()%> + } +} + +IF (has_entity_locators) { + function as_locator { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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')%> + } + + execute \ + on passengers \ + if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ + run block zzz/as_locator/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.$(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 locator wasn't found. + tellraw @a <%TELLRAW.LOCATOR_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.LOCATOR_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function as_all_locators { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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')%> + } + + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { + data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + block execute_as_uuid { with storage <%temp_storage%> args + $execute as $(uuid) at @s run $(command) + } + } + } + } +} + +IF (has_locators) { + function at_locator { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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')%> + } + + execute \ + at @s \ + on passengers \ + if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ + run block zzz/at_locator/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args merge from entity @s data.locators.$(name) + + IF (debug_mode) { + execute unless data storage <%temp_storage%> args.uuid run return run tellraw @a <%TELLRAW.LOCATOR_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.LOCATOR_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function at_all_locators { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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')%> + } + + execute at @s on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/at_all_locators/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { + data modify storage <%temp_storage%> args merge from entity @s data.locators.<%locator.storage_name%> + block execute_at_transform { with storage <%temp_storage%> args + $execute \ + positioned ^$(px) ^$(py) ^$(pz) \ + rotated ~$(ry) ~$(rx) \ + run $(command) + } + } + } + } +} + +IF (has_cameras) { + function as_camera { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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')%> + } + + execute \ + on passengers \ + if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ + run block zzz/as_camera/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from entity @s data.cameras.$(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 camera wasn't found. + tellraw @a <%TELLRAW.CAMERA_ENTITY_NOT_FOUND()%> + } + } + + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.CAMERA_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } +} + +dir remove { + function all { + # Removes all instances of this rig from the world. + execute as @e[type=minecraft:item_display,tag=<%TAGS.PROJECT_ROOT(export_namespace)%>] run function *<%export_namespace%>/remove/this + } + + function entities { + # Removes all entities related to this rig from the world. + kill @e[tag=<%TAGS.PROJECT_ENTITY(export_namespace)%>] + } + + function this { + # Removes the rig this function is executed as. + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + <%% + if (on_remove_function) emit.mcb(on_remove_function) + %%> + + IF (has_entity_locators) { + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { + IF (locator.config?.on_remove_function) { + IF (locator.config.use_entity) { + block as_locator_<%locator.storage_name%> { with entity @s data.locators.<%locator.storage_name%> + $execute as $(uuid) at @s run block locator_<%locator.storage_name%>_on_remove { + <%% + emit.mcb(locator.config.on_remove_function) + %%> + } + } + } ELSE { + block at_locator_<%locator.storage_name%> { with entity @s data.locators.<%locator.storage_name%> + $execute \ + positioned ^$(px) ^$(py) ^$(pz) \ + rotated ~$(ry) ~$(rx) \ + run block locator_<%locator.storage_name%>_on_remove { + <%% + emit.mcb(locator.config.on_remove_function) + %%> + } + } + } + } + } + } + } + + function ./this/without_on_remove_function + } + + dir this { + function without_on_remove_function { + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + IF (has_entity_locators || has_cameras) { + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { + function animated_java:global/remove/entity_stack_by_uuid with entity @s data.locators.<%locator.storage_name%> + } + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'camera')) as camera { + function animated_java:global/remove/entity_stack_by_uuid with entity @s data.cameras.<%camera.storage_name%> + } + } + } + + # Remove the rig using the more expensive & thorough method if the rig_hash doesn't match. + execute \ + on passengers \ + if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ + unless data entity @s {data:{rig_hash: '<%rig_hash%>'}} \ + on vehicle \ + run function animated_java:global/remove/outdated_rig + + function animated_java:global/remove/entity_stack + } + } +} + +IF (Object.keys(rig.variants).length > 1) { + dir variants { + REPEAT (Object.values(rig.variants)) as variant { + dir <%variant.name%> { + function apply { + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + <%% + global.filteredNodes = Object.values(rig.nodes).filter( + node => ( + node.type === 'bone' && + !variant.excluded_nodes.includes(node.uuid) && + ( // Variant has a model override or a config override for this bone. + variant.models[node.uuid] !== undefined || + node.configs.variants[variant.uuid] !== undefined + ) + ) || ( + BONE_TYPES.includes(node.type) && + !variant.excluded_nodes.includes(node.uuid) && + // Variant has a config override for this node. + node.configs.variants[variant.uuid] !== undefined + ) + ) + %%> + + REPEAT (global.filteredNodes) as node { + execute \ + on passengers \ + if entity @s[tag=<%TAGS.PROJECT_NODE_NAMED(export_namespace, node.storage_name)%>] \ + at @s \ + run \ + block zzz/apply_to_node_<%node.storage_name%> { + IF (node.type === 'bone' && variant.models[node.uuid] !== undefined) { + # Special case for `animated_java:empty` model. + IF (variant.models[node.uuid].model === null) { + data modify entity @s item.components."minecraft:item_model" set value "animated_java:empty" + } ELSE { + data modify entity @s item.components."minecraft:item_model" set value "<%variant.models[node.uuid].item_model%>" + } + } + IF (node.configs.variants[variant.uuid]) { + <%% + global.config = DisplayEntityConfig.fromJSON(node.configs.variants[variant.uuid]) + %%> + IF (!global.config.isDefault()) { + data merge entity @s <%global.config.toNBT(undefined, variant.is_default)%> + } + IF (global.config.onApplyFunction) { + <%% + emit.mcb(global.config.onApplyFunction) + %%> + } + } + } + } + # Return success to allow this function to be used in function conditions. + return 1 + } + } + } + } +} + +IF (has_locators || has_cameras) { + dir zzz { + function reset_floating_entities { + IF (has_locators) { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { + execute \ + at @s \ + on passengers \ + if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ + run block zzz/set_default_pose/as_locator_<%locator.storage_name%> { with entity @s data.locators.<%locator.storage_name%> + IF (locator.config?.use_entity) { + $tp $(uuid) \ + ^<%roundTo(locator.default_transform.pos[0], 10)%> \ + ^<%roundTo(locator.default_transform.pos[1], 10)%> \ + ^<%roundTo(locator.default_transform.pos[2], 10)%> \ + ~<%roundTo(locator.default_transform.head_rot[1], 10)%> \ + ~<%roundTo(locator.default_transform.head_rot[0], 10)%> + } + + data modify entity @s data.locators.<%locator.storage_name%> merge value { \ + px: <%roundTo(locator.default_transform.pos[0], 10)%>, \ + py: <%roundTo(locator.default_transform.pos[1], 10)%>, \ + pz: <%roundTo(locator.default_transform.pos[2], 10)%>, \ + ry: <%roundTo(locator.default_transform.head_rot[1], 10)%>, \ + rx: <%roundTo(locator.default_transform.head_rot[0], 10)%> \ + } + } + } + } + + IF (has_cameras) { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'camera')) as camera { + execute \ + at @s \ + on passengers \ + if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ + run block zzz/set_default_pose/as_camera_<%camera.storage_name%> { with entity @s data.cameras.<%camera.storage_name%> + $tp $(uuid) \ + ^<%roundTo(camera.default_transform.pos[0], 10)%> \ + ^<%roundTo(camera.default_transform.pos[1], 10)%> \ + ^<%roundTo(camera.default_transform.pos[2], 10)%> \ + ~<%roundTo(camera.default_transform.head_rot[1], 10)%> \ + ~<%roundTo(camera.default_transform.head_rot[0], 10)%> + + data modify entity @s data.cameras.<%camera.storage_name%> merge value { \ + px: <%roundTo(camera.default_transform.pos[0], 10)%>, \ + py: <%roundTo(camera.default_transform.pos[1], 10)%>, \ + pz: <%roundTo(camera.default_transform.pos[2], 10)%>, \ + ry: <%roundTo(camera.default_transform.head_rot[1], 10)%>, \ + rx: <%roundTo(camera.default_transform.head_rot[0], 10)%> \ + } + } + } + } + } + } +} + +function set_default_pose { + # Changes the pose of the rig to the the default pose without interpolation + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + IF (has_locators || has_cameras) { + function ./zzz/reset_floating_entities + } + + REPEAT (Object.values(rig.nodes).filter(node => node.type !== 'locator' && node.type !== 'camera')) as node { + execute \ + on passengers \ + if entity @s[tag=<%TAGS.PROJECT_NODE_NAMED(export_namespace, node.storage_name)%>] \ + run \ + data merge entity @s { \ + transformation: <%matrixToNbtFloatArray(node.default_transform.matrix).toString()%>, \ + start_interpolation: -1 \ + } + } +} diff --git a/src/systems/datapackCompiler/mcbFiles.ts b/src/systems/datapackCompiler/mcbFiles.ts index d214e373..924d130f 100644 --- a/src/systems/datapackCompiler/mcbFiles.ts +++ b/src/systems/datapackCompiler/mcbFiles.ts @@ -8,6 +8,9 @@ import STATIC_1_20_4 from './1.20.4/static.mcb' import ANIMATION_1_20_5 from './1.20.5/animation.mcb' import STATIC_1_20_5 from './1.20.5/static.mcb' +import ANIMATION_1_21_0 from './1.21.0/animation.mcb' +import STATIC_1_21_0 from './1.21.0/static.mcb' + import ANIMATION_1_21_2 from './1.21.2/animation.mcb' import GLOBAL_1_21_2 from './1.21.2/global.mcb' import STATIC_1_21_2 from './1.21.2/static.mcb' @@ -64,6 +67,12 @@ const MCB_FILES: Record = { global: GLOBAL_1_21_2, globalTemplates: GLOBAL_TEMPLATES_1_20_4, }, + '1.21.0': { + animation: ANIMATION_1_21_0, + static: STATIC_1_21_0, + global: GLOBAL_1_20_4, + globalTemplates: GLOBAL_TEMPLATES_1_20_4, + }, '1.20.5': { animation: ANIMATION_1_20_5, static: STATIC_1_20_5, diff --git a/src/systems/global.ts b/src/systems/global.ts index 58131e97..d15b820c 100644 --- a/src/systems/global.ts +++ b/src/systems/global.ts @@ -5,6 +5,7 @@ import { sortObjectKeys } from './util' export enum SUPPORTED_MINECRAFT_VERSIONS { '1.20.4' = '1.20.4', '1.20.5' = '1.20.5', + '1.21.0' = '1.21.0', '1.21.2' = '1.21.2', '1.21.4' = '1.21.4', '1.21.5' = '1.21.5', diff --git a/src/systems/resourcepackCompiler/index.ts b/src/systems/resourcepackCompiler/index.ts index 6afb9d25..856a9014 100644 --- a/src/systems/resourcepackCompiler/index.ts +++ b/src/systems/resourcepackCompiler/index.ts @@ -19,6 +19,7 @@ const VERSIONED_RESOURCE_PACK_COMPILERS: Record< '1.21.5': EXPORT_1_21_4, '1.21.4': EXPORT_1_21_4, '1.21.2': EXPORT_1_21_2, + '1.21.0': EXPORT_1_21_2, '1.20.5': EXPORT_1_20_4, '1.20.4': EXPORT_1_20_4, } diff --git a/src/util/minecraftUtil.ts b/src/util/minecraftUtil.ts index ca28e262..19048791 100644 --- a/src/util/minecraftUtil.ts +++ b/src/util/minecraftUtil.ts @@ -335,6 +335,8 @@ export function getDataPackFormat(version: SUPPORTED_MINECRAFT_VERSIONS): number return 26 case SUPPORTED_MINECRAFT_VERSIONS['1.20.5']: return 41 + case SUPPORTED_MINECRAFT_VERSIONS['1.21.0']: + return 48 case SUPPORTED_MINECRAFT_VERSIONS['1.21.2']: return 57 case SUPPORTED_MINECRAFT_VERSIONS['1.21.4']: @@ -347,9 +349,6 @@ export function getDataPackFormat(version: SUPPORTED_MINECRAFT_VERSIONS): number return 88.0 case SUPPORTED_MINECRAFT_VERSIONS['1.21.11']: return 93.1 - default: - console.warn(`Unknown Minecraft version: ${version}`) - return Infinity } } @@ -359,6 +358,8 @@ export function getResourcePackFormat(version: SUPPORTED_MINECRAFT_VERSIONS): nu return 22 case SUPPORTED_MINECRAFT_VERSIONS['1.20.5']: return 32 + case SUPPORTED_MINECRAFT_VERSIONS['1.21.0']: + return 34 case SUPPORTED_MINECRAFT_VERSIONS['1.21.2']: return 42 case SUPPORTED_MINECRAFT_VERSIONS['1.21.4']: @@ -371,9 +372,6 @@ export function getResourcePackFormat(version: SUPPORTED_MINECRAFT_VERSIONS): nu return 69.0 case SUPPORTED_MINECRAFT_VERSIONS['1.21.11']: return 74.0 - default: - console.warn(`Unknown Minecraft version: ${version}`) - return Infinity } } From 5118f37606c171d3bc43b6861624a16fccd06613 Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Thu, 18 Dec 2025 09:43:11 -0500 Subject: [PATCH 05/23] =?UTF-8?q?=F0=9F=90=9B=20Fix=20`as=5Flocator`=20&?= =?UTF-8?q?=20`as=5Fall=5Flocators`=20executing=20as=20&=20at=20targeted?= =?UTF-8?q?=20locators?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added new functions called `as_at_locator` & `as_at_all_locators` to replace the previous functionality. Fixes #471 --- .../datapackCompiler/1.20.4/animation.mcb | 58 +++++++++++++++++++ .../datapackCompiler/1.20.4/static.mcb | 58 +++++++++++++++++++ .../datapackCompiler/1.20.5/animation.mcb | 58 +++++++++++++++++++ .../datapackCompiler/1.20.5/static.mcb | 58 +++++++++++++++++++ .../datapackCompiler/1.21.0/animation.mcb | 58 +++++++++++++++++++ .../datapackCompiler/1.21.0/static.mcb | 58 +++++++++++++++++++ .../datapackCompiler/1.21.2/animation.mcb | 58 +++++++++++++++++++ .../datapackCompiler/1.21.2/static.mcb | 58 +++++++++++++++++++ .../datapackCompiler/1.21.4/animation.mcb | 58 +++++++++++++++++++ .../datapackCompiler/1.21.4/static.mcb | 58 +++++++++++++++++++ 10 files changed, 580 insertions(+) diff --git a/src/systems/datapackCompiler/1.20.4/animation.mcb b/src/systems/datapackCompiler/1.20.4/animation.mcb index 8309ccd1..ad4e1b00 100644 --- a/src/systems/datapackCompiler/1.20.4/animation.mcb +++ b/src/systems/datapackCompiler/1.20.4/animation.mcb @@ -736,6 +736,43 @@ IF (has_entity_locators) { execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> } + execute \ + on passengers \ + if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ + run block zzz/as_locator/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.$(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 locator wasn't found. + tellraw @a <%TELLRAW.LOCATOR_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.LOCATOR_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function as_at_locator { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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')%> + } + execute \ on passengers \ if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ @@ -772,6 +809,27 @@ IF (has_entity_locators) { execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> } + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { + data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + block execute_as_uuid { with storage <%temp_storage%> args + $execute as $(uuid) run $(command) + } + } + } + } + + function as_at_all_locators { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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')%> + } + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid diff --git a/src/systems/datapackCompiler/1.20.4/static.mcb b/src/systems/datapackCompiler/1.20.4/static.mcb index 8b66fd80..79400a7f 100644 --- a/src/systems/datapackCompiler/1.20.4/static.mcb +++ b/src/systems/datapackCompiler/1.20.4/static.mcb @@ -313,6 +313,43 @@ IF (has_entity_locators) { execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> } + execute \ + on passengers \ + if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ + run block zzz/as_locator/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.$(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 locator wasn't found. + tellraw @a <%TELLRAW.LOCATOR_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.LOCATOR_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function as_at_locator { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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')%> + } + execute \ on passengers \ if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ @@ -349,6 +386,27 @@ IF (has_entity_locators) { execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> } + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { + data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + block execute_as_uuid { with storage <%temp_storage%> args + $execute as $(uuid) run $(command) + } + } + } + } + + function as_at_all_locators { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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')%> + } + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid diff --git a/src/systems/datapackCompiler/1.20.5/animation.mcb b/src/systems/datapackCompiler/1.20.5/animation.mcb index 69aaa48f..b2780080 100644 --- a/src/systems/datapackCompiler/1.20.5/animation.mcb +++ b/src/systems/datapackCompiler/1.20.5/animation.mcb @@ -736,6 +736,43 @@ IF (has_entity_locators) { execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> } + execute \ + on passengers \ + if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ + run block zzz/as_locator/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.$(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 locator wasn't found. + tellraw @a <%TELLRAW.LOCATOR_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.LOCATOR_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function as_at_locator { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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')%> + } + execute \ on passengers \ if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ @@ -772,6 +809,27 @@ IF (has_entity_locators) { execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> } + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { + data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + block execute_as_uuid { with storage <%temp_storage%> args + $execute as $(uuid) run $(command) + } + } + } + } + + function as_at_all_locators { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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')%> + } + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid diff --git a/src/systems/datapackCompiler/1.20.5/static.mcb b/src/systems/datapackCompiler/1.20.5/static.mcb index 7b3f906f..7f0e2a7c 100644 --- a/src/systems/datapackCompiler/1.20.5/static.mcb +++ b/src/systems/datapackCompiler/1.20.5/static.mcb @@ -313,6 +313,43 @@ IF (has_entity_locators) { execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> } + execute \ + on passengers \ + if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ + run block zzz/as_locator/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.$(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 locator wasn't found. + tellraw @a <%TELLRAW.LOCATOR_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.LOCATOR_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function as_at_locator { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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')%> + } + execute \ on passengers \ if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ @@ -349,6 +386,27 @@ IF (has_entity_locators) { execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> } + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { + data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + block execute_as_uuid { with storage <%temp_storage%> args + $execute as $(uuid) run $(command) + } + } + } + } + + function as_at_all_locators { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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')%> + } + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid diff --git a/src/systems/datapackCompiler/1.21.0/animation.mcb b/src/systems/datapackCompiler/1.21.0/animation.mcb index f1de4b3d..1282d15c 100644 --- a/src/systems/datapackCompiler/1.21.0/animation.mcb +++ b/src/systems/datapackCompiler/1.21.0/animation.mcb @@ -736,6 +736,43 @@ IF (has_entity_locators) { execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> } + execute \ + on passengers \ + if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ + run block zzz/as_locator/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.$(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 locator wasn't found. + tellraw @a <%TELLRAW.LOCATOR_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.LOCATOR_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function as_at_locator { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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')%> + } + execute \ on passengers \ if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ @@ -772,6 +809,27 @@ IF (has_entity_locators) { execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> } + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { + data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + block execute_as_uuid { with storage <%temp_storage%> args + $execute as $(uuid) run $(command) + } + } + } + } + + function as_at_all_locators { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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')%> + } + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid diff --git a/src/systems/datapackCompiler/1.21.0/static.mcb b/src/systems/datapackCompiler/1.21.0/static.mcb index 37337da0..fdee22d7 100644 --- a/src/systems/datapackCompiler/1.21.0/static.mcb +++ b/src/systems/datapackCompiler/1.21.0/static.mcb @@ -313,6 +313,43 @@ IF (has_entity_locators) { execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> } + execute \ + on passengers \ + if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ + run block zzz/as_locator/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.$(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 locator wasn't found. + tellraw @a <%TELLRAW.LOCATOR_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.LOCATOR_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function as_at_locator { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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')%> + } + execute \ on passengers \ if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ @@ -349,6 +386,27 @@ IF (has_entity_locators) { execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> } + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { + data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + block execute_as_uuid { with storage <%temp_storage%> args + $execute as $(uuid) run $(command) + } + } + } + } + + function as_at_all_locators { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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')%> + } + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid diff --git a/src/systems/datapackCompiler/1.21.2/animation.mcb b/src/systems/datapackCompiler/1.21.2/animation.mcb index 80196da2..d01a981d 100644 --- a/src/systems/datapackCompiler/1.21.2/animation.mcb +++ b/src/systems/datapackCompiler/1.21.2/animation.mcb @@ -736,6 +736,43 @@ IF (has_entity_locators) { execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> } + execute \ + on passengers \ + if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ + run block zzz/as_locator/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.$(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 locator wasn't found. + tellraw @a <%TELLRAW.LOCATOR_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.LOCATOR_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function as_at_locator { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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')%> + } + execute \ on passengers \ if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ @@ -772,6 +809,27 @@ IF (has_entity_locators) { execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> } + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { + data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + block execute_as_uuid { with storage <%temp_storage%> args + $execute as $(uuid) run $(command) + } + } + } + } + + function as_at_all_locators { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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')%> + } + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid diff --git a/src/systems/datapackCompiler/1.21.2/static.mcb b/src/systems/datapackCompiler/1.21.2/static.mcb index 3e9261ee..2a41c81d 100644 --- a/src/systems/datapackCompiler/1.21.2/static.mcb +++ b/src/systems/datapackCompiler/1.21.2/static.mcb @@ -313,6 +313,43 @@ IF (has_entity_locators) { execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> } + execute \ + on passengers \ + if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ + run block zzz/as_locator/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.$(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 locator wasn't found. + tellraw @a <%TELLRAW.LOCATOR_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.LOCATOR_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function as_at_locator { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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')%> + } + execute \ on passengers \ if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ @@ -349,6 +386,27 @@ IF (has_entity_locators) { execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> } + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { + data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + block execute_as_uuid { with storage <%temp_storage%> args + $execute as $(uuid) run $(command) + } + } + } + } + + function as_at_all_locators { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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')%> + } + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid diff --git a/src/systems/datapackCompiler/1.21.4/animation.mcb b/src/systems/datapackCompiler/1.21.4/animation.mcb index 797ca19f..6f72c51d 100644 --- a/src/systems/datapackCompiler/1.21.4/animation.mcb +++ b/src/systems/datapackCompiler/1.21.4/animation.mcb @@ -736,6 +736,43 @@ IF (has_entity_locators) { execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> } + execute \ + on passengers \ + if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ + run block zzz/as_locator/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.$(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 locator wasn't found. + tellraw @a <%TELLRAW.LOCATOR_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.LOCATOR_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function as_at_locator { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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')%> + } + execute \ on passengers \ if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ @@ -772,6 +809,27 @@ IF (has_entity_locators) { execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> } + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { + data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + block execute_as_uuid { with storage <%temp_storage%> args + $execute as $(uuid) run $(command) + } + } + } + } + + function as_at_all_locators { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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')%> + } + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid diff --git a/src/systems/datapackCompiler/1.21.4/static.mcb b/src/systems/datapackCompiler/1.21.4/static.mcb index c26e6c12..90589a55 100644 --- a/src/systems/datapackCompiler/1.21.4/static.mcb +++ b/src/systems/datapackCompiler/1.21.4/static.mcb @@ -313,6 +313,43 @@ IF (has_entity_locators) { execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> } + execute \ + on passengers \ + if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ + run block zzz/as_locator/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.$(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 locator wasn't found. + tellraw @a <%TELLRAW.LOCATOR_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.LOCATOR_COMMAND_FAILED_TO_EXECUTE()%> + } + } + } + + function as_at_locator { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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')%> + } + execute \ on passengers \ if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ @@ -349,6 +386,27 @@ IF (has_entity_locators) { execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> } + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { + REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { + data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + block execute_as_uuid { with storage <%temp_storage%> args + $execute as $(uuid) run $(command) + } + } + } + } + + function as_at_all_locators { + #ARGS: {command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + + 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')%> + } + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid From 2553302fa2110ebffabae4b7ddbb0b3320b4db97 Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Thu, 18 Dec 2025 09:48:48 -0500 Subject: [PATCH 06/23] =?UTF-8?q?=F0=9F=90=9B=20Fix=20animation=20loop=20m?= =?UTF-8?q?ode=20`once`=20still=20looping?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #469 (nice) --- src/systems/datapackCompiler/1.20.4/animation.mcb | 1 + src/systems/datapackCompiler/1.20.5/animation.mcb | 1 + src/systems/datapackCompiler/1.21.0/animation.mcb | 1 + src/systems/datapackCompiler/1.21.2/animation.mcb | 1 + src/systems/datapackCompiler/1.21.4/animation.mcb | 1 + 5 files changed, 5 insertions(+) diff --git a/src/systems/datapackCompiler/1.20.4/animation.mcb b/src/systems/datapackCompiler/1.20.4/animation.mcb index ad4e1b00..a24141a6 100644 --- a/src/systems/datapackCompiler/1.20.4/animation.mcb +++ b/src/systems/datapackCompiler/1.20.4/animation.mcb @@ -288,6 +288,7 @@ dir animations { tag @s add <%TAGS.TRANSFORMS_ONLY()%> execute at @s run function ./zzz/set_frame {frame: 0} tag @s remove <%TAGS.TRANSFORMS_ONLY()%> + tag @s remove <%TAGS.ANIMATION_PLAYING(export_namespace, animation.storage_name)%> } } scoreboard players add @s <%OBJECTIVES.FRAME(animation.storage_name)%> 1 diff --git a/src/systems/datapackCompiler/1.20.5/animation.mcb b/src/systems/datapackCompiler/1.20.5/animation.mcb index b2780080..a2af7e82 100644 --- a/src/systems/datapackCompiler/1.20.5/animation.mcb +++ b/src/systems/datapackCompiler/1.20.5/animation.mcb @@ -288,6 +288,7 @@ dir animations { tag @s add <%TAGS.TRANSFORMS_ONLY()%> execute at @s run function ./zzz/set_frame {frame: 0} tag @s remove <%TAGS.TRANSFORMS_ONLY()%> + tag @s remove <%TAGS.ANIMATION_PLAYING(export_namespace, animation.storage_name)%> } } scoreboard players add @s <%OBJECTIVES.FRAME(animation.storage_name)%> 1 diff --git a/src/systems/datapackCompiler/1.21.0/animation.mcb b/src/systems/datapackCompiler/1.21.0/animation.mcb index 1282d15c..497512fb 100644 --- a/src/systems/datapackCompiler/1.21.0/animation.mcb +++ b/src/systems/datapackCompiler/1.21.0/animation.mcb @@ -288,6 +288,7 @@ dir animations { tag @s add <%TAGS.TRANSFORMS_ONLY()%> execute at @s run function ./zzz/set_frame {frame: 0} tag @s remove <%TAGS.TRANSFORMS_ONLY()%> + tag @s remove <%TAGS.ANIMATION_PLAYING(export_namespace, animation.storage_name)%> } } scoreboard players add @s <%OBJECTIVES.FRAME(animation.storage_name)%> 1 diff --git a/src/systems/datapackCompiler/1.21.2/animation.mcb b/src/systems/datapackCompiler/1.21.2/animation.mcb index d01a981d..710c5247 100644 --- a/src/systems/datapackCompiler/1.21.2/animation.mcb +++ b/src/systems/datapackCompiler/1.21.2/animation.mcb @@ -288,6 +288,7 @@ dir animations { tag @s add <%TAGS.TRANSFORMS_ONLY()%> execute at @s run function ./zzz/set_frame {frame: 0} tag @s remove <%TAGS.TRANSFORMS_ONLY()%> + tag @s remove <%TAGS.ANIMATION_PLAYING(export_namespace, animation.storage_name)%> } } scoreboard players add @s <%OBJECTIVES.FRAME(animation.storage_name)%> 1 diff --git a/src/systems/datapackCompiler/1.21.4/animation.mcb b/src/systems/datapackCompiler/1.21.4/animation.mcb index 6f72c51d..94257860 100644 --- a/src/systems/datapackCompiler/1.21.4/animation.mcb +++ b/src/systems/datapackCompiler/1.21.4/animation.mcb @@ -288,6 +288,7 @@ dir animations { tag @s add <%TAGS.TRANSFORMS_ONLY()%> execute at @s run function ./zzz/set_frame {frame: 0} tag @s remove <%TAGS.TRANSFORMS_ONLY()%> + tag @s remove <%TAGS.ANIMATION_PLAYING(export_namespace, animation.storage_name)%> } } scoreboard players add @s <%OBJECTIVES.FRAME(animation.storage_name)%> 1 From 8c2621a41e03b5d005c4ab1803cfed71c3557adb Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Thu, 18 Dec 2025 10:35:23 -0500 Subject: [PATCH 07/23] =?UTF-8?q?=E2=9C=A8=20Add=20buttery=20smooth=20came?= =?UTF-8?q?ra=20rotations=20to=201.21.4=20and=20above.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../datapackCompiler/1.21.2/animation.mcb | 7 +++ .../datapackCompiler/1.21.4/animation.mcb | 7 +++ .../blueprints/armor_stand_1.20.4.ajblueprint | 59 +++++++++++++++++- .../armor_stand_minimal_1.20.4.ajblueprint | 14 ++++- .../blueprints/armor_stand_latest.ajblueprint | 61 ++++++++++++++++++- .../datapacks/animated_java/pack.mcmeta | 2 +- .../datapacks/test-framework/src/test.mcb | 7 +++ test-packs/latest/resources/pack.mcmeta | 2 +- 8 files changed, 151 insertions(+), 8 deletions(-) diff --git a/src/systems/datapackCompiler/1.21.2/animation.mcb b/src/systems/datapackCompiler/1.21.2/animation.mcb index 710c5247..d0df4f82 100644 --- a/src/systems/datapackCompiler/1.21.2/animation.mcb +++ b/src/systems/datapackCompiler/1.21.2/animation.mcb @@ -130,6 +130,13 @@ dir root { positioned ^$(px) ^$(py) ^$(pz) \ rotated ~$(ry) ~$(rx) \ run tp @s ~ ~ ~ ~ ~ + # Precise Rotation Workaround. Fixes MC-272913. + # Thanks @Triton365! (https://discord.com/channels/154777837382008833/157097006500806656/1402253905408163842) + $execute \ + as $(uuid) \ + store success entity @s OnGround byte 1 \ + store success score @s <%OBJECTIVES.I()%> \ + unless score @s <%OBJECTIVES.I()%> matches 1 } } } diff --git a/src/systems/datapackCompiler/1.21.4/animation.mcb b/src/systems/datapackCompiler/1.21.4/animation.mcb index 94257860..e88b8a26 100644 --- a/src/systems/datapackCompiler/1.21.4/animation.mcb +++ b/src/systems/datapackCompiler/1.21.4/animation.mcb @@ -130,6 +130,13 @@ dir root { positioned ^$(px) ^$(py) ^$(pz) \ rotated ~$(ry) ~$(rx) \ run tp @s ~ ~ ~ ~ ~ + # Precise Rotation Workaround. Fixes MC-272913. + # Thanks @Triton365! (https://discord.com/channels/154777837382008833/157097006500806656/1402253905408163842) + $execute \ + as $(uuid) \ + store success entity @s OnGround byte 1 \ + store success score @s <%OBJECTIVES.I()%> \ + unless score @s <%OBJECTIVES.I()%> matches 1 } } } diff --git a/test-packs/1.20.4/blueprints/armor_stand_1.20.4.ajblueprint b/test-packs/1.20.4/blueprints/armor_stand_1.20.4.ajblueprint index c978b1d9..ae9cf654 100644 --- a/test-packs/1.20.4/blueprints/armor_stand_1.20.4.ajblueprint +++ b/test-packs/1.20.4/blueprints/armor_stand_1.20.4.ajblueprint @@ -1,7 +1,7 @@ { "meta": { "format": "animated-java:format/blueprint", - "format_version": "1.8.0-beta.2", + "format_version": "1.8.1", "uuid": "167b27cd-b559-3f13-a97c-0841fe21f1d1", "save_location": "D:\\github-repos\\animated-java\\old-animated-java\\test-packs\\1.20.4\\blueprints\\armor_stand_1.20.4.ajblueprint", "last_used_export_namespace": "armor_stand" @@ -2089,7 +2089,7 @@ "override": false, "length": 1, "snapping": 20, - "selected": true, + "selected": false, "saved": true, "path": "", "anim_time_update": "", @@ -2298,6 +2298,61 @@ ] } } + }, + { + "uuid": "7369e950-97e5-3a18-7b5a-f0369a42cdb4", + "name": "camera_test", + "loop": "loop", + "override": false, + "length": 120, + "snapping": 20, + "selected": true, + "saved": true, + "path": "", + "anim_time_update": "", + "blend_weight": "", + "start_delay": "", + "loop_delay": "0", + "excluded_nodes": [], + "animators": { + "808e3c26-7285-af3f-a079-d8b899176dd3": { + "name": "head", + "type": "bone", + "keyframes": [ + { + "channel": "rotation", + "data_points": [ + { + "x": "0", + "y": "0", + "z": "0" + } + ], + "uuid": "d724507d-0fc4-e256-2dec-90ab2497fcb7", + "time": 0, + "color": -1, + "interpolation": "linear", + "easing": "linear", + "easingArgs": [] + }, + { + "channel": "rotation", + "data_points": [ + { + "x": "0", + "y": "360", + "z": "0" + } + ], + "uuid": "414a86e8-4127-88a3-551c-ec015f01ba8a", + "time": 120, + "color": -1, + "interpolation": "linear", + "easing": "linear" + } + ] + } + } } ], "animation_variable_placeholders": "v.walk_speed = 360 * 1;\nv.run_speed = 360 * 1.5;\nv.stickbug_speed = 360 * 1.25;\n" diff --git a/test-packs/1.20.4/blueprints/armor_stand_minimal_1.20.4.ajblueprint b/test-packs/1.20.4/blueprints/armor_stand_minimal_1.20.4.ajblueprint index b9080027..33aa6a15 100644 --- a/test-packs/1.20.4/blueprints/armor_stand_minimal_1.20.4.ajblueprint +++ b/test-packs/1.20.4/blueprints/armor_stand_minimal_1.20.4.ajblueprint @@ -1,7 +1,7 @@ { "meta": { "format": "animated-java:format/blueprint", - "format_version": "1.8.0", + "format_version": "1.8.1", "uuid": "173aa82f-fb4f-c354-ad34-a34d7fbce647", "save_location": "D:\\github-repos\\animated-java\\old-animated-java\\test-packs\\1.20.4\\blueprints\\armor_stand_minimal_1.20.4.ajblueprint", "last_used_export_namespace": "armor_stand_minimal" @@ -447,6 +447,7 @@ "name": "root", "origin": [0, 0, 0], "color": 0, + "onSummonFunction": "", "configs": { "default": { "billboard": "fixed", @@ -476,6 +477,7 @@ "name": "baseplate_root", "origin": [0, 0, 0], "color": 0, + "onSummonFunction": "", "configs": { "default": { "billboard": "fixed", @@ -505,6 +507,7 @@ "name": "baseplate_pivot_a", "origin": [0, 1, 6], "color": 0, + "onSummonFunction": "", "configs": { "default": { "billboard": "fixed", @@ -537,6 +540,7 @@ "name": "armor_stand_root", "origin": [0, 1, 0], "color": 0, + "onSummonFunction": "", "configs": { "default": { "billboard": "fixed", @@ -567,6 +571,7 @@ "name": "waist_pivot", "origin": [0, 12, 0], "color": 0, + "onSummonFunction": "", "configs": { "default": { "billboard": "fixed", @@ -599,6 +604,7 @@ "name": "body_waist_pivot", "origin": [0, 12, 0], "color": 0, + "onSummonFunction": "", "configs": { "default": { "billboard": "fixed", @@ -631,6 +637,7 @@ "name": "body", "origin": [0, 24, 0], "color": 0, + "onSummonFunction": "", "configs": { "default": { "billboard": "fixed", @@ -696,6 +703,7 @@ "name": "head", "origin": [0, 23, 0], "color": 0, + "onSummonFunction": "", "configs": { "default": { "billboard": "fixed", @@ -744,6 +752,7 @@ "name": "left_arm", "origin": [-6, 23, 0], "color": 0, + "onSummonFunction": "", "configs": { "default": { "billboard": "fixed", @@ -792,6 +801,7 @@ "name": "right_arm", "origin": [6, 23, 0], "color": 0, + "onSummonFunction": "", "configs": { "default": { "billboard": "fixed", @@ -844,6 +854,7 @@ "name": "left_leg", "origin": [2, 12, 0], "color": 0, + "onSummonFunction": "", "configs": { "default": { "billboard": "fixed", @@ -890,6 +901,7 @@ "name": "right_leg", "origin": [-2, 12, 0], "color": 0, + "onSummonFunction": "", "configs": { "default": { "billboard": "fixed", diff --git a/test-packs/latest/blueprints/armor_stand_latest.ajblueprint b/test-packs/latest/blueprints/armor_stand_latest.ajblueprint index f908069b..c51345bd 100644 --- a/test-packs/latest/blueprints/armor_stand_latest.ajblueprint +++ b/test-packs/latest/blueprints/armor_stand_latest.ajblueprint @@ -1,7 +1,7 @@ { "meta": { "format": "animated-java:format/blueprint", - "format_version": "1.8.0-beta.4", + "format_version": "1.8.1", "uuid": "167b27cd-b559-3f13-a97c-0841fe21f1d1", "save_location": "D:\\github-repos\\animated-java\\old-animated-java\\test-packs\\latest\\blueprints\\armor_stand_latest.ajblueprint", "last_used_export_namespace": "armor_stand" @@ -17,7 +17,6 @@ "data_pack": "../datapacks/animated_java", "on_summon_function": "say On-Summon!", "on_remove_function": "say On-Remove!", - "auto_update_rig_orientation": false, "baked_animations": false, "json_file": "../testPluginExport.json" }, @@ -2089,7 +2088,7 @@ "override": false, "length": 1, "snapping": 20, - "selected": true, + "selected": false, "saved": true, "path": "", "anim_time_update": "", @@ -2298,6 +2297,62 @@ ] } } + }, + { + "uuid": "8b5ebbc6-1189-bb9a-00f6-6ef68e212fc1", + "name": "camera_test", + "loop": "loop", + "override": false, + "length": 90, + "snapping": 20, + "selected": true, + "saved": true, + "path": "", + "anim_time_update": "", + "blend_weight": "", + "start_delay": "", + "loop_delay": "0", + "excluded_nodes": [], + "animators": { + "3ffeee76-901e-f8a2-c29a-82f90e16fd1e": { + "name": "camera", + "type": "camera", + "keyframes": [ + { + "channel": "position", + "data_points": [ + { + "x": "math.sin(q.life_time * 4) * 100", + "y": "0", + "z": "math.cos(q.life_time * 4) * 100" + } + ], + "uuid": "7ea87827-b1e4-6f50-5a62-b4a2b3e25b54", + "time": 0, + "color": -1, + "interpolation": "linear", + "easing": "linear", + "easingArgs": [] + }, + { + "channel": "rotation", + "data_points": [ + { + "x": "0", + "y": "q.life_time * 4", + "z": "0" + } + ], + "uuid": "3e52691f-2314-0f11-1f4f-d6ce872df516", + "time": 0, + "color": -1, + "interpolation": "linear", + "easing": "linear", + "easingArgs": [] + } + ] + } + } } ], "animation_variable_placeholders": "v.walk_speed = 360 * 1;\nv.run_speed = 360 * 1.5;\nv.stickbug_speed = 360 * 1.25;\n" diff --git a/test-packs/latest/datapacks/animated_java/pack.mcmeta b/test-packs/latest/datapacks/animated_java/pack.mcmeta index 06f88648..5810daaa 100644 --- a/test-packs/latest/datapacks/animated_java/pack.mcmeta +++ b/test-packs/latest/datapacks/animated_java/pack.mcmeta @@ -1,7 +1,7 @@ { "pack": { "description": "AJ Testing DP", - "min_format": 88, + "min_format": 93.1, "max_format": 9999999 } } \ No newline at end of file diff --git a/test-packs/latest/datapacks/test-framework/src/test.mcb b/test-packs/latest/datapacks/test-framework/src/test.mcb index d885e9f9..33a776b9 100644 --- a/test-packs/latest/datapacks/test-framework/src/test.mcb +++ b/test-packs/latest/datapacks/test-framework/src/test.mcb @@ -41,4 +41,11 @@ dir armor_stand { } } } + + function test_precise_camera_rotation { + function animated_java:global/remove/everything + execute positioned 0 1 0 rotated 0 0 run function animated_java:armor_stand/summon {args:{animation:'camera_test',start_animation:true}} + gamemode spectator @s + execute as @n[tag=aj.global.root] run function animated_java:armor_stand/as_camera {name:'camera',command:'spectate @s @p'} + } } diff --git a/test-packs/latest/resources/pack.mcmeta b/test-packs/latest/resources/pack.mcmeta index a90fb61a..b33969cb 100644 --- a/test-packs/latest/resources/pack.mcmeta +++ b/test-packs/latest/resources/pack.mcmeta @@ -1,6 +1,6 @@ { "pack": { - "min_format": 69, + "min_format": 74, "max_format": 9999999, "description": "AJ Testing RP" } From c12deb629840c62a3303c5683416307bc374fab7 Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Thu, 18 Dec 2025 14:15:00 -0500 Subject: [PATCH 08/23] =?UTF-8?q?=E2=9C=A8=20Buttery=20Smooth=20rotation?= =?UTF-8?q?=20for=20rigs=20in=20MC=201.21.4=20and=20above?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Switched to UUID selection over passenger branching. - Added `as_node` function. - Fixed `move` function not transforming floating entities correctly. --- src/lang/en.yml | 2 +- .../datapackCompiler/1.20.4/global.mcbt | 2 +- .../datapackCompiler/1.21.4/animation.mcb | 222 +++++++++++------- src/systems/datapackCompiler/tellraw.ts | 18 ++ .../blueprints/armor_stand_latest.ajblueprint | 175 +++++++++++++- .../datapacks/test-framework/src/test.mcb | 6 + 6 files changed, 336 insertions(+), 89 deletions(-) diff --git a/src/lang/en.yml b/src/lang/en.yml index d2649f4f..c27d8aa5 100644 --- a/src/lang/en.yml +++ b/src/lang/en.yml @@ -278,7 +278,7 @@ animated_java.dialog.blueprint_settings.json_file.description: The path to the J animated_java.dialog.blueprint_settings.json_file.error.no_file_selected: No file selected! animated_java.dialog.blueprint_settings.json_file.error.not_a_file: The selected path is not a file! -## Bone Config Dialog +## Display Entity Config Dialog animated_java.dialog.display_entity.title: Display Entity Config for "{0}" animated_java.dialog.display_entity.node_options.title: Node diff --git a/src/systems/datapackCompiler/1.20.4/global.mcbt b/src/systems/datapackCompiler/1.20.4/global.mcbt index c6b8bc0b..f0aeff7e 100644 --- a/src/systems/datapackCompiler/1.20.4/global.mcbt +++ b/src/systems/datapackCompiler/1.20.4/global.mcbt @@ -5,4 +5,4 @@ template debug { tellraw @a <%TELLRAW.FUNCTION_NOT_EXECUTED_AS_ROOT_ERROR(context.functions.at(-1), root_tag)%> } } -} \ No newline at end of file +} diff --git a/src/systems/datapackCompiler/1.21.4/animation.mcb b/src/systems/datapackCompiler/1.21.4/animation.mcb index e88b8a26..851d75b6 100644 --- a/src/systems/datapackCompiler/1.21.4/animation.mcb +++ b/src/systems/datapackCompiler/1.21.4/animation.mcb @@ -62,17 +62,22 @@ dir root { IF (auto_update_rig_orientation) { IF (has_locators || has_cameras) { - execute \ - on passengers \ - if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ - run function ./on_tick/transform_floating_entities + execute on passengers run function ./on_tick/transform_floating_entities + } + execute on passengers run { with entity @s data.uuids_by_name + REPEAT (Object.values(rig.nodes).filter(node => BONE_TYPES.includes(node.type))) as node { + $rotate $(<%node.storage_name%>) ~ ~ + # Precise Rotation Workaround. Fixes MC-272913. + # Thanks @Triton365! (https://discord.com/channels/154777837382008833/157097006500806656/1402253905408163842) + $execute \ + as $(<%node.storage_name%>) \ + store success entity @s OnGround byte 1 \ + store success score @s <%OBJECTIVES.I()%> \ + unless score @s <%OBJECTIVES.I()%> matches 1 + } } - execute on passengers run rotate @s ~ ~ } ELSE IF (has_ticking_locators) { - execute \ - on passengers \ - if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ - run function ./on_tick/transform_locators + execute on passengers run function ./on_tick/transform_locators } # Custom post-tick function @@ -106,6 +111,16 @@ dir root { %%> } } + + IF (locator.config?.sync_passenger_rotation) { + # Precise Rotation Workaround. Fixes MC-272913. + # Thanks @Triton365! (https://discord.com/channels/154777837382008833/157097006500806656/1402253905408163842) + $execute \ + as $(uuid) \ + store success entity @s OnGround byte 1 \ + store success score @s <%OBJECTIVES.I()%> \ + unless score @s <%OBJECTIVES.I()%> matches 1 + } } ELSE IF (locator.config?.on_tick_function) { $execute \ positioned ^$(px) ^$(py) ^$(pz) \ @@ -150,13 +165,21 @@ IF (!auto_update_rig_orientation) { tp @s ~ ~ ~ ~ ~ - IF (has_locators || has_cameras) { - execute \ - at @s on passengers \ - if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ - run function ./on_tick/transform_floating_entities + execute on passengers run { with entity @s data.uuids_by_name + IF (has_locators || has_cameras) { + function ./root/on_tick/transform_floating_entities + } + REPEAT (Object.values(rig.nodes).filter(node => BONE_TYPES.includes(node.type))) as node { + $rotate $(<%node.storage_name%>) ~ ~ + # Precise Rotation Workaround. Fixes MC-272913. + # Thanks @Triton365! (https://discord.com/channels/154777837382008833/157097006500806656/1402253905408163842) + $execute \ + as $(<%node.storage_name%>) \ + store success entity @s OnGround byte 1 \ + store success score @s <%OBJECTIVES.I()%> \ + unless score @s <%OBJECTIVES.I()%> matches 1 + } } - execute at @s on passengers run rotate @s ~ ~ } } ELSE { function move { @@ -255,8 +278,15 @@ dir animations { # Tweening logic scoreboard players remove @s <%OBJECTIVES.TWEEN_DURATION()%> 1 execute if score @s <%OBJECTIVES.TWEEN_DURATION()%> matches 1.. run return 1 - execute if score @s <%OBJECTIVES.TWEEN_DURATION()%> matches 0 on passengers run \ - data modify entity @s interpolation_duration set value <%interpolation_duration%> + execute \ + if score @s <%OBJECTIVES.TWEEN_DURATION()%> matches 0 \ + on passengers \ + run { with entity @s data.uuids_by_name + REPEAT (Object.values(rig.nodes).filter(node => BONE_TYPES.includes(node.type))) as node { + $data modify entity $(<%node.storage_name%>) interpolation_duration set value <%interpolation_duration%> + } + } + # Animation logic IF (animation.loop_mode === 'loop' && animation.loop_delay === 0) { # Makes sure function keyframes in the last frame of the animation are activated. @@ -305,24 +335,31 @@ dir animations { function set_frame { #ARGS: {frame: int} $function ./apply_frame {frame: $(frame)} - execute on passengers run data modify entity @s[tag=!<%TAGS.GLOBAL_DATA()%>] start_interpolation set value -1 + execute on passengers run { with entity @s data.uuids_by_name + REPEAT (Object.values(rig.nodes).filter(node => BONE_TYPES.includes(node.type))) as node { + $data modify entity $(<%node.storage_name%>) start_interpolation set value -1 + } + } return 1 } function apply_frame { #ARGS: {frame: int} - REPEAT (Object.values(animation.modified_nodes).sort(nodeSorter)) as node { - IF (BONE_TYPES.includes(node.type)) { - $execute on passengers if entity @s[tag=<%TAGS.PROJECT_NODE_NAMED(export_namespace, node.storage_name)%>] run \ - data modify entity @s {} merge from \ - storage <%project_storage%>/animations <%animation.storage_name%>.$(frame).<%node.type.charAt(0) + '_' + node.storage_name%> - } ELSE IF (node.type === 'locator' || node.type === 'camera') { - $execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run \ - $data modify entity @s data.uuids_by_name.<%node.type + '_' + node.storage_name%> merge from \ - storage <%project_storage%>/animations <%animation.storage_name%>.$(frame).<%node.type.charAt(0) + '_' + node.storage_name%> + + execute on passengers run { with entity @s data.uuids_by_name + REPEAT (Object.values(animation.modified_nodes).sort(nodeSorter)) as node { + IF (BONE_TYPES.includes(node.type)) { + $data modify entity $(<%node.storage_name%>) {} merge from \ + storage <%project_storage%>/animations <%animation.storage_name%>.$(frame).<%node.storage_name%> + } ELSE IF (node.type === 'locator' || node.type === 'camera') { + $execute on passengers run \ + $data modify entity @s data.uuids_by_name.<%node.storage_name%> merge from \ + storage <%project_storage%>/animations <%animation.storage_name%>.$(frame).<%node.storage_name%> + } } } + IF (animation.frames.some(anim => anim.variant)) { $execute \ if data storage <%project_storage%>/animations <%animation.storage_name%>.$(frame).variant \ @@ -339,18 +376,21 @@ dir animations { function set_frame { # Sets the frame without interpolation #ARGS: {frame: int} - $execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run \ + $execute on passengers run \ function ./frames/$(frame) with entity @s data.uuids_by_name - execute on passengers if entity @s[tag=!<%TAGS.GLOBAL_DATA()%>] run \ - data modify entity @s start_interpolation set value -1 + execute on passengers run { with entity @s data.uuids_by_name + REPEAT (Object.values(rig.nodes).filter(node => BONE_TYPES.includes(node.type))) as node { + $data modify entity $(<%node.storage_name%>) start_interpolation set value -1 + } + } return 1 } function apply_frame { #ARGS: {frame: int} - $execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run \ + $execute on passengers run \ function ./frames/$(frame) with entity @s data.uuids_by_name return 1 @@ -383,19 +423,20 @@ dir animations { if (transform.interpolation === 'pre-post' || isStepInterpolation) { frameFunc += - `\n$data merge entity $(${node.type + '_' + node.storage_name})%NEWLINE_PATCH%{` + `\n$data merge entity $(${node.storage_name})%NEWLINE_PATCH%{` + `transformation: ${matrixToNbtFloatArray(transform.matrix).toString()},` + `start_interpolation: ${isStepInterpolation ? -1 : 0},` + `interpolation_duration: ${isStepInterpolation ? 0 : interpolation_duration}` + `}` } else { frameFunc += - `\n$data merge entity $(${node.type + '_' + node.storage_name})%NEWLINE_PATCH%{` + `\n$data merge entity $(${node.storage_name})%NEWLINE_PATCH%{` + `transformation: ${matrixToNbtFloatArray(transform.matrix).toString()},` + `start_interpolation: 0,` + `interpolation_duration: ${interpolation_duration}` + `}` } + ;hasFunction = true break } @@ -415,7 +456,7 @@ dir animations { if (transform.function) { if (node.config?.use_entity) { frameFunc += - `\n$execute on vehicle unless entity @s[tag=${TAGS.TRANSFORMS_ONLY()}] as $(${node.type + '_' + node.storage_name}) ` + `\n$execute on vehicle 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 ` @@ -555,7 +596,7 @@ function summon { function *global/gu/get_entity_uuid_string } data modify entity @s data.uuids append from storage <%gu_storage%> out - data modify entity @s data.uuids_by_name.<%locator.type + '_' + locator.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%locator.storage_name%> set from storage <%gu_storage%> out data modify entity @s data.locators.<%locator.storage_name%>.uuid set from storage <%gu_storage%> out } @@ -578,7 +619,7 @@ function summon { function *global/gu/get_entity_uuid_string } data modify entity @s data.uuids append from storage <%gu_storage%> out - data modify entity @s data.uuids_by_name.<%camera.type + '_' + camera.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%camera.storage_name%> set from storage <%gu_storage%> out data modify entity @s data.cameras.<%camera.storage_name%>.uuid set from storage <%gu_storage%> out } @@ -591,7 +632,7 @@ function summon { function *global/gu/get_entity_uuid_string data modify entity @s data.uuids append from storage <%gu_storage%> out - data modify entity @s data.uuids_by_name.<%node.type + '_' + node.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%node.storage_name%> set from storage <%gu_storage%> out } } @@ -728,6 +769,41 @@ 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 unless entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run ride @s dismount + } +} + +function as_node { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + 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')%> + } + + execute on passengers run block zzz/as_node/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from entity @s data.uuids_by_name.$(name) + 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 node wasn't found. + tellraw @a <%TELLRAW.NODE_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.NODE_COMMAND_FAILED_TO_EXECUTE()%> + } } } @@ -744,10 +820,7 @@ IF (has_entity_locators) { execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> } - execute \ - on passengers \ - if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ - run block zzz/as_locator/as_data { with storage <%temp_storage%> args + execute on passengers run block zzz/as_locator/as_data { with storage <%temp_storage%> args $data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.$(name).uuid IF (debug_mode) { scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 @@ -781,10 +854,7 @@ IF (has_entity_locators) { execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> } - execute \ - on passengers \ - if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ - run block zzz/as_locator/as_data { with storage <%temp_storage%> args + execute on passengers run block zzz/as_locator/as_data { with storage <%temp_storage%> args $data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.$(name).uuid IF (debug_mode) { scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 @@ -817,7 +887,7 @@ IF (has_entity_locators) { execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> } - execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { + execute on passengers run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid block execute_as_uuid { with storage <%temp_storage%> args @@ -838,7 +908,7 @@ IF (has_entity_locators) { execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> } - execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { + execute on passengers run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid block execute_as_uuid { with storage <%temp_storage%> args @@ -862,11 +932,7 @@ IF (has_locators) { execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> } - execute \ - at @s \ - on passengers \ - if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ - run block zzz/at_locator/as_data { with storage <%temp_storage%> args + execute at @s on passengers run block zzz/at_locator/as_data { with storage <%temp_storage%> args $data modify storage <%temp_storage%> args merge from entity @s data.locators.$(name) IF (debug_mode) { @@ -903,7 +969,7 @@ IF (has_locators) { execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> } - execute at @s on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/at_all_locators/as_data { + execute at @s on passengers run block zzz/at_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { data modify storage <%temp_storage%> args merge from entity @s data.locators.<%locator.storage_name%> block execute_at_transform { with storage <%temp_storage%> args @@ -930,10 +996,7 @@ IF (has_cameras) { execute if data storage <%temp_storage%> {args:{command:''}} run return run tellraw @a <%TELLRAW.ARGUMENT_CANNOT_BE_EMPTY('command')%> } - execute \ - on passengers \ - if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ - run block zzz/as_camera/as_data { with storage <%temp_storage%> args + execute on passengers run block zzz/as_camera/as_data { with storage <%temp_storage%> args $data modify storage <%temp_storage%> args.uuid set from entity @s data.cameras.$(name).uuid IF (debug_mode) { @@ -981,7 +1044,7 @@ dir remove { %%> IF (has_entity_locators) { - execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block as_data { + execute on passengers run block as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { IF (locator.config?.on_remove_function) { IF (locator.config.use_entity) { @@ -1017,7 +1080,7 @@ dir remove { debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> IF (has_entity_locators || has_cameras) { - execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block as_data { + execute on passengers run block as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { function animated_java:global/remove/entity_stack_by_uuid with entity @s data.locators.<%locator.storage_name%> } @@ -1030,11 +1093,16 @@ dir remove { # Remove the rig using the more expensive & thorough method if the rig_hash doesn't match. execute \ on passengers \ - if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ unless data entity @s {data:{rig_hash: '<%rig_hash%>'}} \ on vehicle \ run function animated_java:global/remove/outdated_rig + execute on passengers run { with entity @s data.uuids_by_name + REPEAT (Object.values(rig.nodes).filter(node => BONE_TYPES.includes(node.type))) as node { + $kill $(<%node.storage_name%>) + } + } + function animated_java:global/remove/entity_stack } } @@ -1065,13 +1133,9 @@ IF (Object.keys(rig.variants).length > 1) { ) %%> - REPEAT (global.filteredNodes) as node { - execute \ - on passengers \ - if entity @s[tag=<%TAGS.PROJECT_NODE_NAMED(export_namespace, node.storage_name)%>] \ - at @s \ - run \ - block zzz/apply_to_node_<%node.storage_name%> { + execute on passengers run { with entity @s data.uuids_by_name + REPEAT (global.filteredNodes) as node { + $execute as $(<%node.storage_name%>) run block zzz/apply_to_node_<%node.storage_name%> { IF (node.type === 'bone' && variant.models[node.uuid] !== undefined) { # Special case for `animated_java:empty` model. IF (variant.models[node.uuid].model === null) { @@ -1093,8 +1157,10 @@ IF (Object.keys(rig.variants).length > 1) { %%> } } + } } } + # Return success to allow this function to be used in function conditions. return 1 } @@ -1111,7 +1177,6 @@ IF (has_locators || has_cameras) { execute \ at @s \ on passengers \ - if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ run block zzz/set_default_pose/as_locator_<%locator.storage_name%> { with entity @s data.locators.<%locator.storage_name%> IF (locator.config?.use_entity) { $tp $(uuid) \ @@ -1138,7 +1203,6 @@ IF (has_locators || has_cameras) { execute \ at @s \ on passengers \ - if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] \ run block zzz/set_default_pose/as_camera_<%camera.storage_name%> { with entity @s data.cameras.<%camera.storage_name%> $tp $(uuid) \ ^<%roundTo(camera.default_transform.pos[0], 10)%> \ @@ -1169,15 +1233,13 @@ function apply_default_pose { function ./zzz/reset_floating_entities } - REPEAT (Object.values(rig.nodes).filter(node => BONE_TYPES.includes(node.type))) as node { - execute \ - on passengers \ - if entity @s[tag=<%TAGS.PROJECT_NODE_NAMED(export_namespace, node.storage_name)%>] \ - run \ - data merge entity @s { \ + execute on passengers run { with entity @s data.uuids_by_name + REPEAT (Object.values(rig.nodes).filter(node => BONE_TYPES.includes(node.type))) as node { + $data merge entity $(<%node.storage_name%>) { \ transformation: <%matrixToNbtFloatArray(node.default_transform.matrix).toString()%>, \ start_interpolation: 0 \ } + } } } @@ -1189,14 +1251,12 @@ function set_default_pose { function ./zzz/reset_floating_entities } - REPEAT (Object.values(rig.nodes).filter(node => node.type !== 'locator' && node.type !== 'camera')) as node { - execute \ - on passengers \ - if entity @s[tag=<%TAGS.PROJECT_NODE_NAMED(export_namespace, node.storage_name)%>] \ - run \ - data merge entity @s { \ + execute on passengers run { with entity @s data.uuids_by_name + REPEAT (Object.values(rig.nodes).filter(node => BONE_TYPES.includes(node.type))) as node { + $data merge entity $(<%node.storage_name%>) { \ transformation: <%matrixToNbtFloatArray(node.default_transform.matrix).toString()%>, \ start_interpolation: -1 \ } + } } } diff --git a/src/systems/datapackCompiler/tellraw.ts b/src/systems/datapackCompiler/tellraw.ts index be84b04f..aa3e2e6d 100644 --- a/src/systems/datapackCompiler/tellraw.ts +++ b/src/systems/datapackCompiler/tellraw.ts @@ -287,6 +287,24 @@ namespace TELLRAW { '\n Please ensure the command is valid.', ]) + export const NODE_ENTITY_NOT_FOUND = () => + TELLRAW_ERROR('Node Not Found', [ + 'Node ', + { nbt: 'args.name', storage: 'animated_java:temp', color: 'aqua' }, + ' does not exist!', + '\n Please ensure that its name is spelled correctly.', + ]) + + export const NODE_COMMAND_FAILED_TO_EXECUTE = () => + TELLRAW_ERROR('Failed to Execute Command as Node', [ + 'Failed to execute command ', + { nbt: 'args.command', storage: 'animated_java:temp', color: 'yellow' }, + ' as Node ', + { nbt: 'args.name', storage: 'animated_java:temp', color: 'aqua' }, + '.', + '\n Please ensure the command is valid.', + ]) + export const AUTO_UPDATE_RIG_ORIENTATION_MOVE_WARNING = () => TELLRAW_ERROR('Called Move Function while Auto Update Rig Orientation is Enabled', [ 'The ', diff --git a/test-packs/latest/blueprints/armor_stand_latest.ajblueprint b/test-packs/latest/blueprints/armor_stand_latest.ajblueprint index c51345bd..38766698 100644 --- a/test-packs/latest/blueprints/armor_stand_latest.ajblueprint +++ b/test-packs/latest/blueprints/armor_stand_latest.ajblueprint @@ -17,6 +17,7 @@ "data_pack": "../datapacks/animated_java", "on_summon_function": "say On-Summon!", "on_remove_function": "say On-Remove!", + "auto_update_rig_orientation": false, "baked_animations": false, "json_file": "../testPluginExport.json" }, @@ -829,7 +830,7 @@ "config": { "use_entity": true, "entity_type": "minecraft:item_display", - "sync_passenger_rotation": false, + "sync_passenger_rotation": true, "on_summon_function": "say Summon Left Hand!", "on_remove_function": "say Remove Left Hand!", "on_tick_function": "" @@ -872,6 +873,7 @@ "shadow": true, "seeThrough": true, "onSummonFunction": "", + "preciseRotationFix": false, "configs": { "default": { "on_apply_function": "", @@ -928,6 +930,7 @@ "item": "minecraft:diamond", "itemDisplay": "none", "onSummonFunction": "say I'm a Diamond!", + "preciseRotationFix": false, "configs": { "default": { "on_apply_function": "", @@ -958,6 +961,90 @@ } } } + }, + { + "name": "cube", + "box_uv": false, + "rescale": false, + "locked": false, + "light_emission": 0, + "render_order": "default", + "allow_mirror_modeling": true, + "from": [-38, 0, -13], + "to": [-26, 1, -1], + "autouv": 0, + "color": 2, + "origin": [-32, 0, -7], + "faces": { + "north": { + "uv": [12, 44, 24, 45], + "texture": 0 + }, + "east": { + "uv": [0, 44, 12, 45], + "texture": 0 + }, + "south": { + "uv": [36, 44, 48, 45], + "texture": 0 + }, + "west": { + "uv": [24, 44, 36, 45], + "texture": 0 + }, + "up": { + "uv": [12, 32, 24, 44], + "texture": 0 + }, + "down": { + "uv": [24, 32, 36, 44], + "texture": 0 + } + }, + "type": "cube", + "uuid": "a5106dee-70c2-6b1a-f446-7275ffb9c916" + }, + { + "name": "cube", + "box_uv": false, + "rescale": false, + "locked": false, + "light_emission": 0, + "render_order": "default", + "allow_mirror_modeling": true, + "from": [-38, 0, 1], + "to": [-26, 1, 13], + "autouv": 0, + "color": 2, + "origin": [-32, 0, 7], + "faces": { + "north": { + "uv": [12, 44, 24, 45], + "texture": 0 + }, + "east": { + "uv": [0, 44, 12, 45], + "texture": 0 + }, + "south": { + "uv": [36, 44, 48, 45], + "texture": 0 + }, + "west": { + "uv": [24, 44, 36, 45], + "texture": 0 + }, + "up": { + "uv": [12, 32, 24, 44], + "texture": 0 + }, + "down": { + "uv": [24, 32, 36, 44], + "texture": 0 + } + }, + "type": "cube", + "uuid": "b54db0a5-ee46-f8fd-f26f-d87e34896dcf" } ], "outliner": [ @@ -966,6 +1053,7 @@ "origin": [0, 0, 0], "color": 0, "onSummonFunction": "", + "preciseRotationFix": false, "configs": { "default": { "billboard": "fixed", @@ -996,6 +1084,7 @@ "origin": [0, 0, 0], "color": 0, "onSummonFunction": "", + "preciseRotationFix": false, "configs": { "default": { "billboard": "fixed", @@ -1026,19 +1115,20 @@ "origin": [0, 1, 6], "color": 0, "onSummonFunction": "", + "preciseRotationFix": false, "configs": { "default": { + "on_apply_function": "", "billboard": "fixed", + "override_brightness": false, "brightness_override": 0, "enchanted": false, - "glow_color": "#ffffff", "glowing": false, - "inherit_settings": true, + "override_glow_color": false, + "glow_color": "#ffffff", "invisible": false, - "nbt": "{}", "shadow_radius": 0, - "shadow_strength": 1, - "use_nbt": false + "shadow_strength": 1 }, "variants": {} }, @@ -1054,11 +1144,76 @@ } ] }, + { + "name": "baseplate_pivot_a2", + "origin": [-32, 0.5, -7], + "color": 0, + "onSummonFunction": "", + "preciseRotationFix": false, + "configs": { + "default": { + "on_apply_function": "", + "billboard": "fixed", + "override_brightness": false, + "brightness_override": 0, + "enchanted": false, + "glowing": false, + "override_glow_color": false, + "glow_color": "#ffffff", + "invisible": false, + "shadow_radius": 0, + "shadow_strength": 1 + }, + "variants": {} + }, + "uuid": "37f6cf1d-c8fc-620f-91a2-59bd597c3759", + "export": true, + "mirror_uv": false, + "isOpen": true, + "locked": false, + "visibility": true, + "autouv": 0, + "selected": false, + "children": ["a5106dee-70c2-6b1a-f446-7275ffb9c916"] + }, + { + "name": "baseplate_pivot_a3", + "origin": [-32, 0.5, 7], + "color": 0, + "onSummonFunction": "", + "preciseRotationFix": true, + "configs": { + "default": { + "on_apply_function": "", + "billboard": "fixed", + "override_brightness": false, + "brightness_override": 0, + "enchanted": false, + "glowing": false, + "override_glow_color": false, + "glow_color": "#ffffff", + "invisible": false, + "shadow_radius": 0, + "shadow_strength": 1 + }, + "variants": {} + }, + "uuid": "4dea286c-8490-d441-ee02-c3d24d47d034", + "export": true, + "mirror_uv": false, + "isOpen": true, + "locked": false, + "visibility": true, + "autouv": 0, + "selected": false, + "children": ["b54db0a5-ee46-f8fd-f26f-d87e34896dcf"] + }, { "name": "armor_stand_root", "origin": [0, 1, 0], "color": 0, "onSummonFunction": "", + "preciseRotationFix": false, "configs": { "default": { "billboard": "fixed", @@ -1090,6 +1245,7 @@ "origin": [0, 12, 0], "color": 0, "onSummonFunction": "", + "preciseRotationFix": false, "configs": { "default": { "billboard": "fixed", @@ -1123,6 +1279,7 @@ "origin": [0, 12, 0], "color": 0, "onSummonFunction": "", + "preciseRotationFix": false, "configs": { "default": { "billboard": "fixed", @@ -1156,6 +1313,7 @@ "origin": [0, 24, 0], "color": 0, "onSummonFunction": "", + "preciseRotationFix": false, "configs": { "default": { "billboard": "fixed", @@ -1224,6 +1382,7 @@ "origin": [0, 23, 0], "color": 0, "onSummonFunction": "", + "preciseRotationFix": false, "configs": { "default": { "billboard": "fixed", @@ -1273,6 +1432,7 @@ "origin": [-6, 23, 0], "color": 0, "onSummonFunction": "", + "preciseRotationFix": false, "configs": { "default": { "billboard": "fixed", @@ -1322,6 +1482,7 @@ "origin": [6, 23, 0], "color": 0, "onSummonFunction": "", + "preciseRotationFix": false, "configs": { "default": { "billboard": "fixed", @@ -1380,6 +1541,7 @@ "origin": [2, 12, 0], "color": 0, "onSummonFunction": "", + "preciseRotationFix": false, "configs": { "default": { "billboard": "fixed", @@ -1427,6 +1589,7 @@ "origin": [-2, 12, 0], "color": 0, "onSummonFunction": "", + "preciseRotationFix": false, "configs": { "default": { "billboard": "fixed", diff --git a/test-packs/latest/datapacks/test-framework/src/test.mcb b/test-packs/latest/datapacks/test-framework/src/test.mcb index 33a776b9..316ea9ee 100644 --- a/test-packs/latest/datapacks/test-framework/src/test.mcb +++ b/test-packs/latest/datapacks/test-framework/src/test.mcb @@ -42,6 +42,12 @@ dir armor_stand { } } + function test_dismounted { + function animated_java:global/remove/everything + execute positioned 0 1 0 rotated 0 0 run function animated_java:armor_stand/summon {args:{animation:'test',start_animation:true}} + # execute as @e[tag=aj.global.node] run ride @s dismount + } + function test_precise_camera_rotation { function animated_java:global/remove/everything execute positioned 0 1 0 rotated 0 0 run function animated_java:armor_stand/summon {args:{animation:'camera_test',start_animation:true}} From 26e960f040e01f037f37c2a1eda4e8758c475cbc Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Thu, 18 Dec 2025 14:28:27 -0500 Subject: [PATCH 09/23] =?UTF-8?q?=E2=9C=A8=20Remove=20node=20name=20type?= =?UTF-8?q?=20prefix=20in=20NBT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../datapackCompiler/1.20.4/animation.mcb | 52 +++++++++++++--- .../datapackCompiler/1.20.4/static.mcb | 39 +++++++++++- .../datapackCompiler/1.20.5/animation.mcb | 52 +++++++++++++--- .../datapackCompiler/1.20.5/static.mcb | 39 +++++++++++- .../datapackCompiler/1.21.0/animation.mcb | 52 +++++++++++++--- .../datapackCompiler/1.21.0/static.mcb | 39 +++++++++++- .../datapackCompiler/1.21.2/animation.mcb | 61 +++++++++++++------ .../datapackCompiler/1.21.2/static.mcb | 39 +++++++++++- .../datapackCompiler/1.21.4/animation.mcb | 10 ++- .../datapackCompiler/1.21.4/static.mcb | 39 +++++++++++- src/systems/datapackCompiler/index.ts | 4 +- 11 files changed, 364 insertions(+), 62 deletions(-) diff --git a/src/systems/datapackCompiler/1.20.4/animation.mcb b/src/systems/datapackCompiler/1.20.4/animation.mcb index a24141a6..1896a1be 100644 --- a/src/systems/datapackCompiler/1.20.4/animation.mcb +++ b/src/systems/datapackCompiler/1.20.4/animation.mcb @@ -308,11 +308,11 @@ dir animations { IF (BONE_TYPES.includes(node.type)) { $execute on passengers if entity @s[tag=<%TAGS.PROJECT_NODE_NAMED(export_namespace, node.storage_name)%>] run \ data modify entity @s {} merge from \ - storage <%project_storage%>/animations <%animation.storage_name%>.$(frame).<%node.type.charAt(0) + '_' + node.storage_name%> + storage <%project_storage%>/animations <%animation.storage_name%>.$(frame).<%node.storage_name%> } ELSE IF (node.type === 'locator' || node.type === 'camera') { $execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run \ - $data modify entity @s data.uuids_by_name.<%node.type + '_' + node.storage_name%> merge from \ - storage <%project_storage%>/animations <%animation.storage_name%>.$(frame).<%node.type.charAt(0) + '_' + node.storage_name%> + $data modify entity @s data.uuids_by_name.<%node.storage_name%> merge from \ + storage <%project_storage%>/animations <%animation.storage_name%>.$(frame).<%node.storage_name%> } } @@ -376,19 +376,20 @@ dir animations { if (transform.interpolation === 'pre-post' || isStepInterpolation) { frameFunc += - `\n$data merge entity $(${node.type + '_' + node.storage_name})%NEWLINE_PATCH%{` + `\n$data merge entity $(${node.storage_name})%NEWLINE_PATCH%{` + `transformation: ${matrixToNbtFloatArray(transform.matrix).toString()},` + `start_interpolation: ${isStepInterpolation ? -1 : 0},` + `interpolation_duration: ${isStepInterpolation ? 0 : interpolation_duration}` + `}` } else { frameFunc += - `\n$data merge entity $(${node.type + '_' + node.storage_name})%NEWLINE_PATCH%{` + `\n$data merge entity $(${node.storage_name})%NEWLINE_PATCH%{` + `transformation: ${matrixToNbtFloatArray(transform.matrix).toString()},` + `start_interpolation: 0,` + `interpolation_duration: ${interpolation_duration}` + `}` } + ;hasFunction = true break } @@ -408,7 +409,7 @@ dir animations { if (transform.function) { if (node.config?.use_entity) { frameFunc += - `\n$execute on vehicle unless entity @s[tag=${TAGS.TRANSFORMS_ONLY()}] as $(${node.type + '_' + node.storage_name}) ` + `\n$execute on vehicle 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 ` @@ -548,7 +549,7 @@ function summon { function *global/gu/get_entity_uuid_string } data modify entity @s data.uuids append from storage <%gu_storage%> out - data modify entity @s data.uuids_by_name.<%locator.type + '_' + locator.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%locator.storage_name%> set from storage <%gu_storage%> out data modify entity @s data.locators.<%locator.storage_name%>.uuid set from storage <%gu_storage%> out } @@ -571,7 +572,7 @@ function summon { function *global/gu/get_entity_uuid_string } data modify entity @s data.uuids append from storage <%gu_storage%> out - data modify entity @s data.uuids_by_name.<%camera.type + '_' + camera.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%camera.storage_name%> set from storage <%gu_storage%> out data modify entity @s data.cameras.<%camera.storage_name%>.uuid set from storage <%gu_storage%> out } @@ -584,7 +585,7 @@ function summon { function *global/gu/get_entity_uuid_string data modify entity @s data.uuids append from storage <%gu_storage%> out - data modify entity @s data.uuids_by_name.<%node.type + '_' + node.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%node.storage_name%> set from storage <%gu_storage%> out } } @@ -724,6 +725,39 @@ function summon { } } +function as_node { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + 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')%> + } + + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_node/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from entity @s data.uuids_by_name.$(name) + 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 node wasn't found. + tellraw @a <%TELLRAW.NODE_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.NODE_COMMAND_FAILED_TO_EXECUTE()%> + } + } +} + IF (has_entity_locators) { function as_locator { #ARGS: {name: string, command: string} diff --git a/src/systems/datapackCompiler/1.20.4/static.mcb b/src/systems/datapackCompiler/1.20.4/static.mcb index 79400a7f..2099c62c 100644 --- a/src/systems/datapackCompiler/1.20.4/static.mcb +++ b/src/systems/datapackCompiler/1.20.4/static.mcb @@ -172,7 +172,7 @@ function summon { function *global/gu/get_entity_uuid_string } data modify entity @s data.uuids append from storage <%gu_storage%> out - data modify entity @s data.uuids_by_name.<%locator.type + '_' + locator.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%locator.storage_name%> set from storage <%gu_storage%> out data modify entity @s data.locators.<%locator.storage_name%>.uuid set from storage <%gu_storage%> out } @@ -195,7 +195,7 @@ function summon { function *global/gu/get_entity_uuid_string } data modify entity @s data.uuids append from storage <%gu_storage%> out - data modify entity @s data.uuids_by_name.<%camera.type + '_' + camera.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%camera.storage_name%> set from storage <%gu_storage%> out data modify entity @s data.cameras.<%camera.storage_name%>.uuid set from storage <%gu_storage%> out } @@ -208,7 +208,7 @@ function summon { function *global/gu/get_entity_uuid_string data modify entity @s data.uuids append from storage <%gu_storage%> out - data modify entity @s data.uuids_by_name.<%node.type + '_' + node.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%node.storage_name%> set from storage <%gu_storage%> out } } @@ -300,6 +300,39 @@ function summon { } } +function as_node { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + 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')%> + } + + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_node/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from entity @s data.uuids_by_name.$(name) + 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 node wasn't found. + tellraw @a <%TELLRAW.NODE_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.NODE_COMMAND_FAILED_TO_EXECUTE()%> + } + } +} + IF (has_entity_locators) { function as_locator { #ARGS: {name: string, command: string} diff --git a/src/systems/datapackCompiler/1.20.5/animation.mcb b/src/systems/datapackCompiler/1.20.5/animation.mcb index a2af7e82..29fe103c 100644 --- a/src/systems/datapackCompiler/1.20.5/animation.mcb +++ b/src/systems/datapackCompiler/1.20.5/animation.mcb @@ -308,11 +308,11 @@ dir animations { IF (BONE_TYPES.includes(node.type)) { $execute on passengers if entity @s[tag=<%TAGS.PROJECT_NODE_NAMED(export_namespace, node.storage_name)%>] run \ data modify entity @s {} merge from \ - storage <%project_storage%>/animations <%animation.storage_name%>.$(frame).<%node.type.charAt(0) + '_' + node.storage_name%> + storage <%project_storage%>/animations <%animation.storage_name%>.$(frame).<%node.storage_name%> } ELSE IF (node.type === 'locator' || node.type === 'camera') { $execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run \ - $data modify entity @s data.uuids_by_name.<%node.type + '_' + node.storage_name%> merge from \ - storage <%project_storage%>/animations <%animation.storage_name%>.$(frame).<%node.type.charAt(0) + '_' + node.storage_name%> + $data modify entity @s data.uuids_by_name.<%node.storage_name%> merge from \ + storage <%project_storage%>/animations <%animation.storage_name%>.$(frame).<%node.storage_name%> } } @@ -376,19 +376,20 @@ dir animations { if (transform.interpolation === 'pre-post' || isStepInterpolation) { frameFunc += - `\n$data merge entity $(${node.type + '_' + node.storage_name})%NEWLINE_PATCH%{` + `\n$data merge entity $(${node.storage_name})%NEWLINE_PATCH%{` + `transformation: ${matrixToNbtFloatArray(transform.matrix).toString()},` + `start_interpolation: ${isStepInterpolation ? -1 : 0},` + `interpolation_duration: ${isStepInterpolation ? 0 : interpolation_duration}` + `}` } else { frameFunc += - `\n$data merge entity $(${node.type + '_' + node.storage_name})%NEWLINE_PATCH%{` + `\n$data merge entity $(${node.storage_name})%NEWLINE_PATCH%{` + `transformation: ${matrixToNbtFloatArray(transform.matrix).toString()},` + `start_interpolation: 0,` + `interpolation_duration: ${interpolation_duration}` + `}` } + ;hasFunction = true break } @@ -408,7 +409,7 @@ dir animations { if (transform.function) { if (node.config?.use_entity) { frameFunc += - `\n$execute on vehicle unless entity @s[tag=${TAGS.TRANSFORMS_ONLY()}] as $(${node.type + '_' + node.storage_name}) ` + `\n$execute on vehicle 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 ` @@ -548,7 +549,7 @@ function summon { function *global/gu/get_entity_uuid_string } data modify entity @s data.uuids append from storage <%gu_storage%> out - data modify entity @s data.uuids_by_name.<%locator.type + '_' + locator.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%locator.storage_name%> set from storage <%gu_storage%> out data modify entity @s data.locators.<%locator.storage_name%>.uuid set from storage <%gu_storage%> out } @@ -571,7 +572,7 @@ function summon { function *global/gu/get_entity_uuid_string } data modify entity @s data.uuids append from storage <%gu_storage%> out - data modify entity @s data.uuids_by_name.<%camera.type + '_' + camera.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%camera.storage_name%> set from storage <%gu_storage%> out data modify entity @s data.cameras.<%camera.storage_name%>.uuid set from storage <%gu_storage%> out } @@ -584,7 +585,7 @@ function summon { function *global/gu/get_entity_uuid_string data modify entity @s data.uuids append from storage <%gu_storage%> out - data modify entity @s data.uuids_by_name.<%node.type + '_' + node.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%node.storage_name%> set from storage <%gu_storage%> out } } @@ -724,6 +725,39 @@ function summon { } } +function as_node { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + 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')%> + } + + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_node/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from entity @s data.uuids_by_name.$(name) + 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 node wasn't found. + tellraw @a <%TELLRAW.NODE_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.NODE_COMMAND_FAILED_TO_EXECUTE()%> + } + } +} + IF (has_entity_locators) { function as_locator { #ARGS: {name: string, command: string} diff --git a/src/systems/datapackCompiler/1.20.5/static.mcb b/src/systems/datapackCompiler/1.20.5/static.mcb index 7f0e2a7c..07479bf2 100644 --- a/src/systems/datapackCompiler/1.20.5/static.mcb +++ b/src/systems/datapackCompiler/1.20.5/static.mcb @@ -172,7 +172,7 @@ function summon { function *global/gu/get_entity_uuid_string } data modify entity @s data.uuids append from storage <%gu_storage%> out - data modify entity @s data.uuids_by_name.<%locator.type + '_' + locator.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%locator.storage_name%> set from storage <%gu_storage%> out data modify entity @s data.locators.<%locator.storage_name%>.uuid set from storage <%gu_storage%> out } @@ -195,7 +195,7 @@ function summon { function *global/gu/get_entity_uuid_string } data modify entity @s data.uuids append from storage <%gu_storage%> out - data modify entity @s data.uuids_by_name.<%camera.type + '_' + camera.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%camera.storage_name%> set from storage <%gu_storage%> out data modify entity @s data.cameras.<%camera.storage_name%>.uuid set from storage <%gu_storage%> out } @@ -208,7 +208,7 @@ function summon { function *global/gu/get_entity_uuid_string data modify entity @s data.uuids append from storage <%gu_storage%> out - data modify entity @s data.uuids_by_name.<%node.type + '_' + node.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%node.storage_name%> set from storage <%gu_storage%> out } } @@ -300,6 +300,39 @@ function summon { } } +function as_node { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + 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')%> + } + + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_node/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from entity @s data.uuids_by_name.$(name) + 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 node wasn't found. + tellraw @a <%TELLRAW.NODE_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.NODE_COMMAND_FAILED_TO_EXECUTE()%> + } + } +} + IF (has_entity_locators) { function as_locator { #ARGS: {name: string, command: string} diff --git a/src/systems/datapackCompiler/1.21.0/animation.mcb b/src/systems/datapackCompiler/1.21.0/animation.mcb index 497512fb..41a7bb0a 100644 --- a/src/systems/datapackCompiler/1.21.0/animation.mcb +++ b/src/systems/datapackCompiler/1.21.0/animation.mcb @@ -308,11 +308,11 @@ dir animations { IF (BONE_TYPES.includes(node.type)) { $execute on passengers if entity @s[tag=<%TAGS.PROJECT_NODE_NAMED(export_namespace, node.storage_name)%>] run \ data modify entity @s {} merge from \ - storage <%project_storage%>/animations <%animation.storage_name%>.$(frame).<%node.type.charAt(0) + '_' + node.storage_name%> + storage <%project_storage%>/animations <%animation.storage_name%>.$(frame).<%node.storage_name%> } ELSE IF (node.type === 'locator' || node.type === 'camera') { $execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run \ - $data modify entity @s data.uuids_by_name.<%node.type + '_' + node.storage_name%> merge from \ - storage <%project_storage%>/animations <%animation.storage_name%>.$(frame).<%node.type.charAt(0) + '_' + node.storage_name%> + $data modify entity @s data.uuids_by_name.<%node.storage_name%> merge from \ + storage <%project_storage%>/animations <%animation.storage_name%>.$(frame).<%node.storage_name%> } } @@ -376,19 +376,20 @@ dir animations { if (transform.interpolation === 'pre-post' || isStepInterpolation) { frameFunc += - `\n$data merge entity $(${node.type + '_' + node.storage_name})%NEWLINE_PATCH%{` + `\n$data merge entity $(${node.storage_name})%NEWLINE_PATCH%{` + `transformation: ${matrixToNbtFloatArray(transform.matrix).toString()},` + `start_interpolation: ${isStepInterpolation ? -1 : 0},` + `interpolation_duration: ${isStepInterpolation ? 0 : interpolation_duration}` + `}` } else { frameFunc += - `\n$data merge entity $(${node.type + '_' + node.storage_name})%NEWLINE_PATCH%{` + `\n$data merge entity $(${node.storage_name})%NEWLINE_PATCH%{` + `transformation: ${matrixToNbtFloatArray(transform.matrix).toString()},` + `start_interpolation: 0,` + `interpolation_duration: ${interpolation_duration}` + `}` } + ;hasFunction = true break } @@ -408,7 +409,7 @@ dir animations { if (transform.function) { if (node.config?.use_entity) { frameFunc += - `\n$execute on vehicle unless entity @s[tag=${TAGS.TRANSFORMS_ONLY()}] as $(${node.type + '_' + node.storage_name}) ` + `\n$execute on vehicle 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 ` @@ -548,7 +549,7 @@ function summon { function *global/gu/get_entity_uuid_string } data modify entity @s data.uuids append from storage <%gu_storage%> out - data modify entity @s data.uuids_by_name.<%locator.type + '_' + locator.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%locator.storage_name%> set from storage <%gu_storage%> out data modify entity @s data.locators.<%locator.storage_name%>.uuid set from storage <%gu_storage%> out } @@ -571,7 +572,7 @@ function summon { function *global/gu/get_entity_uuid_string } data modify entity @s data.uuids append from storage <%gu_storage%> out - data modify entity @s data.uuids_by_name.<%camera.type + '_' + camera.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%camera.storage_name%> set from storage <%gu_storage%> out data modify entity @s data.cameras.<%camera.storage_name%>.uuid set from storage <%gu_storage%> out } @@ -584,7 +585,7 @@ function summon { function *global/gu/get_entity_uuid_string data modify entity @s data.uuids append from storage <%gu_storage%> out - data modify entity @s data.uuids_by_name.<%node.type + '_' + node.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%node.storage_name%> set from storage <%gu_storage%> out } } @@ -724,6 +725,39 @@ function summon { } } +function as_node { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + 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')%> + } + + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_node/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from entity @s data.uuids_by_name.$(name) + 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 node wasn't found. + tellraw @a <%TELLRAW.NODE_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.NODE_COMMAND_FAILED_TO_EXECUTE()%> + } + } +} + IF (has_entity_locators) { function as_locator { #ARGS: {name: string, command: string} diff --git a/src/systems/datapackCompiler/1.21.0/static.mcb b/src/systems/datapackCompiler/1.21.0/static.mcb index fdee22d7..51b405c5 100644 --- a/src/systems/datapackCompiler/1.21.0/static.mcb +++ b/src/systems/datapackCompiler/1.21.0/static.mcb @@ -172,7 +172,7 @@ function summon { function *global/gu/get_entity_uuid_string } data modify entity @s data.uuids append from storage <%gu_storage%> out - data modify entity @s data.uuids_by_name.<%locator.type + '_' + locator.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%locator.storage_name%> set from storage <%gu_storage%> out data modify entity @s data.locators.<%locator.storage_name%>.uuid set from storage <%gu_storage%> out } @@ -195,7 +195,7 @@ function summon { function *global/gu/get_entity_uuid_string } data modify entity @s data.uuids append from storage <%gu_storage%> out - data modify entity @s data.uuids_by_name.<%camera.type + '_' + camera.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%camera.storage_name%> set from storage <%gu_storage%> out data modify entity @s data.cameras.<%camera.storage_name%>.uuid set from storage <%gu_storage%> out } @@ -208,7 +208,7 @@ function summon { function *global/gu/get_entity_uuid_string data modify entity @s data.uuids append from storage <%gu_storage%> out - data modify entity @s data.uuids_by_name.<%node.type + '_' + node.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%node.storage_name%> set from storage <%gu_storage%> out } } @@ -300,6 +300,39 @@ function summon { } } +function as_node { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + 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')%> + } + + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_node/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from entity @s data.uuids_by_name.$(name) + 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 node wasn't found. + tellraw @a <%TELLRAW.NODE_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.NODE_COMMAND_FAILED_TO_EXECUTE()%> + } + } +} + IF (has_entity_locators) { function as_locator { #ARGS: {name: string, command: string} diff --git a/src/systems/datapackCompiler/1.21.2/animation.mcb b/src/systems/datapackCompiler/1.21.2/animation.mcb index d0df4f82..522c2440 100644 --- a/src/systems/datapackCompiler/1.21.2/animation.mcb +++ b/src/systems/datapackCompiler/1.21.2/animation.mcb @@ -130,13 +130,6 @@ dir root { positioned ^$(px) ^$(py) ^$(pz) \ rotated ~$(ry) ~$(rx) \ run tp @s ~ ~ ~ ~ ~ - # Precise Rotation Workaround. Fixes MC-272913. - # Thanks @Triton365! (https://discord.com/channels/154777837382008833/157097006500806656/1402253905408163842) - $execute \ - as $(uuid) \ - store success entity @s OnGround byte 1 \ - store success score @s <%OBJECTIVES.I()%> \ - unless score @s <%OBJECTIVES.I()%> matches 1 } } } @@ -315,11 +308,11 @@ dir animations { IF (BONE_TYPES.includes(node.type)) { $execute on passengers if entity @s[tag=<%TAGS.PROJECT_NODE_NAMED(export_namespace, node.storage_name)%>] run \ data modify entity @s {} merge from \ - storage <%project_storage%>/animations <%animation.storage_name%>.$(frame).<%node.type.charAt(0) + '_' + node.storage_name%> + storage <%project_storage%>/animations <%animation.storage_name%>.$(frame).<%node.storage_name%> } ELSE IF (node.type === 'locator' || node.type === 'camera') { $execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run \ - $data modify entity @s data.uuids_by_name.<%node.type + '_' + node.storage_name%> merge from \ - storage <%project_storage%>/animations <%animation.storage_name%>.$(frame).<%node.type.charAt(0) + '_' + node.storage_name%> + $data modify entity @s data.uuids_by_name.<%node.storage_name%> merge from \ + storage <%project_storage%>/animations <%animation.storage_name%>.$(frame).<%node.storage_name%> } } @@ -383,19 +376,20 @@ dir animations { if (transform.interpolation === 'pre-post' || isStepInterpolation) { frameFunc += - `\n$data merge entity $(${node.type + '_' + node.storage_name})%NEWLINE_PATCH%{` + `\n$data merge entity $(${node.storage_name})%NEWLINE_PATCH%{` + `transformation: ${matrixToNbtFloatArray(transform.matrix).toString()},` + `start_interpolation: ${isStepInterpolation ? -1 : 0},` + `interpolation_duration: ${isStepInterpolation ? 0 : interpolation_duration}` + `}` } else { frameFunc += - `\n$data merge entity $(${node.type + '_' + node.storage_name})%NEWLINE_PATCH%{` + `\n$data merge entity $(${node.storage_name})%NEWLINE_PATCH%{` + `transformation: ${matrixToNbtFloatArray(transform.matrix).toString()},` + `start_interpolation: 0,` + `interpolation_duration: ${interpolation_duration}` + `}` } + ;hasFunction = true break } @@ -415,7 +409,7 @@ dir animations { if (transform.function) { if (node.config?.use_entity) { frameFunc += - `\n$execute on vehicle unless entity @s[tag=${TAGS.TRANSFORMS_ONLY()}] as $(${node.type + '_' + node.storage_name}) ` + `\n$execute on vehicle 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 ` @@ -555,7 +549,7 @@ function summon { function *global/gu/get_entity_uuid_string } data modify entity @s data.uuids append from storage <%gu_storage%> out - data modify entity @s data.uuids_by_name.<%locator.type + '_' + locator.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%locator.storage_name%> set from storage <%gu_storage%> out data modify entity @s data.locators.<%locator.storage_name%>.uuid set from storage <%gu_storage%> out } @@ -578,7 +572,7 @@ function summon { function *global/gu/get_entity_uuid_string } data modify entity @s data.uuids append from storage <%gu_storage%> out - data modify entity @s data.uuids_by_name.<%camera.type + '_' + camera.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%camera.storage_name%> set from storage <%gu_storage%> out data modify entity @s data.cameras.<%camera.storage_name%>.uuid set from storage <%gu_storage%> out } @@ -591,7 +585,7 @@ function summon { function *global/gu/get_entity_uuid_string data modify entity @s data.uuids append from storage <%gu_storage%> out - data modify entity @s data.uuids_by_name.<%node.type + '_' + node.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%node.storage_name%> set from storage <%gu_storage%> out } } @@ -731,6 +725,39 @@ function summon { } } +function as_node { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + 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')%> + } + + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_node/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from entity @s data.uuids_by_name.$(name) + 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 node wasn't found. + tellraw @a <%TELLRAW.NODE_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.NODE_COMMAND_FAILED_TO_EXECUTE()%> + } + } +} + IF (has_entity_locators) { function as_locator { #ARGS: {name: string, command: string} @@ -1189,7 +1216,7 @@ function set_default_pose { function ./zzz/reset_floating_entities } - REPEAT (Object.values(rig.nodes).filter(node => node.type !== 'locator' && node.type !== 'camera')) as node { + REPEAT (Object.values(rig.nodes).filter(node => BONE_TYPES.includes(node.type))) as node { execute \ on passengers \ if entity @s[tag=<%TAGS.PROJECT_NODE_NAMED(export_namespace, node.storage_name)%>] \ diff --git a/src/systems/datapackCompiler/1.21.2/static.mcb b/src/systems/datapackCompiler/1.21.2/static.mcb index 2a41c81d..e5323298 100644 --- a/src/systems/datapackCompiler/1.21.2/static.mcb +++ b/src/systems/datapackCompiler/1.21.2/static.mcb @@ -172,7 +172,7 @@ function summon { function *global/gu/get_entity_uuid_string } data modify entity @s data.uuids append from storage <%gu_storage%> out - data modify entity @s data.uuids_by_name.<%locator.type + '_' + locator.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%locator.storage_name%> set from storage <%gu_storage%> out data modify entity @s data.locators.<%locator.storage_name%>.uuid set from storage <%gu_storage%> out } @@ -195,7 +195,7 @@ function summon { function *global/gu/get_entity_uuid_string } data modify entity @s data.uuids append from storage <%gu_storage%> out - data modify entity @s data.uuids_by_name.<%camera.type + '_' + camera.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%camera.storage_name%> set from storage <%gu_storage%> out data modify entity @s data.cameras.<%camera.storage_name%>.uuid set from storage <%gu_storage%> out } @@ -208,7 +208,7 @@ function summon { function *global/gu/get_entity_uuid_string data modify entity @s data.uuids append from storage <%gu_storage%> out - data modify entity @s data.uuids_by_name.<%node.type + '_' + node.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%node.storage_name%> set from storage <%gu_storage%> out } } @@ -300,6 +300,39 @@ function summon { } } +function as_node { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + 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')%> + } + + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_node/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from entity @s data.uuids_by_name.$(name) + 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 node wasn't found. + tellraw @a <%TELLRAW.NODE_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.NODE_COMMAND_FAILED_TO_EXECUTE()%> + } + } +} + IF (has_entity_locators) { function as_locator { #ARGS: {name: string, command: string} diff --git a/src/systems/datapackCompiler/1.21.4/animation.mcb b/src/systems/datapackCompiler/1.21.4/animation.mcb index 851d75b6..31adf124 100644 --- a/src/systems/datapackCompiler/1.21.4/animation.mcb +++ b/src/systems/datapackCompiler/1.21.4/animation.mcb @@ -270,7 +270,15 @@ dir animations { execute at @s run function ./zzz/apply_frame {frame: 0} $execute at @s run function ./zzz/apply_frame {frame: $(to_frame)} tag @s remove <%TAGS.TRANSFORMS_ONLY()%> - execute on passengers store result entity @s interpolation_duration int 1 run scoreboard players get #this <%OBJECTIVES.I()%> + + execute on passengers run { with entity @s data.uuids_by_name + REPEAT (Object.values(rig.nodes).filter(node => BONE_TYPES.includes(node.type))) as node { + $execute \ + as $(<%node.storage_name%>) \ + store result entity @s interpolation_duration int 1 \ + run scoreboard players get #this <%OBJECTIVES.I()%> + } + } } dir zzz { diff --git a/src/systems/datapackCompiler/1.21.4/static.mcb b/src/systems/datapackCompiler/1.21.4/static.mcb index 90589a55..a31dc1cc 100644 --- a/src/systems/datapackCompiler/1.21.4/static.mcb +++ b/src/systems/datapackCompiler/1.21.4/static.mcb @@ -172,7 +172,7 @@ function summon { function *global/gu/get_entity_uuid_string } data modify entity @s data.uuids append from storage <%gu_storage%> out - data modify entity @s data.uuids_by_name.<%locator.type + '_' + locator.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%locator.storage_name%> set from storage <%gu_storage%> out data modify entity @s data.locators.<%locator.storage_name%>.uuid set from storage <%gu_storage%> out } @@ -195,7 +195,7 @@ function summon { function *global/gu/get_entity_uuid_string } data modify entity @s data.uuids append from storage <%gu_storage%> out - data modify entity @s data.uuids_by_name.<%camera.type + '_' + camera.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%camera.storage_name%> set from storage <%gu_storage%> out data modify entity @s data.cameras.<%camera.storage_name%>.uuid set from storage <%gu_storage%> out } @@ -208,7 +208,7 @@ function summon { function *global/gu/get_entity_uuid_string data modify entity @s data.uuids append from storage <%gu_storage%> out - data modify entity @s data.uuids_by_name.<%node.type + '_' + node.storage_name%> set from storage <%gu_storage%> out + data modify entity @s data.uuids_by_name.<%node.storage_name%> set from storage <%gu_storage%> out } } @@ -300,6 +300,39 @@ function summon { } } +function as_node { + #ARGS: {name: string, command: string} + debug assert executed_as_root_entity <%TAGS.PROJECT_ROOT(export_namespace)%> + 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')%> + } + + execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_node/as_data { with storage <%temp_storage%> args + $data modify storage <%temp_storage%> args.uuid set from entity @s data.uuids_by_name.$(name) + 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 node wasn't found. + tellraw @a <%TELLRAW.NODE_ENTITY_NOT_FOUND()%> + } + } + IF (debug_mode) { + execute if score #aj.check <%OBJECTIVES.I()%> matches 0 run tellraw @a <%TELLRAW.NODE_COMMAND_FAILED_TO_EXECUTE()%> + } + } +} + IF (has_entity_locators) { function as_locator { #ARGS: {name: string, command: string} diff --git a/src/systems/datapackCompiler/index.ts b/src/systems/datapackCompiler/index.ts index f57759f9..aabf4652 100644 --- a/src/systems/datapackCompiler/index.ts +++ b/src/systems/datapackCompiler/index.ts @@ -654,14 +654,14 @@ async function createAnimationStorage(rig: IRenderedRig, animations: IRenderedAn } if (BONE_TYPES.includes(node.type)) { thisFrame.set( - node.type.charAt(0) + '_' + node.storage_name, + node.storage_name, new NbtCompound() .set('transformation', matrixToNbtFloatArray(transform.matrix)) .set('start_interpolation', new NbtInt(0)) ) } else { thisFrame.set( - node.type.charAt(0) + '_' + node.storage_name, + node.storage_name, new NbtCompound() .set('px', new NbtFloat(roundTo(transform.pos[0], 4))) .set('py', new NbtFloat(roundTo(transform.pos[1], 4))) From 1708e54c7be228289e57eb7545d53f2e7ccb2c66 Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Thu, 18 Dec 2025 14:47:36 -0500 Subject: [PATCH 10/23] =?UTF-8?q?=F0=9F=90=9B=20Fix=20function=20inputs=20?= =?UTF-8?q?consuming=20HTML=20tags?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #474 --- src/components/customCodeJar.svelte | 7 +- src/components/dialogItems/codeInput.svelte | 18 +- .../blueprints/armor_stand_latest.ajblueprint | 162 ------------------ 3 files changed, 6 insertions(+), 181 deletions(-) diff --git a/src/components/customCodeJar.svelte b/src/components/customCodeJar.svelte index 0cbd9745..553a8d64 100644 --- a/src/components/customCodeJar.svelte +++ b/src/components/customCodeJar.svelte @@ -9,7 +9,11 @@ let codeJarElement: HTMLPreElement | undefined function highlight(code: string, syntax?: string) { - if (!syntax) return code + if (!syntax) { + const element = document.createElement('div') + element.textContent = code + return element.innerHTML + } return Prism.highlight(code, Prism.languages[syntax], syntax) } @@ -44,7 +48,6 @@ {highlight} bind:value on:change={() => forceNoWrap()} - preserveIdent history class={'language-' + (syntax ?? 'plaintext')} style={` diff --git a/src/components/dialogItems/codeInput.svelte b/src/components/dialogItems/codeInput.svelte index 66b6fe2b..f86adfca 100644 --- a/src/components/dialogItems/codeInput.svelte +++ b/src/components/dialogItems/codeInput.svelte @@ -13,8 +13,6 @@ value.get() - let codeJarElement: HTMLPreElement | undefined - let warningText = '' let errorText = '' @@ -33,20 +31,6 @@ unsub() }) - const onKeydown = (e: Event) => { - if (!(e instanceof KeyboardEvent)) return - if (e.key === 'Tab' || e.key === 'Enter') { - e.stopPropagation() - } else if ((e.code === 'KeyZ' || e.code === 'KeyY') && e.ctrlKey) { - // CodeJar doesn't capture undo correctly. So we have to fudge it a little. - requestAnimationFrame(() => { - if (codeJarElement?.textContent != undefined) { - $value = codeJarElement.textContent - } - }) - } - } - function onReset() { $value = defaultValue onValueChange() @@ -59,7 +43,7 @@
-
+
Date: Sun, 22 Feb 2026 11:05:45 +0100 Subject: [PATCH 11/23] fix(plugin-export): restore plugin mode and update JSON export schema --- src/components/blueprintSettingsDialog.svelte | 16 ++- src/formats/blueprint/codec.ts | 5 - src/lang/en.yml | 1 + src/systems/datapackCompiler/index.ts | 2 +- src/systems/errors.ts | 37 ++++++ src/systems/exporter.ts | 52 +++----- src/systems/global.ts | 2 +- src/systems/jsonCompiler.ts | 118 +++++++++++------- src/systems/resourcepackCompiler/index.ts | 2 +- src/systems/rigRenderer.ts | 2 +- 10 files changed, 138 insertions(+), 99 deletions(-) create mode 100644 src/systems/errors.ts diff --git a/src/components/blueprintSettingsDialog.svelte b/src/components/blueprintSettingsDialog.svelte index 63146e3a..19ece9f6 100644 --- a/src/components/blueprintSettingsDialog.svelte +++ b/src/components/blueprintSettingsDialog.svelte @@ -271,7 +271,7 @@ console.error(e) return { type: 'error', - message: translate('dialog.blueprint_settings.json_file.error.file_does_not_exist'), + message: translate('dialog.blueprint_settings.json_file.error.invalid_path'), } } switch (true) { @@ -333,8 +333,6 @@ // Export Settings export let exportNamespace: Valuable export let enablePluginMode: Valuable - // FIXME - Force-disable plugin mode for now - $enablePluginMode = false export let resourcePackExportMode: Valuable export let dataPackExportMode: Valuable export let targetMinecraftVersion: Valuable @@ -433,12 +431,12 @@ valueChecker={exportNamespaceChecker} /> - + {#if $enablePluginMode} [1] + ) { + super(message) + this.name = 'IntentionalExportError' + } +} + +export class IntentionalExportErrorFromInvalidFile extends IntentionalExportError { + constructor(filePath: string, public originalError: Error) { + const parsed = PathModule.parse(filePath) + super( + `Failed to read file ${parsed.base}:\n\n` + + '```\n' + + originalError + + '\n```', + { + commands: { + open_file: { + text: 'Open File Location', + icon: 'folder_open', + }, + }, + }, + button => { + if (button === 'open_file') { + shell.showItemInFolder(filePath) + } + } + ) + this.name = 'IntentionalExportErrorFromInvalidFile' + } +} + diff --git a/src/systems/exporter.ts b/src/systems/exporter.ts index 67bed5c0..b1a143f2 100644 --- a/src/systems/exporter.ts +++ b/src/systems/exporter.ts @@ -9,48 +9,13 @@ import { isResourcePackPath } from '../util/minecraftUtil' import { translate } from '../util/translation' import { Variant } from '../variants' import { hashAnimations, renderProjectAnimations } from './animationRenderer' +import { exportJSON } from './jsonCompiler' import compileDataPack from './datapackCompiler' +import { IntentionalExportError } from './errors' import resourcepackCompiler from './resourcepackCompiler' import { hashRig, renderRig } from './rigRenderer' import { isCubeValid } from './util' -export class IntentionalExportError extends Error { - constructor( - message: string, - public messageBoxOptions?: MessageBoxOptions, - public messageBoxCallback?: Parameters[1] - ) { - super(message) - this.name = 'IntentionalExportError' - } -} - -export class IntentionalExportErrorFromInvalidFile extends IntentionalExportError { - constructor(filePath: string, public originalError: Error) { - const parsed = PathModule.parse(filePath) - super( - `Failed to read file ${parsed.base}:\n\n` + - '```\n' + - originalError + - '\n```', - { - commands: { - open_file: { - text: 'Open File Location', - icon: 'folder_open', - }, - }, - }, - button => { - if (button === 'open_file') { - shell.showItemInFolder(filePath) - } - } - ) - this.name = 'IntentionalExportErrorFromInvalidFile' - } -} - export function getExportPaths() { const aj = Project!.animated_java @@ -164,7 +129,7 @@ async function actuallyExportProject({ debugMode, }) - if (aj.data_pack_export_mode !== 'none') { + if (!aj.enable_plugin_mode && aj.data_pack_export_mode !== 'none') { await compileDataPack([aj.target_minecraft_version], { rig, animations, @@ -175,6 +140,17 @@ async function actuallyExportProject({ }) } + if (aj.enable_plugin_mode) { + PROGRESS_DESCRIPTION.set('Exporting Plugin JSON...') + exportJSON({ + rig, + animations, + displayItemPath, + textureExportFolder, + modelExportFolder, + }) + } + Project!.last_used_export_namespace = aj.export_namespace if (forceSave) saveBlueprint() diff --git a/src/systems/global.ts b/src/systems/global.ts index 8e322dbf..0f6fe29c 100644 --- a/src/systems/global.ts +++ b/src/systems/global.ts @@ -1,5 +1,5 @@ import { normalizePath } from '../util/fileUtil' -import { IntentionalExportError, IntentionalExportErrorFromInvalidFile } from './exporter' +import { IntentionalExportError, IntentionalExportErrorFromInvalidFile } from './errors' import { sortObjectKeys } from './util' export enum SUPPORTED_MINECRAFT_VERSIONS { diff --git a/src/systems/jsonCompiler.ts b/src/systems/jsonCompiler.ts index 2dba4d0c..cad1388b 100644 --- a/src/systems/jsonCompiler.ts +++ b/src/systems/jsonCompiler.ts @@ -2,6 +2,7 @@ /// /// +import { PACKAGE } from '../constants' import type { IBlueprintDisplayEntityConfigJSON } from '../formats/blueprint' import { type defaultValues } from '../formats/blueprint/settings' import type { EasingKey } from '../util/easing' @@ -9,6 +10,7 @@ import { resolvePath } from '../util/fileUtil' import { detectCircularReferences, mapObjEntries, scrubUndefined } from '../util/misc' import { Variant } from '../variants' import type { INodeTransform, IRenderedAnimation, IRenderedFrame } from './animationRenderer' +import { IntentionalExportError } from './errors' import { JsonText } from './jsonText' import type { AnyRenderedNode, @@ -18,42 +20,34 @@ import type { IRenderedVariantModel, } from './rigRenderer' -type ExportedNodetransform = Omit< - INodeTransform, - 'type' | 'name' | 'uuid' | 'node' | 'matrix' | 'decomposed' | 'executeCondition' -> & { +type ExportedNodetransform = Omit & { matrix: number[] decomposed: { translation: ArrayVector3 left_rotation: ArrayVector4 scale: ArrayVector3 } - pos: ArrayVector3 - rot: ArrayVector3 - scale: ArrayVector3 - execute_condition?: string } type ExportedRenderedNode = Omit< AnyRenderedNode, - | 'node' - | 'parentNode' - | 'model' - | 'boundingBox' - | 'configs' - | 'baseScale' - | 'path_name' + 'default_transform' + | 'bounding_box' + | 'configs' | 'storage_name' > & { default_transform: ExportedNodetransform bounding_box?: { min: ArrayVector3; max: ArrayVector3 } configs?: Record } -type ExportedAnimationFrame = Omit & { +type ExportedAnimationFrame = Omit & { node_transforms: Record } type ExportedBakedAnimation = Omit< IRenderedAnimation, - 'uuid' | 'frames' | 'modified_nodes' | 'path_name' | 'storage_name' + 'uuid' + | 'frames' + | 'modified_nodes' + | 'storage_name' > & { frames: ExportedAnimationFrame[] modified_nodes: string[] @@ -98,16 +92,16 @@ interface ExportedDynamicAnimation { animators: Record } interface ExportedTexture { + uuid: string name: string src: string } -type ExportedVariantModel = Omit< +type ExportedVariantModel = Pick< IRenderedVariantModel, - 'model_path' | 'resource_location' | 'item_model' -> & { - model: IRenderedModel | null - custom_model_data: number -} + 'custom_model_data' + | 'resource_location' + | 'item_model' +> & { model: IRenderedModel | null } type ExportedVariant = Omit & { /** * A map of bone UUID -> IRenderedVariantModel @@ -116,16 +110,25 @@ type ExportedVariant = Omit & { } export interface IExportedJSON { + format_version: '2.0.0' + exported_with: { + name: string + version: string + } /** * The Blueprint's Settings */ settings: { export_namespace: (typeof defaultValues)['export_namespace'] + target_minecraft_version: (typeof defaultValues)['target_minecraft_version'] + display_item: (typeof defaultValues)['display_item'] bounding_box: (typeof defaultValues)['render_box'] // Resource Pack Settings custom_model_data_offset: (typeof defaultValues)['custom_model_data_offset'] // Plugin Settings baked_animations: (typeof defaultValues)['baked_animations'] + interpolation_duration: (typeof defaultValues)['interpolation_duration'] + teleportation_duration: (typeof defaultValues)['teleportation_duration'] } textures: Record nodes: Record @@ -137,7 +140,12 @@ export interface IExportedJSON { } function transferKey(obj: any, oldKey: string, newKey: string) { - obj[newKey] = obj[oldKey] + if (!Object.prototype.hasOwnProperty.call(obj, oldKey)) return + const value = obj[oldKey] + if (value === undefined) return + if (obj[newKey] === undefined) { + obj[newKey] = value + } delete obj[oldKey] } @@ -209,6 +217,8 @@ function serializeVariant(rig: IRenderedRig, variant: IRenderedVariant): Exporte const json: ExportedVariantModel = { model: model.model, custom_model_data: model.custom_model_data, + resource_location: model.resource_location, + item_model: model.item_model, } return [uuid, json] }), @@ -230,22 +240,29 @@ export function exportJSON(options: { function serializeTexture(texture: Texture): ExportedTexture { return { + uuid: texture.uuid, name: texture.name, src: texture.getDataURL(), } } const json: IExportedJSON = { + format_version: '2.0.0', + exported_with: { + name: PACKAGE.name, + version: PACKAGE.version, + }, settings: { export_namespace: aj.export_namespace, + target_minecraft_version: aj.target_minecraft_version, + display_item: aj.display_item, bounding_box: aj.render_box, custom_model_data_offset: aj.custom_model_data_offset, baked_animations: aj.baked_animations, + interpolation_duration: aj.interpolation_duration, + teleportation_duration: aj.teleportation_duration, }, - textures: mapObjEntries(rig.textures, (_, texture) => [ - texture.uuid, - serializeTexture(texture), - ]), + textures: mapObjEntries(rig.textures, (id, texture) => [id, serializeTexture(texture)]), nodes: mapObjEntries(rig.nodes, (uuid, node) => [uuid, serailizeRenderedNode(node)]), variants: mapObjEntries(rig.variants, (uuid, variant) => [ uuid, @@ -286,12 +303,22 @@ export function exportJSON(options: { try { exportPath = resolvePath(aj.json_file) } catch (e) { - console.log(`Failed to resolve export path '${aj.json_file}'`) - console.error(e) - return + throw new IntentionalExportError( + `Failed to resolve export path ${aj.json_file}: ${String(e)}` + ) } - fs.writeFileSync(exportPath, compileJSON(json).toString()) + try { + const dir = PathModule.dirname(exportPath) + if (dir && dir !== '.' && !fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }) + } + fs.writeFileSync(exportPath, compileJSON(json).toString()) + } catch (e: any) { + throw new IntentionalExportError( + `Failed to write JSON file ${exportPath}: ${String(e)}` + ) + } } function serailizeNodeTransform(node: INodeTransform): ExportedNodetransform { @@ -325,19 +352,24 @@ function serailizeRenderedNode(node: AnyRenderedNode): ExportedRenderedNode { transferKey(json, 'backgroundAlpha', 'background_alpha') json.default_transform = serailizeNodeTransform(json.default_transform as INodeTransform) + + if (node.type !== 'struct' && (node as any).bounding_box) { + json.bounding_box = { + min: (node as any).bounding_box.min.toArray(), + max: (node as any).bounding_box.max.toArray(), + } + } + + if ((node as any).configs) { + json.configs = { ...(node as any).configs?.variants } + const defaultVariant = Variant.getDefault() + if ((node as any).configs?.default && defaultVariant) { + json.configs[defaultVariant.uuid] = (node as any).configs.default + } + } + switch (node.type) { case 'bone': { - delete json.boundingBox - json.bounding_box = { - min: node.bounding_box.min.toArray(), - max: node.bounding_box.max.toArray(), - } - delete json.configs - json.configs = { ...node.configs?.variants } - const defaultVariant = Variant.getDefault() - if (node.configs?.default && defaultVariant) { - json.configs[defaultVariant.uuid] = node.configs.default - } break } case 'text_display': { diff --git a/src/systems/resourcepackCompiler/index.ts b/src/systems/resourcepackCompiler/index.ts index d80d0f10..8071fa42 100644 --- a/src/systems/resourcepackCompiler/index.ts +++ b/src/systems/resourcepackCompiler/index.ts @@ -1,6 +1,6 @@ import { MAX_PROGRESS, PROGRESS, PROGRESS_DESCRIPTION } from '../../interface/dialog/exportProgress' import { getNextSupportedVersion, getResourcePackFormat } from '../../util/minecraftUtil' -import { IntentionalExportError } from '../exporter' +import { IntentionalExportError } from '../errors' import { type IRenderedRig } from '../rigRenderer' import type { ExportedFile } from '../util' diff --git a/src/systems/rigRenderer.ts b/src/systems/rigRenderer.ts index 389907fd..95b8924b 100644 --- a/src/systems/rigRenderer.ts +++ b/src/systems/rigRenderer.ts @@ -20,7 +20,7 @@ import { restoreSceneAngle, updatePreview, } from './animationRenderer' -import { IntentionalExportError } from './exporter' +import { IntentionalExportError } from './errors' import { JsonText } from './jsonText' export interface IRenderedFace { From 1466f8a11b3f1ad4e405be7d3d6127206eb94546 Mon Sep 17 00:00:00 2001 From: Meekiavelique Date: Tue, 17 Mar 2026 17:19:30 +0100 Subject: [PATCH 12/23] fix(plugin-export): align plugin-mode JSON export with blueprint schema v1 --- src/systems/exporter.ts | 10 +- src/systems/pluginCompiler.ts | 675 ++++++++++++++++++++++++++++++++-- 2 files changed, 646 insertions(+), 39 deletions(-) diff --git a/src/systems/exporter.ts b/src/systems/exporter.ts index b1a143f2..7136e0a5 100644 --- a/src/systems/exporter.ts +++ b/src/systems/exporter.ts @@ -9,7 +9,7 @@ import { isResourcePackPath } from '../util/minecraftUtil' import { translate } from '../util/translation' import { Variant } from '../variants' import { hashAnimations, renderProjectAnimations } from './animationRenderer' -import { exportJSON } from './jsonCompiler' +import { exportPluginBlueprint } from './pluginCompiler' import compileDataPack from './datapackCompiler' import { IntentionalExportError } from './errors' import resourcepackCompiler from './resourcepackCompiler' @@ -142,13 +142,7 @@ async function actuallyExportProject({ if (aj.enable_plugin_mode) { PROGRESS_DESCRIPTION.set('Exporting Plugin JSON...') - exportJSON({ - rig, - animations, - displayItemPath, - textureExportFolder, - modelExportFolder, - }) + exportPluginBlueprint({ rig, animations }) } Project!.last_used_export_namespace = aj.export_namespace diff --git a/src/systems/pluginCompiler.ts b/src/systems/pluginCompiler.ts index e4c4a200..144d0a7a 100644 --- a/src/systems/pluginCompiler.ts +++ b/src/systems/pluginCompiler.ts @@ -1,44 +1,657 @@ -namespace PluginBlueprint { - type TextureAnimationFrame = - | number - | { - index: number - time?: number - } +/// - interface TextureAnimation { - width?: number - height?: number - interpolate?: boolean - frametime?: number - frames?: TextureAnimationFrame[] +import type { IBlueprintDisplayEntityConfigJSON } from '../formats/blueprint' +import { resolvePath } from '../util/fileUtil' +import { isResourcePackPath, parseResourcePackPath, sanitizeStorageKey } from '../util/minecraftUtil' +import { detectCircularReferences, scrubUndefined } from '../util/misc' +import { Variant } from '../variants' +import type { INodeTransform, IRenderedAnimation } from './animationRenderer' +import { IntentionalExportError } from './errors' +import type { AnyRenderedNode, IRenderedElement, IRenderedFace, IRenderedModel, IRenderedRig } from './rigRenderer' + +type TextureAnimationFrame = + | number + | { + index: number + time: number + } + +interface TextureAnimation { + interpolate?: boolean + width?: number + height?: number + frametime?: number + frames?: TextureAnimationFrame[] +} + +type PluginTexture = + | { + type: 'custom' + base64_string: string + mime_type?: string + animation?: TextureAnimation + } + | { + type: 'reference' + resource_location: string + } + +interface TexturePalette { + active_state: string + states: Record +} + +type TextureProvider = + | { type: 'texture'; texture: string } + | { type: 'texture_palette'; texture_palette: string } + +interface BoneElementFace { + uv: ArrayVector4 + texture_provider: TextureProvider + tintindex?: number + rotation?: number +} + +type BoneElementFaces = Partial< + Record<'north' | 'east' | 'south' | 'west' | 'up' | 'down', BoneElementFace> +> + +interface BoneElementRotation { + angle: number + axis: 'x' | 'y' | 'z' + origin: ArrayVector3 + rescale?: boolean +} + +interface BoneElement { + from: ArrayVector3 + to: ArrayVector3 + rotation: BoneElementRotation + shade?: boolean + light_emission?: number + faces: BoneElementFaces + display_rotation?: ArrayVector3 +} + +interface NodeTransformation { + matrix?: number[] + decomposed?: { + translation?: ArrayVector3 + left_rotation?: ArrayVector4 + scale?: ArrayVector3 + } + position?: ArrayVector3 + rotation?: ArrayVector3 + head_rotation?: ArrayVector2 + scale?: ArrayVector3 +} + +type NodeType = 'bone' | 'item_display' | 'block_display' | 'text_display' | 'structure' | 'camera' | 'locator' + +type PluginNode = + | { + type: 'bone' + default_transformation?: NodeTransformation + display_properties?: Record + elements: BoneElement[] + } + | { + type: Exclude + default_transformation?: NodeTransformation + display_properties?: Record + } + +type LoopMode = { type: 'once' } | { type: 'hold' } | { type: 'loop'; loop_delay?: string } + +type TransformationKeyframeInterpolation = + | { type: 'linear'; easing: string; easing_arguments?: number[] } + | { + type: 'bezier' + left_handle_time: ArrayVector3 + left_handle_value: ArrayVector3 + right_handle_time: ArrayVector3 + right_handle_value: ArrayVector3 + } + | { type: 'catmullrom' } + | { type: 'step' } + +interface TransformationKeyframe { + value: [string, string, string] + post?: [string, string, string] + interpolation: TransformationKeyframeInterpolation +} + +interface PluginAnimation { + loop_mode: LoopMode + length: number + blend_weight?: string + start_delay?: string + global_keyframes?: { + texture?: Record> + event?: Record } + node_keyframes?: Record< + string, + { + position?: Record + rotation?: Record + scale?: Record + } + > +} - interface CustomTexture { - type: 'custom' - base64: string - mime_type: 'image/png' - animation?: TextureAnimation +export interface PluginBlueprintJson { + $schema?: string + format_version: number + settings: { + id: string } + textures?: Record + texture_palettes?: Record + nodes?: Record + animations?: Record +} - interface ReferenceTexture { - type: 'reference' - resource_location: string +function ensureUniqueKey(baseKey: string, usedKeys: Set) { + const sanitizedBaseKey = sanitizeStorageKey(baseKey || 'unnamed') + let key = sanitizedBaseKey + let i = 2 + while (usedKeys.has(key)) { + key = `${sanitizedBaseKey}_${i++}` } + usedKeys.add(key) + return key +} - export type Texture = CustomTexture | ReferenceTexture +function formatTimestamp(timestamp: number): string { + let s = timestamp.toString() + if (s.includes('e') || s.includes('E')) s = timestamp.toFixed(6) + if (!s.includes('.')) s += '.0' + return s +} - export interface Json { - format_version: string - settings: { - id: string +function toMolangNumber(value: number): string { + if (!Number.isFinite(value)) return '0' + const rounded = Math.round(value * 100000) / 100000 + if (Object.is(rounded, -0)) return '0' + return rounded.toString() +} + +function parseDataUrl(dataUrl: string): { mimeType: string; base64: string } { + const [header, base64] = dataUrl.split(',', 2) + if (!header || !base64) throw new Error('Invalid data URL') + const mimeType = header.replace(/^data:/, '').split(';')[0] || 'image/png' + return { mimeType, base64 } +} + +function readTextureAnimation(texture: Texture): TextureAnimation | undefined { + if (!texture.path) return undefined + const mcmetaPath = texture.path + '.mcmeta' + if (!fs.existsSync(mcmetaPath)) return undefined + try { + const parsed = JSON.parse(fs.readFileSync(mcmetaPath, 'utf-8')) as { + animation?: Record + } + const anim = parsed.animation as any + if (!anim) return undefined + return scrubUndefined({ + interpolate: anim.interpolate, + width: anim.width, + height: anim.height, + frametime: anim.frametime, + frames: anim.frames, + } satisfies TextureAnimation) + } catch (e) { + console.warn(`Failed to parse texture animation mcmeta for ${texture.name}:`, e) + return undefined + } +} + +function serializeNodeTransformation(transform: INodeTransform): NodeTransformation { + return scrubUndefined({ + matrix: transform.matrix.elements.slice(), + decomposed: { + translation: transform.decomposed.translation.toArray() as ArrayVector3, + left_rotation: transform.decomposed.left_rotation.toArray() as ArrayVector4, + scale: transform.decomposed.scale.toArray() as ArrayVector3, + }, + position: transform.pos, + rotation: transform.rot, + head_rotation: transform.head_rot, + scale: transform.scale, + } satisfies NodeTransformation) +} + +function serializeDisplayProperties( + node: AnyRenderedNode, + config: IBlueprintDisplayEntityConfigJSON | undefined +): Record | undefined { + const props: Record = {} + if (config?.billboard !== undefined) props.billboard = config.billboard + + const overrideBrightness = config?.override_brightness ?? false + if (overrideBrightness) { + props.is_custom_brightness_enabled = true + props.custom_brightness = config?.brightness_override ?? 0 + } + + if (config?.glowing !== undefined) props.is_glowing = config.glowing + if (config?.override_glow_color) { + const color = (config.glow_color ?? '#ffffff').replace('#', '') + props.glow_color_override = parseInt(color, 16) + } + + if (config?.shadow_radius !== undefined) props.shadow_radius = config.shadow_radius + if (config?.shadow_strength !== undefined) props.shadow_strength = config.shadow_strength + + if (node.type === 'bone' && config?.enchanted !== undefined) { + props.is_enchanted = config.enchanted + } + + if (Object.keys(props).length === 0) return undefined + return props +} + +function intFromHex8(hex: string): number { + if (hex.startsWith('#')) hex = hex.slice(1) + const unsigned = parseInt(hex, 16) + if (!Number.isFinite(unsigned)) return 0 + return unsigned > 0x7fffffff ? unsigned - 0x100000000 : unsigned +} + +function serializeTextureProvider(options: { + textureId: string + textureIdToKey: Map + textureKeyToPaletteId: Map +}): TextureProvider { + const textureKey = options.textureIdToKey.get(options.textureId) + if (!textureKey) throw new Error(`Missing texture mapping for texture id '${options.textureId}'`) + + const paletteId = options.textureKeyToPaletteId.get(textureKey) + if (paletteId) return { type: 'texture_palette', texture_palette: paletteId } + + return { type: 'texture', texture: textureKey } +} + +function serializeFace(face: IRenderedFace, options: { + textureIdToKey: Map + textureKeyToPaletteId: Map +}): BoneElementFace | undefined { + if (!face.uv) return undefined + const textureId = face.texture?.startsWith('#') ? face.texture.slice(1) : face.texture + if (!textureId) return undefined + + return scrubUndefined({ + uv: face.uv as ArrayVector4, + tintindex: face.tintindex, + rotation: face.rotation, + texture_provider: serializeTextureProvider({ + textureId, + textureIdToKey: options.textureIdToKey, + textureKeyToPaletteId: options.textureKeyToPaletteId, + }), + } satisfies BoneElementFace) +} + +function serializeBoneElements(model: IRenderedModel, options: { + textureIdToKey: Map + textureKeyToPaletteId: Map +}): BoneElement[] { + const elements = model.elements ?? [] + return elements.map((el: IRenderedElement) => { + const faces: BoneElementFaces = {} + for (const [dir, face] of Object.entries(el.faces ?? {})) { + const serializedFace = serializeFace(face as IRenderedFace, options) + if (!serializedFace) continue + ;(faces as any)[dir] = serializedFace + } + + const rotation: BoneElementRotation = + el.rotation && !Array.isArray(el.rotation) + ? { + angle: el.rotation.angle, + axis: el.rotation.axis as BoneElementRotation['axis'], + origin: el.rotation.origin as ArrayVector3, + rescale: (el.rotation as any).rescale, + } + : { angle: 0, axis: 'y', origin: [0, 0, 0] } + + return scrubUndefined({ + from: el.from as ArrayVector3, + to: el.to as ArrayVector3, + rotation, + shade: el.shade, + light_emission: el.light_emission, + faces, + display_rotation: (el as any).display_rotation, + } satisfies BoneElement) + }) +} + +function serializeNode( + node: AnyRenderedNode, + options: { + defaultVariantModels: Record + textureIdToKey: Map + textureKeyToPaletteId: Map + } +): PluginNode { + const base = { + default_transformation: serializeNodeTransformation(node.default_transform), + } as const + + const config = (node as any).configs?.default as IBlueprintDisplayEntityConfigJSON | undefined + const displayProps = serializeDisplayProperties(node, config) + + switch (node.type) { + case 'bone': { + const model = options.defaultVariantModels[node.uuid]?.model + if (!model) { + throw new Error(`Missing model for bone node '${node.name}' (${node.uuid})`) + } + return scrubUndefined({ + type: 'bone', + ...base, + display_properties: displayProps, + elements: serializeBoneElements(model, { + textureIdToKey: options.textureIdToKey, + textureKeyToPaletteId: options.textureKeyToPaletteId, + }), + } satisfies PluginNode) + } + case 'item_display': { + return scrubUndefined({ + type: 'item_display', + ...base, + display_properties: scrubUndefined({ + ...displayProps, + item: (node as any).item, + item_display: (node as any).item_display, + }), + } satisfies PluginNode) + } + case 'block_display': { + return scrubUndefined({ + type: 'block_display', + ...base, + display_properties: scrubUndefined({ + ...displayProps, + block_state: (node as any).block, + }), + } satisfies PluginNode) + } + case 'text_display': { + const argb = intFromHex8((node as any).background_color ?? '#40000000') + return scrubUndefined({ + type: 'text_display', + ...base, + display_properties: scrubUndefined({ + ...displayProps, + alignment: (node as any).align, + background_color: argb, + is_default_background: argb === 0x40000000, + is_see_through: (node as any).see_through, + is_shadowed: (node as any).shadow, + line_width: (node as any).line_width, + text: (node as any).text, + }), + } satisfies PluginNode) + } + case 'struct': + return { type: 'structure', ...base } + case 'camera': + return { type: 'camera', ...base } + case 'locator': + return { type: 'locator', ...base } + default: + throw new Error(`Unsupported node type: ${(node as any).type}`) + } +} + +function buildPalettes(options: { + textures: Record + textureIdToKey: Map +}): { palettes: Record; textureKeyToPaletteId: Map } { + const variants = Variant.allExcludingDefault() + if (variants.length === 0) { + return { palettes: {}, textureKeyToPaletteId: new Map() } + } + + const usedPaletteKeys = new Set() + const palettes: Record = {} + const textureKeyToPaletteId = new Map() + + for (const texture of Object.values(Texture.all)) { + // only exported textures + const textureKey = options.textureIdToKey.get(texture.id) + if (!textureKey) continue + if (!options.textures[textureKey]) continue + + const states: Record = { + default: { texture: textureKey }, + } + + let hasAnyAlternative = false + for (const variant of variants) { + const mapped = variant.textureMap.getMappedTexture(texture.uuid) + let mappedKey = textureKey + if (mapped) { + const key = options.textureIdToKey.get(mapped.id) + if (key) mappedKey = key + } + states[variant.name] = { texture: mappedKey } + if (mappedKey !== textureKey) hasAnyAlternative = true + } + + if (!hasAnyAlternative) continue + + const paletteId = ensureUniqueKey(`${textureKey}_palette`, usedPaletteKeys) + palettes[paletteId] = { + active_state: 'default', + states, + } + textureKeyToPaletteId.set(textureKey, paletteId) + } + + return { palettes, textureKeyToPaletteId } +} + +function serializeTexture(texture: Texture): PluginTexture { + if (texture.path && isResourcePackPath(texture.path)) { + const parsed = parseResourcePackPath(texture.path) + if (parsed?.namespace === 'minecraft') { + return { + type: 'reference', + resource_location: parsed.resourceLocation, + } } - textures: Record } + + const dataUrl = texture.getDataURL() + const { mimeType, base64 } = parseDataUrl(dataUrl) + return scrubUndefined({ + type: 'custom', + base64_string: base64, + mime_type: mimeType, + animation: readTextureAnimation(texture), + } satisfies PluginTexture) } -// export function compilePluginBlueprint(): PluginBlueprint.Json { -// const blueprint: PluginBlueprint.Json = {} +function serializeAnimation(options: { + animation: IRenderedAnimation + nodeUuidToId: Map + paletteIds: string[] +}): PluginAnimation { + const { animation, nodeUuidToId } = options + + const loop_mode: LoopMode = + animation.loop_mode === 'loop' + ? { type: 'loop', loop_delay: String(animation.loop_delay ?? 0) } + : animation.loop_mode === 'hold' + ? { type: 'hold' } + : { type: 'once' } + + const maxTime = animation.frames.at(-1)?.time ?? 0 + + const node_keyframes: NonNullable = {} + + for (const frame of animation.frames) { + const timeKey = formatTimestamp(frame.time) + for (const [uuid, transform] of Object.entries(frame.node_transforms)) { + const nodeId = nodeUuidToId.get(uuid) + if (!nodeId) continue + node_keyframes[nodeId] ??= {} + + const createInterpolation = (): TransformationKeyframeInterpolation => + transform.interpolation === 'step' || transform.interpolation === 'pre-post' + ? { type: 'step' } + : { type: 'linear', easing: 'linear' } + + node_keyframes[nodeId].position ??= {} + node_keyframes[nodeId].rotation ??= {} + node_keyframes[nodeId].scale ??= {} -// return blueprint -// } + node_keyframes[nodeId].position![timeKey] = { + value: [ + toMolangNumber(transform.pos[0]), + toMolangNumber(transform.pos[1]), + toMolangNumber(transform.pos[2]), + ], + interpolation: createInterpolation(), + } + node_keyframes[nodeId].rotation![timeKey] = { + value: [ + toMolangNumber(transform.rot[0]), + toMolangNumber(transform.rot[1]), + toMolangNumber(transform.rot[2]), + ], + interpolation: createInterpolation(), + } + node_keyframes[nodeId].scale![timeKey] = { + value: [ + toMolangNumber(transform.scale[0]), + toMolangNumber(transform.scale[1]), + toMolangNumber(transform.scale[2]), + ], + interpolation: createInterpolation(), + } + } + } + + let global_keyframes: NonNullable | undefined + + // map the baked variant for each frame into the texture keyframes + if (options.paletteIds.length) { + const textureKeyframes: Record> = {} + for (const frame of animation.frames) { + if (!frame.variants?.length) continue + const variant = Variant.getByUUID(frame.variants[0]) + if (!variant) continue + const timeKey = formatTimestamp(frame.time) + textureKeyframes[timeKey] ??= {} + for (const paletteId of options.paletteIds) { + textureKeyframes[timeKey][paletteId] = variant.name + } + } + if (Object.keys(textureKeyframes).length) { + global_keyframes ??= {} + global_keyframes.texture = textureKeyframes + } + } + + return scrubUndefined({ + loop_mode, + blend_weight: '1', + start_delay: '0', + length: maxTime, + global_keyframes, + node_keyframes, + } satisfies PluginAnimation) +} + +export function exportPluginBlueprint(options: { + rig: IRenderedRig + animations: IRenderedAnimation[] +}): void { + const aj = Project!.animated_java + + const usedTextureKeys = new Set() + const textures: Record = {} + const textureIdToKey = new Map() + + for (const texture of Object.values(options.rig.textures)) { + const baseKey = texture.name.replace(/\\.png$/i, '') + const key = ensureUniqueKey(baseKey, usedTextureKeys) + textureIdToKey.set(texture.id, key) + textures[key] = serializeTexture(texture) + } + + const { palettes, textureKeyToPaletteId } = buildPalettes({ textures, textureIdToKey }) + const paletteIds = Object.keys(palettes) + + const usedNodeKeys = new Set() + const nodeUuidToId = new Map() + for (const [uuid, node] of Object.entries(options.rig.nodes)) { + nodeUuidToId.set(uuid, ensureUniqueKey(node.storage_name, usedNodeKeys)) + } + + const defaultVariant = Variant.getDefault() + const defaultVariantModels = options.rig.variants[defaultVariant.uuid]?.models ?? {} + + const nodes: Record = {} + for (const [uuid, node] of Object.entries(options.rig.nodes)) { + const nodeId = nodeUuidToId.get(uuid) + if (!nodeId) continue + nodes[nodeId] = serializeNode(node, { + defaultVariantModels, + textureIdToKey, + textureKeyToPaletteId, + }) + } + + const animations: Record = {} + for (const animation of options.animations) { + const key = ensureUniqueKey(animation.storage_name, new Set(Object.keys(animations))) + animations[key] = serializeAnimation({ + animation, + nodeUuidToId, + paletteIds, + }) + } + + const blueprint: PluginBlueprintJson = scrubUndefined({ + format_version: 1, + settings: { + id: `animated_java:${aj.export_namespace}`, + }, + textures, + texture_palettes: palettes, + nodes, + animations, + }) + + if (detectCircularReferences(blueprint)) { + throw new Error('Circular references detected in exported plugin blueprint.') + } + + let exportPath: string + try { + exportPath = resolvePath(aj.json_file) + } catch (e) { + throw new IntentionalExportError( + `Failed to resolve export path ${aj.json_file}: ${String(e)}` + ) + } + + try { + const dir = PathModule.dirname(exportPath) + if (dir && dir !== '.' && !fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }) + } + fs.writeFileSync(exportPath, compileJSON(blueprint).toString()) + } catch (e: any) { + throw new IntentionalExportError( + `Failed to write JSON file ${exportPath}: ${String(e)}` + ) + } +} From 5d6368f2fa95d83aba14c812529008d849b6e9d8 Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Tue, 17 Mar 2026 18:33:12 -0400 Subject: [PATCH 13/23] =?UTF-8?q?=E2=9C=A8=20Improve=20error=20messages=20?= =?UTF-8?q?for=20`as=5Fall=5Flocators`=20and=20similar=20functions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/global.d.ts | 2 +- .../datapackCompiler/1.20.4/animation.mcb | 46 +++++++++- .../datapackCompiler/1.20.4/static.mcb | 46 +++++++++- .../datapackCompiler/1.20.5/animation.mcb | 46 +++++++++- .../datapackCompiler/1.20.5/static.mcb | 46 +++++++++- .../datapackCompiler/1.21.0/animation.mcb | 46 +++++++++- .../datapackCompiler/1.21.0/static.mcb | 46 +++++++++- .../datapackCompiler/1.21.2/animation.mcb | 46 +++++++++- .../datapackCompiler/1.21.2/static.mcb | 46 +++++++++- .../datapackCompiler/1.21.4/animation.mcb | 46 +++++++++- .../datapackCompiler/1.21.4/static.mcb | 46 +++++++++- src/systems/datapackCompiler/tellraw.ts | 12 +-- src/systems/jsonCompiler.ts | 2 +- .../blueprints/armor_stand_latest.ajblueprint | 89 +++++++++---------- .../datapacks/test-framework/src/test.mcb | 53 +++++++++++ types/blockbench-types.d.ts | 2 +- 16 files changed, 542 insertions(+), 78 deletions(-) diff --git a/src/global.d.ts b/src/global.d.ts index 04c03e60..64ab031a 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -1,4 +1,4 @@ -/// +/// /// declare module '*.png' { diff --git a/src/systems/datapackCompiler/1.20.4/animation.mcb b/src/systems/datapackCompiler/1.20.4/animation.mcb index 1896a1be..eff15b7f 100644 --- a/src/systems/datapackCompiler/1.20.4/animation.mcb +++ b/src/systems/datapackCompiler/1.20.4/animation.mcb @@ -847,8 +847,22 @@ IF (has_entity_locators) { execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args - $execute as $(uuid) run $(command) + 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.LOCATOR_COMMAND_FAILED_TO_EXECUTE({text: locator.storage_name, color: 'aqua'})%> } } } @@ -868,8 +882,22 @@ IF (has_entity_locators) { execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args - $execute as $(uuid) at @s run $(command) + 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.LOCATOR_COMMAND_FAILED_TO_EXECUTE({text: locator.storage_name, color: 'aqua'})%> } } } @@ -933,12 +961,26 @@ IF (has_locators) { execute at @s on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/at_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { data modify storage <%temp_storage%> args merge from entity @s data.locators.<%locator.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.LOCATOR_COMMAND_FAILED_TO_EXECUTE({text: locator.storage_name, color: 'aqua'})%> + } } } } diff --git a/src/systems/datapackCompiler/1.20.4/static.mcb b/src/systems/datapackCompiler/1.20.4/static.mcb index 2099c62c..ebfe63f7 100644 --- a/src/systems/datapackCompiler/1.20.4/static.mcb +++ b/src/systems/datapackCompiler/1.20.4/static.mcb @@ -422,8 +422,22 @@ IF (has_entity_locators) { execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args - $execute as $(uuid) run $(command) + 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.LOCATOR_COMMAND_FAILED_TO_EXECUTE({text: locator.storage_name, color: 'aqua'})%> } } } @@ -443,8 +457,22 @@ IF (has_entity_locators) { execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args - $execute as $(uuid) at @s run $(command) + 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.LOCATOR_COMMAND_FAILED_TO_EXECUTE({text: locator.storage_name, color: 'aqua'})%> } } } @@ -508,12 +536,26 @@ IF (has_locators) { execute at @s on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/at_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { data modify storage <%temp_storage%> args merge from entity @s data.locators.<%locator.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.LOCATOR_COMMAND_FAILED_TO_EXECUTE({text: locator.storage_name, color: 'aqua'})%> + } } } } diff --git a/src/systems/datapackCompiler/1.20.5/animation.mcb b/src/systems/datapackCompiler/1.20.5/animation.mcb index 29fe103c..b65ff743 100644 --- a/src/systems/datapackCompiler/1.20.5/animation.mcb +++ b/src/systems/datapackCompiler/1.20.5/animation.mcb @@ -847,8 +847,22 @@ IF (has_entity_locators) { execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args - $execute as $(uuid) run $(command) + 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.LOCATOR_COMMAND_FAILED_TO_EXECUTE({text: locator.storage_name, color: 'aqua'})%> } } } @@ -868,8 +882,22 @@ IF (has_entity_locators) { execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args - $execute as $(uuid) at @s run $(command) + 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.LOCATOR_COMMAND_FAILED_TO_EXECUTE({text: locator.storage_name, color: 'aqua'})%> } } } @@ -933,12 +961,26 @@ IF (has_locators) { execute at @s on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/at_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { data modify storage <%temp_storage%> args merge from entity @s data.locators.<%locator.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.LOCATOR_COMMAND_FAILED_TO_EXECUTE({text: locator.storage_name, color: 'aqua'})%> + } } } } diff --git a/src/systems/datapackCompiler/1.20.5/static.mcb b/src/systems/datapackCompiler/1.20.5/static.mcb index 07479bf2..b4b504ba 100644 --- a/src/systems/datapackCompiler/1.20.5/static.mcb +++ b/src/systems/datapackCompiler/1.20.5/static.mcb @@ -422,8 +422,22 @@ IF (has_entity_locators) { execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args - $execute as $(uuid) run $(command) + 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.LOCATOR_COMMAND_FAILED_TO_EXECUTE({text: locator.storage_name, color: 'aqua'})%> } } } @@ -443,8 +457,22 @@ IF (has_entity_locators) { execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args - $execute as $(uuid) at @s run $(command) + 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.LOCATOR_COMMAND_FAILED_TO_EXECUTE({text: locator.storage_name, color: 'aqua'})%> } } } @@ -508,12 +536,26 @@ IF (has_locators) { execute at @s on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/at_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { data modify storage <%temp_storage%> args merge from entity @s data.locators.<%locator.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.LOCATOR_COMMAND_FAILED_TO_EXECUTE({text: locator.storage_name, color: 'aqua'})%> + } } } } diff --git a/src/systems/datapackCompiler/1.21.0/animation.mcb b/src/systems/datapackCompiler/1.21.0/animation.mcb index 41a7bb0a..a01b537b 100644 --- a/src/systems/datapackCompiler/1.21.0/animation.mcb +++ b/src/systems/datapackCompiler/1.21.0/animation.mcb @@ -847,8 +847,22 @@ IF (has_entity_locators) { execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args - $execute as $(uuid) run $(command) + 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.LOCATOR_COMMAND_FAILED_TO_EXECUTE({text: locator.storage_name, color: 'aqua'})%> } } } @@ -868,8 +882,22 @@ IF (has_entity_locators) { execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args - $execute as $(uuid) at @s run $(command) + 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.LOCATOR_COMMAND_FAILED_TO_EXECUTE({text: locator.storage_name, color: 'aqua'})%> } } } @@ -933,12 +961,26 @@ IF (has_locators) { execute at @s on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/at_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { data modify storage <%temp_storage%> args merge from entity @s data.locators.<%locator.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.LOCATOR_COMMAND_FAILED_TO_EXECUTE({text: locator.storage_name, color: 'aqua'})%> + } } } } diff --git a/src/systems/datapackCompiler/1.21.0/static.mcb b/src/systems/datapackCompiler/1.21.0/static.mcb index 51b405c5..992de877 100644 --- a/src/systems/datapackCompiler/1.21.0/static.mcb +++ b/src/systems/datapackCompiler/1.21.0/static.mcb @@ -422,8 +422,22 @@ IF (has_entity_locators) { execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args - $execute as $(uuid) run $(command) + 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.LOCATOR_COMMAND_FAILED_TO_EXECUTE({text: locator.storage_name, color: 'aqua'})%> } } } @@ -443,8 +457,22 @@ IF (has_entity_locators) { execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args - $execute as $(uuid) at @s run $(command) + 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.LOCATOR_COMMAND_FAILED_TO_EXECUTE({text: locator.storage_name, color: 'aqua'})%> } } } @@ -508,12 +536,26 @@ IF (has_locators) { execute at @s on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/at_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { data modify storage <%temp_storage%> args merge from entity @s data.locators.<%locator.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.LOCATOR_COMMAND_FAILED_TO_EXECUTE({text: locator.storage_name, color: 'aqua'})%> + } } } } diff --git a/src/systems/datapackCompiler/1.21.2/animation.mcb b/src/systems/datapackCompiler/1.21.2/animation.mcb index 522c2440..8c65e738 100644 --- a/src/systems/datapackCompiler/1.21.2/animation.mcb +++ b/src/systems/datapackCompiler/1.21.2/animation.mcb @@ -847,8 +847,22 @@ IF (has_entity_locators) { execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args - $execute as $(uuid) run $(command) + 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.LOCATOR_COMMAND_FAILED_TO_EXECUTE({text: locator.storage_name, color: 'aqua'})%> } } } @@ -868,8 +882,22 @@ IF (has_entity_locators) { execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args - $execute as $(uuid) at @s run $(command) + 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.LOCATOR_COMMAND_FAILED_TO_EXECUTE({text: locator.storage_name, color: 'aqua'})%> } } } @@ -933,12 +961,26 @@ IF (has_locators) { execute at @s on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/at_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { data modify storage <%temp_storage%> args merge from entity @s data.locators.<%locator.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.LOCATOR_COMMAND_FAILED_TO_EXECUTE({text: locator.storage_name, color: 'aqua'})%> + } } } } diff --git a/src/systems/datapackCompiler/1.21.2/static.mcb b/src/systems/datapackCompiler/1.21.2/static.mcb index e5323298..d5befd6e 100644 --- a/src/systems/datapackCompiler/1.21.2/static.mcb +++ b/src/systems/datapackCompiler/1.21.2/static.mcb @@ -422,8 +422,22 @@ IF (has_entity_locators) { execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args - $execute as $(uuid) run $(command) + 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.LOCATOR_COMMAND_FAILED_TO_EXECUTE({text: locator.storage_name, color: 'aqua'})%> } } } @@ -443,8 +457,22 @@ IF (has_entity_locators) { execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args - $execute as $(uuid) at @s run $(command) + 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.LOCATOR_COMMAND_FAILED_TO_EXECUTE({text: locator.storage_name, color: 'aqua'})%> } } } @@ -508,12 +536,26 @@ IF (has_locators) { execute at @s on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/at_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { data modify storage <%temp_storage%> args merge from entity @s data.locators.<%locator.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.LOCATOR_COMMAND_FAILED_TO_EXECUTE({text: locator.storage_name, color: 'aqua'})%> + } } } } diff --git a/src/systems/datapackCompiler/1.21.4/animation.mcb b/src/systems/datapackCompiler/1.21.4/animation.mcb index 31adf124..a69d1d4c 100644 --- a/src/systems/datapackCompiler/1.21.4/animation.mcb +++ b/src/systems/datapackCompiler/1.21.4/animation.mcb @@ -898,8 +898,22 @@ IF (has_entity_locators) { execute on passengers run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args - $execute as $(uuid) run $(command) + 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.LOCATOR_COMMAND_FAILED_TO_EXECUTE({text: locator.storage_name, color: 'aqua'})%> } } } @@ -919,8 +933,22 @@ IF (has_entity_locators) { execute on passengers run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args - $execute as $(uuid) at @s run $(command) + 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.LOCATOR_COMMAND_FAILED_TO_EXECUTE({text: locator.storage_name, color: 'aqua'})%> } } } @@ -980,12 +1008,26 @@ IF (has_locators) { execute at @s on passengers run block zzz/at_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { data modify storage <%temp_storage%> args merge from entity @s data.locators.<%locator.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.LOCATOR_COMMAND_FAILED_TO_EXECUTE({text: locator.storage_name, color: 'aqua'})%> + } } } } diff --git a/src/systems/datapackCompiler/1.21.4/static.mcb b/src/systems/datapackCompiler/1.21.4/static.mcb index a31dc1cc..87e0133d 100644 --- a/src/systems/datapackCompiler/1.21.4/static.mcb +++ b/src/systems/datapackCompiler/1.21.4/static.mcb @@ -422,8 +422,22 @@ IF (has_entity_locators) { execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args - $execute as $(uuid) run $(command) + 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.LOCATOR_COMMAND_FAILED_TO_EXECUTE({text: locator.storage_name, color: 'aqua'})%> } } } @@ -443,8 +457,22 @@ IF (has_entity_locators) { execute on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/as_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator' && node.config?.use_entity)) as locator { data modify storage <%temp_storage%> args.uuid set from entity @s data.locators.<%locator.storage_name%>.uuid + + IF (debug_mode) { + scoreboard players set #aj.check <%OBJECTIVES.I()%> 0 + } + block execute_as_uuid { with storage <%temp_storage%> args - $execute as $(uuid) at @s run $(command) + 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.LOCATOR_COMMAND_FAILED_TO_EXECUTE({text: locator.storage_name, color: 'aqua'})%> } } } @@ -508,12 +536,26 @@ IF (has_locators) { execute at @s on passengers if entity @s[tag=<%TAGS.GLOBAL_DATA()%>] run block zzz/at_all_locators/as_data { REPEAT (Object.values(rig.nodes).filter(node => node.type === 'locator')) as locator { data modify storage <%temp_storage%> args merge from entity @s data.locators.<%locator.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.LOCATOR_COMMAND_FAILED_TO_EXECUTE({text: locator.storage_name, color: 'aqua'})%> + } } } } diff --git a/src/systems/datapackCompiler/tellraw.ts b/src/systems/datapackCompiler/tellraw.ts index aa3e2e6d..698e7a56 100644 --- a/src/systems/datapackCompiler/tellraw.ts +++ b/src/systems/datapackCompiler/tellraw.ts @@ -259,12 +259,12 @@ namespace TELLRAW { " is enabled in the locator's config.", ]) - export const LOCATOR_COMMAND_FAILED_TO_EXECUTE = () => + export const LOCATOR_COMMAND_FAILED_TO_EXECUTE = (name?: TextElement) => TELLRAW_ERROR('Failed to Execute Command as Locator', [ 'Failed to execute command ', { nbt: 'args.command', storage: 'animated_java:temp', color: 'yellow' }, ' as Locator ', - { nbt: 'args.name', storage: 'animated_java:temp', color: 'aqua' }, + name ?? { nbt: 'args.name', storage: 'animated_java:temp', color: 'aqua' }, '.', '\n Please ensure the command is valid.', ]) @@ -277,12 +277,12 @@ namespace TELLRAW { '\n Please ensure that its name is spelled correctly.', ]) - export const CAMERA_COMMAND_FAILED_TO_EXECUTE = () => + export const CAMERA_COMMAND_FAILED_TO_EXECUTE = (name?: TextElement) => TELLRAW_ERROR('Failed to Execute Command as Camera', [ 'Failed to execute command ', { nbt: 'args.command', storage: 'animated_java:temp', color: 'yellow' }, ' as Camera ', - { nbt: 'args.name', storage: 'animated_java:temp', color: 'aqua' }, + name ?? { nbt: 'args.name', storage: 'animated_java:temp', color: 'aqua' }, '.', '\n Please ensure the command is valid.', ]) @@ -295,12 +295,12 @@ namespace TELLRAW { '\n Please ensure that its name is spelled correctly.', ]) - export const NODE_COMMAND_FAILED_TO_EXECUTE = () => + export const NODE_COMMAND_FAILED_TO_EXECUTE = (name?: TextElement) => TELLRAW_ERROR('Failed to Execute Command as Node', [ 'Failed to execute command ', { nbt: 'args.command', storage: 'animated_java:temp', color: 'yellow' }, ' as Node ', - { nbt: 'args.name', storage: 'animated_java:temp', color: 'aqua' }, + name ?? { nbt: 'args.name', storage: 'animated_java:temp', color: 'aqua' }, '.', '\n Please ensure the command is valid.', ]) diff --git a/src/systems/jsonCompiler.ts b/src/systems/jsonCompiler.ts index 2dba4d0c..6b22fff5 100644 --- a/src/systems/jsonCompiler.ts +++ b/src/systems/jsonCompiler.ts @@ -1,5 +1,5 @@ //// -/// +/// /// import type { IBlueprintDisplayEntityConfigJSON } from '../formats/blueprint' diff --git a/test-packs/latest/blueprints/armor_stand_latest.ajblueprint b/test-packs/latest/blueprints/armor_stand_latest.ajblueprint index 16868924..e33c3a71 100644 --- a/test-packs/latest/blueprints/armor_stand_latest.ajblueprint +++ b/test-packs/latest/blueprints/armor_stand_latest.ajblueprint @@ -3,7 +3,7 @@ "format": "animated-java:format/blueprint", "format_version": "1.8.1", "uuid": "167b27cd-b559-3f13-a97c-0841fe21f1d1", - "save_location": "D:\\github-repos\\animated-java\\old-animated-java\\test-packs\\latest\\blueprints\\armor_stand_latest.ajblueprint", + "save_location": "/var/mnt/ssd2/repos/animated-java/animated-java/test-packs/latest/blueprints/armor_stand_latest.ajblueprint", "last_used_export_namespace": "armor_stand" }, "resolution": { @@ -906,14 +906,44 @@ }, { "name": "camera", - "position": [0, 27, 0], - "rotation": [0, 0, 0], - "fov": 70, - "aspect_ratio": [16, 9], - "linked_preview": "", - "camera_linked": false, - "visibility": true, - "type": "camera", + "box_uv": false, + "rescale": false, + "locked": false, + "light_emission": 0, + "render_order": "default", + "allow_mirror_modeling": true, + "from": [0, 0, 0], + "to": [1, 1, 1], + "autouv": 0, + "color": 3, + "origin": [0, 0, 0], + "faces": { + "north": { + "uv": [0, 0, 1, 1], + "texture": 0 + }, + "east": { + "uv": [0, 0, 1, 1], + "texture": 0 + }, + "south": { + "uv": [0, 0, 1, 1], + "texture": 0 + }, + "west": { + "uv": [0, 0, 1, 1], + "texture": 0 + }, + "up": { + "uv": [0, 0, 1, 1], + "texture": 0 + }, + "down": { + "uv": [0, 0, 1, 1], + "texture": 0 + } + }, + "type": "cube", "uuid": "3ffeee76-901e-f8a2-c29a-82f90e16fd1e" }, { @@ -2314,46 +2344,7 @@ "start_delay": "", "loop_delay": "0", "excluded_nodes": [], - "animators": { - "3ffeee76-901e-f8a2-c29a-82f90e16fd1e": { - "name": "camera", - "type": "camera", - "keyframes": [ - { - "channel": "position", - "data_points": [ - { - "x": "math.sin(q.life_time * 4) * 100", - "y": "0", - "z": "math.cos(q.life_time * 4) * 100" - } - ], - "uuid": "7ea87827-b1e4-6f50-5a62-b4a2b3e25b54", - "time": 0, - "color": -1, - "interpolation": "linear", - "easing": "linear", - "easingArgs": [] - }, - { - "channel": "rotation", - "data_points": [ - { - "x": "0", - "y": "q.life_time * 4", - "z": "0" - } - ], - "uuid": "3e52691f-2314-0f11-1f4f-d6ce872df516", - "time": 0, - "color": -1, - "interpolation": "linear", - "easing": "linear", - "easingArgs": [] - } - ] - } - } + "animators": {} } ], "animation_variable_placeholders": "v.walk_speed = 360 * 1;\nv.run_speed = 360 * 1.5;\nv.stickbug_speed = 360 * 1.25;\n" diff --git a/test-packs/latest/datapacks/test-framework/src/test.mcb b/test-packs/latest/datapacks/test-framework/src/test.mcb index 316ea9ee..51156f90 100644 --- a/test-packs/latest/datapacks/test-framework/src/test.mcb +++ b/test-packs/latest/datapacks/test-framework/src/test.mcb @@ -54,4 +54,57 @@ dir armor_stand { gamemode spectator @s execute as @n[tag=aj.global.root] run function animated_java:armor_stand/as_camera {name:'camera',command:'spectate @s @p'} } + + dir test { + function at_all_locators { + function animated_java:global/remove/everything + execute positioned 0 1 0 rotated 0 0 run function animated_java:armor_stand/summon {args:{}} + + scoreboard players set #test aj.i 0 + execute as @e[tag=aj.armor_stand.root] run function animated_java:armor_stand/at_all_locators {command:'scoreboard players add #test aj.i 1'} + execute if score #test aj.i matches 0 run return run tellraw @a {text: 'TEST FAILED: No locators executed the command', color: 'red'} + + tellraw @a {text: '\nThe following message should be an error warning about an empty command string', color: 'green'} + execute as @e[tag=aj.armor_stand.root] run function animated_java:armor_stand/at_all_locators {command:''} + tellraw @a {text: 'end of message', color: 'green'} + + tellraw @a {text: '\nThe following message(s) should be error(s) warning about an invalid command', color: 'green'} + execute as @e[tag=aj.armor_stand.root] run function animated_java:armor_stand/at_all_locators {command:'someone did an oopsie'} + tellraw @a {text: 'end of message(s)', color: 'green'} + } + + function as_all_locators { + function animated_java:global/remove/everything + execute positioned 0 1 0 rotated 0 0 run function animated_java:armor_stand/summon {args:{}} + + scoreboard players set #test aj.i 0 + execute as @e[tag=aj.armor_stand.root] run function animated_java:armor_stand/as_all_locators {command:'scoreboard players add #test aj.i 1'} + execute if score #test aj.i matches 0 run return run tellraw @a {text: 'TEST FAILED: No locators executed the command', color: 'red'} + + tellraw @a {text: '\nThe following message should be an error warning about an empty command string', color: 'green'} + execute as @e[tag=aj.armor_stand.root] run function animated_java:armor_stand/as_all_locators {command:''} + tellraw @a {text: 'end of message', color: 'green'} + + tellraw @a {text: '\nThe following message(s) should be error(s) warning about an invalid command', color: 'green'} + execute as @e[tag=aj.armor_stand.root] run function animated_java:armor_stand/as_all_locators {command:'someone did an oopsie'} + tellraw @a {text: 'end of message(s)', color: 'green'} + } + + function as_at_all_locators { + function animated_java:global/remove/everything + execute positioned 0 1 0 rotated 0 0 run function animated_java:armor_stand/summon {args:{}} + + scoreboard players set #test aj.i 0 + execute as @e[tag=aj.armor_stand.root] run function animated_java:armor_stand/as_at_all_locators {command:'scoreboard players add #test aj.i 1'} + execute if score #test aj.i matches 0 run return run tellraw @a {text: 'TEST FAILED: No locators executed the command', color: 'red'} + + tellraw @a {text: '\nThe following message should be an error warning about an empty command string', color: 'green'} + execute as @e[tag=aj.armor_stand.root] run function animated_java:armor_stand/as_at_all_locators {command:''} + tellraw @a {text: 'end of message', color: 'green'} + + tellraw @a {text: '\nThe following message(s) should be error(s) warning about an invalid command', color: 'green'} + execute as @e[tag=aj.armor_stand.root] run function animated_java:armor_stand/as_at_all_locators {command:'someone did an oopsie'} + tellraw @a {text: 'end of message(s)', color: 'green'} + } + } } diff --git a/types/blockbench-types.d.ts b/types/blockbench-types.d.ts index 13f5aebe..912af031 100644 --- a/types/blockbench-types.d.ts +++ b/types/blockbench-types.d.ts @@ -1,2 +1,2 @@ //// -/// +/// From 84c4bc7b5809fd8b158f875cec9192e9734dfb25 Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Tue, 17 Mar 2026 19:18:42 -0400 Subject: [PATCH 14/23] =?UTF-8?q?=F0=9F=90=9B=20Fix=20locators=20failing?= =?UTF-8?q?=20to=20tick,=20and=20display=20entities=20being=20left=20behin?= =?UTF-8?q?d?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/systems/datapackCompiler/1.21.4/animation.mcb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/systems/datapackCompiler/1.21.4/animation.mcb b/src/systems/datapackCompiler/1.21.4/animation.mcb index a69d1d4c..f2d72684 100644 --- a/src/systems/datapackCompiler/1.21.4/animation.mcb +++ b/src/systems/datapackCompiler/1.21.4/animation.mcb @@ -66,7 +66,7 @@ dir root { } execute on passengers run { with entity @s data.uuids_by_name REPEAT (Object.values(rig.nodes).filter(node => BONE_TYPES.includes(node.type))) as node { - $rotate $(<%node.storage_name%>) ~ ~ + $tp $(<%node.storage_name%>) ~ ~ ~ ~ ~ # Precise Rotation Workaround. Fixes MC-272913. # Thanks @Triton365! (https://discord.com/channels/154777837382008833/157097006500806656/1402253905408163842) $execute \ @@ -170,7 +170,7 @@ IF (!auto_update_rig_orientation) { function ./root/on_tick/transform_floating_entities } REPEAT (Object.values(rig.nodes).filter(node => BONE_TYPES.includes(node.type))) as node { - $rotate $(<%node.storage_name%>) ~ ~ + $tp $(<%node.storage_name%>) ~ ~ ~ ~ ~ # Precise Rotation Workaround. Fixes MC-272913. # Thanks @Triton365! (https://discord.com/channels/154777837382008833/157097006500806656/1402253905408163842) $execute \ @@ -505,7 +505,7 @@ dir animations { if (Object.keys(to_merge.locators).length > 0 || Object.keys(to_merge.cameras).length > 0) { frameFunc += `\ndata modify entity @s data merge value ${JSON.stringify(to_merge)}` if (!auto_update_rig_orientation) { - frameFunc += `\nfunction ./on_tick/transform_floating_entities` + frameFunc += `\nfunction animated_java:${export_namespace}/root/on_tick/transform_floating_entities` } hasFunction = true } From 02832a876b200655028ac1492534f7b9614a7803 Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Tue, 17 Mar 2026 19:35:54 -0400 Subject: [PATCH 15/23] =?UTF-8?q?=F0=9F=94=96=20v1.9.0-beta.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 4 +-- src/pluginPackage/changelog.json | 47 ++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 0693fee7..b32b2078 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "title": "Animated Java", "icon": "icon.svg", "description": "Effortlessly craft complex animations for Minecraft: Java Edition", - "version": "1.8.1", - "min_blockbench_version": "4.12.0", + "version": "1.9.0-beta.2", + "min_blockbench_version": "4.12.6", "max_blockbench_version": "4.12.6", "variant": "desktop", "tags": [ diff --git a/src/pluginPackage/changelog.json b/src/pluginPackage/changelog.json index cff2b2cd..c388da10 100644 --- a/src/pluginPackage/changelog.json +++ b/src/pluginPackage/changelog.json @@ -603,5 +603,52 @@ ] } ] + }, + "1.9.0-beta.1": { + "title": "v1.9.0-beta.1", + "author": "Titus Evans (SnaveSutit)", + "date": "2026-03-17", + "categories": [ + { + "title": "Changes", + "list": [ + "[BREAKING] Nodes are no longer passengers of the root entity in versions 1.21.4 and above. Any code that relies on `on passengers` to target rig nodes will need to be updated to use `as_node` instead.`", + "[BREAKING] `as_locator` & `as_all_locators` no longer execute `at` the targeted locators.", + "Added `as_at_locator` & `as_at_all_locators` functions to replace previous `as_locator` and `as_all_locators` positional functionality.", + "Made camera and rig rotation much more precise for 1.21.4 and above.", + "Added support for the removal of item model rotation restrictions in 1.21.11.", + "Added `as_node` function." + ] + }, + { + "title": "Fixes", + "list": [ + "Fixed [#475](https://github.com/Animated-Java/animated-java/issues/475)", + "Fixed [#474](https://github.com/Animated-Java/animated-java/issues/474)", + "Fixed [#472](https://github.com/Animated-Java/animated-java/issues/472)", + "Fixed [#471](https://github.com/Animated-Java/animated-java/issues/471)", + "Fixed [#469](https://github.com/Animated-Java/animated-java/issues/469)", + "Fixed [#468](https://github.com/Animated-Java/animated-java/issues/468)", + "Fixed `move` function not transforming floating entities." + ] + } + ] + }, + "1.9.0-beta.2": { + "title": "v1.9.0-beta.2", + "author": "Titus Evans (SnaveSutit)", + "date": "2026-03-17", + "categories": [ + { + "title": "Changes", + "list": ["Improved error messages for `as_all_locators` and similar functions."] + }, + { + "title": "Fixes", + "list": [ + "Fixed locators failing to tick, and display entities being left behind when moving the root (either by `tp` or `move` function)." + ] + } + ] } } From dc4c9f0fd733e1ac7cb757c765b4e1854eb853ba Mon Sep 17 00:00:00 2001 From: Koishem Date: Wed, 25 Mar 2026 06:59:27 +0100 Subject: [PATCH 16/23] =?UTF-8?q?=F0=9F=94=A4=20Improve=20translations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lang/ru.yml | 266 ++++++++++++++++++++++++------------------------ 1 file changed, 133 insertions(+), 133 deletions(-) diff --git a/src/lang/ru.yml b/src/lang/ru.yml index 882f9f5e..cf514a7f 100644 --- a/src/lang/ru.yml +++ b/src/lang/ru.yml @@ -19,9 +19,9 @@ animated_java.action.export_all_debug.description: |- Экспортировать все открытые чертежи в режиме отладки. animated_java.action.extract.name: Извлечь animated_java.action.extract.confirm: Подтвердить извлечение -animated_java.action.create_text_display.title: Добавление текст дисплея -animated_java.action.create_vanilla_item_display.title: Добавление предмет дисплея -animated_java.action.create_vanilla_block_display.title: Добавление блок дисплея +animated_java.action.create_text_display.title: Добавление текст-дисплея +animated_java.action.create_vanilla_item_display.title: Добавление предмет-дисплея +animated_java.action.create_vanilla_block_display.title: Добавление блок-дисплея animated_java.action.copy_display_entity_config.name: Копировать настройку дисплей-сущности animated_java.action.copy_display_entity_config.message: Скопирована настройка дисплей-сущности из "{0}" animated_java.action.paste_display_entity_config.name: Вставить настройку дисплей-сущности @@ -32,108 +32,108 @@ animated_java.popup.loading.loading: Загрузка Animated Java... animated_java.popup.loading.success: Animated Java загружена успешно! animated_java.popup.loading.offline: |- Animated Java не удалось подключить! - Некоторый функционал может быть недоступен. + Некоторые функции могут быть недоступны. animated_java.popup.installed_popup.title: Спасибо за установку! -animated_java.popup.installed_popup.close_button: Начнём анимировать! +animated_java.popup.installed_popup.close_button: Приступим к анимации! -animated_java.popup.incompatability_popup.title: Animated Java Обнаружена несовместимость +animated_java.popup.incompatability_popup.title: Обнаружена несовместимость Animated Java animated_java.popup.incompatability_popup.description: |- - У вас установлены плагины которые вызывают проблемы с Animated Java. - Пожалуйста отключите или удалите эти плагин(ы) и перезапустите Blockbench чтобы использовать Animated Java: + У вас установлены плагины, которые вызывают проблемы с Animated Java. + Пожалуйста отключите или удалите эти плагин(ы) и перезапустите Blockbench, чтобы использовать Animated Java: animated_java.popup.incompatability_popup.disable_button: Отключить плагин animated_java.popup.incompatability_popup.button.disable_all: Отключить все несовместимые плагины -animated_java.popup.incompatability_popup.button.ignore: Игнорировать и продолжить (Не Рекомендуется) +animated_java.popup.incompatability_popup.button.ignore: Игнорировать и продолжить (не рекомендуется) animated_java.plugin_dialog.incompatability_notice: |- Этот плагин вызывает проблемы с Animated Java. - Вы не сможете установить этот плагин пока Animated Java установлен. + Вы не сможете установить этот плагин, пока Animated Java установлен. ### Dialogs animated_java.dialog.reset: Сброс к настройкам по умолчанию ## About -animated_java.dialog.about.title: Про Animated Java +animated_java.dialog.about.title: О Animated Java animated_java.dialog.about.close_button: Закрыть ## Changelog -animated_java.dialog.changelog_dialog.title: Animated Java Список изменений +animated_java.dialog.changelog_dialog.title: Список изменений Animated Java ## Unexpected Error Dialog animated_java.dialog.unexpected_error.title: Произошла непредвиденная ошибка! animated_java.dialog.unexpected_error.close_button: Закрыть animated_java.dialog.unexpected_error.copy_error_message_button.message: Сообщение с ошибкой скопировано в буфер обмена! -animated_java.dialog.unexpected_error.copy_error_message_button.description: Нажмите чтобы скопировать сообщение с ошибкой. -animated_java.dialog.unexpected_error.paragraph: 'Пожалуйста сообщите об этой ошибке в нашем {0} и создайте тикет в #animated-java-support канале, или создав задачу на нашем {1}. Спасибо!' +animated_java.dialog.unexpected_error.copy_error_message_button.description: Нажмите, чтобы скопировать сообщение с ошибкой. +animated_java.dialog.unexpected_error.paragraph: 'Пожалуйста, сообщите об этой ошибке в нашем {0}, создав тикет в #animated-java-support канале, или создав задачу на нашем {1}. Спасибо!' ## Blueprint Settings Dialog animated_java.dialog.blueprint_settings.title: Настройки чертежа animated_java.dialog.blueprint_settings.project_settings.title: Проект -animated_java.dialog.blueprint_settings.advanced_settings_warning: Продвинутые настройки должны быть использованы только при необходимости! +animated_java.dialog.blueprint_settings.advanced_settings_warning: Расширенные настройки должны быть использованы только при необходимости! animated_java.dialog.blueprint_settings.project_name.title: Название animated_java.dialog.blueprint_settings.project_name.description: |- Имя файла чертежа. - Это будет перезаписано, если вы сохраните чертёж по другим именем. + Это будет перезаписано, если вы сохраните чертёж под другим именем. -animated_java.dialog.blueprint_settings.texture_size.title: Размер Текстуры +animated_java.dialog.blueprint_settings.texture_size.title: Размер текстуры animated_java.dialog.blueprint_settings.texture_size.description: |- Разрешение редактора UV. - Это должно соответствовать размеру самой большой текстуры в вашем чертеже. -animated_java.dialog.blueprint_settings.texture_size.warning.not_square: Размер Текстуры должен быть квадратным! + Должно соответствовать размеру самой большой текстуры в вашем чертеже. +animated_java.dialog.blueprint_settings.texture_size.warning.not_square: Размер текстуры должен быть квадратным! animated_java.dialog.blueprint_settings.texture_size.warning.not_a_power_of_2: Размер текстуры должен быть степенью числа 2, если это возможно! -animated_java.dialog.blueprint_settings.texture_size.warning.does_not_match_largest_texture: Выбранный Размер Текстуры не соответствует размеру самой большой текстуры в вашем чертеже! +animated_java.dialog.blueprint_settings.texture_size.warning.does_not_match_largest_texture: Выбранный размер текстуры не соответствует размеру самой большой текстуры в вашем чертеже! # Export Settings animated_java.dialog.blueprint_settings.export_settings.title: Экспорт animated_java.dialog.blueprint_settings.export_namespace.title: Пространство имен animated_java.dialog.blueprint_settings.export_namespace.description: |- - Пространство имён которое будет использовано при генерации Ресурспака и Датапака. + Пространство имён, которое будет использовано при генерации ресурспака и датапака. - Рекомендовано использовать уникальное пространство имён чтобы избежать конфликтов с другими Ресурспаками и Датапаками. + Рекомендовано использовать уникальное пространство имён, чтобы избежать конфликтов с другими ресурспаками и датапаками. animated_java.dialog.blueprint_settings.export_namespace.error.empty: Пространство имён экспорта не может быть пустым! animated_java.dialog.blueprint_settings.export_namespace.error.reserved: Пространство имён экспорта "{0}" зарезервировано для внутренней работы плагина! Пожалуйста выберите другое пространство имён. animated_java.dialog.blueprint_settings.export_namespace.error.invalid_characters: Пространство имён экспорта имеет запрещённые символы! Пространства имён могут содержать только буквы, цифры и нижние подчёркивания. -animated_java.dialog.blueprint_settings.show_render_box.title: Показать Область Рендера -animated_java.dialog.blueprint_settings.show_render_box.description: Когда включено, отображает рендер коробку в редакторе. +animated_java.dialog.blueprint_settings.show_render_box.title: Показать область рендера +animated_java.dialog.blueprint_settings.show_render_box.description: Когда включено, отображает область рендера в редакторе. -animated_java.dialog.blueprint_settings.auto_render_box.title: Авто-размер Области Рендера +animated_java.dialog.blueprint_settings.auto_render_box.title: Авто-размер области рендера animated_java.dialog.blueprint_settings.auto_render_box.description: |- Когда включено, область рендера будет автоматически рассчитываться на основе геометрии модели. -animated_java.dialog.blueprint_settings.render_box.title: Размер Области Рендера +animated_java.dialog.blueprint_settings.render_box.title: Размер области рендера animated_java.dialog.blueprint_settings.render_box.description: |- - [Размер и Высота](https://minecraft.wiki/w/Display#Entity_data) дисплей сущностей рига. + [Размер и высота](https://minecraft.wiki/w/Display#Entity_data) дисплей-сущностей рига. animated_java.dialog.blueprint_settings.view_range.title: Область обзора animated_java.dialog.blueprint_settings.view_range.description: |- [Область обзора](https://minecraft.wiki/w/Display#Entity_data) рига по умолчанию. -animated_java.dialog.blueprint_settings.enable_plugin_mode.title: Режим Плагина +animated_java.dialog.blueprint_settings.enable_plugin_mode.title: Режим плагина animated_java.dialog.blueprint_settings.enable_plugin_mode.description: |- - Когда включено, проект будет экспортирован как JSON файл специально для использования плагинами. + Когда включено, проект будет экспортирован как JSON-файл для использования в плагинах. -animated_java.dialog.blueprint_settings.resource_pack_export_mode.title: Режим Экспорта Ресурспака +animated_java.dialog.blueprint_settings.resource_pack_export_mode.title: Режим экспорта ресурспака animated_java.dialog.blueprint_settings.resource_pack_export_mode.options.folder: Папка -animated_java.dialog.blueprint_settings.resource_pack_export_mode.options.zip: Zip Архив +animated_java.dialog.blueprint_settings.resource_pack_export_mode.options.zip: ZIP-архив animated_java.dialog.blueprint_settings.resource_pack_export_mode.options.none: Ничего -animated_java.dialog.blueprint_settings.data_pack_export_mode.title: Режим Экспорта Датапака +animated_java.dialog.blueprint_settings.data_pack_export_mode.title: Режим экспорта датапака animated_java.dialog.blueprint_settings.data_pack_export_mode.options.folder: Папка -animated_java.dialog.blueprint_settings.data_pack_export_mode.options.zip: Zip Архив +animated_java.dialog.blueprint_settings.data_pack_export_mode.options.zip: ZIP-архив animated_java.dialog.blueprint_settings.data_pack_export_mode.options.none: Ничего -animated_java.dialog.blueprint_settings.target_minecraft_version.title: Выбранная Майнкрафт версия +animated_java.dialog.blueprint_settings.target_minecraft_version.title: Выбранная версия Minecraft animated_java.dialog.blueprint_settings.target_minecraft_version.description: |- - Версия Майнкрафта для которой будет экспортирован проект. + Версия Minecraft, для которой будет экспортирован проект. - Если версия которую вы используете недоступна, выберите ближайшую версию ниже нужной. + Если версия, которую вы используете недоступна, выберите ближайшую версию ниже нужной. - Т.е Если вы используете `1.21.8`, выберите `1.21.5`. + Т. е. Если вы используете `1.21.8`, выберите `1.21.5`. Некоторые функции могут быть изменены, или недоступны в зависимости от выбранной версии. @@ -142,28 +142,28 @@ animated_java.dialog.blueprint_settings.target_minecraft_version.description: |- # Resource Pack Settings animated_java.dialog.blueprint_settings.resource_pack_settings.title: Ресурспак -animated_java.dialog.blueprint_settings.enable_advanced_resource_pack_settings.title: Продвинутые настройки +animated_java.dialog.blueprint_settings.enable_advanced_resource_pack_settings.title: Расширенные настройки animated_java.dialog.blueprint_settings.enable_advanced_resource_pack_folders.title: Продвинутые папки -animated_java.dialog.blueprint_settings.display_item.title: Предмет дисплей +animated_java.dialog.blueprint_settings.display_item.title: Предмет-дисплей animated_java.dialog.blueprint_settings.display_item.description: |- - Предмет используемый для моделей рига. + Предмет, используемый для моделей рига. Множество чертежей в одном и том же проекте могут использовать один и тот же предмет, и они будут автоматически объединены при экспорте. animated_java.dialog.blueprint_settings.display_item.error.no_item_selected: Не выбран предмет! animated_java.dialog.blueprint_settings.display_item.error.invalid_item_id.no_namespace: Выбран недопустимый ID предмета! ID предметов должны быть в формате`namespace:item_id`. animated_java.dialog.blueprint_settings.display_item.error.invalid_item_id.whitespace: Выбран недопустимый ID предмета! ID предметов не должны содержать пробелов. -animated_java.dialog.blueprint_settings.display_item.warning.item_does_not_exist: Выбранный предмет не существует в ванильной игре! +animated_java.dialog.blueprint_settings.display_item.warning.item_does_not_exist: Выбранный предмет не существует в базовой игре! animated_java.dialog.blueprint_settings.display_item.warning.item_model_not_generated: Выбранный предмет не использует `minecraft:item/generated` в форме своего родителя. Это может привести к проблемам в игре. animated_java.dialog.blueprint_settings.display_item.error.item_model_not_found: |- Выбранный предмет не имеет файла модели в ванильном ресурспаке! Если вы предполагаете, что это ошибка, попробуйте перезапустить Blockbench, и подождать пока загрузка AJ пропадёт перед открытием чертежа. -animated_java.dialog.blueprint_settings.custom_model_data_offset.title: CMD Смещение +animated_java.dialog.blueprint_settings.custom_model_data_offset.title: Смещение CMD animated_java.dialog.blueprint_settings.custom_model_data_offset.description: |- - Смещает Custom Model Data (CMD) значения использованные в предикатах предмет дисплея на указанное значение. + Смещает значения Custom Model Data (CMD), используемые в предикатах предмет-дисплея, на указанное значение. animated_java.dialog.blueprint_settings.resource_pack.title: Ресурспак animated_java.dialog.blueprint_settings.resource_pack.description: |- @@ -176,8 +176,8 @@ animated_java.dialog.blueprint_settings.resource_pack.error.folder_does_not_exis animated_java.dialog.blueprint_settings.resource_pack.error.not_a_folder: Указанный путь не является папкой! animated_java.dialog.blueprint_settings.resource_pack.error.missing_pack_mcmeta: Выбранная папка не содержит файл pack.mcmeta! -animated_java.dialog.blueprint_settings.resource_pack_zip.title: Ресурспак Zip Архив -animated_java.dialog.blueprint_settings.resource_pack_zip.description: Путь к .zip архиву для экспорта. +animated_java.dialog.blueprint_settings.resource_pack_zip.title: Ресурспак ZIP-архив +animated_java.dialog.blueprint_settings.resource_pack_zip.description: Путь к .ZIP-архиву для экспорта. animated_java.dialog.blueprint_settings.resource_pack_zip.error.no_file_selected: Файл не выбран! animated_java.dialog.blueprint_settings.resource_pack_zip.error.not_a_file: Указанный путь не является файлом! @@ -186,7 +186,7 @@ animated_java.dialog.blueprint_settings.data_pack_settings.title: Датапак animated_java.dialog.blueprint_settings.data_pack.title: Датапак animated_java.dialog.blueprint_settings.data_pack.description: |- - Основная папка Датапака для экспорта. + Основная папка датапака для экспорта. Выбранный датапак должен иметь файл `pack.mcmeta`. animated_java.dialog.blueprint_settings.data_pack.warning.missing_data_folder: Выбранный датапак не содержит папку `data`! @@ -195,59 +195,59 @@ animated_java.dialog.blueprint_settings.data_pack.error.folder_does_not_exist: animated_java.dialog.blueprint_settings.data_pack.error.not_a_folder: Указанный путь не является папкой! animated_java.dialog.blueprint_settings.data_pack.error.missing_pack_mcmeta: Выбранная папка не содержит файл pack.mcmeta! -animated_java.dialog.blueprint_settings.data_pack_zip.title: Датапак Zip Архив -animated_java.dialog.blueprint_settings.data_pack_zip.description: Путь к .zip архиву для экспорта. +animated_java.dialog.blueprint_settings.data_pack_zip.title: Датапак ZIP-архив +animated_java.dialog.blueprint_settings.data_pack_zip.description: Путь к .ZIP-архиву для экспорта. animated_java.dialog.blueprint_settings.data_pack_zip.error.no_file_selected: Файл не выбран! animated_java.dialog.blueprint_settings.data_pack_zip.error.not_a_file: Указанный путь не является файлом! animated_java.dialog.blueprint_settings.on_summon_function.title: Функция при вызове animated_java.dialog.blueprint_settings.on_summon_function.description: |- - Команды выполняемые `as` (от лица) и `at` (на месте) сущности при вызове. + Команды, выполняемые `as` (от лица) и `at` (на месте) сущности при вызове. Воспринимайте это поле как `.mcfunction` файл. - Поддерживает [MC-Build](https://mcbuild.dev) синтакс. + Поддерживает [MC-Build](https://mcbuild.dev) синтаксис. animated_java.dialog.blueprint_settings.on_remove_function.title: Функция при удалении animated_java.dialog.blueprint_settings.on_remove_function.description: |- - Команды выполняемые `as` (от лица) и `at` (на месте) сущности при удалении. + Команды, выполняемые `as` (от лица) и `at` (на месте) сущности при удалении. Воспринимайте это поле как `.mcfunction` файл. - Поддерживает [MC-Build](https://mcbuild.dev) синтакс. + Поддерживает [MC-Build](https://mcbuild.dev) синтаксис. animated_java.dialog.blueprint_settings.on_pre_tick_function.title: Функция до тика animated_java.dialog.blueprint_settings.on_pre_tick_function.description: |- - Команды выполняемые `as` (от лица) и `at` (на месте) сущности *перед* тик логикой Animated Java. + Команды, выполняемые `as` (от лица) и `at` (на месте) сущности *перед* тик логикой Animated Java. Воспринимайте это поле как `.mcfunction` файл. - Поддерживает [MC-Build](https://mcbuild.dev) синтакс. + Поддерживает [MC-Build](https://mcbuild.dev) синтаксис. animated_java.dialog.blueprint_settings.on_post_tick_function.title: Функция после тика animated_java.dialog.blueprint_settings.on_post_tick_function.description: |- - Команды выполняемые `as` (от лица) и `at` (на месте) сущности *после* тик логики Animated Java. + Команды, выполняемые `as` (от лица) и `at` (на месте) сущности *после* тик логики Animated Java. Воспринимайте это поле как `.mcfunction` файл. - Поддерживает [MC-Build](https://mcbuild.dev) синтакс. + Поддерживает [MC-Build](https://mcbuild.dev) синтаксис. animated_java.dialog.blueprint_settings.interpolation_duration.title: Длительность Интерполяции animated_java.dialog.blueprint_settings.interpolation_duration.description: |- [Длительность интерполяции](https://minecraft.wiki/w/Display#Entity_data) рига по умолчанию. - Чем выше значение тем плавнее анимация, но меньше отзывчивость. + Чем выше значение, тем плавнее анимация, но меньше отзывчивость. animated_java.dialog.blueprint_settings.teleportation_duration.title: Длительность Телепортации animated_java.dialog.blueprint_settings.teleportation_duration.description: |- [Длительность телепортации](https://minecraft.wiki/w/Display#Entity_data) рига по умолчанию. - Чем выше значение тем плавнее крупные движения, но меньше отзывчивость. + Чем выше значение, тем плавнее крупные движения, но меньше отзывчивость. animated_java.dialog.blueprint_settings.auto_update_rig_orientation.title: Автообновление ориентации рига animated_java.dialog.blueprint_settings.auto_update_rig_orientation.description: |- Когда **включено**, синхронизирует позиции и повороты всех нодовых сущностей с корневой сущностью каждый тик. - Позволяет просто телепортировать сущность чтобы перемещать риг. + Позволяет просто телепортировать сущность, чтобы перемещать риг. Когда **выключено**, вам стоит использовать move функцию для перемещения рига: @@ -256,7 +256,7 @@ animated_java.dialog.blueprint_settings.auto_update_rig_orientation.description: positioned rotated run \ function animated_java:<пространство_имён>/move ``` - + Синхронизировать позиции и повороты нодов каждый тик может давать нагрузку, особенно для больших ригов. Отключение этого параметра может улучшить производительность жертвуя удобством. @@ -264,17 +264,17 @@ animated_java.dialog.blueprint_settings.use_storage_for_animation.title: Исп animated_java.dialog.blueprint_settings.use_storage_for_animation.description: |- Когда включено, NBT хранилище будет использовано для хранения данных анимации вместо функций. - Это значительно уменьшает количество файлов экспортированного датапака, будучи в разы менее производительным. + Это значительно уменьшает количество файлов экспортированного датапака, но снижает производительность. # Plugin Settings animated_java.dialog.blueprint_settings.baked_animations.title: Запечённые анимации animated_java.dialog.blueprint_settings.baked_animations.description: |- Запекать или нет экспортированные анимации. - Запечённые кадры анимаций заранее обрабатываются и сохраняются в экспортированный JSON файл, уменьшая сложности рендера модели в игре. + Запечённые кадры анимаций заранее обрабатываются и сохраняются в экспортированный JSON-файл, уменьшая сложности рендера модели в игре. Некоторые Плагины будут требовать эту опцию чтобы работать правильно. -animated_java.dialog.blueprint_settings.json_file.title: JSON Файл -animated_java.dialog.blueprint_settings.json_file.description: Путь к JSON файлу для экспорта. +animated_java.dialog.blueprint_settings.json_file.title: JSON-файл +animated_java.dialog.blueprint_settings.json_file.description: Путь к JSON-файлу для экспорта. animated_java.dialog.blueprint_settings.json_file.error.no_file_selected: Файл не выбран! animated_java.dialog.blueprint_settings.json_file.error.not_a_file: Указанный путь не является файлом! @@ -286,19 +286,19 @@ animated_java.dialog.display_entity.per_variant_options.title: Для каждо animated_java.dialog.display_entity.on_summon_function.title: Функция при вызове animated_java.dialog.display_entity.on_summon_function.description: |- - Команды выполняемые `as` (от лица) и `at` (на месте) дисплей-сущности при вызове. + Команды, выполняемые `as` (от лица) и `at` (на месте) дисплей-сущности при вызове. Воспринимайте это поле как `.mcfunction` файл. - Поддерживает [MC-Build](https://mcbuild.dev) синтакс. + Поддерживает [MC-Build](https://mcbuild.dev) синтаксис. animated_java.dialog.display_entity.on_apply_function.title: Функция при применении animated_java.dialog.display_entity.on_apply_function.description: |- - Команды выполняемые `as` (от лица) дисплей-сущности при применении варианта. + Команды, выполняемые `as` (от лица) дисплей-сущности при применении варианта. Воспринимайте это поле как `.mcfunction` файл. - Поддерживает [MC-Build](https://mcbuild.dev) синтакс. + Поддерживает [MC-Build](https://mcbuild.dev) синтаксис. animated_java.dialog.display_entity.custom_name.title: Имя animated_java.dialog.display_entity.custom_name.description: Пользовательское имя нода. @@ -316,7 +316,7 @@ animated_java.dialog.display_entity.override_glow_color.title: Собствен animated_java.dialog.display_entity.override_glow_color.description: Использовать ли собственный цвет свечения. animated_java.dialog.display_entity.glow_color.title: Цвет свечения -animated_java.dialog.display_entity.glow_color.description: Определяет окрас свечения нода. +animated_java.dialog.display_entity.glow_color.description: Определяет цвет свечения нода. animated_java.dialog.display_entity.shadow_radius.title: Радиус тени animated_java.dialog.display_entity.shadow_radius.description: Определяет радиус тени. @@ -331,7 +331,7 @@ animated_java.dialog.display_entity.brightness_override.title: Яркость animated_java.dialog.display_entity.brightness_override.description: Определяет яркость нода. Значение должно быть от 0 до 15. animated_java.dialog.display_entity.use_custom_brightness.title: Использовать собственную яркость -animated_java.dialog.display_entity.use_custom_brightness.description: Устанавливает собственную яркость нода.. +animated_java.dialog.display_entity.use_custom_brightness.description: Устанавливает собственную яркость нода. animated_java.dialog.display_entity.custom_brightness.title: Собственная яркость animated_java.dialog.display_entity.custom_brightness.description: Определяет яркость нода. Значение должно быть от 0 до 15. @@ -348,64 +348,64 @@ animated_java.dialog.display_entity.billboard.description: |- - **Фиксированный**: Вертикальные и горизонтальные оси фиксированы. - **Вертикальный**: Поворачивается по вертикали. - **Горизонтальный**: Поворачивается по горизонтали. - - **Центрированный**: Поворачивается по центру. + - **По центру**: Поворачивается по центру. animated_java.dialog.display_entity.billboard.options.fixed: Фиксированный animated_java.dialog.display_entity.billboard.options.vertical: Вертикальный animated_java.dialog.display_entity.billboard.options.horizontal: Горизонтальный -animated_java.dialog.display_entity.billboard.options.center: Центрированный +animated_java.dialog.display_entity.billboard.options.center: По центру ## Locator Config Dialog animated_java.dialog.locator_config.title: Настройки локатора animated_java.dialog.locator_config.plugin_mode_warning: |- - Режим Плагина включен! Локаторы не имеют настроек в режиме Плагина. - Вместо этого используйте Plugin API чтобы добавить функционал вашим Локаторам. - Подробнее о Plugin API можно узнать в Официальной Plugin API документации. + Режим плагина включён! Локаторы не имеют настроек в режиме плагина. + Вместо этого используйте Plugin API, чтобы добавить функциональность вашим локаторам. + Подробнее о Plugin API можно узнать в официальной документации Plugin API. animated_java.dialog.locator_config.use_entity.title: Использовать сущность animated_java.dialog.locator_config.use_entity.description: |- Когда включено, локатор создаст и будет использовать сущность вместо координат в игре. animated_java.dialog.locator_config.entity_type.title: Тип сущности -animated_java.dialog.locator_config.entity_type.description: Тип сущности который будет привязан к локатору. +animated_java.dialog.locator_config.entity_type.description: Тип сущности, который будет привязан к локатору. animated_java.dialog.locator_config.entity_type.error.empty: Тип сущности не может быть пустым! -animated_java.dialog.locator_config.entity_type.warning.invalid: Выбранный тип сущности не существует в майнкрафте {0} +animated_java.dialog.locator_config.entity_type.warning.invalid: Выбранный тип сущности не существует в Minecraft {0} -animated_java.dialog.locator_config.sync_passenger_rotation.title: Синхронизировать Поворот Пассажиров +animated_java.dialog.locator_config.sync_passenger_rotation.title: Синхронизировать поворот пассажиров animated_java.dialog.locator_config.sync_passenger_rotation.description: Автоматически синхронизирует поворот пассажиров локатора. animated_java.dialog.locator_config.on_summon_function.title: Функция при вызове animated_java.dialog.locator_config.on_summon_function.description: |- - Команды выполняемые `as` (от лица) корневой сущности и `at` (на месте) локатора при вызове. + Команды, выполняемые `as` (от лица) корневой сущности и `at` (на месте) локатора при вызове. - Поддерживает [MC-Build](https://mcbuild.dev) синтакс. + Поддерживает [MC-Build](https://mcbuild.dev) синтаксис. animated_java.dialog.locator_config.on_summon_function.description_with_use_entity: |- - Команды выполняемые `as` (от лица) корневой сущности и `at` (на месте) сущности локатора при вызове. + Команды, выполняемые `as` (от лица) корневой сущности и `at` (на месте) сущности локатора при вызове. - Поддерживает [MC-Build](https://mcbuild.dev) синтакс. + Поддерживает [MC-Build](https://mcbuild.dev) синтаксис. animated_java.dialog.locator_config.on_remove_function.title: Функция при удалении animated_java.dialog.locator_config.on_remove_function.description: |- - Команды выполняемые `as` (от лица) корневой сущности и `at` (на месте) локатора при удалении. + Команды, выполняемые `as` (от лица) корневой сущности и `at` (на месте) локатора при удалении. - Поддерживает [MC-Build](https://mcbuild.dev) синтакс. + Поддерживает [MC-Build](https://mcbuild.dev) синтаксис. animated_java.dialog.locator_config.on_remove_function.description_with_use_entity: |- - Команды выполняемые `as` (от лица) корневой сущности и `at` (на месте) сущности локатора при удалении. + Команды, выполняемые `as` (от лица) корневой сущности и `at` (на месте) сущности локатора при удалении. - Поддерживает [MC-Build](https://mcbuild.dev) синтакс. + Поддерживает [MC-Build](https://mcbuild.dev) синтаксис. animated_java.dialog.locator_config.on_tick_function.title: Тик функция animated_java.dialog.locator_config.on_tick_function.description: |- - Команды выполняемые `at` (на месте) локатора каждый тик. + Команды, выполняемые `at` (на месте) локатора каждый тик. - Поддерживает [MC-Build](https://mcbuild.dev) синтакс. + Поддерживает [MC-Build](https://mcbuild.dev) синтаксис. animated_java.dialog.locator_config.on_tick_function.description_with_use_entity: |- - Команды выполняемые `at` (на месте) сущности локатора каждый тик. + Команды, выполняемые `at` (на месте) сущности локатора каждый тик. - Поддерживает [MC-Build](https://mcbuild.dev) синтакс. + Поддерживает [MC-Build](https://mcbuild.dev) синтаксис. ## Text Display Config Dialog -animated_java.dialog.text_display_config.title: Настройка текст дисплея +animated_java.dialog.text_display_config.title: Настройка текст-дисплея animated_java.dialog.bone_config.vanilla_item_model.title: Ванильная модель предмета animated_java.dialog.bone_config.vanilla_item_model.description: |- @@ -413,20 +413,20 @@ animated_java.dialog.bone_config.vanilla_item_model.description: |- Это перезапишет существующие кубы кости. animated_java.dialog.text_display_config.use_nbt.title: Использовать NBT -animated_java.dialog.text_display_config.use_nbt.description: Использует NBT чтобы настроить текст дисплей сущность вместо настроек. +animated_java.dialog.text_display_config.use_nbt.description: Использует NBT, чтобы настроить текст-дисплей сущность вместо обычных настроек. animated_java.dialog.text_display_config.use_nbt.use_nbt_warning: Использование NBT перезапишет все другие настройки, и любые изменения не будут отображены в редакторе. Используйте только при необходимости! animated_java.dialog.text_display_config.inherit_settings.title: Наследовать настройки -animated_java.dialog.text_display_config.inherit_settings.description: Наследует настройки от родительской текст дисплей-сущности. +animated_java.dialog.text_display_config.inherit_settings.description: Наследует настройки от родительской текст-дисплей сущности. animated_java.dialog.text_display_config.glowing.title: Свечение -animated_java.dialog.text_display_config.glowing.description: Включает свечение текст дисплей-сущности в игре. +animated_java.dialog.text_display_config.glowing.description: Включает свечение текст-дисплей сущности в игре. animated_java.dialog.text_display_config.override_glow_color.title: Собственный цвет свечения animated_java.dialog.text_display_config.override_glow_color.description: Включает собственный цвет свечения в игре. animated_java.dialog.text_display_config.glow_color.title: Цвет свечения -animated_java.dialog.text_display_config.glow_color.description: Цвет свечения текст дисплей-сущности. +animated_java.dialog.text_display_config.glow_color.description: Цвет свечения текст-дисплей сущности. animated_java.dialog.text_display_config.shadow_radius.title: Радиус тени animated_java.dialog.text_display_config.shadow_radius.description: Определяет радиус тени. @@ -438,24 +438,24 @@ animated_java.dialog.text_display_config.override_brightness.title: Собств animated_java.dialog.text_display_config.override_brightness.description: Включает собственную яркость. animated_java.dialog.text_display_config.brightness_override.title: Яркость -animated_java.dialog.text_display_config.brightness_override.description: Яркость текст дисплей-сущности. Значение должно быть от 0 до 15. +animated_java.dialog.text_display_config.brightness_override.description: Яркость текст-дисплей сущности. Значение должно быть от 0 до 15. -animated_java.dialog.text_display_config.use_custom_brightness.title: Использовать Собственную Яркость +animated_java.dialog.text_display_config.use_custom_brightness.title: Использовать собственную яркость animated_java.dialog.text_display_config.use_custom_brightness.description: Включает собственную яркость для кости. animated_java.dialog.text_display_config.custom_brightness.title: Собственная яркость -animated_java.dialog.text_display_config.custom_brightness.description: Использует собственную яркость для кости. Значение должно быть от 0 до 15. +animated_java.dialog.text_display_config.custom_brightness.description: Использует собственную яркость. Значение должно быть от 0 до 15. animated_java.dialog.text_display_config.invisible.title: Невидимость -animated_java.dialog.text_display_config.invisible.description: Включать ли видимость текст дисплей-сущности в игре. +animated_java.dialog.text_display_config.invisible.description: Включать ли видимость текст-дисплей сущности в игре. animated_java.dialog.text_display_config.nbt.title: NBT -animated_java.dialog.text_display_config.nbt.description: NBT применимое к текст дисплей-сущности. +animated_java.dialog.text_display_config.nbt.description: NBT применимое к текст-дисплей сущности. ## Block Display Config Dialog -animated_java.dialog.vanilla_block_display_config.title: Настройки блок дисплея +animated_java.dialog.vanilla_block_display_config.title: Настройки блок-дисплея animated_java.dialog.vanilla_block_display.custom_name.title: Имя -animated_java.dialog.vanilla_block_display.custom_name.description: Пользовательское имя блок дисплея. +animated_java.dialog.vanilla_block_display.custom_name.description: Пользовательское имя блок-дисплея. animated_java.dialog.vanilla_block_display.custom_name.invalid_json.error: |- Неверный JSON текст! {0} @@ -464,9 +464,9 @@ animated_java.dialog.vanilla_block_display.custom_name_visible.title: Видим animated_java.dialog.vanilla_block_display.custom_name_visible.description: Включать ли видимость имени. ## Item Display Config Dialog -animated_java.dialog.vanilla_item_display_config.title: Настройки предмет дисплея +animated_java.dialog.vanilla_item_display_config.title: Настройки предмет-дисплея animated_java.dialog.vanilla_item_display.custom_name.title: Имя -animated_java.dialog.vanilla_item_display.custom_name.description: Пользовательское имя блок дисплея. +animated_java.dialog.vanilla_item_display.custom_name.description: Пользовательское имя предмет-дисплея. animated_java.dialog.vanilla_item_display.custom_name.invalid_json.error: |- Неверный JSON текст! {0} @@ -478,7 +478,7 @@ animated_java.dialog.vanilla_item_display.custom_name_visible.description: Вк animated_java.dialog.variant_config.title: Настройки вариантов animated_java.dialog.variant_config.variant_display_name: Отображаемое имя -animated_java.dialog.variant_config.variant_display_name.description: Используется в редакторе и в сообщениях ошибок. +animated_java.dialog.variant_config.variant_display_name.description: Используется в редакторе и в сообщениях об ошибках. animated_java.dialog.variant_config.generate_name_from_display_name: Сгенерировать имя из отображаемого имени animated_java.dialog.variant_config.generate_name_from_display_name.description: Автоматически генерирует имя на основе отображаемого имени. @@ -488,19 +488,19 @@ animated_java.dialog.variant_config.variant_name.description: Используе animated_java.dialog.variant_config.texture_map.title: Текстурная карта animated_java.dialog.variant_config.texture_map.description: Карта текстур, которые будут заменены при применении этого варианта. -animated_java.dialog.variant_config.texture_map.create_new_mapping: Создать новый маппинг +animated_java.dialog.variant_config.texture_map.create_new_mapping: Создать новое сопоставление animated_java.dialog.variant_config.texture_map.no_mappings: У варианта нет заменяемых текстур. animated_java.dialog.variant_config.bone_lists.description: |- - Укажите ноды которые должны быть изменены при применении варианта. + Укажите ноды, которые должны быть изменены при применении варианта. Ноды указанные в списке будут изменены при применении варианта. Ноды не указанные в списке останутся нетронутыми. animated_java.dialog.variant_config.excluded_nodes.title: Исключенные ноды -animated_java.dialog.variant_config.excluded_nodes.description: Список нодов которые вариант должен игнорировать. Эти ноды останутся нетронутыми. +animated_java.dialog.variant_config.excluded_nodes.description: Список нодов, которые вариант должен игнорировать. Эти ноды останутся нетронутыми. animated_java.dialog.variant_config.included_nodes.title: Включенные ноды -animated_java.dialog.variant_config.included_nodes.description: Список нодов которые вариант должен изменить. Только эти ноды будут изменены. +animated_java.dialog.variant_config.included_nodes.description: Список нодов, которые вариант должен изменить. Только эти ноды будут изменены. animated_java.dialog.variant_config.swap_columns_button.tooltip: Поменять местами ## Old AJModel Loader Dialog @@ -531,18 +531,18 @@ animated_java.dialog.animation_properties.loop_delay.title: Задержка ц animated_java.dialog.animation_properties.loop_delay.description: |- Задержка в тиках перед повторным запуском анимации в режиме Цикл. - Т.е. Значение 20 значит, что анимация застынет на 1 секунду перед повторным запуском. + Т. е. Значение 20 значит, что анимация застынет на 1 секунду перед повторным запуском. animated_java.dialog.animation_properties.bone_lists.description: |- - Укажите ноды которые анимация должна изменить. + Укажите ноды, которые анимация должна изменить. Ноды указанные в списке будут изменены анимацией. Ноды не указанные в списке останутся нетронутыми. animated_java.dialog.animation_properties.excluded_nodes.title: Исключенные ноды -animated_java.dialog.animation_properties.excluded_nodes.description: Список нодов которые анимация будет игнорировать. Эти ноды останутся нетронутыми. +animated_java.dialog.animation_properties.excluded_nodes.description: Список нодов, которые анимация будет игнорировать. Эти ноды останутся нетронутыми. animated_java.dialog.animation_properties.included_nodes.title: Включенные ноды -animated_java.dialog.animation_properties.included_nodes.description: Список нодов которые анимация будет изменять. Только эти ноды будут изменены. +animated_java.dialog.animation_properties.included_nodes.description: Список нодов, которые анимация будет изменять. Только эти ноды будут изменены. animated_java.dialog.animation_properties.swap_columns_button.tooltip: Поменять местами ## Export Progress Dialog @@ -594,18 +594,18 @@ animated_java.panel.keyframe.variant.description: Вариант примени animated_java.panel.keyframe.function.title: Функция animated_java.panel.keyframe.function.description: |- - Команды выполняемые при достижении ключевого кадра. + Команды, выполняемые при достижении ключевого кадра. Воспринимайте это поле как `.mcfunction` файл. - Поддерживает [MC-Build](https://mcbuild.dev) синтакс. + Поддерживает [MC-Build](https://mcbuild.dev) синтаксис. animated_java.panel.keyframe.execute_condition.title: Условие Execute animated_java.panel.keyframe.execute_condition.description: |- Условие при котором ключевой кадр будет выполнен. Воспринимайте это поле как `execute if` команду. - Т.е. `if score @s myScore matches 1..` + Т. е. `if score @s myScore matches 1..` animated_java.panel.keyframe.repeat.title: Повтор animated_java.panel.keyframe.repeat.description: |- @@ -617,7 +617,7 @@ animated_java.panel.keyframe.repeat_frequency.description: |- Поставьте 1 чтобы запускать каждый тик анимации. animated_java.panel.keyframe.easing_type.title: Тип сглаживания -animated_java.panel.keyframe.easing_type.description: The type of easing to apply to the keyframe. +animated_java.panel.keyframe.easing_type.description: Тип сглаживания, применяемый к ключевому кадру. animated_java.panel.keyframe.easing_type.options.linear: Линейное animated_java.panel.keyframe.easing_type.options.sine: Синусоидальное animated_java.panel.keyframe.easing_type.options.quad: Квадратичное @@ -637,7 +637,7 @@ animated_java.panel.keyframe.easing_mode.options.out: В конце animated_java.panel.keyframe.easing_mode.options.inout: В начале и в конце animated_java.panel.keyframe.easing_args.title: Аргументы сглаживания -animated_java.panel.keyframe.easing_args.description: Агрументы применимые к функции сглаживания. +animated_java.panel.keyframe.easing_args.description: Аргументы, применимые к функции сглаживания. animated_java.panel.keyframe.easing_args.easing_arg.elastic.title: Эластичность animated_java.panel.keyframe.easing_args.easing_arg.elastic.description: Эластичность функции сглаживания. animated_java.panel.keyframe.easing_args.easing_arg.back.title: Перерастяжение @@ -646,17 +646,17 @@ animated_java.panel.keyframe.easing_args.easing_arg.bounce.title: Прыгуче animated_java.panel.keyframe.easing_args.easing_arg.bounce.description: Определяет степень прыгучести функции сглаживания. animated_java.panel.keyframe.nonlinear_interpolation: |- - Продвинутые настройки сглаживания отключены. + Расширенные настройки сглаживания отключены. Смените режим интерполяции ключевого кадра на линейный для включения. # Text Display Panel -animated_java.panel.text_display.title: Текст дисплей +animated_java.panel.text_display.title: Текст-дисплей animated_java.tool.text_display.line_width.title: Длина строки -animated_java.tool.text_display.line_width.description: Ширина текст дисплея в пикселях. +animated_java.tool.text_display.line_width.description: Ширина текст-дисплея в пикселях. animated_java.tool.text_display.background_color.title: Цвет фона -animated_java.tool.text_display.background_color.description: Цвет фона текст дисплея. +animated_java.tool.text_display.background_color.description: Цвет фона текст-дисплея. animated_java.tool.text_display.text_shadow.title: Тень текста animated_java.tool.text_display.text_shadow.description: Включает отображение тени текста. @@ -695,7 +695,7 @@ animated_java.panel.vanilla_block_display.description: Блок, который ### Custom Elements ## Item Display -animated_java.vanilla_item_display.title: Предмет дисплей +animated_java.vanilla_item_display.title: Предмет-дисплей ## Block Display @@ -710,17 +710,17 @@ animated_java.misc.failed_to_export.button: Ок animated_java.misc.failed_to_export.invalid_rotation.message: |- Некоторые кубы в вашей модели имеют неправильные повороты! - Кубы должны иметь поворот в -45, -22.5, 0, 22.5, или 45 градусов, и могут быть повёрнуты только на одной оси при выборе Майнкрафт версий ниже 1.21.6. + Кубы должны иметь поворот в -45, -22.5, 0, 22.5, или 45 градусов, и могут быть повёрнуты только на одной оси при выборе Minecraft версий ниже 1.21.6. - Если хотите повернуть куб более точно, или на множистве осей, переместите куб в кость и поворачивайте её вместо куба. + Если хотите повернуть куб более точно, или на множестве осей, переместите куб в кость и поворачивайте её вместо куба. Все недопустимые кубы подсвечены красным. Пожалуйста исправьте их перед экспортом. animated_java.misc.failed_to_export.invalid_rotation.message_post_1_21_6: |- Некоторые кубы в вашей модели имеют неправильные повороты! - Кубы могут быть повёрнуты только на одной оси при выборе Майнкрафт версии 1.21.6 или выше. + Кубы могут быть повёрнуты только на одной оси при выборе Minecraft версии 1.21.6 или выше. - Если хотите повернуть куб на множистве осей, переместите куб в кость и поворачивайте её вместо куба. + Если хотите повернуть куб на множестве осей, переместите куб в кость и поворачивайте её вместо куба. Все недопустимые кубы подсвечены красным. Пожалуйста исправьте их перед экспортом. animated_java.misc.failed_to_export.rig_has_textures_but_no_custom_models.message: |- @@ -733,14 +733,14 @@ animated_java.misc.failed_to_export.rig_has_custom_models_but_no_textures.messag animated_java.toast.invalid_rotations: |- Неверный поворот кубов! - Кубы должны иметь поворот в -45, -22.5, 0, 22.5, или 45 градусов, и могут быть повёрнуты только на одной оси при выборе Майнкрафт версий ниже 1.21.6. + Кубы должны иметь поворот в -45, -22.5, 0, 22.5, или 45 градусов, и могут быть повёрнуты только на одной оси при выборе Minecraft версий ниже 1.21.6. Все недопустимые кубы подсвечены красным. animated_java.toast.invalid_rotations_post_1_21_6: |- Неверный поворот кубов! - Кубы могут быть повёрнуты только на одной оси при выборе Майнкрафт версии 1.21.6 и выше. + Кубы могут быть повёрнуты только на одной оси при выборе Minecraft версии 1.21.6 и выше. Все недопустимые кубы подсвечены красным. @@ -748,13 +748,13 @@ animated_java.toast.invalid_rotations_post_1_21_6: |- animated_java.format_category.animated_java: Animated Java # Model Manager Warnings -animated_java.block_model_manager.fluid_warning: Жидкости не рендерятся в блок дисплеях. -animated_java.block_model_manager.mob_head_warning: Головы мобов не рендерятся в блок дисплеях. Используйте предмет дисплей. -animated_java.block_model_manager.facing_warning: Состояние "facing" не поддерживается в блок дисплеях. +animated_java.block_model_manager.fluid_warning: Жидкости не рендерятся в блок-дисплеях. +animated_java.block_model_manager.mob_head_warning: Головы мобов не рендерятся в блок-дисплеях. Используйте предмет-дисплей. +animated_java.block_model_manager.facing_warning: Состояние "facing" не поддерживается в блок-дисплеях. # Project Errors animated_java.error.blueprint_export_path_doesnt_exist.title: Путь экспорта чертежа не существует. animated_java.error.blueprint_export_path_doesnt_exist.description: |- Путь экспорта чертежа '{0}' не существует! - Убедитесь что папка в которую происходит экспорт существует и попробуйте снова. + Убедитесь, что папка, в которую происходит экспорт, существует, и попробуйте снова. From 308a360534f0183d03d70a4074b325dfb74b6d90 Mon Sep 17 00:00:00 2001 From: Koishem Date: Wed, 25 Mar 2026 06:59:45 +0100 Subject: [PATCH 17/23] =?UTF-8?q?=F0=9F=94=A4=20Translate=20AJ=20to=20Ukra?= =?UTF-8?q?inian?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lang/uk.yml | 759 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 759 insertions(+) create mode 100644 src/lang/uk.yml diff --git a/src/lang/uk.yml b/src/lang/uk.yml new file mode 100644 index 00000000..2e07bb79 --- /dev/null +++ b/src/lang/uk.yml @@ -0,0 +1,759 @@ +animated_java.menubar.label: Animated Java + +animated_java.format.blueprint.name: Креслення + +### Actions +animated_java.action.open_blueprint_settings.name: Налаштування креслення +animated_java.action.open_documentation.name: Документація +animated_java.action.open_changelog.name: Журнал змін +animated_java.action.open_about.name: Про нас +animated_java.action.open_display_entity_config.name: Налаштування дисплей-сутностей +animated_java.action.open_locator_config.name: Налаштування локатора +animated_java.action.export.name: Експорт +animated_java.action.export_debug.name: Експорт (Налагодження) +animated_java.action.export_all.name: Експорт усього +animated_java.action.export_all.description: |- + Експортувати усі відкриті креслення. +animated_java.action.export_all_debug.name: Експорт усього (Налагодження) +animated_java.action.export_all_debug.description: |- + Експортувати усі відкриті креслення у режимі налагодження. +animated_java.action.extract.name: Екстрактувати +animated_java.action.extract.confirm: Підтвердити екстрактування +animated_java.action.create_text_display.title: Додавання текст-дисплея +animated_java.action.create_vanilla_item_display.title: Додавання предмет-дисплея +animated_java.action.create_vanilla_block_display.title: Додавання блок-дисплея +animated_java.action.copy_display_entity_config.name: Копіювати налаштування дисплей-сутності +animated_java.action.copy_display_entity_config.message: Скопійовано налаштування дисплей-сутності з "{0}" +animated_java.action.paste_display_entity_config.name: Вставити налаштування дисплей-сутності +animated_java.action.paste_display_entity_config.message: Вставлено налаштування дисплей-сутності з "{0}" + +### Popups +animated_java.popup.loading.loading: Завантаження Animated Java... +animated_java.popup.loading.success: Animated Java завантажено успішно! +animated_java.popup.loading.offline: |- + Не вдалося підключити Animated Java! + Деякі функції можуть бути недоступними. + +animated_java.popup.installed_popup.title: Дякуємо за встановлення! +animated_java.popup.installed_popup.close_button: Почнемо анімувати! + +animated_java.popup.incompatability_popup.title: Виявлено несумісність Animated Java +animated_java.popup.incompatability_popup.description: |- + У вас встановлені плагіни, які спричиняють проблеми з Animated Java. + Будь ласка, вимкніть або видаліть ці плагіни та перезапустіть Blockbench, щоб використовувати Animated Java: +animated_java.popup.incompatability_popup.disable_button: Вимкнути плагін +animated_java.popup.incompatability_popup.button.disable_all: Вимкнути усі несумісні плагіни +animated_java.popup.incompatability_popup.button.ignore: Ігнорувати та продовжити (не рекомендується) +animated_java.plugin_dialog.incompatability_notice: |- + Цей плагін спричиняє проблеми з Animated Java. + + Ви не зможете встановити цей плагін, доки встановлено Animated Java. + +### Dialogs +animated_java.dialog.reset: Скинути до заводських налаштувань + +## About +animated_java.dialog.about.title: Про Animated Java +animated_java.dialog.about.close_button: Закрити + +## Changelog +animated_java.dialog.changelog_dialog.title: Animated Java Журнал змін + +## Unexpected Error Dialog +animated_java.dialog.unexpected_error.title: Сталася непередбачена помилка! +animated_java.dialog.unexpected_error.close_button: Закрити +animated_java.dialog.unexpected_error.copy_error_message_button.message: Повідомлення про помилку скопійовано до буфера обміну! +animated_java.dialog.unexpected_error.copy_error_message_button.description: Натисніть, щоб скопіювати повідомлення про помилку. +animated_java.dialog.unexpected_error.paragraph: 'Будь ласка, повідомте про цю помилку в нашому {0} та створіть квиток у каналі #animated-java-support або створіть завдання на нашому {1}. Дякуємо!' +## Blueprint Settings Dialog +animated_java.dialog.blueprint_settings.title: Налаштування креслення +animated_java.dialog.blueprint_settings.project_settings.title: Проєкт + +animated_java.dialog.blueprint_settings.advanced_settings_warning: Розширені налаштування слід використовувати лише за необхідності! + +animated_java.dialog.blueprint_settings.project_name.title: Назва +animated_java.dialog.blueprint_settings.project_name.description: |- + Назва файлу креслення. + + Це буде перезаписано, якщо ви збережете креслення під іншою назвою. + +animated_java.dialog.blueprint_settings.texture_size.title: Розмір текстури +animated_java.dialog.blueprint_settings.texture_size.description: |- + Роздільна здатність редактора UV. + + Це має відповідати розміру найбільшої текстури у вашому кресленні. +animated_java.dialog.blueprint_settings.texture_size.warning.not_square: Розмір текстури має бути квадратним! +animated_java.dialog.blueprint_settings.texture_size.warning.not_a_power_of_2: Розмір текстури має бути степенем числа 2, якщо це можливо! +animated_java.dialog.blueprint_settings.texture_size.warning.does_not_match_largest_texture: Обраний розмір текстури не відповідає розміру найбільшої текстури у вашому кресленні! + +# Export Settings +animated_java.dialog.blueprint_settings.export_settings.title: Експорт + +animated_java.dialog.blueprint_settings.export_namespace.title: Простір імен +animated_java.dialog.blueprint_settings.export_namespace.description: |- + Простір імен, який буде використано під час генерації ресурспаку та датапаку. + + Рекомендується використовувати унікальний простір імен, щоб уникнути конфліктів з іншими ресурспаками та датапаками. +animated_java.dialog.blueprint_settings.export_namespace.error.empty: Простір імен експорту не може бути порожнім! +animated_java.dialog.blueprint_settings.export_namespace.error.reserved: Простір імен експорту "{0}" зарезервовано для внутрішньої роботи плагіна! Будь ласка, виберіть інший простір імен. +animated_java.dialog.blueprint_settings.export_namespace.error.invalid_characters: Простір імен експорту містить заборонені символи! Простори імен можуть містити лише літери, цифри та нижнє підкреслення. + +animated_java.dialog.blueprint_settings.show_render_box.title: Показати область рендерингу +animated_java.dialog.blueprint_settings.show_render_box.description: Якщо увімкнено, відображає коробку рендерингу в редакторі. + +animated_java.dialog.blueprint_settings.auto_render_box.title: Авто-розмір області рендерингу +animated_java.dialog.blueprint_settings.auto_render_box.description: |- + Якщо увімкнено, область рендерингу автоматично розраховуватиметься на основі геометрії моделі. +animated_java.dialog.blueprint_settings.render_box.title: Розмір області рендерингу +animated_java.dialog.blueprint_settings.render_box.description: |- + [Розмір та Висота](https://minecraft.wiki/w/Display#Entity_data) дисплей сутності рига. + +animated_java.dialog.blueprint_settings.view_range.title: Область огляду +animated_java.dialog.blueprint_settings.view_range.description: |- + [Область огляду](https://minecraft.wiki/w/Display#Entity_data) рига за замовчуванням. + +animated_java.dialog.blueprint_settings.enable_plugin_mode.title: Режим плагіна +animated_java.dialog.blueprint_settings.enable_plugin_mode.description: |- + Якщо увімкнено, проєкт буде експортований у вигляді файлу JSON спеціально для використання плагінами. + +animated_java.dialog.blueprint_settings.resource_pack_export_mode.title: Режим експорту ресурспаку +animated_java.dialog.blueprint_settings.resource_pack_export_mode.options.folder: Папка +animated_java.dialog.blueprint_settings.resource_pack_export_mode.options.zip: ZIP-архів +animated_java.dialog.blueprint_settings.resource_pack_export_mode.options.none: Нічого + +animated_java.dialog.blueprint_settings.data_pack_export_mode.title: Режим експорту датапака +animated_java.dialog.blueprint_settings.data_pack_export_mode.options.folder: Папка +animated_java.dialog.blueprint_settings.data_pack_export_mode.options.zip: ZIP-архів +animated_java.dialog.blueprint_settings.data_pack_export_mode.options.none: Нічого + +animated_java.dialog.blueprint_settings.target_minecraft_version.title: Вибрана Minecraft версія +animated_java.dialog.blueprint_settings.target_minecraft_version.description: |- + Версія Minecraft, для якої буде експортовано проєкт. + + Якщо версія, яку ви використовуєте, недоступна, виберіть найближчу версію нижче потрібної. + + Тобто, якщо ви використовуєте версію `1.21.8`, виберіть `1.21.5`. + + Деякі функції можуть бути змінені або недоступні залежно від обраної версії. + + Animated Java повідомить вас про будь-які зміни, які будуть необхідні. + +# Resource Pack Settings +animated_java.dialog.blueprint_settings.resource_pack_settings.title: Ресурспак + +animated_java.dialog.blueprint_settings.enable_advanced_resource_pack_settings.title: Розширені налаштування + +animated_java.dialog.blueprint_settings.enable_advanced_resource_pack_folders.title: Розширені папки + +animated_java.dialog.blueprint_settings.display_item.title: Предмет-дисплей +animated_java.dialog.blueprint_settings.display_item.description: |- + Предмет, що використовується для моделей рига. + + Багато креслень в одному й тому ж проєкті можуть використовувати один і той самий об’єкт, і вони будуть автоматично об’єднані під час експорту. +animated_java.dialog.blueprint_settings.display_item.error.no_item_selected: Предмет не обрано! +animated_java.dialog.blueprint_settings.display_item.error.invalid_item_id.no_namespace: Вибрано неприпустимий ID предмета! ID предметів повинні бути у форматі `namespace:item_id`. +animated_java.dialog.blueprint_settings.display_item.error.invalid_item_id.whitespace: Вибрано неприпустимий ID предмета! ID предметів не повинні містити пробілів. +animated_java.dialog.blueprint_settings.display_item.warning.item_does_not_exist: Обраний предмет не існує у базовій версії гри! +animated_java.dialog.blueprint_settings.display_item.warning.item_model_not_generated: Вибраний предмет не використовує `minecraft:item/generated` як свій батьківський предмет. Це може спричинити проблеми в грі. +animated_java.dialog.blueprint_settings.display_item.error.item_model_not_found: |- + Вибраний предмет не має файлу моделі у базовому ресурспаку! + + Якщо ви вважаєте, що це помилка, спробуйте перезапустити Blockbench і почекати, поки завантаження AJ завершиться, перш ніж відкривати креслення. + +animated_java.dialog.blueprint_settings.custom_model_data_offset.title: CMD Зсув +animated_java.dialog.blueprint_settings.custom_model_data_offset.description: |- + Зміщує значення Custom Model Data (CMD), що використовуються в предикатах об’єкта відображення, на вказане значення. + +animated_java.dialog.blueprint_settings.resource_pack.title: Ресурспак +animated_java.dialog.blueprint_settings.resource_pack.description: |- + Основна папка ресурспаку для експорту. + + Обраний ресурспак повинен містити файл `pack.mcmeta`. +animated_java.dialog.blueprint_settings.resource_pack.warning.missing_assets_folder: Обраний ресурспак не містить папку `assets`! +animated_java.dialog.blueprint_settings.resource_pack.error.no_folder_selected: Папка не обрана! +animated_java.dialog.blueprint_settings.resource_pack.error.folder_does_not_exist: Вибрана папка не існує! +animated_java.dialog.blueprint_settings.resource_pack.error.not_a_folder: Вказаний шлях не є папкою! +animated_java.dialog.blueprint_settings.resource_pack.error.missing_pack_mcmeta: У вибраній папці немає файлу pack.mcmeta! + +animated_java.dialog.blueprint_settings.resource_pack_zip.title: Ресурспак ZIP-архів +animated_java.dialog.blueprint_settings.resource_pack_zip.description: Шлях до архіву .zip для експорту. +animated_java.dialog.blueprint_settings.resource_pack_zip.error.no_file_selected: Файл не обрано! +animated_java.dialog.blueprint_settings.resource_pack_zip.error.not_a_file: Вказаний шлях не є файлом! + +# Data Pack Settings +animated_java.dialog.blueprint_settings.data_pack_settings.title: Датапак + +animated_java.dialog.blueprint_settings.data_pack.title: Датапак +animated_java.dialog.blueprint_settings.data_pack.description: |- + Основна папка датапака для експорту. + + Обраний датапак повинен містити файл `pack.mcmeta`. +animated_java.dialog.blueprint_settings.data_pack.warning.missing_data_folder: Обраний датапак не містить папку `data`! +animated_java.dialog.blueprint_settings.data_pack.error.no_folder_selected: Папка не обрана! +animated_java.dialog.blueprint_settings.data_pack.error.folder_does_not_exist: Вибрана папка не існує! +animated_java.dialog.blueprint_settings.data_pack.error.not_a_folder: Вказаний шлях не є папкою! +animated_java.dialog.blueprint_settings.data_pack.error.missing_pack_mcmeta: У вибраній папці немає файлу pack.mcmeta! + +animated_java.dialog.blueprint_settings.data_pack_zip.title: Датапак ZIP-архів +animated_java.dialog.blueprint_settings.data_pack_zip.description: Шлях до архіву .zip для експорту. +animated_java.dialog.blueprint_settings.data_pack_zip.error.no_file_selected: Файл не обрано! +animated_java.dialog.blueprint_settings.data_pack_zip.error.not_a_file: Вказаний шлях не є файлом! + +animated_java.dialog.blueprint_settings.on_summon_function.title: Функція при виклику +animated_java.dialog.blueprint_settings.on_summon_function.description: |- + Команди, що виконуються `as` (від імені) та `at` (на місці) сутності під час виклику. + + Сприймайте це поле як файл `.mcfunction`. + + Підтримує синтаксис [MC-Build](https://mcbuild.dev). + +animated_java.dialog.blueprint_settings.on_remove_function.title: Функція під час видалення +animated_java.dialog.blueprint_settings.on_remove_function.description: |- + Команди, що виконуються `as` (від імені) та `at` (на місці) сутності під час видалення. + + Сприймайте це поле як файл `.mcfunction`. + + Підтримує синтаксис [MC-Build](https://mcbuild.dev). + +animated_java.dialog.blueprint_settings.on_pre_tick_function.title: Функція до тіка +animated_java.dialog.blueprint_settings.on_pre_tick_function.description: |- + Команди, що виконуються `as` (від імені) та `at` (на місці) сутності *перед* тік-логікою Animated Java. + + Сприймайте це поле як файл `.mcfunction`. + + Підтримує синтаксис [MC-Build](https://mcbuild.dev). + +animated_java.dialog.blueprint_settings.on_post_tick_function.title: Функція після тіка +animated_java.dialog.blueprint_settings.on_post_tick_function.description: |- + Команди, що виконуються `as` (від імені) та `at` (на місці) сутності *після* тік-логіки Animated Java. + + Сприймайте це поле як файл `.mcfunction`. + + Підтримує синтаксис [MC-Build](https://mcbuild.dev). + +animated_java.dialog.blueprint_settings.interpolation_duration.title: Тривалість інтерполяції +animated_java.dialog.blueprint_settings.interpolation_duration.description: |- + [Тривалість інтерполяції](https://minecraft.wiki/w/Display#Entity_data) рига за замовчуванням. + + Чим вище значення, тим плавніша анімація, але менша чутливість. + +animated_java.dialog.blueprint_settings.teleportation_duration.title: Тривалість телепортації +animated_java.dialog.blueprint_settings.teleportation_duration.description: |- + [Тривалість телепортації](https://minecraft.wiki/w/Display#Entity_data) рига за замовчуванням. + + Чим вище значення, тим плавніші великі рухи, але менша чутливість. + +animated_java.dialog.blueprint_settings.auto_update_rig_orientation.title: Автоматичне оновлення орієнтації рига +animated_java.dialog.blueprint_settings.auto_update_rig_orientation.description: |- + Коли **увімкнено**, синхронізує позиції та обертання всіх нодових об’єктів із кореневим об’єктом щотика. + Дозволяє просто телепортувати сутність, щоб перемістити риг. + + Коли **вимкнено**, вам слід використовувати функцію move для переміщення рига: + + ``` + execute as @e[tag=aj.<простір_імен>_root] \ + positioned rotated run \ + function animated_java:<простір_імен>/move + ``` + + Синхронізація позицій і обертання нодів щотика може створювати навантаження, особливо для великих ригів. + Вимкнення цього параметра може підвищити продуктивність, але за рахунок зручності. + +animated_java.dialog.blueprint_settings.use_storage_for_animation.title: Використовувати сховище для анімації +animated_java.dialog.blueprint_settings.use_storage_for_animation.description: |- + Якщо увімкнено, сховище NBT використовуватиметься для зберігання даних анімації замість функцій. + + Це значно зменшує обсяг експортованого датапаку, хоча й є набагато менш ефективним. + +# Plugin Settings +animated_java.dialog.blueprint_settings.baked_animations.title: Запечені анімації +animated_java.dialog.blueprint_settings.baked_animations.description: |- + Чи запікати експортовані анімації. + Запечені кадри анімації заздалегідь обробляються та зберігаються в експортованому файлі JSON, що зменшує складність рендерингу моделі в грі. + Деякі плагіни потребуватимуть цю опцію для коректної роботи. + +animated_java.dialog.blueprint_settings.json_file.title: JSON-файл +animated_java.dialog.blueprint_settings.json_file.description: Шлях до JSON-файлу для експорту. +animated_java.dialog.blueprint_settings.json_file.error.no_file_selected: Файл не обрано! +animated_java.dialog.blueprint_settings.json_file.error.not_a_file: Вказаний шлях не є файлом! + +## Bone Config Dialog +animated_java.dialog.display_entity.title: Налаштування дисплей сутності для "{0}" + +animated_java.dialog.display_entity.node_options.title: Нод +animated_java.dialog.display_entity.per_variant_options.title: Для кожного варіанту + +animated_java.dialog.display_entity.on_summon_function.title: Функція при виклику +animated_java.dialog.display_entity.on_summon_function.description: |- + Команди, що виконуються `as` (від імені) та `at` (на місці) дисплей сутності під час виклику. + + Сприймайте це поле як файл `.mcfunction`. + + Підтримує синтаксис [MC-Build](https://mcbuild.dev). + +animated_java.dialog.display_entity.on_apply_function.title: Функція при застосуванні +animated_java.dialog.display_entity.on_apply_function.description: |- + Команди, що виконуються `as` (від імені) сутності дисплея під час застосування варіанту. + + Сприймайте це поле як файл `.mcfunction`. + + Підтримує синтаксис [MC-Build](https://mcbuild.dev). + +animated_java.dialog.display_entity.custom_name.title: Назва +animated_java.dialog.display_entity.custom_name.description: Користувацька назва нода. +animated_java.dialog.display_entity.custom_name.invalid_json.error: |- + Неправильний текст JSON! + {0} + +animated_java.dialog.display_entity.custom_name_visible.title: Видимість імені +animated_java.dialog.display_entity.custom_name_visible.description: Чи вмикати відображення імені. + +animated_java.dialog.display_entity.glowing.title: Свічення +animated_java.dialog.display_entity.glowing.description: Чи вмикати світіння нода в грі. + +animated_java.dialog.display_entity.override_glow_color.title: Користувацький колір свічення +animated_java.dialog.display_entity.override_glow_color.description: Чи використовувати користувацький колір світіння. + +animated_java.dialog.display_entity.glow_color.title: Колір світіння +animated_java.dialog.display_entity.glow_color.description: Визначає колір світіння нода. + +animated_java.dialog.display_entity.shadow_radius.title: Радіус тіні +animated_java.dialog.display_entity.shadow_radius.description: Визначає радіус тіні. + +animated_java.dialog.display_entity.shadow_strength.title: Інтенсивність тіні +animated_java.dialog.display_entity.shadow_strength.description: Визначає інтенсивність тіні. + +animated_java.dialog.display_entity.override_brightness.title: Користувацька яскравість +animated_java.dialog.display_entity.override_brightness.description: Встановлює користувацьку яскравість нода. + +animated_java.dialog.display_entity.brightness_override.title: Яскравість +animated_java.dialog.display_entity.brightness_override.description: Визначає яскравість нода. Значення має бути від 0 до 15. + +animated_java.dialog.display_entity.use_custom_brightness.title: Використовувати користувацьку яскравість +animated_java.dialog.display_entity.use_custom_brightness.description: Встановлює користувацьку яскравість нода. + +animated_java.dialog.display_entity.custom_brightness.title: Користувацька яскравість +animated_java.dialog.display_entity.custom_brightness.description: Визначає яскравість нода. Значення має бути від 0 до 15. + +animated_java.dialog.display_entity.enchanted.title: Зачарований +animated_java.dialog.display_entity.enchanted.description: Чи вмикати ефект зачарованості нода. + +animated_java.dialog.display_entity.invisible.title: Невидимість +animated_java.dialog.display_entity.invisible.description: Чи робити нод невидимим у грі. + +animated_java.dialog.display_entity.billboard.title: Білборд +animated_java.dialog.display_entity.billboard.description: |- + Налаштовує обертання у бік погляду гравця під час рендерингу. + - **Фіксований**: Вертикальна та горизонтальна осі зафіксовані. + - **Вертикальний**: Повертається по вертикалі. + - **Горизонтальний**: Повертається по горизонталі. + - **По центру**: Повертається по центру. +animated_java.dialog.display_entity.billboard.options.fixed: Фіксований +animated_java.dialog.display_entity.billboard.options.vertical: Вертикальний +animated_java.dialog.display_entity.billboard.options.horizontal: Горизонтальний +animated_java.dialog.display_entity.billboard.options.center: По центру + +## Locator Config Dialog +animated_java.dialog.locator_config.title: Налаштування локатора + +animated_java.dialog.locator_config.plugin_mode_warning: |- + Режим плагіна увімкнено! Локатори не мають налаштувань у режимі плагіна. + Замість цього скористайтеся Plugin API, щоб додати функціонал до ваших локаторів. + Детальніше про Plugin API можна дізнатися в офіційній документації Plugin API. + +animated_java.dialog.locator_config.use_entity.title: Використовувати сутність +animated_java.dialog.locator_config.use_entity.description: |- + Якщо увімкнено, локатор створить і використовуватиме сутність замість координат у грі. + +animated_java.dialog.locator_config.entity_type.title: Тип сутності +animated_java.dialog.locator_config.entity_type.description: Тип сутності, який буде прив’язаний до локатора. +animated_java.dialog.locator_config.entity_type.error.empty: Тип сутності не може бути порожнім! +animated_java.dialog.locator_config.entity_type.warning.invalid: Вибраний тип сутності не існує в Minecraft {0} + +animated_java.dialog.locator_config.sync_passenger_rotation.title: Синхронізувати поворот пасажирів +animated_java.dialog.locator_config.sync_passenger_rotation.description: Автоматично синхронізує поворот пасажирів локатора. + +animated_java.dialog.locator_config.on_summon_function.title: Функція при виклику +animated_java.dialog.locator_config.on_summon_function.description: |- + Команди, що виконуються `as` (від імені) кореневої сутності та `at` (на місці) локатора під час виклику. + + Підтримує синтаксис [MC-Build](https://mcbuild.dev). +animated_java.dialog.locator_config.on_summon_function.description_with_use_entity: |- + Команди, що виконуються `as` (від імені) кореневої сутності та `at` (на місці) сутності локатора під час виклику. + + Підтримує синтаксис [MC-Build](https://mcbuild.dev). + +animated_java.dialog.locator_config.on_remove_function.title: Функція при видаленні +animated_java.dialog.locator_config.on_remove_function.description: |- + Команди, що виконуються `as` (від імені) кореневого об’єкта та `at` (на місці) локатора під час видалення. + + Підтримує синтаксис [MC-Build](https://mcbuild.dev). +animated_java.dialog.locator_config.on_remove_function.description_with_use_entity: |- + Команди, що виконуються `as` (від імені) кореневого об’єкта та `at` (на місці) об’єкта-локатора під час видалення. + + Підтримує синтаксис [MC-Build](https://mcbuild.dev). + +animated_java.dialog.locator_config.on_tick_function.title: Тік функція +animated_java.dialog.locator_config.on_tick_function.description: |- + Команди, що виконуються `at` (на місці) локатора щотика. + + Підтримує синтаксис [MC-Build](https://mcbuild.dev). +animated_java.dialog.locator_config.on_tick_function.description_with_use_entity: |- + Команди, що виконуються `at` (на місці) сутності локатора щотика. + + Підтримує синтаксис [MC-Build](https://mcbuild.dev). + +## Text Display Config Dialog +animated_java.dialog.text_display_config.title: Налаштування текст-дисплея + +animated_java.dialog.bone_config.vanilla_item_model.title: Ванільна модель предмета +animated_java.dialog.bone_config.vanilla_item_model.description: |- + Якщо предмет обрано, кістка відображатиметься як звичайний предмет. + Це перезапише існуючі куби кістки. + +animated_java.dialog.text_display_config.use_nbt.title: Використовувати NBT +animated_java.dialog.text_display_config.use_nbt.description: Використовує NBT для налаштування текст-дисплей сутності замість налаштувань. +animated_java.dialog.text_display_config.use_nbt.use_nbt_warning: Використання NBT замінить усі інші налаштування, і будь-які зміни не відображатимуться в редакторі. Використовуйте лише за потреби! + +animated_java.dialog.text_display_config.inherit_settings.title: Успадкувати налаштування +animated_java.dialog.text_display_config.inherit_settings.description: Успадковує налаштування від батьківської сутності текст-дисплея. + +animated_java.dialog.text_display_config.glowing.title: Свічення +animated_java.dialog.text_display_config.glowing.description: Вмикає підсвічування тексту в грі. + +animated_java.dialog.text_display_config.override_glow_color.title: Користувацький колір свічення +animated_java.dialog.text_display_config.override_glow_color.description: Вмикає користувацький колір свічення в грі. + +animated_java.dialog.text_display_config.glow_color.title: Колір свічення +animated_java.dialog.text_display_config.glow_color.description: Колір свічення текст-дисплей сутності. + +animated_java.dialog.text_display_config.shadow_radius.title: Радіус тіні +animated_java.dialog.text_display_config.shadow_radius.description: Визначає радіус тіні. + +animated_java.dialog.text_display_config.shadow_strength.title: Інтенсивність тіні +animated_java.dialog.text_display_config.shadow_strength.description: Визначає інтенсивність тіні. + +animated_java.dialog.text_display_config.override_brightness.title: Користувацька яскравість +animated_java.dialog.text_display_config.override_brightness.description: Вмикає користувацьку яскравість. + +animated_java.dialog.text_display_config.brightness_override.title: Яскравість +animated_java.dialog.text_display_config.brightness_override.description: Яскравість текст-дисплей сутності. Значення має бути від 0 до 15. + +animated_java.dialog.text_display_config.use_custom_brightness.title: Використовувати користувацьку яскравість +animated_java.dialog.text_display_config.use_custom_brightness.description: Вмикає власну яскравість для кістки. + +animated_java.dialog.text_display_config.custom_brightness.title: Користувацька яскравість +animated_java.dialog.text_display_config.custom_brightness.description: Використовує користувацьку яскравість текст-дисплей сутності. Значення має бути від 0 до 15. + +animated_java.dialog.text_display_config.invisible.title: Невидимість +animated_java.dialog.text_display_config.invisible.description: Чи слід увімкнути відображення тексту сутності дисплея в грі. + +animated_java.dialog.text_display_config.nbt.title: NBT +animated_java.dialog.text_display_config.nbt.description: NBT, що застосовується до текст-дисплей сутності. + +## Block Display Config Dialog +animated_java.dialog.vanilla_block_display_config.title: Налаштування блок-дисплея +animated_java.dialog.vanilla_block_display.custom_name.title: Назва +animated_java.dialog.vanilla_block_display.custom_name.description: Користувацька назва блок-дисплея. +animated_java.dialog.vanilla_block_display.custom_name.invalid_json.error: |- + Неправильний текст JSON! + {0} + +animated_java.dialog.vanilla_block_display.custom_name_visible.title: Видимість імені +animated_java.dialog.vanilla_block_display.custom_name_visible.description: Чи вмикати відображення імені. + +## Item Display Config Dialog +animated_java.dialog.vanilla_item_display_config.title: Налаштування предмет-дисплея +animated_java.dialog.vanilla_item_display.custom_name.title: Назва +animated_java.dialog.vanilla_item_display.custom_name.description: Користувацьке назва предмет-дисплея. +animated_java.dialog.vanilla_item_display.custom_name.invalid_json.error: |- + Неправильний текст JSON! + {0} + +animated_java.dialog.vanilla_item_display.custom_name_visible.title: Видимість імені +animated_java.dialog.vanilla_item_display.custom_name_visible.description: Чи слід увімкнути постійну видимість імені. + +## Variant Config Dialog +animated_java.dialog.variant_config.title: Налаштування варіантів + +animated_java.dialog.variant_config.variant_display_name: Назва для відображення +animated_java.dialog.variant_config.variant_display_name.description: Використовується в редакторі та в повідомленнях про помилки. + +animated_java.dialog.variant_config.generate_name_from_display_name: Створити назву на основі назви для відображення +animated_java.dialog.variant_config.generate_name_from_display_name.description: Автоматично генерує назва на основі назви для відображення + +animated_java.dialog.variant_config.variant_name: Назва +animated_java.dialog.variant_config.variant_name.description: Використовується в датапаку та ресурспаку. + +animated_java.dialog.variant_config.texture_map.title: Текстурна карта +animated_java.dialog.variant_config.texture_map.description: Карта текстур, які будуть замінені при застосуванні цього варіанту. +animated_java.dialog.variant_config.texture_map.create_new_mapping: Створити нове зіставлення +animated_java.dialog.variant_config.texture_map.no_mappings: У цього варіанту немає замінних текстур. + +animated_java.dialog.variant_config.bone_lists.description: |- + Вкажіть ноди, які мають бути змінені при застосуванні цього варіанту. + + Елементи, зазначені у списку, будуть змінені при застосуванні цього варіанту. + + Ноди, яких немає у списку, залишаться недоторканими. +animated_java.dialog.variant_config.excluded_nodes.title: Виключені ноди +animated_java.dialog.variant_config.excluded_nodes.description: Список нодів, які варіант повинен ігнорувати. Ці ноди залишаться недоторканими. +animated_java.dialog.variant_config.included_nodes.title: Увімкнені ноди +animated_java.dialog.variant_config.included_nodes.description: Список нодів, які варіант повинен змінити. Змінюватимуться лише ці ноди. +animated_java.dialog.variant_config.swap_columns_button.tooltip: Поміняти місцями + +## Old AJModel Loader Dialog +animated_java.action.upgrade_old_aj_model_loader.name: Оновити .ajmodel +animated_java.dialog.upgrade_old_aj_model_loader.title: Оновити .ajmodel +animated_java.action.upgrade_old_aj_model_loader.select_file: Вибрати .ajmodel файл +animated_java.action.upgrade_old_aj_model_loader.body: Оновити свої застарілі файли .ajmodel до нового формату .ajblueprint. +animated_java.action.upgrade_old_aj_model_loader.button: Виберіть файл .ajmodel для оновлення + +## Animation Properties Dialog +animated_java.dialog.animation_properties.title: Властивості анімації ({0}) + +animated_java.dialog.animation_properties.animation_name.title: Назва анімації +animated_java.dialog.animation_properties.animation_name.description: Використовується в датапаку. + +animated_java.dialog.animation_properties.loop_mode.title: Режим циклу +animated_java.dialog.animation_properties.loop_mode.description: |- + - Одноразовий: анімація відтвориться один раз, а потім повернеться до першого кадру. + - Затримка: анімація відтвориться один раз і залишиться на останньому кадрі. + - Цикл: анімація буде повторюватися нескінченно. +animated_java.dialog.animation_properties.loop_mode.options.once: Одноразовий +animated_java.dialog.animation_properties.loop_mode.options.hold: Затримка +animated_java.dialog.animation_properties.loop_mode.options.loop: Цикл +animated_java.dialog.animation_properties.animation_name.error.empty: Назва анімації не може бути порожньою! +animated_java.dialog.animation_properties.animation_name.error.invalid_characters: Назва анімації містить недопустимі символи! Назви анімацій можуть містити лише літери, цифри, підкреслення та крапки. + +animated_java.dialog.animation_properties.loop_delay.title: Затримка циклу +animated_java.dialog.animation_properties.loop_delay.description: |- + Затримка в тіках перед повторним запуском анімації в режимі "Цикл". + + Тобто значення 20 означає, що анімація зупиниться на 1 секунду перед повторним запуском. + +animated_java.dialog.animation_properties.bone_lists.description: |- + Вкажіть ноди, які має змінити анімація. + + Ноди, зазначені у списку, будуть змінені за допомогою анімації. + + Ноди, яких немає у списку, залишаться недоторканими. +animated_java.dialog.animation_properties.excluded_nodes.title: Виключені ноди +animated_java.dialog.animation_properties.excluded_nodes.description: Список нодів, які анімація ігноруватиме. Ці ноди залишаться незмінними. +animated_java.dialog.animation_properties.included_nodes.title: Увімкнені ноди +animated_java.dialog.animation_properties.included_nodes.description: Список нодів, які анімація змінюватиме. Тільки ці ноди будуть змінені. +animated_java.dialog.animation_properties.swap_columns_button.tooltip: Поміняти місцями + +## Export Progress Dialog +animated_java.dialog.export_progress.title: Експортування... + +## Blueprint Loading Dialog +animated_java.dialog.blueprint_loading.title: Завантаження креслення... + +### Panels + +## Variants Panel +animated_java.panel.variants.title: Варіанти +animated_java.panel.variants.tool.create_new_variant: Створити новий варіант +animated_java.panel.variants.tool.edit_variant: Налаштувати варіант +animated_java.panel.variants.tool.duplicate_selected_variant: Дублювати вибраний варіант +animated_java.panel.variants.tool.delete_selected_variant: Видалити вибраний варіант +animated_java.panel.variants.tool.variant_visible: Варіант обрано +animated_java.panel.variants.tool.variant_not_visible: Варіант не обрано +animated_java.panel.variants.tool.cannot_delete_default_variant: Не можна видалити варіант за замовчуванням! +animated_java.panel.variants.tool.cannot_edit_default_variant: Не вдалося змінити варіант за замовчуванням! + +animated_java.action.variants.create: Створити варіант +animated_java.action.variants.duplicate: Дублювати варіант +animated_java.action.variants.open_config: Відкрити налаштування варіанту +animated_java.action.variants.delete: Видалити варіант + +### Animator + +## Properties +animated_java.animation.excluded_nodes: Виключені ноди +animated_java.animation.invert_excluded_nodes: Інвертувати виключені ноди + +## Timeline +animated_java.effect_animator.timeline.variant: Варіант +animated_java.effect_animator.timeline.function: Функція + +## Keyframes +animated_java.effect_animator.keyframe_data_point.variant: Варіант +animated_java.effect_animator.keyframe_data_point.function: Функція +animated_java.effect_animator.keyframe_data_point.execute_condition: Умова виконання (Execute) +animated_java.effect_animator.keyframe_data_point.repeat: Повтор +animated_java.effect_animator.keyframe_data_point.repeat_frequency: Інтервал повтору + +# Keyframe Panel +animated_java.panel.keyframe.keyframe_title: Ключовий кадр ({0}) + +animated_java.panel.keyframe.variant.title: Варіант +animated_java.panel.keyframe.variant.description: Варіант, що застосовується до ключового кадру. + +animated_java.panel.keyframe.function.title: Функція +animated_java.panel.keyframe.function.description: |- + Команди, що виконуються при досягненні ключового кадру. + + Сприймайте це поле як файл `.mcfunction`. + + Підтримує синтаксис [MC-Build](https://mcbuild.dev). +animated_java.panel.keyframe.execute_condition.title: Умова виконання (Execute) +animated_java.panel.keyframe.execute_condition.description: |- + Умова, за якої буде виконано ключовий кадр. + + Сприймайте це поле як файл `.mcfunction`. + + Тобто `if score @s myScore дорівнює 1..` + +animated_java.panel.keyframe.repeat.title: Повтор +animated_java.panel.keyframe.repeat.description: |- + Якщо ця опція увімкнена, команди в цьому ключовому кадрі будуть повторюватися з заданим інтервалом. +animated_java.panel.keyframe.repeat_frequency.title: Інтервал +animated_java.panel.keyframe.repeat_frequency.description: |- + Кількість тіків між повторенням команди. + + Поставте 1, щоб запускати з кожним тіком анімації. + +animated_java.panel.keyframe.easing_type.title: Тип плавності +animated_java.panel.keyframe.easing_type.description: Тип плавності, який застосовується до ключового кадру. +animated_java.panel.keyframe.easing_type.options.linear: Лінійне +animated_java.panel.keyframe.easing_type.options.sine: Синусоїдальне +animated_java.panel.keyframe.easing_type.options.quad: Квадратичне +animated_java.panel.keyframe.easing_type.options.cubic: Кубічне +animated_java.panel.keyframe.easing_type.options.quart: Біквадратичне +animated_java.panel.keyframe.easing_type.options.quint: Бікубічне +animated_java.panel.keyframe.easing_type.options.expo: Експоненціальне +animated_java.panel.keyframe.easing_type.options.circ: Циркулярне +animated_java.panel.keyframe.easing_type.options.elastic: Еластичне +animated_java.panel.keyframe.easing_type.options.back: Відтягування +animated_java.panel.keyframe.easing_type.options.bounce: Пружинисте + +animated_java.panel.keyframe.easing_mode.title: Режим плавності +animated_java.panel.keyframe.easing_mode.description: Режим застосування плавності до ключового кадру. +animated_java.panel.keyframe.easing_mode.options.in: На початку +animated_java.panel.keyframe.easing_mode.options.out: В кінці +animated_java.panel.keyframe.easing_mode.options.inout: На початку та в кінці + +animated_java.panel.keyframe.easing_args.title: Аргументи плавності +animated_java.panel.keyframe.easing_args.description: Аргументи, що застосовуються до функції плавності. +animated_java.panel.keyframe.easing_args.easing_arg.elastic.title: Еластичність +animated_java.panel.keyframe.easing_args.easing_arg.elastic.description: Еластичність функції плавності. +animated_java.panel.keyframe.easing_args.easing_arg.back.title: Амплітуда відтягування +animated_java.panel.keyframe.easing_args.easing_arg.back.description: Визначає ступінь амплітуди відтягування функції плавності. +animated_java.panel.keyframe.easing_args.easing_arg.bounce.title: Пружність +animated_java.panel.keyframe.easing_args.easing_arg.bounce.description: Визначає ступінь пружності функції плавності. + +animated_java.panel.keyframe.nonlinear_interpolation: |- + Розширені налаштування згладжування вимкнені. + Змініть режим інтерполяції ключового кадру на лінійний, щоб увімкнути цю функцію. + +# Text Display Panel +animated_java.panel.text_display.title: Текст-дисплей + +animated_java.tool.text_display.line_width.title: Ширина рядка +animated_java.tool.text_display.line_width.description: Ширина текст-дисплея в пікселях. + +animated_java.tool.text_display.background_color.title: Колір фону +animated_java.tool.text_display.background_color.description: Колір фону текст-дисплея. + +animated_java.tool.text_display.text_shadow.title: Тінь текста +animated_java.tool.text_display.text_shadow.description: Вмикає відображення тіні тексту. + +animated_java.tool.text_display.text_alignment.title: Вирівнювання тексту +animated_java.tool.text_display.text_alignment.description: Визначає вирівнювання тексту. +animated_java.tool.text_display.text_alignment.options.left: По лівому краю +animated_java.tool.text_display.text_alignment.options.center: По центру +animated_java.tool.text_display.text_alignment.options.right: По правому краю + +animated_java.tool.text_display.see_through.title: Прозорість +animated_java.tool.text_display.see_through.description: Визначає, чи буде текст видно крізь блоки. + +animated_java.tool.text_display.copy_text.title: Копіювати експортований текстовий компонент +animated_java.tool.text_display.copy_text.description: Копіює експортований текстовий компонент у буфер обміну. +animated_java.tool.text_display.copy_text.copied: Текст скопійовано в буфер обміну! + +# Item Display Panel +animated_java.panel.vanilla_item_display.title: Предмет-дисплей +animated_java.panel.vanilla_item_display.description: Предмет, який буде відображатися. +animated_java.tool.item_display.item_display.title: Режим відображення предмета +animated_java.tool.item_display.item_display.description: Визначає, яка трансформація моделі буде застосована до предмета (як зазначено в полі «display» JSON-файлу моделі). +animated_java.tool.item_display.item_display.options.none: Ні +animated_java.tool.item_display.item_display.options.thirdperson_lefthand: Від третьої особи (ліва рука) +animated_java.tool.item_display.item_display.options.thirdperson_righthand: Від третьої особи (права рука) +animated_java.tool.item_display.item_display.options.firstperson_lefthand: Від першої особи (ліва рука) +animated_java.tool.item_display.item_display.options.firstperson_righthand: Від першої особи (права рука) +animated_java.tool.item_display.item_display.options.head: Голова +animated_java.tool.item_display.item_display.options.gui: Інтерфейс (GUI) +animated_java.tool.item_display.item_display.options.ground: На землі +animated_java.tool.item_display.item_display.options.fixed: Фіксовано + +# Block Display Panel +animated_java.panel.vanilla_block_display.title: Блок, що відображається +animated_java.panel.vanilla_block_display.description: Блок, який буде відображатися. Підтримуються стани блоків! + +### Custom Elements +## Item Display +animated_java.vanilla_item_display.title: Предмет-дисплей + +## Block Display + +### Misc + +# Blueprint Setting Errors - Failed to Export Message Box +animated_java.misc.failed_to_export.title: Не вдалося експортувати +animated_java.misc.failed_to_export.custom_models.message: Ви вимкнули експорт ресурспаку, але у вашому проєкті є користувацькі моделі! Будь ласка, увімкніть експорт ресурспаку або видаліть моделі перед експортом. +animated_java.misc.failed_to_export.blueprint_settings.message: У налаштуваннях креслення виявлено помилки! Будь ласка, виправте їх перед експортом. +animated_java.misc.failed_to_export.blueprint_settings.error_item: 'Виявлено помилку з {0}:' +animated_java.misc.failed_to_export.button: ОК +animated_java.misc.failed_to_export.invalid_rotation.message: |- + Деякі куби у вашій моделі мають неправильні кути повороту! + + Куби повинні мати кут повороту -45, -22,5, 0, 22,5 або 45 градусів і можуть бути повернуті лише навколо однієї осі, якщо вибрано версії Minecraft нижче 1.21.6. + + Якщо ви хочете повернути куб точніше або на кількох осях, перемістіть куб у кістку і повертайте її замість куба. + + Усі неприпустимі куби підсвічені червоним. Будь ласка, виправте їх перед експортом. +animated_java.misc.failed_to_export.invalid_rotation.message_post_1_21_6: |- + Деякі куби у вашій моделі мають неправильні кути повороту! + + Куби повинні бути повернуті лише навколо однієї осі, якщо вибрано версії Minecraft вище 1.21.6. + + Якщо ви хочете повернути куб точніше або на кількох осях, перемістіть куб у кістку і повертайте її замість куба. + + Усі неприпустимі куби підсвічені червоним. Будь ласка, виправте їх перед експортом. +animated_java.misc.failed_to_export.rig_has_textures_but_no_custom_models.message: |- + У вашій моделі є текстури, але немає користувацьких моделей (кубів), до яких їх можна застосувати! + Будь ласка, створіть куби та використовуйте ці текстури, або видаліть текстури перед експортом. +animated_java.misc.failed_to_export.rig_has_custom_models_but_no_textures.message: |- + У вашій моделі є користувацькі моделі (куби), але немає текстур, які можна застосувати до них! + Будь ласка, додайте текстури до ваших кубів або видаліть їх перед експортом. + +animated_java.toast.invalid_rotations: |- + Неправильний поворот кубів! + + Куби повинні мати поворот у -45, -22,5, 0, 22,5 або 45 градусів і можуть бути повернуті лише навколо однієї осі, якщо вибрано версії Minecraft нижче 1.21.6. + + Усі неприпустимі куби підсвічені червоним. + +animated_java.toast.invalid_rotations_post_1_21_6: |- + Неправильний поворот кубів! + + Куби можуть бути повернуті тільки навколо однієї осі при виборі версії Minecraft 1.21.6 і вище. + + Усі неприпустимі куби підсвічені червоним. + +# Format Category +animated_java.format_category.animated_java: Animated Java + +# Model Manager Warnings +animated_java.block_model_manager.fluid_warning: Рідини не відображаються у блок-дисплеях. +animated_java.block_model_manager.mob_head_warning: Голови мобів не відображаються у блок-дисплеях. Використовуйте предмет-дисплей. +animated_java.block_model_manager.facing_warning: Стан «facing» не підтримується в блок-дисплеях. + +# Project Errors +animated_java.error.blueprint_export_path_doesnt_exist.title: Шлях експорту креслення не існує. +animated_java.error.blueprint_export_path_doesnt_exist.description: |- + Шлях експорту креслення '{0}' не існує! + + Переконайтеся, що папка, в яку відбувається експорт, існує, і спробуйте ще раз. From 5f66528baf1ec3ad15376b18bdc1ff6409038cf7 Mon Sep 17 00:00:00 2001 From: Meekiavelique Date: Sun, 22 Feb 2026 11:05:45 +0100 Subject: [PATCH 18/23] fix(plugin-export): restore plugin mode and update JSON export schema --- src/components/blueprintSettingsDialog.svelte | 16 ++- src/formats/blueprint/codec.ts | 5 - src/lang/en.yml | 1 + src/systems/datapackCompiler/index.ts | 2 +- src/systems/errors.ts | 37 ++++++ src/systems/exporter.ts | 52 +++----- src/systems/global.ts | 2 +- src/systems/jsonCompiler.ts | 118 +++++++++++------- src/systems/resourcepackCompiler/index.ts | 2 +- src/systems/rigRenderer.ts | 2 +- 10 files changed, 138 insertions(+), 99 deletions(-) create mode 100644 src/systems/errors.ts diff --git a/src/components/blueprintSettingsDialog.svelte b/src/components/blueprintSettingsDialog.svelte index 63146e3a..19ece9f6 100644 --- a/src/components/blueprintSettingsDialog.svelte +++ b/src/components/blueprintSettingsDialog.svelte @@ -271,7 +271,7 @@ console.error(e) return { type: 'error', - message: translate('dialog.blueprint_settings.json_file.error.file_does_not_exist'), + message: translate('dialog.blueprint_settings.json_file.error.invalid_path'), } } switch (true) { @@ -333,8 +333,6 @@ // Export Settings export let exportNamespace: Valuable export let enablePluginMode: Valuable - // FIXME - Force-disable plugin mode for now - $enablePluginMode = false export let resourcePackExportMode: Valuable export let dataPackExportMode: Valuable export let targetMinecraftVersion: Valuable @@ -433,12 +431,12 @@ valueChecker={exportNamespaceChecker} /> - + {#if $enablePluginMode} [1] + ) { + super(message) + this.name = 'IntentionalExportError' + } +} + +export class IntentionalExportErrorFromInvalidFile extends IntentionalExportError { + constructor(filePath: string, public originalError: Error) { + const parsed = PathModule.parse(filePath) + super( + `Failed to read file ${parsed.base}:\n\n` + + '```\n' + + originalError + + '\n```', + { + commands: { + open_file: { + text: 'Open File Location', + icon: 'folder_open', + }, + }, + }, + button => { + if (button === 'open_file') { + shell.showItemInFolder(filePath) + } + } + ) + this.name = 'IntentionalExportErrorFromInvalidFile' + } +} + diff --git a/src/systems/exporter.ts b/src/systems/exporter.ts index 67bed5c0..b1a143f2 100644 --- a/src/systems/exporter.ts +++ b/src/systems/exporter.ts @@ -9,48 +9,13 @@ import { isResourcePackPath } from '../util/minecraftUtil' import { translate } from '../util/translation' import { Variant } from '../variants' import { hashAnimations, renderProjectAnimations } from './animationRenderer' +import { exportJSON } from './jsonCompiler' import compileDataPack from './datapackCompiler' +import { IntentionalExportError } from './errors' import resourcepackCompiler from './resourcepackCompiler' import { hashRig, renderRig } from './rigRenderer' import { isCubeValid } from './util' -export class IntentionalExportError extends Error { - constructor( - message: string, - public messageBoxOptions?: MessageBoxOptions, - public messageBoxCallback?: Parameters[1] - ) { - super(message) - this.name = 'IntentionalExportError' - } -} - -export class IntentionalExportErrorFromInvalidFile extends IntentionalExportError { - constructor(filePath: string, public originalError: Error) { - const parsed = PathModule.parse(filePath) - super( - `Failed to read file ${parsed.base}:\n\n` + - '```\n' + - originalError + - '\n```', - { - commands: { - open_file: { - text: 'Open File Location', - icon: 'folder_open', - }, - }, - }, - button => { - if (button === 'open_file') { - shell.showItemInFolder(filePath) - } - } - ) - this.name = 'IntentionalExportErrorFromInvalidFile' - } -} - export function getExportPaths() { const aj = Project!.animated_java @@ -164,7 +129,7 @@ async function actuallyExportProject({ debugMode, }) - if (aj.data_pack_export_mode !== 'none') { + if (!aj.enable_plugin_mode && aj.data_pack_export_mode !== 'none') { await compileDataPack([aj.target_minecraft_version], { rig, animations, @@ -175,6 +140,17 @@ async function actuallyExportProject({ }) } + if (aj.enable_plugin_mode) { + PROGRESS_DESCRIPTION.set('Exporting Plugin JSON...') + exportJSON({ + rig, + animations, + displayItemPath, + textureExportFolder, + modelExportFolder, + }) + } + Project!.last_used_export_namespace = aj.export_namespace if (forceSave) saveBlueprint() diff --git a/src/systems/global.ts b/src/systems/global.ts index d15b820c..c3aed8f1 100644 --- a/src/systems/global.ts +++ b/src/systems/global.ts @@ -1,5 +1,5 @@ import { normalizePath } from '../util/fileUtil' -import { IntentionalExportError, IntentionalExportErrorFromInvalidFile } from './exporter' +import { IntentionalExportError, IntentionalExportErrorFromInvalidFile } from './errors' import { sortObjectKeys } from './util' export enum SUPPORTED_MINECRAFT_VERSIONS { diff --git a/src/systems/jsonCompiler.ts b/src/systems/jsonCompiler.ts index 6b22fff5..4e7823b5 100644 --- a/src/systems/jsonCompiler.ts +++ b/src/systems/jsonCompiler.ts @@ -2,6 +2,7 @@ /// /// +import { PACKAGE } from '../constants' import type { IBlueprintDisplayEntityConfigJSON } from '../formats/blueprint' import { type defaultValues } from '../formats/blueprint/settings' import type { EasingKey } from '../util/easing' @@ -9,6 +10,7 @@ import { resolvePath } from '../util/fileUtil' import { detectCircularReferences, mapObjEntries, scrubUndefined } from '../util/misc' import { Variant } from '../variants' import type { INodeTransform, IRenderedAnimation, IRenderedFrame } from './animationRenderer' +import { IntentionalExportError } from './errors' import { JsonText } from './jsonText' import type { AnyRenderedNode, @@ -18,42 +20,34 @@ import type { IRenderedVariantModel, } from './rigRenderer' -type ExportedNodetransform = Omit< - INodeTransform, - 'type' | 'name' | 'uuid' | 'node' | 'matrix' | 'decomposed' | 'executeCondition' -> & { +type ExportedNodetransform = Omit & { matrix: number[] decomposed: { translation: ArrayVector3 left_rotation: ArrayVector4 scale: ArrayVector3 } - pos: ArrayVector3 - rot: ArrayVector3 - scale: ArrayVector3 - execute_condition?: string } type ExportedRenderedNode = Omit< AnyRenderedNode, - | 'node' - | 'parentNode' - | 'model' - | 'boundingBox' - | 'configs' - | 'baseScale' - | 'path_name' + 'default_transform' + | 'bounding_box' + | 'configs' | 'storage_name' > & { default_transform: ExportedNodetransform bounding_box?: { min: ArrayVector3; max: ArrayVector3 } configs?: Record } -type ExportedAnimationFrame = Omit & { +type ExportedAnimationFrame = Omit & { node_transforms: Record } type ExportedBakedAnimation = Omit< IRenderedAnimation, - 'uuid' | 'frames' | 'modified_nodes' | 'path_name' | 'storage_name' + 'uuid' + | 'frames' + | 'modified_nodes' + | 'storage_name' > & { frames: ExportedAnimationFrame[] modified_nodes: string[] @@ -98,16 +92,16 @@ interface ExportedDynamicAnimation { animators: Record } interface ExportedTexture { + uuid: string name: string src: string } -type ExportedVariantModel = Omit< +type ExportedVariantModel = Pick< IRenderedVariantModel, - 'model_path' | 'resource_location' | 'item_model' -> & { - model: IRenderedModel | null - custom_model_data: number -} + 'custom_model_data' + | 'resource_location' + | 'item_model' +> & { model: IRenderedModel | null } type ExportedVariant = Omit & { /** * A map of bone UUID -> IRenderedVariantModel @@ -116,16 +110,25 @@ type ExportedVariant = Omit & { } export interface IExportedJSON { + format_version: '2.0.0' + exported_with: { + name: string + version: string + } /** * The Blueprint's Settings */ settings: { export_namespace: (typeof defaultValues)['export_namespace'] + target_minecraft_version: (typeof defaultValues)['target_minecraft_version'] + display_item: (typeof defaultValues)['display_item'] bounding_box: (typeof defaultValues)['render_box'] // Resource Pack Settings custom_model_data_offset: (typeof defaultValues)['custom_model_data_offset'] // Plugin Settings baked_animations: (typeof defaultValues)['baked_animations'] + interpolation_duration: (typeof defaultValues)['interpolation_duration'] + teleportation_duration: (typeof defaultValues)['teleportation_duration'] } textures: Record nodes: Record @@ -137,7 +140,12 @@ export interface IExportedJSON { } function transferKey(obj: any, oldKey: string, newKey: string) { - obj[newKey] = obj[oldKey] + if (!Object.prototype.hasOwnProperty.call(obj, oldKey)) return + const value = obj[oldKey] + if (value === undefined) return + if (obj[newKey] === undefined) { + obj[newKey] = value + } delete obj[oldKey] } @@ -209,6 +217,8 @@ function serializeVariant(rig: IRenderedRig, variant: IRenderedVariant): Exporte const json: ExportedVariantModel = { model: model.model, custom_model_data: model.custom_model_data, + resource_location: model.resource_location, + item_model: model.item_model, } return [uuid, json] }), @@ -230,22 +240,29 @@ export function exportJSON(options: { function serializeTexture(texture: Texture): ExportedTexture { return { + uuid: texture.uuid, name: texture.name, src: texture.getDataURL(), } } const json: IExportedJSON = { + format_version: '2.0.0', + exported_with: { + name: PACKAGE.name, + version: PACKAGE.version, + }, settings: { export_namespace: aj.export_namespace, + target_minecraft_version: aj.target_minecraft_version, + display_item: aj.display_item, bounding_box: aj.render_box, custom_model_data_offset: aj.custom_model_data_offset, baked_animations: aj.baked_animations, + interpolation_duration: aj.interpolation_duration, + teleportation_duration: aj.teleportation_duration, }, - textures: mapObjEntries(rig.textures, (_, texture) => [ - texture.uuid, - serializeTexture(texture), - ]), + textures: mapObjEntries(rig.textures, (id, texture) => [id, serializeTexture(texture)]), nodes: mapObjEntries(rig.nodes, (uuid, node) => [uuid, serailizeRenderedNode(node)]), variants: mapObjEntries(rig.variants, (uuid, variant) => [ uuid, @@ -286,12 +303,22 @@ export function exportJSON(options: { try { exportPath = resolvePath(aj.json_file) } catch (e) { - console.log(`Failed to resolve export path '${aj.json_file}'`) - console.error(e) - return + throw new IntentionalExportError( + `Failed to resolve export path ${aj.json_file}: ${String(e)}` + ) } - fs.writeFileSync(exportPath, compileJSON(json).toString()) + try { + const dir = PathModule.dirname(exportPath) + if (dir && dir !== '.' && !fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }) + } + fs.writeFileSync(exportPath, compileJSON(json).toString()) + } catch (e: any) { + throw new IntentionalExportError( + `Failed to write JSON file ${exportPath}: ${String(e)}` + ) + } } function serailizeNodeTransform(node: INodeTransform): ExportedNodetransform { @@ -325,19 +352,24 @@ function serailizeRenderedNode(node: AnyRenderedNode): ExportedRenderedNode { transferKey(json, 'backgroundAlpha', 'background_alpha') json.default_transform = serailizeNodeTransform(json.default_transform as INodeTransform) + + if (node.type !== 'struct' && (node as any).bounding_box) { + json.bounding_box = { + min: (node as any).bounding_box.min.toArray(), + max: (node as any).bounding_box.max.toArray(), + } + } + + if ((node as any).configs) { + json.configs = { ...(node as any).configs?.variants } + const defaultVariant = Variant.getDefault() + if ((node as any).configs?.default && defaultVariant) { + json.configs[defaultVariant.uuid] = (node as any).configs.default + } + } + switch (node.type) { case 'bone': { - delete json.boundingBox - json.bounding_box = { - min: node.bounding_box.min.toArray(), - max: node.bounding_box.max.toArray(), - } - delete json.configs - json.configs = { ...node.configs?.variants } - const defaultVariant = Variant.getDefault() - if (node.configs?.default && defaultVariant) { - json.configs[defaultVariant.uuid] = node.configs.default - } break } case 'text_display': { diff --git a/src/systems/resourcepackCompiler/index.ts b/src/systems/resourcepackCompiler/index.ts index 856a9014..0d914bca 100644 --- a/src/systems/resourcepackCompiler/index.ts +++ b/src/systems/resourcepackCompiler/index.ts @@ -1,6 +1,6 @@ import { MAX_PROGRESS, PROGRESS, PROGRESS_DESCRIPTION } from '../../interface/dialog/exportProgress' import { getNextSupportedVersion, getResourcePackFormat } from '../../util/minecraftUtil' -import { IntentionalExportError } from '../exporter' +import { IntentionalExportError } from '../errors' import { type IRenderedRig } from '../rigRenderer' import type { ExportedFile } from '../util' diff --git a/src/systems/rigRenderer.ts b/src/systems/rigRenderer.ts index 389907fd..95b8924b 100644 --- a/src/systems/rigRenderer.ts +++ b/src/systems/rigRenderer.ts @@ -20,7 +20,7 @@ import { restoreSceneAngle, updatePreview, } from './animationRenderer' -import { IntentionalExportError } from './exporter' +import { IntentionalExportError } from './errors' import { JsonText } from './jsonText' export interface IRenderedFace { From 134a8b01e0aae122d324d6574af6bca7d3d0fbdb Mon Sep 17 00:00:00 2001 From: Meekiavelique Date: Tue, 17 Mar 2026 17:19:30 +0100 Subject: [PATCH 19/23] fix(plugin-export): align plugin-mode JSON export with blueprint schema v1 --- src/systems/exporter.ts | 10 +- src/systems/pluginCompiler.ts | 675 ++++++++++++++++++++++++++++++++-- 2 files changed, 646 insertions(+), 39 deletions(-) diff --git a/src/systems/exporter.ts b/src/systems/exporter.ts index b1a143f2..7136e0a5 100644 --- a/src/systems/exporter.ts +++ b/src/systems/exporter.ts @@ -9,7 +9,7 @@ import { isResourcePackPath } from '../util/minecraftUtil' import { translate } from '../util/translation' import { Variant } from '../variants' import { hashAnimations, renderProjectAnimations } from './animationRenderer' -import { exportJSON } from './jsonCompiler' +import { exportPluginBlueprint } from './pluginCompiler' import compileDataPack from './datapackCompiler' import { IntentionalExportError } from './errors' import resourcepackCompiler from './resourcepackCompiler' @@ -142,13 +142,7 @@ async function actuallyExportProject({ if (aj.enable_plugin_mode) { PROGRESS_DESCRIPTION.set('Exporting Plugin JSON...') - exportJSON({ - rig, - animations, - displayItemPath, - textureExportFolder, - modelExportFolder, - }) + exportPluginBlueprint({ rig, animations }) } Project!.last_used_export_namespace = aj.export_namespace diff --git a/src/systems/pluginCompiler.ts b/src/systems/pluginCompiler.ts index e4c4a200..144d0a7a 100644 --- a/src/systems/pluginCompiler.ts +++ b/src/systems/pluginCompiler.ts @@ -1,44 +1,657 @@ -namespace PluginBlueprint { - type TextureAnimationFrame = - | number - | { - index: number - time?: number - } +/// - interface TextureAnimation { - width?: number - height?: number - interpolate?: boolean - frametime?: number - frames?: TextureAnimationFrame[] +import type { IBlueprintDisplayEntityConfigJSON } from '../formats/blueprint' +import { resolvePath } from '../util/fileUtil' +import { isResourcePackPath, parseResourcePackPath, sanitizeStorageKey } from '../util/minecraftUtil' +import { detectCircularReferences, scrubUndefined } from '../util/misc' +import { Variant } from '../variants' +import type { INodeTransform, IRenderedAnimation } from './animationRenderer' +import { IntentionalExportError } from './errors' +import type { AnyRenderedNode, IRenderedElement, IRenderedFace, IRenderedModel, IRenderedRig } from './rigRenderer' + +type TextureAnimationFrame = + | number + | { + index: number + time: number + } + +interface TextureAnimation { + interpolate?: boolean + width?: number + height?: number + frametime?: number + frames?: TextureAnimationFrame[] +} + +type PluginTexture = + | { + type: 'custom' + base64_string: string + mime_type?: string + animation?: TextureAnimation + } + | { + type: 'reference' + resource_location: string + } + +interface TexturePalette { + active_state: string + states: Record +} + +type TextureProvider = + | { type: 'texture'; texture: string } + | { type: 'texture_palette'; texture_palette: string } + +interface BoneElementFace { + uv: ArrayVector4 + texture_provider: TextureProvider + tintindex?: number + rotation?: number +} + +type BoneElementFaces = Partial< + Record<'north' | 'east' | 'south' | 'west' | 'up' | 'down', BoneElementFace> +> + +interface BoneElementRotation { + angle: number + axis: 'x' | 'y' | 'z' + origin: ArrayVector3 + rescale?: boolean +} + +interface BoneElement { + from: ArrayVector3 + to: ArrayVector3 + rotation: BoneElementRotation + shade?: boolean + light_emission?: number + faces: BoneElementFaces + display_rotation?: ArrayVector3 +} + +interface NodeTransformation { + matrix?: number[] + decomposed?: { + translation?: ArrayVector3 + left_rotation?: ArrayVector4 + scale?: ArrayVector3 + } + position?: ArrayVector3 + rotation?: ArrayVector3 + head_rotation?: ArrayVector2 + scale?: ArrayVector3 +} + +type NodeType = 'bone' | 'item_display' | 'block_display' | 'text_display' | 'structure' | 'camera' | 'locator' + +type PluginNode = + | { + type: 'bone' + default_transformation?: NodeTransformation + display_properties?: Record + elements: BoneElement[] + } + | { + type: Exclude + default_transformation?: NodeTransformation + display_properties?: Record + } + +type LoopMode = { type: 'once' } | { type: 'hold' } | { type: 'loop'; loop_delay?: string } + +type TransformationKeyframeInterpolation = + | { type: 'linear'; easing: string; easing_arguments?: number[] } + | { + type: 'bezier' + left_handle_time: ArrayVector3 + left_handle_value: ArrayVector3 + right_handle_time: ArrayVector3 + right_handle_value: ArrayVector3 + } + | { type: 'catmullrom' } + | { type: 'step' } + +interface TransformationKeyframe { + value: [string, string, string] + post?: [string, string, string] + interpolation: TransformationKeyframeInterpolation +} + +interface PluginAnimation { + loop_mode: LoopMode + length: number + blend_weight?: string + start_delay?: string + global_keyframes?: { + texture?: Record> + event?: Record } + node_keyframes?: Record< + string, + { + position?: Record + rotation?: Record + scale?: Record + } + > +} - interface CustomTexture { - type: 'custom' - base64: string - mime_type: 'image/png' - animation?: TextureAnimation +export interface PluginBlueprintJson { + $schema?: string + format_version: number + settings: { + id: string } + textures?: Record + texture_palettes?: Record + nodes?: Record + animations?: Record +} - interface ReferenceTexture { - type: 'reference' - resource_location: string +function ensureUniqueKey(baseKey: string, usedKeys: Set) { + const sanitizedBaseKey = sanitizeStorageKey(baseKey || 'unnamed') + let key = sanitizedBaseKey + let i = 2 + while (usedKeys.has(key)) { + key = `${sanitizedBaseKey}_${i++}` } + usedKeys.add(key) + return key +} - export type Texture = CustomTexture | ReferenceTexture +function formatTimestamp(timestamp: number): string { + let s = timestamp.toString() + if (s.includes('e') || s.includes('E')) s = timestamp.toFixed(6) + if (!s.includes('.')) s += '.0' + return s +} - export interface Json { - format_version: string - settings: { - id: string +function toMolangNumber(value: number): string { + if (!Number.isFinite(value)) return '0' + const rounded = Math.round(value * 100000) / 100000 + if (Object.is(rounded, -0)) return '0' + return rounded.toString() +} + +function parseDataUrl(dataUrl: string): { mimeType: string; base64: string } { + const [header, base64] = dataUrl.split(',', 2) + if (!header || !base64) throw new Error('Invalid data URL') + const mimeType = header.replace(/^data:/, '').split(';')[0] || 'image/png' + return { mimeType, base64 } +} + +function readTextureAnimation(texture: Texture): TextureAnimation | undefined { + if (!texture.path) return undefined + const mcmetaPath = texture.path + '.mcmeta' + if (!fs.existsSync(mcmetaPath)) return undefined + try { + const parsed = JSON.parse(fs.readFileSync(mcmetaPath, 'utf-8')) as { + animation?: Record + } + const anim = parsed.animation as any + if (!anim) return undefined + return scrubUndefined({ + interpolate: anim.interpolate, + width: anim.width, + height: anim.height, + frametime: anim.frametime, + frames: anim.frames, + } satisfies TextureAnimation) + } catch (e) { + console.warn(`Failed to parse texture animation mcmeta for ${texture.name}:`, e) + return undefined + } +} + +function serializeNodeTransformation(transform: INodeTransform): NodeTransformation { + return scrubUndefined({ + matrix: transform.matrix.elements.slice(), + decomposed: { + translation: transform.decomposed.translation.toArray() as ArrayVector3, + left_rotation: transform.decomposed.left_rotation.toArray() as ArrayVector4, + scale: transform.decomposed.scale.toArray() as ArrayVector3, + }, + position: transform.pos, + rotation: transform.rot, + head_rotation: transform.head_rot, + scale: transform.scale, + } satisfies NodeTransformation) +} + +function serializeDisplayProperties( + node: AnyRenderedNode, + config: IBlueprintDisplayEntityConfigJSON | undefined +): Record | undefined { + const props: Record = {} + if (config?.billboard !== undefined) props.billboard = config.billboard + + const overrideBrightness = config?.override_brightness ?? false + if (overrideBrightness) { + props.is_custom_brightness_enabled = true + props.custom_brightness = config?.brightness_override ?? 0 + } + + if (config?.glowing !== undefined) props.is_glowing = config.glowing + if (config?.override_glow_color) { + const color = (config.glow_color ?? '#ffffff').replace('#', '') + props.glow_color_override = parseInt(color, 16) + } + + if (config?.shadow_radius !== undefined) props.shadow_radius = config.shadow_radius + if (config?.shadow_strength !== undefined) props.shadow_strength = config.shadow_strength + + if (node.type === 'bone' && config?.enchanted !== undefined) { + props.is_enchanted = config.enchanted + } + + if (Object.keys(props).length === 0) return undefined + return props +} + +function intFromHex8(hex: string): number { + if (hex.startsWith('#')) hex = hex.slice(1) + const unsigned = parseInt(hex, 16) + if (!Number.isFinite(unsigned)) return 0 + return unsigned > 0x7fffffff ? unsigned - 0x100000000 : unsigned +} + +function serializeTextureProvider(options: { + textureId: string + textureIdToKey: Map + textureKeyToPaletteId: Map +}): TextureProvider { + const textureKey = options.textureIdToKey.get(options.textureId) + if (!textureKey) throw new Error(`Missing texture mapping for texture id '${options.textureId}'`) + + const paletteId = options.textureKeyToPaletteId.get(textureKey) + if (paletteId) return { type: 'texture_palette', texture_palette: paletteId } + + return { type: 'texture', texture: textureKey } +} + +function serializeFace(face: IRenderedFace, options: { + textureIdToKey: Map + textureKeyToPaletteId: Map +}): BoneElementFace | undefined { + if (!face.uv) return undefined + const textureId = face.texture?.startsWith('#') ? face.texture.slice(1) : face.texture + if (!textureId) return undefined + + return scrubUndefined({ + uv: face.uv as ArrayVector4, + tintindex: face.tintindex, + rotation: face.rotation, + texture_provider: serializeTextureProvider({ + textureId, + textureIdToKey: options.textureIdToKey, + textureKeyToPaletteId: options.textureKeyToPaletteId, + }), + } satisfies BoneElementFace) +} + +function serializeBoneElements(model: IRenderedModel, options: { + textureIdToKey: Map + textureKeyToPaletteId: Map +}): BoneElement[] { + const elements = model.elements ?? [] + return elements.map((el: IRenderedElement) => { + const faces: BoneElementFaces = {} + for (const [dir, face] of Object.entries(el.faces ?? {})) { + const serializedFace = serializeFace(face as IRenderedFace, options) + if (!serializedFace) continue + ;(faces as any)[dir] = serializedFace + } + + const rotation: BoneElementRotation = + el.rotation && !Array.isArray(el.rotation) + ? { + angle: el.rotation.angle, + axis: el.rotation.axis as BoneElementRotation['axis'], + origin: el.rotation.origin as ArrayVector3, + rescale: (el.rotation as any).rescale, + } + : { angle: 0, axis: 'y', origin: [0, 0, 0] } + + return scrubUndefined({ + from: el.from as ArrayVector3, + to: el.to as ArrayVector3, + rotation, + shade: el.shade, + light_emission: el.light_emission, + faces, + display_rotation: (el as any).display_rotation, + } satisfies BoneElement) + }) +} + +function serializeNode( + node: AnyRenderedNode, + options: { + defaultVariantModels: Record + textureIdToKey: Map + textureKeyToPaletteId: Map + } +): PluginNode { + const base = { + default_transformation: serializeNodeTransformation(node.default_transform), + } as const + + const config = (node as any).configs?.default as IBlueprintDisplayEntityConfigJSON | undefined + const displayProps = serializeDisplayProperties(node, config) + + switch (node.type) { + case 'bone': { + const model = options.defaultVariantModels[node.uuid]?.model + if (!model) { + throw new Error(`Missing model for bone node '${node.name}' (${node.uuid})`) + } + return scrubUndefined({ + type: 'bone', + ...base, + display_properties: displayProps, + elements: serializeBoneElements(model, { + textureIdToKey: options.textureIdToKey, + textureKeyToPaletteId: options.textureKeyToPaletteId, + }), + } satisfies PluginNode) + } + case 'item_display': { + return scrubUndefined({ + type: 'item_display', + ...base, + display_properties: scrubUndefined({ + ...displayProps, + item: (node as any).item, + item_display: (node as any).item_display, + }), + } satisfies PluginNode) + } + case 'block_display': { + return scrubUndefined({ + type: 'block_display', + ...base, + display_properties: scrubUndefined({ + ...displayProps, + block_state: (node as any).block, + }), + } satisfies PluginNode) + } + case 'text_display': { + const argb = intFromHex8((node as any).background_color ?? '#40000000') + return scrubUndefined({ + type: 'text_display', + ...base, + display_properties: scrubUndefined({ + ...displayProps, + alignment: (node as any).align, + background_color: argb, + is_default_background: argb === 0x40000000, + is_see_through: (node as any).see_through, + is_shadowed: (node as any).shadow, + line_width: (node as any).line_width, + text: (node as any).text, + }), + } satisfies PluginNode) + } + case 'struct': + return { type: 'structure', ...base } + case 'camera': + return { type: 'camera', ...base } + case 'locator': + return { type: 'locator', ...base } + default: + throw new Error(`Unsupported node type: ${(node as any).type}`) + } +} + +function buildPalettes(options: { + textures: Record + textureIdToKey: Map +}): { palettes: Record; textureKeyToPaletteId: Map } { + const variants = Variant.allExcludingDefault() + if (variants.length === 0) { + return { palettes: {}, textureKeyToPaletteId: new Map() } + } + + const usedPaletteKeys = new Set() + const palettes: Record = {} + const textureKeyToPaletteId = new Map() + + for (const texture of Object.values(Texture.all)) { + // only exported textures + const textureKey = options.textureIdToKey.get(texture.id) + if (!textureKey) continue + if (!options.textures[textureKey]) continue + + const states: Record = { + default: { texture: textureKey }, + } + + let hasAnyAlternative = false + for (const variant of variants) { + const mapped = variant.textureMap.getMappedTexture(texture.uuid) + let mappedKey = textureKey + if (mapped) { + const key = options.textureIdToKey.get(mapped.id) + if (key) mappedKey = key + } + states[variant.name] = { texture: mappedKey } + if (mappedKey !== textureKey) hasAnyAlternative = true + } + + if (!hasAnyAlternative) continue + + const paletteId = ensureUniqueKey(`${textureKey}_palette`, usedPaletteKeys) + palettes[paletteId] = { + active_state: 'default', + states, + } + textureKeyToPaletteId.set(textureKey, paletteId) + } + + return { palettes, textureKeyToPaletteId } +} + +function serializeTexture(texture: Texture): PluginTexture { + if (texture.path && isResourcePackPath(texture.path)) { + const parsed = parseResourcePackPath(texture.path) + if (parsed?.namespace === 'minecraft') { + return { + type: 'reference', + resource_location: parsed.resourceLocation, + } } - textures: Record } + + const dataUrl = texture.getDataURL() + const { mimeType, base64 } = parseDataUrl(dataUrl) + return scrubUndefined({ + type: 'custom', + base64_string: base64, + mime_type: mimeType, + animation: readTextureAnimation(texture), + } satisfies PluginTexture) } -// export function compilePluginBlueprint(): PluginBlueprint.Json { -// const blueprint: PluginBlueprint.Json = {} +function serializeAnimation(options: { + animation: IRenderedAnimation + nodeUuidToId: Map + paletteIds: string[] +}): PluginAnimation { + const { animation, nodeUuidToId } = options + + const loop_mode: LoopMode = + animation.loop_mode === 'loop' + ? { type: 'loop', loop_delay: String(animation.loop_delay ?? 0) } + : animation.loop_mode === 'hold' + ? { type: 'hold' } + : { type: 'once' } + + const maxTime = animation.frames.at(-1)?.time ?? 0 + + const node_keyframes: NonNullable = {} + + for (const frame of animation.frames) { + const timeKey = formatTimestamp(frame.time) + for (const [uuid, transform] of Object.entries(frame.node_transforms)) { + const nodeId = nodeUuidToId.get(uuid) + if (!nodeId) continue + node_keyframes[nodeId] ??= {} + + const createInterpolation = (): TransformationKeyframeInterpolation => + transform.interpolation === 'step' || transform.interpolation === 'pre-post' + ? { type: 'step' } + : { type: 'linear', easing: 'linear' } + + node_keyframes[nodeId].position ??= {} + node_keyframes[nodeId].rotation ??= {} + node_keyframes[nodeId].scale ??= {} -// return blueprint -// } + node_keyframes[nodeId].position![timeKey] = { + value: [ + toMolangNumber(transform.pos[0]), + toMolangNumber(transform.pos[1]), + toMolangNumber(transform.pos[2]), + ], + interpolation: createInterpolation(), + } + node_keyframes[nodeId].rotation![timeKey] = { + value: [ + toMolangNumber(transform.rot[0]), + toMolangNumber(transform.rot[1]), + toMolangNumber(transform.rot[2]), + ], + interpolation: createInterpolation(), + } + node_keyframes[nodeId].scale![timeKey] = { + value: [ + toMolangNumber(transform.scale[0]), + toMolangNumber(transform.scale[1]), + toMolangNumber(transform.scale[2]), + ], + interpolation: createInterpolation(), + } + } + } + + let global_keyframes: NonNullable | undefined + + // map the baked variant for each frame into the texture keyframes + if (options.paletteIds.length) { + const textureKeyframes: Record> = {} + for (const frame of animation.frames) { + if (!frame.variants?.length) continue + const variant = Variant.getByUUID(frame.variants[0]) + if (!variant) continue + const timeKey = formatTimestamp(frame.time) + textureKeyframes[timeKey] ??= {} + for (const paletteId of options.paletteIds) { + textureKeyframes[timeKey][paletteId] = variant.name + } + } + if (Object.keys(textureKeyframes).length) { + global_keyframes ??= {} + global_keyframes.texture = textureKeyframes + } + } + + return scrubUndefined({ + loop_mode, + blend_weight: '1', + start_delay: '0', + length: maxTime, + global_keyframes, + node_keyframes, + } satisfies PluginAnimation) +} + +export function exportPluginBlueprint(options: { + rig: IRenderedRig + animations: IRenderedAnimation[] +}): void { + const aj = Project!.animated_java + + const usedTextureKeys = new Set() + const textures: Record = {} + const textureIdToKey = new Map() + + for (const texture of Object.values(options.rig.textures)) { + const baseKey = texture.name.replace(/\\.png$/i, '') + const key = ensureUniqueKey(baseKey, usedTextureKeys) + textureIdToKey.set(texture.id, key) + textures[key] = serializeTexture(texture) + } + + const { palettes, textureKeyToPaletteId } = buildPalettes({ textures, textureIdToKey }) + const paletteIds = Object.keys(palettes) + + const usedNodeKeys = new Set() + const nodeUuidToId = new Map() + for (const [uuid, node] of Object.entries(options.rig.nodes)) { + nodeUuidToId.set(uuid, ensureUniqueKey(node.storage_name, usedNodeKeys)) + } + + const defaultVariant = Variant.getDefault() + const defaultVariantModels = options.rig.variants[defaultVariant.uuid]?.models ?? {} + + const nodes: Record = {} + for (const [uuid, node] of Object.entries(options.rig.nodes)) { + const nodeId = nodeUuidToId.get(uuid) + if (!nodeId) continue + nodes[nodeId] = serializeNode(node, { + defaultVariantModels, + textureIdToKey, + textureKeyToPaletteId, + }) + } + + const animations: Record = {} + for (const animation of options.animations) { + const key = ensureUniqueKey(animation.storage_name, new Set(Object.keys(animations))) + animations[key] = serializeAnimation({ + animation, + nodeUuidToId, + paletteIds, + }) + } + + const blueprint: PluginBlueprintJson = scrubUndefined({ + format_version: 1, + settings: { + id: `animated_java:${aj.export_namespace}`, + }, + textures, + texture_palettes: palettes, + nodes, + animations, + }) + + if (detectCircularReferences(blueprint)) { + throw new Error('Circular references detected in exported plugin blueprint.') + } + + let exportPath: string + try { + exportPath = resolvePath(aj.json_file) + } catch (e) { + throw new IntentionalExportError( + `Failed to resolve export path ${aj.json_file}: ${String(e)}` + ) + } + + try { + const dir = PathModule.dirname(exportPath) + if (dir && dir !== '.' && !fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }) + } + fs.writeFileSync(exportPath, compileJSON(blueprint).toString()) + } catch (e: any) { + throw new IntentionalExportError( + `Failed to write JSON file ${exportPath}: ${String(e)}` + ) + } +} From f13fdabc7bc1d92712415793fa791b03058cf997 Mon Sep 17 00:00:00 2001 From: Koishem Date: Wed, 25 Mar 2026 06:59:27 +0100 Subject: [PATCH 20/23] =?UTF-8?q?=F0=9F=94=A4=20Improve=20translations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lang/ru.yml | 266 ++++++++++++++++++++++++------------------------ 1 file changed, 133 insertions(+), 133 deletions(-) diff --git a/src/lang/ru.yml b/src/lang/ru.yml index 882f9f5e..cf514a7f 100644 --- a/src/lang/ru.yml +++ b/src/lang/ru.yml @@ -19,9 +19,9 @@ animated_java.action.export_all_debug.description: |- Экспортировать все открытые чертежи в режиме отладки. animated_java.action.extract.name: Извлечь animated_java.action.extract.confirm: Подтвердить извлечение -animated_java.action.create_text_display.title: Добавление текст дисплея -animated_java.action.create_vanilla_item_display.title: Добавление предмет дисплея -animated_java.action.create_vanilla_block_display.title: Добавление блок дисплея +animated_java.action.create_text_display.title: Добавление текст-дисплея +animated_java.action.create_vanilla_item_display.title: Добавление предмет-дисплея +animated_java.action.create_vanilla_block_display.title: Добавление блок-дисплея animated_java.action.copy_display_entity_config.name: Копировать настройку дисплей-сущности animated_java.action.copy_display_entity_config.message: Скопирована настройка дисплей-сущности из "{0}" animated_java.action.paste_display_entity_config.name: Вставить настройку дисплей-сущности @@ -32,108 +32,108 @@ animated_java.popup.loading.loading: Загрузка Animated Java... animated_java.popup.loading.success: Animated Java загружена успешно! animated_java.popup.loading.offline: |- Animated Java не удалось подключить! - Некоторый функционал может быть недоступен. + Некоторые функции могут быть недоступны. animated_java.popup.installed_popup.title: Спасибо за установку! -animated_java.popup.installed_popup.close_button: Начнём анимировать! +animated_java.popup.installed_popup.close_button: Приступим к анимации! -animated_java.popup.incompatability_popup.title: Animated Java Обнаружена несовместимость +animated_java.popup.incompatability_popup.title: Обнаружена несовместимость Animated Java animated_java.popup.incompatability_popup.description: |- - У вас установлены плагины которые вызывают проблемы с Animated Java. - Пожалуйста отключите или удалите эти плагин(ы) и перезапустите Blockbench чтобы использовать Animated Java: + У вас установлены плагины, которые вызывают проблемы с Animated Java. + Пожалуйста отключите или удалите эти плагин(ы) и перезапустите Blockbench, чтобы использовать Animated Java: animated_java.popup.incompatability_popup.disable_button: Отключить плагин animated_java.popup.incompatability_popup.button.disable_all: Отключить все несовместимые плагины -animated_java.popup.incompatability_popup.button.ignore: Игнорировать и продолжить (Не Рекомендуется) +animated_java.popup.incompatability_popup.button.ignore: Игнорировать и продолжить (не рекомендуется) animated_java.plugin_dialog.incompatability_notice: |- Этот плагин вызывает проблемы с Animated Java. - Вы не сможете установить этот плагин пока Animated Java установлен. + Вы не сможете установить этот плагин, пока Animated Java установлен. ### Dialogs animated_java.dialog.reset: Сброс к настройкам по умолчанию ## About -animated_java.dialog.about.title: Про Animated Java +animated_java.dialog.about.title: О Animated Java animated_java.dialog.about.close_button: Закрыть ## Changelog -animated_java.dialog.changelog_dialog.title: Animated Java Список изменений +animated_java.dialog.changelog_dialog.title: Список изменений Animated Java ## Unexpected Error Dialog animated_java.dialog.unexpected_error.title: Произошла непредвиденная ошибка! animated_java.dialog.unexpected_error.close_button: Закрыть animated_java.dialog.unexpected_error.copy_error_message_button.message: Сообщение с ошибкой скопировано в буфер обмена! -animated_java.dialog.unexpected_error.copy_error_message_button.description: Нажмите чтобы скопировать сообщение с ошибкой. -animated_java.dialog.unexpected_error.paragraph: 'Пожалуйста сообщите об этой ошибке в нашем {0} и создайте тикет в #animated-java-support канале, или создав задачу на нашем {1}. Спасибо!' +animated_java.dialog.unexpected_error.copy_error_message_button.description: Нажмите, чтобы скопировать сообщение с ошибкой. +animated_java.dialog.unexpected_error.paragraph: 'Пожалуйста, сообщите об этой ошибке в нашем {0}, создав тикет в #animated-java-support канале, или создав задачу на нашем {1}. Спасибо!' ## Blueprint Settings Dialog animated_java.dialog.blueprint_settings.title: Настройки чертежа animated_java.dialog.blueprint_settings.project_settings.title: Проект -animated_java.dialog.blueprint_settings.advanced_settings_warning: Продвинутые настройки должны быть использованы только при необходимости! +animated_java.dialog.blueprint_settings.advanced_settings_warning: Расширенные настройки должны быть использованы только при необходимости! animated_java.dialog.blueprint_settings.project_name.title: Название animated_java.dialog.blueprint_settings.project_name.description: |- Имя файла чертежа. - Это будет перезаписано, если вы сохраните чертёж по другим именем. + Это будет перезаписано, если вы сохраните чертёж под другим именем. -animated_java.dialog.blueprint_settings.texture_size.title: Размер Текстуры +animated_java.dialog.blueprint_settings.texture_size.title: Размер текстуры animated_java.dialog.blueprint_settings.texture_size.description: |- Разрешение редактора UV. - Это должно соответствовать размеру самой большой текстуры в вашем чертеже. -animated_java.dialog.blueprint_settings.texture_size.warning.not_square: Размер Текстуры должен быть квадратным! + Должно соответствовать размеру самой большой текстуры в вашем чертеже. +animated_java.dialog.blueprint_settings.texture_size.warning.not_square: Размер текстуры должен быть квадратным! animated_java.dialog.blueprint_settings.texture_size.warning.not_a_power_of_2: Размер текстуры должен быть степенью числа 2, если это возможно! -animated_java.dialog.blueprint_settings.texture_size.warning.does_not_match_largest_texture: Выбранный Размер Текстуры не соответствует размеру самой большой текстуры в вашем чертеже! +animated_java.dialog.blueprint_settings.texture_size.warning.does_not_match_largest_texture: Выбранный размер текстуры не соответствует размеру самой большой текстуры в вашем чертеже! # Export Settings animated_java.dialog.blueprint_settings.export_settings.title: Экспорт animated_java.dialog.blueprint_settings.export_namespace.title: Пространство имен animated_java.dialog.blueprint_settings.export_namespace.description: |- - Пространство имён которое будет использовано при генерации Ресурспака и Датапака. + Пространство имён, которое будет использовано при генерации ресурспака и датапака. - Рекомендовано использовать уникальное пространство имён чтобы избежать конфликтов с другими Ресурспаками и Датапаками. + Рекомендовано использовать уникальное пространство имён, чтобы избежать конфликтов с другими ресурспаками и датапаками. animated_java.dialog.blueprint_settings.export_namespace.error.empty: Пространство имён экспорта не может быть пустым! animated_java.dialog.blueprint_settings.export_namespace.error.reserved: Пространство имён экспорта "{0}" зарезервировано для внутренней работы плагина! Пожалуйста выберите другое пространство имён. animated_java.dialog.blueprint_settings.export_namespace.error.invalid_characters: Пространство имён экспорта имеет запрещённые символы! Пространства имён могут содержать только буквы, цифры и нижние подчёркивания. -animated_java.dialog.blueprint_settings.show_render_box.title: Показать Область Рендера -animated_java.dialog.blueprint_settings.show_render_box.description: Когда включено, отображает рендер коробку в редакторе. +animated_java.dialog.blueprint_settings.show_render_box.title: Показать область рендера +animated_java.dialog.blueprint_settings.show_render_box.description: Когда включено, отображает область рендера в редакторе. -animated_java.dialog.blueprint_settings.auto_render_box.title: Авто-размер Области Рендера +animated_java.dialog.blueprint_settings.auto_render_box.title: Авто-размер области рендера animated_java.dialog.blueprint_settings.auto_render_box.description: |- Когда включено, область рендера будет автоматически рассчитываться на основе геометрии модели. -animated_java.dialog.blueprint_settings.render_box.title: Размер Области Рендера +animated_java.dialog.blueprint_settings.render_box.title: Размер области рендера animated_java.dialog.blueprint_settings.render_box.description: |- - [Размер и Высота](https://minecraft.wiki/w/Display#Entity_data) дисплей сущностей рига. + [Размер и высота](https://minecraft.wiki/w/Display#Entity_data) дисплей-сущностей рига. animated_java.dialog.blueprint_settings.view_range.title: Область обзора animated_java.dialog.blueprint_settings.view_range.description: |- [Область обзора](https://minecraft.wiki/w/Display#Entity_data) рига по умолчанию. -animated_java.dialog.blueprint_settings.enable_plugin_mode.title: Режим Плагина +animated_java.dialog.blueprint_settings.enable_plugin_mode.title: Режим плагина animated_java.dialog.blueprint_settings.enable_plugin_mode.description: |- - Когда включено, проект будет экспортирован как JSON файл специально для использования плагинами. + Когда включено, проект будет экспортирован как JSON-файл для использования в плагинах. -animated_java.dialog.blueprint_settings.resource_pack_export_mode.title: Режим Экспорта Ресурспака +animated_java.dialog.blueprint_settings.resource_pack_export_mode.title: Режим экспорта ресурспака animated_java.dialog.blueprint_settings.resource_pack_export_mode.options.folder: Папка -animated_java.dialog.blueprint_settings.resource_pack_export_mode.options.zip: Zip Архив +animated_java.dialog.blueprint_settings.resource_pack_export_mode.options.zip: ZIP-архив animated_java.dialog.blueprint_settings.resource_pack_export_mode.options.none: Ничего -animated_java.dialog.blueprint_settings.data_pack_export_mode.title: Режим Экспорта Датапака +animated_java.dialog.blueprint_settings.data_pack_export_mode.title: Режим экспорта датапака animated_java.dialog.blueprint_settings.data_pack_export_mode.options.folder: Папка -animated_java.dialog.blueprint_settings.data_pack_export_mode.options.zip: Zip Архив +animated_java.dialog.blueprint_settings.data_pack_export_mode.options.zip: ZIP-архив animated_java.dialog.blueprint_settings.data_pack_export_mode.options.none: Ничего -animated_java.dialog.blueprint_settings.target_minecraft_version.title: Выбранная Майнкрафт версия +animated_java.dialog.blueprint_settings.target_minecraft_version.title: Выбранная версия Minecraft animated_java.dialog.blueprint_settings.target_minecraft_version.description: |- - Версия Майнкрафта для которой будет экспортирован проект. + Версия Minecraft, для которой будет экспортирован проект. - Если версия которую вы используете недоступна, выберите ближайшую версию ниже нужной. + Если версия, которую вы используете недоступна, выберите ближайшую версию ниже нужной. - Т.е Если вы используете `1.21.8`, выберите `1.21.5`. + Т. е. Если вы используете `1.21.8`, выберите `1.21.5`. Некоторые функции могут быть изменены, или недоступны в зависимости от выбранной версии. @@ -142,28 +142,28 @@ animated_java.dialog.blueprint_settings.target_minecraft_version.description: |- # Resource Pack Settings animated_java.dialog.blueprint_settings.resource_pack_settings.title: Ресурспак -animated_java.dialog.blueprint_settings.enable_advanced_resource_pack_settings.title: Продвинутые настройки +animated_java.dialog.blueprint_settings.enable_advanced_resource_pack_settings.title: Расширенные настройки animated_java.dialog.blueprint_settings.enable_advanced_resource_pack_folders.title: Продвинутые папки -animated_java.dialog.blueprint_settings.display_item.title: Предмет дисплей +animated_java.dialog.blueprint_settings.display_item.title: Предмет-дисплей animated_java.dialog.blueprint_settings.display_item.description: |- - Предмет используемый для моделей рига. + Предмет, используемый для моделей рига. Множество чертежей в одном и том же проекте могут использовать один и тот же предмет, и они будут автоматически объединены при экспорте. animated_java.dialog.blueprint_settings.display_item.error.no_item_selected: Не выбран предмет! animated_java.dialog.blueprint_settings.display_item.error.invalid_item_id.no_namespace: Выбран недопустимый ID предмета! ID предметов должны быть в формате`namespace:item_id`. animated_java.dialog.blueprint_settings.display_item.error.invalid_item_id.whitespace: Выбран недопустимый ID предмета! ID предметов не должны содержать пробелов. -animated_java.dialog.blueprint_settings.display_item.warning.item_does_not_exist: Выбранный предмет не существует в ванильной игре! +animated_java.dialog.blueprint_settings.display_item.warning.item_does_not_exist: Выбранный предмет не существует в базовой игре! animated_java.dialog.blueprint_settings.display_item.warning.item_model_not_generated: Выбранный предмет не использует `minecraft:item/generated` в форме своего родителя. Это может привести к проблемам в игре. animated_java.dialog.blueprint_settings.display_item.error.item_model_not_found: |- Выбранный предмет не имеет файла модели в ванильном ресурспаке! Если вы предполагаете, что это ошибка, попробуйте перезапустить Blockbench, и подождать пока загрузка AJ пропадёт перед открытием чертежа. -animated_java.dialog.blueprint_settings.custom_model_data_offset.title: CMD Смещение +animated_java.dialog.blueprint_settings.custom_model_data_offset.title: Смещение CMD animated_java.dialog.blueprint_settings.custom_model_data_offset.description: |- - Смещает Custom Model Data (CMD) значения использованные в предикатах предмет дисплея на указанное значение. + Смещает значения Custom Model Data (CMD), используемые в предикатах предмет-дисплея, на указанное значение. animated_java.dialog.blueprint_settings.resource_pack.title: Ресурспак animated_java.dialog.blueprint_settings.resource_pack.description: |- @@ -176,8 +176,8 @@ animated_java.dialog.blueprint_settings.resource_pack.error.folder_does_not_exis animated_java.dialog.blueprint_settings.resource_pack.error.not_a_folder: Указанный путь не является папкой! animated_java.dialog.blueprint_settings.resource_pack.error.missing_pack_mcmeta: Выбранная папка не содержит файл pack.mcmeta! -animated_java.dialog.blueprint_settings.resource_pack_zip.title: Ресурспак Zip Архив -animated_java.dialog.blueprint_settings.resource_pack_zip.description: Путь к .zip архиву для экспорта. +animated_java.dialog.blueprint_settings.resource_pack_zip.title: Ресурспак ZIP-архив +animated_java.dialog.blueprint_settings.resource_pack_zip.description: Путь к .ZIP-архиву для экспорта. animated_java.dialog.blueprint_settings.resource_pack_zip.error.no_file_selected: Файл не выбран! animated_java.dialog.blueprint_settings.resource_pack_zip.error.not_a_file: Указанный путь не является файлом! @@ -186,7 +186,7 @@ animated_java.dialog.blueprint_settings.data_pack_settings.title: Датапак animated_java.dialog.blueprint_settings.data_pack.title: Датапак animated_java.dialog.blueprint_settings.data_pack.description: |- - Основная папка Датапака для экспорта. + Основная папка датапака для экспорта. Выбранный датапак должен иметь файл `pack.mcmeta`. animated_java.dialog.blueprint_settings.data_pack.warning.missing_data_folder: Выбранный датапак не содержит папку `data`! @@ -195,59 +195,59 @@ animated_java.dialog.blueprint_settings.data_pack.error.folder_does_not_exist: animated_java.dialog.blueprint_settings.data_pack.error.not_a_folder: Указанный путь не является папкой! animated_java.dialog.blueprint_settings.data_pack.error.missing_pack_mcmeta: Выбранная папка не содержит файл pack.mcmeta! -animated_java.dialog.blueprint_settings.data_pack_zip.title: Датапак Zip Архив -animated_java.dialog.blueprint_settings.data_pack_zip.description: Путь к .zip архиву для экспорта. +animated_java.dialog.blueprint_settings.data_pack_zip.title: Датапак ZIP-архив +animated_java.dialog.blueprint_settings.data_pack_zip.description: Путь к .ZIP-архиву для экспорта. animated_java.dialog.blueprint_settings.data_pack_zip.error.no_file_selected: Файл не выбран! animated_java.dialog.blueprint_settings.data_pack_zip.error.not_a_file: Указанный путь не является файлом! animated_java.dialog.blueprint_settings.on_summon_function.title: Функция при вызове animated_java.dialog.blueprint_settings.on_summon_function.description: |- - Команды выполняемые `as` (от лица) и `at` (на месте) сущности при вызове. + Команды, выполняемые `as` (от лица) и `at` (на месте) сущности при вызове. Воспринимайте это поле как `.mcfunction` файл. - Поддерживает [MC-Build](https://mcbuild.dev) синтакс. + Поддерживает [MC-Build](https://mcbuild.dev) синтаксис. animated_java.dialog.blueprint_settings.on_remove_function.title: Функция при удалении animated_java.dialog.blueprint_settings.on_remove_function.description: |- - Команды выполняемые `as` (от лица) и `at` (на месте) сущности при удалении. + Команды, выполняемые `as` (от лица) и `at` (на месте) сущности при удалении. Воспринимайте это поле как `.mcfunction` файл. - Поддерживает [MC-Build](https://mcbuild.dev) синтакс. + Поддерживает [MC-Build](https://mcbuild.dev) синтаксис. animated_java.dialog.blueprint_settings.on_pre_tick_function.title: Функция до тика animated_java.dialog.blueprint_settings.on_pre_tick_function.description: |- - Команды выполняемые `as` (от лица) и `at` (на месте) сущности *перед* тик логикой Animated Java. + Команды, выполняемые `as` (от лица) и `at` (на месте) сущности *перед* тик логикой Animated Java. Воспринимайте это поле как `.mcfunction` файл. - Поддерживает [MC-Build](https://mcbuild.dev) синтакс. + Поддерживает [MC-Build](https://mcbuild.dev) синтаксис. animated_java.dialog.blueprint_settings.on_post_tick_function.title: Функция после тика animated_java.dialog.blueprint_settings.on_post_tick_function.description: |- - Команды выполняемые `as` (от лица) и `at` (на месте) сущности *после* тик логики Animated Java. + Команды, выполняемые `as` (от лица) и `at` (на месте) сущности *после* тик логики Animated Java. Воспринимайте это поле как `.mcfunction` файл. - Поддерживает [MC-Build](https://mcbuild.dev) синтакс. + Поддерживает [MC-Build](https://mcbuild.dev) синтаксис. animated_java.dialog.blueprint_settings.interpolation_duration.title: Длительность Интерполяции animated_java.dialog.blueprint_settings.interpolation_duration.description: |- [Длительность интерполяции](https://minecraft.wiki/w/Display#Entity_data) рига по умолчанию. - Чем выше значение тем плавнее анимация, но меньше отзывчивость. + Чем выше значение, тем плавнее анимация, но меньше отзывчивость. animated_java.dialog.blueprint_settings.teleportation_duration.title: Длительность Телепортации animated_java.dialog.blueprint_settings.teleportation_duration.description: |- [Длительность телепортации](https://minecraft.wiki/w/Display#Entity_data) рига по умолчанию. - Чем выше значение тем плавнее крупные движения, но меньше отзывчивость. + Чем выше значение, тем плавнее крупные движения, но меньше отзывчивость. animated_java.dialog.blueprint_settings.auto_update_rig_orientation.title: Автообновление ориентации рига animated_java.dialog.blueprint_settings.auto_update_rig_orientation.description: |- Когда **включено**, синхронизирует позиции и повороты всех нодовых сущностей с корневой сущностью каждый тик. - Позволяет просто телепортировать сущность чтобы перемещать риг. + Позволяет просто телепортировать сущность, чтобы перемещать риг. Когда **выключено**, вам стоит использовать move функцию для перемещения рига: @@ -256,7 +256,7 @@ animated_java.dialog.blueprint_settings.auto_update_rig_orientation.description: positioned rotated run \ function animated_java:<пространство_имён>/move ``` - + Синхронизировать позиции и повороты нодов каждый тик может давать нагрузку, особенно для больших ригов. Отключение этого параметра может улучшить производительность жертвуя удобством. @@ -264,17 +264,17 @@ animated_java.dialog.blueprint_settings.use_storage_for_animation.title: Исп animated_java.dialog.blueprint_settings.use_storage_for_animation.description: |- Когда включено, NBT хранилище будет использовано для хранения данных анимации вместо функций. - Это значительно уменьшает количество файлов экспортированного датапака, будучи в разы менее производительным. + Это значительно уменьшает количество файлов экспортированного датапака, но снижает производительность. # Plugin Settings animated_java.dialog.blueprint_settings.baked_animations.title: Запечённые анимации animated_java.dialog.blueprint_settings.baked_animations.description: |- Запекать или нет экспортированные анимации. - Запечённые кадры анимаций заранее обрабатываются и сохраняются в экспортированный JSON файл, уменьшая сложности рендера модели в игре. + Запечённые кадры анимаций заранее обрабатываются и сохраняются в экспортированный JSON-файл, уменьшая сложности рендера модели в игре. Некоторые Плагины будут требовать эту опцию чтобы работать правильно. -animated_java.dialog.blueprint_settings.json_file.title: JSON Файл -animated_java.dialog.blueprint_settings.json_file.description: Путь к JSON файлу для экспорта. +animated_java.dialog.blueprint_settings.json_file.title: JSON-файл +animated_java.dialog.blueprint_settings.json_file.description: Путь к JSON-файлу для экспорта. animated_java.dialog.blueprint_settings.json_file.error.no_file_selected: Файл не выбран! animated_java.dialog.blueprint_settings.json_file.error.not_a_file: Указанный путь не является файлом! @@ -286,19 +286,19 @@ animated_java.dialog.display_entity.per_variant_options.title: Для каждо animated_java.dialog.display_entity.on_summon_function.title: Функция при вызове animated_java.dialog.display_entity.on_summon_function.description: |- - Команды выполняемые `as` (от лица) и `at` (на месте) дисплей-сущности при вызове. + Команды, выполняемые `as` (от лица) и `at` (на месте) дисплей-сущности при вызове. Воспринимайте это поле как `.mcfunction` файл. - Поддерживает [MC-Build](https://mcbuild.dev) синтакс. + Поддерживает [MC-Build](https://mcbuild.dev) синтаксис. animated_java.dialog.display_entity.on_apply_function.title: Функция при применении animated_java.dialog.display_entity.on_apply_function.description: |- - Команды выполняемые `as` (от лица) дисплей-сущности при применении варианта. + Команды, выполняемые `as` (от лица) дисплей-сущности при применении варианта. Воспринимайте это поле как `.mcfunction` файл. - Поддерживает [MC-Build](https://mcbuild.dev) синтакс. + Поддерживает [MC-Build](https://mcbuild.dev) синтаксис. animated_java.dialog.display_entity.custom_name.title: Имя animated_java.dialog.display_entity.custom_name.description: Пользовательское имя нода. @@ -316,7 +316,7 @@ animated_java.dialog.display_entity.override_glow_color.title: Собствен animated_java.dialog.display_entity.override_glow_color.description: Использовать ли собственный цвет свечения. animated_java.dialog.display_entity.glow_color.title: Цвет свечения -animated_java.dialog.display_entity.glow_color.description: Определяет окрас свечения нода. +animated_java.dialog.display_entity.glow_color.description: Определяет цвет свечения нода. animated_java.dialog.display_entity.shadow_radius.title: Радиус тени animated_java.dialog.display_entity.shadow_radius.description: Определяет радиус тени. @@ -331,7 +331,7 @@ animated_java.dialog.display_entity.brightness_override.title: Яркость animated_java.dialog.display_entity.brightness_override.description: Определяет яркость нода. Значение должно быть от 0 до 15. animated_java.dialog.display_entity.use_custom_brightness.title: Использовать собственную яркость -animated_java.dialog.display_entity.use_custom_brightness.description: Устанавливает собственную яркость нода.. +animated_java.dialog.display_entity.use_custom_brightness.description: Устанавливает собственную яркость нода. animated_java.dialog.display_entity.custom_brightness.title: Собственная яркость animated_java.dialog.display_entity.custom_brightness.description: Определяет яркость нода. Значение должно быть от 0 до 15. @@ -348,64 +348,64 @@ animated_java.dialog.display_entity.billboard.description: |- - **Фиксированный**: Вертикальные и горизонтальные оси фиксированы. - **Вертикальный**: Поворачивается по вертикали. - **Горизонтальный**: Поворачивается по горизонтали. - - **Центрированный**: Поворачивается по центру. + - **По центру**: Поворачивается по центру. animated_java.dialog.display_entity.billboard.options.fixed: Фиксированный animated_java.dialog.display_entity.billboard.options.vertical: Вертикальный animated_java.dialog.display_entity.billboard.options.horizontal: Горизонтальный -animated_java.dialog.display_entity.billboard.options.center: Центрированный +animated_java.dialog.display_entity.billboard.options.center: По центру ## Locator Config Dialog animated_java.dialog.locator_config.title: Настройки локатора animated_java.dialog.locator_config.plugin_mode_warning: |- - Режим Плагина включен! Локаторы не имеют настроек в режиме Плагина. - Вместо этого используйте Plugin API чтобы добавить функционал вашим Локаторам. - Подробнее о Plugin API можно узнать в Официальной Plugin API документации. + Режим плагина включён! Локаторы не имеют настроек в режиме плагина. + Вместо этого используйте Plugin API, чтобы добавить функциональность вашим локаторам. + Подробнее о Plugin API можно узнать в официальной документации Plugin API. animated_java.dialog.locator_config.use_entity.title: Использовать сущность animated_java.dialog.locator_config.use_entity.description: |- Когда включено, локатор создаст и будет использовать сущность вместо координат в игре. animated_java.dialog.locator_config.entity_type.title: Тип сущности -animated_java.dialog.locator_config.entity_type.description: Тип сущности который будет привязан к локатору. +animated_java.dialog.locator_config.entity_type.description: Тип сущности, который будет привязан к локатору. animated_java.dialog.locator_config.entity_type.error.empty: Тип сущности не может быть пустым! -animated_java.dialog.locator_config.entity_type.warning.invalid: Выбранный тип сущности не существует в майнкрафте {0} +animated_java.dialog.locator_config.entity_type.warning.invalid: Выбранный тип сущности не существует в Minecraft {0} -animated_java.dialog.locator_config.sync_passenger_rotation.title: Синхронизировать Поворот Пассажиров +animated_java.dialog.locator_config.sync_passenger_rotation.title: Синхронизировать поворот пассажиров animated_java.dialog.locator_config.sync_passenger_rotation.description: Автоматически синхронизирует поворот пассажиров локатора. animated_java.dialog.locator_config.on_summon_function.title: Функция при вызове animated_java.dialog.locator_config.on_summon_function.description: |- - Команды выполняемые `as` (от лица) корневой сущности и `at` (на месте) локатора при вызове. + Команды, выполняемые `as` (от лица) корневой сущности и `at` (на месте) локатора при вызове. - Поддерживает [MC-Build](https://mcbuild.dev) синтакс. + Поддерживает [MC-Build](https://mcbuild.dev) синтаксис. animated_java.dialog.locator_config.on_summon_function.description_with_use_entity: |- - Команды выполняемые `as` (от лица) корневой сущности и `at` (на месте) сущности локатора при вызове. + Команды, выполняемые `as` (от лица) корневой сущности и `at` (на месте) сущности локатора при вызове. - Поддерживает [MC-Build](https://mcbuild.dev) синтакс. + Поддерживает [MC-Build](https://mcbuild.dev) синтаксис. animated_java.dialog.locator_config.on_remove_function.title: Функция при удалении animated_java.dialog.locator_config.on_remove_function.description: |- - Команды выполняемые `as` (от лица) корневой сущности и `at` (на месте) локатора при удалении. + Команды, выполняемые `as` (от лица) корневой сущности и `at` (на месте) локатора при удалении. - Поддерживает [MC-Build](https://mcbuild.dev) синтакс. + Поддерживает [MC-Build](https://mcbuild.dev) синтаксис. animated_java.dialog.locator_config.on_remove_function.description_with_use_entity: |- - Команды выполняемые `as` (от лица) корневой сущности и `at` (на месте) сущности локатора при удалении. + Команды, выполняемые `as` (от лица) корневой сущности и `at` (на месте) сущности локатора при удалении. - Поддерживает [MC-Build](https://mcbuild.dev) синтакс. + Поддерживает [MC-Build](https://mcbuild.dev) синтаксис. animated_java.dialog.locator_config.on_tick_function.title: Тик функция animated_java.dialog.locator_config.on_tick_function.description: |- - Команды выполняемые `at` (на месте) локатора каждый тик. + Команды, выполняемые `at` (на месте) локатора каждый тик. - Поддерживает [MC-Build](https://mcbuild.dev) синтакс. + Поддерживает [MC-Build](https://mcbuild.dev) синтаксис. animated_java.dialog.locator_config.on_tick_function.description_with_use_entity: |- - Команды выполняемые `at` (на месте) сущности локатора каждый тик. + Команды, выполняемые `at` (на месте) сущности локатора каждый тик. - Поддерживает [MC-Build](https://mcbuild.dev) синтакс. + Поддерживает [MC-Build](https://mcbuild.dev) синтаксис. ## Text Display Config Dialog -animated_java.dialog.text_display_config.title: Настройка текст дисплея +animated_java.dialog.text_display_config.title: Настройка текст-дисплея animated_java.dialog.bone_config.vanilla_item_model.title: Ванильная модель предмета animated_java.dialog.bone_config.vanilla_item_model.description: |- @@ -413,20 +413,20 @@ animated_java.dialog.bone_config.vanilla_item_model.description: |- Это перезапишет существующие кубы кости. animated_java.dialog.text_display_config.use_nbt.title: Использовать NBT -animated_java.dialog.text_display_config.use_nbt.description: Использует NBT чтобы настроить текст дисплей сущность вместо настроек. +animated_java.dialog.text_display_config.use_nbt.description: Использует NBT, чтобы настроить текст-дисплей сущность вместо обычных настроек. animated_java.dialog.text_display_config.use_nbt.use_nbt_warning: Использование NBT перезапишет все другие настройки, и любые изменения не будут отображены в редакторе. Используйте только при необходимости! animated_java.dialog.text_display_config.inherit_settings.title: Наследовать настройки -animated_java.dialog.text_display_config.inherit_settings.description: Наследует настройки от родительской текст дисплей-сущности. +animated_java.dialog.text_display_config.inherit_settings.description: Наследует настройки от родительской текст-дисплей сущности. animated_java.dialog.text_display_config.glowing.title: Свечение -animated_java.dialog.text_display_config.glowing.description: Включает свечение текст дисплей-сущности в игре. +animated_java.dialog.text_display_config.glowing.description: Включает свечение текст-дисплей сущности в игре. animated_java.dialog.text_display_config.override_glow_color.title: Собственный цвет свечения animated_java.dialog.text_display_config.override_glow_color.description: Включает собственный цвет свечения в игре. animated_java.dialog.text_display_config.glow_color.title: Цвет свечения -animated_java.dialog.text_display_config.glow_color.description: Цвет свечения текст дисплей-сущности. +animated_java.dialog.text_display_config.glow_color.description: Цвет свечения текст-дисплей сущности. animated_java.dialog.text_display_config.shadow_radius.title: Радиус тени animated_java.dialog.text_display_config.shadow_radius.description: Определяет радиус тени. @@ -438,24 +438,24 @@ animated_java.dialog.text_display_config.override_brightness.title: Собств animated_java.dialog.text_display_config.override_brightness.description: Включает собственную яркость. animated_java.dialog.text_display_config.brightness_override.title: Яркость -animated_java.dialog.text_display_config.brightness_override.description: Яркость текст дисплей-сущности. Значение должно быть от 0 до 15. +animated_java.dialog.text_display_config.brightness_override.description: Яркость текст-дисплей сущности. Значение должно быть от 0 до 15. -animated_java.dialog.text_display_config.use_custom_brightness.title: Использовать Собственную Яркость +animated_java.dialog.text_display_config.use_custom_brightness.title: Использовать собственную яркость animated_java.dialog.text_display_config.use_custom_brightness.description: Включает собственную яркость для кости. animated_java.dialog.text_display_config.custom_brightness.title: Собственная яркость -animated_java.dialog.text_display_config.custom_brightness.description: Использует собственную яркость для кости. Значение должно быть от 0 до 15. +animated_java.dialog.text_display_config.custom_brightness.description: Использует собственную яркость. Значение должно быть от 0 до 15. animated_java.dialog.text_display_config.invisible.title: Невидимость -animated_java.dialog.text_display_config.invisible.description: Включать ли видимость текст дисплей-сущности в игре. +animated_java.dialog.text_display_config.invisible.description: Включать ли видимость текст-дисплей сущности в игре. animated_java.dialog.text_display_config.nbt.title: NBT -animated_java.dialog.text_display_config.nbt.description: NBT применимое к текст дисплей-сущности. +animated_java.dialog.text_display_config.nbt.description: NBT применимое к текст-дисплей сущности. ## Block Display Config Dialog -animated_java.dialog.vanilla_block_display_config.title: Настройки блок дисплея +animated_java.dialog.vanilla_block_display_config.title: Настройки блок-дисплея animated_java.dialog.vanilla_block_display.custom_name.title: Имя -animated_java.dialog.vanilla_block_display.custom_name.description: Пользовательское имя блок дисплея. +animated_java.dialog.vanilla_block_display.custom_name.description: Пользовательское имя блок-дисплея. animated_java.dialog.vanilla_block_display.custom_name.invalid_json.error: |- Неверный JSON текст! {0} @@ -464,9 +464,9 @@ animated_java.dialog.vanilla_block_display.custom_name_visible.title: Видим animated_java.dialog.vanilla_block_display.custom_name_visible.description: Включать ли видимость имени. ## Item Display Config Dialog -animated_java.dialog.vanilla_item_display_config.title: Настройки предмет дисплея +animated_java.dialog.vanilla_item_display_config.title: Настройки предмет-дисплея animated_java.dialog.vanilla_item_display.custom_name.title: Имя -animated_java.dialog.vanilla_item_display.custom_name.description: Пользовательское имя блок дисплея. +animated_java.dialog.vanilla_item_display.custom_name.description: Пользовательское имя предмет-дисплея. animated_java.dialog.vanilla_item_display.custom_name.invalid_json.error: |- Неверный JSON текст! {0} @@ -478,7 +478,7 @@ animated_java.dialog.vanilla_item_display.custom_name_visible.description: Вк animated_java.dialog.variant_config.title: Настройки вариантов animated_java.dialog.variant_config.variant_display_name: Отображаемое имя -animated_java.dialog.variant_config.variant_display_name.description: Используется в редакторе и в сообщениях ошибок. +animated_java.dialog.variant_config.variant_display_name.description: Используется в редакторе и в сообщениях об ошибках. animated_java.dialog.variant_config.generate_name_from_display_name: Сгенерировать имя из отображаемого имени animated_java.dialog.variant_config.generate_name_from_display_name.description: Автоматически генерирует имя на основе отображаемого имени. @@ -488,19 +488,19 @@ animated_java.dialog.variant_config.variant_name.description: Используе animated_java.dialog.variant_config.texture_map.title: Текстурная карта animated_java.dialog.variant_config.texture_map.description: Карта текстур, которые будут заменены при применении этого варианта. -animated_java.dialog.variant_config.texture_map.create_new_mapping: Создать новый маппинг +animated_java.dialog.variant_config.texture_map.create_new_mapping: Создать новое сопоставление animated_java.dialog.variant_config.texture_map.no_mappings: У варианта нет заменяемых текстур. animated_java.dialog.variant_config.bone_lists.description: |- - Укажите ноды которые должны быть изменены при применении варианта. + Укажите ноды, которые должны быть изменены при применении варианта. Ноды указанные в списке будут изменены при применении варианта. Ноды не указанные в списке останутся нетронутыми. animated_java.dialog.variant_config.excluded_nodes.title: Исключенные ноды -animated_java.dialog.variant_config.excluded_nodes.description: Список нодов которые вариант должен игнорировать. Эти ноды останутся нетронутыми. +animated_java.dialog.variant_config.excluded_nodes.description: Список нодов, которые вариант должен игнорировать. Эти ноды останутся нетронутыми. animated_java.dialog.variant_config.included_nodes.title: Включенные ноды -animated_java.dialog.variant_config.included_nodes.description: Список нодов которые вариант должен изменить. Только эти ноды будут изменены. +animated_java.dialog.variant_config.included_nodes.description: Список нодов, которые вариант должен изменить. Только эти ноды будут изменены. animated_java.dialog.variant_config.swap_columns_button.tooltip: Поменять местами ## Old AJModel Loader Dialog @@ -531,18 +531,18 @@ animated_java.dialog.animation_properties.loop_delay.title: Задержка ц animated_java.dialog.animation_properties.loop_delay.description: |- Задержка в тиках перед повторным запуском анимации в режиме Цикл. - Т.е. Значение 20 значит, что анимация застынет на 1 секунду перед повторным запуском. + Т. е. Значение 20 значит, что анимация застынет на 1 секунду перед повторным запуском. animated_java.dialog.animation_properties.bone_lists.description: |- - Укажите ноды которые анимация должна изменить. + Укажите ноды, которые анимация должна изменить. Ноды указанные в списке будут изменены анимацией. Ноды не указанные в списке останутся нетронутыми. animated_java.dialog.animation_properties.excluded_nodes.title: Исключенные ноды -animated_java.dialog.animation_properties.excluded_nodes.description: Список нодов которые анимация будет игнорировать. Эти ноды останутся нетронутыми. +animated_java.dialog.animation_properties.excluded_nodes.description: Список нодов, которые анимация будет игнорировать. Эти ноды останутся нетронутыми. animated_java.dialog.animation_properties.included_nodes.title: Включенные ноды -animated_java.dialog.animation_properties.included_nodes.description: Список нодов которые анимация будет изменять. Только эти ноды будут изменены. +animated_java.dialog.animation_properties.included_nodes.description: Список нодов, которые анимация будет изменять. Только эти ноды будут изменены. animated_java.dialog.animation_properties.swap_columns_button.tooltip: Поменять местами ## Export Progress Dialog @@ -594,18 +594,18 @@ animated_java.panel.keyframe.variant.description: Вариант примени animated_java.panel.keyframe.function.title: Функция animated_java.panel.keyframe.function.description: |- - Команды выполняемые при достижении ключевого кадра. + Команды, выполняемые при достижении ключевого кадра. Воспринимайте это поле как `.mcfunction` файл. - Поддерживает [MC-Build](https://mcbuild.dev) синтакс. + Поддерживает [MC-Build](https://mcbuild.dev) синтаксис. animated_java.panel.keyframe.execute_condition.title: Условие Execute animated_java.panel.keyframe.execute_condition.description: |- Условие при котором ключевой кадр будет выполнен. Воспринимайте это поле как `execute if` команду. - Т.е. `if score @s myScore matches 1..` + Т. е. `if score @s myScore matches 1..` animated_java.panel.keyframe.repeat.title: Повтор animated_java.panel.keyframe.repeat.description: |- @@ -617,7 +617,7 @@ animated_java.panel.keyframe.repeat_frequency.description: |- Поставьте 1 чтобы запускать каждый тик анимации. animated_java.panel.keyframe.easing_type.title: Тип сглаживания -animated_java.panel.keyframe.easing_type.description: The type of easing to apply to the keyframe. +animated_java.panel.keyframe.easing_type.description: Тип сглаживания, применяемый к ключевому кадру. animated_java.panel.keyframe.easing_type.options.linear: Линейное animated_java.panel.keyframe.easing_type.options.sine: Синусоидальное animated_java.panel.keyframe.easing_type.options.quad: Квадратичное @@ -637,7 +637,7 @@ animated_java.panel.keyframe.easing_mode.options.out: В конце animated_java.panel.keyframe.easing_mode.options.inout: В начале и в конце animated_java.panel.keyframe.easing_args.title: Аргументы сглаживания -animated_java.panel.keyframe.easing_args.description: Агрументы применимые к функции сглаживания. +animated_java.panel.keyframe.easing_args.description: Аргументы, применимые к функции сглаживания. animated_java.panel.keyframe.easing_args.easing_arg.elastic.title: Эластичность animated_java.panel.keyframe.easing_args.easing_arg.elastic.description: Эластичность функции сглаживания. animated_java.panel.keyframe.easing_args.easing_arg.back.title: Перерастяжение @@ -646,17 +646,17 @@ animated_java.panel.keyframe.easing_args.easing_arg.bounce.title: Прыгуче animated_java.panel.keyframe.easing_args.easing_arg.bounce.description: Определяет степень прыгучести функции сглаживания. animated_java.panel.keyframe.nonlinear_interpolation: |- - Продвинутые настройки сглаживания отключены. + Расширенные настройки сглаживания отключены. Смените режим интерполяции ключевого кадра на линейный для включения. # Text Display Panel -animated_java.panel.text_display.title: Текст дисплей +animated_java.panel.text_display.title: Текст-дисплей animated_java.tool.text_display.line_width.title: Длина строки -animated_java.tool.text_display.line_width.description: Ширина текст дисплея в пикселях. +animated_java.tool.text_display.line_width.description: Ширина текст-дисплея в пикселях. animated_java.tool.text_display.background_color.title: Цвет фона -animated_java.tool.text_display.background_color.description: Цвет фона текст дисплея. +animated_java.tool.text_display.background_color.description: Цвет фона текст-дисплея. animated_java.tool.text_display.text_shadow.title: Тень текста animated_java.tool.text_display.text_shadow.description: Включает отображение тени текста. @@ -695,7 +695,7 @@ animated_java.panel.vanilla_block_display.description: Блок, который ### Custom Elements ## Item Display -animated_java.vanilla_item_display.title: Предмет дисплей +animated_java.vanilla_item_display.title: Предмет-дисплей ## Block Display @@ -710,17 +710,17 @@ animated_java.misc.failed_to_export.button: Ок animated_java.misc.failed_to_export.invalid_rotation.message: |- Некоторые кубы в вашей модели имеют неправильные повороты! - Кубы должны иметь поворот в -45, -22.5, 0, 22.5, или 45 градусов, и могут быть повёрнуты только на одной оси при выборе Майнкрафт версий ниже 1.21.6. + Кубы должны иметь поворот в -45, -22.5, 0, 22.5, или 45 градусов, и могут быть повёрнуты только на одной оси при выборе Minecraft версий ниже 1.21.6. - Если хотите повернуть куб более точно, или на множистве осей, переместите куб в кость и поворачивайте её вместо куба. + Если хотите повернуть куб более точно, или на множестве осей, переместите куб в кость и поворачивайте её вместо куба. Все недопустимые кубы подсвечены красным. Пожалуйста исправьте их перед экспортом. animated_java.misc.failed_to_export.invalid_rotation.message_post_1_21_6: |- Некоторые кубы в вашей модели имеют неправильные повороты! - Кубы могут быть повёрнуты только на одной оси при выборе Майнкрафт версии 1.21.6 или выше. + Кубы могут быть повёрнуты только на одной оси при выборе Minecraft версии 1.21.6 или выше. - Если хотите повернуть куб на множистве осей, переместите куб в кость и поворачивайте её вместо куба. + Если хотите повернуть куб на множестве осей, переместите куб в кость и поворачивайте её вместо куба. Все недопустимые кубы подсвечены красным. Пожалуйста исправьте их перед экспортом. animated_java.misc.failed_to_export.rig_has_textures_but_no_custom_models.message: |- @@ -733,14 +733,14 @@ animated_java.misc.failed_to_export.rig_has_custom_models_but_no_textures.messag animated_java.toast.invalid_rotations: |- Неверный поворот кубов! - Кубы должны иметь поворот в -45, -22.5, 0, 22.5, или 45 градусов, и могут быть повёрнуты только на одной оси при выборе Майнкрафт версий ниже 1.21.6. + Кубы должны иметь поворот в -45, -22.5, 0, 22.5, или 45 градусов, и могут быть повёрнуты только на одной оси при выборе Minecraft версий ниже 1.21.6. Все недопустимые кубы подсвечены красным. animated_java.toast.invalid_rotations_post_1_21_6: |- Неверный поворот кубов! - Кубы могут быть повёрнуты только на одной оси при выборе Майнкрафт версии 1.21.6 и выше. + Кубы могут быть повёрнуты только на одной оси при выборе Minecraft версии 1.21.6 и выше. Все недопустимые кубы подсвечены красным. @@ -748,13 +748,13 @@ animated_java.toast.invalid_rotations_post_1_21_6: |- animated_java.format_category.animated_java: Animated Java # Model Manager Warnings -animated_java.block_model_manager.fluid_warning: Жидкости не рендерятся в блок дисплеях. -animated_java.block_model_manager.mob_head_warning: Головы мобов не рендерятся в блок дисплеях. Используйте предмет дисплей. -animated_java.block_model_manager.facing_warning: Состояние "facing" не поддерживается в блок дисплеях. +animated_java.block_model_manager.fluid_warning: Жидкости не рендерятся в блок-дисплеях. +animated_java.block_model_manager.mob_head_warning: Головы мобов не рендерятся в блок-дисплеях. Используйте предмет-дисплей. +animated_java.block_model_manager.facing_warning: Состояние "facing" не поддерживается в блок-дисплеях. # Project Errors animated_java.error.blueprint_export_path_doesnt_exist.title: Путь экспорта чертежа не существует. animated_java.error.blueprint_export_path_doesnt_exist.description: |- Путь экспорта чертежа '{0}' не существует! - Убедитесь что папка в которую происходит экспорт существует и попробуйте снова. + Убедитесь, что папка, в которую происходит экспорт, существует, и попробуйте снова. From e9d3f76c5836ce5d9839962b1f816e91796d99bf Mon Sep 17 00:00:00 2001 From: Koishem Date: Wed, 25 Mar 2026 06:59:45 +0100 Subject: [PATCH 21/23] =?UTF-8?q?=F0=9F=94=A4=20Translate=20AJ=20to=20Ukra?= =?UTF-8?q?inian?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lang/uk.yml | 759 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 759 insertions(+) create mode 100644 src/lang/uk.yml diff --git a/src/lang/uk.yml b/src/lang/uk.yml new file mode 100644 index 00000000..2e07bb79 --- /dev/null +++ b/src/lang/uk.yml @@ -0,0 +1,759 @@ +animated_java.menubar.label: Animated Java + +animated_java.format.blueprint.name: Креслення + +### Actions +animated_java.action.open_blueprint_settings.name: Налаштування креслення +animated_java.action.open_documentation.name: Документація +animated_java.action.open_changelog.name: Журнал змін +animated_java.action.open_about.name: Про нас +animated_java.action.open_display_entity_config.name: Налаштування дисплей-сутностей +animated_java.action.open_locator_config.name: Налаштування локатора +animated_java.action.export.name: Експорт +animated_java.action.export_debug.name: Експорт (Налагодження) +animated_java.action.export_all.name: Експорт усього +animated_java.action.export_all.description: |- + Експортувати усі відкриті креслення. +animated_java.action.export_all_debug.name: Експорт усього (Налагодження) +animated_java.action.export_all_debug.description: |- + Експортувати усі відкриті креслення у режимі налагодження. +animated_java.action.extract.name: Екстрактувати +animated_java.action.extract.confirm: Підтвердити екстрактування +animated_java.action.create_text_display.title: Додавання текст-дисплея +animated_java.action.create_vanilla_item_display.title: Додавання предмет-дисплея +animated_java.action.create_vanilla_block_display.title: Додавання блок-дисплея +animated_java.action.copy_display_entity_config.name: Копіювати налаштування дисплей-сутності +animated_java.action.copy_display_entity_config.message: Скопійовано налаштування дисплей-сутності з "{0}" +animated_java.action.paste_display_entity_config.name: Вставити налаштування дисплей-сутності +animated_java.action.paste_display_entity_config.message: Вставлено налаштування дисплей-сутності з "{0}" + +### Popups +animated_java.popup.loading.loading: Завантаження Animated Java... +animated_java.popup.loading.success: Animated Java завантажено успішно! +animated_java.popup.loading.offline: |- + Не вдалося підключити Animated Java! + Деякі функції можуть бути недоступними. + +animated_java.popup.installed_popup.title: Дякуємо за встановлення! +animated_java.popup.installed_popup.close_button: Почнемо анімувати! + +animated_java.popup.incompatability_popup.title: Виявлено несумісність Animated Java +animated_java.popup.incompatability_popup.description: |- + У вас встановлені плагіни, які спричиняють проблеми з Animated Java. + Будь ласка, вимкніть або видаліть ці плагіни та перезапустіть Blockbench, щоб використовувати Animated Java: +animated_java.popup.incompatability_popup.disable_button: Вимкнути плагін +animated_java.popup.incompatability_popup.button.disable_all: Вимкнути усі несумісні плагіни +animated_java.popup.incompatability_popup.button.ignore: Ігнорувати та продовжити (не рекомендується) +animated_java.plugin_dialog.incompatability_notice: |- + Цей плагін спричиняє проблеми з Animated Java. + + Ви не зможете встановити цей плагін, доки встановлено Animated Java. + +### Dialogs +animated_java.dialog.reset: Скинути до заводських налаштувань + +## About +animated_java.dialog.about.title: Про Animated Java +animated_java.dialog.about.close_button: Закрити + +## Changelog +animated_java.dialog.changelog_dialog.title: Animated Java Журнал змін + +## Unexpected Error Dialog +animated_java.dialog.unexpected_error.title: Сталася непередбачена помилка! +animated_java.dialog.unexpected_error.close_button: Закрити +animated_java.dialog.unexpected_error.copy_error_message_button.message: Повідомлення про помилку скопійовано до буфера обміну! +animated_java.dialog.unexpected_error.copy_error_message_button.description: Натисніть, щоб скопіювати повідомлення про помилку. +animated_java.dialog.unexpected_error.paragraph: 'Будь ласка, повідомте про цю помилку в нашому {0} та створіть квиток у каналі #animated-java-support або створіть завдання на нашому {1}. Дякуємо!' +## Blueprint Settings Dialog +animated_java.dialog.blueprint_settings.title: Налаштування креслення +animated_java.dialog.blueprint_settings.project_settings.title: Проєкт + +animated_java.dialog.blueprint_settings.advanced_settings_warning: Розширені налаштування слід використовувати лише за необхідності! + +animated_java.dialog.blueprint_settings.project_name.title: Назва +animated_java.dialog.blueprint_settings.project_name.description: |- + Назва файлу креслення. + + Це буде перезаписано, якщо ви збережете креслення під іншою назвою. + +animated_java.dialog.blueprint_settings.texture_size.title: Розмір текстури +animated_java.dialog.blueprint_settings.texture_size.description: |- + Роздільна здатність редактора UV. + + Це має відповідати розміру найбільшої текстури у вашому кресленні. +animated_java.dialog.blueprint_settings.texture_size.warning.not_square: Розмір текстури має бути квадратним! +animated_java.dialog.blueprint_settings.texture_size.warning.not_a_power_of_2: Розмір текстури має бути степенем числа 2, якщо це можливо! +animated_java.dialog.blueprint_settings.texture_size.warning.does_not_match_largest_texture: Обраний розмір текстури не відповідає розміру найбільшої текстури у вашому кресленні! + +# Export Settings +animated_java.dialog.blueprint_settings.export_settings.title: Експорт + +animated_java.dialog.blueprint_settings.export_namespace.title: Простір імен +animated_java.dialog.blueprint_settings.export_namespace.description: |- + Простір імен, який буде використано під час генерації ресурспаку та датапаку. + + Рекомендується використовувати унікальний простір імен, щоб уникнути конфліктів з іншими ресурспаками та датапаками. +animated_java.dialog.blueprint_settings.export_namespace.error.empty: Простір імен експорту не може бути порожнім! +animated_java.dialog.blueprint_settings.export_namespace.error.reserved: Простір імен експорту "{0}" зарезервовано для внутрішньої роботи плагіна! Будь ласка, виберіть інший простір імен. +animated_java.dialog.blueprint_settings.export_namespace.error.invalid_characters: Простір імен експорту містить заборонені символи! Простори імен можуть містити лише літери, цифри та нижнє підкреслення. + +animated_java.dialog.blueprint_settings.show_render_box.title: Показати область рендерингу +animated_java.dialog.blueprint_settings.show_render_box.description: Якщо увімкнено, відображає коробку рендерингу в редакторі. + +animated_java.dialog.blueprint_settings.auto_render_box.title: Авто-розмір області рендерингу +animated_java.dialog.blueprint_settings.auto_render_box.description: |- + Якщо увімкнено, область рендерингу автоматично розраховуватиметься на основі геометрії моделі. +animated_java.dialog.blueprint_settings.render_box.title: Розмір області рендерингу +animated_java.dialog.blueprint_settings.render_box.description: |- + [Розмір та Висота](https://minecraft.wiki/w/Display#Entity_data) дисплей сутності рига. + +animated_java.dialog.blueprint_settings.view_range.title: Область огляду +animated_java.dialog.blueprint_settings.view_range.description: |- + [Область огляду](https://minecraft.wiki/w/Display#Entity_data) рига за замовчуванням. + +animated_java.dialog.blueprint_settings.enable_plugin_mode.title: Режим плагіна +animated_java.dialog.blueprint_settings.enable_plugin_mode.description: |- + Якщо увімкнено, проєкт буде експортований у вигляді файлу JSON спеціально для використання плагінами. + +animated_java.dialog.blueprint_settings.resource_pack_export_mode.title: Режим експорту ресурспаку +animated_java.dialog.blueprint_settings.resource_pack_export_mode.options.folder: Папка +animated_java.dialog.blueprint_settings.resource_pack_export_mode.options.zip: ZIP-архів +animated_java.dialog.blueprint_settings.resource_pack_export_mode.options.none: Нічого + +animated_java.dialog.blueprint_settings.data_pack_export_mode.title: Режим експорту датапака +animated_java.dialog.blueprint_settings.data_pack_export_mode.options.folder: Папка +animated_java.dialog.blueprint_settings.data_pack_export_mode.options.zip: ZIP-архів +animated_java.dialog.blueprint_settings.data_pack_export_mode.options.none: Нічого + +animated_java.dialog.blueprint_settings.target_minecraft_version.title: Вибрана Minecraft версія +animated_java.dialog.blueprint_settings.target_minecraft_version.description: |- + Версія Minecraft, для якої буде експортовано проєкт. + + Якщо версія, яку ви використовуєте, недоступна, виберіть найближчу версію нижче потрібної. + + Тобто, якщо ви використовуєте версію `1.21.8`, виберіть `1.21.5`. + + Деякі функції можуть бути змінені або недоступні залежно від обраної версії. + + Animated Java повідомить вас про будь-які зміни, які будуть необхідні. + +# Resource Pack Settings +animated_java.dialog.blueprint_settings.resource_pack_settings.title: Ресурспак + +animated_java.dialog.blueprint_settings.enable_advanced_resource_pack_settings.title: Розширені налаштування + +animated_java.dialog.blueprint_settings.enable_advanced_resource_pack_folders.title: Розширені папки + +animated_java.dialog.blueprint_settings.display_item.title: Предмет-дисплей +animated_java.dialog.blueprint_settings.display_item.description: |- + Предмет, що використовується для моделей рига. + + Багато креслень в одному й тому ж проєкті можуть використовувати один і той самий об’єкт, і вони будуть автоматично об’єднані під час експорту. +animated_java.dialog.blueprint_settings.display_item.error.no_item_selected: Предмет не обрано! +animated_java.dialog.blueprint_settings.display_item.error.invalid_item_id.no_namespace: Вибрано неприпустимий ID предмета! ID предметів повинні бути у форматі `namespace:item_id`. +animated_java.dialog.blueprint_settings.display_item.error.invalid_item_id.whitespace: Вибрано неприпустимий ID предмета! ID предметів не повинні містити пробілів. +animated_java.dialog.blueprint_settings.display_item.warning.item_does_not_exist: Обраний предмет не існує у базовій версії гри! +animated_java.dialog.blueprint_settings.display_item.warning.item_model_not_generated: Вибраний предмет не використовує `minecraft:item/generated` як свій батьківський предмет. Це може спричинити проблеми в грі. +animated_java.dialog.blueprint_settings.display_item.error.item_model_not_found: |- + Вибраний предмет не має файлу моделі у базовому ресурспаку! + + Якщо ви вважаєте, що це помилка, спробуйте перезапустити Blockbench і почекати, поки завантаження AJ завершиться, перш ніж відкривати креслення. + +animated_java.dialog.blueprint_settings.custom_model_data_offset.title: CMD Зсув +animated_java.dialog.blueprint_settings.custom_model_data_offset.description: |- + Зміщує значення Custom Model Data (CMD), що використовуються в предикатах об’єкта відображення, на вказане значення. + +animated_java.dialog.blueprint_settings.resource_pack.title: Ресурспак +animated_java.dialog.blueprint_settings.resource_pack.description: |- + Основна папка ресурспаку для експорту. + + Обраний ресурспак повинен містити файл `pack.mcmeta`. +animated_java.dialog.blueprint_settings.resource_pack.warning.missing_assets_folder: Обраний ресурспак не містить папку `assets`! +animated_java.dialog.blueprint_settings.resource_pack.error.no_folder_selected: Папка не обрана! +animated_java.dialog.blueprint_settings.resource_pack.error.folder_does_not_exist: Вибрана папка не існує! +animated_java.dialog.blueprint_settings.resource_pack.error.not_a_folder: Вказаний шлях не є папкою! +animated_java.dialog.blueprint_settings.resource_pack.error.missing_pack_mcmeta: У вибраній папці немає файлу pack.mcmeta! + +animated_java.dialog.blueprint_settings.resource_pack_zip.title: Ресурспак ZIP-архів +animated_java.dialog.blueprint_settings.resource_pack_zip.description: Шлях до архіву .zip для експорту. +animated_java.dialog.blueprint_settings.resource_pack_zip.error.no_file_selected: Файл не обрано! +animated_java.dialog.blueprint_settings.resource_pack_zip.error.not_a_file: Вказаний шлях не є файлом! + +# Data Pack Settings +animated_java.dialog.blueprint_settings.data_pack_settings.title: Датапак + +animated_java.dialog.blueprint_settings.data_pack.title: Датапак +animated_java.dialog.blueprint_settings.data_pack.description: |- + Основна папка датапака для експорту. + + Обраний датапак повинен містити файл `pack.mcmeta`. +animated_java.dialog.blueprint_settings.data_pack.warning.missing_data_folder: Обраний датапак не містить папку `data`! +animated_java.dialog.blueprint_settings.data_pack.error.no_folder_selected: Папка не обрана! +animated_java.dialog.blueprint_settings.data_pack.error.folder_does_not_exist: Вибрана папка не існує! +animated_java.dialog.blueprint_settings.data_pack.error.not_a_folder: Вказаний шлях не є папкою! +animated_java.dialog.blueprint_settings.data_pack.error.missing_pack_mcmeta: У вибраній папці немає файлу pack.mcmeta! + +animated_java.dialog.blueprint_settings.data_pack_zip.title: Датапак ZIP-архів +animated_java.dialog.blueprint_settings.data_pack_zip.description: Шлях до архіву .zip для експорту. +animated_java.dialog.blueprint_settings.data_pack_zip.error.no_file_selected: Файл не обрано! +animated_java.dialog.blueprint_settings.data_pack_zip.error.not_a_file: Вказаний шлях не є файлом! + +animated_java.dialog.blueprint_settings.on_summon_function.title: Функція при виклику +animated_java.dialog.blueprint_settings.on_summon_function.description: |- + Команди, що виконуються `as` (від імені) та `at` (на місці) сутності під час виклику. + + Сприймайте це поле як файл `.mcfunction`. + + Підтримує синтаксис [MC-Build](https://mcbuild.dev). + +animated_java.dialog.blueprint_settings.on_remove_function.title: Функція під час видалення +animated_java.dialog.blueprint_settings.on_remove_function.description: |- + Команди, що виконуються `as` (від імені) та `at` (на місці) сутності під час видалення. + + Сприймайте це поле як файл `.mcfunction`. + + Підтримує синтаксис [MC-Build](https://mcbuild.dev). + +animated_java.dialog.blueprint_settings.on_pre_tick_function.title: Функція до тіка +animated_java.dialog.blueprint_settings.on_pre_tick_function.description: |- + Команди, що виконуються `as` (від імені) та `at` (на місці) сутності *перед* тік-логікою Animated Java. + + Сприймайте це поле як файл `.mcfunction`. + + Підтримує синтаксис [MC-Build](https://mcbuild.dev). + +animated_java.dialog.blueprint_settings.on_post_tick_function.title: Функція після тіка +animated_java.dialog.blueprint_settings.on_post_tick_function.description: |- + Команди, що виконуються `as` (від імені) та `at` (на місці) сутності *після* тік-логіки Animated Java. + + Сприймайте це поле як файл `.mcfunction`. + + Підтримує синтаксис [MC-Build](https://mcbuild.dev). + +animated_java.dialog.blueprint_settings.interpolation_duration.title: Тривалість інтерполяції +animated_java.dialog.blueprint_settings.interpolation_duration.description: |- + [Тривалість інтерполяції](https://minecraft.wiki/w/Display#Entity_data) рига за замовчуванням. + + Чим вище значення, тим плавніша анімація, але менша чутливість. + +animated_java.dialog.blueprint_settings.teleportation_duration.title: Тривалість телепортації +animated_java.dialog.blueprint_settings.teleportation_duration.description: |- + [Тривалість телепортації](https://minecraft.wiki/w/Display#Entity_data) рига за замовчуванням. + + Чим вище значення, тим плавніші великі рухи, але менша чутливість. + +animated_java.dialog.blueprint_settings.auto_update_rig_orientation.title: Автоматичне оновлення орієнтації рига +animated_java.dialog.blueprint_settings.auto_update_rig_orientation.description: |- + Коли **увімкнено**, синхронізує позиції та обертання всіх нодових об’єктів із кореневим об’єктом щотика. + Дозволяє просто телепортувати сутність, щоб перемістити риг. + + Коли **вимкнено**, вам слід використовувати функцію move для переміщення рига: + + ``` + execute as @e[tag=aj.<простір_імен>_root] \ + positioned rotated run \ + function animated_java:<простір_імен>/move + ``` + + Синхронізація позицій і обертання нодів щотика може створювати навантаження, особливо для великих ригів. + Вимкнення цього параметра може підвищити продуктивність, але за рахунок зручності. + +animated_java.dialog.blueprint_settings.use_storage_for_animation.title: Використовувати сховище для анімації +animated_java.dialog.blueprint_settings.use_storage_for_animation.description: |- + Якщо увімкнено, сховище NBT використовуватиметься для зберігання даних анімації замість функцій. + + Це значно зменшує обсяг експортованого датапаку, хоча й є набагато менш ефективним. + +# Plugin Settings +animated_java.dialog.blueprint_settings.baked_animations.title: Запечені анімації +animated_java.dialog.blueprint_settings.baked_animations.description: |- + Чи запікати експортовані анімації. + Запечені кадри анімації заздалегідь обробляються та зберігаються в експортованому файлі JSON, що зменшує складність рендерингу моделі в грі. + Деякі плагіни потребуватимуть цю опцію для коректної роботи. + +animated_java.dialog.blueprint_settings.json_file.title: JSON-файл +animated_java.dialog.blueprint_settings.json_file.description: Шлях до JSON-файлу для експорту. +animated_java.dialog.blueprint_settings.json_file.error.no_file_selected: Файл не обрано! +animated_java.dialog.blueprint_settings.json_file.error.not_a_file: Вказаний шлях не є файлом! + +## Bone Config Dialog +animated_java.dialog.display_entity.title: Налаштування дисплей сутності для "{0}" + +animated_java.dialog.display_entity.node_options.title: Нод +animated_java.dialog.display_entity.per_variant_options.title: Для кожного варіанту + +animated_java.dialog.display_entity.on_summon_function.title: Функція при виклику +animated_java.dialog.display_entity.on_summon_function.description: |- + Команди, що виконуються `as` (від імені) та `at` (на місці) дисплей сутності під час виклику. + + Сприймайте це поле як файл `.mcfunction`. + + Підтримує синтаксис [MC-Build](https://mcbuild.dev). + +animated_java.dialog.display_entity.on_apply_function.title: Функція при застосуванні +animated_java.dialog.display_entity.on_apply_function.description: |- + Команди, що виконуються `as` (від імені) сутності дисплея під час застосування варіанту. + + Сприймайте це поле як файл `.mcfunction`. + + Підтримує синтаксис [MC-Build](https://mcbuild.dev). + +animated_java.dialog.display_entity.custom_name.title: Назва +animated_java.dialog.display_entity.custom_name.description: Користувацька назва нода. +animated_java.dialog.display_entity.custom_name.invalid_json.error: |- + Неправильний текст JSON! + {0} + +animated_java.dialog.display_entity.custom_name_visible.title: Видимість імені +animated_java.dialog.display_entity.custom_name_visible.description: Чи вмикати відображення імені. + +animated_java.dialog.display_entity.glowing.title: Свічення +animated_java.dialog.display_entity.glowing.description: Чи вмикати світіння нода в грі. + +animated_java.dialog.display_entity.override_glow_color.title: Користувацький колір свічення +animated_java.dialog.display_entity.override_glow_color.description: Чи використовувати користувацький колір світіння. + +animated_java.dialog.display_entity.glow_color.title: Колір світіння +animated_java.dialog.display_entity.glow_color.description: Визначає колір світіння нода. + +animated_java.dialog.display_entity.shadow_radius.title: Радіус тіні +animated_java.dialog.display_entity.shadow_radius.description: Визначає радіус тіні. + +animated_java.dialog.display_entity.shadow_strength.title: Інтенсивність тіні +animated_java.dialog.display_entity.shadow_strength.description: Визначає інтенсивність тіні. + +animated_java.dialog.display_entity.override_brightness.title: Користувацька яскравість +animated_java.dialog.display_entity.override_brightness.description: Встановлює користувацьку яскравість нода. + +animated_java.dialog.display_entity.brightness_override.title: Яскравість +animated_java.dialog.display_entity.brightness_override.description: Визначає яскравість нода. Значення має бути від 0 до 15. + +animated_java.dialog.display_entity.use_custom_brightness.title: Використовувати користувацьку яскравість +animated_java.dialog.display_entity.use_custom_brightness.description: Встановлює користувацьку яскравість нода. + +animated_java.dialog.display_entity.custom_brightness.title: Користувацька яскравість +animated_java.dialog.display_entity.custom_brightness.description: Визначає яскравість нода. Значення має бути від 0 до 15. + +animated_java.dialog.display_entity.enchanted.title: Зачарований +animated_java.dialog.display_entity.enchanted.description: Чи вмикати ефект зачарованості нода. + +animated_java.dialog.display_entity.invisible.title: Невидимість +animated_java.dialog.display_entity.invisible.description: Чи робити нод невидимим у грі. + +animated_java.dialog.display_entity.billboard.title: Білборд +animated_java.dialog.display_entity.billboard.description: |- + Налаштовує обертання у бік погляду гравця під час рендерингу. + - **Фіксований**: Вертикальна та горизонтальна осі зафіксовані. + - **Вертикальний**: Повертається по вертикалі. + - **Горизонтальний**: Повертається по горизонталі. + - **По центру**: Повертається по центру. +animated_java.dialog.display_entity.billboard.options.fixed: Фіксований +animated_java.dialog.display_entity.billboard.options.vertical: Вертикальний +animated_java.dialog.display_entity.billboard.options.horizontal: Горизонтальний +animated_java.dialog.display_entity.billboard.options.center: По центру + +## Locator Config Dialog +animated_java.dialog.locator_config.title: Налаштування локатора + +animated_java.dialog.locator_config.plugin_mode_warning: |- + Режим плагіна увімкнено! Локатори не мають налаштувань у режимі плагіна. + Замість цього скористайтеся Plugin API, щоб додати функціонал до ваших локаторів. + Детальніше про Plugin API можна дізнатися в офіційній документації Plugin API. + +animated_java.dialog.locator_config.use_entity.title: Використовувати сутність +animated_java.dialog.locator_config.use_entity.description: |- + Якщо увімкнено, локатор створить і використовуватиме сутність замість координат у грі. + +animated_java.dialog.locator_config.entity_type.title: Тип сутності +animated_java.dialog.locator_config.entity_type.description: Тип сутності, який буде прив’язаний до локатора. +animated_java.dialog.locator_config.entity_type.error.empty: Тип сутності не може бути порожнім! +animated_java.dialog.locator_config.entity_type.warning.invalid: Вибраний тип сутності не існує в Minecraft {0} + +animated_java.dialog.locator_config.sync_passenger_rotation.title: Синхронізувати поворот пасажирів +animated_java.dialog.locator_config.sync_passenger_rotation.description: Автоматично синхронізує поворот пасажирів локатора. + +animated_java.dialog.locator_config.on_summon_function.title: Функція при виклику +animated_java.dialog.locator_config.on_summon_function.description: |- + Команди, що виконуються `as` (від імені) кореневої сутності та `at` (на місці) локатора під час виклику. + + Підтримує синтаксис [MC-Build](https://mcbuild.dev). +animated_java.dialog.locator_config.on_summon_function.description_with_use_entity: |- + Команди, що виконуються `as` (від імені) кореневої сутності та `at` (на місці) сутності локатора під час виклику. + + Підтримує синтаксис [MC-Build](https://mcbuild.dev). + +animated_java.dialog.locator_config.on_remove_function.title: Функція при видаленні +animated_java.dialog.locator_config.on_remove_function.description: |- + Команди, що виконуються `as` (від імені) кореневого об’єкта та `at` (на місці) локатора під час видалення. + + Підтримує синтаксис [MC-Build](https://mcbuild.dev). +animated_java.dialog.locator_config.on_remove_function.description_with_use_entity: |- + Команди, що виконуються `as` (від імені) кореневого об’єкта та `at` (на місці) об’єкта-локатора під час видалення. + + Підтримує синтаксис [MC-Build](https://mcbuild.dev). + +animated_java.dialog.locator_config.on_tick_function.title: Тік функція +animated_java.dialog.locator_config.on_tick_function.description: |- + Команди, що виконуються `at` (на місці) локатора щотика. + + Підтримує синтаксис [MC-Build](https://mcbuild.dev). +animated_java.dialog.locator_config.on_tick_function.description_with_use_entity: |- + Команди, що виконуються `at` (на місці) сутності локатора щотика. + + Підтримує синтаксис [MC-Build](https://mcbuild.dev). + +## Text Display Config Dialog +animated_java.dialog.text_display_config.title: Налаштування текст-дисплея + +animated_java.dialog.bone_config.vanilla_item_model.title: Ванільна модель предмета +animated_java.dialog.bone_config.vanilla_item_model.description: |- + Якщо предмет обрано, кістка відображатиметься як звичайний предмет. + Це перезапише існуючі куби кістки. + +animated_java.dialog.text_display_config.use_nbt.title: Використовувати NBT +animated_java.dialog.text_display_config.use_nbt.description: Використовує NBT для налаштування текст-дисплей сутності замість налаштувань. +animated_java.dialog.text_display_config.use_nbt.use_nbt_warning: Використання NBT замінить усі інші налаштування, і будь-які зміни не відображатимуться в редакторі. Використовуйте лише за потреби! + +animated_java.dialog.text_display_config.inherit_settings.title: Успадкувати налаштування +animated_java.dialog.text_display_config.inherit_settings.description: Успадковує налаштування від батьківської сутності текст-дисплея. + +animated_java.dialog.text_display_config.glowing.title: Свічення +animated_java.dialog.text_display_config.glowing.description: Вмикає підсвічування тексту в грі. + +animated_java.dialog.text_display_config.override_glow_color.title: Користувацький колір свічення +animated_java.dialog.text_display_config.override_glow_color.description: Вмикає користувацький колір свічення в грі. + +animated_java.dialog.text_display_config.glow_color.title: Колір свічення +animated_java.dialog.text_display_config.glow_color.description: Колір свічення текст-дисплей сутності. + +animated_java.dialog.text_display_config.shadow_radius.title: Радіус тіні +animated_java.dialog.text_display_config.shadow_radius.description: Визначає радіус тіні. + +animated_java.dialog.text_display_config.shadow_strength.title: Інтенсивність тіні +animated_java.dialog.text_display_config.shadow_strength.description: Визначає інтенсивність тіні. + +animated_java.dialog.text_display_config.override_brightness.title: Користувацька яскравість +animated_java.dialog.text_display_config.override_brightness.description: Вмикає користувацьку яскравість. + +animated_java.dialog.text_display_config.brightness_override.title: Яскравість +animated_java.dialog.text_display_config.brightness_override.description: Яскравість текст-дисплей сутності. Значення має бути від 0 до 15. + +animated_java.dialog.text_display_config.use_custom_brightness.title: Використовувати користувацьку яскравість +animated_java.dialog.text_display_config.use_custom_brightness.description: Вмикає власну яскравість для кістки. + +animated_java.dialog.text_display_config.custom_brightness.title: Користувацька яскравість +animated_java.dialog.text_display_config.custom_brightness.description: Використовує користувацьку яскравість текст-дисплей сутності. Значення має бути від 0 до 15. + +animated_java.dialog.text_display_config.invisible.title: Невидимість +animated_java.dialog.text_display_config.invisible.description: Чи слід увімкнути відображення тексту сутності дисплея в грі. + +animated_java.dialog.text_display_config.nbt.title: NBT +animated_java.dialog.text_display_config.nbt.description: NBT, що застосовується до текст-дисплей сутності. + +## Block Display Config Dialog +animated_java.dialog.vanilla_block_display_config.title: Налаштування блок-дисплея +animated_java.dialog.vanilla_block_display.custom_name.title: Назва +animated_java.dialog.vanilla_block_display.custom_name.description: Користувацька назва блок-дисплея. +animated_java.dialog.vanilla_block_display.custom_name.invalid_json.error: |- + Неправильний текст JSON! + {0} + +animated_java.dialog.vanilla_block_display.custom_name_visible.title: Видимість імені +animated_java.dialog.vanilla_block_display.custom_name_visible.description: Чи вмикати відображення імені. + +## Item Display Config Dialog +animated_java.dialog.vanilla_item_display_config.title: Налаштування предмет-дисплея +animated_java.dialog.vanilla_item_display.custom_name.title: Назва +animated_java.dialog.vanilla_item_display.custom_name.description: Користувацьке назва предмет-дисплея. +animated_java.dialog.vanilla_item_display.custom_name.invalid_json.error: |- + Неправильний текст JSON! + {0} + +animated_java.dialog.vanilla_item_display.custom_name_visible.title: Видимість імені +animated_java.dialog.vanilla_item_display.custom_name_visible.description: Чи слід увімкнути постійну видимість імені. + +## Variant Config Dialog +animated_java.dialog.variant_config.title: Налаштування варіантів + +animated_java.dialog.variant_config.variant_display_name: Назва для відображення +animated_java.dialog.variant_config.variant_display_name.description: Використовується в редакторі та в повідомленнях про помилки. + +animated_java.dialog.variant_config.generate_name_from_display_name: Створити назву на основі назви для відображення +animated_java.dialog.variant_config.generate_name_from_display_name.description: Автоматично генерує назва на основі назви для відображення + +animated_java.dialog.variant_config.variant_name: Назва +animated_java.dialog.variant_config.variant_name.description: Використовується в датапаку та ресурспаку. + +animated_java.dialog.variant_config.texture_map.title: Текстурна карта +animated_java.dialog.variant_config.texture_map.description: Карта текстур, які будуть замінені при застосуванні цього варіанту. +animated_java.dialog.variant_config.texture_map.create_new_mapping: Створити нове зіставлення +animated_java.dialog.variant_config.texture_map.no_mappings: У цього варіанту немає замінних текстур. + +animated_java.dialog.variant_config.bone_lists.description: |- + Вкажіть ноди, які мають бути змінені при застосуванні цього варіанту. + + Елементи, зазначені у списку, будуть змінені при застосуванні цього варіанту. + + Ноди, яких немає у списку, залишаться недоторканими. +animated_java.dialog.variant_config.excluded_nodes.title: Виключені ноди +animated_java.dialog.variant_config.excluded_nodes.description: Список нодів, які варіант повинен ігнорувати. Ці ноди залишаться недоторканими. +animated_java.dialog.variant_config.included_nodes.title: Увімкнені ноди +animated_java.dialog.variant_config.included_nodes.description: Список нодів, які варіант повинен змінити. Змінюватимуться лише ці ноди. +animated_java.dialog.variant_config.swap_columns_button.tooltip: Поміняти місцями + +## Old AJModel Loader Dialog +animated_java.action.upgrade_old_aj_model_loader.name: Оновити .ajmodel +animated_java.dialog.upgrade_old_aj_model_loader.title: Оновити .ajmodel +animated_java.action.upgrade_old_aj_model_loader.select_file: Вибрати .ajmodel файл +animated_java.action.upgrade_old_aj_model_loader.body: Оновити свої застарілі файли .ajmodel до нового формату .ajblueprint. +animated_java.action.upgrade_old_aj_model_loader.button: Виберіть файл .ajmodel для оновлення + +## Animation Properties Dialog +animated_java.dialog.animation_properties.title: Властивості анімації ({0}) + +animated_java.dialog.animation_properties.animation_name.title: Назва анімації +animated_java.dialog.animation_properties.animation_name.description: Використовується в датапаку. + +animated_java.dialog.animation_properties.loop_mode.title: Режим циклу +animated_java.dialog.animation_properties.loop_mode.description: |- + - Одноразовий: анімація відтвориться один раз, а потім повернеться до першого кадру. + - Затримка: анімація відтвориться один раз і залишиться на останньому кадрі. + - Цикл: анімація буде повторюватися нескінченно. +animated_java.dialog.animation_properties.loop_mode.options.once: Одноразовий +animated_java.dialog.animation_properties.loop_mode.options.hold: Затримка +animated_java.dialog.animation_properties.loop_mode.options.loop: Цикл +animated_java.dialog.animation_properties.animation_name.error.empty: Назва анімації не може бути порожньою! +animated_java.dialog.animation_properties.animation_name.error.invalid_characters: Назва анімації містить недопустимі символи! Назви анімацій можуть містити лише літери, цифри, підкреслення та крапки. + +animated_java.dialog.animation_properties.loop_delay.title: Затримка циклу +animated_java.dialog.animation_properties.loop_delay.description: |- + Затримка в тіках перед повторним запуском анімації в режимі "Цикл". + + Тобто значення 20 означає, що анімація зупиниться на 1 секунду перед повторним запуском. + +animated_java.dialog.animation_properties.bone_lists.description: |- + Вкажіть ноди, які має змінити анімація. + + Ноди, зазначені у списку, будуть змінені за допомогою анімації. + + Ноди, яких немає у списку, залишаться недоторканими. +animated_java.dialog.animation_properties.excluded_nodes.title: Виключені ноди +animated_java.dialog.animation_properties.excluded_nodes.description: Список нодів, які анімація ігноруватиме. Ці ноди залишаться незмінними. +animated_java.dialog.animation_properties.included_nodes.title: Увімкнені ноди +animated_java.dialog.animation_properties.included_nodes.description: Список нодів, які анімація змінюватиме. Тільки ці ноди будуть змінені. +animated_java.dialog.animation_properties.swap_columns_button.tooltip: Поміняти місцями + +## Export Progress Dialog +animated_java.dialog.export_progress.title: Експортування... + +## Blueprint Loading Dialog +animated_java.dialog.blueprint_loading.title: Завантаження креслення... + +### Panels + +## Variants Panel +animated_java.panel.variants.title: Варіанти +animated_java.panel.variants.tool.create_new_variant: Створити новий варіант +animated_java.panel.variants.tool.edit_variant: Налаштувати варіант +animated_java.panel.variants.tool.duplicate_selected_variant: Дублювати вибраний варіант +animated_java.panel.variants.tool.delete_selected_variant: Видалити вибраний варіант +animated_java.panel.variants.tool.variant_visible: Варіант обрано +animated_java.panel.variants.tool.variant_not_visible: Варіант не обрано +animated_java.panel.variants.tool.cannot_delete_default_variant: Не можна видалити варіант за замовчуванням! +animated_java.panel.variants.tool.cannot_edit_default_variant: Не вдалося змінити варіант за замовчуванням! + +animated_java.action.variants.create: Створити варіант +animated_java.action.variants.duplicate: Дублювати варіант +animated_java.action.variants.open_config: Відкрити налаштування варіанту +animated_java.action.variants.delete: Видалити варіант + +### Animator + +## Properties +animated_java.animation.excluded_nodes: Виключені ноди +animated_java.animation.invert_excluded_nodes: Інвертувати виключені ноди + +## Timeline +animated_java.effect_animator.timeline.variant: Варіант +animated_java.effect_animator.timeline.function: Функція + +## Keyframes +animated_java.effect_animator.keyframe_data_point.variant: Варіант +animated_java.effect_animator.keyframe_data_point.function: Функція +animated_java.effect_animator.keyframe_data_point.execute_condition: Умова виконання (Execute) +animated_java.effect_animator.keyframe_data_point.repeat: Повтор +animated_java.effect_animator.keyframe_data_point.repeat_frequency: Інтервал повтору + +# Keyframe Panel +animated_java.panel.keyframe.keyframe_title: Ключовий кадр ({0}) + +animated_java.panel.keyframe.variant.title: Варіант +animated_java.panel.keyframe.variant.description: Варіант, що застосовується до ключового кадру. + +animated_java.panel.keyframe.function.title: Функція +animated_java.panel.keyframe.function.description: |- + Команди, що виконуються при досягненні ключового кадру. + + Сприймайте це поле як файл `.mcfunction`. + + Підтримує синтаксис [MC-Build](https://mcbuild.dev). +animated_java.panel.keyframe.execute_condition.title: Умова виконання (Execute) +animated_java.panel.keyframe.execute_condition.description: |- + Умова, за якої буде виконано ключовий кадр. + + Сприймайте це поле як файл `.mcfunction`. + + Тобто `if score @s myScore дорівнює 1..` + +animated_java.panel.keyframe.repeat.title: Повтор +animated_java.panel.keyframe.repeat.description: |- + Якщо ця опція увімкнена, команди в цьому ключовому кадрі будуть повторюватися з заданим інтервалом. +animated_java.panel.keyframe.repeat_frequency.title: Інтервал +animated_java.panel.keyframe.repeat_frequency.description: |- + Кількість тіків між повторенням команди. + + Поставте 1, щоб запускати з кожним тіком анімації. + +animated_java.panel.keyframe.easing_type.title: Тип плавності +animated_java.panel.keyframe.easing_type.description: Тип плавності, який застосовується до ключового кадру. +animated_java.panel.keyframe.easing_type.options.linear: Лінійне +animated_java.panel.keyframe.easing_type.options.sine: Синусоїдальне +animated_java.panel.keyframe.easing_type.options.quad: Квадратичне +animated_java.panel.keyframe.easing_type.options.cubic: Кубічне +animated_java.panel.keyframe.easing_type.options.quart: Біквадратичне +animated_java.panel.keyframe.easing_type.options.quint: Бікубічне +animated_java.panel.keyframe.easing_type.options.expo: Експоненціальне +animated_java.panel.keyframe.easing_type.options.circ: Циркулярне +animated_java.panel.keyframe.easing_type.options.elastic: Еластичне +animated_java.panel.keyframe.easing_type.options.back: Відтягування +animated_java.panel.keyframe.easing_type.options.bounce: Пружинисте + +animated_java.panel.keyframe.easing_mode.title: Режим плавності +animated_java.panel.keyframe.easing_mode.description: Режим застосування плавності до ключового кадру. +animated_java.panel.keyframe.easing_mode.options.in: На початку +animated_java.panel.keyframe.easing_mode.options.out: В кінці +animated_java.panel.keyframe.easing_mode.options.inout: На початку та в кінці + +animated_java.panel.keyframe.easing_args.title: Аргументи плавності +animated_java.panel.keyframe.easing_args.description: Аргументи, що застосовуються до функції плавності. +animated_java.panel.keyframe.easing_args.easing_arg.elastic.title: Еластичність +animated_java.panel.keyframe.easing_args.easing_arg.elastic.description: Еластичність функції плавності. +animated_java.panel.keyframe.easing_args.easing_arg.back.title: Амплітуда відтягування +animated_java.panel.keyframe.easing_args.easing_arg.back.description: Визначає ступінь амплітуди відтягування функції плавності. +animated_java.panel.keyframe.easing_args.easing_arg.bounce.title: Пружність +animated_java.panel.keyframe.easing_args.easing_arg.bounce.description: Визначає ступінь пружності функції плавності. + +animated_java.panel.keyframe.nonlinear_interpolation: |- + Розширені налаштування згладжування вимкнені. + Змініть режим інтерполяції ключового кадру на лінійний, щоб увімкнути цю функцію. + +# Text Display Panel +animated_java.panel.text_display.title: Текст-дисплей + +animated_java.tool.text_display.line_width.title: Ширина рядка +animated_java.tool.text_display.line_width.description: Ширина текст-дисплея в пікселях. + +animated_java.tool.text_display.background_color.title: Колір фону +animated_java.tool.text_display.background_color.description: Колір фону текст-дисплея. + +animated_java.tool.text_display.text_shadow.title: Тінь текста +animated_java.tool.text_display.text_shadow.description: Вмикає відображення тіні тексту. + +animated_java.tool.text_display.text_alignment.title: Вирівнювання тексту +animated_java.tool.text_display.text_alignment.description: Визначає вирівнювання тексту. +animated_java.tool.text_display.text_alignment.options.left: По лівому краю +animated_java.tool.text_display.text_alignment.options.center: По центру +animated_java.tool.text_display.text_alignment.options.right: По правому краю + +animated_java.tool.text_display.see_through.title: Прозорість +animated_java.tool.text_display.see_through.description: Визначає, чи буде текст видно крізь блоки. + +animated_java.tool.text_display.copy_text.title: Копіювати експортований текстовий компонент +animated_java.tool.text_display.copy_text.description: Копіює експортований текстовий компонент у буфер обміну. +animated_java.tool.text_display.copy_text.copied: Текст скопійовано в буфер обміну! + +# Item Display Panel +animated_java.panel.vanilla_item_display.title: Предмет-дисплей +animated_java.panel.vanilla_item_display.description: Предмет, який буде відображатися. +animated_java.tool.item_display.item_display.title: Режим відображення предмета +animated_java.tool.item_display.item_display.description: Визначає, яка трансформація моделі буде застосована до предмета (як зазначено в полі «display» JSON-файлу моделі). +animated_java.tool.item_display.item_display.options.none: Ні +animated_java.tool.item_display.item_display.options.thirdperson_lefthand: Від третьої особи (ліва рука) +animated_java.tool.item_display.item_display.options.thirdperson_righthand: Від третьої особи (права рука) +animated_java.tool.item_display.item_display.options.firstperson_lefthand: Від першої особи (ліва рука) +animated_java.tool.item_display.item_display.options.firstperson_righthand: Від першої особи (права рука) +animated_java.tool.item_display.item_display.options.head: Голова +animated_java.tool.item_display.item_display.options.gui: Інтерфейс (GUI) +animated_java.tool.item_display.item_display.options.ground: На землі +animated_java.tool.item_display.item_display.options.fixed: Фіксовано + +# Block Display Panel +animated_java.panel.vanilla_block_display.title: Блок, що відображається +animated_java.panel.vanilla_block_display.description: Блок, який буде відображатися. Підтримуються стани блоків! + +### Custom Elements +## Item Display +animated_java.vanilla_item_display.title: Предмет-дисплей + +## Block Display + +### Misc + +# Blueprint Setting Errors - Failed to Export Message Box +animated_java.misc.failed_to_export.title: Не вдалося експортувати +animated_java.misc.failed_to_export.custom_models.message: Ви вимкнули експорт ресурспаку, але у вашому проєкті є користувацькі моделі! Будь ласка, увімкніть експорт ресурспаку або видаліть моделі перед експортом. +animated_java.misc.failed_to_export.blueprint_settings.message: У налаштуваннях креслення виявлено помилки! Будь ласка, виправте їх перед експортом. +animated_java.misc.failed_to_export.blueprint_settings.error_item: 'Виявлено помилку з {0}:' +animated_java.misc.failed_to_export.button: ОК +animated_java.misc.failed_to_export.invalid_rotation.message: |- + Деякі куби у вашій моделі мають неправильні кути повороту! + + Куби повинні мати кут повороту -45, -22,5, 0, 22,5 або 45 градусів і можуть бути повернуті лише навколо однієї осі, якщо вибрано версії Minecraft нижче 1.21.6. + + Якщо ви хочете повернути куб точніше або на кількох осях, перемістіть куб у кістку і повертайте її замість куба. + + Усі неприпустимі куби підсвічені червоним. Будь ласка, виправте їх перед експортом. +animated_java.misc.failed_to_export.invalid_rotation.message_post_1_21_6: |- + Деякі куби у вашій моделі мають неправильні кути повороту! + + Куби повинні бути повернуті лише навколо однієї осі, якщо вибрано версії Minecraft вище 1.21.6. + + Якщо ви хочете повернути куб точніше або на кількох осях, перемістіть куб у кістку і повертайте її замість куба. + + Усі неприпустимі куби підсвічені червоним. Будь ласка, виправте їх перед експортом. +animated_java.misc.failed_to_export.rig_has_textures_but_no_custom_models.message: |- + У вашій моделі є текстури, але немає користувацьких моделей (кубів), до яких їх можна застосувати! + Будь ласка, створіть куби та використовуйте ці текстури, або видаліть текстури перед експортом. +animated_java.misc.failed_to_export.rig_has_custom_models_but_no_textures.message: |- + У вашій моделі є користувацькі моделі (куби), але немає текстур, які можна застосувати до них! + Будь ласка, додайте текстури до ваших кубів або видаліть їх перед експортом. + +animated_java.toast.invalid_rotations: |- + Неправильний поворот кубів! + + Куби повинні мати поворот у -45, -22,5, 0, 22,5 або 45 градусів і можуть бути повернуті лише навколо однієї осі, якщо вибрано версії Minecraft нижче 1.21.6. + + Усі неприпустимі куби підсвічені червоним. + +animated_java.toast.invalid_rotations_post_1_21_6: |- + Неправильний поворот кубів! + + Куби можуть бути повернуті тільки навколо однієї осі при виборі версії Minecraft 1.21.6 і вище. + + Усі неприпустимі куби підсвічені червоним. + +# Format Category +animated_java.format_category.animated_java: Animated Java + +# Model Manager Warnings +animated_java.block_model_manager.fluid_warning: Рідини не відображаються у блок-дисплеях. +animated_java.block_model_manager.mob_head_warning: Голови мобів не відображаються у блок-дисплеях. Використовуйте предмет-дисплей. +animated_java.block_model_manager.facing_warning: Стан «facing» не підтримується в блок-дисплеях. + +# Project Errors +animated_java.error.blueprint_export_path_doesnt_exist.title: Шлях експорту креслення не існує. +animated_java.error.blueprint_export_path_doesnt_exist.description: |- + Шлях експорту креслення '{0}' не існує! + + Переконайтеся, що папка, в яку відбувається експорт, існує, і спробуйте ще раз. From 77f756ce70d9b0117760e8021bf809611a48eac7 Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Fri, 27 Mar 2026 12:02:48 -0400 Subject: [PATCH 22/23] =?UTF-8?q?=F0=9F=94=96=20v1.9.0-beta.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/pluginPackage/changelog.json | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index b32b2078..2f5e3a93 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.9.0-beta.2", + "version": "1.9.0-beta.3", "min_blockbench_version": "4.12.6", "max_blockbench_version": "4.12.6", "variant": "desktop", diff --git a/src/pluginPackage/changelog.json b/src/pluginPackage/changelog.json index c388da10..48ab8455 100644 --- a/src/pluginPackage/changelog.json +++ b/src/pluginPackage/changelog.json @@ -650,5 +650,23 @@ ] } ] + }, + "1.9.0-beta.3": { + "title": "v1.9.0-beta.3", + "author": "Titus Evans (SnaveSutit)", + "date": "2026-03-27", + "categories": [ + { + "title": "Changes", + "list": [ + "Updated Russian translations to improve consistency and punctuation. - Thanks Koishem!", + "Re-enabled Plugin mode - WARNING: This is experimental, and is using a new plugin JSON schema that will likely change in the future. - Thanks Meekiavelique!" + ] + }, + { + "title": "Fixes", + "list": [] + } + ] } } From e5ecc9acd65f34f861c3ddb142679e0e7bf191bb Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Fri, 27 Mar 2026 13:16:33 -0400 Subject: [PATCH 23/23] =?UTF-8?q?=E2=9C=A8=20Add=20bisect=20hosting=20bann?= =?UTF-8?q?er=20to=20Blueprint=20settings.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../banners/bisect-hosting-promo-banner.png | Bin 0 -> 156545 bytes ...pup.svelte => blueprintSettingsAds.svelte} | 31 ++ src/components/blueprintSettingsDialog.svelte | 467 +++++++++--------- src/interface/dialog/blueprintSettings.ts | 4 +- src/pluginPackage/changelog.json | 3 +- 5 files changed, 275 insertions(+), 230 deletions(-) create mode 100644 src/assets/banners/bisect-hosting-promo-banner.png rename src/components/{kofiPopup.svelte => blueprintSettingsAds.svelte} (78%) diff --git a/src/assets/banners/bisect-hosting-promo-banner.png b/src/assets/banners/bisect-hosting-promo-banner.png new file mode 100644 index 0000000000000000000000000000000000000000..b545f9fe2a492b176ec41770624c48b1ba9bb6b6 GIT binary patch literal 156545 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�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

a`^bD~u<*Rb#+Snv}$si!GRUpr~-S?%ed>9e+9 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 literal 0 HcmV?d00001 diff --git a/src/components/kofiPopup.svelte b/src/components/blueprintSettingsAds.svelte similarity index 78% rename from src/components/kofiPopup.svelte rename to src/components/blueprintSettingsAds.svelte index 99e747a7..396dd11a 100644 --- a/src/components/kofiPopup.svelte +++ b/src/components/blueprintSettingsAds.svelte @@ -1,4 +1,5 @@ - - - - - - - - - - -{#if !$autoRenderBox} +

+ + + + -{/if} - - - - - - - -{#if $enablePluginMode} - - -{:else} - - {#if $resourcePackExportMode !== 'none'} - {#if $DISPLAY_ITEM_REQUIRED} - - {/if} + - {#if $resourcePackExportMode === 'folder'} - - {:else if $resourcePackExportMode === 'zip'} - - {/if} + {#if $enablePluginMode} + - {#if $enableAdvancedResourcePackSettings} -

- {translate('dialog.blueprint_settings.advanced_settings_warning')} -

+ + {:else} + + + {#if $resourcePackExportMode !== 'none'} + {#if $DISPLAY_ITEM_REQUIRED} + + {/if} + + {#if $resourcePackExportMode === 'folder'} + + {:else if $resourcePackExportMode === 'zip'} + + {/if} + + + + {#if $enableAdvancedResourcePackSettings} +

+ {translate('dialog.blueprint_settings.advanced_settings_warning')} +

+ + + {/if} {/if} - {/if} - + - - {#if $dataPackExportMode !== 'none'} - {#if $dataPackExportMode === 'folder'} - - {:else if $dataPackExportMode === 'zip'} - + {:else if $dataPackExportMode === 'zip'} + + {/if} + + - {/if} - - - + - + - + - + - + - + - + + {/if} {/if} -{/if} +