Skip to content

Commit 8ecc816

Browse files
committed
Textures are now referenced if they already exist in a resource pack
1 parent 7b995aa commit 8ecc816

File tree

6 files changed

+375
-355
lines changed

6 files changed

+375
-355
lines changed

src/exporter.ts

Lines changed: 2 additions & 226 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,18 @@
11
import * as events from './events'
2+
import { exportResources } from './exporter/resourcePackExporter'
23
import { GUIStructure } from './guiStructure'
3-
import { safeFunctionName } from './minecraft/util'
44
import { projectSettingStructure } from './projectSettings'
55
import { IRenderedAnimation, renderAllAnimations } from './rendering/animationRenderer'
6-
import { CustomModelData, IRenderedRig, renderRig } from './rendering/modelRenderer'
6+
import { IRenderedRig, renderRig } from './rendering/modelRenderer'
77
import { animatedJavaSettings, IInfoPopup, Setting as AJSetting, Setting } from './settings'
88
import { openAjFailedProjectExportReadinessDialog } from './ui/popups/failedProjectExportReadiness'
99
import { openUnexpectedErrorDialog } from './ui/popups/unexpectedError'
1010
import { consoleGroupCollapsed } from './util/console'
1111
import { ExpectedError } from './util/misc'
1212
import { NamespacedString } from './util/moddingTools'
13-
import { ProgressBarController } from './util/progress'
1413
import { translate } from './util/translation'
15-
import { VirtualFolder } from './util/virtualFileSystem'
1614

1715
type ProjectSettings = Record<NamespacedString, AJSetting<any>>
18-
type NotUndefined<T> = T extends undefined ? never : T
1916

2017
interface IAnimatedJavaExporterOptions<S extends ProjectSettings> {
2118
id: NamespacedString
@@ -151,227 +148,6 @@ export const exportProject = consoleGroupCollapsed('exportProject', async () =>
151148
Blockbench.showQuickMessage(translate('animated_java.quickmessage.exported_successfully'), 2000)
152149
})
153150

154-
function showPredicateFileOverwriteConfirmation(path: string) {
155-
const result = confirm(
156-
translate('animated_java.popup.confirm_predicate_file_overwrite.body', {
157-
file: PathModule.parse(path).base,
158-
path,
159-
}),
160-
translate('animated_java.popup.confirm_predicate_file_overwrite.title')
161-
)
162-
if (!result) throw new ExpectedError('User cancelled export due to predicate file overwrite.')
163-
}
164-
165-
async function exportResources(
166-
ajSettings: typeof animatedJavaSettings,
167-
projectSettings: NotUndefined<ModelProject['animated_java_settings']>,
168-
rig: IRenderedRig,
169-
rigExportFolder: string,
170-
textureExportFolder: string,
171-
rigItemModelExportPath: string
172-
) {
173-
const projectNamespace = projectSettings.project_namespace.value
174-
const resourcePackPath = PathModule.parse(projectSettings.resource_pack_mcmeta.value).dir
175-
const assetsPackFolder = new VirtualFolder('assets')
176-
const advancedResourcePackSettingsEnabled =
177-
projectSettings.enable_advanced_resource_pack_settings.value
178-
179-
//------------------------------------
180-
// Minecraft namespace
181-
//------------------------------------
182-
183-
const [rigItemNamespace, rigItemName] = projectSettings.rig_item.value.split(':')
184-
185-
const minecraftFolder = assetsPackFolder.newFolder('minecraft').newFolder('models/item')
186-
187-
//------------------------------------
188-
// Rig Item Predicate File
189-
//------------------------------------
190-
191-
interface IPredicateItemModel {
192-
parent: string
193-
textures: any
194-
overrides: Array<{
195-
predicate: { custom_model_data: number }
196-
model: string
197-
}>
198-
animated_java: {
199-
rigs: Record<string, { used_ids: number[] }>
200-
}
201-
}
202-
203-
const predicateItemFilePath = PathModule.join(
204-
resourcePackPath,
205-
minecraftFolder.path,
206-
`${rigItemName}.json`
207-
)
208-
const content: IPredicateItemModel = {
209-
parent: 'item/generated',
210-
textures: {
211-
layer0: `${rigItemNamespace}:item/${rigItemName}`,
212-
},
213-
overrides: [],
214-
animated_java: {
215-
rigs: {},
216-
},
217-
}
218-
const predicateItemFile = minecraftFolder.newFile(`${rigItemName}.json`, content)
219-
let successfullyReadPredicateItemFile = false
220-
if (fs.existsSync(predicateItemFilePath)) {
221-
const stringContent = await fs.promises.readFile(predicateItemFilePath, 'utf8')
222-
try {
223-
const localContent = JSON.parse(stringContent)
224-
Object.assign(content, localContent)
225-
successfullyReadPredicateItemFile = true
226-
} catch (e) {
227-
console.warn('Failed to read predicate item file as JSON')
228-
console.warn(e)
229-
}
230-
} else successfullyReadPredicateItemFile = true
231-
if (!successfullyReadPredicateItemFile || !content.animated_java) {
232-
showPredicateFileOverwriteConfirmation(predicateItemFilePath)
233-
}
234-
if (!content.overrides) content.overrides = []
235-
if (!content.animated_java.rigs) content.animated_java.rigs = {}
236-
237-
// const content = predicateItemFile.content as IPredicateItemModel
238-
const usedIds: number[] = [] // IDs that are used by other projects
239-
const consumedIds: number[] = [] // IDs that are used by this project
240-
for (const [name, rig] of Object.entries(content.animated_java.rigs)) {
241-
if (!rig.used_ids) {
242-
console.warn('Found existing rig in predicate file, but it is missing used_ids.')
243-
continue
244-
}
245-
const localUsedIds = rig.used_ids
246-
if (name === projectNamespace) {
247-
// Clean out old overrides
248-
content.overrides = content.overrides.filter(o => {
249-
return !localUsedIds.includes(o.predicate.custom_model_data)
250-
})
251-
continue
252-
}
253-
usedIds.push(...rig.used_ids)
254-
}
255-
256-
CustomModelData.usedIds = usedIds
257-
content.animated_java.rigs[projectNamespace] = {
258-
used_ids: consumedIds,
259-
}
260-
261-
//------------------------------------
262-
// Project namespace
263-
//------------------------------------
264-
265-
const NAMESPACE = projectSettings.project_namespace.value
266-
const namespaceFolder = assetsPackFolder.newFolder(`${NAMESPACE}_animated_java_rig`)
267-
const [modelsFolder, texturesFolder] = namespaceFolder.newFolders(
268-
'models/item',
269-
'textures/item'
270-
)
271-
272-
for (const texture of Object.values(rig.textures)) {
273-
let image: Buffer
274-
let mcmeta: Buffer | undefined
275-
let optifineEmissive: Buffer | undefined
276-
if (texture.source?.startsWith('data:')) {
277-
image = Buffer.from(texture.source.split(',')[1], 'base64')
278-
} else if (texture.path) {
279-
image = await fs.promises.readFile(texture.path)
280-
if (fs.existsSync(texture.path + '.mcmeta'))
281-
mcmeta = await fs.promises.readFile(texture.path + '.mcmeta')
282-
const emissivePath = texture.path.replace('.png', '') + '_e.png'
283-
if (fs.existsSync(emissivePath))
284-
optifineEmissive = await fs.promises.readFile(emissivePath)
285-
} else {
286-
throw new Error(`Texture "${texture.name}" has no source or path`)
287-
}
288-
const textureName = safeFunctionName(texture.name)
289-
texturesFolder.newFile(`${textureName}.png`, image)
290-
if (mcmeta) texturesFolder.newFile(`${textureName}.png.mcmeta`, mcmeta)
291-
if (optifineEmissive) texturesFolder.newFile(`${textureName}_e.png`, optifineEmissive)
292-
// console.log(`Exported texture ${texture.name} to ${texturesFolder.path}`)
293-
}
294-
295-
for (const bone of Object.values(rig.nodeMap)) {
296-
if (bone.type !== 'bone') continue
297-
modelsFolder.newFile(`${bone.name}.json`, bone.model)
298-
consumedIds.push((bone.customModelData = CustomModelData.get()))
299-
predicateItemFile.content.overrides.push({
300-
predicate: {
301-
custom_model_data: bone.customModelData,
302-
},
303-
model: bone.resourceLocation,
304-
})
305-
}
306-
307-
for (const [variantName, variantBoneMap] of Object.entries(rig.variantModels)) {
308-
if (variantBoneMap.default) continue
309-
const variantFolder = modelsFolder.newFolder(variantName)
310-
for (const [uuid, variantBone] of Object.entries(variantBoneMap)) {
311-
const bone = rig.nodeMap[uuid]
312-
if (bone.type !== 'bone') continue
313-
variantFolder.newFile(`${bone.name}.json`, variantBone.model)
314-
consumedIds.push((variantBone.customModelData = CustomModelData.get()))
315-
predicateItemFile.content.overrides.push({
316-
predicate: {
317-
custom_model_data: variantBone.customModelData,
318-
},
319-
model: variantBone.resourceLocation,
320-
})
321-
}
322-
}
323-
324-
predicateItemFile.content.overrides.sort(
325-
(a: any, b: any) => a.predicate.custom_model_data - b.predicate.custom_model_data
326-
)
327-
328-
if (advancedResourcePackSettingsEnabled) {
329-
const progress = new ProgressBarController(
330-
'Writing Resource Pack to Disk',
331-
modelsFolder.childCount + texturesFolder.childCount + 1
332-
)
333-
await fs.promises.mkdir(rigExportFolder, { recursive: true })
334-
await modelsFolder.writeChildrenToDisk(rigExportFolder, progress)
335-
336-
await fs.promises.mkdir(textureExportFolder, { recursive: true })
337-
await texturesFolder.writeChildrenToDisk(textureExportFolder, progress)
338-
339-
const predicateItemExportFolder = PathModule.parse(rigItemModelExportPath).dir
340-
await fs.promises.mkdir(predicateItemExportFolder, { recursive: true })
341-
await predicateItemFile.writeToDisk(predicateItemExportFolder, progress)
342-
343-
progress.finish()
344-
} else {
345-
const progress = new ProgressBarController(
346-
'Writing Resource Pack to Disk',
347-
assetsPackFolder.childCount
348-
)
349-
350-
const rigFolderPath = PathModule.join(resourcePackPath, namespaceFolder.path)
351-
await fs.promises
352-
.access(rigFolderPath)
353-
.then(async () => {
354-
await fs.promises.rm(rigFolderPath, { recursive: true })
355-
})
356-
.catch(e => {
357-
console.warn(e)
358-
})
359-
360-
const textureFolderPath = PathModule.join(resourcePackPath, texturesFolder.path)
361-
await fs.promises
362-
.access(textureFolderPath)
363-
.then(async () => {
364-
await fs.promises.rm(textureFolderPath, { recursive: true })
365-
})
366-
.catch(e => {
367-
console.warn(e)
368-
})
369-
370-
await assetsPackFolder.writeToDisk(resourcePackPath, progress)
371-
progress.finish()
372-
}
373-
}
374-
375151
function verifySettings(structure: GUIStructure, settings: Array<Setting<any>>) {
376152
const issues: IInfoPopup[] = []
377153
for (const el of structure) {

0 commit comments

Comments
 (0)