Mooneycalc-Importer (Multi)

脚本基于Mooneycalc-Importer v5.3,增加了组队模拟功能,使用时请手动查看队友装备以获取数据。https://amvoidguy.github.io/MWICombatSimulatorTest/dist/index.html.

  1. // ==UserScript==
  2. // @name Mooneycalc-Importer (Multi)
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.1.4
  5. // @description 脚本基于Mooneycalc-Importer v5.3,增加了组队模拟功能,使用时请手动查看队友装备以获取数据。https://amvoidguy.github.io/MWICombatSimulatorTest/dist/index.html.
  6. // @match https://www.milkywayidle.com/*
  7. // @match https://mooneycalc.vercel.app/*
  8. // @match https://mooneycalc.netlify.app/*
  9. // @match https://amvoidguy.github.io/MWICombatSimulatorTest/dist/index.html
  10. // @match https://shykai.github.io/MWICombatSimulatorTest/dist/
  11. // @match http://localhost:9000/
  12. // @run-at document-start
  13. // @grant GM_getValue
  14. // @grant GM_setValue
  15. // @grant GM_listValues
  16. // @grant GM_deleteValue
  17. // ==/UserScript==
  18.  
  19. //TODO
  20. // 1. 目前只支持当然页面刷新之后的角色 模拟多久升级
  21. // 2. 检验trigger是否工作 找到了trigger数据
  22.  
  23.  
  24. (function () {
  25. "use strict";
  26. const userLanguage = navigator.language || navigator.userLanguage;
  27. const isZH = userLanguage.startsWith("zh");
  28. let profile_arr = new Array();
  29. const SCRIPT_SIMULATE_TIME = "24";//模拟时长
  30. //启动前先把所有队友的装备手动看一遍!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  31.  
  32. if (document.URL.includes("milkywayidle.com")) {
  33. hookWS();
  34. } else if (document.URL.includes("mooneycalc")) {
  35. addImportButton1();
  36. } else if( document.URL.includes("MWICombatSimulatorTest") || document.URL.includes("localhost")){
  37. addImportButton4();
  38. waitSimulationResultsLoading();
  39. }
  40. function waitSimulationResultsLoading(){
  41. const waitForNavi = () => {
  42. if(document.querySelector(`div.row`)?.querySelectorAll(`div.col-md-5`)?.[2]?.querySelector(`div.row > div.col-md-5`))observeResults();
  43. else setTimeout(waitSimulationResultsLoading, 200);
  44. };
  45. waitForNavi();
  46. }
  47.  
  48.  
  49.  
  50. function hookWS() {
  51. const dataProperty = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data");
  52. const oriGet = dataProperty.get;
  53.  
  54. dataProperty.get = hookedGet;
  55. Object.defineProperty(MessageEvent.prototype, "data", dataProperty);
  56.  
  57. function hookedGet() {
  58. const socket = this.currentTarget;
  59. if (!(socket instanceof WebSocket)) {
  60. return oriGet.call(this);
  61. }
  62. if (socket.url.indexOf("api.milkywayidle.com/ws") <= -1) {
  63.  
  64. return oriGet.call(this);
  65. }
  66.  
  67. const message = oriGet.call(this);
  68. Object.defineProperty(this, "data", { value: message }); // Anti-loop
  69.  
  70. return handleMessage(message);
  71. }
  72. }
  73.  
  74. function handleMessage(message) {
  75. let obj = JSON.parse(message);
  76. //const keys = GM_listValues();
  77. //console.log('keys',keys);
  78. // keys.forEach(key => { GM_deleteValue(key); });
  79. if (obj && obj.type === "init_character_data") {
  80.  
  81. if(GM_getValue("profile_arr"))
  82. {
  83. profile_arr = GM_getValue("profile_arr");
  84. }
  85.  
  86. if(!profile_arr.includes(obj.character.name)){
  87. profile_arr.push(obj.character.name);
  88. }
  89.  
  90. // profile_arr.reverse();
  91. // console.log("profileArr",profile_arr);
  92. GM_setValue("profile_arr", profile_arr);
  93. GM_setValue("profile_" + obj.character.name, message);
  94. GM_setValue("init_character_data",message);
  95. const init_client_data = localStorage.getItem("initClientData");
  96. if(init_client_data)GM_setValue("init_client_data", init_client_data);
  97.  
  98. }else if (obj && obj.type === "new_battle"){
  99. let newBattleHandler = [];
  100. obj.players.forEach((player) => {
  101. newBattleHandler.push(player.name);
  102. });
  103.  
  104. console.log("new_battle",newBattleHandler.sort());
  105.  
  106. //以防万一 留一个旧的team battle
  107. GM_setValue("team_battle", message);
  108.  
  109. GM_setValue("team_battle_" + newBattleHandler[0], message);
  110. }else if (obj && obj.type === "profile_shared"){
  111. // console.log("profile_shared",obj);
  112. GM_setValue(obj.profile.sharableCharacter.name, message);
  113. get_sim_json(obj);
  114. }
  115. return message;
  116. }
  117.  
  118. function get_sim_json(obj) {
  119. const checkElem = () => {
  120. const selectedElement = document.querySelector(`div.SharableProfile_overviewTab__W4dCV`);
  121. if (selectedElement) {
  122. clearInterval(timer);
  123. //获取模拟数据
  124. let player_name=obj.profile.sharableCharacter.name;
  125. let exportObj = {};
  126. exportObj.player = {};
  127. exportObj.player.equipment = [];
  128. exportObj.houseRooms = {};
  129. //装备
  130. for (const key in obj.profile.wearableItemMap) {
  131. const item = obj.profile.wearableItemMap[key];
  132. exportObj.player.equipment.push({
  133. itemLocationHrid: item.itemLocationHrid,
  134. itemHrid: item.itemHrid,
  135. enhancementLevel: item.enhancementLevel,
  136. });
  137. }
  138. //别人房子
  139. for (const house of Object.values(obj.profile.characterHouseRoomMap)) {
  140. exportObj.houseRooms[house.houseRoomHrid] = house.level;
  141. }
  142. //技能等级
  143. for (const skill of obj.profile.characterSkills) {
  144. if (skill.skillHrid.includes("stamina")) {
  145. exportObj.player.staminaLevel = skill.level;
  146. } else if (skill.skillHrid.includes("intelligence")) {
  147. exportObj.player.intelligenceLevel = skill.level;
  148. } else if (skill.skillHrid.includes("attack")) {
  149. exportObj.player.attackLevel = skill.level;
  150. } else if (skill.skillHrid.includes("power")) {
  151. exportObj.player.powerLevel = skill.level;
  152. } else if (skill.skillHrid.includes("defense")) {
  153. exportObj.player.defenseLevel = skill.level;
  154. } else if (skill.skillHrid.includes("ranged")) {
  155. exportObj.player.rangedLevel = skill.level;
  156. } else if (skill.skillHrid.includes("magic")) {
  157. exportObj.player.magicLevel = skill.level;
  158. }
  159. }
  160. //战斗技能等级
  161. exportObj.abilities = [
  162. {
  163. abilityHrid: "",
  164. level: "1",
  165. },
  166. {
  167. abilityHrid: "",
  168. level: "1",
  169. },
  170. {
  171. abilityHrid: "",
  172. level: "1",
  173. },
  174. {
  175. abilityHrid: "",
  176. level: "1",
  177. },
  178. {
  179. abilityHrid: "",
  180. level: "1",
  181. },
  182. ];
  183. let clientObj = JSON.parse(GM_getValue("init_client_data"));
  184. let normalAbillityIndex = 1;
  185. for (const ability of obj.profile.equippedAbilities) {
  186. if (ability && clientObj.abilityDetailMap[ability.abilityHrid].isSpecialAbility) {
  187. exportObj.abilities[0] = {
  188. abilityHrid: ability.abilityHrid,
  189. level: ability.level,
  190. };
  191. } else if (ability) {
  192. exportObj.abilities[normalAbillityIndex++] = {
  193. abilityHrid: ability.abilityHrid,
  194. level: ability.level,
  195. };
  196. }
  197. }
  198. //吃喝
  199. exportObj.food={};
  200. exportObj.drinks={};
  201. exportObj.food["/action_types/combat"] = [];
  202. exportObj.drinks["/action_types/combat"] = [];
  203. let weapon=obj.profile.wearableItemMap["/item_locations/main_hand"]?.itemHrid||obj.profile.wearableItemMap["/item_locations/two_hand"]?.itemHrid;
  204. if(weapon.includes("shooter")||weapon.includes("bow")){
  205. //远程
  206. // xp,超远,暴击
  207. exportObj.drinks["/action_types/combat"].push({
  208. itemHrid:"/items/wisdom_coffee"
  209. });
  210. exportObj.drinks["/action_types/combat"].push({
  211. itemHrid:"/items/super_ranged_coffee"
  212. });
  213. exportObj.drinks["/action_types/combat"].push({
  214. itemHrid:"/items/critical_coffee"
  215. });
  216. // 2红1蓝
  217. exportObj.food["/action_types/combat"].push({
  218. itemHrid:"/items/spaceberry_donut"
  219. });
  220. exportObj.food["/action_types/combat"].push({
  221. itemHrid:"/items/spaceberry_cake"
  222. });
  223. exportObj.food["/action_types/combat"].push({
  224. itemHrid:"/items/star_fruit_yogurt"
  225. });
  226. }else if (weapon.includes("boomstick")||weapon.includes("staff")){
  227. //法师
  228. //xp,超魔,吟唱
  229. exportObj.drinks["/action_types/combat"].push({
  230. itemHrid:"/items/wisdom_coffee"
  231. });
  232. exportObj.drinks["/action_types/combat"].push({
  233. itemHrid:"/items/super_magic_coffee"
  234. });
  235. exportObj.drinks["/action_types/combat"].push({
  236. itemHrid:"/items/channeling_coffee"
  237. });
  238. // 1红2蓝
  239. exportObj.food["/action_types/combat"].push({
  240. itemHrid:"/items/spaceberry_cake"
  241. });
  242. exportObj.food["/action_types/combat"].push({
  243. itemHrid:"/items/star_fruit_gummy"
  244. });
  245. exportObj.food["/action_types/combat"].push({
  246. itemHrid:"/items/star_fruit_yogurt"
  247. });
  248. }else if (weapon.includes("bulwark")){
  249. //双手盾 精暮光
  250. //xp,超防,超耐 xp,超智,幸运
  251. exportObj.drinks["/action_types/combat"].push({
  252. itemHrid:"/items/wisdom_coffee"
  253. });
  254. exportObj.drinks["/action_types/combat"].push({
  255. itemHrid:"/items/super_defense_coffee"
  256. });
  257. exportObj.drinks["/action_types/combat"].push({
  258. itemHrid:"/items/super_stamina_coffee"
  259. });
  260. // 2红1蓝
  261. exportObj.food["/action_types/combat"].push({
  262. itemHrid:"/items/spaceberry_donut"
  263. });
  264. exportObj.food["/action_types/combat"].push({
  265. itemHrid:"/items/spaceberry_cake"
  266. });
  267. exportObj.food["/action_types/combat"].push({
  268. itemHrid:"/items/star_fruit_yogurt"
  269. });
  270. }else{
  271. //战士
  272. //xp,超力,迅捷
  273. exportObj.drinks["/action_types/combat"].push({
  274. itemHrid:"/items/wisdom_coffee"
  275. });
  276. exportObj.drinks["/action_types/combat"].push({
  277. itemHrid:"/items/super_power_coffee"
  278. });
  279. exportObj.drinks["/action_types/combat"].push({
  280. itemHrid:"/items/swiftness_coffee"
  281. });
  282. // 2红1蓝
  283. exportObj.food["/action_types/combat"].push({
  284. itemHrid:"/items/spaceberry_donut"
  285. });
  286. exportObj.food["/action_types/combat"].push({
  287. itemHrid:"/items/spaceberry_cake"
  288. });
  289. exportObj.food["/action_types/combat"].push({
  290. itemHrid:"/items/star_fruit_yogurt"
  291. });
  292. }
  293. //队内检测
  294. let team_data = GM_getValue("team_battle", "");
  295. let team_obj = JSON.parse(team_data);
  296. if(team_obj){
  297. for(const team_member of team_obj.players){
  298. if(team_member.name==player_name){
  299. for(const food_or_drink of team_member.combatConsumables){
  300. if(food_or_drink.itemHrid.includes('coffee')){
  301. exportObj.drinks["/action_types/combat"].push({
  302. itemHrid:food_or_drink.itemHrid,
  303. });
  304. }else{
  305. exportObj.food["/action_types/combat"].push({
  306. itemHrid:food_or_drink.itemHrid,
  307. });
  308. }
  309. }
  310. }
  311. }
  312. }
  313. //我不知道有什么用,以防万一写一下,和原版保持一致
  314. exportObj.food["/action_types/combat"].push({
  315. itemHrid:"",
  316. });
  317. exportObj.drinks["/action_types/combat"].push({
  318. itemHrid:"",
  319. });
  320.  
  321. let sim_json_value=JSON.stringify(exportObj);
  322. //按钮粘贴
  323. let button = document.createElement("button");
  324. selectedElement.appendChild(button);
  325. button.textContent = "获取模拟器导入数据";
  326. button.style.borderRadius = '5px';
  327. button.style.height = '30px';
  328. button.style.backgroundColor = '#4357AF';
  329. button.style.color = 'white';
  330. button.style.boxShadow = 'none';
  331. button.style.border = '0px';
  332. button.onclick = function () {
  333. navigator.clipboard.writeText(sim_json_value);
  334. button.textContent = "导入数据已粘贴到剪切板";
  335. return false;
  336. };
  337. return false;
  338. };
  339. };
  340. let timer = setInterval(checkElem, 200);
  341. }
  342.  
  343. function addImportButton1() {
  344. const checkElem = () => {
  345. const selectedElement = document.querySelector(`div[role="tablist"]`);
  346. if (selectedElement) {
  347. clearInterval(timer);
  348. console.log("Mooneycalc-Importer: Found elem");
  349. let button = document.createElement("button");
  350. selectedElement.parentNode.insertBefore(button, selectedElement.nextSibling);
  351. button.textContent = isZH
  352. ? "导入人物数据 (左边的市场里可以改价差,设置里可启用茶)"
  353. : "Import character settings (Refresh game page to update character settings)";
  354. button.style.backgroundColor = "green";
  355. button.style.padding = "5px";
  356. button.onclick = function () {
  357. console.log("Mooneycalc-Importer: Button onclick");
  358. importData1(button);
  359. return false;
  360. };
  361. }
  362. };
  363. let timer = setInterval(checkElem, 200);
  364. }
  365.  
  366. async function importData1(button) {
  367. let data = GM_getValue("init_character_data");
  368. let obj = JSON.parse(data);
  369.  
  370. if (!obj || !obj.characterSkills || !obj.currentTimestamp) {
  371. button.textContent = isZH ? "错误:没有人物数据" : "Error: no character settings found";
  372. return;
  373. }
  374.  
  375. let ls = constructMooneycalcLocalStorage(obj);
  376. localStorage.setItem("settings", ls);
  377.  
  378. let timestamp = new Date(obj.currentTimestamp).getTime();
  379. let now = new Date().getTime();
  380. button.textContent = isZH
  381. ? "已导入,人物数据更新时间:" + timeReadable(now - timestamp) + " 前"
  382. : "Imported, updated " + timeReadable(now - timestamp) + " ago";
  383.  
  384. await new Promise((r) => setTimeout(r, 500));
  385. location.reload();
  386. }
  387.  
  388. function constructMooneycalcLocalStorage(obj) {
  389. const ls = localStorage.getItem("settings");
  390. let lsObj = JSON.parse(ls);
  391.  
  392. if (!lsObj) {
  393. lsObj = {};
  394. }
  395.  
  396. // 人物技能等级
  397. lsObj.state.settings.levels = {};
  398. for (const skill of obj.characterSkills) {
  399. lsObj.state.settings.levels[skill.skillHrid] = skill.level;
  400. }
  401.  
  402. // 社区全局buff
  403. lsObj.state.settings.communityBuffs = {};
  404. for (const buff of obj.communityBuffs) {
  405. lsObj.state.settings.communityBuffs[buff.hrid] = buff.level;
  406. }
  407.  
  408. // 装备 & 装备强化等级
  409. lsObj.state.settings.equipment = {};
  410. lsObj.state.settings.equipmentLevels = {};
  411. for (const item of obj.characterItems) {
  412. lsObj.state.settings.equipment[item.itemLocationHrid.replace("item_locations", "equipment_types")] = item.itemHrid;
  413. lsObj.state.settings.equipmentLevels[item.itemLocationHrid.replace("item_locations", "equipment_types")] = item.enhancementLevel;
  414. }
  415.  
  416. // 房子
  417. lsObj.state.settings.houseRooms = {};
  418. for (const house of Object.values(obj.characterHouseRoomMap)) {
  419. lsObj.state.settings.houseRooms[house.houseRoomHrid] = house.level;
  420. }
  421.  
  422. return JSON.stringify(lsObj);
  423. }
  424.  
  425. function timeReadable(ms) {
  426. const d = new Date(1000 * Math.round(ms / 1000));
  427. function pad(i) {
  428. return ("0" + i).slice(-2);
  429. }
  430. let str = d.getUTCHours() + ":" + pad(d.getUTCMinutes()) + ":" + pad(d.getUTCSeconds());
  431. console.log("Mooneycalc-Importer: " + str);
  432. return str;
  433. }
  434.  
  435. function addImportButton4() {
  436. const checkElem = () => {
  437. const selectedElement = document.querySelector(`button#buttonImportExport`);
  438. if (selectedElement) {
  439. clearInterval(timer);
  440. console.log("Mooneycalc-Importer: Found elem");
  441. let profileArr = GM_getValue("profile_arr");
  442.  
  443. generateComboList(profileArr);
  444.  
  445. let button = document.createElement("button");
  446. selectedElement.parentNode.parentElement.parentElement.insertBefore(button, selectedElement.parentElement.parentElement.nextSibling);
  447. button.textContent = isZH
  448. ? "导入人物数据(刷新游戏网页更新人物数据)"
  449. : "Import character settings (Refresh game page to update character settings)";
  450. button.style.backgroundColor = "green";
  451. button.style.padding = "5px";
  452. button.onclick = function () {
  453.  
  454. console.log("Mooneycalc-Importer: Button onclick");
  455. const getPriceButton = document.querySelector(`button#buttonGetPrices`);
  456. if (getPriceButton) {
  457. console.log("Click getPriceButton");
  458. getPriceButton.click();
  459. }
  460.  
  461. let checkedProfile = getSelectedValue()
  462.  
  463. importData4(button,checkedProfile);
  464. return false;
  465. };
  466. }
  467. };
  468. let timer = setInterval(checkElem, 200);
  469. }
  470.  
  471. function getSelectedValue() {
  472. let select = document.getElementById("profile-combo-container");
  473. let selectedValue = select.value;
  474. return selectedValue;
  475. }
  476.  
  477. async function importData4(button,checkedProfile) {
  478. let playerInfo = JSON.parse(GM_getValue("profile_"+ checkedProfile));
  479.  
  480. let playerArr = [];
  481. if ( playerInfo.partyInfo ) {
  482. Object.values(playerInfo.partyInfo.sharableCharacterMap).forEach((value,key) => { playerArr.push(value.name); });
  483. }else{
  484. playerArr.push(playerInfo.character.name)
  485. }
  486.  
  487. playerArr.sort();
  488.  
  489. let data = GM_getValue("team_battle_" + playerArr[0], "");
  490.  
  491. let obj
  492. if (!data) {
  493. button.textContent = isZH ? "错误:没有人物数据" : "Error: no character settings found";
  494. return;
  495. }else{
  496. obj = JSON.parse(data);
  497. }
  498.  
  499. const jsonObj = constructImportJsonObj_team(obj,checkedProfile);
  500.  
  501. for(let i=1;i<=jsonObj.player_number;i++){
  502. const importInputElem = document.querySelector(`input#inputSetGroupCombatplayer`+i);
  503. importInputElem.value = JSON.stringify(jsonObj.players[i-1]);
  504. }
  505. document.querySelector(`button#buttonImportSet`).click();
  506.  
  507. let timestamp = new Date(obj.combatStartTime).getTime();
  508. let now = new Date().getTime();
  509. button.textContent = isZH
  510. ? "已导入,人物数据更新时间:" + timeReadable(now - timestamp) + " 前"
  511. : "Imported, updated " + timeReadable(now - timestamp) + " ago";
  512.  
  513. if (document.URL.includes(`//amvoidguy.github.io/`)) {
  514. setTimeout(() => {
  515. const selectZone = document.querySelector(`select#selectZone`);
  516. for (let i = 0; i < selectZone.options.length; i++) {
  517. if (selectZone.options[i].value == jsonObj.zone) {
  518. selectZone.options[i].selected = true;
  519. break;
  520. }
  521. }
  522.  
  523. const player1Checkbox = document.querySelector(`input#player1.form-check-input.player-checkbox`);
  524. const player2Checkbox = document.querySelector(`input#player2.form-check-input.player-checkbox`);
  525. const player3Checkbox = document.querySelector(`input#player3.form-check-input.player-checkbox`);
  526. switch (jsonObj.player_number) {
  527. case 1:
  528. player1Checkbox.checked = true;
  529. break;
  530. case 2:
  531. player1Checkbox.checked = true;
  532. player2Checkbox.checked = true;
  533. break;
  534. case 3:
  535. player1Checkbox.checked = true;
  536. player2Checkbox.checked = true;
  537. player3Checkbox.checked = true;
  538. break;
  539. }
  540. const time_set = document.querySelector(`input#inputSimulationTime`);
  541. time_set.value=jsonObj.simulationTime
  542. //无法获取别人的触发器会报错
  543. const errorModal = document.querySelector('div#errorModal');
  544. if (errorModal) {
  545. errorModal.parentNode.removeChild(errorModal);
  546. }
  547.  
  548. document.querySelector(`button#buttonStartSimulation`).click();
  549. document.querySelector(`button.btn.btn-secondary`).click();
  550. }, 500);
  551. }
  552. }
  553.  
  554.  
  555. function generateComboList(profileArr) {
  556. const container = document.getElementById("profile-combo-container");
  557.  
  558. profileArr.forEach(item => {
  559.  
  560. let option = document.createElement("option");
  561. option.value = item;
  562. let labelText = item + " Party:";
  563. let profile = GM_getValue("profile_"+ item);
  564. if (!profile) return;
  565. let playerInfo = JSON.parse(profile);
  566. let partyMember = [];
  567.  
  568. if ( playerInfo.partyInfo ) Object.values(playerInfo.partyInfo.sharableCharacterMap).forEach((value,key) => { partyMember.push(value.name); });
  569. partyMember.reverse();
  570. labelText += partyMember.join(", ");
  571.  
  572. option.textContent = labelText;
  573. container.appendChild(option);
  574. })
  575. }
  576.  
  577.  
  578. function constructImportJsonObj_team(obj,checkedProfile) {
  579. let clientObj = JSON.parse(GM_getValue("init_client_data"));
  580. let init_character_obj = JSON.parse(GM_getValue("profile_"+ checkedProfile));
  581. let exportObj = {};
  582.  
  583. exportObj.player_number=obj.players.length;
  584. exportObj.players = {};
  585.  
  586. //看一圈所有人配置
  587. for(let player_num=0;player_num<obj.players.length;player_num++ ){
  588. // Levels
  589. exportObj.players[player_num]={};
  590. exportObj.players[player_num].player={};
  591. // Items
  592. // HouseRooms
  593. exportObj.players[player_num].player.equipment = [];
  594. exportObj.players[player_num].houseRooms = {};
  595. if(obj.players[player_num].character.name==init_character_obj.character.name){
  596. //自己的装备不用看,直接从init里取
  597. for (const item of init_character_obj.characterItems) {
  598. if (!item.itemLocationHrid.includes("/item_locations/inventory")) {
  599. exportObj.players[player_num].player.equipment.push({
  600. itemLocationHrid: item.itemLocationHrid,
  601. itemHrid: item.itemHrid,
  602. enhancementLevel: item.enhancementLevel,
  603. });
  604. }
  605. }
  606. //自己房子
  607. for (const house of Object.values(init_character_obj.characterHouseRoomMap)) {
  608. exportObj.players[player_num].houseRooms[house.houseRoomHrid] = house.level;
  609. }
  610. //level
  611. for (const skill of init_character_obj.characterSkills) {
  612. if (skill.skillHrid.includes("stamina")) {
  613. exportObj.players[player_num].player.staminaLevel = skill.level;
  614. } else if (skill.skillHrid.includes("intelligence")) {
  615. exportObj.players[player_num].player.intelligenceLevel = skill.level;
  616. } else if (skill.skillHrid.includes("attack")) {
  617. exportObj.players[player_num].player.attackLevel = skill.level;
  618. } else if (skill.skillHrid.includes("power")) {
  619. exportObj.players[player_num].player.powerLevel = skill.level;
  620. } else if (skill.skillHrid.includes("defense")) {
  621. exportObj.players[player_num].player.defenseLevel = skill.level;
  622. } else if (skill.skillHrid.includes("ranged")) {
  623. exportObj.players[player_num].player.rangedLevel = skill.level;
  624. } else if (skill.skillHrid.includes("magic")) {
  625. exportObj.players[player_num].player.magicLevel = skill.level;
  626. }
  627. }
  628. }else{
  629. //手动取装备数据
  630. let team_mate_str=GM_getValue(obj.players[player_num].character.name, "")
  631. console.log("team_mate_str",team_mate_str);
  632. if (!team_mate_str) {
  633. alert("请手动查看一下队友装备信息,当前缺失装备信息队友:"+obj.players[player_num].character.name);
  634. // console.log( "不敢模拟点击,请手动查看一下队友装备信息,当前缺失装备信息队友:"+obj.players[player_num].character.name);
  635. return;
  636. }
  637. let team_mate_obj = JSON.parse(GM_getValue(obj.players[player_num].character.name, ""));
  638. console.log("team_mate_obj",team_mate_obj);
  639. for (const key in team_mate_obj.profile.wearableItemMap) {
  640. const item = team_mate_obj.profile.wearableItemMap[key];
  641. exportObj.players[player_num].player.equipment.push({
  642. itemLocationHrid: item.itemLocationHrid,
  643. itemHrid: item.itemHrid,
  644. enhancementLevel: item.enhancementLevel,
  645. });
  646. }
  647. //别人房子
  648. for (const house of Object.values(team_mate_obj.profile.characterHouseRoomMap)) {
  649. exportObj.players[player_num].houseRooms[house.houseRoomHrid] = house.level;
  650. }
  651. //level
  652. for (const skill of team_mate_obj.profile.characterSkills) {
  653. if (skill.skillHrid.includes("stamina")) {
  654. exportObj.players[player_num].player.staminaLevel = skill.level;
  655. } else if (skill.skillHrid.includes("intelligence")) {
  656. exportObj.players[player_num].player.intelligenceLevel = skill.level;
  657. } else if (skill.skillHrid.includes("attack")) {
  658. exportObj.players[player_num].player.attackLevel = skill.level;
  659. } else if (skill.skillHrid.includes("power")) {
  660. exportObj.players[player_num].player.powerLevel = skill.level;
  661. } else if (skill.skillHrid.includes("defense")) {
  662. exportObj.players[player_num].player.defenseLevel = skill.level;
  663. } else if (skill.skillHrid.includes("ranged")) {
  664. exportObj.players[player_num].player.rangedLevel = skill.level;
  665. } else if (skill.skillHrid.includes("magic")) {
  666. exportObj.players[player_num].player.magicLevel = skill.level;
  667. }
  668. }
  669. }
  670. exportObj.players[player_num].food={};
  671. exportObj.players[player_num].drinks={};
  672. exportObj.players[player_num].food["/action_types/combat"] = [];
  673. exportObj.players[player_num].drinks["/action_types/combat"] = [];
  674. for(const food_or_drink of obj.players[player_num].combatConsumables){
  675. if(food_or_drink.itemHrid.includes('coffee')){
  676. exportObj.players[player_num].drinks["/action_types/combat"].push({
  677. itemHrid:food_or_drink.itemHrid,
  678. });
  679. }else{
  680. exportObj.players[player_num].food["/action_types/combat"].push({
  681. itemHrid:food_or_drink.itemHrid,
  682. });
  683. }
  684. }
  685. //我不知道有什么用,以防万一写一下,和原版保持一致
  686. exportObj.players[player_num].food["/action_types/combat"].push({
  687. itemHrid:"",
  688. });
  689. exportObj.players[player_num].drinks["/action_types/combat"].push({
  690. itemHrid:"",
  691. });
  692. // Abilities
  693. exportObj.players[player_num].abilities = [
  694. {
  695. abilityHrid: "",
  696. level: "1",
  697. },
  698. {
  699. abilityHrid: "",
  700. level: "1",
  701. },
  702. {
  703. abilityHrid: "",
  704. level: "1",
  705. },
  706. {
  707. abilityHrid: "",
  708. level: "1",
  709. },
  710. {
  711. abilityHrid: "",
  712. level: "1",
  713. },
  714. ];
  715. let normalAbillityIndex = 1;
  716. for (const ability of obj.players[player_num].combatAbilities) {
  717. if (ability && clientObj.abilityDetailMap[ability.abilityHrid].isSpecialAbility) {
  718. exportObj.players[player_num].abilities[0] = {
  719. abilityHrid: ability.abilityHrid,
  720. level: ability.level,
  721. };
  722. } else if (ability) {
  723. exportObj.players[player_num].abilities[normalAbillityIndex++] = {
  724. abilityHrid: ability.abilityHrid,
  725. level: ability.level,
  726. };
  727. }
  728. }
  729. // TriggerMap 获取不了别人的触发器
  730. if(obj.players[player_num].character.name==init_character_obj.character.name){
  731. exportObj.players[player_num].triggerMap = { ...init_character_obj.abilityCombatTriggersMap, ...init_character_obj.consumableCombatTriggersMap };
  732. }
  733.  
  734.  
  735. }
  736. // Zone
  737. let hasMap = false;
  738. for (const action of init_character_obj.characterActions) {
  739. if (
  740. action &&
  741. action.actionHrid.includes("/actions/combat/")
  742. ) {
  743. hasMap = true;
  744. exportObj.zone = action.actionHrid;
  745. break;
  746. }
  747. }
  748. if (!hasMap) {
  749. exportObj.zone = "/actions/combat/fly";
  750. }
  751. // SimulationTime
  752. exportObj.simulationTime = SCRIPT_SIMULATE_TIME;
  753. return exportObj;
  754. }
  755.  
  756.  
  757. function constructImportJsonObj(obj) {
  758. let clientObj = JSON.parse(GM_getValue("init_client_data"));
  759.  
  760. let exportObj = {};
  761.  
  762. exportObj.player = {};
  763. // Levels
  764. for (const skill of obj.characterSkills) {
  765. if (skill.skillHrid.includes("stamina")) {
  766. exportObj.player.staminaLevel = skill.level;
  767. } else if (skill.skillHrid.includes("intelligence")) {
  768. exportObj.player.intelligenceLevel = skill.level;
  769. } else if (skill.skillHrid.includes("attack")) {
  770. exportObj.player.attackLevel = skill.level;
  771. } else if (skill.skillHrid.includes("power")) {
  772. exportObj.player.powerLevel = skill.level;
  773. } else if (skill.skillHrid.includes("defense")) {
  774. exportObj.player.defenseLevel = skill.level;
  775. } else if (skill.skillHrid.includes("ranged")) {
  776. exportObj.player.rangedLevel = skill.level;
  777. } else if (skill.skillHrid.includes("magic")) {
  778. exportObj.player.magicLevel = skill.level;
  779. }
  780. }
  781. // Items
  782. exportObj.player.equipment = [];
  783. for (const item of obj.characterItems) {
  784. if (!item.itemLocationHrid.includes("/item_locations/inventory")) {
  785. exportObj.player.equipment.push({
  786. itemLocationHrid: item.itemLocationHrid,
  787. itemHrid: item.itemHrid,
  788. enhancementLevel: item.enhancementLevel,
  789. });
  790. }
  791. }
  792.  
  793. // Food
  794. exportObj.food = {};
  795. exportObj.food["/action_types/combat"] = [];
  796. for (const food of obj.actionTypeFoodSlotsMap["/action_types/combat"]) {
  797. if (food) {
  798. exportObj.food["/action_types/combat"].push({
  799. itemHrid: food.itemHrid,
  800. });
  801. } else {
  802. exportObj.food["/action_types/combat"].push({
  803. itemHrid: "",
  804. });
  805. }
  806. }
  807.  
  808. // Drinks
  809. exportObj.drinks = {};
  810. exportObj.drinks["/action_types/combat"] = [];
  811. for (const drink of obj.actionTypeDrinkSlotsMap["/action_types/combat"]) {
  812. if (drink) {
  813. exportObj.drinks["/action_types/combat"].push({
  814. itemHrid: drink.itemHrid,
  815. });
  816. } else {
  817. exportObj.drinks["/action_types/combat"].push({
  818. itemHrid: "",
  819. });
  820. }
  821. }
  822.  
  823. // Abilities
  824. exportObj.abilities = [
  825. {
  826. abilityHrid: "",
  827. level: "1",
  828. },
  829. {
  830. abilityHrid: "",
  831. level: "1",
  832. },
  833. {
  834. abilityHrid: "",
  835. level: "1",
  836. },
  837. {
  838. abilityHrid: "",
  839. level: "1",
  840. },
  841. {
  842. abilityHrid: "",
  843. level: "1",
  844. },
  845. ];
  846. let normalAbillityIndex = 1;
  847. for (const ability of obj.combatUnit.combatAbilities) {
  848. if (ability && clientObj.abilityDetailMap[ability.abilityHrid].isSpecialAbility) {
  849. exportObj.abilities[0] = {
  850. abilityHrid: ability.abilityHrid,
  851. level: ability.level,
  852. };
  853. } else if (ability) {
  854. exportObj.abilities[normalAbillityIndex++] = {
  855. abilityHrid: ability.abilityHrid,
  856. level: ability.level,
  857. };
  858. }
  859. }
  860.  
  861. // TriggerMap
  862. exportObj.triggerMap = { ...obj.abilityCombatTriggersMap, ...obj.consumableCombatTriggersMap };
  863.  
  864. // Zone
  865. let hasMap = false;
  866. for (const action of obj.characterActions) {
  867. if (
  868. action &&
  869. action.actionHrid.includes("/actions/combat/") &&
  870. !clientObj.actionDetailMap[action.actionHrid]?.combatZoneInfo?.isDungeon
  871. ) {
  872. hasMap = true;
  873. exportObj.zone = action.actionHrid;
  874. break;
  875. }
  876. }
  877. if (!hasMap) {
  878. exportObj.zone = "/actions/combat/fly";
  879. }
  880.  
  881. // SimulationTime
  882. exportObj.simulationTime = "24";
  883.  
  884. // HouseRooms
  885. exportObj.houseRooms = {};
  886. for (const house of Object.values(obj.characterHouseRoomMap)) {
  887. exportObj.houseRooms[house.houseRoomHrid] = house.level;
  888. }
  889.  
  890. return exportObj;
  891. }
  892.  
  893. async function observeResults() {
  894. let resultDiv = document.querySelector(`div.row`)?.querySelectorAll(`div.col-md-5`)?.[2]?.querySelector(`div.row > div.col-md-5`);
  895. if(document.URL.includes("mwisim.github.io")||document.URL.includes("simTest")){
  896. resultDiv = document.querySelectorAll(`div.row`)?.[1]?.querySelectorAll(`div.col-md-5`)?.[2]?.querySelector(`div.row > div.col-md-5`);
  897. }
  898. const deathDiv = document.querySelector(`div#simulationResultPlayerDeaths`);
  899. const expDiv = document.querySelector(`div#simulationResultExperienceGain`);
  900. const consumeDiv = document.querySelector(`div#simulationResultConsumablesUsed`);
  901. deathDiv.style.backgroundColor = "#FFEAE9";
  902. deathDiv.style.color = "black";
  903. expDiv.style.backgroundColor = "#CDFFDD";
  904. expDiv.style.color = "black";
  905. consumeDiv.style.backgroundColor = "#F0F8FF";
  906. consumeDiv.style.color = "black";
  907.  
  908. let div = document.createElement("div");
  909. div.id = "tillLevel";
  910. div.style.backgroundColor = "#FFFFE0";
  911. div.style.color = "black";
  912. div.textContent = "";
  913. resultDiv.append(div);
  914.  
  915. new MutationObserver((mutationsList) => {
  916. mutationsList.forEach((mutation) => {
  917. if (mutation.addedNodes.length >= 3) {
  918. handleResult(mutation.addedNodes, div);
  919. }
  920. });
  921. }).observe(expDiv, { childList: true, subtree: true });
  922. }
  923.  
  924. function handleResult(expNodes, parentDiv) {
  925. let perHourGainExp = {
  926. stamina: 0,
  927. intelligence: 0,
  928. attack: 0,
  929. power: 0,
  930. defense: 0,
  931. ranged: 0,
  932. magic: 0,
  933. };
  934. expNodes.forEach((expNodes) => {
  935. if (expNodes.textContent.includes("Stamina")||expNodes.textContent.includes("耐力")) {
  936. perHourGainExp.stamina = Number(expNodes.children[1].textContent);
  937. } else if (expNodes.textContent.includes("Intelligence")||expNodes.textContent.includes("智力")) {
  938. perHourGainExp.intelligence = Number(expNodes.children[1].textContent);
  939. } else if (expNodes.textContent.includes("Attack")||expNodes.textContent.includes("攻击")) {
  940. perHourGainExp.attack = Number(expNodes.children[1].textContent);
  941. } else if (expNodes.textContent.includes("Power")||expNodes.textContent.includes("力量")) {
  942. perHourGainExp.power = Number(expNodes.children[1].textContent);
  943. } else if (expNodes.textContent.includes("Defense")||expNodes.textContent.includes("防御")) {
  944. perHourGainExp.defense = Number(expNodes.children[1].textContent);
  945. } else if (expNodes.textContent.includes("Ranged")||expNodes.textContent.includes("远程")) {
  946. perHourGainExp.ranged = Number(expNodes.children[1].textContent);
  947. } else if (expNodes.textContent.includes("Magic")||expNodes.textContent.includes("魔法")) {
  948. perHourGainExp.magic = Number(expNodes.children[1].textContent);
  949. }
  950. });
  951.  
  952. let data = GM_getValue("init_character_data");
  953.  
  954. let obj = JSON.parse(data);
  955. if (!obj || !obj.characterSkills || !obj.currentTimestamp) {
  956. console.error("handleResult no character localstorage");
  957. return;
  958. }
  959.  
  960. let skillLevels = {};
  961. for (const skill of obj.characterSkills) {
  962. if (skill.skillHrid.includes("stamina")) {
  963. skillLevels.stamina = {};
  964. skillLevels.stamina.skillName = "Stamina";
  965. skillLevels.stamina.currentLevel = skill.level;
  966. skillLevels.stamina.currentExp = skill.experience;
  967. } else if (skill.skillHrid.includes("intelligence")) {
  968. skillLevels.intelligence = {};
  969. skillLevels.intelligence.skillName = "Intelligence";
  970. skillLevels.intelligence.currentLevel = skill.level;
  971. skillLevels.intelligence.currentExp = skill.experience;
  972. } else if (skill.skillHrid.includes("attack")) {
  973. skillLevels.attack = {};
  974. skillLevels.attack.skillName = "Attack";
  975. skillLevels.attack.currentLevel = skill.level;
  976. skillLevels.attack.currentExp = skill.experience;
  977. } else if (skill.skillHrid.includes("power")) {
  978. skillLevels.power = {};
  979. skillLevels.power.skillName = "Power";
  980. skillLevels.power.currentLevel = skill.level;
  981. skillLevels.power.currentExp = skill.experience;
  982. } else if (skill.skillHrid.includes("defense")) {
  983. skillLevels.defense = {};
  984. skillLevels.defense.skillName = "Defense";
  985. skillLevels.defense.currentLevel = skill.level;
  986. skillLevels.defense.currentExp = skill.experience;
  987. } else if (skill.skillHrid.includes("ranged")) {
  988. skillLevels.ranged = {};
  989. skillLevels.ranged.skillName = "Ranged";
  990. skillLevels.ranged.currentLevel = skill.level;
  991. skillLevels.ranged.currentExp = skill.experience;
  992. } else if (skill.skillHrid.includes("magic")) {
  993. skillLevels.magic = {};
  994. skillLevels.magic.skillName = "Magic";
  995. skillLevels.magic.currentLevel = skill.level;
  996. skillLevels.magic.currentExp = skill.experience;
  997. }
  998. }
  999.  
  1000. const skillNamesInOrder = ["stamina", "intelligence", "attack", "power", "defense", "ranged", "magic"];
  1001. let hTMLStr = "";
  1002. for (const skill of skillNamesInOrder) {
  1003. hTMLStr += `<div id="${"inputDiv_" + skill}" style="display: flex; justify-content: flex-end">${skillLevels[skill].skillName}${
  1004. isZH ? "到" : " to level "
  1005. }<input id="${"input_" + skill}" type="number" value="${skillLevels[skill].currentLevel + 1}" min="${
  1006. skillLevels[skill].currentLevel + 1
  1007. }" max="200">${isZH ? "" : ""}</div>`;
  1008. }
  1009.  
  1010. hTMLStr += `<div id="script_afterDays" style="display: flex; justify-content: flex-end"><input id="script_afterDays_input" type="number" value="1" min="0" max="200">${
  1011. isZH ? "天后" : "days after"
  1012. }</div>`;
  1013.  
  1014. hTMLStr += `<div id="needDiv"></div>`;
  1015. hTMLStr += `<div id="needListDiv"></div>`;
  1016. parentDiv.innerHTML = hTMLStr;
  1017.  
  1018. for (const skill of skillNamesInOrder) {
  1019. const skillDiv = parentDiv.querySelector(`div#${"inputDiv_" + skill}`);
  1020. const skillInput = parentDiv.querySelector(`input#${"input_" + skill}`);
  1021. skillInput.onchange = () => {
  1022. calculateTill(skill, skillInput, skillLevels, parentDiv, perHourGainExp);
  1023. };
  1024. skillInput.addEventListener("keyup", function (evt) {
  1025. calculateTill(skill, skillInput, skillLevels, parentDiv, perHourGainExp);
  1026. });
  1027. skillDiv.onclick = () => {
  1028. calculateTill(skill, skillInput, skillLevels, parentDiv, perHourGainExp);
  1029. };
  1030. }
  1031.  
  1032. const daysAfterDiv = parentDiv.querySelector(`div#script_afterDays`);
  1033. const daysAfterInput = parentDiv.querySelector(`input#script_afterDays_input`);
  1034. daysAfterInput.onchange = () => {
  1035. calculateAfterDays(daysAfterInput, skillLevels, parentDiv, perHourGainExp, skillNamesInOrder);
  1036. };
  1037. daysAfterInput.addEventListener("keyup", function (evt) {
  1038. calculateAfterDays(daysAfterInput, skillLevels, parentDiv, perHourGainExp, skillNamesInOrder);
  1039. });
  1040. daysAfterDiv.onclick = () => {
  1041. calculateAfterDays(daysAfterInput, skillLevels, parentDiv, perHourGainExp, skillNamesInOrder);
  1042. };
  1043.  
  1044. // 提取成本和收益
  1045. const expensesSpan = document.querySelector(`span#expensesSpan`);
  1046. const revenueSpan = document.querySelector(`span#revenueSpan`);
  1047. const profitSpan = document.querySelector(`span#profitPreview`);
  1048. const expenseDiv = document.querySelector(`div#script_expense`);
  1049. const revenueDiv = document.querySelector(`div#script_revenue`);
  1050. if (expenseDiv && expenseDiv) {
  1051. expenseDiv.textContent = expensesSpan.parentNode.textContent;
  1052. revenueDiv.textContent = revenueSpan.parentNode.textContent;
  1053. } else {
  1054. profitSpan.parentNode.insertAdjacentHTML(
  1055. "beforeend",
  1056. `<div id="script_expense" style="background-color: #DCDCDC; color: black;">${expensesSpan.parentNode.textContent}</div><div id="script_revenue" style="background-color: #DCDCDC; color: black;">${revenueSpan.parentNode.textContent}</div>`
  1057. );
  1058. }
  1059. }
  1060.  
  1061. function calculateAfterDays(daysAfterInput, skillLevels, parentDiv, perHourGainExp, skillNamesInOrder) {
  1062. const initData_levelExperienceTable = JSON.parse(GM_getValue("init_client_data")).levelExperienceTable;
  1063. const days = Number(daysAfterInput.value);
  1064. parentDiv.querySelector(`div#needDiv`).textContent = `${isZH ? "" : "After"} ${days} ${isZH ? "天后:" : "days: "}`;
  1065. const listDiv = parentDiv.querySelector(`div#needListDiv`);
  1066.  
  1067. let html = "";
  1068. let resultLevels = {};
  1069. for (const skillName of skillNamesInOrder) {
  1070. for (const skill of Object.values(skillLevels)) {
  1071. if (skill.skillName.toLowerCase() === skillName.toLowerCase()) {
  1072. const exp = skill.currentExp + perHourGainExp[skill.skillName.toLowerCase()] * days * 24;
  1073. let level = 1;
  1074. while (initData_levelExperienceTable[level] < exp) {
  1075. level++;
  1076. }
  1077. level--;
  1078. const minExpAtLevel = initData_levelExperienceTable[level];
  1079. const maxExpAtLevel = initData_levelExperienceTable[level + 1] - 1;
  1080. const expSpanInLevel = maxExpAtLevel - minExpAtLevel;
  1081. const levelPercentage = Number(((exp - minExpAtLevel) / expSpanInLevel) * 100).toFixed(1);
  1082. resultLevels[skillName.toLowerCase()] = level;
  1083. html += `<div>${skill.skillName} ${isZH ? "" : "level"} ${level} ${isZH ? "级" : ""} ${levelPercentage}%</div>`;
  1084. break;
  1085. }
  1086. }
  1087. }
  1088. const combatLevel =
  1089. 0.2 * (resultLevels.stamina + resultLevels.intelligence + resultLevels.defense) +
  1090. 0.4 * Math.max(0.5 * (resultLevels.attack + resultLevels.power), resultLevels.ranged, resultLevels.magic);
  1091. html += `<div>${isZH ? "战斗等级:" : "Combat level: "} ${combatLevel.toFixed(1)}</div>`;
  1092. listDiv.innerHTML = html;
  1093. }
  1094.  
  1095. function calculateTill(skillName, skillInputElem, skillLevels, parentDiv, perHourGainExp) {
  1096. const initData_levelExperienceTable = JSON.parse(GM_getValue("init_client_data")).levelExperienceTable;
  1097. const targetLevel = Number(skillInputElem.value);
  1098. parentDiv.querySelector(`div#needDiv`).textContent = `${skillLevels[skillName].skillName} ${isZH ? "到" : "to level"} ${targetLevel} ${
  1099. isZH ? "级 还需:" : " takes: "
  1100. }`;
  1101. const listDiv = parentDiv.querySelector(`div#needListDiv`);
  1102.  
  1103. const currentLevel = Number(skillLevels[skillName].currentLevel);
  1104. const currentExp = Number(skillLevels[skillName].currentExp);
  1105. if (targetLevel > currentLevel && targetLevel <= 200) {
  1106. if (perHourGainExp[skillName] === 0) {
  1107. listDiv.innerHTML = isZH ? "永远" : "Forever";
  1108. } else {
  1109. let needExp = initData_levelExperienceTable[targetLevel] - currentExp;
  1110. let needHours = needExp / perHourGainExp[skillName];
  1111. let html = "";
  1112. html += `<div>[${hoursToReadableString(needHours)}]</div>`;
  1113.  
  1114. const consumeDivs = document.querySelectorAll(`div#simulationResultConsumablesUsed div.row`);
  1115. for (const elem of consumeDivs) {
  1116. const conName = elem.children[0].textContent;
  1117. const conPerHour = Number(elem.children[1].textContent);
  1118. html += `<div>${conName} ${Number(conPerHour * needHours).toFixed(0)}</div>`;
  1119. }
  1120.  
  1121. listDiv.innerHTML = html;
  1122. }
  1123. } else {
  1124. listDiv.innerHTML = isZH ? "输入错误" : "Input error";
  1125. }
  1126. }
  1127.  
  1128. function hoursToReadableString(hours) {
  1129. const sec = hours * 60 * 60;
  1130. if (sec >= 86400) {
  1131. return Number(sec / 86400).toFixed(1) + (isZH ? " 天" : " days");
  1132. }
  1133. const d = new Date(Math.round(sec * 1000));
  1134. function pad(i) {
  1135. return ("0" + i).slice(-2);
  1136. }
  1137. let str = d.getUTCHours() + "h " + pad(d.getUTCMinutes()) + "m " + pad(d.getUTCSeconds()) + "s";
  1138. return str;
  1139. }
  1140. })();