// ==UserScript==
// @name [Pokechill] 伤害计算器
// @namespace https://play-pokechill.github.io/
// @version 1.2
// @description 计算伤害
// @author 人民当家做主 (修正 by GPT)
// @license MIT
// @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);
// 默认出招时间(来自游戏)
const defaultPlayerMoveTimer = typeof window.defaultPlayerMoveTimer !== 'undefined' ? window.defaultPlayerMoveTimer : 2;
function initCalculator() {
console.log('[伤害计算器] 初始化...');
// 汉化字典
const DICT = window.EN_CN_DICT || {};
// 获取中文名称(与第一个脚本完全一致)
function getChinese(englishId) {
if (!englishId) return '';
let formatted = format(englishId);
return DICT[englishId] || DICT[formatted] || formatted;
}
// 根据显示名称查找ID(用于输入匹配)
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;
}
// 创建带中文选项的datalist(与第一个脚本一致)
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', ''), val); // 注意ID后缀可能不同,这里假设为 '-list'
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 getStageMult(val, isDef = false) {
if (val === 0) return 1;
if (val === 1) return isDef ? 1.5 : 1.5;
if (val === 2) return isDef ? 2 : 2;
if (val === -1) return isDef ? 2/3 : 2/3;
if (val === -2) return isDef ? 0.5 : 0.5;
return 1;
}
// 道具倍率直接调用 power 方法
function getItemMultiplier(itemId, level) {
const it = item[itemId];
if (!it || typeof it.power !== 'function') return 1;
return it.power(level);
}
// 获取隐藏特性
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;
}
// 属性克制表(严格按照Pokechill:克制1.5倍,抵抗0.5倍,免疫0)
const typeChartMap = {
normal: { rock: 0.5, steel: 0.5, ghost: 0 },
fire: { fire: 0.5, water: 0.5, grass: 1.5, ice: 1.5, bug: 1.5, rock: 0.5, dragon: 0.5, steel: 1.5 },
water: { fire: 1.5, water: 0.5, grass: 0.5, ground: 1.5, rock: 1.5, dragon: 0.5 },
electric: { water: 1.5, electric: 0.5, grass: 0.5, ground: 0, flying: 1.5, dragon: 0.5 },
grass: { fire: 0.5, water: 1.5, grass: 0.5, poison: 0.5, ground: 1.5, flying: 0.5, bug: 0.5, rock: 1.5, dragon: 0.5, steel: 0.5 },
ice: { fire: 0.5, water: 0.5, grass: 1.5, ice: 0.5, ground: 1.5, flying: 1.5, dragon: 1.5, steel: 0.5 },
fighting: { normal: 1.5, ice: 1.5, poison: 0.5, flying: 0.5, psychic: 0.5, bug: 0.5, rock: 1.5, ghost: 0, dark: 1.5, fairy: 0.5, steel: 1.5 },
poison: { grass: 1.5, poison: 0.5, ground: 0.5, rock: 0.5, ghost: 0.5, steel: 0, fairy: 1.5 },
ground: { fire: 1.5, electric: 1.5, grass: 0.5, poison: 1.5, flying: 0, bug: 0.5, rock: 1.5, steel: 1.5 },
flying: { electric: 0.5, grass: 1.5, fighting: 1.5, bug: 1.5, rock: 0.5, steel: 0.5 },
psychic: { fighting: 1.5, poison: 1.5, psychic: 0.5, dark: 0, steel: 0.5, fairy: 0.5 },
bug: { fire: 0.5, grass: 1.5, fighting: 0.5, poison: 0.5, flying: 0.5, psychic: 1.5, ghost: 0.5, dark: 1.5, steel: 0.5, fairy: 0.5 },
rock: { fire: 1.5, ice: 1.5, fighting: 0.5, ground: 0.5, flying: 1.5, bug: 1.5, steel: 0.5 },
ghost: { normal: 0, psychic: 1.5, ghost: 1.5, dark: 0.5 },
dragon: { dragon: 1.5, steel: 0.5, fairy: 0 },
dark: { fighting: 0.5, psychic: 1.5, ghost: 1.5, dark: 0.5, fairy: 0.5 },
steel: { fire: 0.5, water: 0.5, electric: 0.5, ice: 1.5, rock: 1.5, steel: 0.5, fairy: 1.5 },
fairy: { fire: 0.5, fighting: 1.5, poison: 0.5, dragon: 1.5, dark: 1.5, steel: 0.5 },
stellar: { stellar: 0 }
};
// 属性克制计算(加入免疫覆盖、冰冻干燥、特性影响)
function calculateTypeEffectiveness(moveType, targetTypes, defenderAbility, context) {
let effectiveness = 1;
for (const targetType of targetTypes) {
const typeChart = typeChartMap[moveType];
if (typeChart && typeChart[targetType] !== undefined) {
effectiveness *= typeChart[targetType];
}
}
// 冰冻干燥对水系额外×1.5(游戏内为1.5)
if (moveType === 'ice' && targetTypes.includes('water')) {
effectiveness *= 1.5;
}
// 免疫覆盖(训练场或无怜悯)
const noImmunities = context.area === 'training' || context.area === 'spiralingTower';
const fieldNoMercy = context.fieldEffect?.includes('noMercy');
if (effectiveness === 0 && (noImmunities || fieldNoMercy)) {
// 重新计算,将0替换为0.5
effectiveness = 1;
for (const targetType of targetTypes) {
let eff = typeChartMap[moveType]?.[targetType] ?? 1;
if (eff === 0) eff = 0.5;
effectiveness *= eff;
}
}
// 特性影响
if (defenderAbility) {
if (defenderAbility.id === 'scrappy' && targetTypes.includes('ghost') && (moveType === 'fighting' || moveType === 'normal')) {
effectiveness = 1;
}
if (defenderAbility.id === 'tintedLens' && effectiveness < 1 && effectiveness > 0) {
effectiveness = 1;
}
if (defenderAbility.id === 'noGuard' && effectiveness === 0) {
effectiveness = 1;
}
if (defenderAbility.id === 'thousandArms') {
effectiveness = 1.5;
}
}
// 反转场地(如果需要)
if (context.fieldEffect?.includes('reverseField')) {
const map = {
0.25: 2.25, 0.5: 1.5, 0.75: 1.25,
1.25: 0.75, 1.5: 0.5, 2.25: 0.25
};
if (map[effectiveness] !== undefined) effectiveness = map[effectiveness];
}
return effectiveness;
}
// 特性应用函数
function applyAbility(abilityObj, context) {
if (!abilityObj) return { mult: 1, note: '' };
const { isPhysical, moveObj, attacker, defender, movePower,
attackerHPPercent, defenderHPPercent,
defenderHasStatus, faintedAllies, weather, isCritical } = context;
const abId = abilityObj.id;
let mult = 1;
let note = '';
// ----- 攻击方增益 -----
if (abId === 'hugePower' || abId === 'purePower') {
if (isPhysical) { mult = 2; note = '大力士/全力'; }
}
else if (abId === 'technician' && movePower <= 60) {
mult = 1.5; note = '技术高手';
}
else if (abId === 'ironFist' && moveObj.affectedBy?.includes('ironFist')) {
mult = 1.5; note = '铁拳';
}
else if (abId === 'strongJaw' && moveObj.affectedBy?.includes('strongJaw')) {
mult = 2; note = '强壮之颚';
}
else if (abId === 'toughClaws' && moveObj.affectedBy?.includes('toughClaws')) {
mult = 2; note = '硬爪';
}
else if (abId === 'sharpness' && moveObj.affectedBy?.includes('sharpness')) {
mult = 1.5; note = '锋锐';
}
else if (abId === 'megaLauncher' && moveObj.affectedBy?.includes('megaLauncher')) {
mult = 1.5; note = ' Mega 发射器';
}
else if (abId === 'reckless' && moveObj.timer > defaultPlayerMoveTimer) {
mult = 1.5; note = '舍身';
}
else if (abId === 'libero' && moveObj.timer < defaultPlayerMoveTimer) {
mult = 2; note = '自由者';
}
else if (abId === 'sheerForce' && moveObj.hitEffect) {
mult = 1.25; note = '强行';
}
else if (['overgrow','blaze','swarm','torrent','bastion','average',
'resolve','mistify','hexerei','glimmer','skyward',
'draconic','noxious','solid','rime','voltage'].includes(abId)) {
const typeMap = {
overgrow:'grass', blaze:'fire', swarm:'bug', torrent:'water',
bastion:'steel', average:'normal', resolve:'fighting',
mistify:'psychic', hexerei:'ghost', glimmer:'fairy',
skyward:'flying', draconic:'dragon', noxious:'poison',
solid:'rock', rime:'ice', voltage:'electric'
};
if (moveObj.type === typeMap[abId] && attackerHPPercent < 0.5) {
mult = 1.3; note = '激流/猛火等';
}
}
else if (abId === 'rivalry') {
if (attacker.type.some(t => defender.type.includes(t))) {
mult = 1.5; note = '斗争心';
}
}
else if (abId === 'toxicBoost' && attacker.status === 'poison' && isPhysical) {
mult = 1.2; note = '剧毒boost';
}
else if (abId === 'flareBoost' && attacker.status === 'burn' && !isPhysical) {
mult = 1.2; note = '热暴走';
}
else if (abId === 'merciless') {
const isNerf = (typeof window.testAbility === 'function' && window.testAbility('active', abId) === 'nerf');
if (defenderHasStatus) {
mult = isNerf ? 1.35 : 1.5; note = '不仁不义';
}
}
else if (abId === 'supremeOverlord') {
mult = 1 + 0.15 * faintedAllies; note = '至尊将领';
}
else if (abId === 'gorillaTactics') {
const isNerf = (typeof window.testAbility === 'function' && window.testAbility('active', abId) === 'nerf');
mult = isNerf ? 1.35 : 1.5; note = '大力士/强壮之颚?';
}
else if (abId === 'parentalBond') {
mult = 1.5; note = '亲子爱';
}
else if (abId === 'soulAsterism') {
mult = 1 + 0.1 * (faintedAllies === 0 ? 5 : 6 - faintedAllies);
note = '魂星群';
}
// ----- 皮肤特性 -----
const ateAbilities = {
ferrilate:'steel', refrigerate:'ice', terralate:'ground',
toxilate:'poison', hydrolate:'water', pyrolate:'fire',
chrysilate:'bug', galvanize:'electric', gloomilate:'dark',
espilate:'psychic', aerilate:'flying', pixilate:'fairy',
dragonMaw:'dragon', verdify:'grass'
};
if (ateAbilities[abId] && moveObj.type === 'normal') {
context.changedType = ateAbilities[abId];
mult = 1.3; note = '皮肤';
}
if (abId === 'normalize' && moveObj.type !== 'normal') {
context.changedType = 'normal';
mult = 1.3; note = '一般皮肤';
}
// ----- 防御方减伤 -----
if (['filter','prismArmor','solidRock'].includes(abId)) {
const typeMult = context.typeEffectiveness;
if (typeMult > 1) {
mult = 0.75; note = '过滤/棱镜/坚石';
}
}
else if (abId === 'multiscale' && defenderHPPercent >= 0.99) {
mult = 0.5; note = '多重鳞片';
}
else if (abId === 'wonderGuard') {
if (context.typeEffectiveness <= 1) {
mult = 0.2; note = '神奇守护';
}
}
else if (abId === 'levitate' && moveObj.type === 'ground') {
mult = 0; note = '浮游';
}
else if (abId === 'thickFat' && (moveObj.type === 'fire' || moveObj.type === 'ice')) {
mult = 0.5; note = '厚脂肪';
}
else if (abId === 'flashFire' && moveObj.type === 'fire') {
mult = 0; note = '引火';
}
else if (abId === 'waterAbsorb' && moveObj.type === 'water') {
mult = 0; note = '储水';
}
else if (abId === 'voltAbsorb' && moveObj.type === 'electric') {
mult = 0; note = '蓄电';
}
else if (abId === 'lightningRod' && moveObj.type === 'electric') {
mult = 0; note = '避雷针';
}
else if (abId === 'motorDrive' && moveObj.type === 'electric') {
mult = 0; note = '电气引擎';
}
else if (abId === 'sapSipper' && moveObj.type === 'grass') {
mult = 0; note = '食草';
}
else if (abId === 'stormDrain' && moveObj.type === 'water') {
mult = 0; note = '引水';
}
if (mult !== 1) {
return { mult, note };
}
return { mult: 1, note: '' };
}
// ---------- 创建UI ----------
const panel = document.createElement('div');
panel.id = 'pokechill-damage-calc';
panel.innerHTML = `
`;
document.body.appendChild(panel);
// 样式
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;
}
input[type="text"], select {
width: 95%;
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', pkmnIds, attackerInput);
createDataListWithChinese('pkmn-list', pkmnIds, defenderInput);
createDataListWithChinese('move-list', moveIds, moveInput);
createDataListWithChinese('ability-list', abilityIds, abilityInput);
createDataListWithChinese('item-list', 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 = '';
document.getElementById('calc-attacker-hp').value = 100;
document.getElementById('calc-attacker-hp-val').innerText = '100';
document.getElementById('calc-defender-hp').value = 100;
document.getElementById('calc-defender-hp-val').innerText = '100';
document.getElementById('calc-defender-status').checked = false;
document.getElementById('calc-fainted-allies').value = 0;
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;
const atkIv = parseInt(atkIvSlider.value, 10);
const defIv = parseInt(defIvSlider.value, 10);
const isShiny = shinyCheck.checked;
const isCross = crossCheck.checked;
const hiddenAbilityId = getHiddenAbility(attackerId);
const attackerHPPercent = parseFloat(document.getElementById('calc-attacker-hp').value) / 100;
const defenderHPPercent = parseFloat(document.getElementById('calc-defender-hp').value) / 100;
const defenderHasStatus = document.getElementById('calc-defender-status').checked;
const faintedAllies = parseInt(document.getElementById('calc-fainted-allies').value, 10) || 0;
// 使用汉化显示隐藏特性名称
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;
const atkStageMult = getStageMult(atkStageVal, false);
const satkStageMult = getStageMult(satkStageVal, false);
const defStageMult = getStageMult(defStageVal, true);
const sdefStageMult = getStageMult(sdefStageVal, true);
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';
// 上下文
const context = {
isPhysical, moveObj, attacker, defender, movePower: moveObj.power,
attackerHPPercent, defenderHPPercent,
defenderHasStatus, faintedAllies, weather: weatherVal,
typeEffectiveness: 1,
area: '', fieldEffect: null,
adaptabilitySTAB: false,
changedType: null
};
// 应用特性
let abilityMult = 1;
if (abilityObj) {
const res = applyAbility(abilityObj, context);
abilityMult *= res.mult;
if (res.note) breakdown.push(`特性 ${res.note} x${res.mult.toFixed(2)}`);
if (context.changedType) moveObj.type = context.changedType;
}
if (hiddenAbilityObj) {
const res = applyAbility(hiddenAbilityObj, context);
abilityMult *= res.mult;
if (res.note) breakdown.push(`隐藏特性 ${res.note} x${res.mult.toFixed(2)}`);
if (context.changedType) moveObj.type = context.changedType;
}
// 属性克制
let typeMult = calculateTypeEffectiveness(moveObj.type, defender.type, hiddenAbilityObj || abilityObj, context);
context.typeEffectiveness = typeMult;
damage *= typeMult;
totalMult *= typeMult;
multLog += ` x${typeMult.toFixed(2)} (克制)`;
breakdown.push(`属性克制 x${typeMult.toFixed(2)}`);
// STAB
let stab = 1;
if (attacker.type.includes(moveObj.type)) {
stab = 1.5; // 基础本系加成
if (attacker.type.length === 1) stab += 0.2; // 单属性额外 +0.2
// 适应力特性:额外 +0.2(只需判断普通或隐藏是否拥有适应力)
const hasAdaptability = (abilityObj && abilityObj.id === 'adaptability') ||
(hiddenAbilityObj && hiddenAbilityObj.id === 'adaptability');
if (hasAdaptability) stab += 0.2;
damage *= stab;
totalMult *= stab;
multLog += ` x${stab.toFixed(2)} (STAB)`;
breakdown.push(`STAB x${stab.toFixed(2)}`);
} else {
breakdown.push(`STAB x1 (无本系)`);
}
// 天气
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) {
itemMult = getItemMultiplier(itemId, itemLevel);
if (itemMult > 1) {
damage *= itemMult;
totalMult *= itemMult;
multLog += ` x${itemMult.toFixed(2)} (道具)`;
breakdown.push(`道具 x${itemMult.toFixed(2)} (星级5)`);
} else {
breakdown.push(`道具: 无伤害加成`);
}
}
// 应用特性总倍率
damage *= abilityMult;
totalMult *= abilityMult;
const finalDamage = Math.round(damage);
// 显示结果
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', () => {
alert('复制功能暂未实现,请手动复制结果。');
});
console.log('[伤害计算器v2.1] 初始化完成,遵循Pokechill 1.5倍克制规则,汉化已启用');
}
})();