Upload files to "/"
This commit is contained in:
824
renderer.js
Normal file
824
renderer.js
Normal file
@@ -0,0 +1,824 @@
|
||||
const { ipcRenderer } = require('electron');
|
||||
const CryptoJS = require('crypto-js');
|
||||
|
||||
// Application state
|
||||
let currentSaveData = null;
|
||||
let currentFilePath = null;
|
||||
let isModified = false;
|
||||
|
||||
// AES encryption constants
|
||||
const AES_KEY_WORDS = [2815074099, 1725469378, 4039046167, 874293617, 3063605751, 3133984764, 4097598161, 3620741625];
|
||||
const AES_KEY = CryptoJS.lib.WordArray.create(AES_KEY_WORDS);
|
||||
const AES_IV = CryptoJS.enc.Hex.parse('7475383967656A693334307438397532');
|
||||
|
||||
// Initialize the application
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
setTimeout(() => {
|
||||
startupAnimation();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
function startupAnimation() {
|
||||
const overlay = document.getElementById('startup-overlay');
|
||||
const progressFill = document.getElementById('startup-progress');
|
||||
const progressPercent = document.getElementById('progress-percent');
|
||||
const statusText = document.getElementById('startup-status');
|
||||
|
||||
if (!overlay || !progressFill || !progressPercent || !statusText) {
|
||||
initializeApp();
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize
|
||||
progressFill.style.width = '0%';
|
||||
progressPercent.textContent = '0%';
|
||||
|
||||
const steps = [
|
||||
{ progress: 25, status: 'Loading Vault-Tec Database...', delay: 300 },
|
||||
{ progress: 50, status: 'Initializing Encryption...', delay: 300 },
|
||||
{ progress: 75, status: 'Connecting Systems...', delay: 300 },
|
||||
{ progress: 100, status: 'Welcome to Vault-Tec!', delay: 400 }
|
||||
];
|
||||
|
||||
let currentStep = 0;
|
||||
|
||||
function updateProgress() {
|
||||
if (currentStep < steps.length) {
|
||||
const step = steps[currentStep];
|
||||
|
||||
statusText.textContent = step.status;
|
||||
progressFill.style.width = step.progress + '%';
|
||||
progressPercent.textContent = step.progress + '%';
|
||||
|
||||
currentStep++;
|
||||
setTimeout(updateProgress, step.delay);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
overlay.classList.add('fade-out');
|
||||
setTimeout(() => {
|
||||
overlay.style.display = 'none';
|
||||
initializeApp();
|
||||
}, 800);
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(updateProgress, 500);
|
||||
}
|
||||
|
||||
function initializeApp() {
|
||||
initializeEventListeners();
|
||||
initializeTabSystem();
|
||||
updateUI();
|
||||
setStatus('Ready');
|
||||
|
||||
setTimeout(() => {
|
||||
showToast('Vault-Tec Save Editor ready!', 'success');
|
||||
}, 200);
|
||||
}
|
||||
|
||||
function initializeEventListeners() {
|
||||
// Navigation tabs
|
||||
document.querySelectorAll('.nav-tab').forEach(tab => {
|
||||
tab.addEventListener('click', () => switchTab(tab.dataset.tab));
|
||||
});
|
||||
|
||||
// File operations
|
||||
const openFileBtn = document.getElementById('open-file-btn');
|
||||
const getStartedBtn = document.getElementById('get-started-btn');
|
||||
const saveBtn = document.getElementById('save-btn');
|
||||
const backupBtn = document.getElementById('backup-btn');
|
||||
|
||||
if (openFileBtn) openFileBtn.addEventListener('click', openFile);
|
||||
if (getStartedBtn) getStartedBtn.addEventListener('click', openFile);
|
||||
if (saveBtn) saveBtn.addEventListener('click', saveFile);
|
||||
if (backupBtn) backupBtn.addEventListener('click', createBackup);
|
||||
|
||||
// Resource operations
|
||||
const maxResourcesBtn = document.getElementById('max-resources-btn');
|
||||
const resetResourcesBtn = document.getElementById('reset-resources-btn');
|
||||
const applyResourcesBtn = document.getElementById('apply-resources-btn');
|
||||
|
||||
if (maxResourcesBtn) maxResourcesBtn.addEventListener('click', maxAllResources);
|
||||
if (resetResourcesBtn) resetResourcesBtn.addEventListener('click', resetResources);
|
||||
if (applyResourcesBtn) applyResourcesBtn.addEventListener('click', applyResourceChanges);
|
||||
|
||||
// Vault operations
|
||||
const applyVaultBtn = document.getElementById('apply-vault-btn');
|
||||
if (applyVaultBtn) applyVaultBtn.addEventListener('click', applyVaultChanges);
|
||||
|
||||
// Raw data operations
|
||||
const formatJsonBtn = document.getElementById('format-json-btn');
|
||||
const copyJsonBtn = document.getElementById('copy-json-btn');
|
||||
|
||||
if (formatJsonBtn) formatJsonBtn.addEventListener('click', formatJSON);
|
||||
if (copyJsonBtn) copyJsonBtn.addEventListener('click', copyJSON);
|
||||
|
||||
// Dweller operations
|
||||
const maxAllDwellersBtn = document.getElementById('max-all-dwellers-btn');
|
||||
const refreshDwellersBtn = document.getElementById('refresh-dwellers-btn');
|
||||
|
||||
if (maxAllDwellersBtn) maxAllDwellersBtn.addEventListener('click', maxAllDwellers);
|
||||
if (refreshDwellersBtn) refreshDwellersBtn.addEventListener('click', populateDwellers);
|
||||
|
||||
// IPC listeners
|
||||
ipcRenderer.on('file-opened', handleFileOpened);
|
||||
ipcRenderer.on('menu-save', saveFile);
|
||||
}
|
||||
|
||||
function initializeTabSystem() {
|
||||
switchTab('overview');
|
||||
}
|
||||
|
||||
function switchTab(tabName) {
|
||||
document.querySelectorAll('.nav-tab').forEach(tab => {
|
||||
tab.classList.toggle('active', tab.dataset.tab === tabName);
|
||||
});
|
||||
|
||||
document.querySelectorAll('.tab-content').forEach(content => {
|
||||
content.classList.toggle('active', content.id === `${tabName}-tab`);
|
||||
});
|
||||
}
|
||||
|
||||
function openFile() {
|
||||
ipcRenderer.send('open-file-dialog');
|
||||
}
|
||||
|
||||
async function handleFileOpened(event, fileData) {
|
||||
showLoading(true);
|
||||
setStatus('Loading save file...');
|
||||
|
||||
try {
|
||||
currentFilePath = fileData.path;
|
||||
document.getElementById('current-file').textContent = fileData.name;
|
||||
|
||||
const decryptedData = decryptSaveData(fileData.data.trim());
|
||||
currentSaveData = JSON.parse(decryptedData);
|
||||
|
||||
populateResourceInputs();
|
||||
populateVaultInfo();
|
||||
populateDwellers();
|
||||
updateRawData();
|
||||
|
||||
document.getElementById('save-btn').disabled = false;
|
||||
document.getElementById('backup-btn').disabled = false;
|
||||
|
||||
switchTab('resources');
|
||||
|
||||
setStatus('Save file loaded successfully');
|
||||
showToast('Save file loaded successfully', 'success');
|
||||
setModified(false);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading save file:', error);
|
||||
showToast(`Error loading save file: ${error.message}`, 'error');
|
||||
setStatus('Error loading save file');
|
||||
} finally {
|
||||
showLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
function decryptSaveData(base64Data) {
|
||||
try {
|
||||
const ciphertext = CryptoJS.enc.Base64.parse(base64Data);
|
||||
const cipherParams = CryptoJS.lib.CipherParams.create({
|
||||
ciphertext: ciphertext
|
||||
});
|
||||
|
||||
const decrypted = CryptoJS.AES.decrypt(cipherParams, AES_KEY, {
|
||||
iv: AES_IV,
|
||||
mode: CryptoJS.mode.CBC,
|
||||
padding: CryptoJS.pad.Pkcs7
|
||||
});
|
||||
|
||||
const decryptedString = decrypted.toString(CryptoJS.enc.Utf8);
|
||||
|
||||
if (!decryptedString) {
|
||||
throw new Error('Decryption failed - invalid key or corrupted data');
|
||||
}
|
||||
|
||||
return decryptedString;
|
||||
} catch (error) {
|
||||
throw new Error(`Decryption failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function encryptSaveData(jsonString) {
|
||||
try {
|
||||
const encrypted = CryptoJS.AES.encrypt(jsonString, AES_KEY, {
|
||||
iv: AES_IV,
|
||||
mode: CryptoJS.mode.CBC,
|
||||
padding: CryptoJS.pad.Pkcs7
|
||||
});
|
||||
|
||||
return encrypted.toString();
|
||||
} catch (error) {
|
||||
throw new Error(`Encryption failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function populateResourceInputs() {
|
||||
if (!currentSaveData || !currentSaveData.vault || !currentSaveData.vault.storage) {
|
||||
console.log('No vault storage data found');
|
||||
return;
|
||||
}
|
||||
|
||||
const resources = currentSaveData.vault.storage.resources;
|
||||
console.log('Available resources:', Object.keys(resources));
|
||||
|
||||
const resourceMap = {
|
||||
'caps-input': 'Nuka',
|
||||
'food-input': 'Food',
|
||||
'water-input': 'Water',
|
||||
'power-input': 'Energy',
|
||||
'stimpaks-input': 'StimPack',
|
||||
'radaway-input': 'RadAway',
|
||||
'quantum-input': 'NukaColaQuantum',
|
||||
'lunchbox-input': 'Lunchbox',
|
||||
'mrhandy-input': 'MrHandy',
|
||||
'petcarrier-input': 'PetCarrier'
|
||||
};
|
||||
|
||||
Object.entries(resourceMap).forEach(([inputId, resourceKey]) => {
|
||||
const input = document.getElementById(inputId);
|
||||
if (input && resources[resourceKey] !== undefined) {
|
||||
input.value = Math.floor(resources[resourceKey]);
|
||||
console.log(`Set ${inputId} to ${resources[resourceKey]}`);
|
||||
} else {
|
||||
console.log(`Could not find ${inputId} or ${resourceKey}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function populateVaultInfo() {
|
||||
if (!currentSaveData || !currentSaveData.vault) {
|
||||
return;
|
||||
}
|
||||
|
||||
const vault = currentSaveData.vault;
|
||||
|
||||
const vaultNameInput = document.getElementById('vault-name-input');
|
||||
if (vaultNameInput && vault.VaultName !== undefined) {
|
||||
vaultNameInput.value = vault.VaultName;
|
||||
}
|
||||
|
||||
const vaultModeInput = document.getElementById('vault-mode-input');
|
||||
if (vaultModeInput && vault.VaultMode !== undefined) {
|
||||
vaultModeInput.value = vault.VaultMode;
|
||||
}
|
||||
|
||||
const vaultThemeInput = document.getElementById('vault-theme-input');
|
||||
if (vaultThemeInput && vault.VaultTheme !== undefined) {
|
||||
vaultThemeInput.value = vault.VaultTheme;
|
||||
}
|
||||
}
|
||||
|
||||
function applyResourceChanges() {
|
||||
if (!currentSaveData || !currentSaveData.vault || !currentSaveData.vault.storage) {
|
||||
showToast('No save data loaded', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Applying resource changes...');
|
||||
const resources = currentSaveData.vault.storage.resources;
|
||||
console.log('Current resources before changes:', resources);
|
||||
|
||||
const resourceMap = {
|
||||
'caps-input': 'Nuka',
|
||||
'food-input': 'Food',
|
||||
'water-input': 'Water',
|
||||
'power-input': 'Energy',
|
||||
'stimpaks-input': 'StimPack',
|
||||
'radaway-input': 'RadAway',
|
||||
'quantum-input': 'NukaColaQuantum',
|
||||
'lunchbox-input': 'Lunchbox',
|
||||
'mrhandy-input': 'MrHandy',
|
||||
'petcarrier-input': 'PetCarrier'
|
||||
};
|
||||
|
||||
let changesApplied = 0;
|
||||
|
||||
Object.entries(resourceMap).forEach(([inputId, resourceKey]) => {
|
||||
const input = document.getElementById(inputId);
|
||||
if (input && input.value.trim()) {
|
||||
const value = parseFloat(input.value);
|
||||
if (!isNaN(value) && value >= 0) {
|
||||
console.log(`Changing ${resourceKey} from ${resources[resourceKey]} to ${value}`);
|
||||
resources[resourceKey] = value;
|
||||
changesApplied++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Resources after changes:', resources);
|
||||
console.log(`Applied ${changesApplied} changes`);
|
||||
|
||||
if (changesApplied > 0) {
|
||||
setModified(true);
|
||||
updateRawData();
|
||||
showToast(`Applied ${changesApplied} resource changes`, 'success');
|
||||
} else {
|
||||
showToast('No valid changes to apply', 'warning');
|
||||
}
|
||||
}
|
||||
|
||||
function maxAllResources() {
|
||||
const maxValues = {
|
||||
'caps-input': 999999,
|
||||
'food-input': 999999,
|
||||
'water-input': 999999,
|
||||
'power-input': 999999,
|
||||
'stimpaks-input': 999999,
|
||||
'radaway-input': 999999,
|
||||
'quantum-input': 999,
|
||||
'lunchbox-input': 999,
|
||||
'mrhandy-input': 99,
|
||||
'petcarrier-input': 999
|
||||
};
|
||||
|
||||
Object.entries(maxValues).forEach(([inputId, value]) => {
|
||||
const input = document.getElementById(inputId);
|
||||
if (input) {
|
||||
input.value = value;
|
||||
}
|
||||
});
|
||||
|
||||
applyResourceChanges();
|
||||
}
|
||||
|
||||
function resetResources() {
|
||||
populateResourceInputs();
|
||||
showToast('Resources reset to saved values', 'info');
|
||||
}
|
||||
|
||||
function applyVaultChanges() {
|
||||
if (!currentSaveData || !currentSaveData.vault) {
|
||||
showToast('No save data loaded', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const vault = currentSaveData.vault;
|
||||
let changesApplied = 0;
|
||||
|
||||
const vaultNameInput = document.getElementById('vault-name-input');
|
||||
if (vaultNameInput && vaultNameInput.value.trim()) {
|
||||
vault.VaultName = vaultNameInput.value.trim();
|
||||
changesApplied++;
|
||||
}
|
||||
|
||||
const vaultModeInput = document.getElementById('vault-mode-input');
|
||||
if (vaultModeInput && vaultModeInput.value) {
|
||||
vault.VaultMode = vaultModeInput.value;
|
||||
changesApplied++;
|
||||
}
|
||||
|
||||
const vaultThemeInput = document.getElementById('vault-theme-input');
|
||||
if (vaultThemeInput && vaultThemeInput.value.trim()) {
|
||||
const theme = parseInt(vaultThemeInput.value);
|
||||
if (!isNaN(theme) && theme >= 0) {
|
||||
vault.VaultTheme = theme;
|
||||
changesApplied++;
|
||||
}
|
||||
}
|
||||
|
||||
if (changesApplied > 0) {
|
||||
setModified(true);
|
||||
updateRawData();
|
||||
showToast(`Applied ${changesApplied} vault changes`, 'success');
|
||||
} else {
|
||||
showToast('No valid changes to apply', 'warning');
|
||||
}
|
||||
}
|
||||
|
||||
function updateRawData() {
|
||||
const textarea = document.getElementById('raw-data-textarea');
|
||||
if (currentSaveData && textarea) {
|
||||
textarea.value = JSON.stringify(currentSaveData, null, 2);
|
||||
}
|
||||
}
|
||||
|
||||
function formatJSON() {
|
||||
const textarea = document.getElementById('raw-data-textarea');
|
||||
if (!textarea) return;
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(textarea.value);
|
||||
textarea.value = JSON.stringify(parsed, null, 2);
|
||||
showToast('JSON formatted successfully', 'success');
|
||||
} catch (error) {
|
||||
showToast('Invalid JSON format', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function copyJSON() {
|
||||
const textarea = document.getElementById('raw-data-textarea');
|
||||
if (!textarea) return;
|
||||
|
||||
textarea.select();
|
||||
document.execCommand('copy');
|
||||
showToast('JSON copied to clipboard', 'success');
|
||||
}
|
||||
|
||||
async function saveFile() {
|
||||
if (!currentSaveData || !currentFilePath) {
|
||||
showToast('No file loaded to save', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Starting save process...');
|
||||
console.log('Current file path:', currentFilePath);
|
||||
console.log('Save data exists:', !!currentSaveData);
|
||||
|
||||
showLoading(true);
|
||||
setStatus('Saving file...');
|
||||
|
||||
try {
|
||||
console.log('Converting to JSON...');
|
||||
const jsonString = JSON.stringify(currentSaveData);
|
||||
console.log('JSON string length:', jsonString.length);
|
||||
|
||||
console.log('Encrypting data...');
|
||||
const encryptedData = encryptSaveData(jsonString);
|
||||
console.log('Encrypted data length:', encryptedData.length);
|
||||
|
||||
console.log('Sending to main process...');
|
||||
const result = await ipcRenderer.invoke('save-file', currentFilePath, encryptedData);
|
||||
console.log('Save result:', result);
|
||||
|
||||
if (result.success) {
|
||||
setModified(false);
|
||||
setStatus('File saved successfully');
|
||||
showToast('File saved successfully', 'success');
|
||||
console.log('Save completed successfully');
|
||||
} else {
|
||||
throw new Error(result.error);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error saving file:', error);
|
||||
showToast(`Error saving file: ${error.message}`, 'error');
|
||||
setStatus('Error saving file');
|
||||
} finally {
|
||||
showLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function createBackup() {
|
||||
if (!currentFilePath) {
|
||||
showToast('No file loaded to backup', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await ipcRenderer.invoke('create-backup', currentFilePath);
|
||||
|
||||
if (result.success) {
|
||||
showToast(`Backup created: ${require('path').basename(result.backupPath)}`, 'success');
|
||||
} else {
|
||||
throw new Error(result.error);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error creating backup:', error);
|
||||
showToast(`Error creating backup: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function setStatus(message) {
|
||||
const statusText = document.getElementById('status-text');
|
||||
if (statusText) {
|
||||
statusText.textContent = message;
|
||||
}
|
||||
}
|
||||
|
||||
function setModified(modified) {
|
||||
isModified = modified;
|
||||
updateUI();
|
||||
}
|
||||
|
||||
function updateUI() {
|
||||
if (isModified) {
|
||||
document.title = 'Fallout Shelter Save Editor - Modified';
|
||||
} else {
|
||||
document.title = 'Fallout Shelter Save Editor';
|
||||
}
|
||||
}
|
||||
|
||||
function showLoading(show) {
|
||||
const overlay = document.getElementById('loading-overlay');
|
||||
if (overlay) {
|
||||
overlay.classList.toggle('show', show);
|
||||
}
|
||||
}
|
||||
|
||||
function showToast(message, type = 'info') {
|
||||
const container = document.getElementById('toast-container');
|
||||
if (!container) return;
|
||||
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `toast ${type}`;
|
||||
toast.textContent = message;
|
||||
|
||||
container.appendChild(toast);
|
||||
|
||||
setTimeout(() => {
|
||||
toast.remove();
|
||||
}, 4000);
|
||||
}
|
||||
|
||||
// Dwellers Management
|
||||
function populateDwellers() {
|
||||
const dwellersList = document.getElementById('dwellers-list');
|
||||
|
||||
if (!currentSaveData || !currentSaveData.dwellers || !currentSaveData.dwellers.dwellers) {
|
||||
if (dwellersList) {
|
||||
dwellersList.innerHTML = `
|
||||
<div class="placeholder">
|
||||
<i class="fas fa-users"></i>
|
||||
<p>No dwellers found in save file</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const dwellers = currentSaveData.dwellers.dwellers;
|
||||
|
||||
if (dwellers.length === 0) {
|
||||
dwellersList.innerHTML = `
|
||||
<div class="placeholder">
|
||||
<i class="fas fa-users"></i>
|
||||
<p>No dwellers in vault</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Found ${dwellers.length} dwellers`);
|
||||
|
||||
dwellersList.innerHTML = dwellers.map((dweller, index) => {
|
||||
const name = dweller.name || `Dweller ${index + 1}`;
|
||||
const level = dweller.experience?.currentLevel || 1;
|
||||
const happiness = Math.round(dweller.happiness?.happinessValue || 0);
|
||||
const health = Math.round(dweller.health?.healthValue || 100);
|
||||
const gender = dweller.gender === 2 ? 'Female' : 'Male';
|
||||
const pregnant = dweller.relations?.pregnant || false;
|
||||
|
||||
// SPECIAL stats
|
||||
const special = dweller.serializeableSpecialStats || {};
|
||||
const strength = special.stats?.[1] || 1;
|
||||
const perception = special.stats?.[2] || 1;
|
||||
const endurance = special.stats?.[3] || 1;
|
||||
const charisma = special.stats?.[4] || 1;
|
||||
const intelligence = special.stats?.[5] || 1;
|
||||
const agility = special.stats?.[6] || 1;
|
||||
const luck = special.stats?.[7] || 1;
|
||||
|
||||
return `
|
||||
<div class="dweller-card" data-index="${index}">
|
||||
<div class="dweller-header">
|
||||
<h3 class="dweller-name">${name}</h3>
|
||||
<span class="dweller-gender ${gender.toLowerCase()}">${gender}</span>
|
||||
${pregnant ? '<span class="pregnant-badge">Pregnant</span>' : ''}
|
||||
</div>
|
||||
|
||||
<div class="dweller-stats">
|
||||
<div class="stat-row">
|
||||
<span>Level:</span>
|
||||
<input type="number" class="dweller-input" data-field="experience.currentLevel" value="${level}" min="1" max="50">
|
||||
</div>
|
||||
<div class="stat-row">
|
||||
<span>Happiness:</span>
|
||||
<input type="number" class="dweller-input" data-field="happiness.happinessValue" value="${happiness}" min="0" max="100">%
|
||||
</div>
|
||||
<div class="stat-row">
|
||||
<span>Health:</span>
|
||||
<input type="number" class="dweller-input" data-field="health.healthValue" value="${health}" min="0" max="100">%
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="special-stats">
|
||||
<h4>SPECIAL</h4>
|
||||
<div class="special-grid">
|
||||
<div class="special-stat">
|
||||
<label>S</label>
|
||||
<input type="number" class="special-input" data-stat="1" value="${strength}" min="1" max="10">
|
||||
</div>
|
||||
<div class="special-stat">
|
||||
<label>P</label>
|
||||
<input type="number" class="special-input" data-stat="2" value="${perception}" min="1" max="10">
|
||||
</div>
|
||||
<div class="special-stat">
|
||||
<label>E</label>
|
||||
<input type="number" class="special-input" data-stat="3" value="${endurance}" min="1" max="10">
|
||||
</div>
|
||||
<div class="special-stat">
|
||||
<label>C</label>
|
||||
<input type="number" class="special-input" data-stat="4" value="${charisma}" min="1" max="10">
|
||||
</div>
|
||||
<div class="special-stat">
|
||||
<label>I</label>
|
||||
<input type="number" class="special-input" data-stat="5" value="${intelligence}" min="1" max="10">
|
||||
</div>
|
||||
<div class="special-stat">
|
||||
<label>A</label>
|
||||
<input type="number" class="special-input" data-stat="6" value="${agility}" min="1" max="10">
|
||||
</div>
|
||||
<div class="special-stat">
|
||||
<label>L</label>
|
||||
<input type="number" class="special-input" data-stat="7" value="${luck}" min="1" max="10">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dweller-actions">
|
||||
<button class="secondary-btn apply-dweller-btn" data-index="${index}">
|
||||
<i class="fas fa-check"></i>
|
||||
Apply Changes
|
||||
</button>
|
||||
<button class="secondary-btn max-special-btn" data-index="${index}">
|
||||
<i class="fas fa-arrow-up"></i>
|
||||
Max SPECIAL
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
// Add event listeners for dweller editing
|
||||
addDwellerEventListeners();
|
||||
}
|
||||
|
||||
function addDwellerEventListeners() {
|
||||
// Apply changes buttons
|
||||
document.querySelectorAll('.apply-dweller-btn').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
const index = parseInt(e.target.closest('.apply-dweller-btn').dataset.index);
|
||||
applyDwellerChanges(index);
|
||||
});
|
||||
});
|
||||
|
||||
// Max SPECIAL buttons
|
||||
document.querySelectorAll('.max-special-btn').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
const index = parseInt(e.target.closest('.max-special-btn').dataset.index);
|
||||
maxDwellerSpecial(index);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function applyDwellerChanges(dwellerIndex) {
|
||||
if (!currentSaveData || !currentSaveData.dwellers || !currentSaveData.dwellers.dwellers[dwellerIndex]) {
|
||||
showToast('Dweller not found', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const dweller = currentSaveData.dwellers.dwellers[dwellerIndex];
|
||||
const card = document.querySelector(`.dweller-card[data-index="${dwellerIndex}"]`);
|
||||
|
||||
if (!card) return;
|
||||
|
||||
let changesApplied = 0;
|
||||
|
||||
// Apply basic stats
|
||||
card.querySelectorAll('.dweller-input').forEach(input => {
|
||||
const field = input.dataset.field;
|
||||
const value = parseFloat(input.value);
|
||||
|
||||
if (!isNaN(value)) {
|
||||
setNestedValue(dweller, field, value);
|
||||
changesApplied++;
|
||||
}
|
||||
});
|
||||
|
||||
// Apply SPECIAL stats
|
||||
card.querySelectorAll('.special-input').forEach(input => {
|
||||
const statIndex = parseInt(input.dataset.stat);
|
||||
const value = parseInt(input.value);
|
||||
|
||||
if (!isNaN(value) && value >= 1 && value <= 10) {
|
||||
if (!dweller.serializeableSpecialStats) {
|
||||
dweller.serializeableSpecialStats = { stats: {} };
|
||||
}
|
||||
if (!dweller.serializeableSpecialStats.stats) {
|
||||
dweller.serializeableSpecialStats.stats = {};
|
||||
}
|
||||
|
||||
dweller.serializeableSpecialStats.stats[statIndex] = value;
|
||||
changesApplied++;
|
||||
}
|
||||
});
|
||||
|
||||
if (changesApplied > 0) {
|
||||
setModified(true);
|
||||
updateRawData();
|
||||
showToast(`Applied ${changesApplied} changes to dweller`, 'success');
|
||||
} else {
|
||||
showToast('No valid changes to apply', 'warning');
|
||||
}
|
||||
}
|
||||
|
||||
function maxDwellerSpecial(dwellerIndex) {
|
||||
const card = document.querySelector(`.dweller-card[data-index="${dwellerIndex}"]`);
|
||||
if (!card) return;
|
||||
|
||||
// Set all SPECIAL stats to 10
|
||||
card.querySelectorAll('.special-input').forEach(input => {
|
||||
input.value = 10;
|
||||
});
|
||||
|
||||
applyDwellerChanges(dwellerIndex);
|
||||
}
|
||||
|
||||
function setNestedValue(obj, path, value) {
|
||||
const keys = path.split('.');
|
||||
let current = obj;
|
||||
|
||||
for (let i = 0; i < keys.length - 1; i++) {
|
||||
if (!current[keys[i]]) {
|
||||
current[keys[i]] = {};
|
||||
}
|
||||
current = current[keys[i]];
|
||||
}
|
||||
|
||||
current[keys[keys.length - 1]] = value;
|
||||
}
|
||||
|
||||
// Advanced Editing Functions
|
||||
function addAdvancedResourceOptions() {
|
||||
// Add lunchbox and other premium resources
|
||||
const advancedResources = {
|
||||
'lunchbox-input': 'Lunchbox',
|
||||
'mrhandy-input': 'MrHandy',
|
||||
'petcarrier-input': 'PetCarrier',
|
||||
'craftedoutfit-input': 'CraftedOutfit',
|
||||
'craftedweapon-input': 'CraftedWeapon',
|
||||
'craftedtheme-input': 'CraftedTheme'
|
||||
};
|
||||
|
||||
if (currentSaveData && currentSaveData.vault && currentSaveData.vault.storage) {
|
||||
const resources = currentSaveData.vault.storage.resources;
|
||||
|
||||
Object.entries(advancedResources).forEach(([inputId, resourceKey]) => {
|
||||
const input = document.getElementById(inputId);
|
||||
if (input && resources[resourceKey] !== undefined) {
|
||||
input.value = Math.floor(resources[resourceKey]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function maxAllDwellers() {
|
||||
if (!currentSaveData || !currentSaveData.dwellers || !currentSaveData.dwellers.dwellers) {
|
||||
showToast('No dwellers found', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const dwellers = currentSaveData.dwellers.dwellers;
|
||||
let modifiedCount = 0;
|
||||
|
||||
dwellers.forEach((dweller, index) => {
|
||||
// Max level
|
||||
if (dweller.experience) {
|
||||
dweller.experience.currentLevel = 50;
|
||||
dweller.experience.experienceValue = 2916000; // Max XP for level 50
|
||||
}
|
||||
|
||||
// Max happiness and health
|
||||
if (dweller.happiness) {
|
||||
dweller.happiness.happinessValue = 100;
|
||||
}
|
||||
if (dweller.health) {
|
||||
dweller.health.healthValue = 100;
|
||||
}
|
||||
|
||||
// Max SPECIAL
|
||||
if (!dweller.serializeableSpecialStats) {
|
||||
dweller.serializeableSpecialStats = { stats: {} };
|
||||
}
|
||||
if (!dweller.serializeableSpecialStats.stats) {
|
||||
dweller.serializeableSpecialStats.stats = {};
|
||||
}
|
||||
|
||||
for (let i = 1; i <= 7; i++) {
|
||||
dweller.serializeableSpecialStats.stats[i] = 10;
|
||||
}
|
||||
|
||||
modifiedCount++;
|
||||
});
|
||||
|
||||
if (modifiedCount > 0) {
|
||||
setModified(true);
|
||||
updateRawData();
|
||||
populateDwellers(); // Refresh the display
|
||||
showToast(`Maxed out ${modifiedCount} dwellers`, 'success');
|
||||
}
|
||||
}
|
||||
|
||||
function formatFileSize(bytes) {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
}
|
Reference in New Issue
Block a user