TheresMoreHelp

Helper for TheresMoreGame

Version au 22/12/2022. Voir la dernière version.

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

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

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

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

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

  const buildingsList = [
    { name: 'City center', resources: [], alwaysBuild: true, isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    { name: 'Cathedral', resources: [], alwaysBuild: true, isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    { name: 'Great fair', resources: [], alwaysBuild: true, isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    { name: 'Palisade', resources: [], alwaysBuild: true, isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    { name: 'Wall', resources: [], alwaysBuild: true, isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    {
      name: 'City center part',
      resources: ['Gold', 'Wood', 'Stone', 'Copper', 'Iron', 'Tools'],
      alwaysBuild: true,
      isSafe: true,
      requires: { resource: '', parameter: '', minValue: 0 },
    },
    { name: 'Great fair unit', resources: ['Gold', 'Cow', 'Horse'], alwaysBuild: true, isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    {
      name: 'Cathedral part',
      resources: ['Gold', 'Wood', 'Stone', 'Materials', 'Supplies', 'Crystal'],
      alwaysBuild: true,
      isSafe: true,
      requires: { resource: '', parameter: '', minValue: 0 },
    },
    {
      name: 'Guild of craftsmen',
      resources: ['Gold', 'Wood', 'Stone', 'Tools'],
      alwaysBuild: true,
      isSafe: true,
      requires: { resource: '', parameter: '', minValue: 0 },
    },
    {
      name: 'Library of Theresmore',
      resources: ['Gold', 'Wood', 'Stone', 'Tools'],
      alwaysBuild: true,
      isSafe: true,
      requires: { resource: '', parameter: '', minValue: 0 },
    },
    {
      name: 'Watchman Outpost',
      resources: ['Wood', 'Tools', 'Supplies', 'Crystal'],
      alwaysBuild: true,
      isSafe: true,
      requires: { resource: '', parameter: '', minValue: 0 },
    },
    { name: 'Foundry', resources: ['Gold', 'Wood', 'Stone', 'Copper', 'Iron', 'Tools'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    { name: 'Bank', resources: ['Gold', 'Wood', 'Stone', 'Tools', 'Materials', 'Steel'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    { name: 'Marketplace', resources: ['Gold', 'Wood', 'Copper', 'Iron', 'Tools'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    { name: 'Artisan Workshop', resources: ['Gold', 'Wood', 'Stone'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    { name: 'Granary', resources: ['Gold', 'Wood', 'Tools'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    { name: 'Fiefdom', resources: ['Gold', 'Wood', 'Stone', 'Copper', 'Iron', 'Tools'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    { name: 'University', resources: ['Gold', 'Wood', 'Stone', 'Tools', 'Materials'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    { name: 'School', resources: ['Gold', 'Wood', 'Stone', 'Tools'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    { name: 'Lumberjack Camp', resources: ['Gold', 'Wood', 'Stone'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    { name: 'Quarry', resources: ['Gold', 'Wood', 'Stone'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    { name: 'Mine', resources: ['Gold', 'Wood', 'Stone'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    { name: 'Palisade part', resources: ['Wood', 'Tools'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    { name: 'Wall part', resources: ['Stone', 'Tools'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    { name: 'Farm', resources: ['Gold', 'Wood'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    { name: 'Temple', resources: ['Gold', 'Wood', 'Stone', 'Copper', 'Iron', 'Tools'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    { name: 'Altar of sacrifices', resources: ['Gold', 'Tools', 'Cow', 'Horse'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    { name: 'Stable', resources: ['Gold', 'Wood', 'Tools'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    { name: 'Valley of plenty', resources: ['Gold', 'Tools', 'Materials'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    { name: 'Magic Circle', resources: ['Stone', 'Copper', 'Faith'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    { name: 'Carpenter workshop', resources: ['Gold', 'Wood', 'Stone', 'Iron', 'Tools'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    { name: 'Grocery', resources: ['Gold', 'Tools', 'Materials'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    { name: 'Steelworks', resources: ['Gold', 'Tools', 'Materials'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    { name: 'Ballista', resources: ['Wood', 'Materials'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    {
      name: 'Recruit training center',
      resources: ['Gold', 'Iron', 'Tools', 'Materials'],
      isSafe: true,
      requires: { resource: '', parameter: '', minValue: 0 },
    },
    {
      name: 'Large storehouse',
      resources: ['Wood', 'Stone', 'Tools', 'Materials', 'Steel'],
      isSafe: true,
      requires: { resource: '', parameter: '', minValue: 0 },
    },
    { name: 'Store', resources: ['Wood', 'Stone', 'Tools'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    { name: 'Barracks', resources: ['Gold', 'Wood', 'Stone', 'Iron', 'Tools'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    { name: 'Fortune grove', resources: ['Faith', 'Mana'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
    {
      name: 'Mansion',
      resources: ['Gold', 'Wood', 'Stone', 'Tools', 'Materials'],
      isSafe: false,
      requires: { resource: 'Food', parameter: 'speed', minValue: 3 },
    },
    {
      name: 'City Hall',
      resources: ['Gold', 'Wood', 'Stone', 'Copper', 'Iron', 'Tools'],
      isSafe: false,
      requires: { resource: 'Food', parameter: 'speed', minValue: 1.5 },
    },
    { name: 'Common House', resources: ['Wood', 'Stone'], isSafe: false, requires: { resource: 'Food', parameter: 'speed', minValue: 1 } },
  ].filter((building) => building.name)

  const sleep = (ms) => new Promise((r) => setTimeout(r, ms))

  // 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 modes = {
    BUILD: 'Build',
    DEFAULT: 'Default',
  }
  let mode = modes.DEFAULT

  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,
    }
  }

  const findResource = (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
  }

  let shouldStopClicking = false

  window.stopClicking = () => (shouldStopClicking = true)

  window.generateGold = async () => {
    window.stopClicking()
    await sleep(10)
    shouldStopClicking = false

    let gold = findResource('Gold')

    if (gold && gold.current < gold.max) {
      const buttons = document.querySelectorAll('[id^="headlessui-tabs-tab-"]')
      let marketplace = false

      buttons.forEach((button) => {
        if (button.innerText.includes('Marketplace')) {
          marketplace = button
        }
      })

      if (marketplace) {
        marketplace.click()
        await sleep(100)

        const buyHorses = document.querySelector(
          'div > div > div.grid.gap-3.grid-cols-fill-240.min-w-full > div:nth-child(8) > div.grid.gap-2.grid-cols-2 > button:nth-child(4)'
        )

        const sellHorses = document.querySelector(
          'div > div > div.grid.gap-3.grid-cols-fill-240.min-w-full > div:nth-child(8) > div.grid.gap-2.grid-cols-2 > button:nth-child(3)'
        )

        while (gold && gold.current < gold.max && !shouldStopClicking) {
          buyHorses.click()
          await sleep(1)
          sellHorses.click()
          await sleep(1)
          gold = findResource('Gold')
        }
      }
    }

    shouldStopClicking = false
  }

  window.getManualResources = async () => {
    window.stopClicking()
    await sleep(10)
    shouldStopClicking = false

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

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

      if (resource && resource.current < resource.max) {
        resourcesToClick.push(resourceName)
      }
    })

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

    while (buttonsToClick.length && !shouldStopClicking) {
      const buttonToClick = buttonsToClick.shift()
      const resource = findResource(buttonToClick.innerText.trim())

      if (resource && resource.current < resource.max) {
        buttonToClick.click()
        await sleep(1)
        buttonsToClick.push(buttonToClick)
      }
    }

    shouldStopClicking = false
  }

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

    let isOnBuildPage = false
    const navButtons = document.querySelectorAll('#main-tabs > div > button')
    navButtons.forEach((button) => {
      if (button.innerText.includes('Build') && button.getAttribute('aria-selected') === 'true') {
        isOnBuildPage = true
      }
    })

    if (!isOnBuildPage) {
      setTimeout(spendResourcesBuilding, 2000)
      return
    }

    const resourceList = getAllResources().map((resource) => findResource(resource))
    const resourceNamesList = resourceList.map((resource) => resource.name)

    const buttonsList = document.querySelectorAll('button.btn:not(.btn-off)')
    const buildingNames = []

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

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

      const building = buildingsList[i]
      let shouldBuild = true

      building.resources.forEach((resource) => (shouldBuild = shouldBuild && resourceNamesList.includes(resource)))
      shouldBuild = shouldBuild || !!building.alwaysBuild

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

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

    setTimeout(spendResourcesBuilding, 15000)
  }

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

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

    if (!controlPanel) {
      const controlPanelElement = document.createElement('div')
      controlPanelElement.id = 'theresMoreHelpControlPanel'
      controlPanelElement.style.position = 'fixed'
      controlPanelElement.style.bottom = '20px'
      controlPanelElement.style.left = '20px'
      controlPanelElement.style.zIndex = '99999999'

      controlPanelElement.innerHTML = `
          <button onClick="window.stopClicking()" title="Stop auto-clicking">🛑</button>
          <button onClick="window.getManualResources()" title="Get Manual Resources"><svg viewBox="0 0 24 24" role="presentation" class="icon inline -mt-1 mr-2"><path d="M14.79,10.62L3.5,21.9L2.1,20.5L13.38,9.21L14.79,10.62M19.27,7.73L19.86,7.14L19.07,6.35L19.71,5.71L18.29,4.29L17.65,4.93L16.86,4.14L16.27,4.73C14.53,3.31 12.57,2.17 10.47,1.37L9.64,3.16C11.39,4.08 13,5.19 14.5,6.5L14,7L17,10L17.5,9.5C18.81,11 19.92,12.61 20.84,14.36L22.63,13.53C21.83,11.43 20.69,9.47 19.27,7.73Z" style="fill: currentcolor;"></path></svg></button>
          <button onClick="window.generateGold()" title="Get Gold From Market"><svg viewBox="0 0 24 24" role="presentation" class="icon inline -mt-1 mr-2"><path d="M1 22L2.5 17H9.5L11 22H1M13 22L14.5 17H21.5L23 22H13M6 15L7.5 10H14.5L16 15H6M23 6.05L19.14 7.14L18.05 11L16.96 7.14L13.1 6.05L16.96 4.96L18.05 1.1L19.14 4.96L23 6.05Z" style="fill: currentcolor;"></path></svg></button>
        `

      document.querySelector('div#root').insertAdjacentElement('afterend', controlPanelElement)
    }
  }

  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 = findResource(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 = findResource(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 performRoutineTasks = () => {
    createPanel()
    calculateTTF()
  }

  const performFastTasks = () => {
    calculateTippyTTF()
  }

  spendResourcesBuilding()

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