MWI-Equipment-Diff

Make life easier

当前为 2025-06-06 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name MWI-Equipment-Diff
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0.2
  5. // @description Make life easier
  6. // @author BKN46
  7. // @match https://*.milkywayidle.com/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=milkywayidle.com
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. let selfData = {};
  17.  
  18. const colors = {
  19. info: 'rgb(0, 108, 158)',
  20. smaller: 'rgb(199, 21, 21)',
  21. greater: 'rgb(23, 151, 12)',
  22. };
  23. let equipmentToDiff = {};
  24. const isZHInGameSetting = localStorage.getItem("i18nextLng")?.toLowerCase()?.startsWith("zh");
  25. let isZH = isZHInGameSetting;
  26.  
  27. function transZH(zh) {
  28. if (isZH) return zh;
  29. return {
  30. "战斗风格": "Combat Style",
  31. "伤害类型": "Damage Type",
  32. "自动攻击伤害": "Auto Attack Damage",
  33. "攻击速度": "Attack Speed",
  34. "攻击间隔": "Attack Interval",
  35. "暴击率": "Critical Rate",
  36. "暴击伤害": "Critical Damage",
  37. "技能急速": "Ability Haste",
  38. "施法速度": "Cast Speed",
  39.  
  40. "刺击精准度": "Stab Accuracy",
  41. "刺击伤害": "Stab Damage",
  42. "斩击精准度": "Slash Accuracy",
  43. "斩击伤害": "Slash Damage",
  44. "钝击精准度": "Smash Accuracy",
  45. "钝击伤害": "Smash Damage",
  46. "远程精准度": "Ranged Accuracy",
  47. "远程伤害": "Ranged Damage",
  48. "魔法精准度": "Magic Accuracy",
  49. "魔法伤害": "Magic Damage",
  50.  
  51. "物理增幅": "Physical Amplify",
  52. "火系增幅": "Fire Amplify",
  53. "水系增幅": "Water Amplify",
  54. "自然系增幅": "Nature Amplify",
  55. "护甲穿透": "Armor Penetration",
  56. "火系穿透": "Fire Penetration",
  57. "水系穿透": "Water Penetration",
  58. "自然系穿透": "Nature Penetration",
  59. }[zh] || zh;
  60. }
  61.  
  62. function parseEquipmentModal(element) {
  63. const equipmentDetail = {};
  64. const detailLines = element.querySelectorAll('.EquipmentStatsText_stat__27Sus');
  65. for (const line of detailLines) {
  66. if (line.querySelector('.EquipmentStatsText_uniqueStat__2xvqX')) continue;
  67. const data = line.textContent.split(':');
  68. if (data.length === 2) {
  69. const key = data[0].trim();
  70. const value = data[1].split('(')[0].trim();
  71. if (value === 'N/A') continue;
  72. equipmentDetail[key] = value;
  73. }
  74. }
  75.  
  76. if (!element.querySelector('.diff-tooltip')) {
  77. const diffTooltip = document.createElement('div');
  78. diffTooltip.className = 'diff-tooltip';
  79. diffTooltip.textContent = isZH ? '按\'d\'来添加作为对比对象' : 'Press "d" to add to quick diff';
  80. diffTooltip.style = `color: ${colors.info}; font-weight: bold;`;
  81. element.appendChild(diffTooltip);
  82.  
  83. if (equipmentToDiff.data) {
  84. const diffRemoveTooltip = document.createElement('div');
  85. diffRemoveTooltip.className = 'diff-remove-tooltip';
  86. diffRemoveTooltip.textContent = isZH ? '按\'r\'来移除当前对比' : 'Press "r" to remove present quick diff';
  87. diffRemoveTooltip.style = `color: ${colors.info}; font-weight: bold;`;
  88. element.appendChild(diffRemoveTooltip);
  89. }
  90. }
  91. return equipmentDetail;
  92. }
  93.  
  94.  
  95. // #region Diff Modal
  96. function addDiffToModal(element, data, price) {
  97. if (element.querySelector('.diff-value') || element.querySelector('.diff-header')) return;
  98. const parentArea = element.querySelector('.EquipmentStatsText_equipmentStatsText__djKBS');
  99. const TextArea = parentArea.firstChild;
  100.  
  101. const diffMap = {};
  102. const detailLines = element.querySelectorAll('.EquipmentStatsText_stat__27Sus');
  103. for (const line of detailLines) {
  104. const key = line.textContent.split(':')[0].trim();
  105. const valueElement = line.querySelectorAll('span')[1];
  106. const diffSpan = document.createElement('span');
  107. diffSpan.className = 'diff-value';
  108. if (key in equipmentToDiff.data) {
  109. const diffValue = equipmentToDiff.data[key];
  110. if (data[key] === diffValue) {
  111. continue;
  112. }
  113. const diff = strDiff(diffValue, data[key]);
  114. diffMap[key] = diff;
  115. diffSpan.textContent = ` (${diff})`;
  116. let color = colors.info;
  117. if (data[key].endsWith('s')) {
  118. color = diff.startsWith('-') ? colors.greater : colors.smaller;
  119. } else {
  120. color = diff.startsWith('-') ? colors.smaller : colors.greater;
  121. }
  122. diffSpan.style = `color: ${color}; font-weight: bold;`;
  123. valueElement.appendChild(diffSpan);
  124. } else if (valueElement) {
  125. diffSpan.textContent = ` (${valueElement.textContent})`;
  126. diffMap[key] = valueElement.textContent.startsWith('-') ? valueElement.textContent.replace('-', '') : `-${valueElement.textContent}`;
  127. const color = valueElement.textContent.startsWith('-') ? colors.smaller: colors.greater;
  128. diffSpan.style = `color: ${color}; font-weight: bold;`;
  129. valueElement.appendChild(diffSpan);
  130. } else { // special ability on equipment
  131. // diffSpan.textContent = ` (N/A)`;
  132. // diffSpan.style = `color: ${colors.smaller}; font-weight: bold;`;
  133. // line.appendChild(diffSpan);
  134. }
  135. }
  136. for (const key in equipmentToDiff.data) {
  137. if (!(key in data)) {
  138. const newLine = document.createElement('div');
  139. newLine.className = 'EquipmentStatsText_stat__27Sus';
  140.  
  141. const keySpan = document.createElement('span');
  142. keySpan.textContent = `${key}: `;
  143. newLine.appendChild(keySpan);
  144.  
  145. const valueSpan = document.createElement('span');
  146. valueSpan.textContent = 'N/A';
  147. newLine.appendChild(valueSpan);
  148.  
  149. const diffSpan = document.createElement('span');
  150. diffSpan.className = 'diff-value';
  151. let diffValue = '';
  152. if (equipmentToDiff.data[key].startsWith('-')) {
  153. diffValue = equipmentToDiff.data[key].replace('-', '+');
  154. } else if (equipmentToDiff.data[key].startsWith('+')) {
  155. diffValue = equipmentToDiff.data[key].replace('+', '-');
  156. } else {
  157. diffValue = `-${equipmentToDiff.data[key]}`;
  158. }
  159. diffSpan.textContent = ` (${diffValue})`;
  160. diffMap[key] = equipmentToDiff.data[key];
  161.  
  162. const color = equipmentToDiff.data[key].startsWith('-') ? colors.greater : colors.smaller;
  163. diffSpan.style = `color: ${color}; font-weight: bold;`;
  164. valueSpan.appendChild(diffSpan);
  165. TextArea.appendChild(newLine);
  166. }
  167. }
  168.  
  169. // #region Diff header
  170. if (equipmentToDiff.name) {
  171. const newLine = document.createElement('div');
  172. newLine.className = 'diff-header';
  173. newLine.style = 'display: flex; grid-gap: 6px; gap: 6px; justify-content: space-between;'
  174.  
  175. const keySpan = document.createElement('span');
  176. keySpan.textContent = isZH ? '对比对象: ' : 'Compares to: ';
  177. keySpan.style = `color: ${colors.info}; font-weight: bold;`;
  178. newLine.appendChild(keySpan);
  179. const diffTitle = document.createElement('span');
  180. diffTitle.textContent = equipmentToDiff.name;
  181. diffTitle.style = `color: ${colors.info}; font-weight: bold;`;
  182. newLine.appendChild(diffTitle);
  183. parentArea.insertBefore(newLine, TextArea);
  184. }
  185. if (price >= 0 && equipmentToDiff.price >= 0) {
  186. const newLine = document.createElement('div');
  187. newLine.className = 'diff-header';
  188. newLine.style = 'display: flex; grid-gap: 6px; gap: 6px; justify-content: space-between;'
  189.  
  190. const keySpan = document.createElement('span');
  191. keySpan.textContent = isZH ? `价格差: ` : `Price diff: `;
  192. keySpan.style = `color: ${colors.info}; font-weight: bold;`;
  193. newLine.appendChild(keySpan);
  194. const priceSpan = document.createElement('span');
  195. priceSpan.className = 'diff-price';
  196. const priceDiff = priceParse(price - equipmentToDiff.price);
  197. priceSpan.textContent = `${priceDiff}`;
  198. const priceColor = priceDiff.startsWith('-') ? colors.greater : colors.smaller;
  199. priceSpan.style = `color: ${priceColor}; font-weight: bold;`;
  200. newLine.appendChild(priceSpan);
  201.  
  202. parentArea.insertBefore(newLine, TextArea);
  203. }
  204. // #region DPS diff
  205. if (Object.keys(diffMap).length > 0) {
  206. const newLine = document.createElement('div');
  207. newLine.className = 'diff-header';
  208. newLine.style = 'display: flex; grid-gap: 6px; gap: 6px; justify-content: space-between;'
  209.  
  210. const keySpan = document.createElement('span');
  211. keySpan.textContent = isZH ? `粗略DPS变化: ` : `Rough DPS diff: `;
  212. keySpan.style = `color: ${colors.info}; font-weight: bold;`;
  213. newLine.appendChild(keySpan);
  214.  
  215. const selfType = getSelfStyleTrans();
  216. const originStyle = equipmentToDiff.data[transZH('战斗风格')];
  217. const targetStyle = data[transZH('战斗风格')];
  218. if ((originStyle || targetStyle) && (targetStyle != selfType || originStyle != selfType)) {
  219. const diffValue = document.createElement('span');
  220. diffValue.textContent = isZH ? '不支持对比战斗风格不一致武器' : 'Cannot compare weapons with different combat styles';
  221. diffValue.style = `color: ${colors.info}; font-weight: bold;`;
  222. newLine.appendChild(diffValue);
  223. parentArea.insertBefore(newLine, TextArea);
  224. return;
  225. }
  226.  
  227. let dpsCompare = 0;
  228. try {
  229. dpsCompare = getDPSCompare(diffMap);
  230. const diffValue = document.createElement('span');
  231. diffValue.textContent = dpsCompare * 100 > 0 ? `+${(dpsCompare * 100).toFixed(3)}%` : `${(dpsCompare * 100).toFixed(3)}%`;
  232. const color = dpsCompare > 0 ? colors.greater : colors.smaller;
  233. diffValue.style = `color: ${color}; font-weight: bold;`;
  234. newLine.appendChild(diffValue);
  235. } catch (error) {
  236. const diffValue = document.createElement('span');
  237. diffValue.textContent = isZH ? '[计算过程发生错误]' : '[Error]';
  238. diffValue.style = `color: ${colors.info}; font-weight: bold;`;
  239. console.error('Equipment Diff: Error calculating DPS diff:', error);
  240. newLine.appendChild(diffValue);
  241. }
  242. parentArea.insertBefore(newLine, TextArea);
  243.  
  244. if (dpsCompare > 0 && price >= 0 && equipmentToDiff.price >= 0) {
  245. const priceLine = document.createElement('div');
  246. priceLine.className = 'diff-header';
  247. priceLine.style = 'display: flex; grid-gap: 6px; gap: 6px; justify-content: space-between;'
  248. const priceKeySpan = document.createElement('span');
  249. priceKeySpan.textContent = isZH ? `每10M价格DPS提升: ` : `DPS% per 10M coin: `;
  250. priceKeySpan.style = `color: ${colors.info}; font-weight: bold;`;
  251. priceLine.appendChild(priceKeySpan);
  252. const dpsPriceSpan = document.createElement('span');
  253. dpsPriceSpan.className = 'diff-price';
  254. const dpsPerMil = (dpsCompare * 100) / ((price - equipmentToDiff.price) / 1e6) * 10;
  255. dpsPriceSpan.textContent = dpsPerMil > 0 ? `+${dpsPerMil.toFixed(5)}%` : `${dpsPerMil.toFixed(5)}%`;
  256. const priceColor = dpsPerMil > 0 ? colors.greater : colors.smaller;
  257. dpsPriceSpan.style = `color: ${priceColor}; font-weight: bold;`;
  258. priceLine.appendChild(dpsPriceSpan);
  259. parentArea.insertBefore(priceLine, TextArea);
  260. }
  261. }
  262. }
  263.  
  264. function strDiff(s1, s2) {
  265. if (s1 === s2) return '';
  266. if (!s1 || !s2) return s1 || s2;
  267. let postfix = '';
  268. let isNeg = false;
  269. if (s1.endsWith('%')) postfix = '%';
  270. if (s1.startsWith('-')) isNeg = true;
  271. const proc = (t) => parseFloat(t.replace('%', '').replace(',', '').replace(' ', '').replace('+', '').replace('_', ''));
  272. const diff = proc(s2) - proc(s1);
  273. if (isNaN(diff)) return s1;
  274. if (diff === 0) return '';
  275. if (diff > 0) return `${isNeg ? '-' : '+'}${parseFloat(diff.toFixed(3))}${postfix}`;
  276. return `${isNeg ? '+' : '-'}${parseFloat(Math.abs(diff).toFixed(3))}${postfix}`;
  277. }
  278.  
  279. function hookWS() {
  280. const dataProperty = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data");
  281. const oriGet = dataProperty.get;
  282.  
  283. dataProperty.get = hookedGet;
  284. Object.defineProperty(MessageEvent.prototype, "data", dataProperty);
  285.  
  286. function hookedGet() {
  287. const socket = this.currentTarget;
  288. if (!(socket instanceof WebSocket)) {
  289. return oriGet.call(this);
  290. }
  291. if (socket.url.indexOf("api.milkywayidle.com/ws") <= -1 && socket.url.indexOf("api-test.milkywayidle.com/ws") <= -1) {
  292. return oriGet.call(this);
  293. }
  294.  
  295. const message = oriGet.call(this);
  296. Object.defineProperty(this, "data", { value: message }); // Anti-loop
  297.  
  298. try {
  299. return handleMessage(message);
  300. } catch (error) {
  301. console.log("Error in handleMessage:", error);
  302. return message;
  303. }
  304. }
  305. }
  306. hookWS();
  307.  
  308. function handleMessage(message) {
  309. if (typeof message !== "string") {
  310. return message;
  311. }
  312. try {
  313. const parsedMessage = JSON.parse(message);
  314. if (parsedMessage && parsedMessage.type === "init_character_data") {
  315. selfData = parsedMessage;
  316. }
  317. } catch (error) {
  318. console.log("Error parsing message:", error);
  319. }
  320. return message;
  321. }
  322.  
  323. function parsePrice(costText) {
  324. if (costText.endsWith('M')) {
  325. return parseFloat(costText.replace('M', '').replace(',', '')) * 1e6
  326. } else if (costText.endsWith('k')) {
  327. return parseFloat(costText.replace('k', '').replace(',', '')) * 1e3
  328. } else if (costText.endsWith('K')) {
  329. return parseFloat(costText.replace('K', '').replace(',', '')) * 1e3
  330. } else if (costText.endsWith('B')) {
  331. return parseFloat(costText.replace('B', '').replace(',', '')) * 1e9
  332. } else if (costText.endsWith('T')) {
  333. return parseFloat(costText.replace('T', '').replace(',', '')) * 1e12
  334. } else {
  335. return parseFloat(costText.replace(',', ''))
  336. }
  337. }
  338.  
  339. function priceParse(priceNum) {
  340. const isNegative = priceNum < 0;
  341. const absValue = Math.abs(priceNum);
  342. let result;
  343. if (absValue >= 1e10) {
  344. result = parseFloat((absValue / 1e9).toFixed(3)) + 'B';
  345. } else if (absValue >= 1e7) {
  346. result = parseFloat((absValue / 1e6).toFixed(3)) + 'M';
  347. } else if (absValue >= 1e4) {
  348. result = parseFloat((absValue / 1e3).toFixed(3)) + 'K';
  349. } else {
  350. result = parseFloat(absValue.toFixed(3));
  351. }
  352. return isNegative ? '-' + result : '+' + result;
  353. }
  354.  
  355. function getMWIToolsPrice(modal) {
  356. const enhancedPriceText = isZH ? '总成本' : 'Total cost';
  357. let costNodes = Array.from(modal.querySelectorAll('*')).filter(el => {
  358. if (!el.textContent || !el.textContent.includes(enhancedPriceText)) return false;
  359. return Array.from(el.childNodes).every(node => node.nodeType === Node.TEXT_NODE);
  360. });
  361. if (costNodes.length > 0) {
  362. const node = costNodes[0];
  363. const costText = node.textContent.replace(enhancedPriceText, '').trim();
  364. return parsePrice(costText);
  365. }
  366.  
  367. const normalPriceText = isZH ? '日均价' : 'Daily average price';
  368. costNodes = Array.from(modal.querySelectorAll('*')).filter(el => {
  369. if (!el.textContent || !el.textContent.includes(normalPriceText)) return false;
  370. return Array.from(el.childNodes).every(node => node.nodeType === Node.TEXT_NODE);
  371. });
  372. if (costNodes.length > 0) {
  373. const node = costNodes[0];
  374. const costText = node.textContent.split('/')[0].split(' ')[1].trim();
  375. return parsePrice(costText);
  376. }
  377.  
  378. return -1;
  379. }
  380.  
  381. function getSelfStyleTrans() {
  382. const combatStyle = selfData?.combatUnit.combatDetails.combatStats.combatStyleHrids[0];
  383. return {
  384. '/combat_styles/stab': isZH ? '刺击' : 'Stab',
  385. '/combat_styles/slash': isZH ? '斩击' : 'Slash',
  386. '/combat_styles/smash': isZH ? '钝击' : 'Smash',
  387. '/combat_styles/ranged': isZH ? '远程' : 'Ranged',
  388. '/combat_styles/magic': isZH ? '魔法' : 'Magic',
  389. }[combatStyle] || combatStyle;
  390. }
  391.  
  392. setInterval(() => {
  393. const modals = document.querySelectorAll('.MuiPopper-root');
  394. if (!modals) return;
  395. // compatibility with chat-enhance addon
  396. let equipmentDetail = null;
  397. let modal = null;
  398. for (const modal_ of modals) {
  399. equipmentDetail = modal_.querySelector('.ItemTooltipText_equipmentDetail__3sIHT');
  400. if (equipmentDetail) {
  401. modal = modal_;
  402. break
  403. };
  404. }
  405. if (!equipmentDetail) return;
  406.  
  407. const equipmentName = modal.querySelector('.ItemTooltipText_name__2JAHA').textContent.trim();
  408. const equipmentData = parseEquipmentModal(equipmentDetail);
  409.  
  410. const price = getMWIToolsPrice(modal);
  411.  
  412. if (equipmentToDiff.data) {
  413. addDiffToModal(equipmentDetail, equipmentData, price);
  414. }
  415.  
  416. document.onkeydown = (event) => {
  417. if (event.key === 'd') {
  418. // event.preventDefault();
  419. console.log(`Added to quick diff: ${equipmentName}`);
  420. equipmentToDiff = {
  421. data: equipmentData,
  422. name: equipmentName,
  423. price: price,
  424. };
  425. equipmentDetail.querySelector('.diff-tooltip').textContent = isZH ? '已添加作为对比' : 'Added to quick diff';
  426. } else if (event.key === 'r') {
  427. // event.preventDefault();
  428. console.log(`Removed from quick diff: ${equipmentName}`);
  429. equipmentToDiff = {};
  430. equipmentDetail.querySelector('.diff-tooltip').textContent = isZH ? '已移除对比' : 'Removed from quick diff';
  431. }
  432. };
  433.  
  434. }, 500)
  435.  
  436. // #region DPS Caculator
  437. const monsterStatusMap = {
  438. 50: {evasion: 80, resistence: 14},
  439. 80: {evasion: 130, resistence: 18},
  440. 100: {evasion: 170, resistence: 30},
  441. 120: {evasion: 250, resistence: 40},
  442. 140: {evasion: 300, resistence: 80},
  443. 200: {evasion: 400, resistence: 100},
  444. }
  445. const abilityDamgeMap = {
  446. 50: {ratio: 0.66, flat: 12},
  447. 80: {ratio: 0.69, flat: 13},
  448. 100: {ratio: 0.735, flat: 14.5},
  449. 120: {ratio: 0.78, flat: 16},
  450. 140: {ratio: 0.825, flat: 17.5},
  451. 200: {ratio: 0.87, flat: 19},
  452. }
  453.  
  454. function chooseBelow(level, map) {
  455. const keys = Object.keys(map).map(Number).filter(k => k <= level);
  456. if (keys.length === 0) return null;
  457. const maxKey = Math.max(...keys);
  458. return map[maxKey];
  459. }
  460.  
  461. function getDamageBase(combatStyle, skillLevel, atkAcc, atkAmp, dmgAmp, dmgPen, critAmp, critDmg) {
  462. const isMagic = combatStyle === "/combat_styles/magic";
  463. const abilityFlatDmg = isMagic ? chooseBelow(skillLevel, abilityDamgeMap)?.flat : 0;
  464. const abilityRatioDmg = isMagic ? chooseBelow(skillLevel, abilityDamgeMap)?.ratio + 1 : 1;
  465. const enemyEvasion = chooseBelow(skillLevel, monsterStatusMap)?.evasion || 80;
  466. const enemyResistence = chooseBelow(skillLevel, monsterStatusMap)?.resistence || 80;
  467.  
  468. const accuracy = (10+skillLevel)*(1+atkAcc)
  469. const hitRate = Math.pow(accuracy, 1.4) / (Math.pow(accuracy, 1.4) + Math.pow(enemyEvasion, 1.4));
  470. const critRate = combatStyle == "/combat_styles/ranged" ? hitRate * 0.3 + critAmp : critAmp;
  471.  
  472. const baseDmg = (10+skillLevel) * (1+atkAmp);
  473. const minDmg = (1+dmgAmp) * (1+abilityFlatDmg);
  474. const maxDmg = (1+dmgAmp) * (abilityRatioDmg*baseDmg + abilityFlatDmg);
  475. const estDmg = (minDmg + maxDmg) / 2 * (1-critRate) + maxDmg * critRate * (1+critDmg);
  476. const actDmg = estDmg * (100 / (100 + (enemyResistence / ( 1 + dmgPen))))
  477. return actDmg * hitRate;
  478. }
  479.  
  480. function getSkillLevel(skillName, map) {
  481. for (const skill of map) {
  482. if (skill.skillHrid === skillName) {
  483. return skill.level;
  484. }
  485. }
  486. return -1
  487. }
  488.  
  489. function getDPSCompare(diffMap) {
  490. const combatDetails = selfData?.combatUnit.combatDetails;
  491. const combatStyle = combatDetails.combatStats.combatStyleHrids[0];
  492. const damageType = combatDetails.combatStats.damageType;
  493.  
  494. function getValueFromDiffMap(key) {
  495. const k = transZH(key);
  496. if (!diffMap || !diffMap[k]) return 0;
  497. const s = diffMap[k].replace(',', '').replace(' ', '').replace('+', '').replace('_', '')
  498. if (s.endsWith('%')) {
  499. return parseFloat(s.replace('%', '')) / 100;
  500. } else if (s.endsWith('s')) {
  501. return parseFloat(s.replace('s', '')) * 1000000000;
  502. } else {
  503. return parseFloat(s);
  504. }
  505. }
  506.  
  507. let atkSkillLevel = getSkillLevel('/skills/attack', selfData.characterSkills);
  508. let dmgSkillLevel = getSkillLevel('/skills/power', selfData.characterSkills);
  509. let originalAmp = {
  510. atkAcc: 0,
  511. atkAmp: 0,
  512. dmgAmp: combatDetails.combatStats.physicalAmplify,
  513. dmgPen: combatDetails.combatStats.armorPenetration,
  514. autoDmg: combatDetails.combatStats.autoAttackDamage || 0,
  515. autoSpeed: combatDetails.combatStats.attackInterval,
  516. abilitySpeed: (100 + (combatDetails.combatStats.abilityHaste || 0))/100 * (1+(combatDetails.combatStats.castSpeed || 0)),
  517. critAmp: combatDetails.combatStats.criticalRate || 0,
  518. critDmg: combatDetails.combatStats.criticalDamage || 0,
  519. speedFactor: 0,
  520. }
  521. let targetAmp = {
  522. atkAcc: 0,
  523. atkAmp: 0,
  524. dmgAmp: originalAmp.dmgAmp + getValueFromDiffMap('物理增幅'),
  525. dmgPen: originalAmp.dmgPen + getValueFromDiffMap('护甲穿透'),
  526. autoDmg: originalAmp.autoDmg + getValueFromDiffMap('自动攻击伤害'),
  527. autoSpeed: originalAmp.autoSpeed + getValueFromDiffMap('攻击间隔'),
  528. abilitySpeed: (100 + combatDetails.combatStats.abilityHaste + getValueFromDiffMap('技能急速'))/100 * (1+combatDetails.combatStats.castSpeed+getValueFromDiffMap('施法速度')),
  529. critAmp: originalAmp.critAmp + getValueFromDiffMap('暴击率'),
  530. critDmg: originalAmp.critDmg + getValueFromDiffMap('暴击伤害'),
  531. speedFactor: 0,
  532. }
  533. switch (combatStyle) {
  534. case "/combat_styles/stab":
  535. originalAmp.atkAcc = combatDetails.stabAccuracyRating / (10 + atkSkillLevel) - 1;
  536. originalAmp.atkAmp = combatDetails.stabMaxDamage / (10 + dmgSkillLevel) - 1;
  537. originalAmp.speedFactor = originalMap.autoSpeed;
  538. targetAmp.atkAcc = originalAmp.atkAcc + getValueFromDiffMap('刺击精准度');
  539. targetAmp.atkAmp = originalAmp.atkAmp + getValueFromDiffMap('刺击伤害');
  540. targetAmp.speedFactor = targetAmp.autoSpeed;
  541. break;
  542. case "/combat_styles/slash":
  543. originalAmp.atkAcc = combatDetails.slashAccuracyRating / (10 + atkSkillLevel) - 1;
  544. originalAmp.atkAmp = combatDetails.slashMaxDamage / (10 + dmgSkillLevel) - 1;
  545. originalAmp.speedFactor = originalMap.autoSpeed;
  546. targetAmp.atkAcc = originalAmp.atkAcc + getValueFromDiffMap('斩击精准度');
  547. targetAmp.atkAmp = originalAmp.atkAmp + getValueFromDiffMap('斩击伤害');
  548. targetAmp.speedFactor = targetAmp.autoSpeed;
  549. break;
  550. case "/combat_styles/smash":
  551. originalAmp.atkAcc = combatDetails.smashAccuracyRating / (10 + atkSkillLevel) - 1;
  552. originalAmp.atkAmp = combatDetails.smashMaxDamage / (10 + dmgSkillLevel) - 1;
  553. originalAmp.speedFactor = originalMap.autoSpeed;
  554. targetAmp.atkAcc = originalAmp.atkAcc + getValueFromDiffMap('钝击精准度');
  555. targetAmp.atkAmp = originalAmp.atkAmp + getValueFromDiffMap('钝击伤害');
  556. targetAmp.speedFactor = targetAmp.autoSpeed;
  557. break;
  558. case "/combat_styles/ranged":
  559. atkSkillLevel = getSkillLevel('/skills/ranged', selfData.characterSkills);
  560. dmgSkillLevel = atkSkillLevel;
  561. originalAmp.atkAcc = combatDetails.rangedAccuracyRating / (10 + atkSkillLevel) - 1;
  562. originalAmp.atkAmp = combatDetails.rangedMaxDamage / (10 + dmgSkillLevel) - 1;
  563. originalAmp.speedFactor = originalMap.autoSpeed;
  564. targetAmp.atkAcc = originalAmp.atkAcc + getValueFromDiffMap('远程精准度');
  565. targetAmp.atkAmp = originalAmp.atkAmp + getValueFromDiffMap('远程伤害');
  566. targetAmp.speedFactor = targetAmp.autoSpeed;
  567. break;
  568. case "/combat_styles/magic":
  569. atkSkillLevel = getSkillLevel('/skills/magic', selfData.characterSkills);
  570. dmgSkillLevel = atkSkillLevel;
  571. originalAmp.atkAcc = combatDetails.magicAccuracyRating / (10 + atkSkillLevel) - 1;
  572. originalAmp.atkAmp = combatDetails.magicMaxDamage / (10 + dmgSkillLevel) - 1;
  573. originalAmp.speedFactor = originalAmp.abilitySpeed;
  574. targetAmp.atkAcc = originalAmp.atkAcc + getValueFromDiffMap('魔法精准度');
  575. targetAmp.atkAmp = originalAmp.atkAmp + getValueFromDiffMap('魔法伤害');
  576. targetAmp.speedFactor = targetAmp.abilitySpeed;
  577. switch (damageType) {
  578. case '/damage_types/fire':
  579. originalAmp.dmgAmp = combatDetails.combatStats.fireAmplify;
  580. originalAmp.dmgPen = combatDetails.combatStats.firePenetration;
  581. targetAmp.dmgAmp = originalAmp.dmgAmp + getValueFromDiffMap('火系增幅');
  582. targetAmp.dmgPen = originalAmp.dmgPen + getValueFromDiffMap('火系穿透');
  583. break;
  584. case '/damage_types/water':
  585. originalAmp.dmgAmp = combatDetails.combatStats.waterAmplify;
  586. originalAmp.dmgPen = combatDetails.combatStats.waterPenetration;
  587. targetAmp.dmgAmp = originalAmp.dmgAmp + getValueFromDiffMap('水系增幅');
  588. targetAmp.dmgPen = originalAmp.dmgPen + getValueFromDiffMap('水系穿透');
  589. break;
  590. case '/damage_types/nature':
  591. originalAmp.dmgAmp = combatDetails.combatStats.natureAmplify;
  592. originalAmp.dmgPen = combatDetails.combatStats.naturePenetration;
  593. targetAmp.dmgAmp = originalAmp.dmgAmp + getValueFromDiffMap('自然系增幅');
  594. targetAmp.dmgPen = originalAmp.dmgPen + getValueFromDiffMap('自然系穿透');
  595. break;
  596. default:
  597. console.warn(`Equipment Diff: Unknown damge style: ${damageType}`);
  598. return 0;
  599. }
  600. break;
  601. default:
  602. console.warn(`Equipment Diff: Unknown combat style: ${combatStyle}`);
  603. return 0;
  604. }
  605.  
  606. const originalDamage = getDamageBase(combatStyle, atkSkillLevel, originalAmp.atkAcc, originalAmp.atkAmp, originalAmp.dmgAmp, originalAmp.dmgPen, originalAmp.critAmp, originalAmp.critDmg);
  607. const targetDamage = getDamageBase(combatStyle, atkSkillLevel, targetAmp.atkAcc, targetAmp.atkAmp, targetAmp.dmgAmp, targetAmp.dmgPen, targetAmp.critAmp, targetAmp.critDmg);
  608.  
  609. const diffRatio = targetDamage / originalDamage * (1 + targetAmp.speedFactor / originalAmp.speedFactor) / 2 - 1;
  610. return diffRatio
  611. }
  612.  
  613. })();