Skip to content

Commit

Permalink
Merge branch 'dev' of https://github.com/ghostboats/bg3_mod_helper in…
Browse files Browse the repository at this point in the history
…to mod_unpacker
  • Loading branch information
khbsd committed May 21, 2024
2 parents 609c12b + 938813a commit 6e1813c
Show file tree
Hide file tree
Showing 12 changed files with 279 additions and 495 deletions.
286 changes: 143 additions & 143 deletions commands/createAtlas.js
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;
Loading

0 comments on commit 6e1813c

Please sign in to comment.