Melvor Snippets

Collection of various snippets

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

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