// ==UserScript== // @name [Pokechill] 伤害计算器 // @namespace https://play-pokechill.github.io/ // @version 1.4 // @description 不好用来骂我 // @author 人民当家做主 // @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); 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 ? 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; } // 道具倍率直接调用 power 方法 function getItemMultiplier(itemId, level) { const it = item[itemId]; if (!it || typeof it.power !== 'function') return 1; return it.power(level); } // 获取隐藏特性ID(无论是否解锁) function getHiddenAbilityId(pkmnId) { const pk = pkmn[pkmnId]; if (pk && pk.hiddenAbility) { return pk.hiddenAbility.id; } return null; } // 检查隐藏特性是否已解锁 function isHiddenAbilityUnlocked(pkmnId) { const pk = pkmn[pkmnId]; return !!(pk && pk.hiddenAbilityUnlocked); } // 天气倍率(与游戏一致) 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, defenderHiddenAbility, context) { let effectiveness = 1; for (const targetType of targetTypes) { const typeChart = typeChartMap[moveType]; if (typeChart && typeChart[targetType] !== undefined) { effectiveness *= typeChart[targetType]; } } 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)) { effectiveness = 1; for (const targetType of targetTypes) { let eff = typeChartMap[moveType]?.[targetType] ?? 1; if (eff === 0) eff = 0.5; effectiveness *= eff; } } function applyAbilityToEffectiveness(abObj) { if (!abObj) return; if (abObj.id === 'scrappy' && targetTypes.includes('ghost') && (moveType === 'fighting' || moveType === 'normal')) { effectiveness = 1; } if (abObj.id === 'tintedLens' && effectiveness < 1 && effectiveness > 0) { effectiveness = 1; } if (abObj.id === 'noGuard' && effectiveness === 0) { effectiveness = 1; } if (abObj.id === 'thousandArms') { effectiveness = 1.5; } } if (defenderAbility) applyAbilityToEffectiveness(defenderAbility); if (defenderHiddenAbility) applyAbilityToEffectiveness(defenderHiddenAbility); 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 { finalMult: 1, note: '' }; const { isPhysical, moveObj, attacker, defender, attackerHPPercent, defenderHPPercent, defenderHasStatus, faintedAllies, weather } = context; const abId = abilityObj.id; let finalMult = 1; let note = ''; // ----- 威力加成类特性(直接修改 context.movePower) ----- const powerMultAbilities = { technician: { condition: context.movePower <= 60, mult: 1.5, note: '技术高手' }, ironFist: { condition: moveObj.affectedBy?.includes('ironFist'), mult: 1.5, note: '铁拳' }, strongJaw: { condition: moveObj.affectedBy?.includes('strongJaw'), mult: 2, note: '强壮之颚' }, toughClaws: { condition: moveObj.affectedBy?.includes('toughClaws'), mult: 2, note: '硬爪' }, sharpness: { condition: moveObj.affectedBy?.includes('sharpness'), mult: 1.5, note: '锋锐' }, megaLauncher: { condition: moveObj.affectedBy?.includes('megaLauncher'), mult: 1.5, note: 'Mega发射器' }, reckless: { condition: moveObj.timer > defaultPlayerMoveTimer, mult: 1.5, note: '舍身' }, libero: { condition: moveObj.timer < defaultPlayerMoveTimer, mult: 2, note: '自由者' } }; if (powerMultAbilities[abId] && powerMultAbilities[abId].condition) { context.movePower *= powerMultAbilities[abId].mult; note = powerMultAbilities[abId].note; return { finalMult: 1, 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]; context.movePower *= 1.3; note = '皮肤'; return { finalMult: 1, note }; } if (abId === 'normalize' && moveObj.type !== 'normal') { context.changedType = 'normal'; context.movePower *= 1.3; note = '一般皮肤'; return { finalMult: 1, note }; } // ----- 最终倍率类特性 ----- if (abId === 'hugePower' || abId === 'purePower') { if (isPhysical) { finalMult = 2; note = '大力士/全力'; } } else if (abId === 'adaptability' && attacker.type.includes(moveObj.type)) { context.adaptabilityActive = true; // 使用新标志 return { powerMult: 1, finalMult: 1, 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) { finalMult = 1.3; note = '激流/猛火等'; } } else if (abId === 'rivalry') { if (attacker.type.some(t => defender.type.includes(t))) { finalMult = 1.5; note = '斗争心'; } } else if (abId === 'toxicBoost' && attacker.status === 'poison' && isPhysical) { finalMult = 1.2; note = '剧毒boost'; } else if (abId === 'flareBoost' && attacker.status === 'burn' && !isPhysical) { finalMult = 1.2; note = '热暴走'; } else if (abId === 'merciless') { const isNerf = (typeof window.testAbility === 'function' && window.testAbility('active', abId) === 'nerf'); if (defenderHasStatus) { finalMult = isNerf ? 1.35 : 1.5; note = '不仁不义'; } } else if (abId === 'supremeOverlord') { finalMult = 1 + 0.15 * faintedAllies; note = '至尊将领'; } else if (abId === 'gorillaTactics') { const isNerf = (typeof window.testAbility === 'function' && window.testAbility('active', abId) === 'nerf'); finalMult = isNerf ? 1.35 : 1.5; note = '大力士/强壮之颚?'; } else if (abId === 'parentalBond') { finalMult = 1.5; note = '亲子爱'; } else if (abId === 'soulAsterism') { finalMult = 1 + 0.1 * (faintedAllies === 0 ? 5 : 6 - faintedAllies); note = '魂星群'; } // ----- 防御方减伤 ----- else if (['filter','prismArmor','solidRock'].includes(abId)) { const typeMult = context.typeEffectiveness; if (typeMult > 1) { finalMult = 0.75; note = '过滤/棱镜/坚石'; } } else if (abId === 'multiscale' && defenderHPPercent >= 0.99) { finalMult = 0.5; note = '多重鳞片'; } else if (abId === 'wonderGuard') { if (context.typeEffectiveness <= 1) { finalMult = 0.2; note = '神奇守护'; } } else if (abId === 'levitate' && moveObj.type === 'ground') { finalMult = 0; note = '浮游'; } else if (abId === 'thickFat' && (moveObj.type === 'fire' || moveObj.type === 'ice')) { finalMult = 0.5; note = '厚脂肪'; } else if (abId === 'flashFire' && moveObj.type === 'fire') { finalMult = 0; note = '引火'; } else if (abId === 'waterAbsorb' && moveObj.type === 'water') { finalMult = 0; note = '储水'; } else if (abId === 'voltAbsorb' && moveObj.type === 'electric') { finalMult = 0; note = '蓄电'; } else if (abId === 'lightningRod' && moveObj.type === 'electric') { finalMult = 0; note = '避雷针'; } else if (abId === 'motorDrive' && moveObj.type === 'electric') { finalMult = 0; note = '电气引擎'; } else if (abId === 'sapSipper' && moveObj.type === 'grass') { finalMult = 0; note = '食草'; } else if (abId === 'stormDrain' && moveObj.type === 'water') { finalMult = 0; note = '引水'; } return { finalMult, note }; } // ---------- 创建UI(攻击/防御分区)---------- const panel = document.createElement('div'); panel.id = 'pokechill-damage-calc'; panel.innerHTML = `
伤害计算器
⚔️ 攻击方
6
星级5(固定)
100
🛡️ 防御方
6
100
🌍 战斗环境
`; document.body.appendChild(panel); // 样式 const style = document.createElement('style'); style.textContent = ` #pokechill-damage-calc { position: fixed; top: 80px; right: 20px; width: 560px; 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: 12px; border: 1px solid #f8d030; padding-bottom: 6px; } .section-title { background: #204080; color: #f8d030; padding: 4px 10px; border-radius: 8px 8px 0 0; font-weight: bold; border-bottom: 1px solid #f8d030; margin-bottom: 8px; } .calc-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; padding: 0 10px 10px 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, input[type="number"] { width: 100%; padding: 4px 6px; background: #f0f0f0; border: 2px solid #f8d030; color: #204080; border-radius: 4px; font-size: 12px; outline: none; font-weight: bold; box-sizing: border-box; } input[type="text"]:focus, select:focus, input[type="number"]: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; } .hidden-ability-box { background: #404040; color: #f8d030; padding: 4px 8px; border-radius: 4px; border: 1px solid #f8d030; font-size: 12px; } .item-level-badge { background: #f8d030; color: #204080; padding: 4px 8px; border-radius: 4px; font-weight: bold; font-size: 12px; } .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: 100%; } } `; 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 = getHiddenAbilityId(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'; window.__lastDamageReport = null; }); // 计算按钮 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 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; 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 inputAbilityObj = abilityId ? ability[abilityId] : null; const hiddenAbilityId = getHiddenAbilityId(attackerId); const hiddenAbilityObj = (hiddenAbilityId && isHiddenAbilityUnlocked(attackerId)) ? ability[hiddenAbilityId] : null; // 防御方特性 const defenderAbilityObj = defender.ability ? ability[defender.ability] : null; const defenderHiddenId = getHiddenAbilityId(defenderId); const defenderHiddenObj = (defenderHiddenId && isHiddenAbilityUnlocked(defenderId)) ? ability[defenderHiddenId] : 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 atkFactor = Math.pow(1.1, atkIv); const defFactor = Math.pow(1.1, defIv); const attackerLevel = 100; const levelMult = 1 + 100 * 0.1; // 初始化上下文,包含可修改的 movePower 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 breakdown = []; let abilityFinalMult = 1; // 用于累积最终倍率 let abilityEffects = []; // 记录最终倍率特性 let powerEffectNotes = []; // 记录威力加成特性 // 应用攻击方手动特性 if (inputAbilityObj) { const res = applyAbility(inputAbilityObj, context); if (res.note) { if (res.finalMult !== 1) { abilityFinalMult *= res.finalMult; abilityEffects.push({ name: getChinese(inputAbilityObj.id), note: res.note, mult: res.finalMult }); } else { powerEffectNotes.push({ name: getChinese(inputAbilityObj.id), note: res.note }); } } if (context.changedType) moveObj.type = context.changedType; } // 应用攻击方隐藏特性 if (hiddenAbilityObj) { const res = applyAbility(hiddenAbilityObj, context); if (res.note) { if (res.finalMult !== 1) { abilityFinalMult *= res.finalMult; abilityEffects.push({ name: getChinese(hiddenAbilityObj.id), note: res.note, mult: res.finalMult }); } else { powerEffectNotes.push({ name: getChinese(hiddenAbilityObj.id), note: res.note }); } } if (context.changedType) moveObj.type = context.changedType; } // 基础伤害计算(使用修改后的 movePower) let attackContribution = attackerBaseStar * 30 * atkFactor; let defenseContribution = defenderBaseStar * 30 * defFactor; let baseDamage = context.movePower + Math.max(0, attackContribution - defenseContribution); let damage = baseDamage * levelMult; // 记录威力加成 breakdown.push(`招式威力(最终): ${context.movePower.toFixed(1)}`); breakdown.push(`攻击基础星级: ${attackerBaseStar.toFixed(1)}`); breakdown.push(`攻击贡献: ${attackerBaseStar.toFixed(1)} x30 x${atkFactor.toFixed(2)} = ${attackContribution.toFixed(0)}`); breakdown.push(`防御基础星级: ${defenderBaseStar.toFixed(1)}`); breakdown.push(`防御减免: ${defenderBaseStar.toFixed(1)} x30 x${defFactor.toFixed(2)} = ${defenseContribution.toFixed(0)}`); breakdown.push(`差值: ${(attackContribution - defenseContribution).toFixed(0)}`); breakdown.push(`基础伤害: ${baseDamage.toFixed(0)}`); breakdown.push(`等级系数 x${levelMult.toFixed(2)}`); let totalMult = 1; let multLog = '1'; // 属性克制 let typeMult = calculateTypeEffectiveness(moveObj.type, defender.type, defenderAbilityObj, defenderHiddenObj, 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; if (context.adaptabilityActive) stab += 0.2; // 适应力额外+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 *= abilityFinalMult; totalMult *= abilityFinalMult; abilityEffects.forEach(ae => { breakdown.push(`特性 ${ae.name} ${ae.note} x${ae.mult.toFixed(2)}`); multLog += ` x${ae.mult.toFixed(2)} (${ae.note})`; }); // ========== 能力等级独立乘区(与游戏一致) ========== // 获取攻击方和防御方是否有 Unaware 特性(需要检查普通和隐藏) const attackerHasUnaware = (inputAbilityObj && inputAbilityObj.id === 'unaware') || (hiddenAbilityObj && hiddenAbilityObj.id === 'unaware'); const defenderHasUnaware = (defenderAbilityObj && defenderAbilityObj.id === 'unaware') || (defenderHiddenObj && defenderHiddenObj.id === 'unaware'); let atkBuffMult = 1; let defBuffMult = 1; if (!defenderHasUnaware) { const stageVal = isPhysical ? atkStageVal : satkStageVal; atkBuffMult = getStageMult(stageVal, false); } else { breakdown.push(`防御方具有纯朴,无视攻击方能力等级`); } if (!attackerHasUnaware) { const stageVal = isPhysical ? defStageVal : sdefStageVal; defBuffMult = getStageMult(stageVal, true); } else { breakdown.push(`攻击方具有纯朴,无视防御方能力等级`); } let stageMult = atkBuffMult * defBuffMult; damage *= stageMult; totalMult *= stageMult; if (atkBuffMult !== 1) { breakdown.push(`攻击能力等级 x${atkBuffMult.toFixed(2)}`); } if (defBuffMult !== 1) { breakdown.push(`防御能力等级 x${defBuffMult.toFixed(2)}`); } if (atkBuffMult !== 1 || defBuffMult !== 1) { multLog += ` x${stageMult.toFixed(2)} (能力等级)`; } // ========== 能力等级乘区结束 ========== const finalDamage = Math.round(damage); // 保存详细报告到全局变量 window.__lastDamageReport = { attacker: { name: getChinese(attackerId), level: 100, split: isPhysical ? '物理' : '特殊', baseStar: attackerBaseStar, stage: isPhysical ? atkStageVal : satkStageVal, stageMult: atkBuffMult, iv: isPhysical ? atkIv : atkIv, ivFactor: atkFactor, finalStat: attackerBaseStar, finalStatValue: attackContribution.toFixed(0), status: attackerStatusVal, shiny: isShiny, powerEffects: powerEffectNotes, // 新增这一行 cross: isCross, item: itemId ? { name: getChinese(itemId), level: itemLevel, mult: itemMult } : null, abilityEffects: abilityEffects, powerEffects: powerEffectNotes // 新增这一行 }, defender: { name: getChinese(defenderId), baseStar: defenderBaseStar, stage: isPhysical ? defStageVal : sdefStageVal, stageMult: defBuffMult, iv: isPhysical ? defIv : defIv, ivFactor: defFactor, finalStat: defenderBaseStar, finalStatValue: defenseContribution.toFixed(0) }, move: { name: getChinese(moveId), power: moveObj.power, type: moveObj.type, finalPower: context.movePower // 新增这一行 }, 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.${r.attacker.level}(${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)} × 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} 星级 ${r.attacker.item.level} → 伤害 x${r.attacker.item.mult.toFixed(2)}`); } if (r.attacker.abilityEffects.length) { r.attacker.abilityEffects.forEach(ae => { lines.push(` ├─ 特性:${ae.name}(${ae.note}) → 伤害 x${ae.mult.toFixed(2)}`); }); } lines.push(''); // 输出威力特性 if (r.attacker.powerEffects && r.attacker.powerEffects.length) { r.attacker.powerEffects.forEach(pe => { lines.push(` ├─ 特性:${pe.name}(${pe.note}) → 威力提升`); }); } // 防御方 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)} × 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}`); let powerDisplay = r.move.power; let powerNote = ''; if (r.move.finalPower && r.move.finalPower !== r.move.power) { powerDisplay = r.move.finalPower.toFixed(1); // 获取威力特性名称 let abilityNames = []; if (r.attacker.powerEffects && r.attacker.powerEffects.length) { abilityNames = r.attacker.powerEffects.map(pe => pe.name); } const abilityText = abilityNames.length ? `经 ${abilityNames.join('、')} 特性修正` : '经特性修正'; powerNote = ` (原始威力 ${r.move.power} ${abilityText})`; } lines.push(` ② 基础伤害 = 招式威力${powerNote} + 威力差值 = ${powerDisplay} + ${diff} = ${r.baseDamage}`); lines.push(` ③ 等级系数 = 1 + 等级 × 0.1 = 1 + ${r.attacker.level} × 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}(${ae.note}):x${ae.mult.toFixed(2)}`); }); lines.push(` • 能力等级综合:x${(r.attacker.stageMult * r.defender.stageMult).toFixed(2)}(攻击 ${r.attacker.stageMult.toFixed(2)} × 防御 ${r.defender.stageMult.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.7] 初始化完成,能力等级独立乘区,支持双特性,汉化已启用'); } })();