// ==UserScript== // @name Pokechill DPS统计 (可折叠,移动端兼容) // @namespace http://tampermonkey.net/ // @version 1.2 // @description 记录所有战斗的秒伤,百分比基于队伍总伤害,战斗结束后数据锁定,保存并退出后清空。支持移动端拖动。 // @author 人民当家做主 // @match https://play-pokechill.github.io/* // @match https://g1tyx.github.io/play-pokechill/* // @icon https://play-pokechill.github.io/img/icons/icon.png // @grant none // ==/UserScript== (function() { 'use strict'; console.log('[DPS统计] 脚本已加载,等待游戏初始化...'); function waitForGameReady() { if (typeof pkmn === 'undefined' || typeof team === 'undefined' || typeof saved === 'undefined') { setTimeout(waitForGameReady, 100); return; } console.log('[DPS统计] 游戏核心变量已就绪'); createDpsPanel(); } // 获取宝可梦中文名称(优先使用游戏内置的 format 函数) function getPokemonName(pkmnId) { if (typeof window.format === 'function') { return window.format(pkmnId); } if (pkmn[pkmnId] && pkmn[pkmnId].rename) return pkmn[pkmnId].rename; if (window.EN_CN_DICT && window.EN_CN_DICT[pkmnId]) { return window.EN_CN_DICT[pkmnId]; } return pkmnId.charAt(0).toUpperCase() + pkmnId.slice(1); } let dpsPanel = null; let dpsInterval = null; let battleStartTime = null; let deadDpsMap = new Map(); let isInBattle = false; let isCollapsed = false; function createDpsPanel() { const oldPanel = document.getElementById('dps-panel'); if (oldPanel) oldPanel.remove(); dpsPanel = document.createElement('div'); dpsPanel.id = 'dps-panel'; dpsPanel.style.cssText = ` position: fixed; top: 100px; left: 20px; background: rgba(0,0,0,0.85); border: 2px solid #aa7a5a; border-radius: 10px; padding: 0; color: #ddd; width: 250px; z-index: 100000; font-family: monospace; backdrop-filter: blur(3px); user-select: none; box-shadow: 0 4px 15px rgba(0,0,0,0.5); display: flex; flex-direction: column; touch-action: none; /* 防止触摸时页面滚动 */ `; const header = document.createElement('div'); header.id = 'dps-panel-header'; header.style.cssText = ` padding: 6px 10px; background: #4a3f35; border-top-left-radius: 8px; border-top-right-radius: 8px; cursor: move; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #aa7a5a; font-weight: bold; color: #ffd966; `; header.innerHTML = ` 📊 Pokechill DPS统计 `; const content = document.createElement('div'); content.id = 'dps-panel-content'; content.style.cssText = ` padding: 10px; min-height: 50px; transition: all 0.2s ease; `; content.innerHTML = `
未进行战斗
`; dpsPanel.appendChild(header); dpsPanel.appendChild(content); document.body.appendChild(dpsPanel); // 拖动逻辑(同时支持鼠标和触摸) let isDragging = false; let offsetX = 0, offsetY = 0; function startDrag(e) { if (e.target.id === 'dps-collapse-btn') return; const clientX = e.clientX ?? (e.touches ? e.touches[0].clientX : 0); const clientY = e.clientY ?? (e.touches ? e.touches[0].clientY : 0); if (!clientX && !clientY) return; isDragging = true; const rect = dpsPanel.getBoundingClientRect(); offsetX = clientX - rect.left; offsetY = clientY - rect.top; dpsPanel.style.cursor = 'grabbing'; e.preventDefault(); } function onDrag(e) { if (!isDragging) return; const clientX = e.clientX ?? (e.touches ? e.touches[0].clientX : 0); const clientY = e.clientY ?? (e.touches ? e.touches[0].clientY : 0); if (!clientX && !clientY) return; let left = clientX - offsetX; let top = clientY - offsetY; left = Math.max(0, Math.min(left, window.innerWidth - dpsPanel.offsetWidth)); top = Math.max(0, Math.min(top, window.innerHeight - dpsPanel.offsetHeight)); dpsPanel.style.left = left + 'px'; dpsPanel.style.top = top + 'px'; e.preventDefault(); } function stopDrag() { isDragging = false; dpsPanel.style.cursor = 'default'; } // 鼠标事件 header.addEventListener('mousedown', startDrag); document.addEventListener('mousemove', onDrag); document.addEventListener('mouseup', stopDrag); // 触摸事件 header.addEventListener('touchstart', startDrag, { passive: false }); document.addEventListener('touchmove', onDrag, { passive: false }); document.addEventListener('touchend', stopDrag); document.addEventListener('touchcancel', stopDrag); // 折叠按钮(触摸和鼠标) const collapseBtn = document.getElementById('dps-collapse-btn'); collapseBtn.addEventListener('click', (e) => { e.stopPropagation(); toggleCollapse(); }); collapseBtn.addEventListener('touchstart', (e) => { e.stopPropagation(); toggleCollapse(); }, { passive: true }); } function toggleCollapse() { const content = document.getElementById('dps-panel-content'); const btn = document.getElementById('dps-collapse-btn'); if (!content || !btn) return; isCollapsed = !isCollapsed; if (isCollapsed) { content.style.display = 'none'; btn.innerHTML = '▶'; } else { content.style.display = 'block'; btn.innerHTML = '▼'; } } function startBattleTracking() { if (dpsInterval) clearInterval(dpsInterval); battleStartTime = performance.now(); deadDpsMap.clear(); isInBattle = true; const content = document.getElementById('dps-panel-content'); if (content && !isCollapsed) { content.innerHTML = `
总秒伤 0
`; } dpsInterval = setInterval(updateDpsPanel, 1000); } function stopTrackingKeepData() { if (dpsInterval) { clearInterval(dpsInterval); dpsInterval = null; } isInBattle = false; } function clearPanel() { stopTrackingKeepData(); const content = document.getElementById('dps-panel-content'); if (content && !isCollapsed) { content.innerHTML = `
未进行战斗
`; } deadDpsMap.clear(); } function updateDpsPanel() { if (!isInBattle) return; const now = performance.now(); const elapsed = (now - battleStartTime) / 1000; if (elapsed <= 0) return; let totalDamageAll = 0; // 所有宝可梦的伤害总和(包括死亡) let totalDamageAlive = 0; // 存活宝可梦的伤害总和(用于秒伤) const slots = []; for (let i = 1; i <= 6; i++) { const slot = `slot${i}`; if (!team[slot] || !team[slot].pkmn) continue; const pkmnObj = team[slot].pkmn; const isAlive = pkmnObj.playerHp > 0; const damage = team[slot].damageDealt || 0; totalDamageAll += damage; if (isAlive) { totalDamageAlive += damage; } if (!isAlive && !deadDpsMap.has(slot)) { const deathDps = damage / elapsed; deadDpsMap.set(slot, deathDps); } slots.push({ slot, pkmn: pkmnObj, damage: damage, name: getPokemonName(pkmnObj.id), alive: isAlive, fixedDps: deadDpsMap.get(slot) }); } const totalDps = totalDamageAlive / elapsed; slots.sort((a, b) => b.damage - a.damage); let html = ''; slots.forEach((s, idx) => { const percent = totalDamageAll ? ((s.damage / totalDamageAll) * 100).toFixed(1) : '0'; let dps; if (!s.alive && s.fixedDps !== undefined) { dps = s.fixedDps.toFixed(0); } else if (s.alive) { dps = (s.damage / elapsed).toFixed(0); } else { dps = '0'; } const status = s.alive ? '' : ' 💀'; html += `
${idx+1}. ${s.name}${status} ${formatNumber(s.damage)} ${dps}/s ${percent}%
`; }); const totalEl = document.getElementById('dps-total'); if (totalEl) totalEl.innerText = formatNumber(totalDps) + '/s'; const statsEl = document.getElementById('dps-stats'); if (statsEl) statsEl.innerHTML = html; } function formatNumber(num) { if (num >= 1e6) return (num / 1e6).toFixed(2) + 'M'; if (num >= 1e3) return (num / 1e3).toFixed(1) + 'k'; return num.toFixed(0); } // 劫持游戏函数 const originalInitArea = window.initialiseArea; window.initialiseArea = function(...args) { originalInitArea.apply(this, args); if (saved.currentArea && saved.currentArea !== undefined) { if (!isInBattle) startBattleTracking(); } }; const originalLeaveCombat = window.leaveCombat; window.leaveCombat = function(...args) { stopTrackingKeepData(); originalLeaveCombat.apply(this, args); }; const originalExitCombat = window.exitCombat; window.exitCombat = function(...args) { clearPanel(); originalExitCombat.apply(this, args); }; waitForGameReady(); })();