Melvor Snippets

Collection of various snippets

当前为 2022-04-01 提交的版本,查看 最新版本

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