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 khbsd_dev
  • Loading branch information
khbsd committed Sep 6, 2024
2 parents c17a5d0 + 8c5952d commit da570bc
Show file tree
Hide file tree
Showing 18 changed files with 766 additions and 190 deletions.
3 changes: 1 addition & 2 deletions .vscodeignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,4 @@ vsc-extension-quickstart.md
**/jsconfig.json
**/*.map
**/.eslintrc.json
**/update_notes.txt
node_modules/**
**/update_notes.txt
143 changes: 143 additions & 0 deletions commands/convertVideoToGif.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
const vscode = require('vscode');
const fs = require('fs');
const path = require('path');
const ffmpeg = require('fluent-ffmpeg');
const ffmpegPath = require('ffmpeg-static');

const { getConfig } = require('../support_files/config');
const { rootModPath } = getConfig();

console.log("FFmpeg Path:", ffmpegPath);
console.log("Type of FFmpeg Path:", typeof ffmpegPath);

if (typeof ffmpegPath === 'string') {
ffmpeg.setFfmpegPath(ffmpegPath);
} else {
console.error("FFmpeg Path is not a string");
}

// Command to open the video conversion webview
const convertVideoToGifCommand = vscode.commands.registerCommand('bg3-mod-helper.convertVideoToGif', async function () {
const panel = vscode.window.createWebviewPanel(
'videoConversion',
'Video Conversion',
vscode.ViewColumn.One,
{ enableScripts: true }
);

const videoFiles = await getAllVideoFiles(rootModPath);

panel.webview.html = getWebviewContent(videoFiles);

// Handle messages from the webview
panel.webview.onDidReceiveMessage(async message => {
switch (message.command) {
case 'convert':
await convertVideoToGifFile(message.videoPath, message.gifPath);
break;
case 'convertAll':
for (const video of videoFiles) {
const gifPath = video.replace(path.extname(video), '.gif');
await convertVideoToGifFile(video, gifPath);
}
break;
case 'selectFile':
const options = {
canSelectMany: false,
openLabel: 'Select a video file',
filters: { 'Video files': ['mp4', 'mkv', 'avi', 'mov'] }
};
const fileUri = await vscode.window.showOpenDialog(options);
if (fileUri && fileUri[0]) {
const videoPath = fileUri[0].fsPath;
const gifPath = videoPath.replace(path.extname(videoPath), '.gif');
await convertVideoToGifFile(videoPath, gifPath);
}
break;
}
});
});

async function getAllVideoFiles(dir) {
let files = [];
const items = await fs.promises.readdir(dir, { withFileTypes: true });
for (const item of items) {
const fullPath = path.join(dir, item.name);
if (item.isDirectory()) {
files = files.concat(await getAllVideoFiles(fullPath));
} else if (/\.(mp4|mkv|avi|mov)$/i.test(item.name)) {
files.push(fullPath);
}
}
return files;
}

function getWebviewContent(videoFiles) {
const videoFileItems = videoFiles.map(file => `
<tr>
<td>${path.basename(file)}</td>
<td>${file}</td>
<td><button onclick="convert('${file.replace(/\\/g, '\\\\')}', '${file.replace(path.extname(file), '.gif').replace(/\\/g, '\\\\')}')">Convert</button></td>
</tr>
`).join('');

return `
<html>
<body>
<h1>Video Conversion</h1>
<table>
<tr>
<th>Name</th>
<th>Path</th>
<th>Action</th>
</tr>
${videoFileItems}
</table>
<button onclick="convertAll()">Convert All</button>
<button onclick="selectFile()">Find Video File on Computer</button>
<script>
const vscode = acquireVsCodeApi();
function convert(videoPath, gifPath) {
vscode.postMessage({ command: 'convert', videoPath: videoPath, gifPath: gifPath });
}
function convertAll() {
vscode.postMessage({ command: 'convertAll' });
}
function selectFile() {
vscode.postMessage({ command: 'selectFile' });
}
</script>
</body>
</html>
`;
}

async function convertVideoToGifFile(inputPath, outputPath) {
return new Promise((resolve, reject) => {
const normalizedInput = path.normalize(inputPath);
const normalizedOutput = path.normalize(outputPath);

console.log("Normalized Input Path:", normalizedInput);
console.log("Normalized Output Path:", normalizedOutput);

ffmpeg(normalizedInput)
.outputOptions([
'-vf', 'fps=24', // Increase fps for smoother animation
'-gifflags', 'transdiff',
'-y', // Overwrite output files without asking
'-q:v', '5' // Set quality level (lower is better, 0 is the best quality)
])
.save(normalizedOutput)
.on('end', () => {
vscode.window.showInformationMessage(`GIF created: ${normalizedOutput}`);
resolve();
})
.on('error', (err) => {
vscode.window.showErrorMessage(`Error: ${err.message}`);
reject(err);
});
});
}


module.exports = { convertVideoToGifCommand };
14 changes: 13 additions & 1 deletion commands/folderShortcuts.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,16 @@ const openWorkspaceFolderCommand = vscode.commands.registerCommand('bg3-mod-help
}
});

module.exports = { openModsFolderCommand, openGameFolderCommand, openLogsFolderCommand, openWorkspaceFolderCommand };
const openPlayerProfilesFolderCommand = vscode.commands.registerCommand('bg3-mod-helper.openPlayerProfilesFolder', async () => {
const appDataPath = process.env.LOCALAPPDATA;
const playerProfilesPath = path.join(appDataPath, 'Larian Studios', "Baldur's Gate 3", 'PlayerProfiles');

try {
await fs.access(playerProfilesPath);
vscode.env.openExternal(vscode.Uri.file(playerProfilesPath));
} catch (error) {
vscode.window.showInformationMessage('PlayerProfiles folder not found.');
}
});

module.exports = { openModsFolderCommand, openGameFolderCommand, openLogsFolderCommand, openWorkspaceFolderCommand, openPlayerProfilesFolderCommand };
64 changes: 64 additions & 0 deletions commands/indentXmlFiles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
const vscode = require('vscode');
const fs = require('fs');
const path = require('path');
const os = require('os');
const { raiseError, raiseInfo } = require('../support_files/log_utils');
const { getConfig, loadConfigFile, setModName, setConfig } = require('../support_files/config');
const builder = require('xmlbuilder');

async function indentXmlFiles() {
const config = getConfig();
const rootModPath = config.rootModPath;
const indentLevel = await vscode.window.showInputBox({
prompt: 'Enter the number of spaces for indentation',
validateInput: (value) => {
const num = parseInt(value, 10);
return isNaN(num) || num < 0 ? 'Please enter a valid non-negative number' : null;
}
});

if (!indentLevel) {
return; // User cancelled the input box
}

const findFiles = (dir, extensions, fileList = []) => {
fs.readdirSync(dir).forEach(file => {
const filePath = path.join(dir, file);
if (fs.statSync(filePath).isDirectory()) {
// Skip the Localization folder
if (path.basename(filePath).toLowerCase() !== 'localization') {
fileList = findFiles(filePath, extensions, fileList);
}
} else if (extensions.some(ext => filePath.endsWith(ext))) {
fileList.push(filePath);
}
});
return fileList;
};

const files = findFiles(rootModPath, ['.lsx', '.lsj']);

for (const filePath of files) {
let fileContent = fs.readFileSync(filePath, 'utf-8');
// Remove BOM if it exists
if (fileContent.charCodeAt(0) === 0xFEFF) {
fileContent = fileContent.slice(1);
}
try {
const doc = builder.create(fileContent, { headless: true });
const formattedContent = doc.end({ pretty: true, indent: ' '.repeat(parseInt(indentLevel, 10)) });
fs.writeFileSync(filePath, formattedContent, 'utf-8');
raiseInfo(`File ${filePath} has been formatted with an indent level of ${indentLevel} spaces.`);
} catch (error) {
raiseError(`Failed to process file ${filePath}: ${error.message}`);
}
}

raiseInfo('XML files formatting process completed.');
}

const indentXmlFilesCommand = vscode.commands.registerCommand('bg3-mod-helper.indentXmlFilesCommand', async function () {
await indentXmlFiles();
});

module.exports = { indentXmlFilesCommand };
65 changes: 63 additions & 2 deletions commands/insertHandleUUID.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,14 @@ let handleDisposable = vscode.commands.registerCommand('bg3-mod-helper.insertHan

// Update localization files with the handle
await updateLocaXmlFiles(changes);

// Save the updated localization files
const locaFiles = await vscode.workspace.findFiles('**/Localization/**/*.xml');
for (const locaFile of locaFiles) {
const document = await vscode.workspace.openTextDocument(locaFile);
await document.save(); // Save each localization XML file directly
}

console.log(`Handle ${handle} created with initial value: ${userText}`);
}
}
Expand All @@ -103,7 +111,60 @@ let handleDisposable = vscode.commands.registerCommand('bg3-mod-helper.insertHan
vscode.window.showInformationMessage('Handles inserted and localization files updated successfully.');
} else {
console.error('Apply Edit failed:', workspaceEdit);
vscode.window.showErrorMessage('Failed to replace the UUIDs.');
vscode.window.showErrorMessage('Failed to replace the handle.');
}
});

// Command to generate and replace all handles
let handleReplaceDisposable = vscode.commands.registerCommand('bg3-mod-helper.generateReplaceAllHandles', async function () {
const editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showInformationMessage('You need to open an editor window to use this command');
return;
}

const document = editor.document;
const selection = editor.selection;
const selectedText = document.getText(selection);

// Validate the selected text as handle
const handleRegex = /^h[0-9a-fA-Fg]{32}[0-9a-fA-Fg]{4}$/i;
console.log(handleRegex)
console.log(selectedText)
if (!handleRegex.test(selectedText)) {
vscode.window.showErrorMessage('The selected text is not a valid handle.');
return;
}

const newHandle = generateHandle();
const globalRegex = new RegExp(selectedText, 'gi'); // Global case-insensitive search
const workspaceEdit = new vscode.WorkspaceEdit();
let documentsToSave = new Set();

// Search across all text files in the workspace
const files = await vscode.workspace.findFiles('**/*.{txt,lsx,lsj,xml}');
for (const file of files) {
const textDoc = await vscode.workspace.openTextDocument(file);
const text = textDoc.getText();
let match;
while ((match = globalRegex.exec(text)) !== null) {
const startPos = textDoc.positionAt(match.index);
const endPos = textDoc.positionAt(match.index + match[0].length);
const range = new vscode.Range(startPos, endPos);
workspaceEdit.replace(file, range, newHandle);
documentsToSave.add(textDoc);
}
}

// Apply/save all collected edits
if (await vscode.workspace.applyEdit(workspaceEdit)) {

for (const doc of documentsToSave) {
await doc.save();
}
vscode.window.showInformationMessage(`Replaced all occurrences of the handle '${selectedText}' with '${newHandle}' and saved changes. Use undo keyboard shortcut to revert all changes back at once (dont forget to save the files if you do).`);
} else {
vscode.window.showErrorMessage('Failed to replace the handles.');
}
});

Expand All @@ -122,7 +183,7 @@ async function updateLocaXmlFiles(changes) {
}

const locaFilePattern = new vscode.RelativePattern(workspaceFolder, '**/Localization/**/*.xml');
const locaFiles = await vscode.workspace.findFiles(locaFilePattern, '**/node_modules/**');
const locaFiles = await vscode.workspace.findFiles(locaFilePattern);
if (locaFiles.length === 0) {
vscode.window.showWarningMessage(`No .xml files found under Localization/. You can create one with the 'Create BG3 File' command.`, 'Create BG3 File').then(selection => {
if (selection === 'Create BG3 File') {
Expand Down
6 changes: 4 additions & 2 deletions commands/launchGame.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ const { getConfig } = require('../support_files/config');


const launchGameCommand = vscode.commands.registerCommand('bg3-mod-helper.launchGame', function () {
const { launchContinueGame, gameInstallLocation } = getConfig();
const { launchContinueGame, gameInstallLocation, laucherAPI } = getConfig();
if (!gameInstallLocation || gameInstallLocation === "") {
vscode.window.showErrorMessage('Game installation location is not set. Please configure it correctly in settings.');
return; // Stop execution if the path is not set
}

const executableName = laucherAPI === 'DirectX' ? 'bg3_dx11.exe' : 'bg3.exe';

// Construct the path to the executable
const binLocation = path.join(gameInstallLocation, 'bin');
const gamePath = path.join(binLocation, "bg3.exe");
const gamePath = path.join(binLocation, executableName);
const args = launchContinueGame ? ["-continueGame"] : [];

const game = spawn(gamePath, args, { cwd: binLocation });
Expand Down
Loading

0 comments on commit da570bc

Please sign in to comment.