던담 커스텀 패널

던담 딜표에 커스텀패널을 추가함

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         던담 커스텀 패널
// @namespace    http://tampermonkey.net/
// @version      14.1.3 // 다중 입력 기능 추가
// @description  던담 딜표에 커스텀패널을 추가함
// @author       ww99w & Gemini
// @match        https://dundam.xyz/character*
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';
    console.log('[던담 시뮬] 스크립트 시작 (v14.1.3)');

    // 전역 스킬 정보 캐시
    let dundamSkillInfoCache = null;

    // --- 스타일 정의 (v14.1.2와 거의 동일, input type text 너비 조정) ---
    GM_addStyle(`
        .cc-part .sk.tr:has(.skcount) { cursor: pointer; transition: all 0.2s ease-in-out; }
        .sk.tr.sim-skill-modified { border: 1.5px solid #4CAF50 !important; box-shadow: 0 0 10px rgba(76, 175, 80, 0.3); }
        .sim-control-panel-row { width: 100%; box-sizing: border-box; padding-left: 54px; padding-right: 16px; margin-top: -4px; margin-bottom: 12px; }
        .sim-control-panel-container { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; padding: 12px 16px; box-sizing: border-box; background-color: #2c2e34; border-radius: 6px; border: 1px solid rgba(0, 0, 0, 0.4); box-shadow: 0 2px 6px rgba(0,0,0,0.35); }
        .sim-input-group { display: flex; align-items: center; gap: 6px; margin-right: 15px; }
        .sim-input-group .sim-label { color: #c0c0c0; font-size: 14px; font-weight: 500; }
        .sim-input-group input[type="number"], .sim-input-group input[type="text"] { /* type="text" 추가 */
            width: 100px; /* 여러 값 입력을 위해 너비 증가 */
            height: 32px; border: 1px solid #181818; background-color: #202225; color: #e0e0e0; border-radius: 4px;
            text-align: center; font-size: 14px; padding: 0 5px;
        }
        .sim-control-panel-container button { height: 32px; padding: 0 14px; border: 1px solid #505050; background-image: linear-gradient(to bottom, #525252, #454545); color: #e0e0e0; cursor: pointer; border-radius: 4px; font-size: 13px; font-weight: 500; transition: background-image 0.2s ease; }
        .sim-control-panel-container button:hover { background-image: linear-gradient(to bottom, #5e5e5e, #505050); }
        .sim-control-panel-container button.sim-apply-all-btn { margin-left: auto; }
        .sim-control-panel-container button.sim-reset-btn { border-color: #704040; background-image: linear-gradient(to bottom, #704040, #603030); }
        .sim-control-panel-container button.sim-reset-btn:hover { background-image: linear-gradient(to bottom, #804a4a, #704040); }
        .sim-input-group input[type="text"]::-webkit-outer-spin-button,
        .sim-input-group input[type="text"]::-webkit-inner-spin-button {
            -webkit-appearance: none; margin: 0;
        }
        .sim-input-group input[type="text"] { -moz-appearance: textfield; }
        .sim-skill-sub-info-container { display: flex !important; flex-direction: column !important; align-items: center !important; justify-content: center !important; width: auto !important; height: 100% !important; }
        .sim-skill-sub-info-wrapper { display: flex; justify-content: center; align-items: center; gap: 8px; font-size: 11px; margin-top: 2px; line-height: 1.2; width: 100%; }
        .sim-cast-change-indicator, .sim-cooldown-indicator { white-space: nowrap; }
        .sim-cooldown-indicator { color: #999; }
        .sim-cast-change-indicator { font-weight: bold; }
        .sim-cast-change-indicator.positive { color: #77b67a; } .sim-cast-change-indicator.negative { color: #d96c6c; }
        .sim-skill-damage-change-indicator { font-weight: bold; margin-left: 6px; font-size: 12px; white-space: nowrap; }
        .sim-skill-damage-change-indicator.positive { color: #81c784; } .sim-skill-damage-change-indicator.negative { color: #e57373; }
        .sim-total-damage-change-indicator { font-size: 1.1rem; font-weight: bold; margin-left: 10px; }
        .sim-total-damage-change-indicator.positive { color: #81c784; } .sim-total-damage-change-indicator.negative { color: #e57373; }
    `);

    const parseDamageString = (text) => parseInt(String(text).replace(/,/g, ''), 10);
    const formatDamageNumber = (num) => Math.round(num).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");

    function getBaseCooldownData(forceRefresh = false) {
        if (!forceRefresh && dundamSkillInfoCache && Object.keys(dundamSkillInfoCache).length > 0) {
            return dundamSkillInfoCache;
        }
        const skillInfoTab = document.querySelector('.tab__content[name="스킬정보"]');
        if (!skillInfoTab) { console.warn('[던담 시뮬] getBaseCooldownData: "스킬정보" 탭 없음.'); return null; }
        const skillRows = skillInfoTab.querySelectorAll('.skinfo .sk.tr');
        if (skillRows.length === 0) { console.warn('[던담 시뮬] getBaseCooldownData: "스킬정보" 탭 내 스킬 행 없음.'); return null; }

        const parsedData = {};
        skillRows.forEach(row => {
            const nameEl = row.querySelector('.skn span');
            const cooldownEl = row.querySelector('.skc ul li.skt span.val');
            if (nameEl && cooldownEl) {
                const name = nameEl.textContent.trim();
                const cooldown = parseFloat(cooldownEl.textContent);
                if (name && !isNaN(cooldown)) parsedData[name] = { baseCooldown: cooldown };
            }
        });
        if (Object.keys(parsedData).length > 0) {
            dundamSkillInfoCache = parsedData;
            return dundamSkillInfoCache;
        }
        return null;
    }

    function toggleModifiedIndicator(skillRow) {
        const damageEl = skillRow.querySelector('.skl .val'), countEl = skillRow.querySelector('.skc-count-p > span');
        if (!damageEl || !countEl || !skillRow.dataset.originalDamage || !skillRow.dataset.originalReportedCasts) return;
        const currentDamage = parseDamageString(damageEl.textContent), currentCount = parseInt(countEl.textContent.replace('회', ''), 10);
        if (currentDamage !== parseInt(skillRow.dataset.originalDamage, 10) || currentCount !== parseInt(skillRow.dataset.originalReportedCasts, 10)) {
            skillRow.classList.add('sim-skill-modified');
        } else {
            skillRow.classList.remove('sim-skill-modified');
        }
    }

    function updateAllDisplayMetrics(currentDealTable) {
        const skillRows = currentDealTable.querySelectorAll('.cc-part .sk.tr');
        const mainTotalDamageDisplayEl = currentDealTable.querySelector('.hd-part .dval');
        if (!mainTotalDamageDisplayEl) return;
        let newTotalDamage = 0;
        skillRows.forEach(row => { if (row.querySelector('.skcount')) { const damageEl = row.querySelector('.skl .val'); if (damageEl) newTotalDamage += parseDamageString(damageEl.textContent); } });
        mainTotalDamageDisplayEl.textContent = formatDamageNumber(newTotalDamage);
        const totalSumRow = Array.from(skillRows).find(row => row.querySelector('.skn span')?.textContent === '총 합');
        if (totalSumRow) {
            const totalSumDamageEl = totalSumRow.querySelector('.skl .val');
            if (totalSumDamageEl) totalSumDamageEl.textContent = formatDamageNumber(newTotalDamage);
            const originalTotalDamage = parseFloat(currentDealTable.dataset.originalTotalDamage);
            const totalChangeIndicatorEl = totalSumRow.querySelector('.sim-total-damage-change-indicator');
            if (totalChangeIndicatorEl && originalTotalDamage > 0) {
                const totalChange = (newTotalDamage / originalTotalDamage - 1) * 100;
                if (Math.abs(totalChange) > 0.001) { totalChangeIndicatorEl.textContent = `(${totalChange >= 0 ? '+' : ''}${totalChange.toFixed(2)}%)`; totalChangeIndicatorEl.className = `sim-total-damage-change-indicator ${totalChange >= 0 ? 'positive' : 'negative'}`; } else { totalChangeIndicatorEl.textContent = ''; }
            }
        }
        skillRows.forEach(row => {
            if (row.querySelector('.skcount')) {
                const damageEl = row.querySelector('.skl .val'), percentEl = row.querySelector('.ser-percent.share'), skillDamageChangeEl = row.querySelector('.sim-skill-damage-change-indicator');
                if (!damageEl || !percentEl || !skillDamageChangeEl) return;
                const currentSkillDamage = parseDamageString(damageEl.textContent), newPercent = newTotalDamage > 0 ? (currentSkillDamage / newTotalDamage * 100) : 0;
                percentEl.textContent = `${newPercent.toFixed(2)}%`;
                const originalSkillDamage = parseFloat(row.dataset.originalDamage);
                if (originalSkillDamage > 0) {
                    const damageChangePercent = (currentSkillDamage / originalSkillDamage - 1) * 100;
                    if (Math.abs(damageChangePercent) > 0.001) { skillDamageChangeEl.textContent = `(${damageChangePercent >= 0 ? '+' : ''}${damageChangePercent.toFixed(1)}%)`; skillDamageChangeEl.className = `sim-skill-damage-change-indicator ${damageChangePercent >= 0 ? 'positive' : 'negative'}`; } else { skillDamageChangeEl.textContent = ''; }
                } else { skillDamageChangeEl.textContent = ''; }
            }
        });
    }

    /**
     * 여러 퍼센트 값을 파싱하여 누적 배율을 계산하는 헬퍼 함수
     */
    function parseCumulativeMultiplier(inputText, isReduction = false) {
        let totalMultiplier = 1.0;
        if (inputText && inputText.trim() !== "") {
            const values = inputText.trim().split(/\s+/);
            values.forEach(valStr => {
                const percent = parseFloat(valStr);
                if (!isNaN(percent)) {
                    totalMultiplier *= (1 + (isReduction ? -percent : percent) / 100);
                }
            });
        }
        return totalMultiplier;
    }

    function createControlPanelElement(skillRow) {
        const currentSkillName = skillRow.querySelector('.skn span')?.textContent.trim();
        let allSkillInfo = getBaseCooldownData(true);
        let baseCDForThisSkill = skillRow.dataset.originalCooldown || "0";
        if (allSkillInfo && allSkillInfo[currentSkillName] && allSkillInfo[currentSkillName].baseCooldown > 0) {
            const newBaseCD = allSkillInfo[currentSkillName].baseCooldown.toString();
            if (skillRow.dataset.originalCooldown !== newBaseCD || !skillRow.dataset.originalCooldown) {
                skillRow.dataset.originalCooldown = newBaseCD;
                baseCDForThisSkill = newBaseCD;
                const subInfoContainer = skillRow.querySelector('.sim-skill-sub-info-wrapper');
                if (subInfoContainer) { const cdDisplayEl = subInfoContainer.querySelector('.sim-cooldown-indicator'); if (cdDisplayEl) cdDisplayEl.textContent = `[쿨: ${parseFloat(baseCDForThisSkill).toFixed(1)}초]`; }
            }
        } else if (currentSkillName && allSkillInfo && !allSkillInfo[currentSkillName]){ skillRow.dataset.originalCooldown = "0"; baseCDForThisSkill = "0"; }

        const panelContainer = document.createElement('div'); panelContainer.className = 'sim-control-panel-container'; panelContainer.addEventListener('click', e => e.stopPropagation());

        const createInputGroup = (labelText, inputPlaceholder, datasetKey, isMultiValue = false) => {
            const group = document.createElement('div'); group.className = 'sim-input-group';
            const label = document.createElement('span'); label.className = 'sim-label'; label.textContent = labelText;
            const input = document.createElement('input');
            input.type = isMultiValue ? 'text' : 'number'; // 딜, 쿨감은 text로 변경
            input.placeholder = inputPlaceholder;
            if (skillRow.dataset[datasetKey]) { input.value = skillRow.dataset[datasetKey]; }
            group.append(label, input);
            return { group, input };
        };

        const dmgElements = createInputGroup('스증(%)', '예: 20 10 -5', 'simDmgPercentMulti', true);
        const countElements = createInputGroup('횟수+', '0', 'simAdditionalCasts'); // 횟수는 단일 값 유지
        const cdElements = createInputGroup('쿨감(%)', '예: 10 5', 'simCooldownReductionMulti', true);

        if (!baseCDForThisSkill || baseCDForThisSkill === "0") { cdElements.input.disabled = true; cdElements.input.title = "스킬정보 탭에서 쿨타임 정보를 찾을 수 없거나 쿨타임이 0초입니다."; }
        else { cdElements.input.disabled = false; cdElements.input.title = "";}

        const applyAllBtn = document.createElement('button'); applyAllBtn.className = 'sim-apply-all-btn'; applyAllBtn.textContent = '모두 적용';
        const resetBtn = document.createElement('button'); resetBtn.className = 'sim-reset-btn'; resetBtn.textContent = '초기화';

        applyAllBtn.onclick = () => {
            const parentDealTable = skillRow.closest('.deal');
            const originalDamage = parseInt(skillRow.dataset.originalDamage, 10);
            const originalReportedCasts = parseInt(skillRow.dataset.originalReportedCasts, 10);
            const currentOriginalCooldown = parseFloat(skillRow.dataset.originalCooldown || "0");

            skillRow.dataset.simDmgPercentMulti = dmgElements.input.value; // 문자열 그대로 저장
            skillRow.dataset.simAdditionalCasts = countElements.input.value;
            skillRow.dataset.simCooldownReductionMulti = cdElements.input.value; // 문자열 그대로 저장

            const cumulativeDmgMultiplier = parseCumulativeMultiplier(dmgElements.input.value, false);
            const cumulativeCdMultiplier = parseCumulativeMultiplier(cdElements.input.value, true);
            const additionalCasts = parseInt(countElements.input.value, 10) || 0;

            let simulatedCasts = originalReportedCasts;
            let currentCooldown = currentOriginalCooldown;

            if (currentOriginalCooldown > 0) { // 0으로 나누기 방지
                currentCooldown = currentOriginalCooldown * cumulativeCdMultiplier;
                currentCooldown = Math.max(0.1, currentCooldown);
                simulatedCasts = 1 + Math.floor(40 / currentCooldown);
            }
            simulatedCasts += additionalCasts;
            simulatedCasts = Math.max(0, simulatedCasts);

            const damagePerOriginalCast = originalReportedCasts > 0 ? originalDamage / originalReportedCasts : 0;
            const newTotalSkillDamage = damagePerOriginalCast * simulatedCasts * cumulativeDmgMultiplier;

            skillRow.querySelector('.skc-count-p > span').textContent = `${simulatedCasts}회`;
            skillRow.querySelector('.skl .val').textContent = formatDamageNumber(newTotalSkillDamage);

            const subInfoContainer = skillRow.querySelector('.sim-skill-sub-info-wrapper');
            if (subInfoContainer) {
                const castChangeEl = subInfoContainer.querySelector('.sim-cast-change-indicator');
                const castDiff = simulatedCasts - originalReportedCasts;
                if (castChangeEl) { if (castDiff !== 0) { castChangeEl.textContent = `(${castDiff > 0 ? '+' : ''}${castDiff}회)`; castChangeEl.className = `sim-cast-change-indicator ${castDiff > 0 ? 'positive' : 'negative'}`; } else { castChangeEl.textContent = ''; } }
                const cdDisplayEl = subInfoContainer.querySelector('.sim-cooldown-indicator');
                if (cdDisplayEl && currentOriginalCooldown > 0) { const cdDiff = currentCooldown - currentOriginalCooldown; cdDisplayEl.textContent = `[쿨: ${currentCooldown.toFixed(1)}초 ${cdDiff !== 0 && Math.abs(cdDiff) > 0.01 ? (cdDiff < 0 ? '▼' : '▲') + Math.abs(cdDiff).toFixed(1) : ''}]`; }
                else if (cdDisplayEl) { cdDisplayEl.textContent = '';}
            }
            updateAllDisplayMetrics(parentDealTable);
            toggleModifiedIndicator(skillRow);
        };

        resetBtn.onclick = () => {
            const parentDealTable = skillRow.closest('.deal');
            skillRow.querySelector('.skl .val').textContent = formatDamageNumber(skillRow.dataset.originalDamage);
            skillRow.querySelector('.skc-count-p > span').textContent = `${skillRow.dataset.originalReportedCasts}회`;
            dmgElements.input.value = ''; countElements.input.value = ''; cdElements.input.value = '';
            delete skillRow.dataset.simDmgPercentMulti; delete skillRow.dataset.simAdditionalCasts; delete skillRow.dataset.simCooldownReductionMulti;
            const subInfoContainer = skillRow.querySelector('.sim-skill-sub-info-wrapper');
            if (subInfoContainer) {
                const castChangeEl = subInfoContainer.querySelector('.sim-cast-change-indicator'); if (castChangeEl) castChangeEl.textContent = '';
                const cdDisplayEl = subInfoContainer.querySelector('.sim-cooldown-indicator');
                if (cdDisplayEl && skillRow.dataset.originalCooldown && skillRow.dataset.originalCooldown !== "0") {
                    cdDisplayEl.textContent = `[쿨: ${parseFloat(skillRow.dataset.originalCooldown).toFixed(1)}초]`;
                } else if (cdDisplayEl) { cdDisplayEl.textContent = ''; }
            }
            updateAllDisplayMetrics(parentDealTable);
            toggleModifiedIndicator(skillRow);
        };

        panelContainer.append(dmgElements.group, countElements.group, cdElements.group, applyAllBtn, resetBtn);
        const controlPanelRow = document.createElement('div'); controlPanelRow.className = 'sim-control-panel-row'; controlPanelRow.appendChild(panelContainer); return controlPanelRow;
    }

    function setupSkillRowInteractions(skillRow, allSkillBaseInfo) {
        if (!skillRow.querySelector('.skcount') || skillRow.dataset.simLogicAssigned === 'true') return;
        skillRow.dataset.simLogicAssigned = 'true';
        const skillNameElement = skillRow.querySelector('.skn span'), skillName = skillNameElement ? skillNameElement.textContent.trim() : null;
        if (!skillName) { console.warn("[던담 시뮬] setupSkillRowInteractions: 스킬명 없음", skillRow); return; }
        const damageEl = skillRow.querySelector('.skl .val'), countDisplayContainer = skillRow.querySelector('.skc-count-p'), shareEl = skillRow.querySelector('.ser-percent.share');
        if (damageEl && countDisplayContainer && shareEl) {
             skillRow.dataset.originalDamage = parseDamageString(damageEl.textContent);
             const countDisplaySpan = countDisplayContainer.querySelector('span');
             if (countDisplaySpan) skillRow.dataset.originalReportedCasts = parseInt(countDisplaySpan.textContent.replace('회', ''), 10);
             const countParentContainer = countDisplayContainer.parentElement;
             let subInfoWrapper = countParentContainer.querySelector('.sim-skill-sub-info-wrapper');
             if (!subInfoWrapper) { subInfoWrapper = document.createElement('div'); subInfoWrapper.className = 'sim-skill-sub-info-wrapper'; countDisplayContainer.insertAdjacentElement('afterend', subInfoWrapper); }
             let castChangeEl = subInfoWrapper.querySelector('.sim-cast-change-indicator');
             if (!castChangeEl) { castChangeEl = document.createElement('span'); castChangeEl.className = 'sim-cast-change-indicator'; subInfoWrapper.appendChild(castChangeEl); }
             let cdDisplayEl = subInfoWrapper.querySelector('.sim-cooldown-indicator');
             if (!cdDisplayEl) { cdDisplayEl = document.createElement('span'); cdDisplayEl.className = 'sim-cooldown-indicator'; subInfoWrapper.appendChild(cdDisplayEl); }
             if (allSkillBaseInfo && allSkillBaseInfo[skillName] && allSkillBaseInfo[skillName].baseCooldown > 0) {
                 const baseCD = allSkillBaseInfo[skillName].baseCooldown;
                 skillRow.dataset.originalCooldown = baseCD;
                 cdDisplayEl.textContent = `[쿨: ${baseCD.toFixed(1)}초]`;
             } else { skillRow.dataset.originalCooldown = "0"; cdDisplayEl.textContent = ''; }
        }
        if (shareEl && (!shareEl.nextElementSibling || !shareEl.nextElementSibling.classList.contains('sim-skill-damage-change-indicator'))) {
            const skillDamageChangeEl = document.createElement('span'); skillDamageChangeEl.className = 'sim-skill-damage-change-indicator'; shareEl.insertAdjacentElement('afterend', skillDamageChangeEl);
        }
        skillRow.addEventListener('click', () => {
            const nextRow = skillRow.nextElementSibling;
            if (nextRow && nextRow.classList.contains('sim-control-panel-row')) { nextRow.remove(); }
            else { const panel = createControlPanelElement(skillRow); skillRow.insertAdjacentElement('afterend', panel); }
        });
    }

    function initializeTableFeatures(dealTable) {
        if (!dealTable.querySelector('.cc-part .sk.tr .skn span')) { return; }
        if (dealTable.dataset.simSetupDone === 'true' && dundamSkillInfoCache && Object.keys(dundamSkillInfoCache).length > 0) { return; }

        const mainTotalDamageDisplayEl = dealTable.querySelector('.hd-part .dval');
        if (mainTotalDamageDisplayEl && !dealTable.dataset.originalTotalDamage) {
            dealTable.dataset.originalTotalDamage = parseDamageString(mainTotalDamageDisplayEl.textContent);
        }

        const totalSumRow = Array.from(dealTable.querySelectorAll('.cc-part .sk.tr')).find(row => row.querySelector('.skn span')?.textContent === '총 합');
        if (totalSumRow) {
            const totalSumDamageValueEl = totalSumRow.querySelector('.skl .val');
            if (totalSumDamageValueEl && (!totalSumDamageValueEl.nextElementSibling || !totalSumDamageValueEl.nextElementSibling.classList.contains('sim-total-damage-change-indicator'))) {
                const totalChangeIndicatorEl = document.createElement('span');
                totalChangeIndicatorEl.className = 'sim-total-damage-change-indicator';
                totalSumDamageValueEl.insertAdjacentElement('afterend', totalChangeIndicatorEl);
            }
        }

        const allSkillBaseInfo = getBaseCooldownData();

        dealTable.querySelectorAll('.cc-part .sk.tr').forEach(row => {
            setupSkillRowInteractions(row, allSkillBaseInfo);
        });

        if (allSkillBaseInfo && Object.keys(allSkillBaseInfo).length > 0) {
            dealTable.dataset.simSetupDone = 'true';
            updateAllDisplayMetrics(dealTable);
        }
    }

    const observer = new MutationObserver((mutations) => {
        for (const mutation of mutations) {
            if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                let newDealContentReady = false;
                for (const addedNode of mutation.addedNodes) {
                    if (addedNode.nodeType === Node.ELEMENT_NODE) {
                        if ((addedNode.matches('.deal') || addedNode.querySelector('.deal')) && addedNode.querySelector('.cc-part .sk.tr .skn span')) {
                            newDealContentReady = true; break;
                        }
                        if (addedNode.matches('.sk.tr') && addedNode.querySelector('.skn span') && addedNode.closest('.deal .cc-part')) {
                             newDealContentReady = true; break;
                        }
                    }
                }
                if (newDealContentReady) {
                    document.querySelectorAll('.deal').forEach(initializeTableFeatures);
                    break;
                }
            }
        }
    });

    console.log('[던담 시뮬] 초기 테이블 설정 시도');
    document.querySelectorAll('.deal').forEach(initializeTableFeatures);

    observer.observe(document.body, { childList: true, subtree: true });
    console.log('[던담 시뮬] 스크립트 초기화 및 관찰 시작');

})();