Advertisement
RexyBadDog

minecraft_fishing_bot.py

May 6th, 2023 (edited)
804
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 14.73 KB | Gaming | 0 0
  1. # TODO: check Minecraft settings. [Esc -> Options... -> Controls... -> Mouse Settings... -> Raw input: OFF]
  2. # TODO: make get_window_handles(winName) function more spesific (other windows with "Minecraft" in their name can trigger this function)
  3. # TODO: try-catch for rtesseract.exe if not found
  4. # TODO: add a way to stop the bot instead of CTRL + C
  5. # TODO: possible code duplication with x, y, h, w variables and window_dimensions[] in 'def capture_window(window_handle):' function
  6. #       maybe make a function that returns the x, y, h, w variables, but will lose the real time windows resizing functionality
  7. # Done: generate updated requirements.txt file
  8. # Last Run: 02-03-2025 python 3.12
  9.  
  10. import os
  11. import re
  12. import sys
  13. import cv2
  14. import time
  15. import datetime
  16. import pytesseract
  17. import numpy
  18. import win32con
  19. import win32gui
  20. import win32ui
  21. from pynput.mouse import Button, Controller as MouseController
  22. from pynput.keyboard import Key, KeyCode, Controller as KeyboardController
  23. from PIL import Image
  24. from shutil import copyfile
  25.  
  26. phrase = "bobber splashes"
  27. pytesseract.pytesseract.tesseract_cmd = "C:\\Program Files\\Tesseract-OCR\\tesseract.exe"
  28. regex_pattern = r"\[System\] \[CHAT\].*?(?<![\w-])[a-z]*?(rex[y]?|-> me\])"
  29. log_file_path = os.path.join(os.getenv('APPDATA'), ".minecraft", "logs", "latest.log")
  30. log_file_copy_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "latest_copy.log")
  31. previous_timestamp = None
  32. flag_fillExpPotions = True
  33. flag_Eat = False
  34.  
  35. mouse_controller = MouseController()
  36. keyboard_controller = KeyboardController()
  37.  
  38. start_t = datetime.datetime.now().time()
  39. start_td = datetime.timedelta(hours=start_t.hour, minutes=start_t.minute, seconds=start_t.second)
  40. end_t = None
  41. end_td = None
  42.  
  43. # hotbar dictionary for key switchinch in Minecraft, the values are the number of items in the slot, or a keyword for the item
  44. # the slot 3 to 9 filled with a full stack - 1 becuase we need to reserve inventory space for the exp bottles. without it the inventory
  45. # will be filled with fishing drops
  46. hotbar = {
  47.     "1": 64,       # food slot
  48.     "2": "Rod",    # tool slot
  49.     "3": 128,        # empty exp potion slot
  50.     "4": 128,        # empty exp potion slot
  51.     "5": 128,        # empty exp potion slot
  52.     "6": 128,        # empty exp potion slot
  53.     "7": 128,        # empty exp potion slot
  54.     "8": 128,        # empty exp potion slot
  55.     "9": 128         # empty exp potion slot
  56. }
  57.  
  58. # getting all the windows handles with the name "Minecraft" at the start into a list ::should be only ONE in the list!::
  59. def get_window_handles(winName):
  60.     def callback(handle, extra):
  61.         if win32gui.IsWindowVisible(handle) and win32gui.GetWindowText(handle)[0:9] == winName:
  62.             window_handles.append(handle)
  63.     window_handles = []
  64.     win32gui.EnumWindows(callback, None)
  65.     return window_handles
  66.  
  67. def capture_window(window_handle):
  68.     if window_handle is None:
  69.         window_handle = win32gui.GetDesktopWindow()
  70.     # getting Minecraft window dimensions into 4 variables for convenience of use
  71.     window_dimensions = win32gui.GetWindowRect(window_handle)
  72.     left = window_dimensions[0]
  73.     top = window_dimensions[1]
  74.     right = window_dimensions[2]
  75.     bottom = window_dimensions[3]
  76.     # setting the borders for the capture area (buttom-left side of the Minecraft window)
  77.     left_border = 8
  78.     right_border = 8
  79.     top_border = 30
  80.     bottom_border = 8
  81.     # calculating the capture area for cropping the screenshot
  82.     width = right - left - left_border - right_border
  83.     height = bottom - top - top_border - bottom_border
  84.     cropped_x = left_border
  85.     cropped_y = top_border
  86.     # capturing the screenshot based on the calculated dimensions
  87.     window_device_context = win32gui.GetWindowDC(window_handle)
  88.     device_context_object = win32ui.CreateDCFromHandle(window_device_context)
  89.     create_device_context = device_context_object.CreateCompatibleDC()
  90.     # creating a bitmap object for because the screenshot is a bitmap
  91.     data_bit_map = win32ui.CreateBitmap()
  92.     data_bit_map.CreateCompatibleBitmap(device_context_object, width, height)
  93.     create_device_context.SelectObject(data_bit_map)
  94.     # BitBlt is a Windows API function that copies the contents of one device context into another
  95.     create_device_context.BitBlt(
  96.         (0, 0),
  97.         (width, height),
  98.         device_context_object,
  99.         (cropped_x, cropped_y),
  100.         win32con.SRCCOPY,
  101.     )
  102.     # instead of saving the screenshot to a file, we can just get the raw data from the bitmap object
  103.     image = numpy.frombuffer(data_bit_map.GetBitmapBits(True), dtype="uint8")
  104.     # the 4 is for the number of color channels (RGB + alpha), may change it to 3 if the alpha channel is not needed
  105.     image.shape = (height, width, 4)
  106.     # releasing the device contexts and deleting the bitmap object, if we dont do this, we will get a memory leak
  107.     device_context_object.DeleteDC()
  108.     create_device_context.DeleteDC()
  109.     win32gui.ReleaseDC(window_handle, window_device_context)
  110.     win32gui.DeleteObject(data_bit_map.GetHandle())
  111.     return image
  112.  
  113. def type_a_command(command_string):
  114.     keyboard_controller.press(KeyCode.from_char("/"))
  115.     keyboard_controller.release(KeyCode.from_char("/"))
  116.     time.sleep(0.2)
  117.     keyboard_controller.type(command_string[1:])
  118.     time.sleep(0.2)
  119.     keyboard_controller.press(Key.enter)
  120.     keyboard_controller.release(Key.enter)
  121.     time.sleep(0.2)
  122.     mouse_controller.click(Button.left)
  123.     time.sleep(0.2)
  124.  
  125. def fill_exp_potions(hotbar):
  126.     # This function is for filling the empty bottles from the SlimeFun plugin with exp
  127.     for i in range(3, 10):
  128.         if hotbar[str(i)] > 0: # as long as the slot in hotbar is not empty
  129.             hotbar[str(i)] -= 1
  130.             keyboard_controller.type(str(i))
  131.             time.sleep(0.4)
  132.             mouse_controller.click(Button.right)
  133.             time.sleep(0.4)
  134.             break  # Exit the loop after filling one bottle
  135.     keyboard_controller.type('2') # Switch back to the fishing rod slot which is "2"
  136.     time.sleep(0.3)
  137.     # Return hotbar
  138.  
  139. def action_eat(hotbar):
  140.     # this function is for eating food from the food slot in the hotbar
  141.     # IMPORTANT: the SlimeFun plugin food is 1 click instead of holding the right mouse button
  142.     if hotbar['1'] > 0:
  143.         hotbar['1'] = hotbar['1'] - 1
  144.         keyboard_controller.type('1')
  145.         time.sleep(0.8)
  146.         mouse_controller.click(Button.right) # eat
  147.         time.sleep(0.8)
  148.         keyboard_controller.type('2') # switch back to weapon slot which is "2"
  149.  
  150. def check_log_file():
  151.     global previous_timestamp
  152.     try:
  153.         current_timestamp = os.path.getmtime(log_file_path)
  154.         if current_timestamp != previous_timestamp:
  155.             previous_timestamp = current_timestamp
  156.             # for some reasen, the Minecraft logfile cannot be read as needed, so we copy it to a new file and read it
  157.             copyfile(log_file_path, log_file_copy_path)
  158.             with open(log_file_copy_path, errors='ignore', mode='r') as file:
  159.                 lines = file.readlines()
  160.                 if lines:
  161.                     last_line = lines[-1].rstrip()
  162.                     return last_line
  163.                 else:
  164.                     print("Log file is empty.")
  165.             return None
  166.     except IOError as e:
  167.         print('Cannot open the logfile. Error: {}'.format(e))
  168.  
  169. def main():
  170.     global end_td  # Declare td_end as a global variable
  171.     fishing_count = 0
  172.     left_right = False
  173.     # Variables for FPS calculation
  174.     frame_count = 0
  175.     start_time = time.time()
  176.     fps = 0
  177.     prev_fps_update_time = start_time
  178.     read_count=0
  179.  
  180.     # Font settings for FPS display
  181.     font = cv2.FONT_HERSHEY_SIMPLEX
  182.     font_scale = 0.7
  183.     color = (0, 255, 0)  # Green color
  184.     thickness = 2
  185.     position = (10, 30)  # Top-left corner of the text
  186.  
  187.     while True:
  188.         # constant check for the Minecraft window
  189.         if len(get_window_handles("Minecraft")) == 0:
  190.             print("Error: no windows with the name \"Minecraft\" were found")
  191.             break
  192.  
  193.         # constant search for keywords in Minecraft chat but only every 10 loops or 'frames'
  194.         # after reading the log file, check if the last line contains the keyword
  195.         if read_count % 10 == 0:
  196.             last_line = check_log_file()
  197.             if last_line and re.search(regex_pattern, str(last_line)):
  198.                 print("Found somting! {} and {}".format(last_line, re.search(regex_pattern, str(last_line))))
  199.         read_count += 1
  200.  
  201.         # strat video vision proccessing
  202.         screenshot = capture_window(get_window_handles("Minecraft")[0])
  203.         x, y, w, h = (
  204.             # this will limit the capture area to the buttom-left side of the Minecraft window
  205.             screenshot.shape[1] * 3 // 4,
  206.             screenshot.shape[0] * 3 // 4,
  207.             screenshot.shape[1] // 4,
  208.             screenshot.shape[0] // 4,
  209.         )
  210.         # screenshot now contains only the bottom-left part of the Minecraft window
  211.         screenshot = screenshot[y : y + h, x : x + w]
  212.  
  213.         # Pytesseract OCR configs for better text recognition (all configs here: https://muthu.co/all-tesseract-ocr-options/)
  214.         # Page segmentation modes (psm) values:
  215.         # 0   Orientation and script detection (OSD) only.
  216.         # 1   Automatic page segmentation with OSD.
  217.         # 2   Automatic page segmentation, but no OSD, or OCR.
  218.         # 3   Fully automatic page segmentation, but no OSD. (Default)
  219.         # 4   Assume a single column of text of variable sizes.
  220.         # 5   Assume a single uniform block of vertically aligned text.
  221.         # 6   Assume a single uniform block of text.
  222.         # 7   Treat the image as a single text line.
  223.         # 8   Treat the image as a single word.
  224.         # 9   Treat the image as a single word in a circle.
  225.         # 10  Treat the image as a single character.
  226.         # 11  Sparse text. Find as much text as possible in no particular order.
  227.         # 12  Sparse text with OSD.
  228.         # 13  Raw line. Treat the image as a single text line, bypassing hacks that are Tesseract-specific.
  229.         # OCR Engine modes (oem) values:
  230.         # 0   Legacy engine only.
  231.         # 1   Neural nets LSTM engine only.
  232.         # 2   Legacy + LSTM engines.
  233.         # 3   Default, based on what is available.
  234.         cfg = '--psm 7 --oem 0'
  235.         screenshot_filter = cv2.cvtColor(screenshot, cv2.COLOR_BGR2GRAY)
  236.         text = pytesseract.image_to_string(
  237.             image = cv2.threshold(screenshot_filter, 220, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
  238.         )
  239.         # print("debug: text value is: ", text)
  240.         if phrase in text.lower():
  241.             end_t = datetime.datetime.now().time()
  242.             end_td = datetime.timedelta(hours=end_t.hour, minutes=end_t.minute, seconds=end_t.second)
  243.             print("fish on, ", end=" ")
  244.             mouse_controller.click(Button.right)
  245.             fishing_count += 1
  246.             print("bobber retrieved, num: {},".format(fishing_count) , end=" ")
  247.             time.sleep(0.4)
  248.             if flag_fillExpPotions: #(fishing_count % 2) == 0 and | fill exp bottles every 2 fishing
  249.                 time.sleep(0.5)
  250.                 # fill_exp_potions(hotbar)
  251.                 time.sleep(0.9)
  252.             # eat every 10 fishings
  253.             if left_right: # rotate the camera back and forth every fishing
  254.                 mouse_controller.move(500, 0)
  255.                 left_right = False
  256.                 print("Moved to the Right")
  257.             else:
  258.                 mouse_controller.move(-500, 0)
  259.                 left_right = True
  260.                 print("Moved to the Left")
  261.                 # executing the /balance command to track how much money you get from fishing (/jobs plugin)
  262.                 # type_a_command("/bal")
  263.             time.sleep(0.8)
  264.             mouse_controller.click(Button.right)
  265.             print("bobbern thrown,", end=" ")
  266.             time.sleep(2.5)
  267.             # td_end = datetime.timedelta(hours=end_time.hour, minutes=end_time.minute, seconds=end_time.second)
  268.  
  269.  
  270.         # converting the screenshot into grayscale and then back to BGR and finally trimming to the same color channels as the screenshot
  271.         screenshot_filter = cv2.cvtColor(screenshot, cv2.COLOR_BGR2GRAY)
  272.         screenshot_filter = cv2.cvtColor(screenshot_filter, cv2.COLOR_GRAY2BGR)
  273.         screenshot = screenshot[:,:,:3]
  274.         tess_view = cv2.threshold(cv2.cvtColor(screenshot, cv2.COLOR_BGR2GRAY), 128, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
  275.         tess_view = cv2.cvtColor(tess_view, cv2.COLOR_GRAY2BGR)
  276.  
  277.         """
  278.        # attempting adaptiveThreshold to see if the text readbilty from the screenshot is easier for the Tesseract
  279.        tess_view2 = cv2.adaptiveThreshold(cv2.cvtColor(screenshot, cv2.COLOR_BGR2GRAY),255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY,11,2)
  280.        tess_view2 = cv2.cvtColor(tess_view2, cv2.COLOR_GRAY2BGR)
  281.  
  282.        # testing print for image shapes , ALL image output have to be the same size AND same color format in order to stack in numpy imshow
  283.        print("screenshot\t", screenshot.shape)
  284.        print("screenshot_filter\t", screenshot_filter.shape)
  285.        print("teseract\t", tess_view.shape)
  286.        print("teseract2\t", tess_view2.shape)
  287.        """
  288.         # Calculate FPS
  289.         current_time = time.time()
  290.         frame_count += 1
  291.  
  292.         # Update FPS approximately every second
  293.         if current_time - prev_fps_update_time >= 1.0:
  294.             # Calculate the FPS over the last second
  295.             elapsed_time = current_time - prev_fps_update_time
  296.             fps = frame_count / elapsed_time
  297.             # Reset counters
  298.             prev_fps_update_time = current_time
  299.             frame_count = 0
  300.  
  301.         # Display FPS on the image
  302.         fps_text = f"FPS: {fps:.2f}"
  303.         screenshot_with_fps = screenshot.copy()
  304.         cv2.putText(screenshot_with_fps, fps_text, position, font, font_scale, color, thickness, cv2.LINE_AA)
  305.         cv2.imshow("Computer Vision - ALL", numpy.vstack((
  306.             screenshot_with_fps
  307.             ,screenshot_filter
  308.             ,tess_view
  309.             #,tess_view2
  310.             )))
  311.  
  312.         if cv2.waitKey(1) == ord("q"):
  313.             cv2.destroyAllWindows()
  314.             break
  315.         end_time = datetime.datetime.now().time()
  316.  
  317. if __name__ == "__main__":
  318.     print("Starting fishing at: {}".format(start_t))
  319.     try:
  320.         main()
  321.     except KeyboardInterrupt:
  322.         # execute the next lines after KeyboardInterrupt: Ctrl+C is pressed to stop the script
  323.         try:
  324.             print("\nFished for: {} (hh:mm:ss)".format(end_td - start_td))
  325.         except TypeError:
  326.             print("\nTypeError (its fine)")
  327.         try:
  328.             sys.exit(130)
  329.         except SystemExit:
  330.             os._exit(130)
  331.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement