Elethor General Purpose

Provides some general additions to Elethor

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

  1. // ==UserScript==
  2. // @name Elethor General Purpose
  3. // @description Provides some general additions to Elethor
  4. // @namespace https://www.elethor.com/
  5. // @version 1.4.1
  6. // @author Anders Morgan Larsen (Xortrox)
  7. // @match https://elethor.com/*
  8. // @match https://www.elethor.com/*
  9. // @run-at document-start
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. const currentUserData = {};
  15.  
  16. const moduleName = 'Elethor General Purpose';
  17. const version = '1.4.1'
  18.  
  19. function initializeXHRHook() {
  20. let rawSend = XMLHttpRequest.prototype.send;
  21.  
  22. XMLHttpRequest.prototype.send = function() {
  23. if (!this._hooked) {
  24. this._hooked = true;
  25.  
  26. this.addEventListener('readystatechange', function() {
  27. if (this.readyState === XMLHttpRequest.DONE) {
  28. setupHook(this);
  29. }
  30. }, false);
  31. }
  32. rawSend.apply(this, arguments);
  33. }
  34.  
  35. function setupHook(xhr) {
  36. if (window.elethorGeneralPurposeOnXHR) {
  37. const e = new Event('EGPXHR');
  38. e.xhr = xhr;
  39.  
  40. window.elethorGeneralPurposeOnXHR.dispatchEvent(e);
  41. }
  42. }
  43. window.elethorGeneralPurposeOnXHR = new EventTarget();
  44.  
  45. console.log(`[${moduleName} v${version}] XHR Hook initialized.`);
  46. }
  47.  
  48. function initializeUserLoadListener() {
  49. elethorGeneralPurposeOnXHR.addEventListener('EGPXHR', function (e) {
  50. if (e && e.xhr
  51. && e.xhr.responseURL
  52. && e.xhr.responseURL.endsWith
  53. && e.xhr.responseURL.endsWith('/game/user')
  54. ) {
  55. try {
  56. const userData = JSON.parse(e.xhr.responseText);
  57. console.log(`[${moduleName} v${version}] User Data hook:`, userData);
  58.  
  59. if (userData) {
  60. for (const key of Object.keys(userData)) {
  61. currentUserData[key] = userData[key];
  62. }
  63.  
  64. console.log(`[${moduleName} v${version}] User Data loaded:`, currentUserData);
  65. }
  66. } catch (e) {
  67. console.log(`[${moduleName} v${version}] Error parsing userData:`, e);
  68. }
  69.  
  70. }
  71. });
  72.  
  73. console.log(`[${moduleName} v${version}] User Load Listener initialized.`);
  74. }
  75.  
  76. function initializeToastKiller() {
  77. document.addEventListener('click', function(e) {
  78. if (e.target
  79. && e.target.className
  80. && e.target.className.includes
  81. && e.target.className.includes('toasted')
  82. ) {
  83. e.target.remove();
  84. }
  85. });
  86.  
  87. console.log(`[${moduleName} v${version}] Toast Killer initialized.`);
  88. }
  89.  
  90. function initializeInventoryStatsLoadListener() {
  91. elethorGeneralPurposeOnXHR.addEventListener('EGPXHR', function (e) {
  92. if (e && e.xhr
  93. && e.xhr.responseURL
  94. && e.xhr.responseURL.endsWith
  95. && e.xhr.responseURL.endsWith('/game/inventory/stats')
  96. ) {
  97. setTimeout(() => {
  98. updateEquipmentPercentageSummary();
  99.  
  100. setTimeout(updateInventoryStatsPercentages, 1000);
  101. });
  102. }
  103. });
  104.  
  105. console.log(`[${moduleName} v${version}] Inventory Stats Load Listener initialized.`);
  106. }
  107.  
  108. function updateEquipmentPercentageSummary() {
  109. document.querySelector('.is-equipment>div>div').setAttribute('style', 'width: 50%')
  110. let percentagesTable = document.querySelector('#egpPercentagesSummary')
  111. if (!percentagesTable){
  112. percentagesTable = document.querySelector('.is-equipment>div>div:nth-child(2)').cloneNode(true);
  113. percentagesTable.setAttribute('style', 'width: 25%');
  114. percentagesTable.id='egpPercentagesSummary';
  115. document.querySelector('.is-equipment>div').appendChild(percentagesTable);
  116.  
  117. for (const child of percentagesTable.children[0].children) {
  118. if (child && child.children && child.children[0]) {
  119. child.children[0].remove();
  120. }
  121. }
  122.  
  123. document.querySelector('#egpPercentagesSummary>table>tr:nth-child(8)').setAttribute('style', 'height:43px');
  124. }
  125. }
  126.  
  127. function getStatSummary(equipment) {
  128. const summary = {
  129. base: {},
  130. energizements: {}
  131. };
  132.  
  133. if (equipment) {
  134. for (const key of Object.keys(equipment)) {
  135. const item = equipment[key];
  136.  
  137. /**
  138. * Sums base attributes by name
  139. * */
  140. if (item && item.attributes) {
  141. for (const attributeName of Object.keys(item.attributes)) {
  142. const attributeValue = item.attributes[attributeName];
  143.  
  144. if (!summary.base[attributeName]) {
  145. summary.base[attributeName] = 0;
  146. }
  147.  
  148. summary.base[attributeName] += attributeValue;
  149. }
  150. }
  151.  
  152. /**
  153. * Sums energizements by stat name
  154. * */
  155. if (item && item.upgrade && item.upgrade.energizements) {
  156. for (const energizement of item.upgrade.energizements) {
  157. if (!summary.energizements[energizement.stat]) {
  158. summary.energizements[energizement.stat] = 0;
  159. }
  160.  
  161. summary.energizements[energizement.stat] += Number(energizement.boost);
  162. }
  163. }
  164. }
  165. }
  166.  
  167. return summary;
  168. }
  169.  
  170. function updateInventoryStatsPercentages() {
  171. let percentagesTable = document.querySelector('#egpPercentagesSummary')
  172. if (percentagesTable && currentUserData && currentUserData.equipment){
  173. const statSummary = getStatSummary(currentUserData.equipment);
  174.  
  175. const baseKeys = Object.keys(statSummary.base);
  176. const energizementKeys = Object.keys(statSummary.energizements);
  177.  
  178. let allKeys = baseKeys.concat(energizementKeys);
  179. const filterUniques = {};
  180. for (const key of allKeys){
  181. filterUniques[key] = true;
  182. }
  183. allKeys = Object.keys(filterUniques);
  184. allKeys.sort();
  185.  
  186. allKeys.push('actions');
  187.  
  188. const tableRows = percentagesTable.children[0].children;
  189.  
  190. for(const row of tableRows) {
  191. if (row
  192. && row.children
  193. && row.children[0]
  194. && row.children[0].children[0]
  195. ) {
  196. const rowText = row.children[0].children[0];
  197. rowText.innerText = '';
  198. }
  199. }
  200.  
  201. let rowIndex = 0;
  202. for (const key of allKeys) {
  203. if (key === 'puncture') {
  204. continue;
  205. }
  206.  
  207. const row = tableRows[rowIndex];
  208. if (row
  209. && row.children
  210. && row.children[0]
  211. && row.children[0].children[0]
  212. ) {
  213. const rowText = row.children[0].children[0];
  214.  
  215. const rowBase = statSummary.base[key] || 0;
  216. const rowEnergizement = (statSummary.energizements[key] || 0);
  217. const rowEnergizementPercentage = (statSummary.energizements[key] || 0) * 100;
  218.  
  219. if (key.startsWith('+')) {
  220. rowText.innerText = `${key} per 10 levels: ${rowEnergizement}`;
  221. } else if (key === 'actions') {
  222. const actions = currentUserData.user.bonus_actions || 0;
  223. rowText.innerText = `Bonus Actions: ${actions}`;
  224. } else {
  225. rowText.innerText = `${key}: ${rowBase} (${rowEnergizementPercentage.toFixed(0)}%)`;
  226. }
  227.  
  228. rowIndex++;
  229. }
  230. }
  231. }
  232. }
  233.  
  234. (function run() {
  235. initializeToastKiller();
  236. initializeXHRHook();
  237. initializeUserLoadListener();
  238. initializeInventoryStatsLoadListener();
  239.  
  240. console.log(`[${moduleName} v${version}] Loaded.`);
  241. })();
  242.  
  243. (async function loadRerollDisableButtonModule() {
  244. async function waitForEcho() {
  245. return new Promise((resolve, reject) => {
  246. const interval = setInterval(() => {
  247. if (window.Echo) {
  248. clearInterval(interval);
  249. resolve();
  250. }
  251. }, 100);
  252. });
  253. }
  254.  
  255. async function waitForUser() {
  256. return new Promise((resolve, reject) => {
  257. const interval = setInterval(() => {
  258. if (currentUserData.user && currentUserData.user.id !== undefined) {
  259. clearInterval(interval);
  260. resolve();
  261. }
  262. }, 100);
  263. });
  264. }
  265.  
  266. await waitForEcho();
  267. await waitForUser();
  268.  
  269. elethorGeneralPurposeOnXHR.addEventListener('EGPXHR', async function (e) {
  270. if (e && e.xhr && e.xhr.responseURL) {
  271. if(e.xhr.responseURL.includes('/game/energize')) {
  272. const itemID = e.xhr.responseURL.substr(e.xhr.responseURL.lastIndexOf('/')+1);
  273. window.lastEnergizeID = Number(itemID);
  274. }
  275. }
  276. });
  277.  
  278. function initializeDisableEnergizementButtonOnReroll() {
  279. const privateRoom = `App.User.${currentUserData.user.id}`;
  280. console.log(`[${moduleName} v${version}] Binding to private room ${privateRoom}`);
  281. window.Echo.private(privateRoom).listen(".App\\Domain\\Inventory\\Events\\UpdateItem", (data) => {
  282. if (data && data.item.id === window.lastEnergizeID){
  283. //enableEnergizementButton();
  284. }
  285. });
  286.  
  287. document.addEventListener('click', function(e) {
  288. try {
  289. if (!e
  290. || !e.target
  291. || !e.target.parentElement
  292. || !e.target.className
  293. || !e.target.parentElement.className
  294. || !e.target.innerText
  295. || !e.target.innerText.includes
  296. ) {
  297. return;
  298. }
  299.  
  300. if ((e.target.innerText.includes('Reroll Energizements') || e.target.innerText.includes('Costs 1 Standard Energizing Shard'))
  301. && (e.target.className.includes('button') || e.target.parentElement.className.includes('button'))
  302. ) {
  303. // disableEnergizementButton();
  304. // setTimeout(enableEnergizementButton, 500);
  305. }
  306. } catch (e) {
  307. console.warn(`[${moduleName} v${version}] Error during document click:`, e);
  308. }
  309. });
  310. }
  311. initializeDisableEnergizementButtonOnReroll();
  312.  
  313. function disableEnergizementButton() {
  314. const button = getEnergizementButton();
  315.  
  316. if (!button) {
  317. return;
  318. }
  319.  
  320. button.setAttribute('style', 'pointer-events: none');
  321. button.setAttribute('disabled', 'true');
  322. }
  323.  
  324. function enableEnergizementButton() {
  325. const button = getEnergizementButton();
  326.  
  327. if (!button) {
  328. return;
  329. }
  330.  
  331. button.removeAttribute('style');
  332. button.removeAttribute('disabled');
  333. }
  334.  
  335. function getEnergizementButton() {
  336. let button;
  337. document.querySelectorAll('.buttons>.button.is-multiline.is-info>span:first-child').forEach((e) => {
  338. if (e && e.innerText && e.innerText === 'Reroll Energizements') {
  339. button = e.parentElement;
  340. }
  341. });
  342. return button;
  343. }
  344. })();
  345.  
  346. (function loadResourceNodeUpdater() {
  347. function updateExperienceRates() {
  348. document.querySelectorAll('.is-resource-node').forEach(visualizeResourceNodeExperienceRates);
  349.  
  350. function visualizeResourceNodeExperienceRates(node) {
  351. const purity = getNodePurityPercentage(node);
  352. const density = getNodeDensityPercentage(node);
  353. const experience = getNodeExperience(node);
  354. const ore = 16;
  355. const experienceRate = getExperienceRate(density, experience);
  356. const oreRate = getOreRate(density, purity, ore);
  357.  
  358. node.children[0].setAttribute('data-after', `${experienceRate} xp/h ${oreRate} ore/h`);
  359. }
  360.  
  361. function getNodePurityPercentage(node) {
  362. const column = node.children[0].children[2];
  363. const label = column.getElementsByClassName('has-text-weight-bold')[0].parentElement;
  364. const percentage = Number(label.innerText.replace('%','').split(':')[1])
  365. return percentage;
  366. }
  367.  
  368. function getNodeDensityPercentage(node) {
  369. const column = node.children[0].children[1];
  370. const label = column.getElementsByClassName('has-text-weight-bold')[0].parentElement;
  371. const percentage = Number(label.innerText.replace('%','').split(':')[1])
  372. return percentage;
  373. }
  374.  
  375. function getNodeExperience(node) {
  376. const column = node.children[0].children[3];
  377. const label = column.getElementsByClassName('has-text-weight-bold')[0].parentElement;
  378. const value = Number(label.innerText.replace('%','').split(':')[1])
  379. return value;
  380. }
  381.  
  382. function getExperienceRate(density, experience) {
  383. return Number((3600 / (60 / (density / 100)) * experience).toFixed(3));
  384. }
  385.  
  386. function getOreRate(density, purity, ore) {
  387. return Number((3600 / (60 / (density / 100)) * (purity / 100) * ore).toFixed(3));
  388. }
  389. }
  390.  
  391. updateExperienceRates();
  392. window.elethorResourceInterval = setInterval(updateExperienceRates, 500);
  393. initializeResourceNodeView();
  394.  
  395. async function initializeResourceNodeView() {
  396. await waitForField(document, 'head');
  397.  
  398. var css = '.columns.is-mobile.is-size-7-mobile::after { content: attr(data-after); padding: 12px;}',
  399. head = document.head || document.getElementsByTagName('head')[0],
  400. style = document.createElement('style');
  401.  
  402. head.appendChild(style);
  403.  
  404. style.type = 'text/css';
  405. if (style.styleSheet){
  406. // This is required for IE8 and below.
  407. style.styleSheet.cssText = css;
  408. } else {
  409. style.appendChild(document.createTextNode(css));
  410. }
  411. }
  412.  
  413. async function waitForField(target, field) {
  414. return new Promise((resolve, reject) => {
  415. const interval = setInterval(() => {
  416. if (target[field] !== undefined) {
  417. clearInterval(interval);
  418. resolve();
  419. }
  420. }, 100);
  421. });
  422. }
  423. })();
  424. })();