Melvor Snippets

Collection of various snippets

当前为 2021-09-09 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Melvor Snippets
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.0.2
  5. // @description Collection of various snippets
  6. // @author GMiclotte
  7. // @match https://*.melvoridle.com/*
  8. // @exclude https://wiki.melvoridle.com*
  9. // @noframes
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. function script() {
  14. // header end
  15.  
  16. ////////////////////////
  17. //obstacle build count//
  18. ////////////////////////
  19.  
  20. listObstaclesWithFewerThanTenBuilds = () => {
  21. // show agility obstacles that have been built less than 10 times
  22. agilityObstacleBuildCount.map((_, i) => i)
  23. .filter(i => agilityObstacleBuildCount[i] < 10)
  24. .map(i => agilityObstacles[i])
  25. .map(x => [x.category + 1, x.name]);
  26. }
  27.  
  28. /////////////////////////////
  29. //Defense Pure Calculations//
  30. /////////////////////////////
  31. window.defensePure = {};
  32.  
  33. defensePure.defLvlToHPLvl = def => {
  34. const hpXP = exp.level_to_xp(10) + 1;
  35. const minDefXP = exp.level_to_xp(def) + 1;
  36. const maxDefXP = exp.level_to_xp(def + 1);
  37. const minHpXP = hpXP + minDefXP / 3;
  38. const maxHpXP = hpXP + maxDefXP / 3;
  39. const minHp = exp.xp_to_level(minHpXP) - 1;
  40. const maxHp = exp.xp_to_level(maxHpXP) - 1;
  41. return {min: minHp, max: maxHp};
  42. }
  43.  
  44. defensePure.defLvlToCbLvl = def => {
  45. const hp = defensePure.defLvlToHPLvl(def);
  46. const att = 1, str = 1, ran = 1, mag = 1, pray = 1;
  47. const minBase = (def + hp.min + Math.floor(pray / 2)) / 4;
  48. const maxBase = (def + hp.max + Math.floor(pray / 2)) / 4;
  49. const melee = (att + str) * 1.3 / 8;
  50. const ranged = Math.floor(1.5 * ran) * 1.3 / 8;
  51. const magic = Math.floor(1.5 * mag) * 1.3 / 8;
  52. const best = Math.max(melee, ranged, magic);
  53. return {min: minBase + best, max: maxBase + best};
  54. }
  55.  
  56. defensePure.lastHitOnly = (skillID, maxLevel = 1) => {
  57. if (skillXP[skillID] >= exp.level_to_xp(maxLevel + 1) - 1) {
  58. combatManager.stopCombat();
  59. return;
  60. }
  61. // swap weapon based on hp left
  62. let itemID;
  63. if (combatManager.enemy.hitpoints > 1) {
  64. if (skillID === CONSTANTS.skill.Magic) {
  65. itemID = CONSTANTS.item.Normal_Shortbow;
  66. } else {
  67. // melee or ranged
  68. itemID = CONSTANTS.item.Staff_of_Air;
  69. }
  70. } else {
  71. if (skillID === CONSTANTS.skill.Ranged) {
  72. itemID = CONSTANTS.item.Iron_Throwing_Knife;
  73. } else if (skillID === CONSTANTS.skill.Magic) {
  74. itemID = CONSTANTS.item.Staff_of_Air;
  75. } else {
  76. // melee
  77. itemID = -1;
  78. }
  79. }
  80. if (player.equipment.slots.Weapon.item.id !== itemID) {
  81. if (itemID === -1) {
  82. player.unequipItem(0, 'Weapon');
  83. } else {
  84. player.equipItem(itemID, 0);
  85. }
  86. }
  87. // loop
  88. setTimeout(() => defensePure.lastHitOnly(skillID, maxLevel), 1000);
  89. }
  90. ////////////////
  91. //Mastery bars//
  92. ////////////////
  93. setInterval(() => {
  94. for (const id in SKILLS) {
  95. if (SKILLS[id].hasMastery) {
  96. if ($(`#skill-nav-mastery-${id} .progress-bar`)[0]) {
  97. $(`#skill-nav-mastery-${id} .progress-bar`)[0].style.width =
  98. (MASTERY[id].pool / getMasteryPoolTotalXP(id)) * 100 + '%';
  99. if (MASTERY[id].pool < getMasteryPoolTotalXP(id)) {
  100. $(`#skill-nav-mastery-${id}`)[0].style.setProperty('background', 'rgb(76,80,84)', 'important');
  101. $(`#skill-nav-mastery-${id} .progress-bar`)[0].className = 'progress-bar bg-warning';
  102. } else {
  103. $(`#skill-nav-mastery-${id}`)[0].style.setProperty('background', 'rgb(48,199,141)', 'success');
  104. $(`#skill-nav-mastery-${id} .progress-bar`)[0].className = 'progress-bar bg-success';
  105. }
  106. const tip = $(`#skill-nav-mastery-${id}`)[0]._tippy;
  107. tip.setContent((Math.min(1, MASTERY[id].pool / getMasteryPoolTotalXP(id)) * 100).toFixed(2) + '%');
  108. } else {
  109. const skillItem = $(`#skill-nav-name-${id}`)[0].parentNode;
  110. skillItem.style.flexWrap = 'wrap';
  111. skillItem.style.setProperty('padding-top', '.25rem', 'important');
  112. const progress = document.createElement('div');
  113. const progressBar = document.createElement('div');
  114. progress.id = `skill-nav-mastery-${id}`;
  115. progress.className = 'progress active pointer-enabled';
  116. progress.style.height = '6px';
  117. progress.style.width = '100%';
  118. progress.style.margin = '.25rem 0rem';
  119. if (MASTERY[id].pool < getMasteryPoolTotalXP(id)) {
  120. progress.style.setProperty('background', 'rgb(76,80,84)', 'important');
  121. progressBar.className = 'progress-bar bg-warning';
  122. } else {
  123. progress.style.setProperty('background', 'rgb(48,199,141)', 'success');
  124. progressBar.className = 'progress-bar bg-success';
  125. }
  126. progressBar.style.width = (MASTERY[id].pool / getMasteryPoolTotalXP(id)) * 100 + '%';
  127. progress.appendChild(progressBar);
  128. skillItem.appendChild(progress);
  129. tippy($(`#skill-nav-mastery-${id}`)[0], {
  130. placement: 'right',
  131. content: ((MASTERY[id].pool / getMasteryPoolTotalXP(id)) * 100).toFixed(2) + '%',
  132. });
  133. }
  134. }
  135. }
  136. }, 5000);
  137.  
  138. //////////////////////////
  139. //buy mastery level base//
  140. //////////////////////////
  141. window.masteryBuyer = {
  142. poolXpPerItem: 500000,
  143. };
  144. masteryBuyer.availXp = (skillID, minPercent = 95) => {
  145. let minPool = MASTERY[skillID].xp.length * masteryBuyer.poolXpPerItem * minPercent / 100;
  146. return MASTERY[skillID].pool - minPool;
  147. }
  148. masteryBuyer.currentBase = (skillID) => {
  149. return Math.min(...MASTERY[skillID].xp.map((_, masteryID) => getMasteryLevel(skillID, masteryID)));
  150. }
  151. masteryBuyer.maxAffordableBase = (skillID, minPercent = 95) => {
  152. let xp = masteryBuyer.availXp(skillID, minPercent);
  153. // make bins with mastery levels
  154. let bins = [];
  155. for (let i = 0; i < 100; i++) {
  156. bins[i] = [];
  157. }
  158. MASTERY[skillID].xp.forEach((_, masteryID) => {
  159. let level = getMasteryLevel(skillID, masteryID);
  160. bins[level].push(masteryID);
  161. });
  162. // level one at a time
  163. let maxBase = 0;
  164. bins.forEach((x, i) => {
  165. if (i >= 99) {
  166. return;
  167. }
  168. if (x.length === 0) {
  169. return;
  170. }
  171. let xpRequired = (exp.level_to_xp(i + 1) - exp.level_to_xp(i)) * x.length;
  172. xp -= xpRequired;
  173. if (xp >= 0) {
  174. maxBase = i + 1;
  175. x.forEach(y => bins[i + 1].push(y));
  176. }
  177. });
  178. maxBase = maxBase > 99 ? 99 : maxBase;
  179. return maxBase;
  180. }
  181. masteryBuyer.increaseBase = (skillID, minPercent = 95, levelCap = 99) => {
  182. // buy until goal
  183. let goal = masteryBuyer.maxAffordableBase(skillID, minPercent);
  184. if (goal === 0) {
  185. goal = masteryBuyer.currentBase(skillID);
  186. }
  187. if (goal > levelCap) {
  188. goal = levelCap;
  189. }
  190. MASTERY[skillID].xp.forEach((_, masteryID) => {
  191. let level = getMasteryLevel(skillID, masteryID);
  192. if (level >= goal) {
  193. return;
  194. }
  195. masteryPoolLevelUp = goal - level;
  196. levelUpMasteryWithPool(skillID, masteryID);
  197. });
  198. // spend remainder on goal + 1
  199. const xpRequired = exp.level_to_xp(goal + 1) - exp.level_to_xp(goal);
  200. let count = Math.floor(masteryBuyer.availXp(skillID, minPercent) / xpRequired);
  201. masteryPoolLevelUp = 1;
  202. MASTERY[skillID].xp.forEach((_, masteryID) => {
  203. if (count === 0) {
  204. return;
  205. }
  206. let level = getMasteryLevel(skillID, masteryID);
  207. if (level > goal || level >= levelCap) {
  208. return;
  209. }
  210. count--;
  211. levelUpMasteryWithPool(skillID, masteryID);
  212. });
  213. // update total mastery
  214. updateTotalMastery(skillID);
  215. }
  216. masteryBuyer.overview = (minPercent = 95) => {
  217. Object.getOwnPropertyNames(SKILLS).forEach(skillID => {
  218. const skill = SKILLS[skillID];
  219. if (!skill.hasMastery) {
  220. return;
  221. }
  222. const maxBase = masteryBuyer.maxAffordableBase(skillID, minPercent);
  223. if (maxBase === 0) {
  224. return;
  225. }
  226. const currentBase = masteryBuyer.currentBase(skillID);
  227. console.log(`${skill.name}: ${currentBase} -> ${maxBase}`);
  228. });
  229. }
  230. masteryBuyer.remaining = (skillID, target = 99) => {
  231. let xp = 0;
  232. let xpTarget = exp.level_to_xp(target);
  233. MASTERY[skillID].xp.forEach(masteryXp => {
  234. xp += Math.max(0, xpTarget - masteryXp);
  235. });
  236. xp = Math.round(xp)
  237. console.log(formatNumber(xp))
  238. return xp
  239. }
  240.  
  241. //////////////////////
  242. //print synergy list//
  243. //////////////////////
  244. printSynergy = (x, y) => console.log('- [ ]',
  245. x.summoningID,
  246. parseInt(y),
  247. items[x.itemID].name,
  248. items[summoningItems[y].itemID].name,
  249. SUMMONING.Synergies[x.summoningID][y].description,
  250. SUMMONING.Synergies[x.summoningID][y].modifiers
  251. );
  252.  
  253. printCombatSynergyList = () => {
  254. // get combat synergies
  255. summoningItems.filter(x => items[x.itemID].summoningMaxHit).map(x => {
  256. for (y in SUMMONING.Synergies[x.summoningID]) {
  257. printSynergy(x, y);
  258. }
  259. });
  260. }
  261.  
  262. printNonCombatSynergyList = () => {
  263. // get non-combat synergies
  264. summoningItems.filter(x => !items[x.itemID].summoningMaxHit).map(x => {
  265. for (y in SUMMONING.Synergies[x.summoningID]) {
  266. printSynergy(x, y);
  267. }
  268. });
  269. }
  270.  
  271. /////////////////////////////
  272. //Quick Equip Max/Comp Cape//
  273. /////////////////////////////
  274. quickEquipSkillcape = (skill) => {
  275. const capes = [
  276. CONSTANTS.item.Cape_of_Completion,
  277. CONSTANTS.item.Max_Skillcape,
  278. skillcapeItems[skill],
  279. ];
  280. for (let i = 0; i < capes.length; i++) {
  281. const capeId = capes[i];
  282. if (player.equipment.checkForItemID(capeId)) {
  283. notifyPlayer(skill, `${items[capeId].name} is already equipped.`, "info");
  284. return;
  285. }
  286. const bankId = getBankId(capeId);
  287. if (bankId === -1) {
  288. continue;
  289. }
  290. if (!player.equipItem(capeId, player.selectedEquipmentSet)) {
  291. continue;
  292. }
  293. notifyPlayer(skill, `${items[capeId].name} Equipped.`, "success");
  294. if (skill === 0) {
  295. updateWCRates();
  296. }
  297. return;
  298. }
  299. notifyPlayer(skill, "There's no " + setToUppercase(skillName[skill]) + " Skillcape in your bank *shrug*", "danger");
  300. }
  301.  
  302. ///////////////////
  303. //remove elements//
  304. ///////////////////
  305. // combat
  306. document.getElementById('offline-combat-alert').remove();
  307.  
  308. // summoning marks
  309. // green
  310. document.getElementById('summoning-category-0').children[0].children[0].children[2].remove();
  311. // orange and red
  312. document.getElementById('summoning-category-0').children[0].children[0].children[1].remove();
  313.  
  314. // summoning tablets
  315. document.getElementById('summoning-notice').remove();
  316.  
  317. // alt. magic
  318. document.getElementById('magic-container').children[0].children[1].remove();
  319.  
  320. // cloud saving
  321. document.getElementById('header-cloud-save-time').remove();
  322. document.getElementById('header-cloud-save-btn-connected').remove();
  323.  
  324. // minibar-max-cape
  325. document.getElementById('minibar-max-cape').remove();
  326.  
  327. /////////////////
  328. //reroll slayer//
  329. /////////////////
  330. window.rerollSlayerTask = (monsterIDs, tier, extend = true) => {
  331. if (window.stopRerolling) {
  332. return;
  333. }
  334. const task = combatManager.slayerTask;
  335. const taskID = task.monster.id;
  336. const taskName = MONSTERS[taskID].name;
  337. if (!combatManager.slayerTask.taskTimer.active) {
  338. // only do something if slayer task timer is not running
  339. if (!combatManager.slayerTask.active || !monsterIDs.includes(taskID)) {
  340. // roll task if we don't have one, or if it has the wrong monster
  341. console.log(`rerolling ${taskName} for tier ${tier} task ${monsterIDs.map(monsterID => MONSTERS[monsterID].name).join(', ')}`);
  342. combatManager.slayerTask.selectTask(tier, true, true, false);
  343. } else if (extend && !task.extended) {
  344. // extend task if it is the right monster
  345. console.log(`extending ${taskName}`);
  346. combatManager.slayerTask.extendTask();
  347. }
  348. }
  349. setTimeout(() => rerollSlayerTask(monsterIDs, tier, extend), 1000);
  350. }
  351.  
  352. //////////////////
  353. //mining swapper//
  354. //////////////////
  355. window.rockOrder = [];
  356. setInterval(() => {
  357. if (currentRock === null) {
  358. return;
  359. }
  360. for (let i = 0; i < window.rockOrder.length; i++) {
  361. let rock = window.rockOrder[i];
  362. if (miningData[rock].level > skillLevel[CONSTANTS.skill.Mining]) {
  363. continue;
  364. }
  365. if (!rockData[rock].depleted) {
  366. if (currentRock === rock) {
  367. return;
  368. } else {
  369. console.log("start mining " + rock);
  370. mineRock(rock);
  371. return;
  372. }
  373. }
  374. }
  375. }, 1000);
  376.  
  377. ///////////////
  378. //shards used//
  379. ///////////////
  380. shardsUsed = () => {
  381. // compute amount of gp spent on summoning shards that have been used (for summoning or agility obstacles)
  382. items.map((x, i) => [x, i])
  383. .filter(x => x[0].type === 'Shard' && x[0].category === 'Summoning')
  384. .map(x => x[1])
  385. .map(x => (itemStats[x].stats[0] - getBankQty(x) - itemStats[x].stats[1]) * items[x].buysFor)
  386. .reduce((a, b) => a + b, 0);
  387. }
  388.  
  389. /////////////////////
  390. //Show Fish Cooking//
  391. /////////////////////
  392. // make cookable array and sort it based on cooking milestone index
  393. const cookable = items.filter(x => x.cookingID !== undefined);
  394. cookable.forEach((x, i) => {
  395. for (let j = 0; j < MILESTONES.Cooking.length; j++) {
  396. const ms = MILESTONES.Cooking[j];
  397. if (ms.name === x.name) {
  398. cookable[i].msId = j;
  399. return;
  400. }
  401. }
  402. });
  403. cookable.sort((a, b) => a.msId - b.msId);
  404. // set to true to show raw foods with 0 amount banked
  405. window.showAllRaws = true;
  406. // override updateAvailableFood
  407. updateAvailableFood = () => {
  408. $("#cooking-food-dropdown").html("");
  409. let selectedFoodExists = 0;
  410. cookable.forEach(raw => {
  411. let onClick = 'void(0)';
  412. let required = `<div className="font-size-sm"><small>Level ${raw.cookingLevel} Required</small></div>`
  413. if (skillLevel[3] >= raw.cookingLevel) {
  414. onClick = `selectFood(${raw.id})`;
  415. required = '';
  416. }
  417. const bankId = getBankId(raw.id);
  418. const qty = bankId === -1 ? 0 : bank[bankId].qty;
  419. if (!showAllRaws && qty === 0) {
  420. return;
  421. }
  422. $("#cooking-food-dropdown").append(''
  423. + `<a class="dropdown-item pointer-enabled" id="skill-cooking-food-${raw.msId}" onClick="${onClick}">`
  424. + ' <div class="media d-flex align-items-center push mb-0">'
  425. // img
  426. + ' <div class="mr-2">'
  427. + ' <img class="skill-icon-sm" src="' + raw.media + '">'
  428. + ' </div>'
  429. + ' <div class="media-body">'
  430. // name
  431. + ' <div class="font-w600 font-size-sm">'
  432. + raw.name
  433. + ' </div>'
  434. // required level
  435. + required
  436. // qty
  437. + ' <div class="font-w600 font-size-sm">'
  438. + ' <span class="badge badge-pill badge-primary">'
  439. + formatNumber(qty)
  440. + ' </span>'
  441. + ' </div>'
  442. // xp and healing
  443. + ' <div class="font-size-sm text-info">'
  444. + ' <small>'
  445. + `${raw.cookingXP} XP`
  446. + ' </small>'
  447. + ' <small class="text-success ml-2">'
  448. + ' <img class="skill-icon-xxs mr-1" src="https://melvorcdn.fra1.cdn.digitaloceanspaces.com/current/assets/media/skills/combat/hitpoints.svg">'
  449. + `${items[raw.cookedItemID].healsFor * numberMultiplier} HP`
  450. + ' </small>'
  451. + ' </div>'
  452. + ' </div>'
  453. + ' </div>'
  454. + '</a>'
  455. );
  456. if (selectedFood === raw.id) {
  457. $("#skill-cooking-food-selected-qty").text(formatNumber(qty));
  458. selectedFoodExists++;
  459. }
  460. });
  461. if (selectedFoodExists < 1) {
  462. $("#skill-cooking-food-selected-qty").text(0);
  463. }
  464. }
  465.  
  466. /////////////////
  467. //spawn Ahrenia//
  468. /////////////////
  469. window.spawnAhrenia = (phaseToSpawn = 1) => {
  470. // run
  471. combatManager.runCombat();
  472. // set respawn to 0
  473. if (!petUnlocked[0]) {
  474. unlockPet(0);
  475. }
  476. PETS[0].modifiers.decreasedMonsterRespawnTimer = 0;
  477. player.computeAllStats();
  478. PETS[0].modifiers.decreasedMonsterRespawnTimer = 3000 - TICK_INTERVAL - player.modifiers.decreasedMonsterRespawnTimer + player.modifiers.increasedMonsterRespawnTimer;
  479. player.computeAllStats();
  480. // unlock itm
  481. dungeonCompleteCount[CONSTANTS.dungeon.Fire_God_Dungeon] = Math.max(
  482. dungeonCompleteCount[CONSTANTS.dungeon.Fire_God_Dungeon],
  483. 1,
  484. );
  485. skillLevel[CONSTANTS.skill.Slayer] = Math.max(
  486. skillLevel[CONSTANTS.skill.Slayer],
  487. 90,
  488. );
  489. // skip to desired phase
  490. combatManager.selectDungeon(15);
  491. combatManager.dungeonProgress = 19 + phaseToSpawn;
  492. combatManager.loadNextEnemy();
  493. }
  494.  
  495. /////////////////////
  496. //don't cap pool xp//
  497. /////////////////////
  498. eval(addMasteryXPToPool.toString()
  499. .replace('MASTERY[skill].pool>getMasteryPoolTotalXP(skill)', 'false')
  500. .replace(/^function (\w+)/, "window.$1 = function")
  501. );
  502.  
  503. ////////////////////////////
  504. //don't cap token claiming//
  505. ////////////////////////////
  506. eval(claimToken.toString()
  507. .replace('qty>=tokensToFillPool', 'false')
  508. .replace(/^function (\w+)/, "window.$1 = function")
  509. );
  510.  
  511. /////////////////////
  512. //unsell sold items//
  513. /////////////////////
  514. unsell = (id, count = Infinity) => {
  515. if (count < 0) {
  516. return;
  517. }
  518. let stats = itemStats[id].stats;
  519. if (stats[Stats.TimesSold] === 0) {
  520. console.log("zero times sold");
  521. return;
  522. }
  523. // check if transaction is affordable
  524. let times = Math.min(count, stats[Stats.TimesSold]);
  525. let cost = Math.ceil(stats[Stats.GpFromSale] / stats[Stats.TimesSold] * times);
  526. if (gp < cost) {
  527. console.log("can't afford: " + times + " costs " + cost + " have " + gp);
  528. return;
  529. }
  530. // add item
  531. if (times > 0) {
  532. addItemToBank(id, times);
  533. }
  534. stats[Stats.TimesFound] -= times;
  535. stats[Stats.TimesSold] -= times;
  536. // remove cost
  537. gp = Math.floor(gp - cost);
  538. stats[Stats.GpFromSale] -= cost;
  539. updateGP();
  540. // fix statistics
  541. statsGeneral[0].count -= cost;
  542. statsGeneral[1].count -= times;
  543. updateStats();
  544. // log transaction
  545. console.log("bought " + times + " for " + cost);
  546. }
  547.  
  548. // footer start
  549. }
  550.  
  551. // inject the script
  552. (function () {
  553. function injectScript(main) {
  554. const scriptElement = document.createElement('script');
  555. scriptElement.textContent = `try {(${main})();} catch (e) {console.log(e);}`;
  556. document.body.appendChild(scriptElement).parentNode.removeChild(scriptElement);
  557. }
  558.  
  559. function loadScript() {
  560. if ((window.isLoaded && !window.currentlyCatchingUp)
  561. || (typeof unsafeWindow !== 'undefined' && unsafeWindow.isLoaded && !unsafeWindow.currentlyCatchingUp)) {
  562. // Only load script after game has opened
  563. clearInterval(scriptLoader);
  564. injectScript(script);
  565. }
  566. }
  567.  
  568. const scriptLoader = setInterval(loadScript, 200);
  569. })();