TheresMoreHelp

Helper for TheresMoreGame

  1. // ==UserScript==
  2. // @name TheresMoreHelp
  3. // @namespace TheresMoreGame.com
  4. // @match https://www.theresmoregame.com/play/
  5. // @grant none
  6. // @version 2.0.26
  7. // @description Helper for TheresMoreGame
  8. // @license MIT
  9. // @run-at document-idle
  10. // ==/UserScript==
  11.  
  12. ;(async () => {
  13. let cheatsOff = true
  14.  
  15. let scriptPaused = true
  16. let haveManualResourceButtons = true
  17. let isClicking = false
  18. let mainLoopRunning = false
  19.  
  20. const buildingsList = [
  21. { id: 'Mausoleum of gods', alwaysBuild: true, isSafe: true },
  22. { id: 'Tower of mana', alwaysBuild: true, isSafe: true },
  23. { id: 'City of Lights', alwaysBuild: true, isSafe: true },
  24. { id: 'Harbor district', alwaysBuild: true, isSafe: true },
  25. { id: 'Stock exchange', alwaysBuild: true, isSafe: true },
  26. { id: 'Mana pit', alwaysBuild: true, isSafe: true },
  27. { id: 'Great bombard', alwaysBuild: true, isSafe: true },
  28. { id: 'Refugees district', alwaysBuild: true, isSafe: true },
  29. { id: 'Academy of Freethinkers', alwaysBuild: true, isSafe: true },
  30. { id: 'City center', alwaysBuild: true, isSafe: true },
  31. { id: 'Cathedral', alwaysBuild: true, isSafe: true },
  32. { id: 'Great fair', alwaysBuild: true, isSafe: true },
  33. { id: 'Palisade', alwaysBuild: true, isSafe: true },
  34. { id: 'Wall', alwaysBuild: true, isSafe: true },
  35. { id: 'Tower of mana part', alwaysBuild: true, isSafe: true },
  36. { id: 'City of Lights part', alwaysBuild: true, isSafe: true },
  37. { id: 'Harbor district part', alwaysBuild: true, isSafe: true },
  38. { id: 'Stock exchange part', alwaysBuild: true, isSafe: true },
  39. { id: 'Mana pit part', alwaysBuild: true, isSafe: true },
  40. { id: 'Great bombard part', alwaysBuild: true, isSafe: true },
  41. { id: 'Refugees district part', alwaysBuild: true, isSafe: true },
  42. { id: 'A. of Freethinkers Part', alwaysBuild: true, isSafe: true },
  43. { id: 'City center part', alwaysBuild: true, isSafe: true },
  44. { id: 'Great fair unit', alwaysBuild: true, isSafe: true },
  45. { id: 'Cathedral part', alwaysBuild: true, isSafe: true },
  46. { id: 'Guild of craftsmen', alwaysBuild: true, isSafe: true },
  47. { id: 'Machines of gods', alwaysBuild: true, isSafe: true },
  48. { id: 'Library of Theresmore', alwaysBuild: true, isSafe: true },
  49. { id: 'Monastery', alwaysBuild: true, isSafe: true },
  50. { id: 'Watchman Outpost', alwaysBuild: true, isSafe: true },
  51. { id: 'Hall of the dead', alwaysBuild: true, isSafe: true },
  52. { id: 'Sawmill', alwaysBuild: true, isSafe: true },
  53. { id: 'Monument', alwaysBuild: true, isSafe: true },
  54. { id: 'Foundry', alwaysBuild: true, isSafe: true },
  55. { id: 'Builder district', alwaysBuild: true, isSafe: true },
  56. { id: 'Gan Eden', alwaysBuild: true, isSafe: true },
  57. { id: 'Portal of the dead', alwaysBuild: true, isSafe: true },
  58. { id: 'Library of SouLs', alwaysBuild: true, isSafe: true },
  59. { id: 'Souls', isSafe: true },
  60. { id: 'Books', isSafe: true },
  61. { id: 'Observatory', isSafe: true },
  62. { id: 'The Vaults', isSafe: true },
  63. { id: 'Credit union', isSafe: true },
  64. { id: 'Canava trading post', isSafe: true },
  65. { id: 'Bank', isSafe: true },
  66. { id: 'Marketplace', isSafe: true },
  67. { id: 'Artisan Workshop', isSafe: true },
  68. { id: 'Granary', isSafe: true },
  69. { id: 'Hall of wisdom', isSafe: true },
  70. { id: 'School', isSafe: true },
  71. { id: 'University', isSafe: true },
  72. { id: 'Research plant', isSafe: true },
  73. { id: 'Undead Herds', isSafe: true },
  74. { id: 'Guarded warehouse', isSafe: true },
  75. { id: 'Fiefdom', isSafe: true },
  76. { id: 'Natronite refinery', isSafe: true },
  77. { id: 'Alchemical laboratory', isSafe: true },
  78. { id: 'Lumberjack Camp', isSafe: true },
  79. { id: 'Quarry', isSafe: true },
  80. { id: 'Mine', isSafe: true },
  81. { id: 'Palisade part', isSafe: true },
  82. { id: 'Wall part', isSafe: true },
  83. { id: 'Rampart', alwaysBuild: true, isSafe: true },
  84. { id: 'Rampart part', isSafe: true },
  85. { id: 'Farm', isSafe: true },
  86. { id: 'Matter transmuter', isSafe: true },
  87. { id: 'Stable', isSafe: true },
  88. { id: 'Spiritual garden', isSafe: true },
  89. { id: 'Conclave', isSafe: true },
  90. { id: 'Magical tower', isSafe: true },
  91. { id: 'Temple', isSafe: true },
  92. { id: 'Altar of sacrifices', isSafe: true },
  93. { id: 'Fountain of Prosperity', isSafe: true },
  94. { id: 'Valley of plenty', isSafe: true },
  95. { id: 'Tax revenue checkpoints', isSafe: true },
  96. { id: 'Industrial plant', isSafe: true },
  97. { id: 'Magic Circle', isSafe: true },
  98. { id: 'Carpenter workshop', isSafe: true },
  99. { id: 'Grocery', isSafe: true },
  100. { id: 'Steelworks', isSafe: true },
  101. { id: 'Military academy', isSafe: true },
  102. { id: 'Ancient vault', isSafe: true },
  103. { id: 'Recruit training center', isSafe: true },
  104. { id: 'Officer training ground', isSafe: true },
  105. { id: 'Castrum Militia', isSafe: true },
  106. { id: 'Mansion', isSafe: false, requires: { resource: 'Food', parameter: 'speed', minValue: 3 } },
  107. { id: 'City Hall', isSafe: false, requires: { resource: 'Food', parameter: 'speed', minValue: 1.5 } },
  108. { id: 'Residential block', isSafe: false, requires: { resource: 'Food', parameter: 'speed', minValue: 5 } },
  109. { id: 'Common House', isSafe: false, requires: { resource: 'Food', parameter: 'speed', minValue: 1 } },
  110. { id: 'Storage facility', isSafe: true },
  111. { id: 'Ballista', isSafe: true },
  112. { id: 'Large warehouse', isSafe: true },
  113. { id: 'Large storehouse', isSafe: true },
  114. { id: 'Store', isSafe: true },
  115. { id: 'Natronite depot', isSafe: true },
  116. { id: 'Barracks', isSafe: true },
  117. { id: 'Minefield', isSafe: true },
  118. { id: 'Natronite balloon', isSafe: true },
  119. { id: 'Decryption of the portal', isSafe: true },
  120. { id: 'Fortune grove', isSafe: true },
  121. { id: 'Pillars of mana', isSafe: false, requires: { resource: 'Gold', parameter: 'speed', minValue: 100 } },
  122. ]
  123. .filter((building) => building.id)
  124. .map((building, index) => {
  125. return {
  126. ...building,
  127. order: index,
  128. }
  129. })
  130.  
  131. const lang = {
  132. pop_artisan: 'Artisan',
  133. pop_breeder: 'Breeder',
  134. pop_farmer: 'Farmer',
  135. pop_lumberjack: 'Lumberjack',
  136. pop_merchant: 'Merchant',
  137. pop_trader: 'Trader',
  138. pop_miner: 'Miner',
  139. pop_quarryman: 'Quarryman',
  140. pop_priest: 'Priest',
  141. pop_carpenter: 'Carpenter',
  142. pop_steelworker: 'Steelworker',
  143. pop_professor: 'Professor',
  144. pop_skymancer: 'Skymancer',
  145. pop_supplier: 'Supplier',
  146. pop_alchemist: 'Alchemist',
  147. pop_unemployed: 'Unemployed',
  148. pop_natro_refiner: 'Nat-Refiner',
  149. pop_researcher: 'Researcher',
  150. res_army: 'Army',
  151. res_coin: 'Coin',
  152. res_copper: 'Copper',
  153. res_cow: 'Cow',
  154. res_crystal: 'Crystal',
  155. res_faith: 'Faith',
  156. res_fame: 'Fame',
  157. res_food: 'Food',
  158. res_gold: 'Gold',
  159. res_horse: 'Horse',
  160. res_iron: 'Iron',
  161. res_legacy: 'Legacy',
  162. res_luck: 'Luck',
  163. res_mana: 'Mana',
  164. res_natronite: 'Natronite',
  165. res_population: 'Population',
  166. res_stone: 'Stone',
  167. res_relic: 'Relic',
  168. res_research: 'Research',
  169. res_tools: 'Tools',
  170. res_wood: 'Wood',
  171. res_building_material: 'Materials',
  172. res_steel: 'Steel',
  173. res_supplies: 'Supplies',
  174. res_saltpetre: 'Saltpetre',
  175. res_tome_wisdom: 'Tome of Wisdom',
  176. res_gem: 'Gem',
  177. }
  178.  
  179. const allJobs = [
  180. {
  181. id: 'unemployed',
  182. },
  183. {
  184. id: 'farmer',
  185. req: [
  186. {
  187. type: 'building',
  188. id: 'farm',
  189. value: 1,
  190. },
  191. ],
  192. gen: [
  193. {
  194. type: 'resource',
  195. id: 'food',
  196. value: 1.6,
  197. },
  198. ],
  199. },
  200. {
  201. id: 'lumberjack',
  202. req: [
  203. {
  204. type: 'building',
  205. id: 'lumberjack_camp',
  206. value: 1,
  207. },
  208. ],
  209. gen: [
  210. {
  211. type: 'resource',
  212. id: 'wood',
  213. value: 0.7,
  214. },
  215. ],
  216. },
  217. {
  218. id: 'quarryman',
  219. req: [
  220. {
  221. type: 'building',
  222. id: 'quarry',
  223. value: 1,
  224. },
  225. ],
  226. gen: [
  227. {
  228. type: 'resource',
  229. id: 'stone',
  230. value: 0.6,
  231. },
  232. ],
  233. },
  234. {
  235. id: 'miner',
  236. req: [
  237. {
  238. type: 'building',
  239. id: 'mine',
  240. value: 1,
  241. },
  242. ],
  243. gen: [
  244. {
  245. type: 'resource',
  246. id: 'copper',
  247. value: 0.5,
  248. },
  249. {
  250. type: 'resource',
  251. id: 'iron',
  252. value: 0.3,
  253. },
  254. ],
  255. },
  256. {
  257. id: 'artisan',
  258. req: [
  259. {
  260. type: 'building',
  261. id: 'artisan_workshop',
  262. value: 1,
  263. },
  264. ],
  265. gen: [
  266. {
  267. type: 'resource',
  268. id: 'gold',
  269. value: 0.5,
  270. },
  271. {
  272. type: 'resource',
  273. id: 'tools',
  274. value: 0.3,
  275. },
  276. ],
  277. },
  278. {
  279. id: 'merchant',
  280. req: [
  281. {
  282. type: 'building',
  283. id: 'marketplace',
  284. value: 1,
  285. },
  286. ],
  287. gen: [
  288. {
  289. type: 'resource',
  290. id: 'gold',
  291. value: 3,
  292. },
  293. ],
  294. },
  295. {
  296. id: 'trader',
  297. req: [
  298. {
  299. type: 'building',
  300. id: 'credit_union',
  301. value: 1,
  302. },
  303. ],
  304. gen: [
  305. {
  306. type: 'resource',
  307. id: 'gold',
  308. value: 6,
  309. },
  310. ],
  311. },
  312. {
  313. id: 'breeder',
  314. req: [
  315. {
  316. type: 'building',
  317. id: 'stable',
  318. value: 1,
  319. },
  320. ],
  321. gen: [
  322. {
  323. type: 'resource',
  324. id: 'cow',
  325. value: 0.2,
  326. },
  327. {
  328. type: 'resource',
  329. id: 'horse',
  330. value: 0.1,
  331. },
  332. ],
  333. },
  334. {
  335. id: 'carpenter',
  336. req: [
  337. {
  338. type: 'building',
  339. id: 'carpenter_workshop',
  340. value: 1,
  341. },
  342. ],
  343. gen: [
  344. {
  345. type: 'resource',
  346. id: 'building_material',
  347. value: 0.3,
  348. },
  349. {
  350. type: 'resource',
  351. id: 'wood',
  352. value: -3,
  353. },
  354. {
  355. type: 'resource',
  356. id: 'stone',
  357. value: -1.5,
  358. },
  359. {
  360. type: 'resource',
  361. id: 'tools',
  362. value: -0.5,
  363. },
  364. ],
  365. },
  366. {
  367. id: 'steelworker',
  368. req: [
  369. {
  370. type: 'building',
  371. id: 'steelworks',
  372. value: 1,
  373. },
  374. ],
  375. gen: [
  376. {
  377. type: 'resource',
  378. id: 'steel',
  379. value: 0.4,
  380. },
  381. {
  382. type: 'resource',
  383. id: 'copper',
  384. value: -1,
  385. },
  386. {
  387. type: 'resource',
  388. id: 'iron',
  389. value: -0.5,
  390. },
  391. ],
  392. },
  393. {
  394. id: 'professor',
  395. req: [
  396. {
  397. type: 'building',
  398. id: 'university',
  399. value: 1,
  400. },
  401. ],
  402. gen: [
  403. {
  404. type: 'resource',
  405. id: 'crystal',
  406. value: 0.06,
  407. },
  408. {
  409. type: 'resource',
  410. id: 'research',
  411. value: 1,
  412. },
  413. ],
  414. },
  415. {
  416. id: 'researcher',
  417. req: [
  418. {
  419. type: 'building',
  420. id: 'research_plant',
  421. value: 1,
  422. },
  423. ],
  424. gen: [
  425. {
  426. type: 'resource',
  427. id: 'research',
  428. value: 3,
  429. },
  430. ],
  431. },
  432. {
  433. id: 'supplier',
  434. req: [
  435. {
  436. type: 'building',
  437. id: 'grocery',
  438. value: 1,
  439. },
  440. ],
  441. gen: [
  442. {
  443. type: 'resource',
  444. id: 'supplies',
  445. value: 0.4,
  446. },
  447. {
  448. type: 'resource',
  449. id: 'food',
  450. value: -2,
  451. },
  452. {
  453. type: 'resource',
  454. id: 'cow',
  455. value: -0.2,
  456. },
  457. ],
  458. },
  459. {
  460. id: 'skymancer',
  461. req: [
  462. {
  463. type: 'building',
  464. id: 'observatory',
  465. value: 1,
  466. },
  467. ],
  468. gen: [
  469. {
  470. type: 'resource',
  471. id: 'faith',
  472. value: 3,
  473. },
  474. {
  475. type: 'resource',
  476. id: 'mana',
  477. value: 3,
  478. },
  479. ],
  480. },
  481. {
  482. id: 'alchemist',
  483. req: [
  484. {
  485. type: 'building',
  486. id: 'alchemic_laboratory',
  487. value: 1,
  488. },
  489. ],
  490. gen: [
  491. {
  492. type: 'resource',
  493. id: 'saltpetre',
  494. value: 0.7,
  495. },
  496. ],
  497. },
  498. {
  499. id: 'natro_refiner',
  500. req: [
  501. {
  502. type: 'building',
  503. id: 'natronite_refinery',
  504. value: 1,
  505. },
  506. ],
  507. gen: [
  508. {
  509. type: 'resource',
  510. id: 'natronite',
  511. value: 1,
  512. },
  513. {
  514. type: 'resource',
  515. id: 'mana',
  516. value: -5,
  517. },
  518. {
  519. type: 'resource',
  520. id: 'saltpetre',
  521. value: -0.5,
  522. },
  523. ],
  524. },
  525. ]
  526. .filter((job) => job.gen && job.gen.length)
  527. .map((job) => {
  528. return {
  529. id: lang[`pop_${job.id}`],
  530. gen: job.gen
  531. .filter((gen) => gen.type === 'resource')
  532. .map((gen) => {
  533. return {
  534. id: lang[`res_${gen.id}`],
  535. value: gen.value,
  536. }
  537. }),
  538. }
  539. })
  540. .map((job) => {
  541. return {
  542. id: job.id,
  543. isSafe: !job.gen.find((gen) => gen.value < 0),
  544. resourcesGenerated: job.gen
  545. .filter((gen) => gen.value > 0)
  546. .map((gen) => {
  547. return { id: gen.id, value: gen.value }
  548. }),
  549. resourcesUsed: job.gen
  550. .filter((gen) => gen.value < 0)
  551. .map((gen) => {
  552. return { id: gen.id, value: gen.value }
  553. }),
  554. }
  555. })
  556.  
  557. const resourcesToTrade = ['Cow', 'Horse', 'Food', 'Copper', 'Wood', 'Stone', 'Iron', 'Tools']
  558. const timeToWaitUntilFullGold = 60
  559. const minFarmers = 5
  560.  
  561. const sleep = (miliseconds) => new Promise((resolve) => setTimeout(resolve, miliseconds))
  562.  
  563. // https://stackoverflow.com/a/55366435
  564. class NumberParser {
  565. constructor(locale) {
  566. const format = new Intl.NumberFormat(locale)
  567. const parts = format.formatToParts(12345.6)
  568. const numerals = Array.from({ length: 10 }).map((_, i) => format.format(i))
  569. const index = new Map(numerals.map((d, i) => [d, i]))
  570. this._group = new RegExp(`[${parts.find((d) => d.type === 'group').value}]`, 'g')
  571. this._decimal = new RegExp(`[${parts.find((d) => d.type === 'decimal').value}]`)
  572. this._numeral = new RegExp(`[${numerals.join('')}]`, 'g')
  573. this._index = (d) => index.get(d)
  574. }
  575.  
  576. parse(string) {
  577. let multiplier = 1
  578. if (string.includes('K')) {
  579. multiplier = 1000
  580. } else if (string.includes('M')) {
  581. multiplier = 1000000
  582. }
  583.  
  584. return (string = string.replace('K', '').replace('M', '').trim().replace(this._group, '').replace(this._decimal, '.').replace(this._numeral, this._index))
  585. ? +string * multiplier
  586. : NaN
  587. }
  588. }
  589. const numberParser = new NumberParser()
  590.  
  591. const formatTime = (timeToFormat) => {
  592. const timeValues = {
  593. seconds: 0,
  594. minutes: 0,
  595. hours: 0,
  596. days: 0,
  597. }
  598.  
  599. let timeShort = ''
  600. let timeLong = ''
  601.  
  602. timeValues.seconds = timeToFormat % 60
  603. timeToFormat = (timeToFormat - (timeToFormat % 60)) / 60
  604. timeValues.minutes = timeToFormat % 60
  605. timeToFormat = (timeToFormat - (timeToFormat % 60)) / 60
  606. timeValues.hours = timeToFormat % 24
  607. timeToFormat = (timeToFormat - (timeToFormat % 24)) / 24
  608. timeValues.days = timeToFormat
  609.  
  610. if (timeValues.days) {
  611. timeShort += `${timeValues.days}d `
  612. timeLong += `${timeValues.days} days `
  613. }
  614. if (timeValues.hours) {
  615. timeShort += `${timeValues.hours}h `
  616. timeLong += `${timeValues.hours} hrs `
  617. }
  618. if (timeValues.minutes) {
  619. timeShort += `${timeValues.minutes}m `
  620. timeLong += `${timeValues.minutes} min `
  621. }
  622. if (timeValues.seconds) {
  623. timeShort += `${timeValues.seconds}s `
  624. timeLong += `${timeValues.seconds} sec `
  625. }
  626.  
  627. timeShort = timeShort.trim()
  628. timeLong = timeLong.trim()
  629.  
  630. return {
  631. timeShort,
  632. timeLong,
  633. timeValues,
  634. }
  635. }
  636.  
  637. const logger = ({ msgLevel, msg }) => {
  638. const logText = `[TMH][${new Date().toLocaleTimeString()}] ${msg}`
  639. const levelsToLog = ['log', 'warn', 'error']
  640.  
  641. if (levelsToLog.includes(msgLevel)) {
  642. const logHolder = document.querySelector('#root > div > div > div > div.w-full.order-2.flex-grow.overflow-x-hidden.overflow-y-auto.pr-4')
  643.  
  644. const tmhLogs = [...logHolder.querySelectorAll('.tmh-log')]
  645. if (tmhLogs.length > 10) {
  646. for (let i = 10; i < tmhLogs.length; i++) {
  647. tmhLogs[i].remove()
  648. }
  649. }
  650.  
  651. const p = document.createElement('p')
  652. p.classList.add('text-xs', 'mb-2', 'text-green-600', 'tmh-log')
  653. p.innerText = logText
  654. logHolder.insertAdjacentElement('afterbegin', p)
  655. }
  656.  
  657. console[msgLevel](logText)
  658. }
  659.  
  660. const getAllButtons = () => {
  661. return [...document.querySelectorAll('#maintabs-container > div > div[role=tabpanel] button.btn.btn-dark:not(.btn-off)')]
  662. }
  663.  
  664. const getResource = (resourceName = 'Gold') => {
  665. let resourceFound = false
  666. let resourceToFind = { name: resourceName, current: 0, max: 0, speed: 0, ttf: null, ttz: null }
  667. const resources = [...document.querySelectorAll('#root div > div > div > table > tbody > tr > td:nth-child(1) > span')]
  668. resources.map((resource) => {
  669. if (resource.textContent.includes(resourceName)) {
  670. resourceFound = true
  671. const values = resource.parentNode.parentNode.childNodes[1].textContent
  672. .split('/')
  673. .map((x) => numberParser.parse(x.replace(/[^0-9KM\-,\.]/g, '').trim()))
  674. resourceToFind.current = values[0]
  675. resourceToFind.max = values[1]
  676.  
  677. resourceToFind.speed = numberParser.parse(resource.parentNode.parentNode.childNodes[2].textContent.replace(/[^0-9KM\-,\.]/g, '').trim()) || 0
  678.  
  679. resourceToFind.ttf =
  680. resourceToFind.speed > 0 && resourceToFind.max !== resourceToFind.current
  681. ? formatTime(Math.ceil((resourceToFind.max - resourceToFind.current) / resourceToFind.speed))
  682. : null
  683. resourceToFind.ttz =
  684. resourceToFind.speed < 0 && resourceToFind.current ? formatTime(Math.ceil(resourceToFind.current / (resourceToFind.speed * -1))) : null
  685. }
  686. })
  687.  
  688. return resourceFound ? resourceToFind : null
  689. }
  690.  
  691. const hasUnassignedPopulation = () => {
  692. let unassignedPopulation = false
  693.  
  694. const navButtons = document.querySelectorAll('#main-tabs > div > button')
  695. navButtons.forEach((button) => {
  696. if (button.innerText.includes(KEYS.PAGES.POPULATION)) {
  697. unassignedPopulation = !!button.querySelector('span')
  698. }
  699. })
  700.  
  701. return unassignedPopulation
  702. }
  703.  
  704. const canAffordBA = () => {
  705. const faith = getResource('Faith')
  706. const mana = getResource('Mana')
  707.  
  708. if (faith && mana) {
  709. if (faith.current >= 2000 && mana.current >= 600) {
  710. return true
  711. }
  712. }
  713.  
  714. return false
  715. }
  716.  
  717. const shouldBuyBA = () => {
  718. const faith = getResource('Faith')
  719. const mana = getResource('Mana')
  720.  
  721. if (faith && mana) {
  722. if (
  723. faith.current + faith.speed * timeToWaitUntilFullGold >= faith.max &&
  724. mana.current + mana.speed * timeToWaitUntilFullGold >= mana.max &&
  725. mana.speed > 20
  726. ) {
  727. return true
  728. }
  729. }
  730.  
  731. return false
  732. }
  733.  
  734. const lastSell = {}
  735.  
  736. const shouldSell = () => {
  737. return !!resourcesToTrade.find((resName) => {
  738. if (!lastSell[resName]) lastSell[resName] = 0
  739.  
  740. const res = getResource(resName)
  741. if (
  742. res &&
  743. (res.current === res.max || res.current + res.speed * timeToWaitUntilFullGold >= res.max) &&
  744. lastSell[resName] + 90 * 1000 < new Date().getTime()
  745. )
  746. return true
  747. })
  748. }
  749.  
  750. const KEYS = {
  751. PAGES: {
  752. BUILD: 'Build',
  753. RESEARCH: 'Research',
  754. POPULATION: 'Population',
  755. ARMY: 'Army',
  756. MARKETPLACE: 'Marketplace',
  757. MAGIC: 'Magic',
  758. },
  759. }
  760.  
  761. const hasPage = (page) => {
  762. const navButtons = [...document.querySelectorAll('#main-tabs > div > button')]
  763.  
  764. return !!navButtons.find((button) => button.innerText.includes(page))
  765. }
  766.  
  767. const switchPage = async (page) => {
  768. let foundPage = hasPage(page)
  769. if (!foundPage) {
  770. await switchPage(KEYS.PAGES.BUILD)
  771. return
  772. }
  773.  
  774. let pageButton
  775. let switchedPage = false
  776.  
  777. const navButtons = document.querySelectorAll('#main-tabs > div > button')
  778. navButtons.forEach((button) => {
  779. if (button.innerText.includes(page) && button.getAttribute('aria-selected') !== 'true') {
  780. pageButton = button
  781. }
  782. })
  783.  
  784. if (pageButton) {
  785. pageButton.click()
  786. switchedPage = true
  787. }
  788.  
  789. await sleep(2000)
  790.  
  791. if (switchedPage) {
  792. logger({ msgLevel: 'debug', msg: `Switched page to ${page}` })
  793. }
  794. }
  795.  
  796. const pages = [
  797. {
  798. id: KEYS.PAGES.BUILD,
  799. action: async () => {
  800. await switchPage(KEYS.PAGES.BUILD)
  801.  
  802. let buttons = getAllButtons()
  803. .map((button) => {
  804. const id = button.innerText.split('\n').shift()
  805. return { id: id, element: button, building: buildingsList.find((building) => building.id === id) }
  806. })
  807. .filter((button) => button.building)
  808. .sort((a, b) => a.building.order - b.building.order)
  809.  
  810. if (buttons.length) {
  811. while (!scriptPaused && buttons.length) {
  812. let shouldBuild = true
  813. const button = buttons.shift()
  814.  
  815. if (!button.building.isSafe) {
  816. const requiredResource = getResource(button.building.requires.resource)
  817. if (!requiredResource) {
  818. shouldBuild = false
  819. } else {
  820. if (
  821. button.id === 'Common House' &&
  822. (!button.element.querySelector('span') || numberParser.parse(button.element.querySelector('span').innerText) < 2)
  823. ) {
  824. shouldBuild = true
  825. } else {
  826. shouldBuild = shouldBuild && requiredResource[button.building.requires.parameter] > button.building.requires.minValue
  827. }
  828. }
  829. }
  830.  
  831. if (shouldBuild) {
  832. button.element.click()
  833. logger({ msgLevel: 'log', msg: `Building ${button.building.id}` })
  834. await sleep(6000)
  835.  
  836. buttons = getAllButtons()
  837. .map((button) => {
  838. const id = button.innerText.split('\n').shift()
  839. return { id: id, element: button, building: buildingsList.find((building) => building.id === id) }
  840. })
  841. .filter((button) => button.building)
  842. .sort((a, b) => a.building.order - b.building.order)
  843. }
  844. }
  845. }
  846.  
  847. await sleep(5000)
  848. },
  849. },
  850. {
  851. id: KEYS.PAGES.RESEARCH,
  852. action: async () => {
  853. await switchPage(KEYS.PAGES.RESEARCH)
  854.  
  855. const manualResearches = [
  856. 'A moonlight night',
  857. 'Dragon assault',
  858. 'Mysterious robbery',
  859. 'The Fallen Angel reveal',
  860. 'Persuade the nobility',
  861. 'Persuade the people',
  862. ]
  863.  
  864. let buttonsList = getAllButtons().filter((button) => !manualResearches.includes(button.innerText.split('\n').shift()))
  865.  
  866. if (buttonsList.length) {
  867. while (!scriptPaused && buttonsList.length) {
  868. const button = buttonsList.shift()
  869.  
  870. button.click()
  871. logger({ msgLevel: 'log', msg: `Researching ${button.innerText.split('\n').shift()}` })
  872. await sleep(6000)
  873.  
  874. buttonsList = getAllButtons().filter((button) => !manualResearches.includes(button.innerText.split('\n').shift()))
  875. }
  876. }
  877.  
  878. await sleep(5000)
  879. },
  880. },
  881. {
  882. id: KEYS.PAGES.POPULATION,
  883. action: async () => {
  884. await switchPage(KEYS.PAGES.POPULATION)
  885.  
  886. let canAssignJobs = true
  887. const container = document.querySelector('#maintabs-container > div > div[role=tabpanel]')
  888. let availablePop = container
  889. .querySelector('div > span.ml-2')
  890. .textContent.split('/')
  891. .map((pop) => numberParser.parse(pop.trim()))
  892.  
  893. const availableJobsQSA = container.querySelectorAll('h5')
  894. const availableJobs = []
  895.  
  896. availableJobsQSA.forEach((job) => {
  897. const jobTitle = job.textContent.trim()
  898. availableJobs.push({
  899. ...allJobs.find((allJob) => allJob.id === jobTitle),
  900. container: job.parentElement.parentElement,
  901. current: +job.parentElement.parentElement.querySelector('input').value.split('/').shift().trim(),
  902. max: +job.parentElement.parentElement.querySelector('input').value.split('/').pop().trim(),
  903. })
  904. })
  905.  
  906. if (availablePop[0] > 0) {
  907. while (!scriptPaused && canAssignJobs) {
  908. const jobsWithSpace = availableJobs.filter((job) => !!job.container.querySelector('button.btn-green'))
  909. canAssignJobs = false
  910.  
  911. if (jobsWithSpace.length) {
  912. const foodJob = jobsWithSpace.find((job) => job.resourcesGenerated.find((res) => res.id === 'Food'))
  913.  
  914. if (foodJob && (getResource('Food').speed <= 0 || foodJob.current < Math.min(minFarmers, foodJob.max))) {
  915. const addJobButton = foodJob.container.querySelector('button.btn-green')
  916. if (addJobButton) {
  917. logger({ msgLevel: 'log', msg: `Assigning worker as ${foodJob.id}` })
  918.  
  919. addJobButton.click()
  920. canAssignJobs = true
  921. await sleep(1000)
  922. }
  923. } else {
  924. let unassigned = container
  925. .querySelector('div > span.ml-2')
  926. .textContent.split('/')
  927. .map((pop) => numberParser.parse(pop.trim()))
  928. .shift()
  929.  
  930. if (unassigned > 0) {
  931. const resources = [
  932. 'Natronite',
  933. 'Saltpetre',
  934. 'Tools',
  935. 'Wood',
  936. 'Stone',
  937. 'Iron',
  938. // 'Copper', // Same as Iron
  939. 'Mana',
  940. // 'Faith', // Same as Mana
  941. 'Research',
  942. 'Materials',
  943. 'Steel',
  944. 'Supplies',
  945. 'Gold',
  946. 'Crystal',
  947. 'Horse',
  948. // 'Cow', // Same as Horse
  949. ]
  950. .filter((res) => getResource(res))
  951. .filter((res) => jobsWithSpace.find((job) => job.resourcesGenerated.find((resGen) => resGen.id === res)))
  952.  
  953. const resourcesWithNegativeGen = resources.filter((res) => getResource(res) && res.speed < 0)
  954. const resourcesWithNoGen = resources.filter((res) => !resourcesWithNegativeGen.includes(res) && getResource(res) && !res.speed)
  955. const resourcesLeft = resources.filter((res) => !resourcesWithNegativeGen.includes(res) && !resourcesWithNoGen.includes(res))
  956.  
  957. const resourcesSorted = resourcesWithNegativeGen.concat(resourcesWithNoGen).concat(resourcesLeft)
  958.  
  959. if (resourcesSorted.length) {
  960. for (let i = 0; i < resourcesSorted.length && !scriptPaused; i++) {
  961. if (unassigned === 0) break
  962.  
  963. const resourceName = resourcesSorted[i]
  964.  
  965. const jobsForResource = jobsWithSpace
  966. .filter((job) => job.resourcesGenerated.find((resGen) => resGen.id === resourceName))
  967. .sort(
  968. (a, b) =>
  969. b.resourcesGenerated.find((resGen) => resGen.id === resourceName).value -
  970. a.resourcesGenerated.find((resGen) => resGen.id === resourceName).value
  971. )
  972.  
  973. if (jobsForResource.length) {
  974. for (let i = 0; i < jobsForResource.length && !scriptPaused; i++) {
  975. if (unassigned === 0) break
  976. const job = jobsForResource[i]
  977.  
  978. let isSafeToAdd = true
  979.  
  980. if (!job.isSafe) {
  981. job.resourcesUsed.forEach((resUsed) => {
  982. const res = getResource(resUsed.id)
  983.  
  984. if (!res || res.speed < Math.abs(resUsed.value * 2)) {
  985. isSafeToAdd = false
  986. }
  987. })
  988. }
  989.  
  990. if (isSafeToAdd) {
  991. const addJobButton = job.container.querySelector('button.btn-green')
  992. if (addJobButton) {
  993. logger({ msgLevel: 'log', msg: `Assigning worker as ${job.id}` })
  994.  
  995. addJobButton.click()
  996. unassigned -= 1
  997. canAssignJobs = !!unassigned
  998. await sleep(1000)
  999. }
  1000. }
  1001. }
  1002. }
  1003. }
  1004. }
  1005. }
  1006. }
  1007. }
  1008.  
  1009. const unassigned = container
  1010. .querySelector('div > span.ml-2')
  1011. .textContent.split('/')
  1012. .map((pop) => numberParser.parse(pop.trim()))
  1013. .shift()
  1014. if (unassigned === 0) {
  1015. canAssignJobs = false
  1016. }
  1017.  
  1018. await sleep(10)
  1019. }
  1020. }
  1021.  
  1022. await sleep(5000)
  1023. },
  1024. },
  1025. {
  1026. id: KEYS.PAGES.ARMY,
  1027. action: async () => {
  1028. await switchPage(KEYS.PAGES.ARMY)
  1029.  
  1030. if (canAffordBA() && shouldBuyBA()) {
  1031. const allButtonsQSA = document.querySelectorAll('div > div > div > div > div > span > button:not(.btn-off)')
  1032. let buyBAButton = null
  1033.  
  1034. allButtonsQSA.forEach((button) => {
  1035. if (button.innerText.includes('Battle Angel')) {
  1036. buyBAButton = button
  1037. }
  1038. })
  1039.  
  1040. if (buyBAButton) {
  1041. buyBAButton.click()
  1042. logger({ msgLevel: 'log', msg: `Buying Battle Angel(s)` })
  1043. await sleep(5000)
  1044. }
  1045. }
  1046.  
  1047. await sleep(5000)
  1048. },
  1049. },
  1050. {
  1051. id: KEYS.PAGES.MARKETPLACE,
  1052. action: async () => {
  1053. await switchPage(KEYS.PAGES.MARKETPLACE)
  1054.  
  1055. let gold = getResource('Gold')
  1056.  
  1057. if (gold && gold.current < gold.max && shouldSell()) {
  1058. const resourceHoldersQSA = document.querySelectorAll('div > div.tab-container > div > div > div')
  1059. const resourceHolders = []
  1060.  
  1061. if (resourceHoldersQSA) {
  1062. resourceHoldersQSA.forEach((resourceHolder) => {
  1063. const resNameElem = resourceHolder.querySelector('h5')
  1064. if (resNameElem) {
  1065. const resName = resNameElem.innerText
  1066. const res = getResource(resName)
  1067.  
  1068. if (resourcesToTrade.includes(resName) && res && (res.current === res.max || res.current + res.speed * timeToWaitUntilFullGold >= res.max)) {
  1069. resourceHolders.push(resourceHolder)
  1070. }
  1071. }
  1072. })
  1073. }
  1074.  
  1075. let goldEarned = 0
  1076. let soldTotals = {}
  1077.  
  1078. for (let i = 0; i < resourceHolders.length && !scriptPaused; i++) {
  1079. gold = getResource('Gold')
  1080. const resourceHolder = resourceHolders[i]
  1081. const resName = resourceHolder.querySelector('h5').innerText
  1082. let res = getResource(resName)
  1083.  
  1084. const initialPrice = numberParser.parse(resourceHolder.querySelector('div:nth-child(2) > div > table > tbody > tr > td:nth-child(2)').innerText)
  1085. let price = initialPrice
  1086. let sellButtons = resourceHolder.querySelectorAll('div > div.grid.gap-3 button.btn-red:not(.btn-dark)')
  1087.  
  1088. while (
  1089. !scriptPaused &&
  1090. sellButtons &&
  1091. sellButtons.length &&
  1092. gold.current < gold.max &&
  1093. res.current + res.speed * timeToWaitUntilFullGold * 2 >= res.max
  1094. ) {
  1095. let maxSellButton = 2
  1096. const missingResToSell = Math.ceil((gold.max - gold.current) / price)
  1097.  
  1098. if (missingResToSell < 80) {
  1099. maxSellButton = 0
  1100. } else if (missingResToSell < 800) {
  1101. maxSellButton = 1
  1102. }
  1103. maxSellButton = Math.min(maxSellButton, sellButtons.length - 1)
  1104. sellButtons[maxSellButton].click()
  1105. lastSell[resName] = new Date().getTime()
  1106. soldTotals[resName] = soldTotals[resName] ? soldTotals[resName] : { amount: 0, gold: 0 }
  1107. soldTotals[resName].amount += +sellButtons[maxSellButton].innerText
  1108. soldTotals[resName].gold += +sellButtons[maxSellButton].innerText * price
  1109. logger({ msgLevel: 'debug', msg: `Selling ${sellButtons[maxSellButton].innerText} of ${res.name} for ${price}` })
  1110. goldEarned += numberParser.parse(sellButtons[maxSellButton].innerText) * price
  1111. await sleep(10)
  1112. sellButtons = resourceHolder.querySelectorAll('div:nth-child(2) > div.grid.gap-3 button:not(.btn-dark)')
  1113. gold = getResource('Gold')
  1114. res = getResource(resName)
  1115. price = numberParser.parse(resourceHolder.querySelector('div:nth-child(2) > div > table > tbody > tr > td:nth-child(2)').innerText)
  1116. await sleep(1)
  1117.  
  1118. if (price / initialPrice < 0.1) {
  1119. break
  1120. }
  1121. }
  1122. }
  1123.  
  1124. if (goldEarned) {
  1125. const totals = Object.keys(soldTotals)
  1126. .filter((resName) => soldTotals[resName] && soldTotals[resName].gold && soldTotals[resName].amount)
  1127. .map(
  1128. (resName) =>
  1129. `${resName}: ${new Intl.NumberFormat().format(soldTotals[resName].amount)} units for ${new Intl.NumberFormat().format(
  1130. Math.round(soldTotals[resName].gold)
  1131. )} gold (avg price: ${(soldTotals[resName].gold / soldTotals[resName].amount).toFixed(2)})`
  1132. )
  1133.  
  1134. logger({ msgLevel: 'log', msg: `Earned ${new Intl.NumberFormat().format(goldEarned)} gold on Marketplace [${totals.join(', ')}]` })
  1135. }
  1136. }
  1137.  
  1138. await sleep(5000)
  1139. },
  1140. },
  1141. ]
  1142.  
  1143. window.switchScriptState = () => {
  1144. scriptPaused = !scriptPaused
  1145. window.localStorage.setItem('TMH_cheatsOff', JSON.stringify(false))
  1146. window.localStorage.setItem('TMH_scriptPaused', JSON.stringify(scriptPaused))
  1147.  
  1148. if (!scriptPaused) {
  1149. mainLoop()
  1150. }
  1151. }
  1152.  
  1153. const lastVisited = {
  1154. [KEYS.PAGES.BUILD]: 1,
  1155. [KEYS.PAGES.RESEARCH]: 0,
  1156. [KEYS.PAGES.POPULATION]: 0,
  1157. [KEYS.PAGES.ARMY]: 0,
  1158. [KEYS.PAGES.MARKETPLACE]: 0,
  1159. }
  1160.  
  1161. const mainLoop = async () => {
  1162. if (cheatsOff) return
  1163. if (mainLoopRunning) {
  1164. setTimeout(mainLoop, 1000)
  1165. return
  1166. }
  1167.  
  1168. mainLoopRunning = true
  1169.  
  1170. while (!scriptPaused) {
  1171. const should = {
  1172. [KEYS.PAGES.BUILD]: () => {
  1173. return hasPage(KEYS.PAGES.BUILD) && lastVisited[KEYS.PAGES.BUILD] < lastVisited[KEYS.PAGES.RESEARCH]
  1174. },
  1175. [KEYS.PAGES.RESEARCH]: () => {
  1176. return hasPage(KEYS.PAGES.RESEARCH) && lastVisited[KEYS.PAGES.RESEARCH] < lastVisited[KEYS.PAGES.BUILD]
  1177. },
  1178. [KEYS.PAGES.POPULATION]: () => {
  1179. return hasPage(KEYS.PAGES.POPULATION) && hasUnassignedPopulation()
  1180. },
  1181. [KEYS.PAGES.ARMY]: () => {
  1182. const timeout = lastVisited[KEYS.PAGES.ARMY] + 2 * 60 * 1000 < new Date().getTime()
  1183. return hasPage(KEYS.PAGES.ARMY) && canAffordBA() && shouldBuyBA() && timeout
  1184. },
  1185. [KEYS.PAGES.MARKETPLACE]: () => {
  1186. const gold = getResource('Gold')
  1187.  
  1188. return hasPage(KEYS.PAGES.MARKETPLACE) && gold.current + gold.speed * timeToWaitUntilFullGold < gold.max && shouldSell()
  1189. },
  1190. }
  1191.  
  1192. const pagesToCheck = [KEYS.PAGES.POPULATION, KEYS.PAGES.MARKETPLACE, KEYS.PAGES.ARMY, KEYS.PAGES.RESEARCH, KEYS.PAGES.BUILD]
  1193.  
  1194. while (!scriptPaused && pagesToCheck.length) {
  1195. const pageToCheck = pagesToCheck.shift()
  1196.  
  1197. if (should[pageToCheck] && should[pageToCheck]()) {
  1198. const page = pages.find((page) => page.id === pageToCheck)
  1199.  
  1200. if (page) {
  1201. logger({ msgLevel: 'debug', msg: `Executing ${page.id} action` })
  1202. lastVisited[page.id] = new Date().getTime()
  1203. await page.action()
  1204. await sleep(1000)
  1205. }
  1206. }
  1207. }
  1208.  
  1209. await sleep(1000)
  1210. }
  1211.  
  1212. mainLoopRunning = false
  1213. }
  1214.  
  1215. const managePanel = () => {
  1216. if (cheatsOff) return
  1217.  
  1218. const controlPanel = document.querySelector('div#theresMoreHelpControlPanel')
  1219.  
  1220. let scriptState = scriptPaused ? `▶️` : `⏸️`
  1221.  
  1222. if (!controlPanel) {
  1223. const controlPanelElement = document.createElement('div')
  1224. controlPanelElement.id = 'theresMoreHelpControlPanel'
  1225. controlPanelElement.classList.add('dark')
  1226. controlPanelElement.classList.add('dark:bg-mydark-300')
  1227. controlPanelElement.style.position = 'fixed'
  1228. controlPanelElement.style.bottom = '10px'
  1229. controlPanelElement.style.left = '10px'
  1230. controlPanelElement.style.zIndex = '99999999'
  1231. controlPanelElement.style.border = '1px black solid'
  1232. controlPanelElement.style.padding = '10px'
  1233.  
  1234. controlPanelElement.innerHTML = `
  1235. <p class="mb-2">TheresMoreHelp:
  1236. <button onClick="window.switchScriptState()" title="Start/stop script" class="scriptState">${scriptState}</button>
  1237. </p>
  1238. `
  1239.  
  1240. document.querySelector('div#root').insertAdjacentElement('afterend', controlPanelElement)
  1241. } else {
  1242. controlPanel.querySelector('.scriptState').textContent = scriptState
  1243. }
  1244.  
  1245. if (!scriptPaused) {
  1246. const fullPageOverlay = document.querySelector('div > div.absolute.top-0.right-0.z-20.pt-4.pr-4 > button')
  1247. if (fullPageOverlay) {
  1248. fullPageOverlay.click()
  1249. }
  1250. }
  1251. }
  1252.  
  1253. const calculateTTF = () => {
  1254. const resourceTrNodes = document.querySelectorAll('#root > div > div:not(#maintabs-container) > div > div > div > table:not(.hidden) > tbody > tr')
  1255. resourceTrNodes.forEach((row) => {
  1256. const cells = row.querySelectorAll('td')
  1257. const resourceName = cells[0].textContent.trim()
  1258. const resource = getResource(resourceName)
  1259. let ttf = ''
  1260.  
  1261. if (resource && resource.current < resource.max && resource.speed) {
  1262. ttf = resource.ttf ? resource.ttf.timeShort : resource.ttz ? resource.ttz.timeShort : ''
  1263. }
  1264.  
  1265. if (!cells[3]) {
  1266. const ttfElement = document.createElement('td')
  1267. ttfElement.className = 'px-3 3xl:px-5 py-3 lg:py-2 3xl:py-3 whitespace-nowrap w-1/3 text-right'
  1268. ttfElement.textContent = ttf
  1269. row.appendChild(ttfElement)
  1270. } else {
  1271. cells[3].textContent = ttf
  1272. }
  1273. })
  1274. }
  1275.  
  1276. const calculateTippyTTF = () => {
  1277. let potentialResourcesToFillTable = document.querySelectorAll('div.tippy-box > div.tippy-content > div > div > table')
  1278. if (potentialResourcesToFillTable.length) {
  1279. potentialResourcesToFillTable = potentialResourcesToFillTable[0]
  1280. const rows = potentialResourcesToFillTable.querySelectorAll('tr')
  1281. rows.forEach((row) => {
  1282. const cells = row.querySelectorAll('td')
  1283. const resourceName = cells[0].textContent.trim()
  1284.  
  1285. const resource = getResource(resourceName)
  1286. if (resource) {
  1287. let ttf = '✅'
  1288.  
  1289. const target = numberParser.parse(
  1290. cells[1].textContent
  1291. .split(' ')
  1292. .shift()
  1293. .replace(/[^0-9KM\-,\.]/g, '')
  1294. .trim()
  1295. )
  1296.  
  1297. if (target > resource.max || resource.speed <= 0) {
  1298. ttf = 'never'
  1299. } else if (target > resource.current) {
  1300. ttf = formatTime(Math.ceil((target - resource.current) / resource.speed)).timeShort
  1301. }
  1302.  
  1303. if (!cells[2]) {
  1304. const ttfElement = document.createElement('td')
  1305. ttfElement.className = 'px-4 3xl:py-1 text-right'
  1306. ttfElement.textContent = ttf
  1307. row.appendChild(ttfElement)
  1308. } else {
  1309. cells[2].textContent = ttf
  1310. }
  1311. }
  1312. })
  1313. }
  1314. }
  1315.  
  1316. const tossACoinToYourClicker = async () => {
  1317. if (cheatsOff) return
  1318. if (!haveManualResourceButtons) return
  1319. if (scriptPaused) return
  1320. if (isClicking) return
  1321.  
  1322. isClicking = true
  1323. const manualResources = ['Food', 'Wood', 'Stone'].filter((resourceName) => {
  1324. const resource = getResource(resourceName)
  1325.  
  1326. if (resource && resource.current < Math.min(200, resource.max) && (!resource.speed || resource.speed <= 0)) {
  1327. return true
  1328. }
  1329. })
  1330. const buttons = [
  1331. ...document.querySelectorAll('#root > div.flex.flex-wrap.w-full.mx-auto.p-2 > div.w-full.lg\\:pl-2 > div > div.order-2.flex.flex-wrap.gap-3 > button'),
  1332. ]
  1333.  
  1334. if (!buttons.length) {
  1335. haveManualResourceButtons = false
  1336. return
  1337. }
  1338.  
  1339. const buttonsToClick = buttons.filter((button) => manualResources.includes(button.innerText.trim()))
  1340.  
  1341. while (!scriptPaused && buttonsToClick.length) {
  1342. const buttonToClick = buttonsToClick.shift()
  1343. buttonToClick.click()
  1344. await sleep(250)
  1345. }
  1346.  
  1347. isClicking = false
  1348. }
  1349.  
  1350. const performRoutineTasks = async () => {
  1351. calculateTTF()
  1352.  
  1353. if (!cheatsOff) {
  1354. managePanel()
  1355. if (haveManualResourceButtons) tossACoinToYourClicker()
  1356. }
  1357. }
  1358.  
  1359. const performFastTasks = async () => {
  1360. calculateTippyTTF()
  1361. }
  1362.  
  1363. window.setInterval(performRoutineTasks, 1000)
  1364. window.setInterval(performFastTasks, 100)
  1365.  
  1366. const loadSettingsFromLocalStorage = () => {
  1367. const TMH_scriptPaused = window.localStorage.getItem('TMH_scriptPaused')
  1368. const TMH_cheatsOff = window.localStorage.getItem('TMH_cheatsOff')
  1369.  
  1370. if (TMH_cheatsOff) {
  1371. cheatsOff = JSON.parse(TMH_cheatsOff)
  1372. }
  1373.  
  1374. if (TMH_scriptPaused) {
  1375. scriptPaused = JSON.parse(TMH_scriptPaused)
  1376. }
  1377. }
  1378. loadSettingsFromLocalStorage()
  1379.  
  1380. if (!cheatsOff) {
  1381. await sleep(5000)
  1382.  
  1383. if (!scriptPaused) {
  1384. mainLoop()
  1385. }
  1386. } else {
  1387. logger({ msgLevel: 'log', msg: 'Welcome to TheresMoreHelp script!' })
  1388. logger({ msgLevel: 'log', msg: 'Please execute the following in the console if you want to enable cheats:' })
  1389. logger({ msgLevel: 'log', msg: ' ' })
  1390. logger({ msgLevel: 'log', msg: 'window.localStorage.setItem("TMH_cheatsOff", JSON.stringify(false))' })
  1391. logger({ msgLevel: 'log', msg: ' ' })
  1392. logger({ msgLevel: 'log', msg: 'and then refresh the page. Have fun!' })
  1393. }
  1394. })()
  1395.