Kittens Game - Progress Bars

Adds progress bars to Kittens Game to see how close you are to an upgrade

  1. // ==UserScript==
  2. // @name Kittens Game - Progress Bars
  3. // @namespace https://greasyfork.org/en/scripts/526715-kittens-game-progress-bars
  4. // @version 1.3
  5. // @description Adds progress bars to Kittens Game to see how close you are to an upgrade
  6. // @author Mashiro-chan
  7. // @match https://kittensgame.com/web/
  8. // @license MIT
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. 'use strict';
  13.  
  14. const init = () => {
  15. if (!game.resPool) {
  16. setTimeout(init, 100);
  17. return;
  18. }
  19.  
  20. document.addEventListener("click", function(event) {
  21. const button = event.target.closest(".tab");
  22. if (button && !button.classList.contains("activeTab")) {
  23. updateButtons();
  24. }
  25. });
  26.  
  27. var getButtons = () => {
  28. var buttons = [game.tabs.find(t => t.tabId == game.ui.activeTabId)]
  29. .flatMap(t => [...new Set(Object.keys(t).filter(k => /btn|button|panel|children/i.test(k)).flatMap(k => t[k]))])
  30. .filter(Boolean);
  31. while (buttons.some(x => !x.model)) {
  32. buttons = buttons.flatMap(x =>
  33. x.model
  34. ? x
  35. : [x.tradeBtn, x.race, x.embassyButton, x.children].flat().filter(Boolean)
  36. );
  37. }
  38. return buttons;
  39. };
  40.  
  41. const extendPrices = prices => prices.map(p => ({
  42. 'name': p.name,
  43. 'have': game.resPool.get(p.name).value,
  44. 'need': p.val
  45. })).map(p => ({
  46. ...p,
  47. 'delta': p.need - p.have,
  48. 'percent': p.have / p.need
  49. }));
  50.  
  51. let progressBarColor = '#FF0000';
  52.  
  53. const bodyClass = document.body.className.match(/scheme_([\w-]+)/);
  54. const themeName = bodyClass[1];
  55. const themeStylesheet = `theme_${themeName}.css`;
  56. const selector = `.scheme_${themeName} .btn.modern.disabled.limited span.btnTitle`;
  57. const sheet = [...document.styleSheets].find(s => s.href && s.href.includes(themeStylesheet));
  58. const rule = [...sheet.cssRules].find(r => r.selectorText === selector);
  59.  
  60. progressBarColor = rule.style.color;
  61.  
  62. const initButtonExtension = button => {
  63. const buttonContent = button.buttonContent;
  64. if (!buttonContent) return;
  65.  
  66. const statusBar = document.createElement('div');
  67. statusBar.className = 'statusBar';
  68. Object.assign(statusBar.style, {
  69. display: 'none',
  70. position: 'absolute',
  71. bottom: '0px',
  72. left: '4%',
  73. height: '1px',
  74. width: '92%',
  75. pointerEvents: 'none'
  76. });
  77. buttonContent.appendChild(statusBar);
  78. button.statusBar = statusBar;
  79.  
  80. const progressBar = document.createElement('div');
  81. progressBar.className = 'progressBar';
  82. Object.assign(progressBar.style, {
  83. display: 'inline-block',
  84. float: 'left',
  85. height: '100%',
  86. width: '0%',
  87. backgroundColor: progressBarColor,
  88. borderRadius: '2px 2px 2px 2px'
  89. });
  90. statusBar.appendChild(progressBar);
  91. statusBar.progressBar = progressBar;
  92. };
  93.  
  94. const updateButtons = () => {
  95. document.querySelectorAll('.tabInner .btn.nosel .statusBar').forEach(el => {
  96. el.style.display = 'none';
  97. });
  98.  
  99. getButtons()
  100. .filter(b => b.model.visible && !b.model.enabled && b.buttonContent.offsetParent)
  101. .filter(b => !/\((?:complete|in progress)\)/i.test(b.model.name))
  102. .forEach(button => {
  103. if (!button.buttonContent.querySelector('.statusBar')) {
  104. initButtonExtension(button);
  105. }
  106.  
  107. const prices = extendPrices(button.model.prices);
  108. const minPercent = Math.max(Math.min(1, ...prices.map(p => p.percent)), 0.01);
  109. if (minPercent >= 1) return;
  110.  
  111. button.statusBar.style.display = 'inline-block';
  112. button.statusBar.progressBar.style.width = (minPercent * 100) + '%';
  113. });
  114. };
  115.  
  116. setInterval(() => {
  117. updateButtons();
  118. }, 100);
  119. };
  120.  
  121. if (document.readyState === 'loading') {
  122. document.addEventListener('DOMContentLoaded', init);
  123. } else {
  124. init();
  125. }
  126. })();