Little Alchemy 2 Ultimate Mod

Advanced mod for Little Alchemy 2 with working unlock, auto-discovery, and encyclopedia!

// ==UserScript==
// @name         Little Alchemy 2 Ultimate Mod
// @namespace    http://tampermonkey.net/
// @version      2.4
// @description  Advanced mod for Little Alchemy 2 with working unlock, auto-discovery, and encyclopedia!
// @author       You
// @match        https://littlealchemy2.com/*
// @license      CC BY-NC 4.0; https://creativecommons.org/licenses/by-nc/4.0/
// @grant        none
// ==/UserScript==

/*
    This work is licensed under the Creative Commons Attribution-NonCommercial 4.0 International License.
    To view a copy of this license, visit https://creativecommons.org/licenses/by-nc/4.0/

    You are free to share and adapt this code, but:
    - You must give appropriate credit.
    - You may not use this work for commercial purposes.
*/

(function() {
    'use strict';

    let autoDiscoveryInterval = null;
    let autoDiscoveryActive = false;
    let discoveredCount = 0;
    let lastDiscoveredCount = 0;

    function getVueStore() {
        const workspace = document.querySelector('#workspace');
        if (workspace && workspace.__vue__ && workspace.__vue__.$store) {
            return workspace.__vue__.$store;
        }
        return null;
    }

    function showNotification(message, duration = 3000) {
        const notif = document.createElement('div');
        notif.textContent = message;
        notif.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 15px 25px;
            border-radius: 8px;
            font-family: Arial, sans-serif;
            font-size: 14px;
            z-index: 9999999;
            box-shadow: 0 4px 12px rgba(0,0,0,0.3);
            animation: slideIn 0.3s ease-out;
        `;
        document.body.appendChild(notif);
        setTimeout(() => {
            notif.style.animation = 'slideOut 0.3s ease-in';
            setTimeout(() => notif.remove(), 300);
        }, duration);
    }

    function findAndUpdateArrays(obj, allIds, elements, path = '', arraysUpdated = []) {
        for (let key in obj) {
            const currentPath = path ? `${path}.${key}` : key;
            const value = obj[key];
            
            if (Array.isArray(value) && value.length < 700) {
                let isElementArray = false;
                
                if (value.length > 0) {
                    const first = value[0];
                    isElementArray = typeof first === 'string' && elements[first];
                } else {
                    isElementArray = ['progress', 'discovered', 'encyclopedia', 'library', 'history'].some(name => 
                        currentPath.toLowerCase().includes(name)
                    );
                }
                
                if (isElementArray) {
                    let addedCount = 0;
                    allIds.forEach(id => {
                        if (!value.includes(id) && !elements[id].prime) {
                            value.push(id);
                            addedCount++;
                        }
                    });
                    if (addedCount > 0) {
                        arraysUpdated.push(`${currentPath} (+${addedCount})`);
                        console.log(`Added ${addedCount} elements to:`, currentPath);
                    }
                }
            } else if (value && typeof value === 'object' && !Array.isArray(value) && key !== '__ob__') {
                findAndUpdateArrays(value, allIds, elements, currentPath, arraysUpdated);
            }
        }
        return arraysUpdated;
    }

    function unlockAllElements() {
        try {
            const store = getVueStore();
            if (!store) {
                showNotification('❌ Store not found! Wait for game to load.');
                return;
            }

            console.log('=== UNLOCK ATTEMPT ===');
            const beforeCount = store.getters.totalProgress || 0;
            console.log('Before count:', beforeCount);

            const allIds = store.getters.elementsIds;
            const state = store.state;
            const elements = store.getters.elements;
            
            console.log('Searching for element arrays in state...');
            const arraysUpdated = findAndUpdateArrays(state, allIds, elements);

            setTimeout(() => {
                const afterCount = store.getters.totalProgress || 0;
                console.log('After count:', afterCount);
                const unlocked = afterCount - beforeCount;

                if (unlocked > 0) {
                    showNotification(`🎉 Unlocked ${unlocked} elements!`, 4000);
                    console.log('✅ Success! Arrays updated:', arraysUpdated);
                } else {
                    showNotification('⚠️ No new elements unlocked. Already complete?', 4000);
                }

                console.log('Final progress:', store.getters.totalProgress, '/ 720');
            }, 500);

        } catch (e) {
            console.error('Unlock error:', e);
            showNotification('❌ Unlock failed! Check console for details.');
        }
    }

    function viewGameData() {
        const store = getVueStore();
        if (!store) {
            showNotification('❌ Store not found!');
            return;
        }

        console.log('=== LITTLE ALCHEMY 2 GAME DATA ===');
        console.log('Total Discovered:', store.getters.totalProgress);
        console.log('Prime Elements:', store.state.base?.prime);
        console.log('Progress Elements:', store.state.progress);
        console.log('Encyclopedia:', store.state.encyclopedia);
        console.log('Final Elements:', store.state.base?.final);
        console.log('All Discovered:', [...(store.state.progress || []), ...(store.state.base?.prime || [])]);
        console.log('---');
        console.log('Store State Keys:', Object.keys(store.state));
        
        console.log('State Arrays:');
        for (let key in store.state) {
            if (Array.isArray(store.state[key])) {
                console.log(`  ${key}: ${store.state[key].length} items`);
            }
        }
        
        console.log('---');
        console.log('Total Available Elements:', store.getters.elementsIds?.length || 0);
        console.log('=================================');

        showNotification('📊 Game data logged to console! (F12)', 4000);
    }

    function toggleAutoDiscovery() {
        autoDiscoveryActive = !autoDiscoveryActive;
        const btn = document.getElementById('la2-auto-discovery-btn');
        
        if (autoDiscoveryActive) {
            btn.textContent = 'Auto-Discovery [ON]';
            btn.style.background = '#28a745';
            showNotification('🤖 Auto-discovery started!');
            startAutoDiscovery();
        } else {
            btn.textContent = 'Auto-Discovery [OFF]';
            btn.style.background = '#444';
            showNotification('⏸️ Auto-discovery stopped.');
            stopAutoDiscovery();
        }
    }

    function startAutoDiscovery() {
        if (autoDiscoveryInterval) return;

        discoveredCount = 0;
        const store = getVueStore();
        if (!store) {
            showNotification('❌ Store not found!');
            autoDiscoveryActive = false;
            return;
        }

        lastDiscoveredCount = store.getters.totalProgress || 0;

        autoDiscoveryInterval = setInterval(() => {
            try {
                const store = getVueStore();
                if (!store) return;

                const allElements = store.getters.elements;
                const recipes = store.getters.recipes;
                const elementIds = Object.keys(allElements);

                const element1Id = elementIds[Math.floor(Math.random() * elementIds.length)];
                const element2Id = elementIds[Math.floor(Math.random() * elementIds.length)];

                const recipeKey = [element1Id, element2Id].sort().join(',');
                
                if (recipes[recipeKey]) {
                    const result = recipes[recipeKey];
                    const childId = result.child || result;

                    const state = store.state;
                    let added = false;

                    function addToArrays(obj, path = '') {
                        for (let key in obj) {
                            const currentPath = path ? `${path}.${key}` : key;
                            const value = obj[key];
                            
                            if (Array.isArray(value) && value.length < 700) {
                                let isElementArray = false;
                                
                                if (value.length > 0) {
                                    const first = value[0];
                                    isElementArray = typeof first === 'string' && allElements[first];
                                } else {
                                    isElementArray = ['progress', 'discovered', 'encyclopedia', 'library', 'history'].some(name => 
                                        currentPath.toLowerCase().includes(name)
                                    );
                                }
                                
                                if (isElementArray) {
                                    if (!value.includes(childId) && !allElements[childId]?.prime) {
                                        value.push(childId);
                                        added = true;
                                    }
                                }
                            } else if (value && typeof value === 'object' && !Array.isArray(value) && key !== '__ob__') {
                                addToArrays(value, currentPath);
                            }
                        }
                    }

                    addToArrays(state);

                    if (added) {
                        const element1 = allElements[element1Id];
                        const element2 = allElements[element2Id];
                        const child = allElements[childId];
                        console.log(`✨ Discovered: ${child?.name || childId} from ${element1?.name || element1Id} + ${element2?.name || element2Id}`);

                        setTimeout(() => {
                            const newCount = store.getters.totalProgress || 0;
                            if (newCount > lastDiscoveredCount) {
                                discoveredCount++;
                                lastDiscoveredCount = newCount;
                                
                                const autoInfo = document.querySelector('#la2-auto-info');
                                if (autoInfo) {
                                    autoInfo.textContent = `✨ Discovered: ${discoveredCount} new elements`;
                                }
                            }
                        }, 100);
                    }
                }

            } catch (e) {
                console.log('Auto-discovery error:', e);
            }
        }, 1500);
    }

    function stopAutoDiscovery() {
        if (autoDiscoveryInterval) {
            clearInterval(autoDiscoveryInterval);
            autoDiscoveryInterval = null;
        }
    }

    function createGUI() {
        const gui = document.createElement('div');
        gui.id = 'la2-mod-gui';
        gui.style.cssText = `
            position: fixed;
            top: 20px;
            left: 20px;
            background: linear-gradient(135deg, #1e1e1e 0%, #2d2d2d 100%);
            color: #fff;
            border: 2px solid #555;
            border-radius: 12px;
            padding: 0;
            z-index: 999998;
            font-family: Arial, sans-serif;
            box-shadow: 0 8px 32px rgba(0,0,0,0.5);
            min-width: 300px;
            max-width: 350px;
        `;

        const header = document.createElement('div');
        header.style.cssText = `
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            padding: 12px 15px;
            border-radius: 10px 10px 0 0;
            cursor: move;
            user-select: none;
            font-weight: bold;
            font-size: 16px;
            display: flex;
            justify-content: space-between;
            align-items: center;
        `;
        header.textContent = '🧪 LA2 Mod v2.4';

        let isMinimized = false;
        const minimizeBtn = document.createElement('button');
        minimizeBtn.textContent = '−';
        minimizeBtn.style.cssText = `
            background: rgba(255,255,255,0.2);
            border: none;
            color: white;
            cursor: pointer;
            padding: 2px 8px;
            border-radius: 3px;
            font-size: 18px;
            line-height: 1;
        `;

        const content = document.createElement('div');
        content.id = 'la2-content';
        content.style.cssText = 'padding: 15px; max-height: 70vh; overflow-y: auto;';

        minimizeBtn.onclick = function(e) {
            e.stopPropagation();
            isMinimized = !isMinimized;
            content.style.display = isMinimized ? 'none' : 'block';
            minimizeBtn.textContent = isMinimized ? '+' : '−';
        };

        header.appendChild(minimizeBtn);
        gui.appendChild(header);
        gui.appendChild(content);

        makeDraggable(gui, header);

        const { section: unlockSection, content: unlockContent } = createCollapsibleSection('🔓 Unlock Elements', true);
        
        const unlockBtn = createButton('Unlock All Elements', unlockAllElements);
        unlockBtn.style.background = '#28a745';
        unlockBtn.style.fontSize = '14px';
        unlockContent.appendChild(unlockBtn);

        const viewDataBtn = createButton('View Game Data (Console)', viewGameData);
        viewDataBtn.style.marginTop = '8px';
        unlockContent.appendChild(viewDataBtn);

        const warningText = document.createElement('div');
        warningText.textContent = '✅ Safe unlock - persists after refresh!';
        warningText.style.cssText = 'font-size: 11px; color: #28a745; margin-top: 10px; text-align: center;';
        unlockContent.appendChild(warningText);
        
        content.appendChild(unlockSection);

        const { section: autoSection, content: autoContent } = createCollapsibleSection('🤖 Auto-Discovery', false);
        
        const autoBtn = createButton('Auto-Discovery [OFF]', toggleAutoDiscovery);
        autoBtn.id = 'la2-auto-discovery-btn';
        autoContent.appendChild(autoBtn);

        const autoInfo = document.createElement('div');
        autoInfo.id = 'la2-auto-info';
        autoInfo.textContent = 'Finds valid combinations and adds them to your discovery list';
        autoInfo.style.cssText = 'font-size: 11px; color: #aaa; margin-top: 8px; line-height: 1.4;';
        autoContent.appendChild(autoInfo);

        content.appendChild(autoSection);

        const { section: infoSection, content: infoContent } = createCollapsibleSection('ℹ️ Info', false);
        
        const infoText = document.createElement('div');
        infoText.innerHTML = `
            <div style="font-size: 12px; line-height: 1.6; color: #ccc;">
                <strong>v2.4 - Fixed All Issues!</strong><br>
                • Unlock now persists after refresh ✅<br>
                • Auto-discovery works correctly ✅<br>
                • Encyclopedia updates properly ✅<br><br>
                <em style="color: #999;">Press F12 for detailed debug info</em>
            </div>
        `;
        infoContent.appendChild(infoText);

        content.appendChild(infoSection);

        document.body.appendChild(gui);
    }

    function createCollapsibleSection(title, isExpanded = false) {
        const section = document.createElement('div');
        section.style.cssText = 'margin-bottom: 10px; border: 1px solid #555; border-radius: 5px; background: #2a2a2a;';

        const header = document.createElement('div');
        header.style.cssText = `
            padding: 10px 15px;
            cursor: pointer;
            user-select: none;
            background: #333;
            border-radius: 4px;
            display: flex;
            justify-content: space-between;
            align-items: center;
            font-weight: bold;
            transition: background 0.2s;
        `;
        header.onmouseover = () => header.style.background = '#3a3a3a';
        header.onmouseout = () => header.style.background = '#333';

        const titleSpan = document.createElement('span');
        titleSpan.textContent = title;

        const arrow = document.createElement('span');
        arrow.textContent = isExpanded ? '▼' : '▶';
        arrow.style.cssText = 'font-size: 12px;';

        header.appendChild(titleSpan);
        header.appendChild(arrow);

        const content = document.createElement('div');
        content.style.cssText = `
            padding: 10px 15px;
            display: ${isExpanded ? 'block' : 'none'};
        `;

        header.onclick = function(e) {
            e.stopPropagation();
            const isNowExpanded = content.style.display === 'none';
            content.style.display = isNowExpanded ? 'block' : 'none';
            arrow.textContent = isNowExpanded ? '▼' : '▶';
        };

        section.appendChild(header);
        section.appendChild(content);

        return { section, content };
    }

    function createButton(text, onClick) {
        const btn = document.createElement('button');
        btn.textContent = text;
        btn.style.cssText = `
            width: 100%;
            padding: 10px;
            margin-bottom: 5px;
            background: #444;
            color: white;
            border: 1px solid #555;
            border-radius: 5px;
            cursor: pointer;
            font-size: 13px;
            transition: all 0.2s;
        `;
        btn.onmouseover = () => {
            btn.style.background = '#555';
            btn.style.transform = 'translateY(-1px)';
        };
        btn.onmouseout = () => {
            btn.style.background = btn.id === 'la2-auto-discovery-btn' && autoDiscoveryActive ? '#28a745' : '#444';
            btn.style.transform = 'translateY(0)';
        };
        btn.onclick = onClick;
        return btn;
    }

    function makeDraggable(element, handle) {
        let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
        
        handle.onmousedown = dragMouseDown;

        function dragMouseDown(e) {
            e.preventDefault();
            pos3 = e.clientX;
            pos4 = e.clientY;
            document.onmouseup = closeDragElement;
            document.onmousemove = elementDrag;
        }

        function elementDrag(e) {
            e.preventDefault();
            pos1 = pos3 - e.clientX;
            pos2 = pos4 - e.clientY;
            pos3 = e.clientX;
            pos4 = e.clientY;
            element.style.top = (element.offsetTop - pos2) + 'px';
            element.style.left = (element.offsetLeft - pos1) + 'px';
        }

        function closeDragElement() {
            document.onmouseup = null;
            document.onmousemove = null;
        }
    }

    const style = document.createElement('style');
    style.textContent = `
        @keyframes slideIn {
            from {
                transform: translateX(400px);
                opacity: 0;
            }
            to {
                transform: translateX(0);
                opacity: 1;
            }
        }
        @keyframes slideOut {
            from {
                transform: translateX(0);
                opacity: 1;
            }
            to {
                transform: translateX(400px);
                opacity: 0;
            }
        }
    `;
    document.head.appendChild(style);

    function waitForPage() {
        if (document.body && document.querySelector('#workspace')) {
            setTimeout(() => {
                createGUI();
                showNotification('✨ Little Alchemy 2 Mod v2.4 loaded!');
                
                setTimeout(() => {
                    const store = getVueStore();
                    if (store) {
                        console.log('✅ Vue store detected!');
                        console.log('Current progress:', store.getters.totalProgress, '/ 720');
                    }
                }, 1000);
            }, 2000);
        } else {
            setTimeout(waitForPage, 200);
        }
    }

    waitForPage();
})();