Permet d'afficher temporairement des vêtements dans l'inventaire personnage.
// ==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();
})();