diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ca4f2a7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,160 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ \ No newline at end of file diff --git a/.vs/MercFinder/FileContentIndex/37ff7298-31a5-43ea-9aed-17961f413ebe.vsidx b/.vs/MercFinder/FileContentIndex/37ff7298-31a5-43ea-9aed-17961f413ebe.vsidx new file mode 100644 index 0000000..2414aaf Binary files /dev/null and b/.vs/MercFinder/FileContentIndex/37ff7298-31a5-43ea-9aed-17961f413ebe.vsidx differ diff --git a/.vs/MercFinder/FileContentIndex/5f360155-3c71-4640-96b9-8b924cbeb35a.vsidx b/.vs/MercFinder/FileContentIndex/5f360155-3c71-4640-96b9-8b924cbeb35a.vsidx new file mode 100644 index 0000000..ffc26a3 Binary files /dev/null and b/.vs/MercFinder/FileContentIndex/5f360155-3c71-4640-96b9-8b924cbeb35a.vsidx differ diff --git a/.vs/MercFinder/FileContentIndex/73ce178c-7d46-4f32-98ff-891c4c705904.vsidx b/.vs/MercFinder/FileContentIndex/73ce178c-7d46-4f32-98ff-891c4c705904.vsidx new file mode 100644 index 0000000..bad9dc6 Binary files /dev/null and b/.vs/MercFinder/FileContentIndex/73ce178c-7d46-4f32-98ff-891c4c705904.vsidx differ diff --git a/.vs/MercFinder/FileContentIndex/ef221bf6-b696-4a97-9579-b844c917e6d3.vsidx b/.vs/MercFinder/FileContentIndex/ef221bf6-b696-4a97-9579-b844c917e6d3.vsidx new file mode 100644 index 0000000..0f09e69 Binary files /dev/null and b/.vs/MercFinder/FileContentIndex/ef221bf6-b696-4a97-9579-b844c917e6d3.vsidx differ diff --git a/.vs/MercFinder/v17/.wsuo b/.vs/MercFinder/v17/.wsuo new file mode 100644 index 0000000..273d6c1 Binary files /dev/null and b/.vs/MercFinder/v17/.wsuo differ diff --git a/.vs/MercFinder/v17/DocumentLayout.json b/.vs/MercFinder/v17/DocumentLayout.json new file mode 100644 index 0000000..fb8ce0f --- /dev/null +++ b/.vs/MercFinder/v17/DocumentLayout.json @@ -0,0 +1,152 @@ +{ + "Version": 1, + "WorkspaceRootPath": "C:\\Projetos\\TotalBattle\\MercFinder\\", + "Documents": [ + { + "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Projetos\\TotalBattle\\MercFinder\\Main.py||{3B902123-F8A7-4915-9F01-361F908088D0}", + "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:Main.py||{3B902123-F8A7-4915-9F01-361F908088D0}" + }, + { + "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Projetos\\TotalBattle\\MercFinder\\README.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}", + "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:README.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}" + } + ], + "DocumentGroupContainers": [ + { + "Orientation": 0, + "VerticalTabListWidth": 256, + "DocumentGroups": [ + { + "DockedWidth": 200, + "SelectedChildIndex": 19, + "Children": [ + { + "$type": "Bookmark", + "Name": "ST:0:0:{1c4feeaa-4718-4aa9-859d-94ce25d182ba}" + }, + { + "$type": "Bookmark", + "Name": "ST:129:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:130:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:128:0:{13b12e3e-c1b4-4539-9371-4fe9a0d523fc}" + }, + { + "$type": "Bookmark", + "Name": "ST:132:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:131:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:129:0:{13b12e3e-c1b4-4539-9371-4fe9a0d523fc}" + }, + { + "$type": "Bookmark", + "Name": "ST:139:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:138:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:136:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:135:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:134:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:130:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Bookmark", + "Name": "ST:131:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Bookmark", + "Name": "ST:137:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Bookmark", + "Name": "ST:133:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Bookmark", + "Name": "ST:129:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Bookmark", + "Name": "ST:128:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Document", + "DocumentIndex": 1, + "Title": "README.md", + "DocumentMoniker": "C:\\Projetos\\TotalBattle\\MercFinder\\README.md", + "RelativeDocumentMoniker": "README.md", + "ToolTip": "C:\\Projetos\\TotalBattle\\MercFinder\\README.md", + "RelativeToolTip": "README.md", + "ViewState": "AQIAAAAAAAAAAAAAAAAAAAMAAAAAAAAA", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001818|", + "WhenOpened": "2024-03-20T13:07:24.219Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 0, + "Title": "main.py", + "DocumentMoniker": "C:\\Projetos\\TotalBattle\\MercFinder\\main.py", + "RelativeDocumentMoniker": "main.py", + "ToolTip": "C:\\Projetos\\TotalBattle\\MercFinder\\main.py", + "RelativeToolTip": "main.py", + "ViewState": "AQIAAAAAAAAAAAAAAAAAAA4AAAAYAAAA", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.002457|", + "WhenOpened": "2024-03-20T13:07:10.002Z", + "EditorCaption": "" + } + ] + }, + { + "DockedWidth": 200, + "SelectedChildIndex": -1, + "Children": [ + { + "$type": "Bookmark", + "Name": "ST:0:0:{004be353-6879-467c-9d1e-9ac23cdf6d49}" + }, + { + "$type": "Bookmark", + "Name": "ST:0:0:{5a4e9529-b6a0-46b5-be4f-0f0b239bc0eb}" + }, + { + "$type": "Bookmark", + "Name": "ST:0:0:{a80febb4-e7e0-4147-b476-21aaf2453969}" + }, + { + "$type": "Bookmark", + "Name": "ST:0:0:{d78612c7-9962-4b83-95d9-268046dad23a}" + }, + { + "$type": "Bookmark", + "Name": "ST:0:0:{34e76e81-ee4a-11d0-ae2e-00a0c90fffc3}" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/.vs/ProjectSettings.json b/.vs/ProjectSettings.json new file mode 100644 index 0000000..866f1e1 --- /dev/null +++ b/.vs/ProjectSettings.json @@ -0,0 +1,3 @@ +{ + "CurrentProjectSetting": null +} \ No newline at end of file diff --git a/.vs/VSWorkspaceState.json b/.vs/VSWorkspaceState.json new file mode 100644 index 0000000..fec0992 --- /dev/null +++ b/.vs/VSWorkspaceState.json @@ -0,0 +1,8 @@ +{ + "ExpandedNodes": [ + "", + "\\Images" + ], + "SelectedNode": "\\Main.py", + "PreviewInSolutionExplorer": false +} \ No newline at end of file diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite new file mode 100644 index 0000000..e9b33d8 Binary files /dev/null and b/.vs/slnx.sqlite differ diff --git a/Image Assets b/Image Assets deleted file mode 100644 index e69de29..0000000 diff --git a/Images/arena-zoom25-4k.png b/Images/arena-zoom25-4k.png new file mode 100644 index 0000000..77fe76d Binary files /dev/null and b/Images/arena-zoom25-4k.png differ diff --git a/Images/default_image.png b/Images/default_image.png new file mode 100644 index 0000000..d77996f Binary files /dev/null and b/Images/default_image.png differ diff --git a/ExchangeBeep.zip b/Images/exchange.png similarity index 93% rename from ExchangeBeep.zip rename to Images/exchange.png index a364d92..7ed4675 100644 Binary files a/ExchangeBeep.zip and b/Images/exchange.png differ diff --git a/Images/house44.png b/Images/house44.png new file mode 100644 index 0000000..bb6a668 Binary files /dev/null and b/Images/house44.png differ diff --git a/Images/house45.png b/Images/house45.png new file mode 100644 index 0000000..fb78e31 Binary files /dev/null and b/Images/house45.png differ diff --git a/Images/jogador.png b/Images/jogador.png new file mode 100644 index 0000000..5bef864 Binary files /dev/null and b/Images/jogador.png differ diff --git a/Images/monster-rare.png b/Images/monster-rare.png new file mode 100644 index 0000000..48a3f44 Binary files /dev/null and b/Images/monster-rare.png differ diff --git a/Images/ogre.png b/Images/ogre.png new file mode 100644 index 0000000..e237bc8 Binary files /dev/null and b/Images/ogre.png differ diff --git a/Main.py b/Main.py index e4ac7c6..943fa73 100644 --- a/Main.py +++ b/Main.py @@ -1,19 +1,90 @@ +from time import sleep import winsound -import pyautogui as pag -import time -from python_imagesearch.imagesearch import imagesearch - -# frequency is set to 500Hz -freq = 500 +import easygui +import pyautogui +import json +import psutil +import win32ui, win32con +from python_imagesearch.imagesearch import imagesearch, imagesearch_click +from config import Config +from file_to_search import FileToSearch +from hotkeys import HotKeysService +from constants import * + +def make_sure_total_battle_is_running(): + found = tb_process_name in (p.name() for p in psutil.process_iter()) + + if (not found): + win32ui.MessageBox('Total Battle was not found. Please, start it before use MercFinder', title, win32con.MB_OK) + exit() + +class Main: + __config: Config + __fileToSearch: FileToSearch + __hotkeyService: HotKeysService + + def __init__(self, config: Config, fileToSearch: FileToSearch, hotKeyService: HotKeysService) -> None: + self.__config = config + self.__fileToSearch = fileToSearch + self.__hotkeyService = hotKeyService + + def make_sure_stop_when_found(self) -> None: + if (config['stop_when_found']): + casaEncontrada = win32ui.MessageBox("Você está vendo a casa de troca?", "Casa Encontrada", win32con.MB_SYSTEMMODAL + win32con.MB_YESNO) + + if (casaEncontrada == win32con.IDYES): + pyautogui.click(button='middle') + imagesearch_click(self.__fileToSearch.filename, action="left", delay=1.5, precision=config['precision']) + pyautogui.leftClick() + + sleep(5) + + sleep(1) + + def main_loop(self): + while self.__hotkeyService.running: + if (self.__hotkeyService.choosingFile == True): + sleep(1) + continue + + self.__hotkeyService.wait_to_continue() + + pos = imagesearch(self.__fileToSearch.filename, precision=self.__config['precision']) + + if pos[0] != -1: + winsound.Beep(self.__config['alert_frequency'], self.__config['alert_duration_ms']) + + self.make_sure_stop_when_found() + del pos + + sleep(self.__config['wait_time']) + +make_sure_total_battle_is_running() -# duration is set to 100 milliseconds -dur = 500 - +easygui.msgbox(title=title, msg="Este é um programa em desenvolvimento para busca de qualquer imagem em sua tela.\n\n"+ + "Para trocar a imagem padrão aperte a tecla HOME 2x e siga as instruções\n\n"+ + "Para pausar a execução da busca, aperte a tecla PAUSE 2x e para continuar, novamente PAUSE 2x\n\n"+ + "Para capturar uma nova região a procurar, aperte a tecla CTRL + M 2x e siga as instruções\n\n"+ + "Você pode utilizar as teclas W, A, S e D para fazer rolagem na tela\n\n"+ + "Para sair da aplicação, aperte a tecla DEL 2x") + +with open("config.json", "r") as file: + config: Config = json.load(file) + + filename = str.format("Images\{}", default_image_filename) + + fileToSearch = FileToSearch(filename) + hotkeysService = HotKeysService(fileToSearch, config) + + main = Main(config, fileToSearch, hotkeysService) -while True: - pos = imagesearch("Image assets\Exchange.PNG") - if pos[0] != -1: - winsound.Beep(freq, dur) - del pos - time.sleep(0.05) + hotkeysService.start() + + try: + main.main_loop() + except KeyboardInterrupt: + print('\n') + + with open("config.json", 'w', encoding='utf-8') as f: + json.dump(config, f, ensure_ascii=False, indent=4) diff --git a/MercFinderMoveWithCursor.exe b/MercFinderMoveWithCursor.exe new file mode 100644 index 0000000..be787c3 Binary files /dev/null and b/MercFinderMoveWithCursor.exe differ diff --git a/README.md b/README.md index bb8675d..62f37ed 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,35 @@ -A showcase of what this does can be found at : -A guide to install this can be found at : +Para devs: + +pip install python-imagesearch + +pip install pyautogui + +pip install pywin32 + +pip install easygui + +pip install opencv-python + +pip install global_hotkeys -U + + +Para jogadores: + +No arquivo config.json, é possível alterar as seguintes configurações: + +precision: percentual de igualdade da imagem. quanto maior for o valor (0 até 1.0), maior será a exigência +wait_time: tempo de espera para cada verificação. Padrão é de 0.001 milisegundos +alert_frequency: frequência do som emitido. Padrão 2500, +alert_duration_ms: duração do alerta em ms. Padrão 400, +stop_when_found: quando true, será mostrado um dialógo na tela quando a imagem for encontrada (esta opção só funciona corretamente se você estiver rodando o jogo com apenas 1 monitor), pedindo sua confirmação se a mesma foi encontrada. Caso diga que não, a busca irá continuar. + + + +Utilização: + +Trocar imagem para busca: HOME 2x +Pausar a execução da busca: PAUSE 2x +Capturar imagem: CTRL + M 2x +Sair da aplicação: DEL 2x + +Você pode navegar com as setas: W, A, S, D diff --git a/config.json b/config.json new file mode 100644 index 0000000..f5d8b48 --- /dev/null +++ b/config.json @@ -0,0 +1,8 @@ +{ + "precision": 0.67, + "wait_time": 0.001, + "alert_frequency": 2500, + "alert_duration_ms": 400, + "stop_when_found": false, + "show_video_tutorial": false +} \ No newline at end of file diff --git a/config.py b/config.py new file mode 100644 index 0000000..f150ee7 --- /dev/null +++ b/config.py @@ -0,0 +1,7 @@ +class Config: + precision: float + wait_time: float + alert_frequency: int + alert_duration_ms: int + stop_when_found: bool + show_video_tutorial: bool diff --git a/constants.py b/constants.py new file mode 100644 index 0000000..6158e60 --- /dev/null +++ b/constants.py @@ -0,0 +1,5 @@ +title = "Mercenary Exchange Finder - By Marco André" +move_with_cursor_process_name = "MercFinderMoveWithCursor.exe" +tb_process_name = "TotalBattle.exe" +temp_file_name = "image_to_find.png" +default_image_filename = "default_image.png" \ No newline at end of file diff --git a/file_to_search.py b/file_to_search.py new file mode 100644 index 0000000..564a359 --- /dev/null +++ b/file_to_search.py @@ -0,0 +1,5 @@ +class FileToSearch: + filename: str + + def __init__(self, filename: str) -> None: + self.filename = filename \ No newline at end of file diff --git a/hotkeys.py b/hotkeys.py new file mode 100644 index 0000000..4cfd5a3 --- /dev/null +++ b/hotkeys.py @@ -0,0 +1,116 @@ +import os, signal +import subprocess +import easygui +import numpy as np +import psutil +import cv2 +import pyautogui +from constants import * +from config import Config +from file_to_search import FileToSearch +from constants import title +from os import startfile +from time import sleep +from global_hotkeys import * + +class HotKeysService: + __fileToSearch: FileToSearch + __config: Config + __bindings = [] + choosingFile: bool + paused: bool + running: bool + + def __init__(self, fileToSearch: FileToSearch, config: Config) -> None: + self.choosingFile = False + self.running = True + self.paused = False + self.__fileToSearch = fileToSearch + self.__config = config + + self.__bindings = [ + ["home, home", None, self.change_file, True], + ["pause, pause", None, self.toggle_pause, True], + ["delete, delete", None, self.exit, True], + ["control + m, control + m", None, self.capture_image_from_selection, True], + ] + + def start(self): + register_hotkeys(self.__bindings) + start_checking_hotkeys() + self.start_move_with_cursor_process() + + def stop(self): + self.running = False + clear_hotkeys() + exit() + + def toggle_pause(self): + self.paused = not self.paused + if (self.paused): + self.kill_move_with_cursor_process() + return + self.start_move_with_cursor_process() + + def change_file(self): + self.choosingFile = True + + path = easygui.fileopenbox( + title="Escolha o arquivo de imagem que deseja procurar", + default=".png", + filetypes=["*.png", "*.jpg"]) + + if (not path is None): + self.__fileToSearch.filename = path + + self.choosingFile = False + + def wait_to_continue(self): + while self.paused: + sleep(1) + + def kill_move_with_cursor_process(self): + for p in psutil.process_iter(): + if (p.name() == move_with_cursor_process_name): + os.kill(p.pid, signal.SIGTERM) + + def start_move_with_cursor_process(self): + subprocess.Popen([move_with_cursor_process_name], stdout=subprocess.PIPE) + + def capture_image_from_selection(self): + self.choosingFile = True + self.paused = True + self.kill_move_with_cursor_process() + + easygui.msgbox(title=title, msg="Certifique-se que o arquivo de imagem seja capturado de tal forma que apenas o centro dela seja visível (excluir partes irrelevantes como grama, areia, etc)\n\n" + + "Quando finalizar a seleção da área que deseja capturar, tecle ENTER\n\n"+ + "Veja o arquivo image_capture_example.mp4 para mais detalhes.") + + if (self.__config['show_video_tutorial'] == True): + startfile("image_capture_example.mp4") + self.__config['show_video_tutorial'] = False + + image = pyautogui.screenshot() + image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR) + (x,y,w,h) = cv2.selectROI("Selecionar imagem", img=image, fromCenter=False, showCrosshair=False) + ROI = image[y:y+h, x:x+w] + + if easygui.ynbox("Deseja salvar esta imagem?", "Salvar imagem"): + filename = str.format("{}\Images\{}", os.getcwd(), temp_file_name) + filename = easygui.filesavebox("Entre com o nome do arquivo", "Salvar imagem", filename, [filename]) + filename = filename if filename != "" else temp_file_name + cv2.imwrite(filename, ROI) + self.__fileToSearch.filename = filename + + self.choosingFile = False + + self.paused = False + + cv2.destroyAllWindows() + + self.start_move_with_cursor_process() + pass + + def exit(self): + self.kill_move_with_cursor_process() + self.stop() diff --git a/image_capture_example.mp4 b/image_capture_example.mp4 new file mode 100644 index 0000000..ff83404 Binary files /dev/null and b/image_capture_example.mp4 differ diff --git a/image_to_find.png b/image_to_find.png new file mode 100644 index 0000000..979e41d Binary files /dev/null and b/image_to_find.png differ