TheresMoreHelp

Helper for TheresMoreGame

当前为 2022-12-30 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        TheresMoreHelp
// @namespace   TheresMoreGame.com
// @match       https://www.theresmoregame.com/play/
// @grant       none
// @version     1.13
// @description Helper for TheresMoreGame
// @license     MIT
// @run-at      document-idle
// ==/UserScript==

;(async () => {
  let cheatsOff = true

  let scriptPaused = true
  let manualGold = false
  let haveManualResourceButtons = true
  let playingMarket = false

  const buildingsList = [
    { name: 'City of Lights', alwaysBuild: true, isSafe: true },
    { name: 'Harbor district', alwaysBuild: true, isSafe: true },
    { name: 'Stock exchange', alwaysBuild: true, isSafe: true },
    { name: 'Mana pit', alwaysBuild: true, isSafe: true },
    { name: 'Great bombard', alwaysBuild: true, isSafe: true },
    { name: 'Refugees district', alwaysBuild: true, isSafe: true },
    { name: 'Academy of Freethinkers', alwaysBuild: true, isSafe: true },
    { name: 'City center', alwaysBuild: true, isSafe: true },
    { name: 'Cathedral', alwaysBuild: true, isSafe: true },
    { name: 'Great fair', alwaysBuild: true, isSafe: true },
    { name: 'Palisade', alwaysBuild: true, isSafe: true },
    { name: 'Wall', alwaysBuild: true, isSafe: true },
    { name: 'City of Lights part', alwaysBuild: true, isSafe: true },
    { name: 'Harbor district part', alwaysBuild: true, isSafe: true },
    { name: 'Stock exchange part', alwaysBuild: true, isSafe: true },
    { name: 'Mana pit part', alwaysBuild: true, isSafe: true },
    { name: 'Great bombard part', alwaysBuild: true, isSafe: true },
    { name: 'Refugees district part', alwaysBuild: true, isSafe: true },
    { name: 'A. of Freethinkers Part', alwaysBuild: true, isSafe: true },
    { name: 'City center part', alwaysBuild: true, isSafe: true },
    { name: 'Great fair unit', alwaysBuild: true, isSafe: true },
    { name: 'Cathedral part', alwaysBuild: true, isSafe: true },
    { name: 'Guild of craftsmen', alwaysBuild: true, isSafe: true },
    { name: 'Machines of gods', alwaysBuild: true, isSafe: true },
    { name: 'Library of Theresmore', alwaysBuild: true, isSafe: true },
    { name: 'Monastery', alwaysBuild: true, isSafe: true },
    { name: 'Watchman Outpost', alwaysBuild: true, isSafe: true },
    { name: 'Hall of the dead', alwaysBuild: true, isSafe: true },
    { name: 'Sawmill', alwaysBuild: true, isSafe: true },
    { name: 'Monument', alwaysBuild: true, isSafe: true },
    { name: 'Foundry', alwaysBuild: true, isSafe: true },
    { name: 'Builder district', alwaysBuild: true, isSafe: true },
    { name: 'Gan Eden', alwaysBuild: true, isSafe: true },
    { name: 'Observatory', isSafe: true },
    { name: 'The Vaults', isSafe: true },
    { name: 'Credit union', isSafe: true },
    { name: 'Canava trading post', isSafe: true },
    { name: 'Bank', isSafe: true },
    { name: 'Marketplace', isSafe: true },
    { name: 'Artisan Workshop', isSafe: true },
    { name: 'Granary', isSafe: true },
    { name: 'School', isSafe: true },
    { name: 'University', isSafe: true },
    { name: 'Research plant', isSafe: true },
    { name: 'Undead Herds', isSafe: true },
    { name: 'Guarded warehouse', isSafe: true },
    { name: 'Fiefdom', isSafe: true },
    { name: 'Natronite refinery', isSafe: true },
    { name: 'Alchemical laboratory', isSafe: true },
    { name: 'Lumberjack Camp', isSafe: true },
    { name: 'Quarry', isSafe: true },
    { name: 'Mine', isSafe: true },
    { name: 'Palisade part', isSafe: true },
    { name: 'Wall part', isSafe: true },
    { name: 'Farm', isSafe: true },
    { name: 'Matter transmuter', isSafe: true },
    { name: 'Stable', isSafe: true },
    { name: 'Spiritual garden', isSafe: true },
    { name: 'Conclave', isSafe: true },
    { name: 'Magical tower', isSafe: true },
    { name: 'Temple', isSafe: true },
    { name: 'Altar of sacrifices', isSafe: true },
    { name: 'Fountain of Prosperity', isSafe: true },
    { name: 'Valley of plenty', isSafe: true },
    { name: 'Tax revenue checkpoints', isSafe: true },
    { name: 'Industrial plant', isSafe: true },
    { name: 'Magic Circle', isSafe: true },
    { name: 'Carpenter workshop', isSafe: true },
    { name: 'Grocery', isSafe: true },
    { name: 'Steelworks', isSafe: true },
    { name: 'Military academy', isSafe: true },
    { name: 'Ancient vault', isSafe: true },
    { name: 'Recruit training center', isSafe: true },
    { name: 'Officer training ground', isSafe: true },
    { name: 'Mansion', isSafe: false, requires: { resource: 'Food', parameter: 'speed', minValue: 3 } },
    { name: 'City Hall', isSafe: false, requires: { resource: 'Food', parameter: 'speed', minValue: 1.5 } },
    { name: 'Residential block', isSafe: false, requires: { resource: 'Food', parameter: 'speed', minValue: 5 } },
    { name: 'Common House', isSafe: false, requires: { resource: 'Food', parameter: 'speed', minValue: 1 } },
    { name: 'Storage facility', isSafe: true },
    { name: 'Ballista', isSafe: true },
    { name: 'Large warehouse', isSafe: true },
    { name: 'Large storehouse', isSafe: true },
    { name: 'Store', isSafe: true },
    { name: 'Natronite depot', isSafe: true },
    { name: 'Barracks', isSafe: true },
    { name: 'Minefield', isSafe: true },
    { name: 'Natronite balloon', isSafe: true },
    { name: 'Decryption of the portal', isSafe: true },
    { name: 'Fortune grove', isSafe: true },
    { name: 'Pillars of mana', isSafe: false, requires: { resource: 'Gold', parameter: 'speed', minValue: 100 } },
  ].filter((building) => building.name)

  const modes = {
    RESEARCH: 'Research',
    GAIN_GOLD: 'Gain Gold',
    BUILD: 'Build',
    DEFAULT: 'Default',
  }
  let mode = modes.DEFAULT
  let marketPlaceAvailable = false

  const pages = {
    BUILD: 'Build',
    RESEARCH: 'Research',
    POPULATION: 'Population',
    ARMY: 'Army',
    MARKETPLACE: 'Marketplace',
    MAGIC: 'Magic',
    UNKNOWN: 'Unknown',
  }

  const timeToWaitUntilFullGold = 60

  const sleep = (miliseconds) => new Promise((resolve) => setTimeout(resolve, miliseconds))

  // https://stackoverflow.com/a/55366435
  class NumberParser {
    constructor(locale) {
      const format = new Intl.NumberFormat(locale)
      const parts = format.formatToParts(12345.6)
      const numerals = Array.from({ length: 10 }).map((_, i) => format.format(i))
      const index = new Map(numerals.map((d, i) => [d, i]))
      this._group = new RegExp(`[${parts.find((d) => d.type === 'group').value}]`, 'g')
      this._decimal = new RegExp(`[${parts.find((d) => d.type === 'decimal').value}]`)
      this._numeral = new RegExp(`[${numerals.join('')}]`, 'g')
      this._index = (d) => index.get(d)
    }

    parse(string) {
      let multiplier = 1
      if (string.includes('K')) {
        multiplier = 1000
      } else if (string.includes('M')) {
        multiplier = 1000000
      }

      return (string = string.replace('K', '').replace('M', '').trim().replace(this._group, '').replace(this._decimal, '.').replace(this._numeral, this._index))
        ? +string * multiplier
        : NaN
    }
  }
  const numberParser = new NumberParser()

  const formatTime = (timeToFormat) => {
    const timeValues = {
      seconds: 0,
      minutes: 0,
      hours: 0,
      days: 0,
    }

    let timeShort = ''
    let timeLong = ''

    timeValues.seconds = timeToFormat % 60
    timeToFormat = (timeToFormat - (timeToFormat % 60)) / 60
    timeValues.minutes = timeToFormat % 60
    timeToFormat = (timeToFormat - (timeToFormat % 60)) / 60
    timeValues.hours = timeToFormat % 24
    timeToFormat = (timeToFormat - (timeToFormat % 24)) / 24
    timeValues.days = timeToFormat

    if (timeValues.days) {
      timeShort += `${timeValues.days}d `
      timeLong += `${timeValues.days} days `
    }
    if (timeValues.hours) {
      timeShort += `${timeValues.hours}h `
      timeLong += `${timeValues.hours} hrs `
    }
    if (timeValues.minutes) {
      timeShort += `${timeValues.minutes}m `
      timeLong += `${timeValues.minutes} min `
    }
    if (timeValues.seconds) {
      timeShort += `${timeValues.seconds}s `
      timeLong += `${timeValues.seconds} sec `
    }

    timeShort = timeShort.trim()
    timeLong = timeLong.trim()

    return {
      timeShort,
      timeLong,
      timeValues,
    }
  }

  window.switchScriptState = () => {
    scriptPaused = !scriptPaused
    window.localStorage.setItem('TMH_cheatsOff', JSON.stringify(false))
    window.localStorage.setItem('TMH_scriptPaused', JSON.stringify(scriptPaused))

    if (!scriptPaused) {
      mode = modes.DEFAULT
      switchPage(pages.BUILD)
    }
  }

  window.switchManualGold = () => {
    manualGold = !manualGold
    window.localStorage.setItem('TMH_cheatsOff', JSON.stringify(false))
    window.localStorage.setItem('TMH_manualGold', JSON.stringify(manualGold))
  }

  const getAllButtons = () => {
    let buttonsList = []
    document.querySelectorAll('#maintabs-container button.btn.btn-dark:not(.btn-off)').forEach((button) => buttonsList.push(button))

    return buttonsList
  }

  const getResource = (resourceName = 'Gold') => {
    let resourceFound = false
    let resourceToFind = { name: resourceName, current: 0, max: 0, speed: 0, ttf: null, ttz: null }
    const resources = document.querySelectorAll('#root div > div > div > table > tbody > tr > td:nth-child(1) > span')
    resources.forEach((resource) => {
      if (resource.textContent.includes(resourceName)) {
        resourceFound = true
        const values = resource.parentNode.parentNode.childNodes[1].textContent
          .split('/')
          .map((x) => numberParser.parse(x.replace(/[^0-9KM\-,\.]/g, '').trim()))
        resourceToFind.current = values[0]
        resourceToFind.max = values[1]

        resourceToFind.speed = numberParser.parse(resource.parentNode.parentNode.childNodes[2].textContent.replace(/[^0-9KM\-,\.]/g, '').trim())

        resourceToFind.ttf =
          resourceToFind.speed > 0 && resourceToFind.max !== resourceToFind.current
            ? formatTime(Math.ceil((resourceToFind.max - resourceToFind.current) / resourceToFind.speed))
            : null
        resourceToFind.ttz =
          resourceToFind.speed < 0 && resourceToFind.current ? formatTime(Math.ceil(resourceToFind.current / (resourceToFind.speed * -1))) : null
      }
    })

    return resourceFound ? resourceToFind : null
  }

  const getAllResources = () => {
    const resources = document.querySelectorAll('#root div > div > div > table > tbody > tr > td:nth-child(1) > span')
    const resourceNames = []

    resources.forEach((resource) => resourceNames.push(resource.textContent.trim()))

    return resourceNames
  }

  const getPage = () => {
    let page = pages.UNKNOWN

    const pageSelectedSelector = document.querySelector('#main-tabs > div > button[aria-selected="true"]')

    if (pageSelectedSelector) {
      const pageSelected = pageSelectedSelector.textContent

      if (pageSelected.includes('Build')) {
        page = pages.BUILD
      } else if (pageSelected.includes('Research')) {
        page = pages.RESEARCH
      } else if (pageSelected.includes('Population')) {
        page = pages.POPULATION
      } else if (pageSelected.includes('Army')) {
        page = pages.ARMY
      } else if (pageSelected.includes('Marketplace')) {
        page = pages.MARKETPLACE
      } else if (pageSelected.includes('Magic')) {
        page = pages.MAGIC
      }
    }

    return page
  }

  const switchPage = async (page) => {
    if (cheatsOff) return
    if (scriptPaused) return

    let foundPage = false
    let switchedPage = false

    const navButtons = document.querySelectorAll('#main-tabs > div > button')
    navButtons.forEach((button) => {
      if (button.innerText.includes(page)) {
        foundPage = true

        if (button.getAttribute('aria-selected') !== 'true') {
          button.click()
          switchedPage = true
        }
      }
    })

    if (switchedPage) {
      console.log(`$-> Switched page to ${page}`)
      await sleep(3500)
    }

    if (!foundPage) {
      mode = modes.DEFAULT
      switchPage(pages.BUILD)
      return
    }

    setTimeout(takePageAction, 1)
  }

  const takePageAction = async () => {
    if (cheatsOff) return
    if (scriptPaused) return

    let page = getPage()

    if (page !== pages.BUILD && mode === modes.BUILD) {
      switchPage(pages.BUILD)
      return
    } else if (page !== pages.RESEARCH && mode === modes.RESEARCH) {
      switchPage(pages.RESEARCH)
      return
    } else if (page !== pages.MARKETPLACE && mode === modes.GAIN_GOLD) {
      switchPage(pages.MARKETPLACE)
      return
    }

    if (page === pages.BUILD) {
      setTimeout(spendResourcesBuilding, 1)
    } else if (page === pages.RESEARCH) {
      setTimeout(performReserach, 1)
    } else if (page === pages.MARKETPLACE) {
      setTimeout(playMarket, 1)
    } else {
      setTimeout(takePageAction, 1000)
    }
  }

  const spendResourcesBuilding = async () => {
    if (cheatsOff) return
    if (scriptPaused) return

    if (getPage() !== pages.BUILD) {
      setTimeout(takePageAction, 1000)
      return
    }

    const buttonsList = getAllButtons()
    const buildingNames = []

    buttonsList.forEach((button) => buildingNames.push(button.innerText.split('\n').shift()))

    let hasBuilt = false
    let shouldBuild = false

    for (let i = 0; i < buildingsList.length && !hasBuilt; i++) {
      if (hasBuilt) break

      const building = buildingsList[i]

      shouldBuild = buildingNames.includes(building.name)

      if (!building.isSafe) {
        const requiredResource = getResource(building.requires.resource)
        if (!requiredResource) {
          shouldBuild = false
        } else {
          shouldBuild = shouldBuild && requiredResource[building.requires.parameter] > building.requires.minValue
        }
      }

      if (shouldBuild) {
        if (building.alwaysBuild || mode === modes.BUILD) {
          buttonsList.find((button) => {
            if (button.innerText.split('\n').shift() === building.name) {
              button.click()
              hasBuilt = true
              mode = modes.DEFAULT
              console.log(`#-> Started building ${building.name}`)
              return true
            }
          })
        } else {
          mode = modes.BUILD
        }
      }
    }

    await sleep(5000)

    if (mode === modes.BUILD || shouldBuild || hasBuilt) {
      spendResourcesBuilding()
    } else {
      const gold = getResource('Gold')

      const resourcesToTrade = ['Cow', 'Horse']

      let shouldSell = resourcesToTrade.find((resName) => {
        const res = getResource(resName)
        if (res && res.current === res.max) return true
      })

      if (marketPlaceAvailable && gold.current + gold.speed * timeToWaitUntilFullGold < gold.max && shouldSell) {
        mode = modes.GAIN_GOLD
        switchPage(pages.MARKETPLACE)
      } else {
        mode = modes.RESEARCH
        switchPage(pages.RESEARCH)
      }
    }
  }

  const performReserach = async () => {
    if (cheatsOff) return
    if (scriptPaused) return

    if (getPage() !== pages.RESEARCH) {
      setTimeout(takePageAction, 1000)
      return
    }

    const manualResearches = ['A moonlight night', 'Dragon assault', 'Mysterious robbery', 'The Fallen Angel reveal']

    let didResearch = false

    const buttonsList = getAllButtons()
    const researchesList = []

    buttonsList.forEach(
      (button) => !manualResearches.includes(button.innerText.split('\n').shift()) && researchesList.push(button.innerText.split('\n').shift())
    )

    if (researchesList.length) {
      while (!didResearch && researchesList.length) {
        const research = researchesList.shift()

        buttonsList.forEach((button) => {
          if (button.innerText.split('\n').shift() === research) {
            button.click()
            didResearch = true
            console.log(`%-> Researching ${research}`)
          }
        })
      }
    }

    if (didResearch) {
      await sleep(5000)
    } else {
      await sleep(2000)
    }

    mode = modes.DEFAULT
    await switchPage(pages.BUILD)
  }

  const playMarket = async () => {
    if (cheatsOff) return
    if (playingMarket) return

    if (getPage() !== pages.MARKETPLACE) {
      setTimeout(takePageAction, 1000)
      return
    }

    let gold = getResource('Gold')

    const resourcesToTrade = ['Cow', 'Horse']

    let shouldSell = resourcesToTrade.find((resName) => {
      const res = getResource(resName)
      if (res && res.current === res.max) return true
    })

    if (gold && gold.current < gold.max && shouldSell) {
      playingMarket = true

      const resourceHoldersQSA = document.querySelectorAll('div > div.tab-container > div > div > div')
      const resourceHolders = []

      if (resourceHoldersQSA) {
        resourceHoldersQSA.forEach((resourceHolder) => {
          const resName = resourceHolder.querySelector('h5').innerText
          const res = getResource(resName)

          if (resourcesToTrade.includes(resName) && res && res.current === res.max) {
            resourceHolders.push(resourceHolder)
          }
        })
      }

      for (let i = 0; i < resourceHolders.length; i++) {
        gold = getResource('Gold')
        const resourceHolder = resourceHolders[i]

        const initialPrice = numberParser.parse(resourceHolder.querySelector('div:nth-child(2) > div > table > tbody > tr > td:nth-child(2)').innerText)
        let sellButtons = resourceHolder.querySelectorAll('div:nth-child(2) > div.grid.gap-3 button:not(.btn-dark)')
        while (sellButtons && sellButtons.length && gold.current < gold.max) {
          let maxSellButton = 2
          const price = numberParser.parse(resourceHolder.querySelector('div:nth-child(2) > div > table > tbody > tr > td:nth-child(2)').innerText)
          const missingResToSell = Math.ceil((gold.max - gold.current) / price)

          if (missingResToSell < 80) {
            maxSellButton = 0
          } else if (missingResToSell < 800) {
            maxSellButton = 1
          }
          maxSellButton = Math.min(maxSellButton, sellButtons.length - 1)
          sellButtons[maxSellButton].click()
          await sleep(10)
          sellButtons = resourceHolder.querySelectorAll('div:nth-child(2) > div.grid.gap-3 button:not(.btn-dark)')
          gold = getResource('Gold')
          await sleep(1)

          if (price / initialPrice < 0.25) {
            break
          }
        }
      }

      playingMarket = false
    }

    mode = modes.RESEARCH
    await switchPage(pages.RESEARCH)
  }

  const managePanel = async () => {
    if (cheatsOff) return

    const controlPanel = document.querySelector('div#theresMoreHelpControlPanel')

    let scriptState = scriptPaused ? `▶️` : `⏸️`
    let manualGoldState = manualGold ? '🤑' : `😅`

    if (!controlPanel) {
      const controlPanelElement = document.createElement('div')
      controlPanelElement.id = 'theresMoreHelpControlPanel'
      controlPanelElement.classList.add('dark')
      controlPanelElement.classList.add('dark:bg-mydark-300')
      controlPanelElement.style.position = 'fixed'
      controlPanelElement.style.bottom = '10px'
      controlPanelElement.style.left = '10px'
      controlPanelElement.style.zIndex = '99999999'
      controlPanelElement.style.border = '1px black solid'
      controlPanelElement.style.padding = '10px'

      controlPanelElement.innerHTML = `
          <p class="mb-2">TheresMoreHelp:
            <button onClick="window.switchScriptState()" title="Start/stop script" class="scriptState">${scriptState}</button> 
            <button onClick="window.switchManualGold()" title="Always play market" class="manualGold">${manualGoldState}</button>
          </p>
          <p class="currentPage text-xs mb-2 text-orange-500">Page: ${getPage()}</p>
          <p class="currentAction text-xs text-orange-500">Mode: ${mode}</p>
        `

      document.querySelector('div#root').insertAdjacentElement('afterend', controlPanelElement)
    } else {
      controlPanel.querySelector('.currentPage').textContent = `Page: ${getPage()}`
      controlPanel.querySelector('.currentAction').textContent = `Mode: ${mode}`
      controlPanel.querySelector('.scriptState').textContent = scriptState
      controlPanel.querySelector('.manualGold').textContent = manualGoldState
    }
  }

  const calculateTTF = () => {
    const resourceTrNodes = document.querySelectorAll('#root > div > div:not(#maintabs-container) > div > div > div > table:not(.hidden) > tbody > tr')
    resourceTrNodes.forEach((row) => {
      const cells = row.querySelectorAll('td')
      const resourceName = cells[0].textContent.trim()
      const resource = getResource(resourceName)
      let ttf = ''

      if (resource && resource.current < resource.max && resource.speed) {
        ttf = resource.ttf ? resource.ttf.timeLong : resource.ttz.timeLong
      }

      if (!cells[3]) {
        const ttfElement = document.createElement('td')
        ttfElement.className = 'px-3 3xl:px-5 py-3 lg:py-2 3xl:py-3 whitespace-nowrap w-1/3 text-right'
        ttfElement.textContent = ttf
        row.appendChild(ttfElement)
      } else {
        cells[3].textContent = ttf
      }
    })
  }

  const calculateTippyTTF = () => {
    let potentialResourcesToFillTable = document.querySelectorAll('div.tippy-box > div.tippy-content > div > div > table')
    if (potentialResourcesToFillTable.length) {
      potentialResourcesToFillTable = potentialResourcesToFillTable[0]
      const rows = potentialResourcesToFillTable.querySelectorAll('tr')
      rows.forEach((row) => {
        const cells = row.querySelectorAll('td')
        const resourceName = cells[0].textContent.trim()

        const resource = getResource(resourceName)
        if (resource) {
          let ttf = '✅'

          const target = numberParser.parse(
            cells[1].textContent
              .split(' ')
              .shift()
              .replace(/[^0-9KM\-,\.]/g, '')
              .trim()
          )

          if (target > resource.max || resource.speed <= 0) {
            ttf = 'never'
          } else if (target > resource.current) {
            ttf = formatTime(Math.ceil((target - resource.current) / resource.speed)).timeShort
          }

          if (!cells[2]) {
            const ttfElement = document.createElement('td')
            ttfElement.className = 'px-4 3xl:py-1 text-right'
            ttfElement.textContent = ttf
            row.appendChild(ttfElement)
          } else {
            cells[2].textContent = ttf
          }
        }
      })
    }
  }

  const checkMarketplaceAvailable = async () => {
    let hasResourcesToTrade = false
    let hasMarketplaceEnabled = false

    const resourcesToTrade = ['Cow', 'Horse']

    resourcesToTrade.forEach((resName) => {
      const res = getResource(resName)

      if (res) {
        hasResourcesToTrade = true
      }
    })

    const navButtons = document.querySelectorAll('#main-tabs > div > button')
    navButtons.forEach((button) => {
      if (button.innerText.includes('Marketplace') && button.getAttribute('aria-selected') !== 'true') {
        hasMarketplaceEnabled = true
      }
    })

    marketPlaceAvailable = hasResourcesToTrade && hasMarketplaceEnabled
  }

  const tossACoinToYourClicker = () => {
    if (cheatsOff) return
    if (!haveManualResourceButtons) return
    if (scriptPaused) return

    const manualResources = ['Food', 'Wood', 'Stone']
    const resourcesToClick = []
    const buttonsToClick = []
    const buttons = 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'
    )

    if (!buttons) {
      haveManualResourceButtons = false
      return
    }

    manualResources.forEach((resourceName) => {
      const resource = getResource(resourceName)

      if (resource && resource.current < resource.max && (!resource.speed || resource.speed <= 0)) {
        resourcesToClick.push(resourceName)
      }
    })

    buttons.forEach((button) => {
      if (resourcesToClick.includes(button.innerText.trim())) {
        buttonsToClick.push(button)
      }
    })

    while (buttonsToClick.length) {
      const buttonToClick = buttonsToClick.shift()
      buttonToClick.click()
    }
  }

  const checkToPlayMarket = () => {
    if (cheatsOff) return

    const page = getPage()

    if (scriptPaused && manualGold && marketPlaceAvailable && !playingMarket && page === pages.MARKETPLACE) {
      playMarket()
    }

    if (page !== pages.MARKETPLACE) {
      playingMarket = false
    }
  }

  const performRoutineTasks = () => {
    calculateTTF()

    if (!cheatsOff) {
      checkMarketplaceAvailable()
      managePanel()
      checkToPlayMarket()
    }
  }

  const performFastTasks = () => {
    calculateTippyTTF()

    if (!cheatsOff) {
      if (haveManualResourceButtons) tossACoinToYourClicker()
    }
  }

  window.setInterval(performRoutineTasks, 1000)
  window.setInterval(performFastTasks, 100)

  const loadSettingsFromLocalStorage = () => {
    const TMH_scriptPaused = window.localStorage.getItem('TMH_scriptPaused')
    const TMH_manualGold = window.localStorage.getItem('TMH_manualGold')
    const TMH_cheatsOff = window.localStorage.getItem('TMH_cheatsOff')

    if (TMH_cheatsOff) {
      cheatsOff = JSON.parse(TMH_cheatsOff)
    }

    if (TMH_scriptPaused) {
      scriptPaused = JSON.parse(TMH_scriptPaused)
    }

    if (TMH_manualGold) {
      manualGold = JSON.parse(TMH_manualGold)
    }
  }
  loadSettingsFromLocalStorage()

  if (!cheatsOff) {
    takePageAction()
  }
})()