Ajoute un slider pour contrôler la taille des pions + affiche les avatars personnalisés des joueurs + détection automatique du combat + couleurs par action + emojis d'action + contrôle taille emojis + personnalisation des couleurs + slider d'opacité
// ==UserScript==
// @name Dreadcast - PimpMyPion [[VERSION TEST]]
// @namespace http://tampermonkey.net/
// @version 0.5.1
// @description Ajoute un slider pour contrôler la taille des pions + affiche les avatars personnalisés des joueurs + détection automatique du combat + couleurs par action + emojis d'action + contrôle taille emojis + personnalisation des couleurs + slider d'opacité
// @author Darlene
// @match https://www.dreadcast.net/*
// @match http://www.dreadcast.net/*
// @grant none
// @run-at document-end
// ==/UserScript==
(function () {
'use strict';
// ============================================================
// CONFIGURATION
// ============================================================
const CONFIG = Object.freeze({
// Clefs stockage
STORAGE_KEY_SIZE: 'dreadcast_avatar_size',
STORAGE_KEY_ENABLED: 'dreadcast_avatar_enabled',
STORAGE_KEY_EMOJI_ENABLED: 'dreadcast_emoji_enabled',
STORAGE_KEY_EMOJI_SIZE: 'dreadcast_emoji_size',
STORAGE_KEY_CUSTOM_COLORS: 'dreadcast_custom_colors',
STORAGE_KEY_COLOR_OPACITY: 'dreadcast_color_opacity',
// Paramètres de taille(en %)
DEFAULT_SIZE: 100,
MIN_SIZE: 75,
MAX_SIZE: 125,
// Paramètres de taille emoji (en px)
DEFAULT_EMOJI_SIZE: 12,
MIN_EMOJI_SIZE: 12,
MAX_EMOJI_SIZE: 28,
// Paramètres d'opacité des couleurs (en %)
DEFAULT_COLOR_OPACITY: 100,
MIN_COLOR_OPACITY: 0,
MAX_COLOR_OPACITY: 100,
// URLs & chemins
AVATAR_BASE_URL: 'https://www.dreadcast.net/images/avatars/',
// Timing (millisecondes)
REAPPLY_INTERVAL: 50,
RAF_THROTTLE: 0,
INIT_DELAY: 2000,
SECONDARY_DELAY: 5000,
MENU_CHECK_INTERVAL: 500,
MENU_CHECK_TIMEOUT: 10000,
EVENT_ATTACH_DELAY: 100,
COMBAT_CHECK_INTERVAL: 200,
// Z-indices
Z_INDEX_AVATAR: 999,
Z_INDEX_EMOJI: 1001,
Z_INDEX_OVERLAY: 999999,
Z_INDEX_PANEL: 1000000,
// Selecteurs CSS
SELECTOR_PIONS: '.personnages .icon_perso',
SELECTOR_ICON: '.le_icon_perso',
SELECTOR_INFO: '.info_a_afficher',
SELECTOR_SETTINGS_MENU: '.parametres ul',
SELECTOR_COMBAT: '#combat_carte',
SELECTOR_PLAYER_ACTION: '#icon_action',
// Classes CSS
CLASS_AVATAR_IMG: 'custom-avatar-img',
CLASS_CONNECTED: 'connecte',
CLASS_ACTION_EMOJI: 'action-emoji',
// Data attributes
ATTR_AVATAR_STATUS: 'data-avatar-applied',
ATTR_PLAYER_NAME: 'data-player-name',
ATTR_CURRENT_ACTION: 'data-current-action',
// Status avatars
STATUS_SUCCESS: 'success',
STATUS_FAILED: 'failed',
// Couleurs par action
ACTION_COLORS: {
'en_combat': '#ef4444', // Rouge vif - Combat
'encombat': '#ef4444', // Rouge vif - Combat (variante)
'aucune': '#9ca3af', // Gris - Aucune action
'noaction': '#9ca3af', // Gris - Aucune action (variante)
'repos': '#06b6d4', // Cyan - Repos
'recherche': '#f59e0b', // Jaune/Or - Fouille
'cacher': '#8b5cf6', // Violet - Cacher
'scruter': '#f97316', // Orange - Scruter
'soin': '#10b981', // Vert - Soigner
'travail': '#92400e', // Marron - Travailler
'ko': '#1f2937', // Gris foncé - Mort
'destruction': '#ef4444', // Rouge vif
'reparation': '#ef4444' // Rouge vif
},
// Emojis par action
ACTION_EMOJIS: {
'en_combat': '⚔️',
'encombat': '⚔️',
'aucune': '⏸️',
'noaction': '⏸️',
'repos': '😴',
'recherche': '⛏️',
'cacher': '🫣',
'scruter': '👀',
'soin': '💊',
'travail': '⚙️',
'ko': '💀',
'destruction': '💥',
'reparation': '🔧'
},
// Couleurs de base (fallback)
COLOR_CONNECTED: '#4ade80',
COLOR_DISCONNECTED: '#ffffff',
// Divers
DEBUG_MODE: true
});
// ============================================================
// Gestion des état
// ============================================================
class AvatarState {
constructor() {
this.avatarCache = new Map();
this.avatarUrlCache = new Map();
this.actionCache = new Map();
this.reapplyIntervalId = null;
this.reapplyAnimationFrameId = null;
this.lastReapplyTime = 0;
}
clearCaches() {
this.avatarCache.clear();
this.avatarUrlCache.clear();
this.actionCache.clear();
}
stopReapplication() {
if (this.reapplyIntervalId) {
clearInterval(this.reapplyIntervalId);
this.reapplyIntervalId = null;
}
if (this.reapplyAnimationFrameId) {
cancelAnimationFrame(this.reapplyAnimationFrameId);
this.reapplyAnimationFrameId = null;
}
}
}
const state = new AvatarState();
// ============================================================
// Détection des actions
// ============================================================
const ActionDetector = {
/**
* Détecte l'action d'un joueur à partir de son pion
* @param {HTMLElement} pionElement - L'élément .icon_perso
* @returns {string|null} - Le nom de l'action détectée ou null
*/
getPlayerAction(pionElement) {
try {
// Vérifier le cache
if (state.actionCache.has(pionElement)) {
const cached = state.actionCache.get(pionElement);
// Vérifier si le cache est encore valide (< 500ms)
if (Date.now() - cached.timestamp < 500) {
return cached.action;
}
}
// Chercher l'action dans .le_icon_perso
const iconElement = pionElement.querySelector(CONFIG.SELECTOR_ICON);
if (!iconElement) return null;
// Les classes possibles pour les actions
const actionClasses = [
'en_combat', 'encombat',
'recherche', 'fouille',
'repos',
'cacher',
'scruter',
'soin',
'travail',
'destruction',
'reparation',
'aucune', 'noaction',
'ko'
];
// Chercher quelle action est active
for (const actionClass of actionClasses) {
if (iconElement.classList.contains(actionClass)) {
// Mettre en cache
state.actionCache.set(pionElement, {
action: actionClass,
timestamp: Date.now()
});
return actionClass;
}
}
// Si aucune action trouvée, retourner null
return null;
} catch (error) {
Utils.debugLog('❌ Erreur détection action:', error);
return null;
}
},
/**
* Récupère la couleur correspondant à une action
* @param {string} action - Le nom de l'action
* @returns {string} - La couleur hexadécimale
*/
getActionColor(action) {
if (!action) return Storage.getColorForStatus(false);
return Storage.getColorForAction(action.toLowerCase());
},
/**
* Détecte l'action du joueur principal via #icon_action
* @returns {string|null} - Le nom de l'action détectée ou null
*/
getMainPlayerAction() {
try {
const iconAction = document.querySelector(CONFIG.SELECTOR_PLAYER_ACTION);
if (!iconAction) return null;
// Les classes possibles sur #icon_action
const actionClasses = [
'actionEncombat',
'actionAucune',
'actionRepos',
'actionFouille',
'actionCacher',
'actionScruter',
'actionSoin',
'actionTravailler',
'actionDetruire',
'actionReparer',
'actionKo',
];
for (const actionClass of actionClasses) {
if (iconAction.classList.contains(actionClass)) {
// Convertir le nom de classe en nom d'action
const actionName = actionClass.replace('action', '').toLowerCase();
return actionName;
}
}
return null;
} catch (error) {
Utils.debugLog('❌ Erreur détection action principale:', error);
return null;
}
},
/**
* Vide le cache des actions
*/
clearCache() {
state.actionCache.clear();
Utils.debugLog('✅ Cache des actions vidé');
},
/**
* Récupère l'emoji correspondant à une action
* @param {string} action - Le nom de l'action
* @returns {string|null} - L'emoji ou null
*/
getActionEmoji(action) {
if (!action) return null;
const emoji = CONFIG.ACTION_EMOJIS[action.toLowerCase()];
return emoji || null;
},
/**
* Crée ou met à jour l'élément emoji sur un avatar
* @param {HTMLElement} iconElement - L'élément .le_icon_perso
* @param {string} action - Le nom de l'action
*/
updateActionEmoji(iconElement, action) {
try {
if (!Storage.loadEmojiEnabled()) {
// Supprimer l'emoji s'il existe
const existingEmoji = iconElement.querySelector(`.${CONFIG.CLASS_ACTION_EMOJI}`);
if (existingEmoji) {
existingEmoji.remove();
Utils.debugLog('🗑️ Emoji supprimé (fonction désactivée)');
}
return;
}
const emoji = this.getActionEmoji(action);
if (!emoji) {
// Si pas d'emoji pour cette action, supprimer l'emoji existant
const existingEmoji = iconElement.querySelector(`.${CONFIG.CLASS_ACTION_EMOJI}`);
if (existingEmoji) {
existingEmoji.remove();
Utils.debugLog('🗑️ Emoji supprimé (aucun emoji pour cette action)');
}
return;
}
let emojiElement = iconElement.querySelector(`.${CONFIG.CLASS_ACTION_EMOJI}`);
if (!emojiElement) {
// Créer un nouveau span pour l'emoji
emojiElement = document.createElement('span');
emojiElement.className = CONFIG.CLASS_ACTION_EMOJI;
iconElement.appendChild(emojiElement);
Utils.debugLog('✨ Nouvel emoji créé:', emoji);
}
// Mettre à jour le contenu de l'emoji si nécessaire
if (emojiElement.textContent !== emoji) {
emojiElement.textContent = emoji;
Utils.debugLog('🔄 Emoji mis à jour:', emoji);
}
} catch (error) {
Utils.debugLog('❌ Erreur mise à jour emoji:', error);
}
},
/**
* Supprime l'emoji d'action d'un avatar
* @param {HTMLElement} iconElement - L'élément .le_icon_perso
*/
removeActionEmoji(iconElement) {
try {
const emojiElement = iconElement.querySelector(`.${CONFIG.CLASS_ACTION_EMOJI}`);
if (emojiElement) {
emojiElement.remove();
Utils.debugLog('🗑️ Emoji supprimé');
}
} catch (error) {
Utils.debugLog('❌ Erreur suppression emoji:', error);
}
}
};
// ============================================================
// COMBAT DETECTOR
// ============================================================
const CombatDetector = {
isInCombat: false,
savedSize: null,
checkInterval: null,
detectCombat() {
return document.querySelector(CONFIG.SELECTOR_COMBAT) !== null;
},
start() {
Utils.debugLog('⚔️ Démarrage du détecteur de combat');
this.checkInterval = setInterval(() => {
const inCombat = this.detectCombat();
if (inCombat && !this.isInCombat) {
this.onEnterCombat();
} else if (!inCombat && this.isInCombat) {
this.onExitCombat();
}
}, CONFIG.COMBAT_CHECK_INTERVAL);
},
onEnterCombat() {
Utils.debugLog('⚔️ 🔴 ENTRÉE EN COMBAT - Forçage taille 100%');
const currentSize = Storage.loadAvatarSize();
this.savedSize = currentSize;
this.isInCombat = true;
SizingSystem.applyAvatarSize(100);
this.disableSlider();
},
onExitCombat() {
Utils.debugLog('⚔️ 🟢 SORTIE DE COMBAT - Restauration taille sauvegardée');
this.isInCombat = false;
if (this.savedSize !== null) {
SizingSystem.applyAvatarSize(this.savedSize);
Utils.debugLog(`✅ Taille restaurée à ${this.savedSize}%`);
}
this.enableSlider();
this.savedSize = null;
},
disableSlider() {
const slider = document.getElementById('avatar-size-slider');
const valueDisplay = document.getElementById('avatar-size-value');
if (slider) {
slider.disabled = true;
slider.style.opacity = '0.5';
slider.style.cursor = 'not-allowed';
}
if (valueDisplay) {
valueDisplay.textContent = '100% ⚔️';
valueDisplay.style.color = '#ef4444';
}
},
enableSlider() {
const slider = document.getElementById('avatar-size-slider');
const valueDisplay = document.getElementById('avatar-size-value');
if (slider) {
slider.disabled = false;
slider.style.opacity = '1';
slider.style.cursor = 'pointer';
const currentSize = Storage.loadAvatarSize();
slider.value = currentSize;
if (valueDisplay) {
valueDisplay.textContent = `${currentSize}%`;
valueDisplay.style.color = '#667eea';
}
}
},
stop() {
if (this.checkInterval) {
clearInterval(this.checkInterval);
this.checkInterval = null;
}
Utils.debugLog('⚔️ Arrêt du détecteur de combat');
}
};
// ============================================================
// Fonctions utilitaires
// ============================================================
const Utils = {
debugLog(message, ...args) {
if (CONFIG.DEBUG_MODE) {
console.log(`[Dreadcast PimpMyPion] ${message}`, ...args);
}
},
encodePlayerName(name) {
return encodeURIComponent(name);
},
buildAvatarUrl(playerName) {
return `${CONFIG.AVATAR_BASE_URL}${this.encodePlayerName(playerName)}.png`;
},
throttle(fn, delay) {
let lastCall = 0;
return function (...args) {
const now = Date.now();
if (now - lastCall < delay) return;
lastCall = now;
fn.apply(this, args);
};
},
/**
* Convertit une couleur hexadécimale en rgba avec opacité
* @param {string} hex - Couleur hex (#rrggbb)
* @param {number} opacity - Opacité de 0 à 1
* @returns {string} - Couleur rgba
*/
hexToRgba(hex, opacity) {
// Supprimer le # si présent
hex = hex.replace('#', '');
// Convertir en RGB
const r = parseInt(hex.substring(0, 2), 16);
const g = parseInt(hex.substring(2, 4), 16);
const b = parseInt(hex.substring(4, 6), 16);
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
}
};
// ============================================================
// Fonctions de stockage
// ============================================================
const Storage = {
loadAvatarSize() {
const savedSize = localStorage.getItem(CONFIG.STORAGE_KEY_SIZE);
return savedSize ? parseInt(savedSize, 10) : CONFIG.DEFAULT_SIZE;
},
saveAvatarSize(size) {
localStorage.setItem(CONFIG.STORAGE_KEY_SIZE, String(size));
Utils.debugLog('✅ Taille sauvegardée:', `${size}%`);
},
loadAvatarEnabled() {
const savedEnabled = localStorage.getItem(CONFIG.STORAGE_KEY_ENABLED);
return savedEnabled === null ? true : savedEnabled === 'true';
},
saveAvatarEnabled(enabled) {
localStorage.setItem(CONFIG.STORAGE_KEY_ENABLED, String(enabled));
Utils.debugLog('✅ Affichage des avatars:', enabled ? 'activé' : 'désactivé');
},
loadEmojiEnabled() {
const savedEnabled = localStorage.getItem(CONFIG.STORAGE_KEY_EMOJI_ENABLED);
return savedEnabled === null ? true : savedEnabled === 'true';
},
saveEmojiEnabled(enabled) {
localStorage.setItem(CONFIG.STORAGE_KEY_EMOJI_ENABLED, String(enabled));
Utils.debugLog('✅ Affichage des emojis:', enabled ? 'activé' : 'désactivé');
},
loadEmojiSize() {
const savedSize = localStorage.getItem(CONFIG.STORAGE_KEY_EMOJI_SIZE);
return savedSize ? parseInt(savedSize, 10) : CONFIG.DEFAULT_EMOJI_SIZE;
},
saveEmojiSize(size) {
localStorage.setItem(CONFIG.STORAGE_KEY_EMOJI_SIZE, String(size));
Utils.debugLog('✅ Taille emoji sauvegardée:', `${size}px`);
},
loadCustomColors() {
const saved = localStorage.getItem(CONFIG.STORAGE_KEY_CUSTOM_COLORS);
return saved ? JSON.parse(saved) : {};
},
saveCustomColors(colors) {
localStorage.setItem(CONFIG.STORAGE_KEY_CUSTOM_COLORS, JSON.stringify(colors));
Utils.debugLog('✅ Couleurs personnalisées sauvegardées');
},
loadColorOpacity() {
const saved = localStorage.getItem(CONFIG.STORAGE_KEY_COLOR_OPACITY);
return saved ? parseInt(saved, 10) : CONFIG.DEFAULT_COLOR_OPACITY;
},
saveColorOpacity(opacity) {
localStorage.setItem(CONFIG.STORAGE_KEY_COLOR_OPACITY, String(opacity));
Utils.debugLog('✅ Opacité des couleurs sauvegardée:', `${opacity}%`);
},
getColorForAction(action) {
const customColors = this.loadCustomColors();
const opacity = this.loadColorOpacity() / 100; // Convertir % en 0-1
let hexColor;
if (customColors[action]) {
hexColor = customColors[action];
} else {
hexColor = CONFIG.ACTION_COLORS[action] || CONFIG.COLOR_CONNECTED;
}
return Utils.hexToRgba(hexColor, opacity);
},
getColorForStatus(isConnected) {
const customColors = this.loadCustomColors();
const opacity = this.loadColorOpacity() / 100; // Convertir % en 0-1
let hexColor;
if (isConnected && customColors['connected']) {
hexColor = customColors['connected'];
} else if (!isConnected && customColors['disconnected']) {
hexColor = customColors['disconnected'];
} else {
hexColor = isConnected ? CONFIG.COLOR_CONNECTED : CONFIG.COLOR_DISCONNECTED;
}
return Utils.hexToRgba(hexColor, opacity);
}
};
// ============================================================
// DOM UTILITIES
// ============================================================
const DOMUtils = {
getPlayerNameFromPion(pionElement) {
try {
const cachedName = pionElement.getAttribute(CONFIG.ATTR_PLAYER_NAME);
if (cachedName) return cachedName;
const infoElement = pionElement.querySelector(CONFIG.SELECTOR_INFO);
if (infoElement?.textContent) {
const playerName = infoElement.textContent.trim();
pionElement.setAttribute(CONFIG.ATTR_PLAYER_NAME, playerName);
return playerName;
}
} catch (error) {
Utils.debugLog('❌ Erreur extraction nom joueur:', error);
}
return null;
},
isAvatarValid(pionElement) {
const iconElement = pionElement.querySelector(CONFIG.SELECTOR_ICON);
if (!iconElement) return false;
const avatarImg = iconElement.querySelector(`.${CONFIG.CLASS_AVATAR_IMG}`);
if (!avatarImg) return false;
const isAttached = avatarImg.parentElement !== null;
const isVisible = avatarImg.style.display !== 'none' &&
avatarImg.style.visibility !== 'hidden' &&
avatarImg.style.opacity !== '0';
return isAttached && isVisible;
},
getAllPions() {
return document.querySelectorAll(CONFIG.SELECTOR_PIONS);
}
};
// ============================================================
// Chargement des images
// ============================================================
const ImageLoader = {
async checkImageExists(url, playerName) {
if (state.avatarUrlCache.has(playerName)) {
return state.avatarUrlCache.get(playerName).exists;
}
return new Promise((resolve) => {
const img = new Image();
img.onload = () => {
state.avatarUrlCache.set(playerName, { url, exists: true });
resolve(true);
};
img.onerror = () => {
state.avatarUrlCache.set(playerName, { url, exists: false });
resolve(false);
};
img.src = url;
});
}
};
// ============================================================
// Style des avatars
// ============================================================
const AvatarStyler = {
createAvatarImage(avatarUrl, playerName) {
const img = document.createElement('img');
img.className = CONFIG.CLASS_AVATAR_IMG;
img.src = avatarUrl;
img.alt = playerName;
img.setAttribute('loading', 'eager');
img.setAttribute('decoding', 'sync');
return img;
},
applyAvatarStyles(avatarImg, borderColor) {
const styles = {
width: '20px',
height: '20px',
'object-fit': 'cover',
'border-radius': '50%',
border: `3px solid ${borderColor}`,
'box-shadow': '0 2px 8px rgba(0, 0, 0, 0.3)',
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
'z-index': String(CONFIG.Z_INDEX_AVATAR),
'pointer-events': 'none',
display: 'block',
visibility: 'visible',
opacity: '1',
transition: 'border-color 0.3s ease',
animation: 'none'
};
Object.entries(styles).forEach(([property, value]) => {
avatarImg.style.setProperty(property, value, 'important');
});
},
getBorderColor(pionElement) {
const iconElement = pionElement.querySelector(CONFIG.SELECTOR_ICON);
if (!iconElement) return Storage.getColorForStatus(false);
// Détecter l'action du joueur
const action = ActionDetector.getPlayerAction(pionElement);
if (action) {
// Utiliser la couleur de l'action
const actionColor = ActionDetector.getActionColor(action);
Utils.debugLog(`🎨 Action détectée: ${action} -> ${actionColor}`);
return actionColor;
}
// Fallback: utiliser le système connecté/déconnecté avec couleurs personnalisées
const isConnected = iconElement.classList.contains(CONFIG.CLASS_CONNECTED);
return Storage.getColorForStatus(isConnected);
}
};
// ============================================================
// Application des avatars
// ============================================================
const AvatarManager = {
async applyCustomAvatar(pionElement, force = false) {
try {
if (!Storage.loadAvatarEnabled()) return;
const cachedStatus = state.avatarCache.get(pionElement);
if (!force && cachedStatus === CONFIG.STATUS_SUCCESS && DOMUtils.isAvatarValid(pionElement)) {
// Même si l'avatar est valide, on doit mettre à jour la bordure
const iconElement = pionElement.querySelector(CONFIG.SELECTOR_ICON);
const avatarImg = iconElement?.querySelector(`.${CONFIG.CLASS_AVATAR_IMG}`);
if (avatarImg) {
const borderColor = AvatarStyler.getBorderColor(pionElement);
avatarImg.style.setProperty('border', `3px solid ${borderColor}`, 'important');
}
return;
}
if (!force && cachedStatus === CONFIG.STATUS_FAILED) return;
const playerName = DOMUtils.getPlayerNameFromPion(pionElement);
if (!playerName) {
state.avatarCache.set(pionElement, CONFIG.STATUS_FAILED);
return;
}
const avatarUrl = Utils.buildAvatarUrl(playerName);
if (!state.avatarUrlCache.has(playerName) || force) {
const exists = await ImageLoader.checkImageExists(avatarUrl, playerName);
if (!exists) {
state.avatarCache.set(pionElement, CONFIG.STATUS_FAILED);
pionElement.setAttribute(CONFIG.ATTR_AVATAR_STATUS, CONFIG.STATUS_FAILED);
return;
}
} else if (!state.avatarUrlCache.get(playerName).exists) {
return;
}
const iconElement = pionElement.querySelector(CONFIG.SELECTOR_ICON);
if (!iconElement) return;
let avatarImg = iconElement.querySelector(`.${CONFIG.CLASS_AVATAR_IMG}`);
if (!avatarImg) {
avatarImg = AvatarStyler.createAvatarImage(avatarUrl, playerName);
if (iconElement.firstChild) {
iconElement.insertBefore(avatarImg, iconElement.firstChild);
} else {
iconElement.appendChild(avatarImg);
}
} else if (!DOMUtils.isAvatarValid(pionElement)) {
avatarImg.src = avatarUrl;
avatarImg.alt = playerName;
}
const borderColor = AvatarStyler.getBorderColor(pionElement);
AvatarStyler.applyAvatarStyles(avatarImg, borderColor);
if (!avatarImg.complete || avatarImg.naturalHeight === 0) {
avatarImg.src = avatarImg.src;
}
pionElement.setAttribute(CONFIG.ATTR_AVATAR_STATUS, CONFIG.STATUS_SUCCESS);
pionElement.setAttribute(CONFIG.ATTR_PLAYER_NAME, playerName);
state.avatarCache.set(pionElement, CONFIG.STATUS_SUCCESS);
// Gérer l'emoji d'action
const action = ActionDetector.getPlayerAction(pionElement);
const currentAction = pionElement.getAttribute(CONFIG.ATTR_CURRENT_ACTION);
// Mettre à jour l'emoji si l'action a changé ou si force est activé
if (force || action !== currentAction) {
ActionDetector.updateActionEmoji(iconElement, action);
pionElement.setAttribute(CONFIG.ATTR_CURRENT_ACTION, action || '');
Utils.debugLog('🎯 Action mise à jour:', action);
}
} catch (error) {
Utils.debugLog('❌ Erreur application avatar:', error);
state.avatarCache.set(pionElement, CONFIG.STATUS_FAILED);
}
},
removeCustomAvatars() {
document.querySelectorAll(`.${CONFIG.CLASS_AVATAR_IMG}`).forEach(img => img.remove());
document.querySelectorAll(`.${CONFIG.CLASS_ACTION_EMOJI}`).forEach(emoji => emoji.remove());
DOMUtils.getAllPions().forEach(pion => {
pion.removeAttribute(CONFIG.ATTR_AVATAR_STATUS);
pion.removeAttribute(CONFIG.ATTR_PLAYER_NAME);
pion.removeAttribute(CONFIG.ATTR_CURRENT_ACTION);
});
state.avatarCache.clear();
Utils.debugLog('✅ Avatars et emojis supprimés');
},
async applyAvatarsToAllPions(force = false) {
if (!Storage.loadAvatarEnabled()) {
this.removeCustomAvatars();
return;
}
const pions = DOMUtils.getAllPions();
Utils.debugLog(`🔍 ${pions.length} pion(s) trouvé(s)`);
for (const pion of pions) {
if (!force && DOMUtils.isAvatarValid(pion)) {
// Mettre à jour uniquement la bordure
const iconElement = pion.querySelector(CONFIG.SELECTOR_ICON);
const avatarImg = iconElement?.querySelector(`.${CONFIG.CLASS_AVATAR_IMG}`);
if (avatarImg) {
const borderColor = AvatarStyler.getBorderColor(pion);
avatarImg.style.setProperty('border', `3px solid ${borderColor}`, 'important');
}
continue;
}
await this.applyCustomAvatar(pion, force);
}
},
reapplyAvatarsSync(force = false) {
if (!Storage.loadAvatarEnabled()) return;
DOMUtils.getAllPions().forEach(pion => {
if (!force && DOMUtils.isAvatarValid(pion)) {
// Mettre à jour uniquement la bordure
const iconElement = pion.querySelector(CONFIG.SELECTOR_ICON);
const avatarImg = iconElement?.querySelector(`.${CONFIG.CLASS_AVATAR_IMG}`);
if (avatarImg) {
const borderColor = AvatarStyler.getBorderColor(pion);
avatarImg.style.setProperty('border', `3px solid ${borderColor}`, 'important');
}
return;
}
this.applyCustomAvatar(pion, force);
});
}
};
// ============================================================
// Système de Réapplication rapide
// ============================================================
const ReapplicationSystem = {
ultraFastReapplication() {
const now = Date.now();
if (now - state.lastReapplyTime < CONFIG.RAF_THROTTLE) {
state.reapplyAnimationFrameId = requestAnimationFrame(() => this.ultraFastReapplication());
return;
}
state.lastReapplyTime = now;
if (Storage.loadAvatarEnabled()) {
AvatarManager.reapplyAvatarsSync(false);
}
state.reapplyAnimationFrameId = requestAnimationFrame(() => this.ultraFastReapplication());
},
start() {
state.stopReapplication();
Utils.debugLog('🔄 Démarrage système de réapplication');
state.reapplyIntervalId = setInterval(() => {
if (Storage.loadAvatarEnabled()) {
AvatarManager.reapplyAvatarsSync(false);
}
}, CONFIG.REAPPLY_INTERVAL);
this.ultraFastReapplication();
},
stop() {
state.stopReapplication();
Utils.debugLog('⏹️ Arrêt système de réapplication');
}
};
// ============================================================
// Systeme de taille & styles
// ============================================================
const SizingSystem = {
applyAvatarSize(size) {
const scale = size / 100;
let styleElement = document.getElementById('dreadcast-avatar-resize-style');
if (!styleElement) {
styleElement = document.createElement('style');
styleElement.id = 'dreadcast-avatar-resize-style';
document.head.appendChild(styleElement);
}
const emojiSize = Storage.loadEmojiSize();
styleElement.textContent = `
.personnages .icon_perso {
transform: scale(${scale}) !important;
transform-origin: center center !important;
}
.personnages .icon_perso .le_icon_perso {
transform: scale(1) !important;
position: relative !important;
}
.personnages .icon_perso {
z-index: auto !important;
}
.${CONFIG.CLASS_AVATAR_IMG} {
pointer-events: none !important;
width: 70px !important;
height: 70px !important;
object-fit: cover !important;
border-radius: 50% !important;
border: 2px solid rgba(255, 255, 255, 0.8) !important;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3) !important;
position: absolute !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
z-index: ${CONFIG.Z_INDEX_AVATAR} !important;
display: block !important;
visibility: visible !important;
opacity: 1 !important;
transition: border-color 0.3s ease !important;
animation: none !important;
}
.personnages .icon_perso .le_icon_perso > * {
position: relative !important;
}
.personnages .icon_perso .le_icon_perso > .${CONFIG.CLASS_AVATAR_IMG} {
z-index: ${CONFIG.Z_INDEX_AVATAR} !important;
}
.personnages .icon_perso .le_icon_perso > svg,
.personnages .icon_perso .le_icon_perso > use {
z-index: 1 !important;
}
.${CONFIG.CLASS_ACTION_EMOJI} {
position: absolute !important;
top: -5px !important;
right: -15px !important;
font-size: ${emojiSize}px !important;
border-radius: 50% !important;
width: ${emojiSize * 0.56}px !important;
height: ${emojiSize * 0.56}px !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3) !important;
z-index: ${CONFIG.Z_INDEX_EMOJI} !important;
pointer-events: none !important;
border: 2px solid rgba(0, 0, 0, 0.1) !important;
transition: opacity 0.2s ease !important;
}
`;
},
applyEmojiSize(size) {
// Cette fonction met à jour uniquement la taille des emojis
// Elle réutilise applyAvatarSize qui utilise déjà Storage.loadEmojiSize()
const currentAvatarSize = Storage.loadAvatarSize();
this.applyAvatarSize(currentAvatarSize);
Utils.debugLog('✅ Taille emoji appliquée:', `${size}px`);
}
};
// ============================================================
// Mise à jour de la légende des couleurs
// ============================================================
function updateColorLegend() {
const legendContainer = document.getElementById('color-legend-container');
if (!legendContainer) return;
const customColors = Storage.loadCustomColors();
const opacity = Storage.loadColorOpacity() / 100;
const legendHTML = Object.entries(CONFIG.ACTION_COLORS)
.filter(([action]) => !['encombat', 'noaction'].includes(action))
.map(([action, defaultColor]) => {
const hexColor = customColors[action] || defaultColor;
const rgbaColor = Utils.hexToRgba(hexColor, opacity);
const labels = {
'en_combat': '⚔️ Combat',
'aucune': '⏸️ Aucune',
'repos': '😴 Repos',
'recherche': '⛏️ Fouille',
'cacher': '🫣 Cacher',
'scruter': '👀 Scruter',
'soin': '💊 Soigner',
'travail': '⚙️ Travailler',
'ko': '💀 Mort',
'destruction': '💥 Destruction',
'reparation': '🔧 Reparation'
};
return `
<div style="display: inline-flex !important; align-items: center !important; margin: 4px 8px !important;">
<div style="width: 16px !important; height: 16px !important; border-radius: 50% !important; background: ${rgbaColor} !important; margin-right: 6px !important; border: 2px solid rgba(0, 0, 0, 0.1) !important;"></div>
<span style="font-size: 12px !important; color: #4a5568 !important;">${labels[action]}</span>
</div>
`;
}).join('');
legendContainer.innerHTML = legendHTML;
}
// ============================================================
// Composants UI
// ============================================================
const UIComponents = {
createDraggableBehavior(element, handle) {
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
let isDragging = false;
handle.onmousedown = (e) => {
e.preventDefault();
e.stopPropagation();
isDragging = true;
const rect = element.getBoundingClientRect();
element.style.transform = 'none';
element.style.top = `${rect.top}px`;
element.style.left = `${rect.left}px`;
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDrag;
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 closeDrag(e) {
if (e) {
e.stopPropagation();
}
isDragging = false;
document.onmouseup = null;
document.onmousemove = null;
}
},
attachPanelEvents() {
const closeBtn = document.getElementById('avatar-close-btn');
const slider = document.getElementById('avatar-size-slider');
const valueDisplay = document.getElementById('avatar-size-value');
const resetBtn = document.getElementById('avatar-reset-btn');
const avatarCheckbox = document.getElementById('avatar-enabled-checkbox');
const emojiCheckbox = document.getElementById('emoji-enabled-checkbox');
const emojiSizeSlider = document.getElementById('emoji-size-slider');
const emojiSizeValue = document.getElementById('emoji-size-value');
const resetColorsBtn = document.getElementById('reset-colors-btn');
const panel = document.getElementById('dreadcast-avatar-config-panel');
if (!closeBtn || !slider || !valueDisplay || !resetBtn || !avatarCheckbox || !emojiCheckbox || !emojiSizeSlider || !emojiSizeValue) {
Utils.debugLog('❌ Éléments du panneau introuvables');
return;
}
const currentSize = Storage.loadAvatarSize();
const avatarsEnabled = Storage.loadAvatarEnabled();
const emojisEnabled = Storage.loadEmojiEnabled();
const currentEmojiSize = Storage.loadEmojiSize();
slider.value = currentSize;
valueDisplay.textContent = `${currentSize}%`;
avatarCheckbox.checked = avatarsEnabled;
emojiCheckbox.checked = emojisEnabled;
emojiSizeSlider.value = currentEmojiSize;
emojiSizeValue.textContent = `${currentEmojiSize}px`;
closeBtn.addEventListener('click', (e) => {
e.stopPropagation();
panel.style.display = 'none';
});
slider.addEventListener('input', function () {
const size = parseInt(this.value, 10);
valueDisplay.textContent = `${size}%`;
if (!CombatDetector.isInCombat) {
SizingSystem.applyAvatarSize(size);
Storage.saveAvatarSize(size);
} else {
CombatDetector.savedSize = size;
Storage.saveAvatarSize(size);
Utils.debugLog('⚔️ En combat: taille sauvegardée mais non appliquée');
}
});
avatarCheckbox.addEventListener('change', function () {
const enabled = this.checked;
Storage.saveAvatarEnabled(enabled);
if (enabled) {
Utils.debugLog('🖼️ Activation des avatars...');
ReapplicationSystem.start();
AvatarManager.applyAvatarsToAllPions(true);
} else {
Utils.debugLog('🚫 Désactivation des avatars...');
ReapplicationSystem.stop();
AvatarManager.removeCustomAvatars();
}
});
emojiCheckbox.addEventListener('change', function () {
const enabled = this.checked;
Storage.saveEmojiEnabled(enabled);
if (enabled) {
Utils.debugLog('😀 Activation des emojis...');
// Réappliquer tous les avatars pour ajouter les emojis
AvatarManager.applyAvatarsToAllPions(true);
} else {
Utils.debugLog('🚫 Désactivation des emojis...');
// Supprimer tous les emojis
document.querySelectorAll(`.${CONFIG.CLASS_ACTION_EMOJI}`).forEach(emoji => emoji.remove());
}
});
emojiSizeSlider.addEventListener('input', function () {
const size = parseInt(this.value, 10);
emojiSizeValue.textContent = `${size}px`;
SizingSystem.applyEmojiSize(size);
Storage.saveEmojiSize(size);
// Réappliquer les avatars pour mettre à jour les emojis
AvatarManager.applyAvatarsToAllPions(true);
});
resetBtn.addEventListener('click', () => {
slider.value = CONFIG.DEFAULT_SIZE;
valueDisplay.textContent = `${CONFIG.DEFAULT_SIZE}%`;
avatarCheckbox.checked = true;
emojiCheckbox.checked = true;
emojiSizeSlider.value = CONFIG.DEFAULT_EMOJI_SIZE;
emojiSizeValue.textContent = `${CONFIG.DEFAULT_EMOJI_SIZE}px`;
// Réinitialiser l'opacité
const opacitySlider = document.getElementById('color-opacity-slider');
const opacityValue = document.getElementById('color-opacity-value');
if (opacitySlider && opacityValue) {
opacitySlider.value = CONFIG.DEFAULT_COLOR_OPACITY;
opacityValue.textContent = `${CONFIG.DEFAULT_COLOR_OPACITY}%`;
Storage.saveColorOpacity(CONFIG.DEFAULT_COLOR_OPACITY);
}
// Réinitialiser les couleurs aussi
Storage.saveCustomColors({});
SizingSystem.applyAvatarSize(CONFIG.DEFAULT_SIZE);
SizingSystem.applyEmojiSize(CONFIG.DEFAULT_EMOJI_SIZE);
Storage.saveAvatarSize(CONFIG.DEFAULT_SIZE);
Storage.saveAvatarEnabled(true);
Storage.saveEmojiEnabled(true);
Storage.saveEmojiSize(CONFIG.DEFAULT_EMOJI_SIZE);
// Réinitialiser les color pickers
const colorActions = ['en_combat', 'aucune', 'repos', 'recherche', 'cacher', 'scruter', 'soin', 'travail', 'ko', 'destruction', 'reparation'];
const colorStatuses = ['connected', 'disconnected'];
colorActions.forEach(action => {
const colorPicker = document.getElementById(`color-${action}`);
if (colorPicker) {
colorPicker.value = CONFIG.ACTION_COLORS[action];
}
});
colorStatuses.forEach(status => {
const colorPicker = document.getElementById(`color-${status}`);
if (colorPicker) {
colorPicker.value = status === 'connected' ? CONFIG.COLOR_CONNECTED : CONFIG.COLOR_DISCONNECTED;
}
});
// Mettre à jour la légende
updateColorLegend();
state.clearCaches();
ReapplicationSystem.start();
AvatarManager.applyAvatarsToAllPions(true);
});
// Color pickers pour les actions
const colorActions = ['en_combat', 'aucune', 'repos', 'recherche', 'fouille', 'cacher', 'scruter', 'soin', 'travail', 'ko', 'destruction', 'reparation'];
const colorStatuses = ['connected', 'disconnected'];
// Charger les couleurs personnalisées
const customColors = Storage.loadCustomColors();
// Initialiser les color pickers avec les couleurs personnalisées ou par défaut
colorActions.forEach(action => {
const colorPicker = document.getElementById(`color-${action}`);
if (colorPicker) {
// Charger la couleur personnalisée ou la couleur par défaut
const savedColor = customColors[action] || CONFIG.ACTION_COLORS[action];
colorPicker.value = savedColor;
// Event listener pour sauvegarder la couleur
colorPicker.addEventListener('input', function() {
const newColor = this.value;
const allCustomColors = Storage.loadCustomColors();
allCustomColors[action] = newColor;
Storage.saveCustomColors(allCustomColors);
// Mettre à jour la légende
updateColorLegend();
// Réappliquer les avatars pour mettre à jour les couleurs
AvatarManager.applyAvatarsToAllPions(true);
Utils.debugLog(`🎨 Couleur ${action} mise à jour: ${newColor}`);
});
}
});
// Color pickers pour les statuts
colorStatuses.forEach(status => {
const colorPicker = document.getElementById(`color-${status}`);
if (colorPicker) {
const savedColor = customColors[status] || (status === 'connected' ? CONFIG.COLOR_CONNECTED : CONFIG.COLOR_DISCONNECTED);
colorPicker.value = savedColor;
colorPicker.addEventListener('input', function() {
const newColor = this.value;
const allCustomColors = Storage.loadCustomColors();
allCustomColors[status] = newColor;
Storage.saveCustomColors(allCustomColors);
AvatarManager.applyAvatarsToAllPions(true);
Utils.debugLog(`🎨 Couleur ${status} mise à jour: ${newColor}`);
});
}
});
// Slider d'opacité des couleurs
const opacitySlider = document.getElementById('color-opacity-slider');
const opacityValue = document.getElementById('color-opacity-value');
if (opacitySlider && opacityValue) {
// Charger l'opacité sauvegardée
const currentOpacity = Storage.loadColorOpacity();
opacitySlider.value = currentOpacity;
opacityValue.textContent = `${currentOpacity}%`;
// Event listener pour le slider
opacitySlider.addEventListener('input', function() {
const opacity = parseInt(this.value, 10);
opacityValue.textContent = `${opacity}%`;
// Sauvegarder l'opacité
Storage.saveColorOpacity(opacity);
// Réappliquer tous les avatars pour mettre à jour les couleurs
AvatarManager.applyAvatarsToAllPions(true);
// Mettre à jour la légende
if (typeof updateColorLegend === 'function') {
updateColorLegend();
}
Utils.debugLog(`👁️ Opacité des couleurs mise à jour: ${opacity}%`);
});
}
// Bouton reset couleurs
if (resetColorsBtn) {
resetColorsBtn.addEventListener('click', () => {
// Réinitialiser toutes les couleurs
Storage.saveCustomColors({});
// Réinitialiser l'opacité
const opacitySlider = document.getElementById('color-opacity-slider');
const opacityValue = document.getElementById('color-opacity-value');
if (opacitySlider && opacityValue) {
opacitySlider.value = CONFIG.DEFAULT_COLOR_OPACITY;
opacityValue.textContent = `${CONFIG.DEFAULT_COLOR_OPACITY}%`;
Storage.saveColorOpacity(CONFIG.DEFAULT_COLOR_OPACITY);
}
// Réinitialiser les color pickers
colorActions.forEach(action => {
const colorPicker = document.getElementById(`color-${action}`);
if (colorPicker) {
colorPicker.value = CONFIG.ACTION_COLORS[action];
}
});
colorStatuses.forEach(status => {
const colorPicker = document.getElementById(`color-${status}`);
if (colorPicker) {
colorPicker.value = status === 'connected' ? CONFIG.COLOR_CONNECTED : CONFIG.COLOR_DISCONNECTED;
}
});
// Mettre à jour la légende
updateColorLegend();
// Réappliquer les avatars
AvatarManager.applyAvatarsToAllPions(true);
Utils.debugLog('✅ Couleurs réinitialisées');
});
}
const dataBox = panel.querySelector('.dataBox');
const head = panel.querySelector('.head');
if (dataBox && head) {
this.createDraggableBehavior(dataBox, head);
}
},
createConfigPanel() {
let panel = document.getElementById('dreadcast-avatar-config-panel');
if (panel) return panel;
panel = document.createElement('div');
panel.id = 'dreadcast-avatar-config-panel';
panel.style.cssText = `
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
background-color: rgba(0, 0, 0, 0.7) !important;
z-index: ${CONFIG.Z_INDEX_OVERLAY} !important;
display: none !important;
`;
// Créer la légende des couleurs
const customColors = Storage.loadCustomColors();
const legendHTML = Object.entries(CONFIG.ACTION_COLORS)
.filter(([action]) => !['encombat', 'noaction'].includes(action))
.map(([action, defaultColor]) => {
const color = customColors[action] || defaultColor;
const labels = {
'en_combat': '⚔️ Combat',
'aucune': '⏸️ Aucune',
'repos': '😴 Repos',
'recherche': '⛏️ Fouille',
'cacher': '🫣 Cacher',
'scruter': '👀 Scruter',
'soin': '💊 Soigner',
'travail': '⚙️ Travailler',
'ko': '💀 Mort/KO',
'destruction': '💥 Destruction',
'reparation': '🔧 Réparation'
};
return `
<div style="display: inline-flex !important; align-items: center !important; margin: 4px 8px !important;">
<div style="width: 16px !important; height: 16px !important; border-radius: 50% !important; background: ${color} !important; margin-right: 6px !important; border: 2px solid rgba(0, 0, 0, 0.1) !important;"></div>
<span style="font-size: 12px !important; color: #4a5568 !important;">${labels[action]}</span>
</div>
`;
}).join('');
panel.innerHTML = `
<div class="dataBox" style="position: fixed !important; top: 50% !important; left: 50% !important; transform: translate(-50%, -50%) !important; z-index: ${CONFIG.Z_INDEX_PANEL} !important; width: 600px !important; max-height: 90vh !important; overflow-y: auto !important; background: #ffffff !important; border-radius: 12px !important; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3) !important;">
<relative>
<div class="head" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; padding: 20px !important; cursor: move !important; position: relative !important;">
<div class="title" style="color: #ffffff !important; font-size: 18px !important; font-weight: 600 !important; text-align: center !important;">🎀 PmP</div>
<div title="Fermer" class="close" id="avatar-close-btn" style="position: absolute !important; top: 15px !important; right: 15px !important; color: #ffffff !important; cursor: pointer !important; font-size: 24px !important; background: rgba(255, 255, 255, 0.2) !important; border-radius: 6px !important; width: 32px !important; height: 32px !important; display: flex !important; align-items: center !important; justify-content: center !important;">✕</div>
</div>
<div class="content" style="padding: 30px !important; background: #ffffff !important;">
<div style="margin-bottom: 25px !important; padding: 18px !important; background: linear-gradient(135deg, #f6f8fb 0%, #eef2f7 100%) !important; border-left: 4px solid #667eea !important; border-radius: 8px !important;">
<label style="display: flex !important; align-items: center !important; cursor: pointer !important;">
<input type="checkbox" id="avatar-enabled-checkbox" style="width: 20px !important; height: 20px !important; margin-right: 12px !important; cursor: pointer !important; accent-color: #667eea !important;">
<span style="font-size: 15px !important; color: #2d3748 !important; font-weight: 500 !important;">🖼️ Afficher les avatars des joueurs</span>
</label>
<div style="margin-top: 10px !important; font-size: 12px !important; color: #718096 !important; padding-left: 32px !important;">Remplace l'icône par défaut par l'avatar réel de chaque joueur</div>
</div>
<div style="margin-bottom: 25px !important; padding: 18px !important; background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%) !important; border-left: 4px solid #f59e0b !important; border-radius: 8px !important;">
<label style="display: flex !important; align-items: center !important; cursor: pointer !important;">
<input type="checkbox" id="emoji-enabled-checkbox" style="width: 20px !important; height: 20px !important; margin-right: 12px !important; cursor: pointer !important; accent-color: #f59e0b !important;">
<span style="font-size: 15px !important; color: #2d3748 !important; font-weight: 500 !important;">😀 Afficher les emojis d'action</span>
</label>
<div style="margin-top: 10px !important; font-size: 12px !important; color: #92400e !important; padding-left: 32px !important;">Ajoute un emoji dans le coin de l'avatar pour indiquer l'action en cours</div>
</div>
<div style="margin-bottom: 20px !important; padding: 18px !important; background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%) !important; border-left: 4px solid #f59e0b !important; border-radius: 8px !important;">
<div style="font-size: 14px !important; color: #78350f !important; font-weight: 600 !important; margin-bottom: 12px !important;">🎨 Couleurs par action</div>
<div id="color-legend-container" style="display: flex !important; flex-wrap: wrap !important; gap: 4px !important;">
${legendHTML}
</div>
<div style="margin-top: 10px !important; font-size: 11px !important; color: #92400e !important;">La bordure de l'avatar change de couleur selon l'action du joueur</div>
</div>
<div style="margin-bottom: 20px !important; padding: 18px !important; background: linear-gradient(135deg, #e0e7ff 0%, #c7d2fe 100%) !important; border-left: 4px solid #6366f1 !important; border-radius: 8px !important;">
<div style="font-size: 14px !important; color: #312e81 !important; font-weight: 600 !important; margin-bottom: 12px !important;">🎨 Personnaliser les couleurs</div>
<div style="display: grid !important; grid-template-columns: 1fr 1fr !important; gap: 10px !important; margin-top: 12px !important;">
<!-- Combat -->
<div style="display: flex !important; align-items: center !important; justify-content: space-between !important;">
<label style="font-size: 12px !important; color: #4a5568 !important;">⚔️ Combat</label>
<input type="color" id="color-en_combat" value="#ef4444" style="width: 40px !important; height: 30px !important; border: none !important; border-radius: 4px !important; cursor: pointer !important;">
</div>
<!-- Aucune -->
<div style="display: flex !important; align-items: center !important; justify-content: space-between !important;">
<label style="font-size: 12px !important; color: #4a5568 !important;">⏸️ Aucune</label>
<input type="color" id="color-aucune" value="#9ca3af" style="width: 40px !important; height: 30px !important; border: none !important; border-radius: 4px !important; cursor: pointer !important;">
</div>
<!-- Repos -->
<div style="display: flex !important; align-items: center !important; justify-content: space-between !important;">
<label style="font-size: 12px !important; color: #4a5568 !important;">😴 Repos</label>
<input type="color" id="color-repos" value="#06b6d4" style="width: 40px !important; height: 30px !important; border: none !important; border-radius: 4px !important; cursor: pointer !important;">
</div>
<!-- Recherche -->
<div style="display: flex !important; align-items: center !important; justify-content: space-between !important;">
<label style="font-size: 12px !important; color: #4a5568 !important;">⛏️ Fouille</label>
<input type="color" id="color-recherche" value="#f59e0b" style="width: 40px !important; height: 30px !important; border: none !important; border-radius: 4px !important; cursor: pointer !important;">
</div>
<!-- Cacher -->
<div style="display: flex !important; align-items: center !important; justify-content: space-between !important;">
<label style="font-size: 12px !important; color: #4a5568 !important;">🫣 Cacher</label>
<input type="color" id="color-cacher" value="#8b5cf6" style="width: 40px !important; height: 30px !important; border: none !important; border-radius: 4px !important; cursor: pointer !important;">
</div>
<!-- Scruter -->
<div style="display: flex !important; align-items: center !important; justify-content: space-between !important;">
<label style="font-size: 12px !important; color: #4a5568 !important;">👀 Scruter</label>
<input type="color" id="color-scruter" value="#f97316" style="width: 40px !important; height: 30px !important; border: none !important; border-radius: 4px !important; cursor: pointer !important;">
</div>
<!-- Soigner -->
<div style="display: flex !important; align-items: center !important; justify-content: space-between !important;">
<label style="font-size: 12px !important; color: #4a5568 !important;">💊 Soigner</label>
<input type="color" id="color-soin" value="#10b981" style="width: 40px !important; height: 30px !important; border: none !important; border-radius: 4px !important; cursor: pointer !important;">
</div>
<!-- Travailler -->
<div style="display: flex !important; align-items: center !important; justify-content: space-between !important;">
<label style="font-size: 12px !important; color: #4a5568 !important;">⚙️ Travailler</label>
<input type="color" id="color-travail" value="#92400e" style="width: 40px !important; height: 30px !important; border: none !important; border-radius: 4px !important; cursor: pointer !important;">
</div>
<!-- KO/Mort -->
<div style="display: flex !important; align-items: center !important; justify-content: space-between !important;">
<label style="font-size: 12px !important; color: #4a5568 !important;">💀 KO/Mort</label>
<input type="color" id="color-ko" value="#1f2937" style="width: 40px !important; height: 30px !important; border: none !important; border-radius: 4px !important; cursor: pointer !important;">
</div>
<!-- Destruction -->
<div style="display: flex !important; align-items: center !important; justify-content: space-between !important;">
<label style="font-size: 12px !important; color: #4a5568 !important;">💥 Destruction</label>
<input type="color" id="color-destruction" value="#ef4444" style="width: 40px !important; height: 30px !important; border: none !important; border-radius: 4px !important; cursor: pointer !important;">
</div>
<!-- Réparation -->
<div style="display: flex !important; align-items: center !important; justify-content: space-between !important;">
<label style="font-size: 12px !important; color: #4a5568 !important;">🔧 Réparation</label>
<input type="color" id="color-reparation" value="#ef4444" style="width: 40px !important; height: 30px !important; border: none !important; border-radius: 4px !important; cursor: pointer !important;">
</div>
<!-- Connecté -->
<div style="display: flex !important; align-items: center !important; justify-content: space-between !important;">
<label style="font-size: 12px !important; color: #4a5568 !important;">🟢 Connecté</label>
<input type="color" id="color-connected" value="#4ade80" style="width: 40px !important; height: 30px !important; border: none !important; border-radius: 4px !important; cursor: pointer !important;">
</div>
<!-- Déconnecté -->
<div style="display: flex !important; align-items: center !important; justify-content: space-between !important;">
<label style="font-size: 12px !important; color: #4a5568 !important;">⚪ Déconnecté</label>
<input type="color" id="color-disconnected" value="#ffffff" style="width: 40px !important; height: 30px !important; border: none !important; border-radius: 4px !important; cursor: pointer !important;">
</div>
</div>
<div style="margin-top: 12px !important; text-align: center !important;">
<button id="reset-colors-btn" style="padding: 8px 20px !important; background: #6366f1 !important; color: #ffffff !important; border: none !important; border-radius: 6px !important; cursor: pointer !important; font-size: 12px !important; font-weight: 600 !important;">🔄 Réinitialiser les couleurs</button>
</div>
<div style="margin-top: 10px !important; font-size: 11px !important; color: #4338ca !important;">Cliquez sur les carrés de couleur pour personnaliser chaque action</div>
</div>
<div style="margin-bottom: 25px !important; padding: 18px !important; background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%) !important; border-left: 4px solid #f59e0b !important; border-radius: 8px !important;">
<label style="display: block !important; margin-bottom: 15px !important; font-size: 15px !important; color: #2d3748 !important; font-weight: 500 !important;">
👁️ Opacité des couleurs : <span id="color-opacity-value" style="color: #f59e0b !important; font-weight: 700 !important;">100%</span>
</label>
<input type="range" id="color-opacity-slider" min="${CONFIG.MIN_COLOR_OPACITY}" max="${CONFIG.MAX_COLOR_OPACITY}" value="${CONFIG.DEFAULT_COLOR_OPACITY}" style="width: 100% !important; height: 6px !important; background: linear-gradient(to right, #fde68a, #f59e0b) !important; border-radius: 10px !important; cursor: pointer !important;">
<div style="margin-top: 8px !important; font-size: 12px !important; color: #92400e !important;">💡 0% = invisible, 100% = couleurs vives</div>
<style>
#color-opacity-slider::-webkit-slider-thumb {
-webkit-appearance: none !important;
width: 20px !important;
height: 20px !important;
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%) !important;
border-radius: 50% !important;
cursor: pointer !important;
box-shadow: 0 2px 6px rgba(245, 158, 11, 0.4) !important;
}
#color-opacity-slider::-webkit-slider-thumb:hover {
transform: scale(1.2) !important;
}
#color-opacity-slider::-moz-range-thumb {
width: 20px !important;
height: 20px !important;
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%) !important;
border: none !important;
border-radius: 50% !important;
cursor: pointer !important;
}
</style>
</div>
<div style="margin-bottom: 25px !important;">
<label style="display: block !important; margin-bottom: 15px !important; font-size: 15px !important; color: #2d3748 !important; font-weight: 500 !important;">
📏 Taille des pions : <span id="avatar-size-value" style="color: #667eea !important; font-weight: 700 !important;">100%</span>
</label>
<input type="range" id="avatar-size-slider" min="${CONFIG.MIN_SIZE}" max="${CONFIG.MAX_SIZE}" value="100" style="width: 100% !important; height: 6px !important; background: linear-gradient(to right, #e2e8f0, #667eea) !important; border-radius: 10px !important; cursor: pointer !important;">
<div style="margin-top: 8px !important; font-size: 12px !important; color: #718096 !important;">⚔️ La taille passe automatiquement à 100% en combat</div>
</div>
<div style="margin-bottom: 25px !important;">
<label style="display: block !important; margin-bottom: 15px !important; font-size: 15px !important; color: #2d3748 !important; font-weight: 500 !important;">
😀 Taille des emojis : <span id="emoji-size-value" style="color: #f59e0b !important; font-weight: 700 !important;">18px</span>
</label>
<input type="range" id="emoji-size-slider" min="${CONFIG.MIN_EMOJI_SIZE}" max="${CONFIG.MAX_EMOJI_SIZE}" value="${CONFIG.DEFAULT_EMOJI_SIZE}" style="width: 100% !important; height: 6px !important; background: linear-gradient(to right, #fde68a, #f59e0b) !important; border-radius: 10px !important; cursor: pointer !important;">
<div style="margin-top: 8px !important; font-size: 12px !important; color: #718096 !important;">📐 Ajustez la taille des emojis d'action</div>
<style>
#avatar-size-slider::-webkit-slider-thumb {
-webkit-appearance: none !important;
width: 20px !important;
height: 20px !important;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
border-radius: 50% !important;
cursor: pointer !important;
box-shadow: 0 2px 6px rgba(102, 126, 234, 0.4) !important;
}
#avatar-size-slider::-webkit-slider-thumb:hover {
transform: scale(1.2) !important;
}
#avatar-size-slider::-moz-range-thumb {
width: 20px !important;
height: 20px !important;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
border: none !important;
border-radius: 50% !important;
cursor: pointer !important;
}
#emoji-size-slider::-webkit-slider-thumb {
-webkit-appearance: none !important;
width: 20px !important;
height: 20px !important;
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%) !important;
border-radius: 50% !important;
cursor: pointer !important;
box-shadow: 0 2px 6px rgba(245, 158, 11, 0.4) !important;
}
#emoji-size-slider::-webkit-slider-thumb:hover {
transform: scale(1.2) !important;
}
#emoji-size-slider::-moz-range-thumb {
width: 20px !important;
height: 20px !important;
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%) !important;
border: none !important;
border-radius: 50% !important;
cursor: pointer !important;
}
#avatar-size-slider:disabled::-webkit-slider-thumb {
background: #9ca3af !important;
}
#avatar-size-slider:disabled::-moz-range-thumb {
background: #9ca3af !important;
}
#avatar-close-btn:hover {
background: rgba(255, 255, 255, 0.3) !important;
transform: rotate(90deg) !important;
}
#avatar-reset-btn:hover {
transform: translateY(-2px) !important;
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4) !important;
}
#reset-colors-btn:hover {
background: #4f46e5 !important;
transform: translateY(-1px) !important;
}
</style>
</div>
<div style="text-align: center !important;">
<button id="avatar-reset-btn" style="padding: 12px 30px !important; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; color: #ffffff !important; border: none !important; border-radius: 8px !important; cursor: pointer !important; font-size: 14px !important; font-weight: 600 !important;">🔄 Réinitialiser</button>
</div>
</div>
</relative>
</div>
`;
panel.addEventListener('click', (e) => {
if (e.target === panel) {
panel.style.display = 'none';
}
});
const appendPanel = () => {
if (document.body) {
document.body.appendChild(panel);
setTimeout(() => this.attachPanelEvents(), CONFIG.EVENT_ATTACH_DELAY);
} else {
setTimeout(appendPanel, CONFIG.MENU_CHECK_INTERVAL);
}
};
appendPanel();
return panel;
},
openConfigPanel() {
let panel = document.getElementById('dreadcast-avatar-config-panel');
if (!panel) {
panel = this.createConfigPanel();
}
if (panel) {
panel.style.display = 'block';
Utils.debugLog('✅ Panneau ouvert');
}
}
};
// ============================================================
// Intégration du menu
// ============================================================
const MenuIntegration = {
addMenuOption() {
const checkMenu = setInterval(() => {
const parametresMenu = document.querySelector(CONFIG.SELECTOR_SETTINGS_MENU);
if (parametresMenu) {
clearInterval(checkMenu);
if (document.getElementById('avatar-resize-menu-option')) return;
const menuOption = document.createElement('li');
menuOption.id = 'avatar-resize-menu-option';
menuOption.className = 'link couleur2';
menuOption.textContent = '🎀 PmP v0.5.1';
menuOption.style.cursor = 'pointer';
menuOption.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
UIComponents.openConfigPanel();
}, true);
const lastSeparator = parametresMenu.querySelector('.separator:last-of-type');
if (lastSeparator) {
parametresMenu.insertBefore(menuOption, lastSeparator);
} else {
parametresMenu.appendChild(menuOption);
}
Utils.debugLog('✅ Option menu ajoutée');
}
}, CONFIG.MENU_CHECK_INTERVAL);
setTimeout(() => clearInterval(checkMenu), CONFIG.MENU_CHECK_TIMEOUT);
}
};
// ============================================================
// DOM OBSERVER
// ============================================================
const DOMObserver = {
observe() {
const targetNode = document.querySelector('.personnages');
if (!targetNode) return;
const observer = new MutationObserver((mutations) => {
Utils.debugLog('🔍 Changement DOM détecté:', mutations.length);
const currentSize = Storage.loadAvatarSize();
if (CombatDetector.isInCombat) {
SizingSystem.applyAvatarSize(100);
} else {
SizingSystem.applyAvatarSize(currentSize);
}
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType === 1 && node.classList?.contains('icon_perso')) {
Utils.debugLog('🆕 Nouveau pion détecté');
AvatarManager.applyCustomAvatar(node, true);
}
});
if (mutation.type === 'childList' || mutation.type === 'attributes') {
const target = mutation.target;
const pionElement = target.classList?.contains('icon_perso')
? target
: target.closest('.icon_perso');
if (pionElement && !DOMUtils.isAvatarValid(pionElement)) {
Utils.debugLog('⚠️ Pion modifié, réapplication...');
AvatarManager.applyCustomAvatar(pionElement, true);
}
}
});
});
observer.observe(targetNode, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['class'],
attributeOldValue: true,
characterData: true,
characterDataOldValue: true
});
Utils.debugLog('✅ MutationObserver activé');
}
};
// ============================================================
// Initialisation
// ============================================================
function init() {
Utils.debugLog('⚡ PimpMyPion - Initialisation');
const savedSize = Storage.loadAvatarSize();
const savedEmojiSize = Storage.loadEmojiSize();
SizingSystem.applyAvatarSize(savedSize);
SizingSystem.applyEmojiSize(savedEmojiSize);
Utils.debugLog('✅ Taille pions appliquée:', `${savedSize}%`);
Utils.debugLog('✅ Taille emojis appliquée:', `${savedEmojiSize}px`);
MenuIntegration.addMenuOption();
DOMObserver.observe();
CombatDetector.start();
Utils.debugLog('✅ Détecteur de combat initialisé');
setTimeout(() => {
Utils.debugLog('🖼️ Application initiale des avatars');
AvatarManager.applyAvatarsToAllPions(true);
if (Storage.loadAvatarEnabled()) {
ReapplicationSystem.start();
}
}, CONFIG.INIT_DELAY);
setTimeout(() => {
Utils.debugLog('🔄 Réapplication de sécurité');
AvatarManager.applyAvatarsToAllPions(true);
}, CONFIG.SECONDARY_DELAY);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();