VisioFWi

Permet d'afficher temporairement des vêtements dans l'inventaire personnage.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

Advertisement:

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

Advertisement:

// ==UserScript==
// @name         VisioFWi
// @namespace    Dreadcast
// @version      2.0
// @author       L'Auryn
// @description  Permet d'afficher temporairement des vêtements dans l'inventaire personnage.
// @match        https://www.dreadcast.net/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=dreadcast.net
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue

// ==/UserScript==

(function () {
    'use strict';

    const STORAGE_KEY = 'VisioFWi_images_v2_0';
    const LAST_URL_KEY = 'VisioFWi_last_url_v2_0';

    let lastCopiedUrl = GM_getValue(LAST_URL_KEY, '');
    let pickMode = false;

    const SLOTS = [
        { name: 'Tête', selector: '.zone_case1' },
        { name: 'Buste', selector: '.zone_case5' },
        { name: 'Pantalon', selector: '.zone_case-1' },
        { name: 'Pieds', selector: '.zone_case6' },
        { name: 'Secondaire', selector: '.zone_case2' },
        { name: 'SecondaireRP', selector: '.zone_case-2' },
        { name: 'Main1', selector: '.zone_case3' },
        { name: 'Main2', selector: '.zone_case4' }
    ];

    function getSaved() {
        return GM_getValue(STORAGE_KEY, {});
    }

    function saveAll(data) {
        GM_setValue(STORAGE_KEY, data);
    }

    function notify(message) {
        if (window.engine?.displayLightInfo) {
            engine.displayLightInfo(message, 4);
        } else {
            console.log('[VisioFWi]', message);
        }
    }

    function normalizeUrl(url) {
        if (!url) return '';
        url = String(url).trim();
        if (url.startsWith('//')) url = location.protocol + url;
        if (url.startsWith('/')) url = location.origin + url;
        return url;
    }

    function getSlotBox(slotSelector) {
        return document.querySelector(`#equipement_inventaire ${slotSelector} .case_objet`);
    }

    function getSlotImage(slotSelector) {
        return getSlotBox(slotSelector)?.querySelector('img.item');
    }

    function createInjectedImage(box, url) {
        const img = document.createElement('img');

        img.className = 'item visiofwi_injected_img';
        img.src = url;
        img.alt = 'VisioFWi';
        img.dataset.visiofwiInjected = 'true';
        img.dataset.visiofwiCustomSrc = url;

        box.appendChild(img);

        return img;
    }

    function applyImage(slotSelector, url) {
        const box = getSlotBox(slotSelector);

        if (!box) {
            notify('VisioFWi : case introuvable.');
            return false;
        }

        url = normalizeUrl(url);

        if (!url) {
            notify('VisioFWi : aucun lien image valide.');
            return false;
        }

        let img = getSlotImage(slotSelector);

        if (!img) {
            createInjectedImage(box, url);
            return true;
        }

        if (!img.dataset.visiofwiInjected && !img.dataset.visiofwiOriginalSrc) {
            img.dataset.visiofwiOriginalSrc = img.src;
        }

        img.src = url;
        img.dataset.visiofwiCustomSrc = url;

        return true;
    }

    function applySavedImages() {
        const saved = getSaved();

        for (const slot of SLOTS) {
            if (saved[slot.selector]) {
                applyImage(slot.selector, saved[slot.selector]);
            }
        }
    }

    function setSlotImage(slotSelector) {
        const input = document.querySelector('#visiofwi_url');
        const url = normalizeUrl(input?.value || lastCopiedUrl || GM_getValue(LAST_URL_KEY, ''));

        if (!url) {
            notify('VisioFWi : aucun lien image copié.');
            return;
        }

        if (!applyImage(slotSelector, url)) return;

        const saved = getSaved();
        saved[slotSelector] = url;
        saveAll(saved);

        notify('VisioFWi : image appliquée.');
    }

    function resetAll() {
        GM_deleteValue(STORAGE_KEY);

        document.querySelectorAll('#equipement_inventaire img.item[data-visiofwi-original-src]').forEach(img => {
            img.src = img.dataset.visiofwiOriginalSrc;
            delete img.dataset.visiofwiOriginalSrc;
            delete img.dataset.visiofwiCustomSrc;
        });

        document.querySelectorAll('#equipement_inventaire img.visiofwi_injected_img').forEach(img => {
            img.remove();
        });

        notify('VisioFWi : inventaire réinitialisé.');
    }

    function markSelectedImage(img) {
        document.body.classList.remove('visiofwi_pick_mode');

        if (!img) return;

        img.classList.add('visiofwi_selected_image');

        setTimeout(() => {
            img.classList.remove('visiofwi_selected_image');
        }, 2000);
    }

    function setCopiedUrl(url, img = null) {
        url = normalizeUrl(url);

        if (!url) {
            notify('VisioFWi : lien image invalide.');
            return;
        }

        lastCopiedUrl = url;
        GM_setValue(LAST_URL_KEY, url);

        const input = document.querySelector('#visiofwi_url');
        if (input) input.value = url;

        markSelectedImage(img);

        if (navigator.clipboard?.writeText) {
            navigator.clipboard.writeText(url)
                .then(() => notify('VisioFWi : lien image copié.'))
                .catch(() => prompt('Copie ce lien image :', url));
        } else {
            prompt('Copie ce lien image :', url);
        }
    }

    function enablePickMode() {
        pickMode = true;
        document.body.classList.add('visiofwi_pick_mode');
        notify('VisioFWi : clique sur une image à copier.');
    }

    function findImageFromEvent(event) {
        const path = event.composedPath ? event.composedPath() : [];

        for (const el of path) {
            if (el?.tagName?.toLowerCase() === 'img' && el.src) {
                return el;
            }
        }

        const direct = event.target?.closest?.('img');
        if (direct?.src) return direct;

        const underMouse = document.elementFromPoint(event.clientX, event.clientY);
        const underImg = underMouse?.closest?.('img');
        if (underImg?.src) return underImg;

        const forumImages = [...document.querySelectorAll('.zone_display_text img[src]')];

        return forumImages.find(img => {
            const rect = img.getBoundingClientRect();

            return (
                event.clientX >= rect.left &&
                event.clientX <= rect.right &&
                event.clientY >= rect.top &&
                event.clientY <= rect.bottom
            );
        });
    }

    function pickImageFromEvent(event) {
        if (!pickMode) return;

        if (event.target.closest?.('#visiofwi_panel')) return;

        const img = findImageFromEvent(event);

        event.preventDefault();
        event.stopImmediatePropagation();

        if (!img?.src) {
            pickMode = false;
            document.body.classList.remove('visiofwi_pick_mode');
            notify('VisioFWi : aucune image détectée à cet endroit.');
            return;
        }

        setCopiedUrl(img.src, img);
        pickMode = false;
    }

    function togglePanel() {
        createPanel();

        const panel = document.querySelector('#visiofwi_panel');
        const input = document.querySelector('#visiofwi_url');

        const visible = panel.style.display !== 'none' && panel.style.display !== '';

        panel.style.display = visible ? 'none' : 'block';

        if (!visible && input) {
            input.value = lastCopiedUrl || GM_getValue(LAST_URL_KEY, '') || '';
        }
    }

    function injectCss() {
        if (document.querySelector('#visiofwi_css')) return;

        const style = document.createElement('style');
        style.id = 'visiofwi_css';

        style.textContent = `
            #zone_inventaire {
                position: relative;
            }

            #visiofwi_open {
                position: absolute !important;
                z-index: 10000 !important;
                display: flex !important;
                align-items: center;
                justify-content: center;
                font-weight: bold;
                font-size: 12px;
                border: 1px solid #fff !important;
                color: #fff !important;
                transform: translateY(-30px) translateX(7px);
            }

            #visiofwi_panel {
                display: none;
                position: absolute;
                z-index: 10001;
                right: -245px;
                bottom: 35px;
                width: 230px;
            }

            #visiofwi_panel .content {
                padding: 8px;
            }

            #visiofwi_panel .visiofwi_title {
                text-align: center;
                font-weight: bold;
                margin-bottom: 6px;
                cursor: move;
                user-select: none;
            }

            #visiofwi_url {
                width: 100%;
                box-sizing: border-box;
                margin-bottom: 6px;
                background: #111;
                color: #ddd;
                border: 1px solid #444;
                padding: 4px;
                font-size: 11px;
            }

            .visiofwi_hint {
                font-size: 10px;
                color: #888;
                margin-bottom: 6px;
                line-height: 1.25;
            }

            .visiofwi_row {
                display: flex;
                justify-content: space-between;
                align-items: center;
                margin: 4px 0;
            }

            .visiofwi_row button,
            #visiofwi_pick_image,
            #visiofwi_reset_inside {
                cursor: pointer;
                background: #222;
                color: #ddd;
                border: 1px solid #555;
                padding: 2px 6px;
                font-size: 11px;
            }

            .visiofwi_row button:hover,
            #visiofwi_pick_image:hover,
            #visiofwi_reset_inside:hover {
                background: #333;
            }

            #visiofwi_pick_image,
            #visiofwi_reset_inside {
                width: 100%;
                margin-bottom: 6px;
            }

            #visiofwi_reset_inside {
                margin-top: 8px;
                color: #fff;
                border-color: #777;
            }

            body.visiofwi_pick_mode img {
                cursor: crosshair !important;
                outline: 1px dashed rgba(255,255,255,0.5);
            }

            img.visiofwi_selected_image {
                outline: 3px solid #7df9ff !important;
                box-shadow: 0 0 12px #7df9ff, 0 0 22px #7df9ff !important;
                filter: brightness(1.35) contrast(1.15) !important;
                transition: outline 0.2s, box-shadow 0.2s, filter 0.2s;
            }

            #equipement_inventaire img.visiofwi_injected_img {
                display: block;
                max-width: 100%;
                max-height: 100%;
                margin: 0 auto;
                pointer-events: none;
            }
        `;

        document.head.appendChild(style);
    }

    function createPanel() {
        if (document.querySelector('#visiofwi_panel')) return;

        const zoneInventaire = document.querySelector('#zone_inventaire');
        if (!zoneInventaire) return;

        const panel = document.createElement('div');
        panel.id = 'visiofwi_panel';
        panel.className = 'fakeToolTip';

        panel.innerHTML = `
            <div class="deco4"></div>
            <div class="content">
                <div class="visiofwi_title couleur0">✨ VisioFWi</div>
                <input id="visiofwi_url" type="text" placeholder="Lien image copié">
                <button id="visiofwi_pick_image">Mode clic image</button>
                <div class="visiofwi_hint">
                    Forum : Alt+I puis clique sur l’image.<br>
                    Ou utilise le bouton ci-dessus.
                </div>
                <div id="visiofwi_slots"></div>
                <button id="visiofwi_reset_inside">Réinitialiser</button>
            </div>
        `;

        zoneInventaire.appendChild(panel);

        const input = panel.querySelector('#visiofwi_url');
        input.value = lastCopiedUrl || GM_getValue(LAST_URL_KEY, '') || '';

        panel.querySelector('#visiofwi_pick_image').addEventListener('click', event => {
            event.preventDefault();
            event.stopPropagation();
            enablePickMode();
        });

        panel.querySelector('#visiofwi_reset_inside').addEventListener('click', event => {
            event.preventDefault();
            event.stopPropagation();
            resetAll();
        });

        const slotsContainer = panel.querySelector('#visiofwi_slots');

        for (const slot of SLOTS) {
            const row = document.createElement('div');
            row.className = 'visiofwi_row';

            const label = document.createElement('span');
            label.textContent = slot.name;

            const button = document.createElement('button');
            button.textContent = 'Utiliser';

            button.addEventListener('click', event => {
                event.preventDefault();
                event.stopPropagation();
                setSlotImage(slot.selector);
            });

            row.append(label, button);
            slotsContainer.appendChild(row);
        }

        makePanelDraggable(panel);
    }

    function makePanelDraggable(panel) {
        const title = panel.querySelector('.visiofwi_title');
        if (!title) return;

        let dragging = false;
        let startX = 0;
        let startY = 0;
        let startLeft = 0;
        let startTop = 0;

        title.addEventListener('mousedown', event => {
            dragging = true;

            const rect = panel.getBoundingClientRect();

            panel.style.left = rect.left + 'px';
            panel.style.top = rect.top + 'px';
            panel.style.right = 'auto';
            panel.style.bottom = 'auto';
            panel.style.position = 'fixed';

            startX = event.clientX;
            startY = event.clientY;
            startLeft = rect.left;
            startTop = rect.top;

            event.preventDefault();
            event.stopPropagation();
        });

        document.addEventListener('mousemove', event => {
            if (!dragging) return;

            panel.style.left = startLeft + (event.clientX - startX) + 'px';
            panel.style.top = startTop + (event.clientY - startY) + 'px';
        });

        document.addEventListener('mouseup', () => {
            dragging = false;
        });
    }

    function createButtons() {
        const scissors = document.querySelector('#ciseauxInventaire');
        if (!scissors) return;

        if (!document.querySelector('#visiofwi_open')) {
            const open = scissors.cloneNode(true);
            open.id = 'visiofwi_open';
            open.innerHTML = '✨';
            open.title = 'VisioFWi';
            open.removeAttribute('onclick');

            open.addEventListener('click', event => {
                event.preventDefault();
                event.stopPropagation();
                togglePanel();
            });

            scissors.parentElement.appendChild(open);
        }
    }

    document.addEventListener('pointerdown', pickImageFromEvent, true);
    document.addEventListener('mousedown', pickImageFromEvent, true);
    document.addEventListener('click', pickImageFromEvent, true);

    window.addEventListener('keydown', event => {
        const tag = document.activeElement?.tagName?.toLowerCase();
        const typing = tag === 'input' || tag === 'textarea';

        if (typing) return;

        if (event.altKey && event.key.toLowerCase() === 'i') {
            event.preventDefault();
            event.stopPropagation();
            enablePickMode();
        }

        if (event.key === 'Escape' && pickMode) {
            pickMode = false;
            document.body.classList.remove('visiofwi_pick_mode');
            notify('VisioFWi : sélection annulée.');
        }
    }, true);

    const observer = new MutationObserver(() => {
        injectCss();
        createPanel();
        createButtons();
        applySavedImages();
    });

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

    injectCss();
    createPanel();
    createButtons();
    applySavedImages();

})();