// ==UserScript==
// @name [Pokechill] 伤害计算器
// @namespace https://play-pokechill.github.io/
// @version 1.1
// @description 计算伤害,支持复制到粘贴板
// @author 人民当家做主
// @icon https://play-pokechill.github.io/img/icons/icon.png
// @match https://play-pokechill.github.io/*
// @match https://g1tyx.github.io/play-pokechill/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
const MAX_WAIT = 15000;
const CHECK_INTERVAL = 300;
let waited = 0;
const interval = setInterval(() => {
if (document.readyState === 'complete' &&
typeof pkmn !== 'undefined' && typeof move !== 'undefined' &&
typeof ability !== 'undefined' && typeof item !== 'undefined' &&
typeof format !== 'undefined' && typeof openMenu !== 'undefined') {
clearInterval(interval);
initCalculator();
} else {
waited += CHECK_INTERVAL;
if (waited >= MAX_WAIT) {
clearInterval(interval);
console.warn('[伤害计算器] 游戏数据加载超时');
initCalculator();
}
}
}, CHECK_INTERVAL);
function initCalculator() {
const DICT = window.EN_CN_DICT || {};
function getChinese(englishId) {
if (!englishId) return '';
let formatted = format(englishId);
return DICT[englishId] || DICT[formatted] || formatted;
}
function calculateTypeEffectiveness(moveType, targetTypes) {
let effectiveness = 1;
for (const targetType of targetTypes) {
const typeChart = typeChartMap[moveType];
if (typeChart && typeChart[targetType] !== undefined) {
effectiveness *= typeChart[targetType];
} else {
effectiveness *= 1;
}
}
return effectiveness;
}
const typeChartMap = {
normal: { rock: 0.5, steel: 0.5, ghost: 0 },
fire: { fire: 0.5, water: 0.5, grass: 2, ice: 2, bug: 2, rock: 0.5, dragon: 0.5, steel: 2 },
water: { fire: 2, water: 0.5, grass: 0.5, ground: 2, rock: 2, dragon: 0.5 },
electric: { water: 2, electric: 0.5, grass: 0.5, ground: 0, flying: 2, dragon: 0.5 },
grass: { fire: 0.5, water: 2, grass: 0.5, poison: 0.5, ground: 2, flying: 0.5, bug: 0.5, rock: 2, dragon: 0.5, steel: 0.5 },
ice: { fire: 0.5, water: 0.5, grass: 2, ice: 0.5, ground: 2, flying: 2, dragon: 2, steel: 0.5 },
fighting: { normal: 2, ice: 2, poison: 0.5, flying: 0.5, psychic: 0.5, bug: 0.5, rock: 2, ghost: 0, dark: 2, fairy: 0.5, steel: 2 },
poison: { grass: 2, poison: 0.5, ground: 0.5, rock: 0.5, ghost: 0.5, steel: 0, fairy: 2 },
ground: { fire: 2, electric: 2, grass: 0.5, poison: 2, flying: 0, bug: 0.5, rock: 2, steel: 2 },
flying: { electric: 0.5, grass: 2, fighting: 2, bug: 2, rock: 0.5, steel: 0.5 },
psychic: { fighting: 2, poison: 2, psychic: 0.5, dark: 0, steel: 0.5, fairy: 0.5 },
bug: { fire: 0.5, grass: 2, fighting: 0.5, poison: 0.5, flying: 0.5, psychic: 2, ghost: 0.5, dark: 2, steel: 0.5, fairy: 0.5 },
rock: { fire: 2, ice: 2, fighting: 0.5, ground: 0.5, flying: 2, bug: 2, steel: 0.5 },
ghost: { normal: 0, psychic: 2, ghost: 2, dark: 0.5 },
dragon: { dragon: 2, steel: 0.5, fairy: 0 },
dark: { fighting: 0.5, psychic: 2, ghost: 2, dark: 0.5, fairy: 0.5 },
steel: { fire: 0.5, water: 0.5, electric: 0.5, ice: 2, rock: 2, steel: 0.5, fairy: 2 },
fairy: { fire: 0.5, fighting: 2, poison: 0.5, dragon: 2, dark: 2, steel: 0.5 },
stellar: { stellar: 0 }
};
function findIdByDisplayName(category, displayName) {
if (!displayName) return null;
let list;
if (category === 'pkmn') list = Object.keys(pkmn).filter(id => !pkmn[id].hidden);
else if (category === 'move') list = Object.keys(move);
else if (category === 'ability') list = Object.keys(ability);
else if (category === 'item') list = Object.keys(item).filter(id => item[id].type === 'held' && !item[id].hidden);
else return null;
const lowerDisplay = displayName.toLowerCase();
for (const id of list) {
const chinese = getChinese(id).toLowerCase();
if (chinese === lowerDisplay || id.toLowerCase() === lowerDisplay) return id;
}
return null;
}
function createDataListWithChinese(id, items, inputElement) {
const datalist = document.createElement('datalist');
datalist.id = id;
for (const key of items) {
const option = document.createElement('option');
option.value = getChinese(key);
option.dataset.id = key;
datalist.appendChild(option);
}
document.body.appendChild(datalist);
inputElement.addEventListener('input', function () {
const val = this.value;
const options = datalist.options;
for (let opt of options) {
if (opt.value === val) {
this.dataset.id = opt.dataset.id;
return;
}
}
const foundId = findIdByDisplayName(id.replace('-list-cn', ''), val);
if (foundId) {
this.dataset.id = foundId;
this.value = getChinese(foundId);
} else {
this.dataset.id = '';
}
});
inputElement.addEventListener('change', function () {
if (this.dataset.id) return;
const val = this.value;
const options = datalist.options;
for (let opt of options) {
if (opt.value === val) {
this.dataset.id = opt.dataset.id;
return;
}
}
});
}
function getItemMultiplierFormula(itemId) {
const it = item[itemId];
if (!it || typeof it.power !== 'function') return null;
const fnStr = it.power.toString();
const match = fnStr.match(/return\s+1\+\((\d+(?:\.\d+)?)\s*\*\s*returnItemLevel\(/);
if (match) {
const coeff = parseFloat(match[1]);
return (level) => 1 + coeff * level;
}
const directMatch = fnStr.match(/return\s+([\d.]+);?$/);
if (directMatch) {
const fixed = parseFloat(directMatch[1]);
return () => fixed;
}
return () => 1;
}
function getHiddenAbility(pkmnId) {
const pk = pkmn[pkmnId];
if (pk && pk.hiddenAbility) {
return pk.hiddenAbility.id;
}
return null;
}
function getWeatherTypeMult(weather, moveType) {
if (!weather) return 1;
switch (weather) {
case 'sunny':
if (moveType === 'fire') return 1.5;
if (moveType === 'water') return 0.5;
break;
case 'rainy':
if (moveType === 'water') return 1.5;
if (moveType === 'fire') return 0.5;
break;
case 'sandstorm':
if (moveType === 'rock' || moveType === 'ground') return 1.5;
break;
case 'hail':
if (moveType === 'ice') return 1.5;
break;
case 'electricTerrain':
if (moveType === 'electric' || moveType === 'steel') return 1.5;
break;
case 'grassyTerrain':
if (moveType === 'grass' || moveType === 'bug') return 1.5;
break;
case 'mistyTerrain':
if (moveType === 'fairy' || moveType === 'psychic') return 1.5;
break;
case 'foggy':
if (moveType === 'ghost' || moveType === 'dark') return 1.5;
break;
default: return 1;
}
return 1;
}
// ---------- 创建UI ----------
const panel = document.createElement('div');
panel.id = 'pokechill-damage-calc';
panel.innerHTML = `
`;
document.body.appendChild(panel);
// 样式调整:输入框/下拉框宽度减少1/3,统一样式
const style = document.createElement('style');
style.textContent = `
#pokechill-damage-calc {
position: fixed;
top: 80px;
right: 20px;
width: 540px;
max-width: 95%;
background: #3068b0;
border: 4px solid #f8d030;
border-radius: 16px;
box-shadow: 0 12px 30px rgba(0,0,0,0.8);
z-index: 10000;
color: #f8f8f8;
font-family: 'Winky Sans', 'Segoe UI', sans-serif;
font-size: 13px;
user-select: none;
backdrop-filter: blur(2px);
}
#pokechill-damage-calc .calc-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 14px;
background: #204080;
border-radius: 12px 12px 0 0;
cursor: move;
border-bottom: 2px solid #f8d030;
}
#pokechill-damage-calc .calc-title {
font-weight: bold;
color: #f8d030;
display: flex;
align-items: center;
gap: 6px;
font-size: 15px;
text-shadow: 2px 2px 0 #183060;
}
#pokechill-damage-calc .calc-header-buttons {
display: flex;
gap: 6px;
}
#pokechill-damage-calc .calc-toggle-btn {
background: #f8d030;
border: 2px solid #c0a020;
color: #204080;
font-size: 16px;
cursor: pointer;
padding: 4px 8px;
border-radius: 6px;
transition: 0.1s;
line-height: 1;
font-weight: bold;
}
#pokechill-damage-calc .calc-toggle-btn:hover {
background: #ffdf70;
border-color: #e0b020;
}
#pokechill-damage-calc .calc-body {
padding: 12px;
max-height: 70vh;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: #f8d030 #204080;
}
.calc-section {
background: rgba(32, 64, 128, 0.7);
border-radius: 10px;
margin-bottom: 10px;
border: 1px solid #f8d030;
}
.calc-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
padding: 10px;
}
.calc-field {
display: flex;
flex-direction: column;
gap: 2px;
}
.calc-field label {
display: flex;
align-items: center;
gap: 4px;
color: #f8e0a0;
font-size: 12px;
text-shadow: 1px 1px 0 #183060;
}
.field-icon {
font-size: 14px;
}
/* 统一输入框和下拉框样式,宽度减少1/3 */
input[type="text"], select {
width: 95%; /* 原95%的2/3 ≈63.3%,取整60% */
padding: 4px 6px;
background: #f0f0f0;
border: 2px solid #f8d030;
color: #204080;
border-radius: 4px;
font-size: 12px;
outline: none;
font-weight: bold;
}
input[type="text"]:focus, select:focus {
border-color: #ffb0b0;
box-shadow: 0 0 0 2px rgba(255,176,176,0.5);
}
.slider-group {
display: flex;
align-items: center;
gap: 6px;
width: 100%;
}
input[type="range"] {
flex: 1;
height: 6px;
background: #f8d030;
border-radius: 3px;
-webkit-appearance: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 16px;
height: 16px;
background: #f8f8f8;
border: 2px solid #204080;
border-radius: 50%;
cursor: pointer;
}
.slider-group span {
min-width: 20px;
text-align: center;
background: #f8d030;
padding: 1px 0;
border-radius: 3px;
color: #204080;
font-size: 12px;
font-weight: bold;
}
.checkbox-field {
justify-content: center;
flex-direction: row;
align-items: center;
}
.checkbox-field label {
display: flex;
align-items: center;
gap: 4px;
cursor: pointer;
}
input[type="checkbox"] {
width: 14px;
height: 14px;
accent-color: #f8d030;
}
.calc-actions {
display: flex;
gap: 8px;
justify-content: center;
margin: 10px 0 6px;
}
.calc-btn {
border: none;
padding: 6px 14px;
border-radius: 20px;
font-weight: bold;
font-size: 13px;
cursor: pointer;
transition: 0.15s;
display: inline-flex;
align-items: center;
gap: 4px;
border: 2px solid;
}
.calc-btn.primary {
background: #f8d030;
color: #204080;
border-color: #c0a020;
box-shadow: 0 3px 0 #a08010;
}
.calc-btn.primary:hover {
background: #ffdf70;
transform: translateY(-1px);
box-shadow: 0 4px 0 #a08010;
}
.calc-btn.primary:active {
transform: translateY(1px);
box-shadow: 0 2px 0 #a08010;
}
.calc-btn.secondary {
background: #c0c0c0;
color: #204080;
border-color: #a0a0a0;
box-shadow: 0 3px 0 #808080;
}
.calc-btn.secondary:hover {
background: #d0d0d0;
transform: translateY(-1px);
box-shadow: 0 4px 0 #808080;
}
.calc-result {
background: rgba(32, 64, 128, 0.8);
border-radius: 8px;
padding: 10px;
margin-top: 10px;
border: 2px solid #f8d030;
animation: fadeIn 0.3s;
}
.calc-result-title {
font-size: 13px;
font-weight: bold;
color: #f8d030;
margin-bottom: 8px;
text-align: center;
text-shadow: 1px 1px 0 #183060;
}
.calc-total {
font-size: 18px;
font-weight: bold;
text-align: center;
margin-top: 8px;
color: #f8f8f8;
background: #204080;
padding: 4px;
border-radius: 20px;
border: 1px solid #f8d030;
}
#calc-breakdown {
font-size: 11px;
line-height: 1.5;
color: #f0f0f0;
max-height: 150px;
overflow-y: auto;
padding-right: 4px;
}
#calc-breakdown div {
border-bottom: 1px dotted #f8d030;
padding: 2px 0;
}
#calc-breakdown span {
color: #f8d030;
font-weight: bold;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
@media (max-width: 600px) {
#pokechill-damage-calc { width: 95%; right: 2.5%; top: 60px; }
.calc-grid { grid-template-columns: 1fr; }
input[type="text"], select { width: 80%; } /* 移动端稍宽 */
}
`;
document.head.appendChild(style);
// 获取元素
const attackerInput = document.getElementById('calc-attacker');
const defenderInput = document.getElementById('calc-defender');
const moveInput = document.getElementById('calc-move');
const abilityInput = document.getElementById('calc-ability');
const itemInput = document.getElementById('calc-item');
const atkIvSlider = document.getElementById('calc-atk-iv');
const defIvSlider = document.getElementById('calc-def-iv');
const atkIvVal = document.getElementById('calc-atk-iv-val');
const defIvVal = document.getElementById('calc-def-iv-val');
const shinyCheck = document.getElementById('calc-shiny');
const crossCheck = document.getElementById('calc-cross');
const atkStage = document.getElementById('calc-atk-stage');
const satkStage = document.getElementById('calc-satk-stage');
const defStage = document.getElementById('calc-def-stage');
const sdefStage = document.getElementById('calc-sdef-stage');
const attackerStatus = document.getElementById('calc-attacker-status');
const weatherSelect = document.getElementById('calc-weather');
const toggleBtn = panel.querySelector('.calc-toggle-btn');
const copyBtn = document.getElementById('calc-copy-result');
const hiddenAbilityDisplay = document.getElementById('calc-hidden-ability-display');
// 滑块联动
atkIvSlider.addEventListener('input', () => { atkIvVal.innerText = atkIvSlider.value; });
defIvSlider.addEventListener('input', () => { defIvVal.innerText = defIvSlider.value; });
// 构建数据列表
const pkmnIds = Object.keys(pkmn).filter(id => !pkmn[id].hidden);
const moveIds = Object.keys(move);
const abilityIds = Object.keys(ability);
const itemIds = Object.keys(item).filter(id => item[id].type === 'held' && !item[id].hidden);
createDataListWithChinese('pkmn-list-cn', pkmnIds, attackerInput);
createDataListWithChinese('pkmn-list-cn', pkmnIds, defenderInput);
createDataListWithChinese('move-list-cn', moveIds, moveInput);
createDataListWithChinese('ability-list-cn', abilityIds, abilityInput);
createDataListWithChinese('item-list-cn', itemIds, itemInput);
// 攻击方输入变化时自动更新隐藏特性
attackerInput.addEventListener('change', function() {
const id = this.dataset.id;
if (id) {
const hiddenId = getHiddenAbility(id);
hiddenAbilityDisplay.textContent = hiddenId ? getChinese(hiddenId) : '无';
} else {
hiddenAbilityDisplay.textContent = '无';
}
});
// 拖动
let isDragging = false, offsetX, offsetY;
const header = panel.querySelector('.calc-header');
header.addEventListener('mousedown', (e) => {
if (e.target.tagName === 'BUTTON') return;
isDragging = true;
offsetX = e.clientX - panel.offsetLeft;
offsetY = e.clientY - panel.offsetTop;
panel.style.cursor = 'grabbing';
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
panel.style.left = (e.clientX - offsetX) + 'px';
panel.style.top = (e.clientY - offsetY) + 'px';
panel.style.right = 'auto';
});
document.addEventListener('mouseup', () => {
isDragging = false;
panel.style.cursor = 'default';
});
// 折叠/展开
let isCollapsed = false;
toggleBtn.addEventListener('click', () => {
isCollapsed = !isCollapsed;
const body = panel.querySelector('.calc-body');
const result = panel.querySelector('#calc-result');
if (isCollapsed) {
toggleBtn.textContent = '︾';
body.style.display = 'none';
if (result.style.display !== 'none') {
result.style.display = 'none';
result.__wasVisibleBeforeCollapse = true;
}
} else {
toggleBtn.textContent = '︽';
body.style.display = 'block';
if (result.__wasVisibleBeforeCollapse) {
result.style.display = 'block';
delete result.__wasVisibleBeforeCollapse;
}
}
});
// 重置
document.getElementById('calc-reset').addEventListener('click', () => {
attackerInput.value = ''; attackerInput.dataset.id = '';
defenderInput.value = ''; defenderInput.dataset.id = '';
moveInput.value = ''; moveInput.dataset.id = '';
abilityInput.value = ''; abilityInput.dataset.id = '';
itemInput.value = ''; itemInput.dataset.id = '';
atkIvSlider.value = 6; atkIvVal.innerText = '6';
defIvSlider.value = 6; defIvVal.innerText = '6';
shinyCheck.checked = false;
crossCheck.checked = false;
atkStage.value = '0';
satkStage.value = '0';
defStage.value = '0';
sdefStage.value = '0';
attackerStatus.value = 'none';
weatherSelect.value = '';
hiddenAbilityDisplay.textContent = '无';
document.getElementById('calc-result').style.display = 'none';
});
// 计算按钮
document.getElementById('calc-btn').addEventListener('click', () => {
const attackerId = attackerInput.dataset.id;
const defenderId = defenderInput.dataset.id;
const moveId = moveInput.dataset.id;
const abilityId = abilityInput.dataset.id;
const itemId = itemInput.dataset.id;
const itemLevel = 5; // 固定5级
const atkIv = parseInt(atkIvSlider.value, 10);
const defIv = parseInt(defIvSlider.value, 10);
const isShiny = shinyCheck.checked;
const isCross = crossCheck.checked;
const hiddenAbilityId = getHiddenAbility(attackerId);
// 更新隐藏特性显示(确保即使不触发change也能更新)
if (hiddenAbilityId) {
hiddenAbilityDisplay.textContent = getChinese(hiddenAbilityId);
} else {
hiddenAbilityDisplay.textContent = '无';
}
const atkStageVal = parseFloat(atkStage.value);
const satkStageVal = parseFloat(satkStage.value);
const defStageVal = parseFloat(defStage.value);
const sdefStageVal = parseFloat(sdefStage.value);
const attackerStatusVal = attackerStatus.value;
const weatherVal = weatherSelect.value;
const attacker = pkmn[attackerId];
const defender = pkmn[defenderId];
const moveObj = move[moveId];
if (!attacker || !defender || !moveObj) {
alert('请正确填写宝可梦和招式');
return;
}
if (moveObj.power === 0) {
alert('变化招式没有伤害');
return;
}
const abilityObj = ability[abilityId];
const hiddenAbilityObj = hiddenAbilityId ? ability[hiddenAbilityId] : null;
const split = moveObj.split;
const isPhysical = split === 'physical';
let attackerBaseStar = isPhysical ? attacker.bst.atk : attacker.bst.satk;
let defenderBaseStar = isPhysical ? defender.bst.def : defender.bst.sdef;
function getStageMult(val, isDef = false) {
if (val === 0) return 1;
if (val === 1) return isDef ? 2/3 : 1.5;
if (val === 2) return isDef ? 0.5 : 2;
if (val === -1) return isDef ? 1.5 : 2/3;
if (val === -2) return isDef ? 2 : 0.5;
return 1;
}
const atkStageMult = isPhysical ? getStageMult(atkStageVal) : 1;
const satkStageMult = !isPhysical ? getStageMult(satkStageVal) : 1;
const defStageMult = isPhysical ? getStageMult(defStageVal, true) : 1;
const sdefStageMult = !isPhysical ? getStageMult(sdefStageVal, true) : 1;
let attackerStar = attackerBaseStar * (isPhysical ? atkStageMult : satkStageMult);
let defenderStar = defenderBaseStar * (isPhysical ? defStageMult : sdefStageMult);
const atkFactor = Math.pow(1.1, atkIv);
const defFactor = Math.pow(1.1, defIv);
const attackerLevel = 100;
const levelMult = 1 + 100 * 0.1;
let baseDamage = moveObj.power + Math.max(0,
(attackerStar * 30 * atkFactor) - (defenderStar * 30 * defFactor)
);
let damage = baseDamage * levelMult;
let breakdown = [];
breakdown.push(`基础威力: ${moveObj.power}`);
breakdown.push(`攻击星: ${attackerBaseStar.toFixed(1)} x能力${(isPhysical ? atkStageVal : satkStageVal)} = ${attackerStar.toFixed(1)} (x30 x${atkFactor.toFixed(2)}) = ${(attackerStar * 30 * atkFactor).toFixed(0)}`);
breakdown.push(`防御星: ${defenderBaseStar.toFixed(1)} x能力${(isPhysical ? defStageVal : sdefStageVal)} = ${defenderStar.toFixed(1)} (x30 x${defFactor.toFixed(2)}) = ${(defenderStar * 30 * defFactor).toFixed(0)}`);
breakdown.push(`差值: ${(attackerStar * 30 * atkFactor - defenderStar * 30 * defFactor).toFixed(0)}`);
breakdown.push(`基础伤害: ${baseDamage.toFixed(0)}`);
breakdown.push(`等级系数 x${levelMult.toFixed(2)}`);
let totalMult = 1;
let multLog = '1';
// STAB
let stab = 1;
if (attacker.type.includes(moveObj.type)) {
stab = 1.5;
if (attacker.type.length === 1) stab += 0.2;
if (abilityObj && abilityObj.id === 'adaptability') stab = 2;
if (hiddenAbilityObj && hiddenAbilityObj.id === 'adaptability') stab = 2;
damage *= stab;
totalMult *= stab;
multLog += ` x${stab.toFixed(2)} (STAB)`;
breakdown.push(`STAB x${stab.toFixed(2)}`);
} else {
breakdown.push(`STAB x1 (无本系)`);
}
// 属性克制
let typeMult = calculateTypeEffectiveness(moveObj.type, defender.type);
damage *= typeMult;
totalMult *= typeMult;
multLog += ` x${typeMult.toFixed(2)} (克制)`;
breakdown.push(`属性克制 x${typeMult.toFixed(2)}`);
// 天气
const weatherMult = getWeatherTypeMult(weatherVal, moveObj.type);
if (weatherMult !== 1) {
damage *= weatherMult;
totalMult *= weatherMult;
multLog += ` x${weatherMult.toFixed(2)} (天气)`;
breakdown.push(`天气 x${weatherMult.toFixed(2)}`);
}
// 闪光
if (isShiny) {
damage *= 1.15;
totalMult *= 1.15;
multLog += ` x1.15 (闪光)`;
breakdown.push(`闪光 x1.15`);
}
// 交叉之力
if (isCross) {
damage *= 1.3;
totalMult *= 1.3;
multLog += ` x1.3 (交叉)`;
breakdown.push(`交叉之力 x1.3`);
}
// 攻击方状态
if (attackerStatusVal === 'burn' && isPhysical) {
damage /= 1.5;
totalMult /= 1.5;
multLog += ` ÷1.5 (烧伤)`;
breakdown.push(`烧伤 ÷1.5`);
}
if (attackerStatusVal === 'poison' && !isPhysical) {
damage /= 1.5;
totalMult /= 1.5;
multLog += ` ÷1.5 (中毒)`;
breakdown.push(`中毒 ÷1.5`);
}
// 道具
let itemMult = 1;
if (itemId) {
const formula = getItemMultiplierFormula(itemId);
if (formula) {
itemMult = formula(itemLevel);
if (itemMult > 1) {
damage *= itemMult;
totalMult *= itemMult;
multLog += ` x${itemMult.toFixed(2)} (道具)`;
breakdown.push(`道具 x${itemMult.toFixed(2)} (星级5)`);
} else {
breakdown.push(`道具: 无伤害加成`);
}
}
}
function applyAbility(abObj, label) {
if (!abObj) return 1;
let mult = 1;
let note = '';
switch (abObj.id) {
case 'hugePower': if (isPhysical) { mult = 2; note = '大力士'; } break;
case 'purePower': if (isPhysical) { mult = 2; note = '全力'; } break;
case 'technician': if (moveObj.power <= 60) { mult = 1.5; note = '技术高手'; } break;
case 'ironFist': if (moveObj.affectedBy && moveObj.affectedBy.includes('ironFist')) { mult = 1.5; note = '铁拳'; } break;
case 'strongJaw': if (moveObj.affectedBy && moveObj.affectedBy.includes('strongJaw')) { mult = 2; note = '强壮之颚'; } break;
case 'toughClaws': if (moveObj.affectedBy && moveObj.affectedBy.includes('toughClaws')) { mult = 2; note = '硬爪'; } break;
case 'sheerForce': if (moveObj.hitEffect) { mult = 1.25; note = '强行'; } break;
case 'reckless': if (moveObj.timer > 10) { mult = 1.5; note = '舍身'; } break;
case 'libero': if (moveObj.timer < 10) { mult = 2; note = '自由者'; } break;
case 'overgrow': if (moveObj.type === 'grass' && attacker.hp / attacker.hpMax < 0.5) { mult = 1.5; note = '茂盛'; } break;
case 'blaze': if (moveObj.type === 'fire' && attacker.hp / attacker.hpMax < 0.5) { mult = 1.5; note = '猛火'; } break;
case 'swarm': if (moveObj.type === 'bug' && attacker.hp / attacker.hpMax < 0.5) { mult = 1.5; note = '虫之预感'; } break;
case 'torrent': if (moveObj.type === 'water' && attacker.hp / attacker.hpMax < 0.5) { mult = 1.5; note = '激流'; } break;
default: break;
}
if (mult > 1) {
damage *= mult;
totalMult *= mult;
multLog += ` x${mult.toFixed(2)} (${label})`;
breakdown.push(`特性 ${label} ${note} x${mult.toFixed(2)}`);
} else {
breakdown.push(`特性 ${label}: 无伤害加成`);
}
return mult;
}
if (abilityObj) applyAbility(abilityObj, '可选特性');
if (hiddenAbilityObj) applyAbility(hiddenAbilityObj, '隐藏特性');
const finalDamage = Math.round(damage);
// 保存详细报告
window.__lastDamageReport = {
attacker: {
name: getChinese(attackerId),
level: 100,
split: isPhysical ? '物理' : '特殊',
baseStar: isPhysical ? attackerBaseStar : attackerBaseStar,
stage: isPhysical ? atkStageVal : satkStageVal,
stageMult: isPhysical ? atkStageMult : satkStageMult,
iv: isPhysical ? atkIv : atkIv,
ivFactor: isPhysical ? atkFactor : Math.pow(1.1, atkIv),
finalStat: attackerStar,
finalStatValue: (attackerStar * 30 * (isPhysical ? atkFactor : Math.pow(1.1, atkIv))).toFixed(0),
status: attackerStatusVal,
shiny: isShiny,
cross: isCross,
item: itemId ? { name: getChinese(itemId), level: itemLevel, mult: itemMult } : null,
abilityEffects: []
},
defender: {
name: getChinese(defenderId),
baseStar: isPhysical ? defenderBaseStar : defenderBaseStar,
stage: isPhysical ? defStageVal : sdefStageVal,
stageMult: isPhysical ? defStageMult : sdefStageMult,
iv: isPhysical ? defIv : defIv,
ivFactor: defFactor,
finalStat: defenderStar,
finalStatValue: (defenderStar * 30 * defFactor).toFixed(0)
},
move: {
name: getChinese(moveId),
power: moveObj.power,
type: moveObj.type
},
weather: weatherVal,
typeMult: typeMult,
stab: stab,
levelMult: levelMult,
baseDamage: baseDamage.toFixed(0),
damageBeforeMult: (damage / totalMult).toFixed(0),
totalMult: totalMult,
finalDamage: finalDamage
};
// 显示结果
if (isCollapsed) {
isCollapsed = false;
panel.querySelector('.calc-body').style.display = 'block';
const result = panel.querySelector('#calc-result');
if (result.__wasVisibleBeforeCollapse) {
result.style.display = 'block';
delete result.__wasVisibleBeforeCollapse;
}
toggleBtn.textContent = '︽';
}
document.getElementById('calc-result').style.display = 'block';
document.getElementById('calc-total').innerText = finalDamage.toLocaleString();
const breakdownDiv = document.getElementById('calc-breakdown');
breakdownDiv.innerHTML = '';
breakdown.forEach(line => {
const p = document.createElement('div');
p.innerHTML = line.replace(/x([\d.]+)/g, 'x$1');
breakdownDiv.appendChild(p);
});
const multLine = document.createElement('div');
multLine.style.marginTop = '6px';
multLine.style.borderTop = '1px dashed #f8d030';
multLine.style.paddingTop = '4px';
multLine.innerHTML = `总倍率: ${multLog} = ${totalMult.toFixed(3)}`;
breakdownDiv.appendChild(multLine);
});
// 复制详细报告
copyBtn.addEventListener('click', () => {
const r = window.__lastDamageReport;
if (!r || r.finalDamage === undefined) {
alert('请先计算伤害');
return;
}
const lines = [];
lines.push('══════════════════════════════════════════');
lines.push(' 宝可梦伤害计算详细报告');
lines.push('══════════════════════════════════════════');
lines.push('');
// 攻击方
lines.push('【攻击方】');
lines.push(` 宝可梦:${r.attacker.name} Lv.100(${r.attacker.split})`);
lines.push(` ├─ 基础星级:${r.attacker.baseStar.toFixed(1)} ★(种族值转化)`);
lines.push(` ├─ 能力等级:${r.attacker.stage} → 倍率 x${r.attacker.stageMult.toFixed(2)}`);
lines.push(` ├─ 个体值:${r.attacker.iv} 星 → 因子 x${r.attacker.ivFactor.toFixed(2)}`);
lines.push(` ├─ 最终攻击值 = 基础星级 × 能力倍率 × 30 × 个体因子`);
lines.push(` = ${r.attacker.baseStar.toFixed(1)} × ${r.attacker.stageMult.toFixed(2)} × 30 × ${r.attacker.ivFactor.toFixed(2)} = ${r.attacker.finalStatValue}`);
if (r.attacker.status !== 'none') {
const statusText = r.attacker.status === 'burn' ? '烧伤' : '中毒';
lines.push(` ├─ 状态:${statusText}(${r.attacker.split === '物理' ? '物理' : '特殊'}攻击伤害 ÷1.5)`);
}
if (r.attacker.shiny) lines.push(` ├─ 闪光:伤害 x1.15`);
if (r.attacker.cross) lines.push(` ├─ 交叉之力:伤害 x1.3`);
if (r.attacker.item) {
lines.push(` ├─ 携带道具:${r.attacker.item.name} 星级 5 → 伤害 x${r.attacker.item.mult.toFixed(2)}`);
}
if (r.attacker.abilityEffects.length) {
r.attacker.abilityEffects.forEach(ae => {
lines.push(` ├─ 特性:${ae.name}${ae.note ? '(' + ae.note + ')' : ''} → 伤害 x${ae.mult.toFixed(2)}`);
});
}
lines.push('');
// 防御方
lines.push('【防御方】');
lines.push(` 宝可梦:${r.defender.name}`);
lines.push(` ├─ 基础星级:${r.defender.baseStar.toFixed(1)} ★(种族值转化)`);
lines.push(` ├─ 能力等级:${r.defender.stage} → 倍率 x${r.defender.stageMult.toFixed(2)}`);
lines.push(` ├─ 个体值:${r.defender.iv} 星 → 因子 x${r.defender.ivFactor.toFixed(2)}`);
lines.push(` ├─ 最终防御值 = 基础星级 × 能力倍率 × 30 × 个体因子`);
lines.push(` = ${r.defender.baseStar.toFixed(1)} × ${r.defender.stageMult.toFixed(2)} × 30 × ${r.defender.ivFactor.toFixed(2)} = ${r.defender.finalStatValue}`);
lines.push('');
// 招式
lines.push('【招式】');
lines.push(` 名称:${r.move.name}`);
lines.push(` 威力:${r.move.power}`);
lines.push(` 属性:${r.move.type}`);
lines.push('');
// 基础伤害计算
lines.push('【基础伤害计算】');
const attackVal = parseInt(r.attacker.finalStatValue);
const defenseVal = parseInt(r.defender.finalStatValue);
const diff = Math.max(0, attackVal - defenseVal);
lines.push(` ① 威力差值 = max(0, 攻击方最终值 - 防御方最终值) = max(0, ${attackVal} - ${defenseVal}) = ${diff}`);
lines.push(` ② 基础伤害 = 招式威力 + 威力差值 = ${r.move.power} + ${diff} = ${r.baseDamage}`);
lines.push(` ③ 等级系数 = 1 + 等级 × 0.1 = 1 + 100 × 0.1 = ${r.levelMult.toFixed(2)}`);
lines.push(` ④ 基础值 = 基础伤害 × 等级系数 = ${r.baseDamage} × ${r.levelMult.toFixed(2)} = ${r.damageBeforeMult}`);
lines.push('');
// 倍率明细
lines.push('【伤害倍率明细】');
lines.push(` • STAB(本系加成):x${r.stab.toFixed(2)}${r.stab > 1 ? '(同属性威力提升)' : '(无本系)'}`);
lines.push(` • 属性克制:x${r.typeMult.toFixed(2)}`);
if (r.weather) {
const weatherNames = { sunny: '大晴天', rainy: '下雨', sandstorm: '沙暴', hail: '冰雹', electricTerrain: '电气场地', grassyTerrain: '青草场地', mistyTerrain: '薄雾场地', foggy: '起雾' };
const wMult = getWeatherTypeMult(r.weather, r.move.type);
if (wMult !== 1) lines.push(` • 天气(${weatherNames[r.weather] || r.weather}):x${wMult.toFixed(2)}`);
}
if (r.attacker.status === 'burn' && r.attacker.split === '物理') lines.push(` • 烧伤(物理攻击减伤):÷1.5`);
if (r.attacker.status === 'poison' && r.attacker.split === '特殊') lines.push(` • 中毒(特殊攻击减伤):÷1.5`);
if (r.attacker.shiny) lines.push(` • 闪光:x1.15`);
if (r.attacker.cross) lines.push(` • 交叉之力:x1.3`);
if (r.attacker.item) lines.push(` • 道具(${r.attacker.item.name}):x${r.attacker.item.mult.toFixed(2)}`);
r.attacker.abilityEffects.forEach(ae => {
lines.push(` • 特性 ${ae.name}:x${ae.mult.toFixed(2)}`);
});
lines.push('');
// 总倍率与最终伤害
lines.push('【计算结果】');
lines.push(` 总倍率 = 上述所有倍率连乘 = ${r.totalMult.toFixed(3)}`);
lines.push(` 最终伤害 = 基础值 × 总倍率 = ${r.damageBeforeMult} × ${r.totalMult.toFixed(3)} = ${r.finalDamage.toLocaleString()}`);
lines.push('');
lines.push('══════════════════════════════════════════');
navigator.clipboard.writeText(lines.join('\n')).then(() => {
alert('详细报告已复制到剪贴板');
}).catch(() => {
alert('复制失败,请手动复制');
});
});
console.log('[伤害计算器v2.6] 已加载');
}
})();