-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'dev' of https://github.com/ghostboats/bg3_mod_helper in…
…to mod_unpacker
- Loading branch information
Showing
12 changed files
with
279 additions
and
495 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,172 +1,172 @@ | ||
const vscode = require('vscode'); | ||
const { exec } = require('child_process'); | ||
const path = require('path'); | ||
const { v4: uuidv4 } = require('uuid'); | ||
const fs = require('fs'); | ||
const { getConfig } = require('../support_files/config'); | ||
const { Magick, MagickCore } = require('magickwand.js'); | ||
const xmlbuilder = require('xmlbuilder'); | ||
const { getConfig, setConfig } = require('../support_files/config'); | ||
const { getModName } = require('../support_files/helper_functions.js'); | ||
const { v4: uuidv4 } = require('uuid'); | ||
|
||
function findDivineExe(lslibPath) { | ||
let divinePath = null; | ||
|
||
function searchDir(dir) { | ||
const files = fs.readdirSync(dir); | ||
for (const file of files) { | ||
const filePath = path.join(dir, file); | ||
const stat = fs.lstatSync(filePath); | ||
if (stat.isDirectory()) { | ||
searchDir(filePath); | ||
} else if (file === 'Divine.exe') { | ||
divinePath = filePath; | ||
return; | ||
} | ||
} | ||
} | ||
|
||
if (fs.existsSync(lslibPath) && fs.lstatSync(lslibPath).isDirectory()) { | ||
searchDir(lslibPath); | ||
} else { | ||
vscode.window.showErrorMessage('lslib directory not found.'); | ||
throw new Error('lslib directory not found.'); | ||
} | ||
|
||
if (!divinePath) { | ||
vscode.window.showErrorMessage('No divine.exe found in lslib directory.'); | ||
throw new Error('No divine.exe found in lslib directory.'); | ||
} | ||
|
||
return divinePath; | ||
const truncate = (number, digits) => { | ||
const stepper = Math.pow(10.0, digits); | ||
return Math.round(number * stepper) / stepper; | ||
} | ||
|
||
async function createAtlas(iconsDir, atlasPath, texturePath, textureUUID) { | ||
const { rootModPath } = getConfig(); | ||
const modName = await getModName(); | ||
const iconSize = 64; | ||
const textureSize = 2048; | ||
const textureSizeString = `${textureSize}x${textureSize}`; | ||
|
||
let createAtlasCommand = vscode.commands.registerCommand('bg3-mod-helper.createAtlas', async function () { // Made the function async | ||
console.log('‾‾createAtlasCommand‾‾'); | ||
const { rootModPath, lslibPath } = getConfig(); | ||
let atlas = new Magick.Image(textureSizeString, 'rgba(0, 0, 0, 0)'); | ||
|
||
const scriptPath = path.join(__dirname, '..', 'support_files', 'scripts', 'python', 'add_icons_to_atlas.py'); | ||
const icons = await fs.promises.readdir(iconsDir); | ||
const iconXMLNodes = []; | ||
|
||
let import_test = false | ||
for (let i = 0; i < icons.length; i++) { | ||
const iconPath = path.join(iconsDir, icons[i]); | ||
const iconName = path.parse(icons[i]).name; | ||
const iconImage = new Magick.Image(); | ||
await iconImage.readAsync(iconPath); | ||
|
||
let divinePath_; | ||
try { | ||
divinePath_ = findDivineExe(lslibPath); | ||
} catch (error) { | ||
console.log(error) | ||
return; | ||
const x = (i % (textureSize / iconSize)) * iconSize; | ||
const y = Math.floor(i / (textureSize / iconSize)) * iconSize; | ||
|
||
const u1 = truncate(x / textureSize, 7); | ||
const v1 = truncate(y / textureSize, 7); | ||
const u2 = truncate((x + iconSize) / textureSize, 7); | ||
const v2 = truncate((y + iconSize) / textureSize, 7); | ||
|
||
const geometry = new Magick.Geometry(iconSize, iconSize, x, y); | ||
await atlas.compositeAsync(iconImage, geometry, MagickCore.OverCompositeOp); | ||
|
||
iconXMLNodes.push({ | ||
'@id': 'IconUV', | ||
attribute: [ | ||
{ '@id': 'MapKey', '@value': iconName, '@type': 'FixedString' }, | ||
{ '@id': 'U1', '@value': u1.toString(), '@type': 'float' }, | ||
{ '@id': 'V1', '@value': v1.toString(), '@type': 'float' }, | ||
{ '@id': 'U2', '@value': u2.toString(), '@type': 'float' }, | ||
{ '@id': 'V2', '@value': v2.toString(), '@type': 'float' } | ||
] | ||
}); | ||
} | ||
|
||
const modName = await getModName(); | ||
|
||
function checkPythonImports(packages) { | ||
return new Promise((resolve, reject) => { | ||
let importCommands = packages.map(pkg => `import ${pkg}`).join(';'); | ||
exec(`python -c "${importCommands}"`, (error, stdout, stderr) => { | ||
if (error) { | ||
reject(`Missing package(s): ${packages.join(', ')}`); | ||
} else { | ||
resolve(); | ||
const ddsTexturePath = texturePath.replace('.png', '.dds'); | ||
await atlas.writeAsync(ddsTexturePath); | ||
|
||
const relativeTexturePath = modName + path.sep + path.relative(path.join(rootModPath, 'Public', modName), ddsTexturePath); | ||
|
||
const xmlContent = xmlbuilder.create({ | ||
save: { | ||
version: { | ||
'@major': '4', '@minor': '0', '@revision': '9', '@build': '322' | ||
}, | ||
region: [ | ||
{ | ||
'@id': 'IconUVList', | ||
node: { | ||
'@id': 'root', | ||
children: { | ||
node: iconXMLNodes | ||
} | ||
} | ||
}, | ||
{ | ||
'@id': 'TextureAtlasInfo', | ||
node: { | ||
'@id': 'root', | ||
children: { | ||
node: [ | ||
{ | ||
'@id': 'TextureAtlasIconSize', | ||
attribute: [ | ||
{ '@id': 'Height', '@value': iconSize, '@type': 'int32' }, | ||
{ '@id': 'Width', '@value': iconSize, '@type': 'int32' } | ||
] | ||
}, | ||
{ | ||
'@id': 'TextureAtlasPath', | ||
attribute: [ | ||
{ '@id': 'Path', '@value': relativeTexturePath, '@type': 'string' }, | ||
{ '@id': 'UUID', '@value': textureUUID, '@type': 'FixedString' } | ||
] | ||
}, | ||
{ | ||
'@id': 'TextureAtlasTextureSize', | ||
attribute: [ | ||
{ '@id': 'Height', '@value': textureSize, '@type': 'int32' }, | ||
{ '@id': 'Width', '@value': textureSize, '@type': 'int32' } | ||
] | ||
} | ||
] | ||
} | ||
} | ||
} | ||
}); | ||
}); | ||
} | ||
] | ||
} | ||
}, { encoding: 'UTF-8' }).end({ pretty: true }); | ||
|
||
try { | ||
await checkPythonImports(['PIL', 'numpy']); | ||
import_test = true; | ||
} catch (error) { | ||
const terminal = vscode.window.createTerminal('Package Install'); | ||
terminal.show(); | ||
terminal.sendText('pip install Pillow numpy', false); | ||
vscode.window.showInformationMessage(`${error}. Please run the command in the opened terminal by pressing Enter.`); | ||
fs.writeFileSync(atlasPath, xmlContent); | ||
} | ||
|
||
let createAtlasCommand = vscode.commands.registerCommand('bg3-mod-helper.createAtlas', async function () { | ||
console.log('‾‾createAtlasCommand‾‾'); | ||
const { rootModPath } = getConfig(); | ||
const modName = await getModName(); // Assuming this function correctly fetches the mod's name | ||
const newUuid = uuidv4(); | ||
|
||
// Directories for icons, texture, and atlas XML | ||
const iconsDirPath = await vscode.window.showOpenDialog({ canSelectFolders: true, canSelectFiles: false }); | ||
if (!iconsDirPath) { | ||
vscode.window.showErrorMessage('Icons directory not selected.'); | ||
return; | ||
} | ||
|
||
if (import_test === true) { | ||
const newUuid = uuidv4(); | ||
// Function to prompt user to select a directory | ||
async function selectDirectory() { | ||
const options = { | ||
canSelectMany: false, | ||
openLabel: 'Select', | ||
canSelectFolders: true, | ||
canSelectFiles: false | ||
}; | ||
const fileUri = await vscode.window.showOpenDialog(options); | ||
if (fileUri && fileUri[0]) { | ||
return fileUri[0].fsPath; | ||
} else { | ||
return null; | ||
} | ||
} | ||
const texturesDirPath = path.join(rootModPath, 'Public', modName, 'Assets', 'Textures', 'Icons'); | ||
const texturePath = path.join(texturesDirPath, `Icons_${modName}.png`); | ||
const atlasDirPath = path.join(rootModPath, 'Public', modName, 'GUI'); | ||
const atlasPath = path.join(atlasDirPath, `Icons_${modName}.lsx`); | ||
|
||
// Prompt user to select the icons directory | ||
const iconsDirPath = await selectDirectory(); | ||
if (!iconsDirPath) { | ||
vscode.window.showErrorMessage('Icons directory not selected.'); | ||
return; | ||
} | ||
const mergedDirPath = path.join(rootModPath, 'Public', modName, 'Content', 'UI', '[PAK]_UI'); | ||
const mergedPath = path.join(mergedDirPath, 'merged.lsx'); | ||
|
||
const texturesDirPath = path.join(rootModPath, 'Public', modName, 'Assets', 'Textures', 'Icons'); | ||
const texturesPath = path.join(texturesDirPath, `Icons_${modName}.dds`); | ||
const atlasDirPath = path.join(rootModPath, 'Public', modName, 'GUI'); | ||
const atlasPath = path.join(atlasDirPath, `Icons_${modName}.lsx`); | ||
const mergedDirPath = path.join(rootModPath, 'Public', modName, 'Content', 'UI', '[PAK]_UI'); | ||
const mergedPath = path.join(mergedDirPath, 'merged.lsx'); | ||
const args = [ | ||
'-i', `"${iconsDirPath}"`, | ||
'-a', `"${atlasPath}"`, | ||
'-t', `"${texturesPath}"`, | ||
'-u', `"${newUuid}"`, | ||
'--divine', `"${divinePath_}"` | ||
].join(' '); | ||
|
||
// Before writing the modified content to the new merged.lsx file | ||
if (!fs.existsSync(mergedDirPath)) { | ||
fs.mkdirSync(mergedDirPath, { recursive: true }); | ||
// Ensure directories exist | ||
[texturesDirPath, atlasDirPath].forEach(dir => { | ||
if (!fs.existsSync(dir)) { | ||
fs.mkdirSync(dir, { recursive: true }); | ||
} | ||
}); | ||
|
||
const command = `python "${scriptPath}" ${args}`; | ||
|
||
exec(command, (error, stdout, stderr) => { | ||
if (error) { | ||
console.error(`Error: ${error.message}`); | ||
vscode.window.showErrorMessage(`Error: ${error.message}`); | ||
return; | ||
} | ||
if (stderr) { | ||
console.error(`stderr: ${stderr}`); | ||
vscode.window.showErrorMessage(`stderr: ${stderr}`); | ||
return; | ||
} | ||
console.log(`stdout: ${stdout}`); | ||
vscode.window.showInformationMessage('Python script executed successfully.'); | ||
}); | ||
console.log('#################################') | ||
// Check if merged.lsx exists | ||
if (fs.existsSync(mergedPath)) { | ||
const overwrite = await vscode.window.showInformationMessage('A merged.lsx file already exists. Do you want to overwrite it?', 'Yes', 'No'); | ||
if (overwrite === 'No') { | ||
return; // Stop execution if user chooses not to overwrite | ||
} | ||
} | ||
// Create atlas and texture | ||
try { | ||
await createAtlas(iconsDirPath[0].fsPath, atlasPath, texturePath, newUuid); | ||
vscode.window.showInformationMessage('Atlas and texture created successfully.'); | ||
} catch (error) { | ||
vscode.window.showErrorMessage(`Failed to create atlas or texture: ${error.message}`); | ||
} | ||
|
||
// Use a skeleton merged.lsx file as a template | ||
const skeletonMergedPath = path.join(__dirname, '../support_files/templates/long_skeleton_files/merged_atlas.lsx'); | ||
let mergedContent = fs.readFileSync(skeletonMergedPath, 'utf8'); | ||
// Check if merged.lsx exists | ||
if (fs.existsSync(mergedPath)) { | ||
const overwrite = await vscode.window.showInformationMessage('A merged.lsx file already exists. Do you want to overwrite it?', 'Yes', 'No'); | ||
if (overwrite === 'No') { | ||
return; // Stop execution if user chooses not to overwrite | ||
} | ||
} | ||
|
||
// Replace placeholders in merged.lsx file | ||
mergedContent = mergedContent.replace(/\{uuid\}/g, newUuid); | ||
mergedContent = mergedContent.replace(/\{file_name\}/g, `Icons_${modName}`); | ||
mergedContent = mergedContent.replace(/\{file_path\}/g, `Public/${modName}/Assets/Textures/Icons/Icons_${modName}.dds`); | ||
// Use a skeleton merged.lsx file as a template | ||
const skeletonMergedPath = path.join(__dirname, '../support_files/templates/long_skeleton_files/merged_atlas.lsx'); | ||
let mergedContent = fs.readFileSync(skeletonMergedPath, 'utf8'); | ||
|
||
// Write the modified content to the new merged.lsx file | ||
fs.writeFileSync(mergedPath, mergedContent, 'utf8'); | ||
vscode.window.showInformationMessage(`merged.lsx file created/updated successfully at ${mergedDirPath}`); | ||
// Replace placeholders in merged.lsx file | ||
mergedContent = mergedContent.replace(/\{uuid\}/g, newUuid); | ||
mergedContent = mergedContent.replace(/\{file_name\}/g, `Icons_${modName}`); | ||
mergedContent = mergedContent.replace(/\{file_path\}/g, `Public/${modName}/Assets/Textures/Icons/Icons_${modName}.dds`); | ||
|
||
} else { | ||
console.log('Pil not found, not running exe for atlas icon stuff') | ||
} | ||
console.log('__createAtlasCommand__'); | ||
// Write the modified content to the new merged.lsx file | ||
fs.writeFileSync(mergedPath, mergedContent, 'utf8'); | ||
vscode.window.showInformationMessage(`merged.lsx file created/updated successfully at ${mergedDirPath}`); | ||
}); | ||
|
||
module.exports = createAtlasCommand; |
Oops, something went wrong.