Scaley Way Idle

Change the scale of CombatUnit divs with a draggable control panel

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Scaley Way Idle
// @namespace    http://tampermonkey.net/
// @version      1.23
// @description  Change the scale of CombatUnit divs with a draggable control panel
// @author       Frotty
// @match        *://milkywayidle.com/*
// @match        *://*.milkywayidle.com/*
// @grant        GM_addStyle
// ==/UserScript==

(function() {
    'use strict';

    const STORAGE_KEY = 'swi_storage';

    // Load settings from localStorage
    function loadSettings() {
        const defaults = {
            panelLeft: null,
            panelTop: 20,
            panelMinimized: false,
            playerScale: 100,
            monsterScale: 100,
            rightPanelHeight: 50,
            playerGridPos: null,
            monsterGridPos: null
        };
        try {
            const stored = localStorage.getItem(STORAGE_KEY);
            if (stored) {
                return { ...defaults, ...JSON.parse(stored) };
            }
        } catch (e) {
            console.error('Scaley: Error loading settings', e);
        }
        return defaults;
    }

    // Save settings to localStorage
    function saveSettings(settings) {
        try {
            localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
        } catch (e) {
            console.error('Scaley: Error saving settings', e);
        }
    }

    let settings = loadSettings();

    GM_addStyle(`
        #scaley-panel {
            position: fixed;
            top: 20px;
            right: 20px;
            background: #2a2a2a;
            border: 2px solid #555;
            border-radius: 8px;
            padding: 15px;
            z-index: 1;
            font-family: Arial, sans-serif;
            color: #fff;
            box-shadow: 0 4px 12px rgba(0,0,0,0.4);
            user-select: none;
        }
        #scaley-panel.minimized {
            padding: 8px 15px;
        }
        #scaley-panel.minimized .scaley-content {
            display: none;
        }
        #scaley-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            cursor: move;
            margin-bottom: 10px;
        }
        #scaley-panel.minimized #scaley-header {
            margin-bottom: 0;
        }
        #scaley-panel h3 {
            margin: 0;
            font-size: 14px;
            color: #4CAF50;
        }
        #scaley-minimize {
            background: none;
            border: none;
            color: #fff;
            font-size: 16px;
            cursor: pointer;
            padding: 0 4px;
            line-height: 1;
        }
        #scaley-minimize:hover {
            color: #4CAF50;
        }
        #scaley-panel label {
            font-size: 12px;
            display: block;
            margin-bottom: 5px;
        }
        #scaley-panel .scaley-row {
            margin-bottom: 10px;
        }
        #scaley-panel .scaley-row:last-child {
            margin-bottom: 0;
        }
        #scaley-player-input, #scaley-monster-input, #scaley-rightpanel-input {
            width: 60px;
            padding: 5px;
            border: 1px solid #555;
            border-radius: 4px;
            background: #1a1a1a;
            color: #fff;
            font-size: 14px;
            cursor: text;
        }
        #scaley-player-input:focus, #scaley-monster-input:focus, #scaley-rightpanel-input:focus {
            outline: none;
            border-color: #4CAF50;
        }
        #scaley-apply {
            padding: 5px 12px;
            background: #4CAF50;
            border: none;
            border-radius: 4px;
            color: #fff;
            cursor: pointer;
            font-size: 12px;
        }
        #scaley-apply:hover {
            background: #45a049;
        }

        /* Drag handle for combat unit grid */
        .scaley-drag-handle {
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            height: 20px;
            background: rgba(76, 175, 80, 0.8);
            cursor: grab;
            opacity: 0;
            transition: opacity 0.2s;
            z-index: 1000;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 10px;
            color: #fff;
            font-weight: bold;
            pointer-events: auto;
        }
        .scaley-drag-handle:active {
            cursor: grabbing;
        }
        .BattlePanel_playersArea__vvwlB:hover .scaley-drag-handle,
        .BattlePanel_monstersArea__2dzrY:hover .scaley-drag-handle {
            opacity: 1;
        }

        /* Transparent shim to force 50% width */
        .scaley-shim {
            width: 50% !important;
            min-width: 50% !important;
            opacity: 0 !important;
            pointer-events: none !important;
            height: 1px !important;
            overflow: hidden !important;
            display: inline-block !important;
            vertical-align: top !important;
        }

        /* Force horizontal layout */
        .BattlePanel_battleArea__U9hij {
            display: flex !important;
            flex-direction: row !important;
            flex-wrap: nowrap !important;
            align-items: flex-start !important;
        }

        .BattlePanel_playersArea__vvwlB {
            flex: 0 0 50% !important;
            max-width: 50% !important;
            position: relative !important;
            border-right: none !important;
        }

        .BattlePanel_monstersArea__2dzrY {
            flex: 0 0 50% !important;
            max-width: 50% !important;
            position: relative !important;
        }

        /* Force combat unit grid horizontal layout */
        .BattlePanel_combatUnitGrid__2hTAM {
            display: flex !important;
            flex-direction: row !important;
            flex-wrap: wrap !important;
            align-items: flex-start !important;
        }
    `);

    const panel = document.createElement('div');
    panel.id = 'scaley-panel';
    panel.innerHTML = `
        <div id="scaley-header">
            <h3>Scaley Way Idle</h3>
            <button id="scaley-minimize">−</button>
        </div>
        <div class="scaley-content">
            <div class="scaley-row">
                <label>Player Scale (20-100%):</label>
                <input type="number" id="scaley-player-input" min="20" max="100" value="${settings.playerScale}">
            </div>
            <div class="scaley-row">
                <label>Enemy Scale (20-100%):</label>
                <input type="number" id="scaley-monster-input" min="20" max="100" value="${settings.monsterScale}">
            </div>
            <div class="scaley-row">
                <label>Right Panel (20-100%):</label>
                <input type="number" id="scaley-rightpanel-input" min="20" max="100" value="${settings.rightPanelHeight}">
            </div>
            <div class="scaley-row">
                <button id="scaley-apply">Apply</button>
            </div>
        </div>
    `;
    document.body.appendChild(panel);

    // Restore panel position
    if (settings.panelLeft !== null) {
        panel.style.left = settings.panelLeft + 'px';
        panel.style.top = settings.panelTop + 'px';
        panel.style.right = 'auto';
    }

    // Restore minimized state
    if (settings.panelMinimized) {
        panel.classList.add('minimized');
    }

    // Minimize functionality
    const minimizeBtn = document.getElementById('scaley-minimize');
    minimizeBtn.textContent = settings.panelMinimized ? '+' : '−';
    minimizeBtn.addEventListener('click', () => {
        panel.classList.toggle('minimized');
        const isMinimized = panel.classList.contains('minimized');
        minimizeBtn.textContent = isMinimized ? '+' : '−';
        settings.panelMinimized = isMinimized;
        saveSettings(settings);
    });

    // Draggable functionality for control panel
    let isPanelDragging = false;
    let panelOffsetX, panelOffsetY;
    const header = document.getElementById('scaley-header');

    header.addEventListener('mousedown', (e) => {
        if (e.target.tagName === 'BUTTON') return;
        isPanelDragging = true;
        panelOffsetX = e.clientX - panel.getBoundingClientRect().left;
        panelOffsetY = e.clientY - panel.getBoundingClientRect().top;
    });

    document.addEventListener('mousemove', (e) => {
        if (!isPanelDragging) return;
        const left = e.clientX - panelOffsetX;
        const top = e.clientY - panelOffsetY;
        panel.style.left = left + 'px';
        panel.style.top = top + 'px';
        panel.style.right = 'auto';
    });

    document.addEventListener('mouseup', () => {
        if (isPanelDragging) {
            settings.panelLeft = parseInt(panel.style.left);
            settings.panelTop = parseInt(panel.style.top);
            saveSettings(settings);
        }
        isPanelDragging = false;
    });

    // Area dragging (drag the parent area, not the grid)
    let isAreaDragging = false;
    let areaOffsetX, areaOffsetY;
    let currentArea = null;
    let currentAreaType = null;

    document.addEventListener('mousemove', (e) => {
        if (!isAreaDragging || !currentArea) return;
        const x = e.clientX - areaOffsetX;
        const y = e.clientY - areaOffsetY;
        currentArea.style.setProperty('position', 'fixed', 'important');
        currentArea.style.setProperty('left', x + 'px', 'important');
        currentArea.style.setProperty('top', y + 'px', 'important');
        currentArea.style.setProperty('z-index', '1', 'important');
    });

    document.addEventListener('mouseup', () => {
        if (isAreaDragging && currentArea && currentAreaType) {
            const pos = {
                left: parseInt(currentArea.style.left),
                top: parseInt(currentArea.style.top)
            };
            if (currentAreaType === 'player') {
                settings.playerGridPos = pos;
            } else {
                settings.monsterGridPos = pos;
            }
            saveSettings(settings);
        }
        isAreaDragging = false;
        currentArea = null;
        currentAreaType = null;
    });

    // Scale functionality
    const playerInput = document.getElementById('scaley-player-input');
    const monsterInput = document.getElementById('scaley-monster-input');
    const rightPanelInput = document.getElementById('scaley-rightpanel-input');
    const applyBtn = document.getElementById('scaley-apply');

    function setupDragHandle(area, type) {
        if (area.querySelector('.scaley-drag-handle')) return;

        const dragHandle = document.createElement('div');
        dragHandle.className = 'scaley-drag-handle';
        dragHandle.textContent = '⋮⋮ DRAG ⋮⋮';
        // Append at end instead of inserting at beginning to avoid interfering with other scripts
        area.appendChild(dragHandle);

        dragHandle.addEventListener('mousedown', (e) => {
            e.preventDefault();
            e.stopPropagation();
            isAreaDragging = true;
            currentArea = area;
            currentAreaType = type;
            const rect = area.getBoundingClientRect();
            areaOffsetX = e.clientX - rect.left;
            areaOffsetY = e.clientY - rect.top;
        });

        // Restore position if saved
        const savedPos = type === 'player' ? settings.playerGridPos : settings.monsterGridPos;
        if (savedPos) {
            area.style.setProperty('position', 'fixed', 'important');
            area.style.setProperty('left', savedPos.left + 'px', 'important');
            area.style.setProperty('top', savedPos.top + 'px', 'important');
            area.style.setProperty('z-index', '1', 'important');
        }
    }

    // Apply scale from saved settings (does not touch input fields)
    function applyScaleFromSettings() {
        const playerScale = settings.playerScale / 100;
        const monsterScale = settings.monsterScale / 100;

        // Scale player grids
        const playerGrids = document.querySelectorAll('.BattlePanel_playersArea__vvwlB .BattlePanel_combatUnitGrid__2hTAM');
        playerGrids.forEach(grid => {
            grid.style.setProperty('transform', `scale(${playerScale})`, 'important');
            grid.style.setProperty('transform-origin', 'top left', 'important');
        });

        // Scale monster grids
        const monsterGrids = document.querySelectorAll('.BattlePanel_monstersArea__2dzrY .BattlePanel_combatUnitGrid__2hTAM');
        monsterGrids.forEach(grid => {
            grid.style.setProperty('transform', `scale(${monsterScale})`, 'important');
            grid.style.setProperty('transform-origin', 'top left', 'important');
        });

        // Apply right panel height
        const rightPanels = document.querySelectorAll('.GamePage_characterManagementPanel__3OYQL');
        rightPanels.forEach(panel => {
            panel.style.setProperty('height', `${settings.rightPanelHeight}vh`, 'important');
            panel.style.setProperty('overflow-y', 'auto', 'important');
        });

        // Setup drag handles on areas
        const playerAreas = document.querySelectorAll('.BattlePanel_playersArea__vvwlB');
        playerAreas.forEach(area => setupDragHandle(area, 'player'));

        const monsterAreas = document.querySelectorAll('.BattlePanel_monstersArea__2dzrY');
        monsterAreas.forEach(area => setupDragHandle(area, 'monster'));

        // Add shim to battle area if needed
        const battleAreas = document.querySelectorAll('.BattlePanel_battleArea__U9hij');
        battleAreas.forEach(battleArea => {
            if (!battleArea.querySelector('.scaley-shim')) {
                const shim = document.createElement('div');
                shim.className = 'scaley-shim';
                battleArea.appendChild(shim);
            }
        });
    }

    // Apply scale from input fields (only called when user clicks Apply or presses Enter)
    function applyScale() {
        let playerValue = parseInt(playerInput.value, 10);
        if (isNaN(playerValue) || playerValue < 20) playerValue = 20;
        if (playerValue > 100) playerValue = 100;
        playerInput.value = playerValue;

        let monsterValue = parseInt(monsterInput.value, 10);
        if (isNaN(monsterValue) || monsterValue < 20) monsterValue = 20;
        if (monsterValue > 100) monsterValue = 100;
        monsterInput.value = monsterValue;

        let rightPanelValue = parseInt(rightPanelInput.value, 10);
        if (isNaN(rightPanelValue) || rightPanelValue < 20) rightPanelValue = 20;
        if (rightPanelValue > 100) rightPanelValue = 100;
        rightPanelInput.value = rightPanelValue;

        settings.playerScale = playerValue;
        settings.monsterScale = monsterValue;
        settings.rightPanelHeight = rightPanelValue;
        saveSettings(settings);

        applyScaleFromSettings();
    }

    applyBtn.addEventListener('click', applyScale);
    playerInput.addEventListener('keypress', (e) => {
        if (e.key === 'Enter') applyScale();
    });
    monsterInput.addEventListener('keypress', (e) => {
        if (e.key === 'Enter') applyScale();
    });
    rightPanelInput.addEventListener('keypress', (e) => {
        if (e.key === 'Enter') applyScale();
    });

    // Setup drag handles on page load and watch for new areas
    function setupAllDragHandles() {
        const playerAreas = document.querySelectorAll('.BattlePanel_playersArea__vvwlB');
        playerAreas.forEach(area => setupDragHandle(area, 'player'));

        const monsterAreas = document.querySelectorAll('.BattlePanel_monstersArea__2dzrY');
        monsterAreas.forEach(area => setupDragHandle(area, 'monster'));
    }

    // Initial setup
    setupAllDragHandles();

    // Auto-apply saved settings on load
    applyScaleFromSettings();

    // Watch for dynamically added areas
    const observer = new MutationObserver(() => {
        setupAllDragHandles();
        // Re-apply settings to newly added elements (uses saved settings, doesn't touch inputs)
        applyScaleFromSettings();
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });
})();