// ==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();
})();