TheresMoreHelp

Helper for TheresMoreGame

目前为 2022-12-31 提交的版本。查看 最新版本

// ==UserScript==
// @name        TheresMoreHelp
// @namespace   TheresMoreGame.com
// @match       https://www.theresmoregame.com/play/
// @grant       none
// @version     1.21
// @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',
    BUY_BATTLE_ANGEL: 'Buy BA',
    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 resourcesToTrade = ['Cow', 'Horse', 'Food', 'Copper', 'Wood', 'Stone', 'Iron', 'Tools']
  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) {
      setTimeout(() => {
        window.location.reload()
      }, 60 * 60 * 1000)

      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()) || 0

        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 hasPage = (page) => {
    let foundPage = false

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

    return foundPage
  }

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

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

    let switchedPage = false

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

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

    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
    } else if (page !== pages.ARMY && mode === modes.BUY_BATTLE_ANGEL) {
      switchPage(pages.ARMY)
      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 if (page === pages.ARMY) {
      setTimeout(buyArmy, 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')

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

      if (marketPlaceAvailable && gold.current + gold.speed * timeToWaitUntilFullGold < gold.max && shouldSell) {
        mode = modes.GAIN_GOLD
        switchPage(pages.MARKETPLACE)
      } else {
        if (hasPage(pages.ARMY) && canAffordBA() && shouldBuyBA()) {
          mode = modes.BUY_BATTLE_ANGEL
          switchPage(pages.ARMY)
        } 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')

    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 || res.current + res.speed * timeToWaitUntilFullGold >= res.max)) {
            resourceHolders.push(resourceHolder)
          }
        })
      }

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

        const initialPrice = numberParser.parse(resourceHolder.querySelector('div:nth-child(2) > div > table > tbody > tr > td:nth-child(2)').innerText)
        let price = initialPrice
        let sellButtons = resourceHolder.querySelectorAll('div:nth-child(2) > div.grid.gap-3 button:not(.btn-dark)')

        while (sellButtons && sellButtons.length && gold.current < gold.max && res.current + res.speed * timeToWaitUntilFullGold * 2 >= res.max) {
          let maxSellButton = 2
          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')
          res = getResource(resName)
          price = numberParser.parse(resourceHolder.querySelector('div:nth-child(2) > div > table > tbody > tr > td:nth-child(2)').innerText)
          await sleep(1)

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

      playingMarket = false
    }

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

  const canAffordBA = () => {
    const faith = getResource('Faith')
    const mana = getResource('Mana')

    if (faith && mana) {
      if (faith.current >= 2000 && mana.current >= 600) {
        return true
      }
    }

    return false
  }

  const shouldBuyBA = () => {
    const faith = getResource('Faith')
    const mana = getResource('Mana')

    if (faith && mana) {
      if (
        faith.current + faith.speed * timeToWaitUntilFullGold >= faith.max &&
        mana.current + mana.speed * timeToWaitUntilFullGold >= mana.max &&
        mana.speed >= 10
      ) {
        return true
      }
    }

    return false
  }

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

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

    if (canAffordBA() && shouldBuyBA()) {
      const allButtonsQSA = document.querySelectorAll('div > div > div > div > div > span > button:not(.btn-off)')
      let buyBAButton = null

      allButtonsQSA.forEach((button) => {
        if (button.innerText.includes('Battle Angel')) {
          buyBAButton = button
        }
      })

      if (buyBAButton) {
        buyBAButton.click()
        await sleep(5000)
      }
    }

    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

    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()

    if (!scriptPaused) {
      setTimeout(() => {
        window.location.reload()
      }, 60 * 60 * 1000)
    }
  }
})()

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址