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