[Pokeclicker] Additional Visual Settings

Adds additional settings for hiding some visual things to help out with performance. Also, includes various features that help with ease of accessibility.

  1. // ==UserScript==
  2. // @name [Pokeclicker] Additional Visual Settings
  3. // @namespace Pokeclicker Scripts
  4. // @author Ephenia (Credit: Optimatum)
  5. // @description Adds additional settings for hiding some visual things to help out with performance. Also, includes various features that help with ease of accessibility.
  6. // @copyright https://github.com/Ephenia
  7. // @license GPL-3.0 License
  8. // @version 3.0
  9.  
  10. // @homepageURL https://github.com/Ephenia/Pokeclicker-Scripts/
  11. // @supportURL https://github.com/Ephenia/Pokeclicker-Scripts/issues
  12.  
  13. // @match https://www.pokeclicker.com/
  14. // @icon https://www.google.com/s2/favicons?domain=pokeclicker.com
  15. // @grant unsafeWindow
  16. // @run-at document-idle
  17. // ==/UserScript==
  18.  
  19. // TODO disable party attack number + tooltip
  20.  
  21. class AdditionalVisualSettings {
  22. static graphicsDisabledSettings = {
  23. route: {
  24. header: ko.observable(false),
  25. pokemon: ko.observable(false),
  26. catchIcon: ko.observable(false),
  27. healthbar: ko.observable(false),
  28. attack: ko.observable(false),
  29. },
  30. gym: {
  31. header: ko.observable(false),
  32. timer: ko.observable(false),
  33. pokemon: ko.observable(false),
  34. healthbar: ko.observable(false),
  35. attack: ko.observable(false),
  36. },
  37. dungeon: {
  38. header: ko.observable(false),
  39. timer: ko.observable(false),
  40. images: ko.observable(false),
  41. attack: ko.observable(false),
  42. },
  43. battleFrontier: {
  44. header: ko.observable(false),
  45. timer: ko.observable(false),
  46. pokemon: ko.observable(false),
  47. healthbar: ko.observable(false),
  48. },
  49. };
  50. static autoClickerIntegration = ko.observable(JSON.parse(localStorage.getItem('AVSautoClickerIntegration') || 'false'));
  51. // Disable graphics unless autoclicker integration is on and autoclicker is not running
  52. static graphicsSettingsActive = ko.computed({
  53. read: () => !(typeof EnhancedAutoClicker === 'function' && this.autoClickerIntegration() && !EnhancedAutoClicker.autoClickState()),
  54. deferEvaluation: true
  55. });
  56.  
  57. static loadGraphicsSettings() {
  58. try {
  59. const savedSettings = JSON.parse(localStorage.getItem('AVSgraphicsDisabledSettings') || '{}');
  60. Object.keys(this.graphicsDisabledSettings).forEach(state => {
  61. Object.keys(this.graphicsDisabledSettings[state]).forEach(setting => {
  62. if (savedSettings[state]?.[setting] != undefined) {
  63. const val = !!savedSettings[state][setting];
  64. this.graphicsDisabledSettings[state][setting](val);
  65. }
  66. });
  67. });
  68. } catch {
  69. this.saveGraphicsSettings();
  70. }
  71. }
  72.  
  73. static saveGraphicsSettings() {
  74. const settingsToSave = {};
  75. Object.keys(this.graphicsDisabledSettings).forEach(state => {
  76. settingsToSave[state] = {};
  77. Object.keys(this.graphicsDisabledSettings[state]).forEach(setting => {
  78. settingsToSave[state][setting] = this.graphicsDisabledSettings[state][setting]();
  79. });
  80. });
  81. localStorage.setItem('AVSgraphicsDisabledSettings', JSON.stringify(settingsToSave));
  82. }
  83.  
  84. static initOnLoad() {
  85. this.addGraphicsBindings();
  86. this.addOptimizeVitamins();
  87. }
  88.  
  89. static initVisualSettings() {
  90. this.loadGraphicsSettings();
  91.  
  92. // Add shortcut menu icons
  93. const getMenu = document.getElementById('startMenu');
  94. const shortcutsToAdd = [
  95. ['quick-settings', '#settingsModal', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAMAAADW3miqAAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAkZQTFRFAAAAAAAAAAAAAAAAAAAAAAAAAAAAU1tsSEhgGyM0AAAAu8Pcu8PUwMDYKSk5AAAAAAAAoqq7AAAAAAAAe3uUxMTdu8TcvcXet7fIgJGiAAAAs7vUwsLTx8fYw8PUt8jZAAAAAAAAZWV2v7/hMTlKtLS8vMTVucLavMTdAAAAAAAASEhIJycncXFxcXl5c3N7cnJyZmZmu7vUAAAAAAAAtLzNv7/QRERE////t7e3xMTEQEBAFRUVdHR8aGhwAAAAdXV1vLzNc3OEAAAACgoKs7vLAgIKdHR0dXV9BgYOtLzEAAAAAAAAAAAAe4OTOjpCVFRcKTE5xMTVAAABBgYGbW11b29vAAAAAAAArr/QtsfYpqa3MDAwAA0eAFZnAFZ4AA0vAAAAEBAQvcbWMjtLISEhAEVnAJO0CbbfAKHUAJK0AAAeb2+ACQkJv7/YSUlaNDQ0IYepAJC5AI3AISEyvb3Wa3OEXWV2HT8/DZW3AJKzCZGzAAAICSsrAAUFAHudAIytAGuNKTFCMjJLRUVWACYmAHmSACwsDAwMICAgAAAAm5u0RUVFAAojABMjvLzecnJ7YWFyKSk6Y2t7S0tLBwcHAAANh4eYAAAJanJybXV1VFRUAgoKrq6uAgIDNjpTVGVlWlpaeXl5cnt7CgoTqqqzmpqqOztbMTlKY2N4na6uKioqERERcnJ6ChMTvLzVY2N4YGF2wMDiMjIyCAgIExsbtLTEOj1FjIydOkBAs8TVL0BAWWp7YmJzkpqzc3uMdnaHAAAAI+eLBgAAAMJ0Uk5TABFE/6rdd////2b/////iFX/Qv3////////v//////+E4f////////9x3P//////////5fv/////////////Uf///zP/////////DzGo////////////u+7//////////yL/////////////////////////////////////////////////////if//////////////////////////E///////////DAP/////////gv///////////////////5k2RgJ2AAACq0lEQVR4nJXUaVfTQBQGYPBFUZNqtTRFKa3SElCBVqQQLNEARXFfqtgGt4qKYkVQEEXcxR1w38UdRcUNrIr7P3MmCdDQw+F4P83JPHPv5E4mcXH/HfGjijEAErTxWIwbiSSO1+aACcDEGEMIw7CGSZhsNBqBKSxL2DAz1ZRk5khYoEQyHU8D9HucbjKrKMVqTU212ejYnqzPNWNmmsPBWQwGp5rJmc5nZHCZejRrtoKcmIOs7ByXey6cubkEGXXl5uU5PPkoEArne4tEGguQzjuH7XyhycEDAqlUCAWJEopLUKpDeb6yRYtVVFBerrIlS6P3tGz5CthXrlKRoGUSxdVr1voHUpWaTGTOtk4S10MQBK+3osKtIBd4v3+DigIIynKwcqMkutw+YNPmLVtVJGYFS2g/QvRAtskcV7U9J4c+37Gzetfumj1hOg5Le2tZfp+yMdQRxMDlohOor95/oKa+QSQZCGpkec9BBTWxFKkVDh2ubm5uPtIikhcIh8FwHNsUg45qSO3ZCOjYcYJOnGwRaChIrlPQKQ/PNp6ukCg603qWBM7RTOcvoGoAXQT8ibWXLiuoobWtrb3d7ZYI8vq8tDlq20PjSPUOEOQmA1ESr9DXRF3YZwtevTb03Seg4/oNUSS9vKkh0hCvz04WBQZvROjW7cw7dDmyB05OvIsyt9LtqHvAMLgH3B9CDx7y6HwUjUJ4LCfjyVO17+Rrevb8hacLL191RyEjXstW65u3PT3a2b57zzm6PnR+7NaVqyWN+yT39uFzUVFfL2OwcI5IEr7oqn1Vkd1eXJyfn2KhV8ocieCbfuP9/eR5pgfffzT91JB52B2OB9LI81/qtUPEwDJMJfQdiKP/Efz+oy0FDGwXYg3dlx+BwbwkjDFEmRu8ZvGBv7FZRol/3T2xhRzFta4AAAAASUVORK5CYII='],
  96. ['quick-inventory', '#showItemsModal', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADgAAAA4BAMAAABaqCYtAAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAACdQTFRFAAAA0KhgoHhA+PCw+OB46MhgQEhoqKioeHh4WFhYuEgA2GgAgCgAQaYPaAAAAA10Uk5TAP///////////////y0EQa0AAAH3SURBVHictdTNkpNAEABg5IBXe7bgnGGJOSuU3kmT8koq4xnWdZKjFasSHsBKyBuQV/AR9OKj2TP8zMCa1YtdRULPR1MzTIPj/D1ewKsbieO4d/Fbk93Hb2aOncbJkLvL2L7UjacYm9L5J8K0zyLCd+sBcxYnaBCXMfeH+0CAiFnRZt6GkhJmA0KEKLrUFYgpDOgBPKKAoM1KELgCKAyynIUh6AhD7vMRBms1xzaSdF6OK40pHVcyjK3Af0W9lBFaS3kWHf4UmWOjPdsnqErfH49fVeEIS4WoTCkqDCx8pBGsFdbqbGVjsKeRD7WOb3S6KA16wWGM27IwyI50X4LuZ3XkBt1wt9+gLqJyzBancHgG7l3y+rzf9Jgtqofl0Ln3ccJ354PsYlud2LLvXFdtA9vV9VVHXZ+42qTZgBEwOQSH1CDNPULVPG0AYEpDBmklmS905NS3Bj1kcorff/74pXErL9fbWNUyuoWefICbSC30DNJLNEVhLeV/4ZyOHgnWwjwhQhodUAiFhUHfxtzGTPA819W6yveZ2PRIl3LwgbfIIAdGd+la7DMAvefA228CoyMA+Nh3Ju3+GGnA9CZtm43y4NixraQV5ynWzbWLSz3Bl9Xl0mPTnL+M0NOVTaMuocpihI6k4Ua9ZPTXSGcSnplPMbU/xm/a6CAG6AGINQAAAABJRU5ErkJggg=='],
  97. ['quick-pokedex', '#pokedexModal', 'data:image/png;base64,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YWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA0LjQuMC1FeGl2MiI+CiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICB4bWxuczppcHRjRXh0PSJodHRwOi8vaXB0Yy5vcmcvc3RkL0lwdGM0eG1wRXh0LzIwMDgtMDItMjkvIgogICAgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iCiAgICB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIgogICAgeG1sbnM6cGx1cz0iaHR0cDovL25zLnVzZXBsdXMub3JnL2xkZi94bXAvMS4wLyIKICAgIHhtbG5zOkdJTVA9Imh0dHA6Ly93d3cuZ2ltcC5vcmcveG1wLyIKICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIgogICAgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICB4bXBNTTpEb2N1bWVudElEPSJnaW1wOmRvY2lkOmdpbXA6NzMwODk4ZTQtNmFlNy00OTg4LTkxY2QtMWZjNzljOWM2YWMyIgogICB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjdlN2U2MmY1LTIyNWMtNGU0YS04NjlhLTJmYjRlYzg1ODUyZSIKICAgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOjk0YzQwZGZmLTBkODctNDc1Ny1iZDNlLWZhZjQ5ZDg5MTU3ZSIKICAgR0lNUDpBUEk9IjIuMCIKICAgR0lNUDpQbGF0Zm9ybT0iV2luZG93cyIKICAgR0lNUDpUaW1lU3RhbXA9IjE2NTI2MjA2NTQ2Mzk5MjQiCiAgIEdJTVA6VmVyc2lvbj0iMi4xMC4yMiIKICAgZGM6Rm9ybWF0PSJpbWFnZS9wbmciCiAgIHRpZmY6T3JpZW50YXRpb249IjEiCiAgIHhtcDpDcmVhdG9yVG9vbD0iR0lNUCAyLjEwIj4KICAgPGlwdGNFeHQ6TG9jYXRpb25DcmVhdGVkPgogICAgPHJkZjpCYWcvPgogICA8L2lwdGNFeHQ6TG9jYXRpb25DcmVhdGVkPgogICA8aXB0Y0V4dDpMb2NhdGlvblNob3duPgogICAgPHJkZjpCYWcvPgogICA8L2lwdGNFeHQ6TG9jYXRpb25TaG93bj4KICAgPGlwdGNFeHQ6QXJ0d29ya09yT2JqZWN0PgogICAgPHJkZjpCYWcvPgogICA8L2lwdGNFeHQ6QXJ0d29ya09yT2JqZWN0PgogICA8aXB0Y0V4dDpSZWdpc3RyeUlkPgogICAgPHJkZjpCYWcvPgogICA8L2lwdGNFeHQ6UmVnaXN0cnlJZD4KICAgPHhtcE1NOkhpc3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6MGIzZjhkNTAtNjQ2My00NjJlLTkzZGEtOTM0OWNhOTE1YWFkIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJHaW1wIDIuMTAgKFdpbmRvd3MpIgogICAgICBzdEV2dDp3aGVuPSIyMDIyLTA1LTE1VDE1OjE3OjM0Ii8+CiAgICA8L3JkZjpTZXE+CiAgIDwveG1wTU06SGlzdG9yeT4KICAgPHBsdXM6SW1hZ2VTdXBwbGllcj4KICAgIDxyZGY6U2VxLz4KICAgPC9wbHVzOkltYWdlU3VwcGxpZXI+CiAgIDxwbHVzOkltYWdlQ3JlYXRvcj4KICAgIDxyZGY6U2VxLz4KICAgPC9wbHVzOkltYWdlQ3JlYXRvcj4KICAgPHBsdXM6Q29weXJpZ2h0T3duZXI+CiAgICA8cmRmOlNlcS8+CiAgIDwvcGx1czpDb3B5cmlnaHRPd25lcj4KICAgPHBsdXM6TGljZW5zb3I+CiAgICA8cmRmOlNlcS8+CiAgIDwvcGx1czpMaWNlbnNvcj4KICA8L3JkZjpEZXNjcmlwdGlvbj4KIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/Pn9Za1QAAAAGYktHRAD/AP8A/6C9p5MAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAAHdElNRQfmBQ8NESISKzy+AAAA3ElEQVQoz5WSIXrDMAyFf3cBA4MBPYKPIRjoIwwWFg7mCIU+QqCgoeBgYGDhQA4wpoLGWZaFTMRP+t57ki0HdiEivs3NLGzzZk+8mO0d/I9QRHwAH2P07/zhY4w+xugD+ADeg1fDUxXFGInXBO/981xqAHHT8EVE/GJG27bMnxPt6xfTTVfCPM8AvN3vINI3WyeA6aZcz2cO71NHqoSKSymklMgiTxPAFtwAlFLoum4V5Zx/8EJMKaGqhPpKpZSVVMUppbWmqphZOB251pGOIuwXtyeq6q+Nh/9+jQcRyGEasqbeQgAAAABJRU5ErkJggg=='],
  98. ];
  99. shortcutsToAdd.forEach(([id, modal, source]) => {
  100. const quickElem = document.createElement('img');
  101. quickElem.id = id;
  102. quickElem.src = source;
  103. quickElem.setAttribute('href', modal);
  104. quickElem.setAttribute('data-toggle', 'modal');
  105. getMenu.prepend(quickElem);
  106. });
  107.  
  108. // Add AVS settings options to scripts tab
  109. const settingsBody = createScriptSettingsContainer('Additional Visual Settings');
  110.  
  111. let elem = document.createElement('tr');
  112. elem.innerHTML = `<td class="p-2" colspan="2"><label class="m-0">Disable battle visuals</label></td>`;
  113. settingsBody.appendChild(elem);
  114.  
  115. // Graphics-disabling settings
  116. Object.keys(this.graphicsDisabledSettings).forEach(state => {
  117. elem = document.createElement('tr');
  118. elem.innerHTML = `<th class="p-2 col-md-5" scope="row">${GameConstants.camelCaseToString(state)}</th><td class="p-2" style="display:flex;"></td>`;
  119. let innerElem = elem.querySelector('td');
  120. Object.keys(this.graphicsDisabledSettings[state]).forEach(setting => {
  121. const container = document.createElement('div');
  122. container.className = 'px-3'
  123. container.innerHTML = `${GameConstants.camelCaseToString(setting)} <input id="checkbox-AVS-${state}-${setting}" type="checkbox" class="mx-1"></td>`;
  124. const checkbox = container.querySelector('input');
  125. checkbox.checked = this.graphicsDisabledSettings[state][setting]();
  126. checkbox.addEventListener('change', event => {
  127. this.graphicsDisabledSettings[state][setting](event.target.checked);
  128. this.saveGraphicsSettings();
  129. });
  130. innerElem.appendChild(container);
  131. });
  132. settingsBody.appendChild(elem);
  133. });
  134.  
  135. // EnhancedAutoClicker integration setting, if the script is present
  136. if (typeof EnhancedAutoClicker === 'function') {
  137. elem = document.createElement('tr');
  138. elem.innerHTML = `<td class="p-2" colspan="2"><label class="m-0" for="checkbox-AVS-autoClickerIntegration">Disable graphics only when EnhancedAutoClicker is on</label>` +
  139. `<input id="checkbox-AVS-autoClickerIntegration" type="checkbox" class="mx-2"></td>`;
  140. settingsBody.appendChild(elem);
  141. const checkbox = elem.querySelector('input');
  142. checkbox.checked = this.autoClickerIntegration();
  143. checkbox.addEventListener('change', event => {
  144. this.autoClickerIntegration(event.target.checked);
  145. localStorage.setItem('AVSautoClickerIntegration', this.autoClickerIntegration());
  146. });
  147. }
  148.  
  149. // Create travel shortcut buttons on town map
  150. const travelShortcutsToAdd = [
  151. ['dock-button', 'Dock', {left: 32, top: 0}, MapHelper.openShipModal],
  152. ['gyms-button', 'Gyms', {left: 75, top: -8}, () => { AdditionalVisualSettings.generateRegionGymsList(); $('#gymsShortcutModal').modal('show'); }],
  153. ['dungeons-button', 'Dungeons', {left: 121, top: -8}, () => { AdditionalVisualSettings.generateRegionDungeonssList(); $('#dungeonsShortcutModal').modal('show'); }],
  154. ];
  155.  
  156. travelShortcutsToAdd.forEach(([id, name, pos, func]) => {
  157. const button = document.createElement('button');
  158. button.id = id;
  159. button.textContent = name;
  160. button.className = 'btn btn-block btn-success';
  161. button.style = `position: absolute; left: ${pos.left}px; top: ${pos.top}px; width: auto; height: 41px; font-size: 11px;`;
  162. button.addEventListener('click', func);
  163. document.getElementById('townMap').appendChild(button);
  164. });
  165.  
  166. // Prevent ship modal sequence-breaking
  167. document.getElementById('dock-button').setAttribute('data-bind', 'enabled: TownList[GameConstants.DockTowns[player.region]].isUnlocked()');
  168. ko.applyBindings(App.game, document.getElementById('dock-button'));
  169.  
  170. // Create gym and dungeon shortcut modals
  171. const modalNames = ['gyms', 'dungeons'];
  172. const fragment = new DocumentFragment();
  173. for (const name of modalNames) {
  174. const customModal = document.createElement('div');
  175. customModal.setAttribute('class', 'modal noselect fade');
  176. customModal.setAttribute('tabindex', '-1');
  177. customModal.setAttribute('role', 'dialogue');
  178. customModal.setAttribute('id', `${name}ShortcutModal`);
  179. customModal.setAttribute('aria-labelledby', `${name}ShortcutModalLabel`);
  180. customModal.innerHTML = `<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered modal-sm" role="document">
  181. <div class="modal-content">
  182. <div class="modal-header" style="justify-content: space-around;">
  183. <h5 id="${name}-shortcut-modal-title" class="modal-title"></h5>
  184. <button type="button" class="close" data-dismiss="modal" aria-label="Close">
  185. <span aria-hidden="true">×</span>
  186. </button>
  187. </div>
  188. <div class="modal-body bg-ocean">
  189. <div id="${name}-shortcut-buttons"></div>
  190. </div>
  191. </div>
  192. </div>`;
  193. fragment.appendChild(customModal);
  194. }
  195. document.getElementById('ShipModal').after(fragment);
  196.  
  197. addGlobalStyle('.pageItemTitle { height:38px }');
  198. addGlobalStyle('#quick-settings, #quick-inventory, #quick-pokedex { height: 36px; background-color: #eee; border: 4px solid #eee; cursor: pointer; image-rendering: pixelated; }');
  199. addGlobalStyle('#quick-pokedex { padding: 2px; }')
  200. addGlobalStyle(':is(#quick-settings, #quick-inventory, #quick-pokedex):hover { background-color:#ddd; border: 4px solid #ddd; }');
  201. addGlobalStyle('#shortcutsContainer { display: block !important; }');
  202. addGlobalStyle('.gyms-shortcut-leaders { display: flex; pointer-events: none; position: absolute; height: 36px; top: 0; left: 0; image-rendering: pixelated; }');
  203. addGlobalStyle('.gyms-shortcut-badges { position: absolute; height: 36px; display: flex; top: 0; right: 0; }');
  204. addGlobalStyle('.dungeons-shortcut-costs { position: relative; margin-right: 12px; filter: none !important }');
  205. addGlobalStyle('#dungeons-shortcut-buttons > button:hover { -webkit-animation: bounceBackground 60s linear infinite alternate; animation: bounceBackground 60s linear infinite alternate; }');
  206. addGlobalStyle('#dungeons-shortcut-buttons > button * { z-index: 2 }');
  207. addGlobalStyle('.dungeons-shortcut-overlay { width: 100%; height: 100%; position: absolute; background-color: rgba(0,0,0,0.45); margin-top: -6px; margin-left: -8px; z-index: 1 !important }');
  208. addGlobalStyle('.dungeons-shortcut-info { position: relative; font-weight: bold }');
  209. }
  210.  
  211. static generateRegionGymsList() {
  212. const gymsBtns = document.getElementById('gyms-shortcut-buttons');
  213. const gymsHead = document.getElementById('gyms-shortcut-modal-title');
  214. gymsHead.textContent = `Gym Select (${GameConstants.camelCaseToString(GameConstants.Region[player.region])})`;
  215. gymsBtns.innerHTML = '';
  216. const fragment = new DocumentFragment();
  217. const regionGyms = Object.values(GymList).filter((gym) => gym.parent?.region === player.region);
  218. for (const gym of regionGyms) {
  219. const hasBadgeImage = !(BadgeEnums[gym.badgeReward].startsWith('Elite') || BadgeEnums[gym.badgeReward] == 'None');
  220. const badgeImage = (hasBadgeImage ? `assets/images/badges/${BadgeEnums[gym.badgeReward]}.svg` : '');
  221. const btn = document.createElement('button');
  222. btn.setAttribute('style', 'position: relative;');
  223. btn.setAttribute('class', 'btn btn-block btn-success');
  224. btn.addEventListener('click', () => {
  225. if (!MapHelper.isTownCurrentLocation(gym.parent.name)) {
  226. MapHelper.moveToTown(gym.parent.name);
  227. }
  228. $("#gymsShortcutModal").modal("hide");
  229. GymRunner.startGym(gym);
  230. });
  231. btn.disabled = !(gym.isUnlocked() && gym.parent.isUnlocked());
  232. btn.innerHTML = `<div class="gyms-shortcut-leaders">
  233. <img src="${gym.imagePath}" onerror="{ this.src='assets/images/npcs/specialNPCs/Mysterious Trainer.png'; }">
  234. </div>
  235. <div class="gyms-shortcut-badges">
  236. <img src="${badgeImage}" onerror="{ this.onerror=null; this.style.display='none'; }">
  237. </div>
  238. ${gym.leaderName}`;
  239. fragment.appendChild(btn);
  240. }
  241. gymsBtns.appendChild(fragment);
  242. }
  243.  
  244. static generateRegionDungeonssList() {
  245. const dungeonsBtns = document.getElementById('dungeons-shortcut-buttons');
  246. const dungeonsHead = document.getElementById('dungeons-shortcut-modal-title');
  247. dungeonsHead.textContent = `Dungeon Select (${GameConstants.camelCaseToString(GameConstants.Region[player.region])})`;
  248. dungeonsBtns.innerHTML = '';
  249. const fragment = new DocumentFragment();
  250. const dungeonTowns = Object.values(TownList).filter((town) => (town.region === player.region && town.constructor.name === 'DungeonTown' && town.dungeon != null));
  251. for (const town of dungeonTowns) {
  252. const dungeon = town.dungeon;
  253. const dungeonClears = App.game.statistics.dungeonsCleared[GameConstants.getDungeonIndex(dungeon.name)]();
  254. const canAffordEntry = App.game.wallet.currencies[GameConstants.Currency.dungeonToken]() >= dungeon.tokenCost;
  255. const canAccess = town.isUnlocked() && dungeon.isUnlocked() && canAffordEntry;
  256. const btn = document.createElement('button');
  257. btn.setAttribute('style', `position: relative; background-image: url("assets/images/towns/${dungeon.name}.png"); background-position: center;opacity: ${canAccess ? 1 : 0.70}; filter: brightness(${canAccess ? 1 : 0.70});`);
  258. btn.setAttribute('class', 'btn btn-block btn-success');
  259. btn.addEventListener('click', () => {
  260. if (!MapHelper.isTownCurrentLocation(town.name)) {
  261. MapHelper.moveToTown(town.name);
  262. }
  263. $('#dungeonsShortcutModal').modal('hide');
  264. DungeonRunner.initializeDungeon(dungeon);
  265. });
  266. btn.disabled = !canAccess;
  267. btn.innerHTML = `<div class="dungeons-overlay"></div>
  268. <div class="dungeons-shortcut-costs">
  269. <img src="assets/images/currency/dungeonToken.svg" style="height: 24px; width: 24px;">
  270. <span style="font-weight: bold;color: ${canAffordEntry ? 'greenyellow' : '#f04124'}">${dungeon.tokenCost.toLocaleString('en-US')}</span>
  271. </div>
  272. <div class="dungeons-shortcut-info">
  273. <span>${dungeon.name}</span>
  274. <div>${dungeonClears.toLocaleString('en-US')} clears</div>
  275. </div>`;
  276. fragment.appendChild(btn);
  277. }
  278. dungeonsBtns.appendChild(fragment);
  279. }
  280.  
  281. // Must execute before game loads and applies knockout bindings
  282. static addGraphicsBindings() {
  283. function selectorWorkaround(element, selector) {
  284. try {
  285. return element.querySelector(selector);
  286. } catch {
  287. const [, outer, inner] = selector.match(/(.+):has\((.+)\)/);
  288. const innerElem = element.querySelector(`${outer} ${inner}`);
  289. return Array.from(element.querySelectorAll(outer)).find(e => e.contains(innerElem));
  290. }
  291. }
  292.  
  293. const selectors = {
  294. route: {
  295. container: '#routeBattleContainer',
  296. header: '.pageItemTitle > div:has(> knockout)',
  297. pokemon: 'div:has(> knockout[data-bind*="pokemonSpriteTemplate"])',
  298. catchIcon: 'div.catchChance',
  299. healthbar: 'div.progress.hitpoints',
  300. attack: '.pageItemFooter knockout[data-bind*="pokemonAttackTemplate"]',
  301. },
  302. gym: {
  303. container: '#battleContainer div[data-bind="if: App.game.gameState === GameConstants.GameState.gym"]',
  304. header: [
  305. ['h2.pageItemTitle > knockout:has(knockout[data-bind*="pokemonNameTemplate"])', 'before'],
  306. ['h2.pageItemTitle > knockout:has(span[data-bind*="pokemonsDefeatedComputable"])', 'after']
  307. ],
  308. timer: 'h2.pageItemTitle .timer',
  309. pokemon: 'div:has(> knockout[data-bind*="pokemonSpriteTemplate"])',
  310. healthbar: 'div.progress.hitpoints',
  311. attack: '.pageItemFooter knockout[data-bind*="pokemonAttackTemplate"]',
  312. },
  313. dungeon: {
  314. container: '#battleContainer div[data-bind="if: App.game.gameState === GameConstants.GameState.dungeon"]',
  315. header: [
  316. ['h2.pageItemTitle > knockout:has(knockout[data-bind*="pokemonNameTemplate"])', 'before'],
  317. ['h2.pageItemTitle > knockout:has(span[data-bind*="defeatedTrainerPokemon"])', 'after']
  318. ],
  319. timer: 'h2.pageItemTitle .timer',
  320. images: [
  321. ['h2.pageItemTitle', 'after'],
  322. ['h2.pageItemFooter', 'before']
  323. ],
  324. attack: '.pageItemFooter knockout[data-bind*="pokemonAttackTemplate"]',
  325. },
  326. battleFrontier: {
  327. container: '#battleContainer div[data-bind="if: App.game.gameState == GameConstants.GameState.battleFrontier"]',
  328. header: [
  329. ['h2.pageItemTitle > knockout:has(knockout[data-bind*="pokemonNameTemplate"])', 'before'],
  330. ['h2.pageItemTitle > knockout[data-bind*="pokemonLeftImages"]', 'after']
  331. ],
  332. timer: 'h2.pageItemTitle .timer',
  333. pokemon: 'div:has(> knockout[data-bind*="pokemonSpriteTemplate"])',
  334. healthbar: 'div.progress.hitpoints',
  335. },
  336. };
  337.  
  338. Object.keys(this.graphicsDisabledSettings).forEach(state => {
  339. const container = document.querySelector(selectors[state]?.container)
  340. if (!container) {
  341. console.error(`AVS: could not find ${state} container`);
  342. return;
  343. }
  344. Object.keys(this.graphicsDisabledSettings[state]).forEach(setting => {
  345. if (!selectors[state]?.[setting]) {
  346. return;
  347. }
  348. const selector = selectors[state][setting];
  349. const binding = `ko ifnot: AdditionalVisualSettings.graphicsDisabledSettings.${state}.${setting}() && AdditionalVisualSettings.graphicsSettingsActive()`;
  350. // Add binding for this setting
  351. if (Array.isArray(selector)) {
  352. // For binding multiple elements at once, which requires more complicated selecting
  353. selector.forEach(([query, order], i) => {
  354. const elem = selectorWorkaround(container, query);
  355. const commentBinding = i % 2 == 0 ? binding : '/ko';
  356. if (order == 'before') {
  357. elem.before(new Comment(commentBinding));
  358. } else {
  359. elem.after(new Comment(commentBinding));
  360. }
  361. });
  362. } else {
  363. const elem = selectorWorkaround(container, selector);
  364. // Special case: insert a backup attack-disabled element so formatting doesn't look weird
  365. // Do this before applying the main binding to put it outside the binding comments
  366. if (setting == 'attack') {
  367. const replacementAttack = document.createElement('span');
  368. elem.after(replacementAttack);
  369. replacementAttack.outerHTML = `<span style="display: inline;" data-bind="${binding.replace('ko ifnot:', 'if:')}">Pokémon Attack: <span>-----</span></span>`;
  370. }
  371. // Insert the binding
  372. elem.before(new Comment(binding));
  373. elem.after(new Comment('/ko'));
  374. }
  375. });
  376. });
  377. }
  378.  
  379.  
  380. static addOptimizeVitamins() {
  381. // Add button to vitamin menu
  382. // (must execute before game loads and applies knockout bindings)
  383. const btn = document.createElement('button');
  384. btn.setAttribute('class', 'btn btn-link btn-sm text-decoration-none align-text-top');
  385. btn.setAttribute('style', 'line-height: 0.6; font-size: 1rem; float: right;');
  386. btn.setAttribute('data-bind', `click: () => { if ($data) { $data.optimizeVitamins() } }, class: (!$data.breeding ? 'text-success' : 'text-muted')`);
  387. btn.innerHTML = '⚖';
  388. document.querySelector('#pokemonVitaminExpandedModal tbody[data-bind*="PartyController.getVitaminSortedList"] td').appendChild(btn);
  389.  
  390. // Add optimize-vitamin functions for party pokemon (adapted from wiki)
  391. PartyPokemon.prototype.calcBreedingEfficiency = function(vitaminsUsed) {
  392. // attack bonus
  393. const attackBonusPercent = (GameConstants.BREEDING_ATTACK_BONUS + vitaminsUsed[GameConstants.VitaminType.Calcium]) / 100;
  394. const proteinBoost = vitaminsUsed[GameConstants.VitaminType.Protein];
  395. const breedingAttackBonus = (this.baseAttack * attackBonusPercent) + proteinBoost;
  396. // egg steps
  397. const div = 300;
  398. const extraCycles = (vitaminsUsed[GameConstants.VitaminType.Calcium] + vitaminsUsed[GameConstants.VitaminType.Protein]) / 2;
  399. const steps = (this.eggCycles + extraCycles) * GameConstants.EGG_CYCLE_MULTIPLIER;
  400. const adjustedSteps = (steps <= div ? steps : Math.round(((steps / div) ** (1 - vitaminsUsed[GameConstants.VitaminType.Carbos] / 70)) * div));
  401. // efficiency
  402. return (breedingAttackBonus / adjustedSteps) * GameConstants.EGG_CYCLE_MULTIPLIER;
  403. }
  404.  
  405. PartyPokemon.prototype.optimizeVitamins = function() {
  406. const totalVitamins = (player.highestRegion() + 1) * 5;
  407. const carbosUnlocked = player.highestRegion() >= GameConstants.Region.unova;
  408. const calciumUnlocked = player.highestRegion() >= GameConstants.Region.hoenn;
  409. const prices = GameHelper.enumStrings(GameConstants.VitaminType).map(v => ItemList[v].basePrice);
  410. // Add our initial starting efficiency here
  411. let optimalVitamins = [0, 0, 0];
  412. let eff = this.calcBreedingEfficiency(optimalVitamins);
  413. // Check all max-vitamin combinations
  414. for (let carbos = carbosUnlocked * totalVitamins; carbos >= 0; carbos--) {
  415. for (let calcium = calciumUnlocked * (totalVitamins - carbos); calcium >= 0; calcium--) {
  416. let protein = totalVitamins - (carbos + calcium);
  417. let newEff = this.calcBreedingEfficiency([protein, calcium, carbos]);
  418. if (newEff >= eff) {
  419. const newVitamins = [protein, calcium, carbos];
  420. if (newEff == eff) {
  421. // Choose cheaper version
  422. const oldPrice = optimalVitamins.reduce((sum, v, i) => (sum + v * prices[i]), 0);
  423. const newPrice = newVitamins.reduce((sum, v, i) => (sum + v * prices[i]), 0);
  424. if (oldPrice <= newPrice) {
  425. continue;
  426. }
  427. }
  428. eff = newEff;
  429. optimalVitamins = newVitamins;
  430. }
  431. }
  432. }
  433. // Optimally use vitamins
  434. GameHelper.enumNumbers(GameConstants.VitaminType).forEach((v) => {
  435. if (this.vitaminsUsed[v]()) {
  436. this.removeVitamin(v, Infinity);
  437. }
  438. });
  439. GameHelper.enumNumbers(GameConstants.VitaminType).forEach((v) => {
  440. if (v < optimalVitamins.length && optimalVitamins[v] > 0) {
  441. this.useVitamin(v, optimalVitamins[v]);
  442. }
  443. });
  444. }
  445. }
  446. }
  447.  
  448.  
  449. /**
  450. * Creates container for scripts settings in the settings menu, adding scripts tab if it doesn't exist yet
  451. */
  452. function createScriptSettingsContainer(name) {
  453. const settingsID = name.replaceAll(/s/g, '').toLowerCase();
  454. var settingsContainer = document.getElementById('settings-scripts-container');
  455.  
  456. // Create scripts settings tab if it doesn't exist yet
  457. if (!settingsContainer) {
  458. // Fixes the Scripts nav item getting wrapped to the bottom by increasing the max width of the window
  459. document.querySelector('#settingsModal div').style.maxWidth = '850px';
  460. // Create and attach script settings tab link
  461. const settingTabs = document.querySelector('#settingsModal ul.nav-tabs');
  462. const li = document.createElement('li');
  463. li.classList.add('nav-item');
  464. li.innerHTML = `<a class="nav-link" href="#settings-scripts" data-toggle="tab">Scripts</a>`;
  465. settingTabs.appendChild(li);
  466. // Create and attach script settings tab contents
  467. const tabContent = document.querySelector('#settingsModal .tab-content');
  468. scriptSettings = document.createElement('div');
  469. scriptSettings.classList.add('tab-pane');
  470. scriptSettings.setAttribute('id', 'settings-scripts');
  471. tabContent.appendChild(scriptSettings);
  472. settingsContainer = document.createElement('div');
  473. settingsContainer.setAttribute('id', 'settings-scripts-container');
  474. scriptSettings.appendChild(settingsContainer);
  475. }
  476.  
  477. // Create settings container
  478. const settingsTable = document.createElement('table');
  479. settingsTable.classList.add('table', 'table-striped', 'table-hover', 'm-0');
  480. const header = document.createElement('thead');
  481. header.innerHTML = `<tr><th colspan="2">${name}</th></tr>`;
  482. settingsTable.appendChild(header);
  483. const settingsBody = document.createElement('tbody');
  484. settingsBody.setAttribute('id', `settings-scripts-${settingsID}`);
  485. settingsTable.appendChild(settingsBody);
  486.  
  487. // Insert settings container in alphabetical order
  488. let settingsList = Array.from(settingsContainer.children);
  489. let insertBefore = settingsList.find(elem => elem.querySelector('tbody').id > `settings-scripts-${settingsID}`);
  490. if (insertBefore) {
  491. insertBefore.before(settingsTable);
  492. } else {
  493. settingsContainer.appendChild(settingsTable);
  494. }
  495.  
  496. return settingsBody;
  497. }
  498.  
  499. function addGlobalStyle(css) {
  500. var head, style;
  501. head = document.getElementsByTagName('head')[0];
  502. if (!head) { return; }
  503. style = document.createElement('style');
  504. style.type = 'text/css';
  505. style.innerHTML = css;
  506. head.appendChild(style);
  507. }
  508.  
  509. function loadEpheniaScript(scriptName, initFunction, priorityFunction) {
  510. function reportScriptError(scriptName, error) {
  511. console.error(`Error while initializing '${scriptName}' userscript:\n${error}`);
  512. Notifier.notify({
  513. type: NotificationConstants.NotificationOption.warning,
  514. title: scriptName,
  515. message: `The '${scriptName}' userscript crashed while loading. Check for updates or disable the script, then restart the game.\n\nReport script issues to the script developer, not to the Pokéclicker team.`,
  516. timeout: GameConstants.DAY,
  517. });
  518. }
  519. const windowObject = !App.isUsingClient ? unsafeWindow : window;
  520. // Inject handlers if they don't exist yet
  521. if (windowObject.epheniaScriptInitializers === undefined) {
  522. windowObject.epheniaScriptInitializers = {};
  523. const oldInit = Preload.hideSplashScreen;
  524. var hasInitialized = false;
  525.  
  526. // Initializes scripts once enough of the game has loaded
  527. Preload.hideSplashScreen = function (...args) {
  528. var result = oldInit.apply(this, args);
  529. if (App.game && !hasInitialized) {
  530. // Initialize all attached userscripts
  531. Object.entries(windowObject.epheniaScriptInitializers).forEach(([scriptName, initFunction]) => {
  532. try {
  533. initFunction();
  534. } catch (e) {
  535. reportScriptError(scriptName, e);
  536. }
  537. });
  538. hasInitialized = true;
  539. }
  540. return result;
  541. }
  542. }
  543.  
  544. // Prevent issues with duplicate script names
  545. if (windowObject.epheniaScriptInitializers[scriptName] !== undefined) {
  546. console.warn(`Duplicate '${scriptName}' userscripts found!`);
  547. Notifier.notify({
  548. type: NotificationConstants.NotificationOption.warning,
  549. title: scriptName,
  550. message: `Duplicate '${scriptName}' userscripts detected. This could cause unpredictable behavior and is not recommended.`,
  551. timeout: GameConstants.DAY,
  552. });
  553. let number = 2;
  554. while (windowObject.epheniaScriptInitializers[`${scriptName} ${number}`] !== undefined) {
  555. number++;
  556. }
  557. scriptName = `${scriptName} ${number}`;
  558. }
  559. // Add initializer for this particular script
  560. windowObject.epheniaScriptInitializers[scriptName] = initFunction;
  561. // Run any functions that need to execute before the game starts
  562. if (priorityFunction) {
  563. $(document).ready(() => {
  564. try {
  565. priorityFunction();
  566. } catch (e) {
  567. reportScriptError(scriptName, e);
  568. // Remove main initialization function
  569. windowObject.epheniaScriptInitializers[scriptName] = () => null;
  570. }
  571. });
  572. }
  573. }
  574.  
  575. if (!App.isUsingClient || localStorage.getItem('additionalvisualsettings') === 'true') {
  576. if (!App.isUsingClient) {
  577. // Necessary for userscript managers
  578. unsafeWindow.AdditionalVisualSettings = AdditionalVisualSettings;
  579. }
  580. loadEpheniaScript('additionalvisualsettings', () => AdditionalVisualSettings.initVisualSettings(), () => AdditionalVisualSettings.initOnLoad());
  581. }