diff --git a/src/blockbenchTypeMods.d.ts b/src/blockbenchTypeMods.d.ts
index c937d7c4..f526024b 100644
--- a/src/blockbenchTypeMods.d.ts
+++ b/src/blockbenchTypeMods.d.ts
@@ -23,8 +23,9 @@ declare global {
enable_data_pack: boolean
enable_advanced_data_pack_settings: boolean
data_pack: string
- root_entity_summon_commands: string
+ custom_summon_commands: string
}
+ last_used_export_namespace: string
variants: Variant[]
}
diff --git a/src/blueprintFormat.ts b/src/blueprintFormat.ts
index 9cc25af3..13753fa9 100644
--- a/src/blueprintFormat.ts
+++ b/src/blueprintFormat.ts
@@ -85,6 +85,7 @@ export interface IBlueprintFormatJSON {
box_uv: boolean
backup: boolean
save_location: string
+ last_used_export_namespace: string
}
/**
* The project settings of the Blueprint
@@ -174,6 +175,9 @@ export const BLUEPRINT_CODEC = new Blockbench.Codec('animated_java_blueprint', {
Project.animated_java = { ...Project.animated_java, ...model.project_settings }
}
+ Project.last_used_export_namespace =
+ model.meta.last_used_export_namespace || Project.animated_java.export_namespace
+
if (model.textures) {
for (const texture of model.textures) {
const newTexture = new Texture(texture, texture.uuid).add(false)
@@ -301,6 +305,7 @@ export const BLUEPRINT_CODEC = new Blockbench.Codec('animated_java_blueprint', {
format_version: PACKAGE.version,
uuid: Project.uuid,
save_location: Project.save_path,
+ last_used_export_namespace: Project.last_used_export_namespace,
},
project_settings: Project.animated_java,
resolution: {
@@ -446,6 +451,7 @@ export const BLUEPRINT_FORMAT = new Blockbench.ModelFormat({
}
Project.variants ??= []
+ Project.last_used_export_namespace = Project.animated_java.export_namespace
if (newModel) {
new Variant('Default', true)
}
@@ -508,3 +514,9 @@ BLUEPRINT_CODEC.format = BLUEPRINT_FORMAT
export function isCurrentFormat() {
return Format.id === BLUEPRINT_FORMAT.id
}
+
+export function saveBlueprint() {
+ if (!Project || !Format) return
+ if (Format !== BLUEPRINT_FORMAT) return
+ BLUEPRINT_CODEC.write(BLUEPRINT_CODEC.compile(), Project.save_path)
+}
diff --git a/src/blueprintSettings.ts b/src/blueprintSettings.ts
index 2b111df5..e2164907 100644
--- a/src/blueprintSettings.ts
+++ b/src/blueprintSettings.ts
@@ -1,10 +1,10 @@
export const defaultValues: ModelProject['animated_java'] = {
- export_namespace: '',
+ export_namespace: 'blueprint',
// Plugin Settings
enable_plugin_mode: false,
// Resource Pack Settings
enable_resource_pack: true,
- display_item: '',
+ display_item: 'minecraft:white_dye',
customModelDataOffset: 0,
enable_advanced_resource_pack_settings: false,
resource_pack: '',
@@ -15,5 +15,5 @@ export const defaultValues: ModelProject['animated_java'] = {
enable_data_pack: true,
enable_advanced_data_pack_settings: false,
data_pack: '',
- root_entity_summon_commands: '',
+ custom_summon_commands: '',
}
diff --git a/src/components/blueprintSettingsDialog.svelte b/src/components/blueprintSettingsDialog.svelte
index 8db94115..0efe15f5 100644
--- a/src/components/blueprintSettingsDialog.svelte
+++ b/src/components/blueprintSettingsDialog.svelte
@@ -381,10 +381,8 @@
/>
{/if}
{/if}
diff --git a/src/interface/blueprintSettingsDialog.ts b/src/interface/blueprintSettingsDialog.ts
index 739801bd..3b2aceb4 100644
--- a/src/interface/blueprintSettingsDialog.ts
+++ b/src/interface/blueprintSettingsDialog.ts
@@ -67,7 +67,7 @@ function getSettings(): Record> {
Project!.animated_java.enable_advanced_data_pack_settings
),
dataPack: new Valuable(Project!.animated_java.data_pack),
- rootEntitySummonCommands: new Valuable(Project!.animated_java.root_entity_summon_commands),
+ rootEntitySummonCommands: new Valuable(Project!.animated_java.custom_summon_commands),
}
}
@@ -94,7 +94,7 @@ function setSettings(settings: any) {
Project.animated_java.enable_advanced_data_pack_settings =
settings.enableAdvancedDataPackSettings.get()
Project.animated_java.data_pack = settings.dataPack.get()
- Project.animated_java.root_entity_summon_commands = settings.rootEntitySummonCommands.get()
+ Project.animated_java.custom_summon_commands = settings.rootEntitySummonCommands.get()
console.log('Successfully saved project settings', Project)
}
diff --git a/src/lang/en.yml b/src/lang/en.yml
index c7e6a418..b53d45f1 100644
--- a/src/lang/en.yml
+++ b/src/lang/en.yml
@@ -97,8 +97,8 @@ animated_java.dialog.blueprint_settings.data_pack.error.not_a_folder: The select
animated_java.dialog.blueprint_settings.data_pack.error.missing_pack_mcmeta: The selected folder is missing a pack.mcmeta file!
animated_java.dialog.blueprint_settings.data_pack.error.missing_data_folder: The selected Data Pack is missing a data folder!
-animated_java.dialog.blueprint_settings.root_entity_summon_commands.title: Custom On-Summon Commands
-animated_java.dialog.blueprint_settings.root_entity_summon_commands.description: Commands to run as the root entity when summoned. Treat this text input as a normal mcfunction.
+animated_java.dialog.blueprint_settings.custom_summon_commands.title: Custom On-Summon Commands
+animated_java.dialog.blueprint_settings.custom_summon_commands.description: Commands to run as the root entity when summoned. Treat this text input as a normal mcfunction.
## Bone Config Dialog
animated_java.dialog.bone_config.title: Bone Config
diff --git a/src/mods/saveProjectActionMod.ts b/src/mods/saveProjectActionMod.ts
index 297d232a..016ae13a 100644
--- a/src/mods/saveProjectActionMod.ts
+++ b/src/mods/saveProjectActionMod.ts
@@ -1,4 +1,4 @@
-import { BLUEPRINT_CODEC, BLUEPRINT_FORMAT } from '../blueprintFormat'
+import { BLUEPRINT_FORMAT, saveBlueprint } from '../blueprintFormat'
import { PACKAGE } from '../constants'
import { createBlockbenchMod } from '../util/moddingTools'
@@ -12,7 +12,7 @@ createBlockbenchMod(
context.action.click = (event: Event) => {
if (!Project || !Format) return
if (Format === BLUEPRINT_FORMAT) {
- BLUEPRINT_CODEC.write(BLUEPRINT_CODEC.compile(), Project.save_path)
+ saveBlueprint()
} else {
context.originalClick.call(context.action, event)
}
diff --git a/src/systems/datapackCompiler.ts b/src/systems/datapackCompiler.ts
index 59f5b164..802b04e9 100644
--- a/src/systems/datapackCompiler.ts
+++ b/src/systems/datapackCompiler.ts
@@ -2,7 +2,6 @@ import { Compiler, Parser, Tokenizer, SyncIo } from 'mc-build'
import { VariableMap } from 'mc-build/dist/mcl/Compiler'
import { isFunctionTagPath } from '../util/fileUtil'
import datapackTemplate from './datapackCompiler/animated_java.mcb'
-import { openUnexpectedErrorDialog } from '../interface/unexpectedErrorDialog'
import { IRenderedNodes, IRenderedRig } from './rigRenderer'
import { IRenderedAnimation } from './animationRenderer'
import { Variant } from '../variants'
@@ -183,41 +182,40 @@ function createCustomSyncIO(): SyncIo {
return io
}
-export function compileDataPack(rig: IRenderedRig, animations: IRenderedAnimation[]) {
- try {
- console.log('Compiling Data Pack...')
- const compiler = new Compiler('src/', {
- libDir: null,
- generatedDirName: 'zzz',
- internalScoreboardName: 'aj.i',
- eqVarScoreboardName: null,
- eqConstScoreboardName: null,
- header: '# This file was generated by Animated Java via MC-Build. It is not recommended to edit this file directly.',
- ioThreadCount: null,
- setup: null,
- })
- compiler.io = createCustomSyncIO()
- compiler.disableRequire = true
-
- const variables = {
- export_namespace: Project!.animated_java.export_namespace,
- rig,
- animations,
- variants: Variant.all,
- export_version: Math.random().toString().substring(2, 12),
- root_entity_passengers: generateRootEntityPassengers(rig),
- TAGS,
- }
+export function compileDataPack(options: { rig: IRenderedRig; animations: IRenderedAnimation[] }) {
+ const { rig, animations } = options
+ console.log('Compiling Data Pack...')
+ const compiler = new Compiler('src/', {
+ libDir: null,
+ generatedDirName: 'zzz',
+ internalScoreboardName: 'aj.i',
+ eqVarScoreboardName: null,
+ eqConstScoreboardName: null,
+ header: '# This file was generated by Animated Java via MC-Build. It is not recommended to edit this file directly.',
+ ioThreadCount: null,
+ setup: null,
+ })
+ compiler.io = createCustomSyncIO()
+ compiler.disableRequire = true
- console.time('Data Pack Compilation')
- compiler.addFile(
- 'src/animated_java.mcb',
- Parser.parseMcbFile(Tokenizer.tokenize(datapackTemplate, 'src/animated_java.mcb'))
- )
- compiler.compile(VariableMap.fromObject(variables))
- console.timeEnd('Data Pack Compilation')
- } catch (e: any) {
- openUnexpectedErrorDialog(e as Error)
- console.error(e)
+ const aj = Project!.animated_java
+
+ const variables = {
+ export_namespace: aj.export_namespace,
+ rig,
+ animations,
+ variants: Variant.all,
+ export_version: Math.random().toString().substring(2, 10),
+ root_entity_passengers: generateRootEntityPassengers(rig),
+ TAGS,
+ custom_summon_commands: aj.custom_summon_commands,
}
+
+ console.time('Data Pack Compilation')
+ compiler.addFile(
+ 'src/animated_java.mcb',
+ Parser.parseMcbFile(Tokenizer.tokenize(datapackTemplate, 'src/animated_java.mcb'))
+ )
+ compiler.compile(VariableMap.fromObject(variables))
+ console.timeEnd('Data Pack Compilation')
}
diff --git a/src/systems/datapackCompiler/animated_java.mcb b/src/systems/datapackCompiler/animated_java.mcb
index 85e24708..f0dc89a8 100644
--- a/src/systems/datapackCompiler/animated_java.mcb
+++ b/src/systems/datapackCompiler/animated_java.mcb
@@ -7,8 +7,8 @@ dir global {
scoreboard objectives add aj.frame dummy
scoreboard objectives add aj.rig_is_loaded dummy
- scoreboard players set aj.true aj.i 1
- scoreboard players set aj.false aj.i 0
+ scoreboard players set aj.1b aj.i 1
+ scoreboard players set aj.0b aj.i 0
scoreboard players add aj.last_id aj.id 0
scoreboard players reset * aj.rig_is_loaded
@@ -54,7 +54,19 @@ dir global {
dir errors {
function function_not_executed_as_root_entity {
# Make this text fancier in the actual implementation.
- $tellraw @a 'The function $(function_path) must be executed as the root entity of the rig.'
+ $tellraw @a "The function $(function_path) must be executed as the root entity of the rig."
+ }
+ }
+
+ dir remove {
+ # Removes all instances of all rigs from the world.
+ function everything {
+ # Args: {confirm: boolean}
+ $execute if score aj.$(confirm) aj.i matches 0 run tellraw @a "Are you sure you want to remove all Animated Java rigs from the world? Run this command again with {confirm:true} to confirm."
+ $execute if score aj.$(confirm) aj.i matches 1 run {
+ kill @e[type=item_display,tag=aj.rig_entity]
+ tellraw @a "All Animated Java rigs have been removed from the world."
+ }
}
}
}
@@ -143,54 +155,54 @@ dir <%export_namespace%> {
function play {
execute unless entity @s[type=item_display,tag=aj.root_entity] run return run \
function *global/errors/function_not_executed_as_root_entity \
- with {'function_path': 'animated_java:<%export_namespace%>/animations/<%animation.name%>/play'}
+ {'function_path': 'animated_java:<%export_namespace%>/animations/<%animation.name%>/play'}
tag @s add aj.<%export_namespace%>.animation.<%animation.name%>.playing
}
function stop {
execute unless entity @s[type=item_display,tag=aj.root_entity] run return run \
function *global/errors/function_not_executed_as_root_entity \
- with {'function_path': 'animated_java:<%export_namespace%>/animations/<%animation.name%>/stop'}
+ {'function_path': 'animated_java:<%export_namespace%>/animations/<%animation.name%>/stop'}
tag @s remove aj.<%export_namespace%>.animation.<%animation.name%>.playing
}
function pause {
execute unless entity @s[type=item_display,tag=aj.root_entity] run return run \
function *global/errors/function_not_executed_as_root_entity \
- with {'function_path': 'animated_java:<%export_namespace%>/animations/<%animation.name%>/pause'}
+ {'function_path': 'animated_java:<%export_namespace%>/animations/<%animation.name%>/pause'}
tag @s remove aj.<%export_namespace%>.animation.<%animation.name%>.playing
}
function resume {
execute unless entity @s[type=item_display,tag=aj.root_entity] run return run \
function *global/errors/function_not_executed_as_root_entity \
- with {'function_path': 'animated_java:<%export_namespace%>/animations/<%animation.name%>/resume'}
+ {'function_path': 'animated_java:<%export_namespace%>/animations/<%animation.name%>/resume'}
tag @s add aj.<%export_namespace%>.animation.<%animation.name%>.playing
}
function reset {
execute unless entity @s[type=item_display,tag=aj.root_entity] run return run \
function *global/errors/function_not_executed_as_root_entity \
- with {'function_path': 'animated_java:<%export_namespace%>/animations/<%animation.name%>/reset'}
+ {'function_path': 'animated_java:<%export_namespace%>/animations/<%animation.name%>/reset'}
tag @s remove aj.<%export_namespace%>.animation.<%animation.name%>.playing
}
function next_frame {
execute unless entity @s[type=item_display,tag=aj.root_entity] run return run \
function *global/errors/function_not_executed_as_root_entity \
- with {'function_path': 'animated_java:<%export_namespace%>/animations/<%animation.name%>/next_frame'}
+ {'function_path': 'animated_java:<%export_namespace%>/animations/<%animation.name%>/next_frame'}
}
function apply_frame {
# ARGS: {frame: int}
execute unless entity @s[type=item_display,tag=aj.root_entity] run return run \
function *global/errors/function_not_executed_as_root_entity \
- with {'function_path': 'animated_java:<%export_namespace%>/animations/<%animation.name%>/apply_frame'}
+ {'function_path': 'animated_java:<%export_namespace%>/animations/<%animation.name%>/apply_frame'}
}
function tween_play {
execute unless entity @s[type=item_display,tag=aj.root_entity] run return run \
function *global/errors/function_not_executed_as_root_entity \
- with {'function_path': 'animated_java:<%export_namespace%>/animations/<%animation.name%>/tween_play'}
+ {'function_path': 'animated_java:<%export_namespace%>/animations/<%animation.name%>/tween_play'}
tag @s add aj.<%export_namespace%>.animation.<%animation.name%>.tween_playing
}
function tween_resume {
execute unless entity @s[type=item_display,tag=aj.root_entity] run return run \
function *global/errors/function_not_executed_as_root_entity \
- with {'function_path': 'animated_java:<%export_namespace%>/animations/<%animation.name%>/tween_resume'}
+ {'function_path': 'animated_java:<%export_namespace%>/animations/<%animation.name%>/tween_resume'}
tag @s add aj.<%export_namespace%>.animation.<%animation.name%>.tween_playing
}
dir zzz {
@@ -215,26 +227,26 @@ dir <%export_namespace%> {
execute store result score @s aj.id run scoreboard players add aj.last_id aj.id 1
tag @s remove aj.new
- data set value aj:temp args set value {variant:'', animation:'', frame: 0}
+ data modify storage aj:temp args set value {variant:'', animation:'', frame: 0}
$execute store success score #success aj.i run data modify storage aj:temp args set value $(args)
# Variant Arguement
execute if data storage aj:temp args.variant run {
- execute if data storage aj:temp {args:{variant:''}} run return run tellraw @a 'The variant argument cannot be an empty string.'
+ execute if data storage aj:temp {args:{variant:''}} run return run tellraw @a "The variant argument cannot be an empty string."
# Attempt to apply the variant, if it fails, print an error.
execute store success score #success aj.i run { with storage aj:temp args
$execute store success score #success aj.i run function *<%export_namespace%>/variants/$(variant)/apply
execute if score #success aj.i matches 1 run return 1
return fail
}
- $execute unless score #success aj.i matches 1 run return run tellraw @a 'The variant $(variant) does not exist.'
+ $execute unless score #success aj.i matches 1 run return run tellraw @a "The variant $(variant) does not exist."
}
# Animation, Frame, and Start Animation Arguments
execute if data storage aj:temp args.animation run {
- execute if data storage aj:temp {args:{animation:''}} run return run tellraw @a 'The animation argument cannot be an empty string.'
+ execute if data storage aj:temp {args:{animation:''}} run return run tellraw @a "The animation argument cannot be an empty string."
execute store result score #frame aj.i run data get storage aj:temp args.frame
- execute if score #frame aj.i matches ..-1 run return run tellraw @a 'The frame argument must be a non-negative integer.'
+ execute if score #frame aj.i matches ..-1 run return run tellraw @a "The frame argument must be a non-negative integer."
execute store result storage aj:temp args.frame int 1 run scoreboard players get #frame aj.i
# Attempt to apply the animation frame, if it fails, print an error.
execute store success score #success aj.i run { with storage aj:temp args
@@ -242,11 +254,15 @@ dir <%export_namespace%> {
execute if score #success aj.i matches 1 run return 1
return fail
}
- $execute unless score #success aj.i matches 1 run return run tellraw @a 'The animation $(animation) does not exist.'
+ $execute unless score #success aj.i matches 1 run return run tellraw @a "The animation $(animation) does not exist."
execute if data storage aj:temp {args:{start_animation: true}} run { with storage aj:temp args
$function *<%export_namespace%>/animations/$(animation)/resume
}
}
+ # Custom Summon Commands
+ <%custom_summon_commands%>
+ # Custom Summon Commands
+
# Run the on_summon function for the root entity.
function #*<%export_namespace%>/as_root/on_summon
}
@@ -274,15 +290,6 @@ dir <%export_namespace%> {
execute on passengers run kill @s
kill @s
}
- # Removes all instances of all rigs from the world.
- function everything {
- # Args: {confirm: boolean}
- $execute if score aj.$(confirm) aj.i matches 0 run tellraw @a 'Are you sure you want to remove all Animated Java rigs from the world? Run this command again with {confirm:true} to confirm.'
- $execute if score aj.$(confirm) aj.i matches 1 run {
- kill @e[type=item_display,tag=aj.rig_entity]
- tellraw @a 'All Animated Java rigs have been removed from the world.'
- }
- }
}
dir variants {
@@ -312,7 +319,7 @@ dir <%export_namespace%> {
dir zzz {
function check_if_rig_outdated {
- execute unless score @s aj.<%export_version%> matches <%export_version%> run function #animated_java:<%export_namespace%>/zzz/update_outdated_rig
+ execute unless score @s aj.export_version matches <%export_version%> run function #animated_java:<%export_namespace%>/zzz/update_outdated_rig
}
function update_outdated_rig {
diff --git a/src/systems/datapackCompiler/scoreboards.ts b/src/systems/datapackCompiler/scoreboards.ts
deleted file mode 100644
index a2d318f9..00000000
--- a/src/systems/datapackCompiler/scoreboards.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { JsonText } from '../../util/jsonText'
-
-export class Scoreboard {
- public static all: Scoreboard[] = []
- constructor(public name: string, public displayName?: JsonText, public type = 'dummy') {}
-
- public toString() {
- return this.name
- }
-
- public toInitializer() {
- return `scoreboard objectives add ${this.name} ${this.type}${
- this.displayName ? ' ' + this.displayName.toString() : ''
- }`
- }
-
- public static get(name: string) {
- return Scoreboard.all.find(scoreboard => scoreboard.name === name)
- }
-}
-
-export function generateScoreboard() {
- new Scoreboard('aj.i')
- new Scoreboard('aj.id')
- new Scoreboard('aj.frame')
- new Scoreboard('aj.rig_is_loaded')
-}
diff --git a/src/systems/exporter.ts b/src/systems/exporter.ts
index 4cd527da..36eac0d3 100644
--- a/src/systems/exporter.ts
+++ b/src/systems/exporter.ts
@@ -1,44 +1,71 @@
+import { saveBlueprint } from '../blueprintFormat'
+import { openUnexpectedErrorDialog } from '../interface/unexpectedErrorDialog'
import { renderProjectAnimations } from './animationRenderer'
import { compileDataPack } from './datapackCompiler'
+import { compileResourcePack } from './resourcepackCompiler'
import { renderRig } from './rigRenderer'
export function exportProject() {
- if (!Project) return // TODO: Handle this error better
- const aj = Project.animated_java
-
- let resourcePackFolder: string,
- dataPackFolder: string,
- textureExportFolder: string,
- modelExportFolder: string,
- displayItemPath: string
-
- resourcePackFolder = aj.resource_pack
- dataPackFolder = aj.data_pack
-
- if (aj.enable_advanced_resource_pack_settings) {
- modelExportFolder = aj.model_folder
- textureExportFolder = aj.texture_folder
- displayItemPath = aj.display_item_path
- } else {
- modelExportFolder = PathModule.join(
- resourcePackFolder,
- 'assets/animated_java/models/',
- aj.export_namespace
- )
- textureExportFolder = PathModule.join(
- resourcePackFolder,
- 'assets/animated_java/textures/',
- aj.export_namespace
- )
- displayItemPath = PathModule.join(
- resourcePackFolder,
- 'assets/minecraft/models/item/',
- aj.display_item + '.json'
- )
- }
+ try {
+ if (!Project) return // TODO: Handle this error better
+ const aj = Project.animated_java
+
+ let resourcePackFolder: string,
+ dataPackFolder: string,
+ textureExportFolder: string,
+ modelExportFolder: string,
+ displayItemPath: string
+
+ resourcePackFolder = aj.resource_pack
+ dataPackFolder = aj.data_pack
+
+ if (aj.enable_advanced_resource_pack_settings) {
+ modelExportFolder = aj.model_folder
+ textureExportFolder = aj.texture_folder
+ displayItemPath = aj.display_item_path
+ } else {
+ modelExportFolder = PathModule.join(
+ resourcePackFolder,
+ 'assets/animated_java/models/',
+ aj.export_namespace
+ )
+ textureExportFolder = PathModule.join(
+ resourcePackFolder,
+ 'assets/animated_java/textures/',
+ aj.export_namespace
+ )
+ displayItemPath = PathModule.join(
+ resourcePackFolder,
+ 'assets/minecraft/models/item/',
+ aj.display_item.split(':').at(-1)! + '.json'
+ )
+ }
- const rig = renderRig(modelExportFolder, textureExportFolder)
- const animations = renderProjectAnimations(Project, rig)
+ const rig = renderRig(modelExportFolder, textureExportFolder)
+ const animations = renderProjectAnimations(Project, rig)
- compileDataPack(rig, animations)
+ if (aj.enable_resource_pack) {
+ compileResourcePack({
+ rig,
+ animations,
+ displayItemPath,
+ resourcePackFolder,
+ textureExportFolder,
+ modelExportFolder,
+ dataPackFolder,
+ })
+ }
+
+ if (aj.enable_data_pack) {
+ compileDataPack({ rig, animations })
+ }
+
+ Project.last_used_export_namespace = aj.export_namespace
+
+ saveBlueprint()
+ Blockbench.showQuickMessage('Project exported successfully!', 2000)
+ } catch (e: any) {
+ console.error(e)
+ openUnexpectedErrorDialog(e as Error)
+ }
}
diff --git a/src/systems/resourcepackCompiler.ts b/src/systems/resourcepackCompiler.ts
index 7b36480d..35e57cd5 100644
--- a/src/systems/resourcepackCompiler.ts
+++ b/src/systems/resourcepackCompiler.ts
@@ -1,3 +1,216 @@
-export function compileResourcePack() {
- // TODO Compile resource pack
+import { isResourcePackPath, toSafeFuntionName } from '../util/minecraftUtil'
+import { IRenderedAnimation } from './animationRenderer'
+import { IRenderedNodes, IRenderedRig } from './rigRenderer'
+import { replacePathPart } from './util'
+
+interface IPredicateItemModel {
+ parent: string
+ textures: any
+ overrides: Array<{
+ predicate: { custom_model_data: number }
+ model: string
+ }>
+ animated_java: {
+ rigs: Record
+ }
+}
+
+class PredicateItemModel {
+ private lastOverrideId = 1
+ private overrides = new Map()
+ public usedIds = new Set()
+ public rigs: Record = {}
+
+ // constructor() {}
+
+ setOverride(id: number, model: string) {
+ if (!this.usedIds.has(id)) this.usedIds.add(id)
+ this.overrides.set(id, model)
+ }
+
+ addOverride(model: string) {
+ let id = this.lastOverrideId
+ while (this.overrides.get(id) !== undefined) id++
+ this.lastOverrideId = id
+ this.usedIds.add(id)
+ this.overrides.set(id, model)
+ return id
+ }
+
+ assertOverride(id: number, model: string) {
+ if (this.overrides.get(id) === undefined) this.setOverride(id, model)
+ }
+
+ readExisting(path: string) {
+ let file: IPredicateItemModel
+ try {
+ file = JSON.parse(fs.readFileSync(path, 'utf-8'))
+ } catch (e) {
+ console.error('Failed to read existing display item model:', e)
+ return
+ }
+
+ if (typeof file.animated_java !== 'object') {
+ // TODO Inform the user that they are attempting to merge into a non-animated_java model. And give them the option to cancel.
+ }
+
+ // Assert important fields
+ file.overrides ??= []
+ file.animated_java ??= { rigs: {} }
+ file.animated_java.rigs ??= {}
+
+ for (const [name, rig] of Object.entries(file.animated_java.rigs)) {
+ const namespace = Project!.animated_java.export_namespace
+ const lastNamespace = Project!.last_used_export_namespace
+ if (name === namespace || name === lastNamespace) {
+ file.overrides = file.overrides.filter(
+ override => !rig.used_ids.includes(override.predicate.custom_model_data)
+ )
+ if (name === lastNamespace && namespace !== lastNamespace)
+ delete file.animated_java.rigs[lastNamespace]
+ continue
+ }
+
+ rig.used_ids.forEach(id => this.usedIds.add(id))
+ this.rigs[name] = rig
+ }
+ }
+
+ toJSON(): IPredicateItemModel {
+ const [displayItemNamespace, displayItemName] =
+ Project!.animated_java.display_item.split(':')
+ const exportNamespace = Project!.animated_java.export_namespace
+ return {
+ parent: 'item/generated',
+ textures: {
+ layer0: `${displayItemNamespace}:item/${displayItemName}`,
+ },
+ overrides: [...this.overrides.entries()].map(([id, model]) => ({
+ predicate: { custom_model_data: id },
+ model,
+ })),
+ animated_java: {
+ rigs: {
+ ...this.rigs,
+ [exportNamespace]: {
+ used_ids: [...this.usedIds.values()],
+ },
+ },
+ },
+ }
+ }
+}
+
+export function compileResourcePack(options: {
+ rig: IRenderedRig
+ animations: IRenderedAnimation[]
+ displayItemPath: string
+ resourcePackFolder: string
+ textureExportFolder: string
+ modelExportFolder: string
+ dataPackFolder: string
+}) {
+ const {
+ rig,
+ // animations,
+ displayItemPath,
+ resourcePackFolder,
+ textureExportFolder,
+ modelExportFolder,
+ // dataPackFolder,
+ } = options
+ const aj = Project!.animated_java
+ const lastUsedExportNamespace = Project!.last_used_export_namespace
+ console.log('Compiling resource pack...', options)
+
+ // Internal Models
+ fs.mkdirSync(PathModule.join(resourcePackFolder, 'assets/animated_java/models/'), {
+ recursive: true,
+ })
+ fs.writeFileSync(
+ PathModule.join(resourcePackFolder, 'assets/animated_java/models/empty.json'),
+ '{}'
+ )
+
+ // Display Item
+ const displayItemModel = new PredicateItemModel()
+ if (fs.existsSync(displayItemPath)) {
+ console.warn('Display item already exists! Attempting to merge...')
+ displayItemModel.readExisting(displayItemPath)
+ }
+
+ // Empty model for hiding bones / snowballs
+ displayItemModel.assertOverride(1, 'animated_java:item/empty')
+
+ // Models
+ fs.rmSync(replacePathPart(modelExportFolder, aj.export_namespace, lastUsedExportNamespace), {
+ recursive: true,
+ force: true,
+ })
+ fs.mkdirSync(modelExportFolder, { recursive: true })
+ for (const [boneUuid, model] of Object.entries(rig.models)) {
+ const bone = rig.nodeMap[boneUuid] as IRenderedNodes['Bone']
+ bone.customModelData = displayItemModel.addOverride(bone.resourceLocation)
+ fs.writeFileSync(
+ PathModule.join(modelExportFolder, bone.name + '.json'),
+ JSON.stringify(model)
+ )
+ }
+
+ // Textures
+ fs.rmSync(replacePathPart(textureExportFolder, aj.export_namespace, lastUsedExportNamespace), {
+ recursive: true,
+ force: true,
+ })
+ fs.mkdirSync(textureExportFolder, { recursive: true })
+ for (const texture of Object.values(rig.textures)) {
+ let image: Buffer | undefined
+ let mcmeta: Buffer | undefined
+ let optifineEmissive: Buffer | undefined
+ if (texture.source?.startsWith('data:')) {
+ image = Buffer.from(texture.source.split(',')[1], 'base64')
+ } else if (texture.path && fs.existsSync(texture.path)) {
+ if (!isResourcePackPath(texture.path)) {
+ image = fs.readFileSync(texture.path)
+ const mcmetaPath = texture.path + '.mcmeta'
+ const emissivePath = texture.path.replace('.png', '_e.png')
+ if (fs.existsSync(mcmetaPath)) mcmeta = fs.readFileSync(mcmetaPath)
+ if (fs.existsSync(emissivePath)) optifineEmissive = fs.readFileSync(emissivePath)
+ }
+ } else {
+ throw new Error(`Texture ${texture.name} has no image!`)
+ }
+
+ const textureName = toSafeFuntionName(texture.name)
+ fs.writeFileSync(PathModule.join(textureExportFolder, textureName), image!)
+ if (mcmeta !== undefined)
+ fs.writeFileSync(PathModule.join(textureExportFolder, textureName + '.mcmeta'), mcmeta)
+ if (optifineEmissive !== undefined)
+ fs.writeFileSync(
+ PathModule.join(textureExportFolder, textureName + '_e.png'),
+ optifineEmissive
+ )
+ }
+
+ // Variant Models
+ for (const [variantName, models] of Object.entries(rig.variantModels)) {
+ fs.mkdirSync(PathModule.join(modelExportFolder, variantName), { recursive: true })
+ for (const [boneUuid, variantModel] of Object.entries(models)) {
+ const bone = rig.nodeMap[boneUuid] as IRenderedNodes['Bone']
+ variantModel.customModelData = displayItemModel.addOverride(
+ variantModel.resourceLocation
+ )
+ fs.writeFileSync(
+ PathModule.join(modelExportFolder, variantName, bone.name + '.json'),
+ JSON.stringify(variantModel.model)
+ )
+ }
+ }
+
+ // Write display item model
+ console.log('Display Item Model', displayItemModel.toJSON())
+ fs.mkdirSync(PathModule.dirname(displayItemPath), { recursive: true })
+ fs.writeFileSync(displayItemPath, JSON.stringify(displayItemModel.toJSON()))
+
+ console.log('Resource pack compiled!')
}
diff --git a/src/systems/rigRenderer.ts b/src/systems/rigRenderer.ts
index 8cd1ab2d..3ebdbaa5 100644
--- a/src/systems/rigRenderer.ts
+++ b/src/systems/rigRenderer.ts
@@ -154,23 +154,6 @@ export interface IRenderedRig {
textureExportFolder: string
}
-export class CustomModelData {
- private static current = 0
- public static usedIds: number[] = []
-
- public static get() {
- let id = this.current
- while (this.usedIds.includes(id)) id++
- this.current = id + 1
- this.usedIds.push(id)
- return id
- }
-
- public static set(value: number) {
- this.current = value
- }
-}
-
// function countNodesRecursive(nodes: OutlinerNode[] = Outliner.root): number {
// let count = 0
// for (const node of nodes) {
@@ -251,8 +234,6 @@ function renderCube(cube: Cube, rig: IRenderedRig, model: IRenderedModel) {
}
if (Object.keys(element.faces).length === 0) return
- // progress.add(1)
- // progress.update()
return element
}
@@ -261,7 +242,7 @@ export function getTextureResourceLocation(texture: Texture, rig: IRenderedRig)
const parsed = parseResourcePackPath(texture.path)
if (parsed) return parsed
}
- const path = PathModule.join(rig.textureExportFolder, toSafeFuntionName(texture.name) + '.png')
+ const path = PathModule.join(rig.textureExportFolder, toSafeFuntionName(texture.name))
const parsed = parseResourcePackPath(path)
if (parsed) return parsed
@@ -355,7 +336,6 @@ function renderGroup(group: Group, rig: IRenderedRig) {
} else {
console.warn(`Encountered unknown node type:`, node)
}
- // progress.add(1)
}
// Don't export groups without a model.
@@ -374,8 +354,8 @@ function renderGroup(group: Group, rig: IRenderedRig) {
element.rotation.origin = element.rotation.origin.map(v => v * scale + 8)
}
}
- renderedBone.scale = 1 / scale
+ renderedBone.scale = 1 / scale
rig.models[group.uuid] = renderedBone.model
rig.nodeMap[group.uuid] = renderedBone
return structure
@@ -413,7 +393,6 @@ function renderCamera(camera: ICamera, rig: IRenderedRig): INodeStructure {
}
rig.nodeMap[camera.uuid] = renderedCamera
- // progress.add(1)
return {
uuid: camera.uuid,
children: [],
@@ -423,21 +402,23 @@ function renderCamera(camera: ICamera, rig: IRenderedRig): INodeStructure {
function renderVariantModels(variant: Variant, rig: IRenderedRig) {
const bones: Record = {}
+ // TODO Remove elements if they are entirely transparent.
+
for (const [uuid, bone] of Object.entries(rig.nodeMap)) {
if (bone.type !== 'bone') continue
const textures: IRenderedModel['textures'] = {}
- for (const [fromUUID, toUUID] of Object.entries(variant.textureMap.map) as Array<
- [string, string]
- >) {
+
+ for (const [fromUUID, toUUID] of variant.textureMap.map.entries()) {
if (!(fromUUID && toUUID))
throw new Error(
`Invalid texture mapping found while exporting variant models. If you're seeing this error something has gone horribly wrong.`
)
- const fromTexture = rig.textures[fromUUID]
- if (!fromTexture) throw new Error(`Texture not found: ${fromUUID}`)
- const toTexture = rig.textures[toUUID]
- if (!toTexture) throw new Error(`Texture not found: ${toUUID}`)
+ const fromTexture = Texture.all.find(t => t.uuid === fromUUID)
+ if (!fromTexture) throw new Error(`From texture not found: ${fromUUID}`)
+ const toTexture = Texture.all.find(t => t.uuid === toUUID)
+ if (!toTexture) throw new Error(`To texture not found: ${toUUID}`)
textures[fromTexture.id] = getTextureResourceLocation(toTexture, rig).resourceLocation
+ rig.textures[toTexture.id] = toTexture
}
const parsed = PathModule.parse(bone.modelPath)
@@ -445,6 +426,9 @@ function renderVariantModels(variant: Variant, rig: IRenderedRig) {
const parsedModelPath = parseResourcePackPath(modelPath)
if (!parsedModelPath) throw new Error(`Invalid variant model path: ${modelPath}`)
+ // Don't export models without any texture changes
+ if (Object.keys(textures).length === 0) continue
+
bones[uuid] = {
model: {
parent: bone.resourceLocation,
@@ -468,7 +452,6 @@ function getDefaultPose(rig: IRenderedRig) {
}
export function renderRig(modelExportFolder: string, textureExportFolder: string): IRenderedRig {
- CustomModelData.set(1)
Texture.all.forEach((t, i) => (t.id = String(i)))
Animator.showDefaultPose()
diff --git a/src/systems/util.ts b/src/systems/util.ts
index 42902f54..5bc72a83 100644
--- a/src/systems/util.ts
+++ b/src/systems/util.ts
@@ -8,3 +8,10 @@ export function matrixToNbtFloatArray(matrix: THREE.Matrix4) {
const matrixArray = new THREE.Matrix4().copy(matrix).transpose().toArray()
return arrayToNbtFloatArray(matrixArray)
}
+
+export function replacePathPart(path: string, oldPart: string, newPart: string) {
+ return path
+ .split(PathModule.sep)
+ .map(v => (v === oldPart ? newPart : v))
+ .join(PathModule.sep)
+}
diff --git a/src/util/minecraftUtil.ts b/src/util/minecraftUtil.ts
index 14dbecff..ab5a612a 100644
--- a/src/util/minecraftUtil.ts
+++ b/src/util/minecraftUtil.ts
@@ -7,6 +7,11 @@ export function toSafeFuntionName(name: string): string {
.replace(/_+/g, '_')
}
+export function isResourcePackPath(path: string) {
+ const parsed = parseResourcePackPath(path)
+ return parsed && parsed.namespace && parsed.resourcePath
+}
+
export function parseResourcePackPath(path: string) {
path = path.replaceAll(/\\/g, '/')
const parts = path.split('/')
@@ -19,7 +24,7 @@ export function parseResourcePackPath(path: string) {
const resourcePath = parts.slice(assetsIndex + 3).join('/')
const fileName = pathjs.basename(path).split('.')[0]
if (fileName !== fileName.toLowerCase()) return undefined
- const resourceLocation = namespace + ':' + resourcePath + '/' + fileName
+ const resourceLocation = namespace + ':' + resourcePath
return {
resourcePackRoot,
diff --git a/tools/esbuild.ts b/tools/esbuild.ts
index 5d02b93d..000ad043 100644
--- a/tools/esbuild.ts
+++ b/tools/esbuild.ts
@@ -153,6 +153,7 @@ async function buildDev() {
minify: false,
platform: 'node',
sourcemap: 'inline',
+ sourceRoot: 'http://animated-java/',
loader: { '.svg': 'dataurl', '.ttf': 'binary', '.mcb': 'text' },
plugins: [
inlineImage({