Elethor Recyclobot Calculator

Calculate platinum production and gold profit with a UI, now with searchable item selection, on Elethor.com.

安装此脚本?
作者推荐脚本

您可能也喜欢Elethor Chameleon

安装此脚本
  1. // ==UserScript==
  2. // @name Elethor Recyclobot Calculator
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.1
  5. // @description Calculate platinum production and gold profit with a UI, now with searchable item selection, on Elethor.com.
  6. // @author Eugene
  7. // @match https://elethor.com/*
  8. // @grant none
  9. // @license GPL-3.0-or-later
  10. // ==/UserScript==
  11. /*
  12. * This program is free software: you can redistribute it and/or modify
  13. * it under the terms of the GNU General Public License as published by
  14. * the Free Software Foundation, either version 3 of the License, or
  15. * (at your option) any later version.
  16. *
  17. * This program is distributed in the hope that it will be useful,
  18. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. * GNU General Public License for more details.
  21. *
  22. * You should have received a copy of the GNU General Public License
  23. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  24. */
  25. (function() {
  26. 'use strict';
  27.  
  28. // Error logging function
  29. function logError(message, error) {
  30. console.error(`Elethor Recyclobot Calculator Error: ${message}`, error);
  31. }
  32.  
  33. // Global error handler
  34. window.addEventListener('error', function(event) {
  35. logError('Uncaught error', event.error);
  36. });
  37.  
  38. // Define the target URL for the calculator to display
  39. const TARGET_URL = 'https://elethor.com/character/companion/recyclobot';
  40.  
  41. // Define the items and their base platinum points
  42. const itemList = [
  43. { name: "Rat Pelt", points: 55, id: 14 },
  44. { name: "Lava Worm Tooth", points: 60, id: 15 },
  45. { name: "Tormented Scale", points: 65, id: 16 },
  46. { name: "Skrivet Pelt", points: 70, id: 17 },
  47. { name: "Rippling Scale", points: 75, id: 18 },
  48. { name: "Worg Tooth", points: 80, id: 19 },
  49. { name: "Razen Hide", points: 85, id: 20 },
  50. { name: "Basic Reinforcement", points: 0, id: 39 },
  51. { name: "T0 Scrap", points: 600, id: 40 },
  52. { name: "T1 Scrap", points: 1000, id: 41 },
  53. { name: "T2 Scrap", points: 2000, id: 42 },
  54. { name: "T3 Scrap", points: 3000, id: 43 },
  55. { name: "Basic Energizing Shard", points: 0, id: 44 },
  56. { name: "Puncture 1 Essence", points: 320000, id: 45 },
  57. { name: "Puncture 2 Essence", points: 480000, id: 46 },
  58. { name: "Puncture 3 Essence", points: 800000, id: 47 },
  59. { name: "Puncture 4 Essence", points: 1280000, id: 48 },
  60. { name: "Standard Reinforcement", points: 0, id: 51 },
  61. { name: "Worg Claw", points: 160, id: 52 },
  62. { name: "Standard Energizing Shard", points: 0, id: 53 },
  63. { name: "Azure Tusk", points: 90, id: 54 },
  64. { name: "Fire Scale", points: 1200, id: 55 },
  65. { name: "Slotted Talon", points: 1500, id: 56 },
  66. { name: "Crimson Tusk", points: 95, id: 61 },
  67. { name: "Gory Tusk", points: 100, id: 62 },
  68. { name: "Poisonous Barb", points: 105, id: 63 },
  69. { name: "Karth Plate", points: 110, id: 64 },
  70. { name: "Improved Reinforcement", points: 0, id: 73 },
  71. { name: "Quality Reinforcement", points: 0, id: 74 },
  72. { name: "Improved Energizing Shard", points: 0, id: 75 },
  73. { name: "Quality Energizing Shard", points: 0, id: 76 },
  74. { name: "Bat Claw", points: 160, id: 77 },
  75. { name: "T4 Scrap", points: 4000, id: 78 },
  76. { name: "T5 Scrap", points: 5000, id: 79 },
  77. { name: "T6 Scrap", points: 6000, id: 80 },
  78. { name: "T7 Scrap", points: 7000, id: 81 },
  79. { name: "Spine Fragment", points: 165, id: 82 },
  80. { name: "Abyssal Essence", points: 0, id: 83 },
  81. { name: "Black Ink", points: 170, id: 84 },
  82. { name: "Dark Tusk", points: 175, id: 85 },
  83. { name: "Dark Delve", points: 180, id: 86 },
  84. { name: "Fireproof Tongue", points: 185, id: 87 },
  85. { name: "Serrated Thorn", points: 190, id: 104 },
  86. { name: "Imprinted Skull", points: 195, id: 117 },
  87. { name: "Unhinged Jawbone", points: 200, id: 122 },
  88. { name: "Pierced Voicebox", points: 205, id: 123 },
  89. { name: "Broken Shovel", points: 210, id: 124 },
  90. { name: "Pincered Talon", points: 215, id: 125 },
  91. { name: "Translucent Scale", points: 220, id: 140 },
  92. { name: "Scarred Fang", points: 225, id: 141 },
  93. { name: "Crooked Leg", points: 230, id: 142 },
  94. { name: "Venom Sample", points: 235, id: 143 },
  95. { name: "Condensed Vapor", points: 240, id: 144 },
  96. { name: "Fragment of Silence", points: 245, id: 145 },
  97. { name: "T8 Scrap", points: 0, id: 155 },
  98. { name: "Mechanoid Energizing Shard", points: 0, id: 156 },
  99. { name: "Mechanoid Reinforcement", points: 0, id: 157 },
  100. { name: "Unholy Remainder", points: 290, id: 158 },
  101. { name: "Synthetic Tar", points: 295, id: 159 },
  102. { name: "8.1 Reactor Core", points: 0, id: 160 },
  103. { name: "8.2 Reactor Core", points: 0, id: 161 },
  104. { name: "8.3 Reactor Core", points: 0, id: 162 },
  105. { name: "8.4 Reactor Core", points: 0, id: 163 },
  106. { name: "8.5 Reactor Core", points: 0, id: 164 },
  107. { name: "Growg Arm", points: 300, id: 165 },
  108. { name: "Decomposing Mask", points: 305, id: 166 },
  109. { name: "Severed Claw", points: 310, id: 168 },
  110. { name: "Void Fragment", points: 315, id: 169 },
  111. { name: "T9 Scrap", points: 0, id: 208 },
  112. { name: "Mutated Energizing Shard", points: 0, id: 209 },
  113. { name: "Mutated Reinforcement", points: 0, id: 210 },
  114. { name: "Mutagen", points: 0, id: 211 },
  115. { name: "Shattered Larynx", points: 340, id: 212 },
  116. { name: "Slimy Tentacle", points: 345, id: 213 },
  117. { name: "Broken Shackle", points: 350, id: 214 },
  118. { name: "Drop of Aether", points: 650, id: 215 },
  119. { name: "Torn Shadow", points: 355, id: 216 },
  120. { name: "Cursed Bane", points: 360, id: 217 },
  121. { name: "Volatile Dust", points: 365, id: 218 },
  122. { name: "Unstable Mutagen", points: 0, id: 226 },
  123. { name: "Unstable Particulate", points: 0, id: 227 },
  124. { name: "Unsorted Valuables", points: 390, id: 228 },
  125. { name: "T10 Scrap", points: 0, id: 276 },
  126. { name: "Deepwrought Energizing Shard", points: 0, id: 277 },
  127. { name: "Deepwrought Reinforcement", points: 0, id: 278 },
  128. { name: "Deepwrought Helm", points: 0, id: 279 },
  129. { name: "Spyglass", points: 445, id: 280 },
  130. { name: "Shattered Shield", points: 455, id: 281 },
  131. { name: "Shimmering Spearhead", points: 465, id: 282 },
  132. { name: "T'Chek Incisor", points: 475, id: 283 },
  133. { name: "Cracked Chestpiece", points: 485, id: 284 },
  134. { name: "Fractured Rocket", points: 500, id: 285 },
  135. { name: "Curved Blade", points: 1080, id: 286 },
  136. { name: "Void Artifact", points: 300000, id: 385 }
  137. ];
  138.  
  139. // Create a button to open the calculator
  140. let button;
  141. try {
  142. button = document.createElement('button');
  143. button.innerText = 'Open Recyclobot Calculator';
  144. button.style.padding = '10px';
  145. button.style.backgroundColor = '#18743c'; // Button color
  146. button.style.color = '#dee5ed'; // Text color
  147. button.style.border = 'none';
  148. button.style.borderRadius = '5px';
  149. button.style.cursor = 'pointer';
  150. button.style.position = 'absolute'; // Use absolute positioning for precise placement
  151. button.style.zIndex = '9999'; // Ensure the button appears on top
  152. button.style.display = 'none'; // Initially hidden
  153. } catch (error) {
  154. logError('Error creating calculator button', error);
  155. }
  156.  
  157. // Function to position the button
  158. function positionButton() {
  159. try {
  160. const referenceElement = document.querySelector('.button.is-info.is-multiline.w-full'); // Target button
  161. if (referenceElement) {
  162. const rect = referenceElement.getBoundingClientRect();
  163. button.style.top = `${rect.top + window.scrollY - button.offsetHeight - 50}px`; // Move button above the reference element (50px margin)
  164. button.style.left = `${rect.left + window.scrollX - button.offsetWidth - 10}px`; // Position to the left with a margin
  165. document.body.appendChild(button); // Append button to the body
  166. }
  167. } catch (error) {
  168. logError('Error positioning calculator button', error);
  169. }
  170. }
  171.  
  172. // Create a MutationObserver to detect changes in the DOM
  173. let observer;
  174. try {
  175. observer = new MutationObserver((mutations) => {
  176. // Check if the target element is in the DOM
  177. const referenceElement = document.querySelector('.button.is-info.is-multiline.w-full');
  178. if (referenceElement) {
  179. positionButton(); // Position the button
  180. observer.disconnect(); // Stop observing once the button is placed
  181. }
  182. });
  183.  
  184. // Start observing the document body for changes
  185. observer.observe(document.body, { childList: true, subtree: true });
  186. } catch (error) {
  187. logError('Error setting up MutationObserver', error);
  188. }
  189.  
  190. // Create the calculator UI
  191. let calculatorContainer;
  192. try {
  193. calculatorContainer = document.createElement('div');
  194. calculatorContainer.style.display = 'none'; // Initially hidden
  195. calculatorContainer.style.position = 'fixed';
  196. calculatorContainer.style.top = '10px';
  197. calculatorContainer.style.left = '10px';
  198. calculatorContainer.style.maxHeight = '90vh';
  199. calculatorContainer.style.overflowY = 'auto';
  200. calculatorContainer.style.maxWidth = '95vw';
  201. calculatorContainer.style.border = '2px solid #505c6c'; // Border around calculator
  202. calculatorContainer.style.borderRadius = '10px';
  203. calculatorContainer.style.backgroundColor = '#202c3c'; // Background color
  204. calculatorContainer.style.zIndex = '1001';
  205. calculatorContainer.style.padding = '20px';
  206.  
  207. // Add media query for mobile devices
  208. const mobileStyle = document.createElement('style');
  209. mobileStyle.textContent = `
  210. @media screen and (max-width: 768px) {
  211. #calculatorContainer {
  212. width: 95vw !important;
  213. max-height: 80vh !important;
  214. overflow-y: auto !important;
  215. }
  216. input, select {
  217. width: 100% !important;
  218. margin-bottom: 10px !important;
  219. }
  220. .tooltip .tooltiptext {
  221. width: 80vw !important;
  222. margin-left: -40vw !important;
  223. }
  224. }
  225. `;
  226. document.head.appendChild(mobileStyle);
  227. } catch (error) {
  228. logError('Error creating calculator container', error);
  229. }
  230.  
  231. // Function to position the calculator below the "Prospector" element
  232. function positionCalculator() {
  233. try {
  234. const gearElement = document.querySelector('a[href="/character/gear"]');
  235. if (gearElement) {
  236. const rect = gearElement.getBoundingClientRect();
  237. calculatorContainer.style.top = `${rect.bottom + window.scrollY}px`;// From the bottom of the Gear element
  238. calculatorContainer.style.left = `${rect.left + window.scrollX}px`; // Align horizontally with the Gear element
  239. }
  240. } catch (error) {
  241. logError('Error positioning calculator', error);
  242. }
  243. }
  244.  
  245. // Set the initial position of the calculator
  246. try {
  247. positionCalculator();
  248. document.body.appendChild(calculatorContainer);
  249. } catch (error) {
  250. logError('Error setting initial calculator position', error);
  251. }
  252.  
  253. // Add form elements to the calculator
  254. calculatorContainer.innerHTML = `
  255. <style>
  256. .tooltip {
  257. position: relative;
  258. display: inline-block;
  259. cursor: pointer;
  260. margin-left: 5px;
  261. }
  262. .tooltip .tooltiptext {
  263. visibility: hidden;
  264. width: 200px;
  265. background-color: #555;
  266. color: #fff;
  267. text-align: center;
  268. border-radius: 6px;
  269. padding: 5px;
  270. position: absolute;
  271. z-index: 1;
  272. bottom: 125%;
  273. left: 50%;
  274. margin-left: -100px;
  275. opacity: 0;
  276. transition: opacity 0.3s;
  277. }
  278. .tooltip:hover .tooltiptext {
  279. visibility: visible;
  280. opacity: 1;
  281. }
  282. #itemDropdown {
  283. background-color: #fff;
  284. border: 1px solid #dee5ed;
  285. border-radius: 5px;
  286. max-height: 200px;
  287. overflow-y: auto;
  288. position: absolute;
  289. width: 200px;
  290. z-index: 1000;
  291. }
  292. #itemDropdown div {
  293. padding: 5px;
  294. cursor: pointer;
  295. color: black; /* This matches the color of other input fields */
  296. }
  297. #itemDropdown div:hover {
  298. background-color: #f0f0f0;
  299. }
  300. </style>
  301. <h3 style="color: #dee5ed;">Elethor Recyclobot Calculator</h3>
  302. <p style="color: #dee5ed; font-size: 0.9em;">Made by <a href="https://elethor.com/profile/49979" target="_blank" style="color: #6cb4e4; text-decoration: underline;">Eugene</a></p>
  303. <div style="margin-bottom: 5px; position: relative;">
  304. <label style="color: #dee5ed;">Select Item:</label>
  305. <input type="text" id="itemSearch" placeholder="Search items..." style="border: 1px solid #dee5ed; border-radius: 5px; padding: 5px; width: 200px; margin-right: 10px; color: black;">
  306. <div id="itemDropdown" style="display: none;"></div>
  307. <span class="tooltip">ℹ️<span class="tooltiptext">Choose the item you want to recycle</span></span>
  308. </div>
  309. <div style="margin-bottom: 5px;">
  310. <label style="color: #dee5ed;">Bonus Platinum Level:</label>
  311. <input type="number" id="bonusPlatinumLevel" required style="border: 1px solid #dee5ed; border-radius: 5px; padding: 5px; width: 100px; margin-right: 10px; color: black;">
  312. <span class="tooltip">ℹ️<span class="tooltiptext">Your current Bonus Platinum level</span></span>
  313. </div>
  314. <div style="margin-bottom: 5px;">
  315. <label style="color: #dee5ed;">Exchange Rate Level:</label>
  316. <input type="number" id="exchangeRateLevel" required style="border: 1px solid #dee5ed; border-radius: 5px; padding: 5px; width: 100px; margin-right: 10px; color: black;">
  317. <span class="tooltip">ℹ️<span class="tooltiptext">Your current Exchange Rate level</span></span>
  318. </div>
  319. <div style="margin-bottom: 5px;">
  320. <label style="color: #dee5ed;">Gold cost per item:</label>
  321. <div style="display: flex; align-items: center; gap: 10px;">
  322. <input type="number" step="0.01" id="itemGoldCost" required style="border: 1px solid #dee5ed; border-radius: 5px; padding: 5px; width: 100px; color: black;">
  323. <div style="display: flex; align-items: center;">
  324. <input type="checkbox" id="autoPull" style="margin-right: 5px;">
  325. <label for="autoPull" style="color: #dee5ed; margin-right: 10px;">Auto-pull</label>
  326. <select id="priceType" style="border: 1px solid #dee5ed; border-radius: 5px; padding: 5px; color: black;">
  327. <option value="sell">Sell price</option>
  328. <option value="buy">Buy price</option>
  329. </select>
  330. </div>
  331. </div>
  332. <span class="tooltip">ℹ️<span class="tooltiptext">The gold cost for each item you're recycling. Enable Auto-pull to fetch current market prices.</span></span>
  333. </div>
  334. <div style="margin-bottom: 5px;">
  335. <label style="color: #dee5ed;">Gold value per platinum (in millions):</label>
  336. <input type="number" step="0.01" id="goldPerPlatinum" required style="border: 1px solid #dee5ed; border-radius: 5px; padding: 5px; width: 100px; margin-right: 10px; color: black;">
  337. <span class="tooltip">ℹ️<span class="tooltiptext">The current gold value of one platinum in millions</span></span>
  338. </div>
  339. <div style="margin-bottom: 5px;">
  340. <label style="color: #dee5ed;">
  341. <input type="checkbox" id="useProfitForProduction"> Use profit for production
  342. </label>
  343. <span class="tooltip">ℹ️<span class="tooltiptext">When unchecked: It calculates profit per platinum. It will stop when it the cost of producing the next platinum exceeds sale price. Use this if you want to make profit from the platinum you produce. When checked: It calculates profit per platinum. When cost of producing next platinum exceeds sale price,the loss is deducted from the profit you have made till then.Use this if you just want to make platinum. It's better to sell off the mob drops than use the profit, but the decision is up to you. </span></span>
  344. </div>
  345. <div style="display: flex; gap: 10px; margin-top: 10px;">
  346. <button id="calculateBtn" style="background-color: #18743c; color: white; border: none; padding: 10px; border-radius: 5px; cursor: pointer;">Calculate</button>
  347. <button id="resetBtn" style="background-color: #2596be; color: white; border: none; padding: 10px; border-radius: 5px; cursor: pointer;">🔄 Reset</button>
  348. <button id="closeBtn" style="background-color: #a02424; color: white; border: none; padding: 10px; border-radius: 5px; cursor: pointer;">X Close</button>
  349. </div>
  350. <div style="display: flex; gap: 10px; margin-top: 10px;">
  351. <button id="copyInputBtn" style="background-color: #ea951f; color: white; border: none; padding: 10px; border-radius: 5px; cursor: pointer;">📋 Copy Input</button>
  352. <button id="copyOutputBtn" style="background-color: #ea951f; color: white; border: none; padding: 10px; border-radius: 5px; cursor: pointer;">📋 Copy Output</button>
  353. </div>
  354. <div id="results" style="margin-top: 20px;"></div>
  355. `;
  356.  
  357. // Show/hide calculator on button click
  358. button.addEventListener('click', () => {
  359. try {
  360. // Toggle the visibility of the calculator
  361. if (calculatorContainer.style.display === 'none') {
  362. calculatorContainer.style.display = 'block';
  363. positionCalculator(); // Ensure it is positioned correctly when opened
  364. loadInputs(); // Load saved inputs when opening
  365. } else {
  366. calculatorContainer.style.display = 'none';
  367. }
  368. } catch (error) {
  369. logError('Error toggling calculator visibility', error);
  370. }
  371. });
  372.  
  373. // Close button
  374. calculatorContainer.querySelector('#closeBtn').addEventListener('click', () => {
  375. try {
  376. calculatorContainer.style.display = 'none';
  377. } catch (error) {
  378. logError('Error closing calculator', error);
  379. }
  380. });
  381.  
  382. // Reset button functionality
  383. calculatorContainer.querySelector('#resetBtn').addEventListener('click', () => {
  384. try {
  385. document.querySelector('#itemSearch').value = ''; // Clear search input
  386. document.querySelector('#bonusPlatinumLevel').value = '';
  387. document.querySelector('#exchangeRateLevel').value = '';
  388. document.querySelector('#itemGoldCost').value = '';
  389. document.querySelector('#goldPerPlatinum').value = '';
  390. clearInputs(); // Clear localStorage
  391. document.getElementById('results').innerHTML = ''; // Clear results
  392. } catch (error) {
  393. logError('Error resetting calculator', error);
  394. }
  395. });
  396.  
  397. // Function to flash the button when clicked
  398. function flashButton(button) {
  399. try {
  400. const originalColor = button.style.backgroundColor;
  401. button.style.backgroundColor = '#4CAF50'; // Success color (green)
  402. setTimeout(() => {
  403. button.style.backgroundColor = originalColor; // Revert back after 300ms
  404. }, 300);
  405. } catch (error) {
  406. logError('Error flashing button', error);
  407. }
  408. }
  409.  
  410. // Copy Output button functionality with flash effect
  411. calculatorContainer.querySelector('#copyOutputBtn').addEventListener('click', () => {
  412. try {
  413. const resultsText = document.getElementById('results').innerText;
  414. navigator.clipboard.writeText(resultsText)
  415. .then(() => {
  416. flashButton(calculatorContainer.querySelector('#copyOutputBtn')); // Flash button
  417. console.log('Output copied to clipboard!');
  418. })
  419. .catch(err => logError('Failed to copy output', err));
  420. } catch (error) {
  421. logError('Error copying output', error);
  422. }
  423. });
  424.  
  425. // Copy Input button functionality with flash effect
  426. calculatorContainer.querySelector('#copyInputBtn').addEventListener('click', () => {
  427. try {
  428. const selectedItem = document.querySelector('#itemSearch').value;
  429. const bonusPlatinumLevel = document.querySelector('#bonusPlatinumLevel').value;
  430. const exchangeRateLevel = document.querySelector('#exchangeRateLevel').value;
  431. const itemGoldCost = document.querySelector('#itemGoldCost').value;
  432. const goldPerPlatinum = document.querySelector('#goldPerPlatinum').value;
  433.  
  434. const inputText = `
  435. Selected Item: ${selectedItem}
  436. Bonus Platinum Level: ${bonusPlatinumLevel}
  437. Exchange Rate Level: ${exchangeRateLevel}
  438. Gold cost per item: ${itemGoldCost}
  439. Gold value per platinum (in millions): ${goldPerPlatinum}
  440. `;
  441.  
  442. navigator.clipboard.writeText(inputText.trim())
  443. .then(() => {
  444. flashButton(calculatorContainer.querySelector('#copyInputBtn')); // Flash button
  445. console.log('Input values copied to clipboard!');
  446. })
  447. .catch(err => logError('Failed to copy input', err));
  448. } catch (error) {
  449. logError('Error copying input', error);
  450. }
  451. });
  452.  
  453. // Searchable dropdown functionality
  454. const itemSearch = document.getElementById('itemSearch');
  455. const itemDropdown = document.getElementById('itemDropdown');
  456. let selectedItemPoints = 0;
  457.  
  458. function populateDropdown(items) {
  459. try {
  460. itemDropdown.innerHTML = '';
  461. items.forEach(item => {
  462. const div = document.createElement('div');
  463. div.textContent = item.name;
  464. div.onclick = function() {
  465. itemSearch.value = item.name;
  466. selectedItemPoints = item.points;
  467. itemDropdown.style.display = 'none';
  468. };
  469. itemDropdown.appendChild(div);
  470. });
  471. itemDropdown.style.display = 'block';
  472. } catch (error) {
  473. logError('Error populating dropdown', error);
  474. }
  475. }
  476.  
  477. itemSearch.addEventListener('focus', function() {
  478. try {
  479. if (this.value === '') {
  480. populateDropdown(itemList);
  481. }
  482. } catch (error) {
  483. logError('Error handling item search focus', error);
  484. }
  485. });
  486.  
  487. itemSearch.addEventListener('input', function() {
  488. try {
  489. const searchTerm = this.value.toLowerCase();
  490. const filteredItems = searchTerm === '' ? itemList : itemList.filter(item =>
  491. item.name.toLowerCase().includes(searchTerm)
  492. );
  493. populateDropdown(filteredItems);
  494. } catch (error) {
  495. logError('Error handling item search input', error);
  496. }
  497. });
  498.  
  499. document.addEventListener('click', function(e) {
  500. try {
  501. if (e.target !== itemSearch && e.target !== itemDropdown) {
  502. itemDropdown.style.display = 'none';
  503. }
  504. } catch (error) {
  505. logError('Error handling document click', error);
  506. }
  507. });
  508.  
  509. // Function to fetch market data
  510. async function fetchMarketData(itemId, priceType) {
  511. try {
  512. const response = await fetch(`https://elethor.com/game/market/listings?itemId=${itemId}`);
  513. const data = await response.json();
  514.  
  515. if (!data || !Array.isArray(data)) {
  516. throw new Error('Invalid market data received');
  517. }
  518.  
  519. // Filter listings based on price type
  520. const relevantListings = data.filter(listing => listing.type === priceType);
  521.  
  522. if (relevantListings.length === 0) {
  523. throw new Error(`No ${priceType} listings found`);
  524. }
  525.  
  526. // Get the appropriate price based on type
  527. if (priceType === 'sell') {
  528. // Get lowest sell price
  529. return Math.min(...relevantListings.map(listing => listing.price));
  530. } else {
  531. // Get highest buy price
  532. return Math.max(...relevantListings.map(listing => listing.price));
  533. }
  534. } catch (error) {
  535. logError('Error fetching market data', error);
  536. throw error;
  537. }
  538. }
  539.  
  540. // Auto-pull checkbox event handler
  541. const autoPullCheckbox = document.getElementById('autoPull');
  542. const itemGoldCostInput = document.getElementById('itemGoldCost');
  543. const priceTypeSelect = document.getElementById('priceType');
  544.  
  545. autoPullCheckbox.addEventListener('change', function() {
  546. itemGoldCostInput.disabled = this.checked;
  547. });
  548.  
  549. // Calculate button functionality
  550. calculatorContainer.querySelector('#calculateBtn').addEventListener('click', async () => {
  551. try {
  552. const bonusPlatinumLevel = parseFloat(document.querySelector('#bonusPlatinumLevel').value);
  553. const exchangeRateLevel = parseFloat(document.querySelector('#exchangeRateLevel').value);
  554. const goldPerPlatinum = parseFloat(document.querySelector('#goldPerPlatinum').value);
  555.  
  556. // Get the selected item's ID
  557. const selectedItem = itemList.find(item => item.name === document.querySelector('#itemSearch').value);
  558. if (!selectedItem) {
  559. throw new Error('Please select a valid item');
  560. }
  561.  
  562. let itemGoldCost;
  563. const autoPull = document.getElementById('autoPull').checked;
  564.  
  565. if (autoPull) {
  566. try {
  567. const priceType = document.getElementById('priceType').value;
  568. itemGoldCost = await fetchMarketData(selectedItem.id, priceType);
  569. document.getElementById('itemGoldCost').value = itemGoldCost; // Update the input field
  570. } catch (error) {
  571. alert('Failed to fetch market price. Please check if the item is available on the market or enter the price manually.');
  572. return;
  573. }
  574. } else {
  575. itemGoldCost = parseFloat(document.querySelector('#itemGoldCost').value);
  576. }
  577.  
  578. // Validation: Check for positive values
  579. if (
  580. selectedItemPoints <= 0 ||
  581. isNaN(bonusPlatinumLevel) || bonusPlatinumLevel < 0 ||
  582. isNaN(exchangeRateLevel) || exchangeRateLevel < 0 ||
  583. isNaN(itemGoldCost) || itemGoldCost < 0 ||
  584. isNaN(goldPerPlatinum) || goldPerPlatinum < 0
  585. ) {
  586. throw new Error('Invalid input values');
  587. }
  588.  
  589. saveInputs(selectedItemPoints, bonusPlatinumLevel, exchangeRateLevel, itemGoldCost, goldPerPlatinum);
  590.  
  591. const ITEMS_PER_RECYCLE = 200;
  592. const BASE_PLATINUM_POINTS = selectedItemPoints;
  593. const BASE_PLATINUM_COST = 10000;
  594. const MAX_COST_INCREASE_PLATINUM = 20;
  595. const PLATINUM_GOLD_COST = 250000;
  596. const BONUS_PLATINUM_INCREMENT = 0.002;
  597.  
  598. const exchangeRateMultiplier = 1 + (exchangeRateLevel * 0.01);
  599. const bonusPlatinumMultiplier = 1 + (bonusPlatinumLevel * BONUS_PLATINUM_INCREMENT);
  600. const platinumPointsPerRecycle = Math.floor(BASE_PLATINUM_POINTS * exchangeRateMultiplier);
  601. let totalGoldProfit = 0;
  602. let platinumCount = 0;
  603. let totalGoldSpent = 0;
  604. let totalItemsUsed = 0;
  605. let currentPlatinumCost = BASE_PLATINUM_COST;
  606.  
  607. while (true) {
  608. const platinumPointsNeeded = currentPlatinumCost;
  609. const requiredRecycles = (platinumPointsNeeded / platinumPointsPerRecycle);
  610. const requiredItems = requiredRecycles * ITEMS_PER_RECYCLE;
  611. totalItemsUsed += requiredItems;
  612.  
  613. const itemsGoldCostTotal = requiredItems * itemGoldCost;
  614. const totalGoldCost = PLATINUM_GOLD_COST + itemsGoldCostTotal;
  615.  
  616. // Total platinum produced including bonus
  617. const totalPlatinum = 1 * bonusPlatinumMultiplier;
  618. const basePlatinum = 1; // Base platinum unit
  619. const bonusPlatinum = totalPlatinum - basePlatinum; // Bonus platinum units
  620.  
  621. // Cost per unit of platinum
  622. const costPerPlatinum = totalGoldCost / totalPlatinum;
  623.  
  624. // Calculate profit per platinum unit
  625. let profit = 0;
  626.  
  627. // Profit from base platinum (100% of its gold value)
  628. profit += (basePlatinum * goldPerPlatinum * 1_000_000) - (costPerPlatinum * basePlatinum);
  629.  
  630. // Profit from bonus platinum (90% of its gold value)
  631. profit += (bonusPlatinum * goldPerPlatinum * 1_000_000 * 0.9) - (costPerPlatinum * bonusPlatinum);
  632.  
  633. const useProfitForProduction = document.getElementById('useProfitForProduction').checked;
  634.  
  635. if (profit <= 0) {
  636. if (useProfitForProduction && totalGoldProfit > 0) {
  637. totalGoldProfit += profit; // Deduct loss from totalGoldProfit
  638. } else {
  639. break; // Exit the loop if profit is non-positive and checkbox is not checked
  640. }
  641. }
  642.  
  643. // Accumulate the total profits and costs
  644. totalGoldProfit += profit;
  645. totalGoldSpent += totalGoldCost;
  646. platinumCount++;
  647.  
  648. // Increase platinum cost for the next cycle
  649. if (platinumCount < MAX_COST_INCREASE_PLATINUM) {
  650. currentPlatinumCost += 1000;
  651. } else {
  652. currentPlatinumCost += 500;
  653. }
  654. }
  655.  
  656. // Display results
  657. document.getElementById('results').innerHTML = `
  658. <p style="color: #dee5ed;">Platinum to produce: ${platinumCount}</p>
  659. <p style="color: #dee5ed;">Total Gold Profit: ${formatToBillions(totalGoldProfit)}</p>
  660. <p style="color: #dee5ed;">Maximum Bonus Platinum: ${(platinumCount * bonusPlatinumMultiplier).toFixed(2)}</p>
  661. <p style="color: #dee5ed;">Total Items Used: ${totalItemsUsed}</p>
  662. `;
  663. } catch (error) {
  664. logError('Error in calculation', error);
  665. alert('An error occurred during calculation. Please check your inputs and try again.');
  666. }
  667. });
  668.  
  669. // Function to format value to billions
  670. function formatToBillions(value) {
  671. try {
  672. let billions = value / 1_000_000_000;
  673. return billions.toFixed(2) + " billion";
  674. } catch (error) {
  675. logError('Error formatting to billions', error);
  676. return "Error";
  677. }
  678. }
  679.  
  680. // Save inputs to localStorage
  681. function saveInputs(selectedItemPoints, bonusPlatinumLevel, exchangeRateLevel, itemGoldCost, goldPerPlatinum) {
  682. try {
  683. localStorage.setItem('selectedItemPoints', selectedItemPoints);
  684. localStorage.setItem('bonusPlatinumLevel', bonusPlatinumLevel);
  685. localStorage.setItem('exchangeRateLevel', exchangeRateLevel);
  686. localStorage.setItem('itemGoldCost', itemGoldCost);
  687. localStorage.setItem('goldPerPlatinum', goldPerPlatinum);
  688. } catch (error) {
  689. logError('Error saving inputs to localStorage', error);
  690. }
  691. }
  692.  
  693. // Load inputs from localStorage
  694. function loadInputs() {
  695. try {
  696. const savedItemPoints = localStorage.getItem('selectedItemPoints');
  697. if (savedItemPoints) {
  698. const savedItem = itemList.find(item => item.points == savedItemPoints);
  699. if (savedItem) {
  700. document.querySelector('#itemSearch').value = savedItem.name;
  701. selectedItemPoints = savedItem.points;
  702. }
  703. }
  704. document.querySelector('#bonusPlatinumLevel').value = localStorage.getItem('bonusPlatinumLevel') || '';
  705. document.querySelector('#exchangeRateLevel').value = localStorage.getItem('exchangeRateLevel') || '';
  706. document.querySelector('#itemGoldCost').value = localStorage.getItem('itemGoldCost') || '';
  707. document.querySelector('#goldPerPlatinum').value = localStorage.getItem('goldPerPlatinum') || '';
  708. } catch (error) {
  709. logError('Error loading inputs from localStorage', error);
  710. }
  711. }
  712.  
  713. // Clear inputs from localStorage
  714. function clearInputs() {
  715. try {
  716. localStorage.removeItem('selectedItemPoints');
  717. localStorage.removeItem('bonusPlatinumLevel');
  718. localStorage.removeItem('exchangeRateLevel');
  719. localStorage.removeItem('itemGoldCost');
  720. localStorage.removeItem('goldPerPlatinum');
  721. } catch (error) {
  722. logError('Error clearing inputs from localStorage', error);
  723. }
  724. }
  725.  
  726. // URL check function to show button and calculator only on the correct page
  727. function checkURL() {
  728. try {
  729. if (window.location.href === TARGET_URL) {
  730. button.style.display = 'block';
  731. if (calculatorContainer.style.display !== 'none') {
  732. positionCalculator();
  733. }
  734. } else {
  735. button.style.display = 'none';
  736. calculatorContainer.style.display = 'none';
  737. }
  738. } catch (error) {
  739. logError('Error checking URL', error);
  740. }
  741. }
  742.  
  743. // Check URL initially and set interval for changes
  744. checkURL();
  745. setInterval(checkURL, 500); // Check URL every 500 milliseconds
  746.  
  747. })();