diff --git a/.gitignore b/.gitignore index c46954d9..885f5509 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ __pycache__/ assets/models/ .idea/ -.keybinds +.settings/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..14bdb5c5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "resources"] + path = resources + url = https://github.com/tanjeffreyz/auto-maple-resources.git diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 94a25f7f..b639d34d 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,5 +2,6 @@ + \ No newline at end of file diff --git a/assets/ding.mp3 b/assets/alerts/ding.mp3 similarity index 100% rename from assets/ding.mp3 rename to assets/alerts/ding.mp3 diff --git a/assets/alerts/rune_appeared.mp3 b/assets/alerts/rune_appeared.mp3 new file mode 100644 index 00000000..a775d099 Binary files /dev/null and b/assets/alerts/rune_appeared.mp3 differ diff --git a/assets/alert.mp3 b/assets/alerts/siren.mp3 similarity index 100% rename from assets/alert.mp3 rename to assets/alerts/siren.mp3 diff --git a/command_books/blaster.py b/command_books/blaster.py deleted file mode 100644 index fa7d48ae..00000000 --- a/command_books/blaster.py +++ /dev/null @@ -1,173 +0,0 @@ -"""A collection of all commands that a Blaster can use to interact with the game.""" - -import config -import time -import math -import settings -import utils -from components import Command -from vkeys import press, key_down, key_up - -# -# class Move(Command): -# """Moves to a given position using the shortest path based on the current Layout.""" -# -# def __init__(self, x, y, max_steps=15): -# super().__init__(locals()) -# self.target = (float(x), float(y)) -# self.max_steps = settings.validate_nonnegative_int(max_steps) -# -# def main(self): -# counter = self.max_steps -# path = config.layout.shortest_path(config.player_pos, self.target) -# # config.path = path.copy() -# # config.path.insert(0, config.player_pos) -# for point in path: -# counter = self._step(point, counter) -# -# @utils.run_if_enabled -# def _step(self, target, counter): -# toggle = True -# local_error = utils.distance(config.player_pos, target) -# global_error = utils.distance(config.player_pos, self.target) -# while config.enabled and \ -# counter > 0 and \ -# local_error > settings.move_tolerance and \ -# global_error > settings.move_tolerance: -# if toggle: -# d_x = target[0] - config.player_pos[0] -# if abs(d_x) > settings.move_tolerance / math.sqrt(2): -# if d_x < 0: -# Jump('left').main() -# else: -# Jump('right').main() -# counter -= 1 -# else: -# d_y = target[1] - config.player_pos[1] -# if abs(d_y) > settings.move_tolerance / math.sqrt(2): -# if d_y < 0: -# Jump('up').main() -# else: -# Jump('down').main() -# counter -= 1 -# local_error = utils.distance(config.player_pos, target) -# global_error = utils.distance(config.player_pos, self.target) -# toggle = not toggle -# return counter -# -# -# class Adjust(Command): -# """Fine-tunes player position using small movements.""" -# -# def __init__(self, x, y, max_steps=5): -# super().__init__(locals()) -# self.target = (float(x), float(y)) -# self.max_steps = settings.validate_nonnegative_int(max_steps) -# -# def main(self): -# counter = self.max_steps -# toggle = True -# error = utils.distance(config.player_pos, self.target) -# while config.enabled and counter > 0 and error > settings.adjust_tolerance: -# if toggle: -# d_x = self.target[0] - config.player_pos[0] -# threshold = settings.adjust_tolerance / math.sqrt(2) -# if abs(d_x) > threshold: -# walk_counter = 0 -# if d_x < 0: -# key_down('left') -# while config.enabled and d_x < -1 * threshold and walk_counter < 60: -# time.sleep(0.05) -# walk_counter += 1 -# d_x = self.target[0] - config.player_pos[0] -# key_up('left') -# else: -# key_down('right') -# while config.enabled and d_x > threshold and walk_counter < 60: -# time.sleep(0.05) -# walk_counter += 1 -# d_x = self.target[0] - config.player_pos[0] -# key_up('right') -# counter -= 1 -# else: -# d_y = self.target[1] - config.player_pos[1] -# if abs(d_y) > settings.adjust_tolerance / math.sqrt(2): -# if d_y < 0: -# Jump('up').main() -# else: -# key_down('down') -# time.sleep(0.05) -# press('space', 3, down_time=0.1) -# key_up('down') -# time.sleep(0.05) -# counter -= 1 -# error = utils.distance(config.player_pos, self.target) -# toggle = not toggle -# -# -# def step(direction, target): -# print('blaster step test') - - -class Buff(Command): - """Uses each of Blaster's buffs once.""" - - def __init__(self): - super().__init__(locals()) - self.booster_time = 0 - self.warrior_time = 0 - - def main(self): - now = time.time() - if self.booster_time == 0 or now - self.booster_time > 190: - press('f1', 2) - self.booster_time = now - if self.warrior_time == 0 or now - self.warrior_time > 890: - press('f2', 2) - self.warrior_time = now - - -class Jump(Command): - """Performs a flash jump or 'Detonate' in the given direction.""" - - def __init__(self, direction): - super().__init__(locals()) - self.direction = settings.validate_arrows(direction) - - def main(self): - key_down(self.direction) - time.sleep(0.1) - press('space', 1) - if self.direction == 'up': - press('d', 1) - else: - press('space', 1) - key_up(self.direction) - time.sleep(0.5) - - -class MagnumPunch(Command): - """Performs a 'No-Reload Magnum Punch' combo once.""" - - def __init__(self, direction): - super().__init__(locals()) - self.direction = settings.validate_arrows(direction) - - def main(self): - key_down(self.direction) - time.sleep(0.05) - key_down('q') - time.sleep(0.1) - for _ in range(3): - key_down('r') - time.sleep(0.05) - key_down('e') - time.sleep(0.05) - key_up('r') - key_up('e') - time.sleep(0.05) - key_up('q') - time.sleep(0.025) - press('space', 1) - key_up(self.direction) - time.sleep(0.05) diff --git a/command_books/kanna.py b/command_books/kanna.py deleted file mode 100644 index cc0550e3..00000000 --- a/command_books/kanna.py +++ /dev/null @@ -1,297 +0,0 @@ -"""A collection of all commands that a Kanna can use to interact with the game.""" - -import config -import time -import math -import settings -import utils -from components import Command -from vkeys import press, key_down, key_up - - -# List of key mappings -class Key: - # Movement - JUMP = 'space' - TELEPORT = 'e' - CHARM = 'd' - - # Buffs - HAKU = 'f4' - AKATSUKI_WARRIOR = 'f3' - HOLY_SYMBOL = 'f2' - SPEED_INFUSION = 'f1' - - # Skills - SHIKIGAMI = 'r' - TENGU = 'q' - YAKSHA = '2' - VANQUISHER = 'f' - KISHIN = 'ctrl' - NINE_TAILS = '3' - EXORCIST = 'w' - DOMAIN = 'z' - ONI_LEGION = '5' - BLOSSOM_BARRIER = 'g' - YUKIMUSUME = 'c' - MANA_BALANCE = 'lshift' - - -######################### -# Commands # -######################### -def step(direction, target): - """ - Performs one movement step in the given DIRECTION towards TARGET. - Should not press any arrow keys, as those are handled by Auto Maple. - """ - - num_presses = 2 - if direction == 'up' or direction == 'down': - num_presses = 1 - if config.stage_fright and direction != 'up' and utils.bernoulli(0.75): - time.sleep(utils.rand_float(0.1, 0.3)) - d_y = target[1] - config.player_pos[1] - if abs(d_y) > settings.move_tolerance * 1.5: - if direction == 'down': - press(Key.JUMP, 3) - elif direction == 'up': - press(Key.JUMP, 1) - press(Key.TELEPORT, num_presses) - - -class Adjust(Command): - """Fine-tunes player position using small movements.""" - - def __init__(self, x, y, max_steps=5): - super().__init__(locals()) - self.target = (float(x), float(y)) - self.max_steps = settings.validate_nonnegative_int(max_steps) - - def main(self): - counter = self.max_steps - toggle = True - error = utils.distance(config.player_pos, self.target) - while config.enabled and counter > 0 and error > settings.adjust_tolerance: - if toggle: - d_x = self.target[0] - config.player_pos[0] - threshold = settings.adjust_tolerance / math.sqrt(2) - if abs(d_x) > threshold: - walk_counter = 0 - if d_x < 0: - key_down('left') - while config.enabled and d_x < -1 * threshold and walk_counter < 60: - time.sleep(0.05) - walk_counter += 1 - d_x = self.target[0] - config.player_pos[0] - key_up('left') - else: - key_down('right') - while config.enabled and d_x > threshold and walk_counter < 60: - time.sleep(0.05) - walk_counter += 1 - d_x = self.target[0] - config.player_pos[0] - key_up('right') - counter -= 1 - else: - d_y = self.target[1] - config.player_pos[1] - if abs(d_y) > settings.adjust_tolerance / math.sqrt(2): - if d_y < 0: - Teleport('up').main() - else: - key_down('down') - time.sleep(0.05) - press(Key.JUMP, 3, down_time=0.1) - key_up('down') - time.sleep(0.05) - counter -= 1 - error = utils.distance(config.player_pos, self.target) - toggle = not toggle - - -class Buff(Command): - """Uses each of Kanna's buffs once. Uses 'Haku Reborn' whenever it is available.""" - - def __init__(self): - super().__init__(locals()) - self.haku_time = 0 - self.buff_time = 0 - - def main(self): - buffs = [Key.SPEED_INFUSION, Key.HOLY_SYMBOL] - now = time.time() - if self.haku_time == 0 or now - self.haku_time > 490: - press(Key.HAKU, 2) - press(Key.AKATSUKI_WARRIOR, 2) - self.haku_time = now - if self.buff_time == 0 or now - self.buff_time > settings.buff_cooldown: - for key in buffs: - press(key, 3, up_time=0.3) - self.buff_time = now - - -class Teleport(Command): - """ - Teleports in a given direction, jumping if specified. Adds the player's position - to the current Layout if necessary. - """ - - def __init__(self, direction, jump='False'): - super().__init__(locals()) - self.direction = settings.validate_arrows(direction) - self.jump = settings.validate_boolean(jump) - - def main(self): - num_presses = 3 - time.sleep(0.05) - if self.direction in ['up', 'down']: - num_presses = 2 - if self.direction != 'up': - key_down(self.direction) - time.sleep(0.05) - if self.jump: - if self.direction == 'down': - press(Key.JUMP, 3, down_time=0.1) - else: - press(Key.JUMP, 1) - if self.direction == 'up': - key_down(self.direction) - time.sleep(0.05) - press(Key.TELEPORT, num_presses) - key_up(self.direction) - if settings.record_layout: - config.layout.add(*config.player_pos) - - -class Shikigami(Command): - """Attacks using 'Shikigami Haunting' in a given direction.""" - - def __init__(self, direction, attacks=2, repetitions=1): - super().__init__(locals()) - self.direction = settings.validate_horizontal_arrows(direction) - self.attacks = int(attacks) - self.repetitions = int(repetitions) - - def main(self): - time.sleep(0.05) - key_down(self.direction) - time.sleep(0.05) - if config.stage_fright and utils.bernoulli(0.7): - time.sleep(utils.rand_float(0.1, 0.3)) - for _ in range(self.repetitions): - press(Key.SHIKIGAMI, self.attacks, up_time=0.05) - key_up(self.direction) - if self.attacks > 2: - time.sleep(0.3) - else: - time.sleep(0.2) - - -class Tengu(Command): - """Uses 'Tengu Strike' once.""" - - def main(self): - press(Key.TENGU, 1, up_time=0.05) - - -class Yaksha(Command): - """ - Places 'Ghost Yaksha Boss' in a given direction, or towards the center of the map if - no direction is specified. - """ - - def __init__(self, direction=None): - super().__init__(locals()) - if direction is None: - self.direction = direction - else: - self.direction = settings.validate_horizontal_arrows(direction) - - def main(self): - if self.direction: - press(self.direction, 1, down_time=0.1, up_time=0.05) - else: - if config.player_pos[0] > 0.5: - press('left', 1, down_time=0.1, up_time=0.05) - else: - press('right', 1, down_time=0.1, up_time=0.05) - press(Key.YAKSHA, 3) - - -class Vanquisher(Command): - """Holds down 'Vanquisher's Charm' until this command is called again.""" - - def main(self): - key_up(Key.VANQUISHER) - time.sleep(0.075) - key_down(Key.VANQUISHER) - time.sleep(0.15) - - -class Kishin(Command): - """Uses 'Kishin Shoukan' once.""" - - def main(self): - press(Key.KISHIN, 4, down_time=0.1, up_time=0.15) - - -class NineTails(Command): - """Uses 'Nine-Tailed Fury' once.""" - - def main(self): - press(Key.NINE_TAILS, 3) - - -class Exorcist(Command): - """Uses 'Exorcist's Charm' once.""" - - def __init__(self, jump='False'): - super().__init__(locals()) - self.jump = settings.validate_boolean(jump) - - def main(self): - if self.jump: - press(Key.JUMP, 1, down_time=0.1, up_time=0.15) - press(Key.EXORCIST, 2, up_time=0.05) - - -class Domain(Command): - """Uses 'Spirit's Domain' once.""" - - def main(self): - press(Key.DOMAIN, 3) - - -class Legion(Command): - """Uses 'Ghost Yaksha: Great Oni Lord's Legion' once.""" - - def main(self): - press(Key.ONI_LEGION, 2, down_time=0.1) - - -class BlossomBarrier(Command): - """Places a 'Blossom Barrier' on the ground once.""" - - def main(self): - press(Key.BLOSSOM_BARRIER, 2) - - -class Yukimusume(Command): - """Uses 'Yuki-musume Shoukan' once.""" - - def main(self): - press(Key.YUKIMUSUME, 2) - - -class Balance(Command): - """Restores mana using 'Mana Balance' once.""" - - def main(self): - press(Key.MANA_BALANCE, 2) - - -class Charm(Command): - """Jumps up using 'Shikigami Charm'.""" - - def main(self): - press(Key.CHARM, 2) diff --git a/gui_components/__init__.py b/gui_components/__init__.py deleted file mode 100644 index 18ec8c72..00000000 --- a/gui_components/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -import gui_components.menu.main as menu -import gui_components.view.main as view -import gui_components.edit.main as edit -import gui_components.settings.main as settings - -Menu = menu.Menu -View = view.View -Edit = edit.Edit -Settings = settings.Settings diff --git a/gui_components/view/main.py b/gui_components/view/main.py deleted file mode 100644 index dd41e751..00000000 --- a/gui_components/view/main.py +++ /dev/null @@ -1,217 +0,0 @@ -"""Displays the current minimap as well as various information regarding the current routine.""" - -import config -import utils -import cv2 -import tkinter as tk -from gui_components.interfaces import LabelFrame, Tab -from components import Point -from PIL import Image, ImageTk - - -class View(Tab): - def __init__(self, parent, **kwargs): - super().__init__(parent, 'View', **kwargs) - - self.grid_columnconfigure(0, weight=1) - self.grid_columnconfigure(3, weight=1) - - self.minimap = Minimap(self) - self.minimap.grid(row=0, column=2, sticky=tk.NSEW, padx=10, pady=10) - - self.status = Status(self) - self.status.grid(row=1, column=2, sticky=tk.NSEW, padx=10, pady=10) - - self.details = Details(self) - self.details.grid(row=2, column=2, sticky=tk.NSEW, padx=10, pady=10) - - self.routine = Routine(self) - self.routine.grid(row=0, column=1, rowspan=3, sticky=tk.NSEW, padx=10, pady=10) - - -class Minimap(LabelFrame): - def __init__(self, parent, **kwargs): - super().__init__(parent, 'Minimap', **kwargs) - - self.WIDTH = 400 - self.HEIGHT = 300 - self.canvas = tk.Canvas(self, bg='black', - width=self.WIDTH, height=self.HEIGHT, - borderwidth=0, highlightthickness=0) - self.canvas.pack(expand=True, fill='both', padx=5, pady=5) - self.container = None - - def display_minimap(self): - """Updates the Main page with the current minimap.""" - - minimap = config.capture.minimap - if minimap: - rune_active = minimap['rune_active'] - rune_pos = minimap['rune_pos'] - path = minimap['path'] - player_pos = minimap['player_pos'] - - img = cv2.cvtColor(minimap['minimap'], cv2.COLOR_BGR2RGB) - height, width, _ = img.shape - - # Resize minimap to fit the Canvas - ratio = min(self.WIDTH / width, self.HEIGHT / height) - new_width = int(width * ratio) - new_height = int(height * ratio) - if new_height * new_width > 0: - img = cv2.resize(img, (new_width, new_height), interpolation=cv2.INTER_AREA) - - # Mark the position of the active rune - if rune_active: - cv2.circle(img, - utils.convert_to_absolute(rune_pos, img), - 3, - (128, 0, 128), - -1) - - # Draw the current path that the program is taking - if config.enabled and len(path) > 1: - for i in range(len(path) - 1): - start = utils.convert_to_absolute(path[i], img) - end = utils.convert_to_absolute(path[i + 1], img) - cv2.line(img, start, end, (0, 255, 255), 1) - - # Draw each Point in the routine as a circle - for p in config.routine.sequence: - if isinstance(p, Point): - utils.draw_location(img, - p.location, - (0, 255, 0) if config.enabled else (255, 0, 0)) - - # Display the current Layout - if config.layout: - config.layout.draw(img) - - # Draw the player's position on top of everything - cv2.circle(img, - utils.convert_to_absolute(player_pos, img), - 3, - (0, 0, 255), - -1) - - # Display the minimap in the Canvas - img = ImageTk.PhotoImage(Image.fromarray(img)) - if self.container is None: - self.container = self.canvas.create_image(self.WIDTH // 2, - self.HEIGHT // 2, - image=img, anchor=tk.CENTER) - else: - self.canvas.itemconfig(self.container, image=img) - self._img = img # Prevent garbage collection - self.after(50, self.display_minimap) - - -class Status(LabelFrame): - def __init__(self, parent, **kwargs): - super().__init__(parent, 'Status', **kwargs) - - self.grid_columnconfigure(0, weight=1) - self.grid_columnconfigure(3, weight=1) - - self.curr_cb = tk.StringVar() - self.curr_routine = tk.StringVar() - - self.cb_label = tk.Label(self, text='Command Book:') - self.cb_label.grid(row=0, column=1, padx=5, pady=(5, 0), sticky=tk.E) - self.cb_entry = tk.Entry(self, textvariable=self.curr_cb, state=tk.DISABLED) - self.cb_entry.grid(row=0, column=2, padx=(0, 5), pady=(5, 0), sticky=tk.EW) - - self.r_label = tk.Label(self, text='Routine:') - self.r_label.grid(row=1, column=1, padx=5, pady=(0, 5), sticky=tk.E) - self.r_entry = tk.Entry(self, textvariable=self.curr_routine, state=tk.DISABLED) - self.r_entry.grid(row=1, column=2, padx=(0, 5), pady=(0, 5), sticky=tk.EW) - - def set_cb(self, string): - self.curr_cb.set(string) - - def set_routine(self, string): - self.curr_routine.set(string) - - -class Details(LabelFrame): - def __init__(self, parent, **kwargs): - super().__init__(parent, 'Details', **kwargs) - self.name_var = tk.StringVar() - - self.name = tk.Entry(self, textvariable=self.name_var, justify=tk.CENTER, state=tk.DISABLED) - self.name.pack(pady=(5, 2)) - - self.scroll = tk.Scrollbar(self) - self.scroll.pack(side=tk.RIGHT, fill=tk.Y, pady=5) - - self.text = tk.Text(self, width=1, height=10, - yscrollcommand=self.scroll.set, - state=tk.DISABLED, wrap=tk.WORD) - self.text.pack(side=tk.LEFT, expand=True, fill='both', padx=(5, 0), pady=(0, 5)) - - self.scroll.config(command=self.text.yview) - - def show_details(self, e): - """Callback for updating the Details section everytime Listbox selection changes.""" - - selections = e.widget.curselection() - if len(selections) > 0: - index = int(selections[0]) - self.display_info(index) - - def update_details(self): - """Updates Details to show info about the current selection.""" - - selects = self.parent.routine.listbox.curselection() - if len(selects) > 0: - self.display_info(int(selects[0])) - else: - self.clear_info() - - def display_info(self, index): - """Updates the Details section to show info about the Component at position INDEX.""" - - self.text.config(state=tk.NORMAL) - - info = config.routine[index].info() - self.name_var.set(info['name']) - arr = [] - for key, value in info['vars'].items(): - arr.append(f'{key}: {value}') - self.text.delete(1.0, 'end') - self.text.insert(1.0, '\n'.join(arr)) - - self.text.config(state=tk.DISABLED) - - def clear_info(self): - self.name_var.set('') - self.text.config(state=tk.NORMAL) - self.text.delete(1.0, 'end') - self.text.config(state=tk.DISABLED) - - -class Routine(LabelFrame): - def __init__(self, parent, **kwargs): - super().__init__(parent, 'Routine', **kwargs) - - self.scroll = tk.Scrollbar(self) - self.scroll.pack(side=tk.RIGHT, fill='both', pady=5) - - self.listbox = tk.Listbox(self, width=25, - listvariable=config.gui.routine_var, - exportselection=False, - activestyle='none', - yscrollcommand=self.scroll.set) - self.listbox.bind('', lambda e: 'break') - self.listbox.bind('', lambda e: 'break') - self.listbox.bind('', lambda e: 'break') - self.listbox.bind('', lambda e: 'break') - self.listbox.bind('<>', parent.details.show_details) - self.listbox.pack(side=tk.LEFT, expand=True, fill='both', padx=(5, 0), pady=5) - - self.scroll.config(command=self.listbox.yview) - - def select(self, i): - self.listbox.selection_clear(0, 'end') - self.listbox.selection_set(i) - self.listbox.see(i) diff --git a/layouts/bd1 b/layouts/bd1 deleted file mode 100644 index 8540c479..00000000 Binary files a/layouts/bd1 and /dev/null differ diff --git a/layouts/cf1 b/layouts/cf1 deleted file mode 100644 index b95f8ecb..00000000 Binary files a/layouts/cf1 and /dev/null differ diff --git a/layouts/dcsa b/layouts/dcsa deleted file mode 100644 index 2b6338fb..00000000 Binary files a/layouts/dcsa and /dev/null differ diff --git a/layouts/dcup2 b/layouts/dcup2 deleted file mode 100644 index 96246c1f..00000000 Binary files a/layouts/dcup2 and /dev/null differ diff --git a/layouts/hft b/layouts/hft deleted file mode 100644 index d25c477a..00000000 Binary files a/layouts/hft and /dev/null differ diff --git a/layouts/mts3 b/layouts/mts3 deleted file mode 100644 index 8dc44c83..00000000 Binary files a/layouts/mts3 and /dev/null differ diff --git a/layouts/srs1 b/layouts/srs1 deleted file mode 100644 index 5aa43a88..00000000 Binary files a/layouts/srs1 and /dev/null differ diff --git a/main.py b/main.py index 0b41ffb5..9f2a430b 100644 --- a/main.py +++ b/main.py @@ -1,11 +1,11 @@ """The central program that ties all the modules together.""" import time -from bot import Bot -from capture import Capture -from notifier import Notifier -from listener import Listener -from gui import GUI +from src.modules.bot import Bot +from src.modules.capture import Capture +from src.modules.notifier import Notifier +from src.modules.listener import Listener +from src.modules.gui import GUI bot = Bot() @@ -29,7 +29,7 @@ while not listener.ready: time.sleep(0.01) -print('\n[~] Successfully initialized Auto Maple.') +print('\n[~] Successfully initialized Auto Maple') gui = GUI() gui.start() diff --git a/requirements.txt b/requirements.txt index 75b0d64a..44bf382a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +GitPython keyboard mss numpy diff --git a/resources b/resources new file mode 160000 index 00000000..7a1c14a1 --- /dev/null +++ b/resources @@ -0,0 +1 @@ +Subproject commit 7a1c14a1298bf882bdb2508eeebef115b8179cac diff --git a/routines/cf1.csv b/routines/cf1.csv deleted file mode 100644 index beac64a3..00000000 --- a/routines/cf1.csv +++ /dev/null @@ -1,62 +0,0 @@ -$, target=move_tolerance, value=0.09 -$, target=buff_cooldown, value=185 - -@, label=kish -*, x=0.811, y=0.243, frequency=1, skip=False, adjust=False - Kishin -*, x=0.640, y=0.243, frequency=1, skip=False, adjust=True -*, x=0.415, y=0.243, frequency=1, skip=False, adjust=False - Yaksha, direction=right -*, x=0.150, y=0.243, frequency=1, skip=False, adjust=False - Shikigami, direction=left, attacks=3, repetitions=1 - Shikigami, direction=left, attacks=2, repetitions=1 - Shikigami, direction=right, attacks=3, repetitions=1 - -@, label=main -*, x=0.275, y=0.142, frequency=1, skip=False, adjust=False - Shikigami, direction=left, attacks=3, repetitions=1 - Shikigami, direction=right, attacks=3, repetitions=1 - Wait, duration=0.05 -*, x=0.275, y=0.142, frequency=1, skip=False, adjust=True - Exorcist, jump=False - Wait, duration=0.3 - Teleport, direction=up, jump=False - Shikigami, direction=right, attacks=3, repetitions=1 - Shikigami, direction=right, attacks=2, repetitions=1 - Teleport, direction=down, jump=False - Wait, duration=0.05 ->, label=boss, frequency=7, skip=True ->, label=main, frequency=1, skip=False - -@, label=boss ->, label=loot, frequency=3, skip=True -*, x=0.415, y=0.243, frequency=1, skip=False, adjust=False - Yaksha, direction=right - Charm - Teleport, direction=left, jump=False ->, label=main, frequency=1, skip=False - -@, label=loot -*, x=0.125, y=0.142, frequency=1, skip=False, adjust=False - Yaksha, direction=right - Wait, duration=0.1 - Balance -*, x=0.257, y=0.142, frequency=1, skip=False, adjust=True - Exorcist, jump=False - Tengu - Teleport, direction=up, jump=False - Shikigami, direction=left, attacks=3, repetitions=1 - Teleport, direction=right, jump=False - Shikigami, direction=right, attacks=3, repetitions=1 - Shikigami, direction=right, attacks=2, repetitions=1 - Walk, direction=right, duration=0.4 - Teleport, direction=down, jump=False -*, x=0.640, y=0.108, frequency=1, skip=False, adjust=True - Shikigami, direction=left, attacks=2, repetitions=1 - Shikigami, direction=right, attacks=2, repetitions=1 -*, x=0.640, y=0.108, frequency=1, skip=False, adjust=False - NineTails - Tengu - Wait, duration=0.2 -*, x=0.640, y=0.243, frequency=1, skip=False, adjust=False ->, label=kish, frequency=1, skip=False diff --git a/routines/dcup2.csv b/routines/dcup2.csv deleted file mode 100644 index 58b076cf..00000000 --- a/routines/dcup2.csv +++ /dev/null @@ -1,83 +0,0 @@ -$, target=move_tolerance, value=0.075 - -@, label=main -*, x=0.873, y=0.083, frequency=1, skip=False, adjust=False - Tengu -*, x=0.873, y=0.200, frequency=1, skip=False, adjust=False - Shikigami, direction=left, attacks=2, repetitions=1 - Shikigami, direction=right, attacks=2, repetitions=1 ->, label=loot, frequency=4, skip=False -*, x=0.873, y=0.200, frequency=1, skip=False, adjust=False - Fall, distance=0.05 - -@, label=afterboss -*, x=0.055, y=0.125, frequency=1, skip=False, adjust=False - Shikigami, direction=right, attacks=2, repetitions=1 -*, x=0.055, y=0.125, frequency=1, skip=False, adjust=False - Fall, distance=0.005 - Shikigami, direction=right, attacks=2, repetitions=1 - Walk, direction=left, duration=0.2 ->, label=main, frequency=1, skip=False - -@, label=loot -*, x=0.873, y=0.200, frequency=1, skip=False, adjust=False - Teleport, direction=left, jump=False - Yaksha, direction=left -*, x=0.750, y=0.200, frequency=1, skip=False, adjust=False - Teleport, direction=left, jump=False - NineTails - Tengu -*, x=0.44, y=0.199, frequency=1, skip=False, adjust=True - Balance - Shikigami, direction=right, attacks=3, repetitions=1 -*, x=0.350, y=0.200, frequency=1, skip=False, adjust=True - Kishin - Wait, duration=0.15 -*, x=0.225, y=0.200, frequency=1, skip=False, adjust=True - Walk, direction=left, duration=0.2 - Shikigami, direction=left, attacks=2, repetitions=1 -*, x=0.225, y=0.200, frequency=1, skip=False, adjust=False - Fall, distance=0.05 - Walk, direction=left, duration=0.2 - Tengu -*, x=0.660, y=0.083, frequency=1, skip=False, adjust=False - Shikigami, direction=left, attacks=2, repetitions=1 - Tengu -*, x=0.394, y=0.100, frequency=1, skip=False, adjust=False - Shikigami, direction=left, attacks=2, repetitions=1 - Tengu - Walk, direction=left, duration=0.5 -*, x=0.394, y=0.100, frequency=1, skip=False, adjust=False - Walk, direction=left, duration=0.2 ->, label=afterboss2, frequency=1, skip=False - -@, label=main2 -*, x=0.873, y=0.083, frequency=1, skip=False, adjust=False - Tengu -*, x=0.873, y=0.200, frequency=1, skip=False, adjust=False - Shikigami, direction=left, attacks=2, repetitions=1 - Shikigami, direction=right, attacks=2, repetitions=1 ->, label=boss2, frequency=5, skip=True -*, x=0.873, y=0.200, frequency=1, skip=False, adjust=False - Fall, distance=0.05 - Walk, direction=right, duration=0.2 - -@, label=afterboss2 -*, x=0.055, y=0.125, frequency=1, skip=False, adjust=False - Shikigami, direction=right, attacks=2, repetitions=1 -*, x=0.055, y=0.125, frequency=1, skip=False, adjust=False - Fall, distance=0.005 - Shikigami, direction=right, attacks=2, repetitions=1 - Walk, direction=left, duration=0.2 ->, label=main2, frequency=1, skip=False - -@, label=boss2 -*, x=0.873, y=0.200, frequency=1, skip=False, adjust=False - Teleport, direction=left, jump=False - Yaksha, direction=left -*, x=0.750, y=0.200, frequency=3, skip=True, adjust=False - Fall, distance=0.05 ->, label=afterboss, frequency=3, skip=True -*, x=0.750, y=0.200, frequency=1, skip=False, adjust=False - Fall, distance=0.05 ->, label=afterboss2, frequency=1, skip=False diff --git a/routines/mts3.csv b/routines/mts3.csv deleted file mode 100644 index 5455821e..00000000 --- a/routines/mts3.csv +++ /dev/null @@ -1,217 +0,0 @@ -s, adjust_tolerance, 0.0125 - - - -@, loot_no_domain -*, 0.120, 0.303, adjust=True - shikigami, direction=right - yukimusume -*, 0.138, 0.414 - shikigami, direction=right -*, 0.336, 0.414 - legion -*, 0.461, 0.414 - ninetails -*, 0.737, 0.414 - kishin -*, 0.868, 0.414, adjust=True -*, 0.685, 0.414, adjust=True - teleport, direction=up -*, 0.750, 0.303 - walk, right, 0.15 -*, 0.865, 0.303, adjust=True - shikigami, direction=left, attacks=3 - wait, 0.1 -*, 0.865, 0.138 - shikigami, direction=left - walk, left, 0.3 -*, 0.645, 0.138, adjust=True - wait, 0.1 - teleport, direction=down -*, 0.539, 0.237 - yaksha, direction=right -*, 0.468, 0.237, adjust=True - exorcist - wait, 0.3 - teleport, direction=left -*, 0.315, 0.151, adjust=True - teleport, direction=down -*, 0.283, 0.303 - legion -*, 0.283, 0.303 - goto, main1 - -@, main1 -*, 0.283, 0.303, adjust=True - shikigami, direction=left - shikigami, direction=right, attacks=3 -*, 0.283, 0.303 - wait, 0.2 - exorcist - teleport, direction=up - wait, 0.3 - shikigami, direction=right, attacks=3 - teleport, direction=down -*, 0.460, 0.342, frequency=4, counter=1 - shikigami, direction=right -*, 0.500, 0.342, frequency=4, counter=1 - goto, boss1 -*, 0.283, 0.303 - teleport, direction=right - shikigami, direction=right -*, 0.283, 0.303 - goto, main1 - -@, main2 -*, 0.283, 0.303, adjust=True - shikigami, direction=left - shikigami, direction=right, attacks=3 -*, 0.283, 0.303 - wait, 0.2 - exorcist - teleport, direction=up - wait, 0.3 - shikigami, direction=right, attacks=3 - teleport, direction=down -*, 0.460, 0.342, frequency=5, counter=1 - shikigami, direction=right -*, 0.500, 0.342, frequency=5, counter=1 - goto, boss2 -*, 0.283, 0.303 - teleport, direction=right - shikigami, direction=right -*, 0.283, 0.303 - goto, main2 - -@, boss1 -*, 0.539, 0.237 - yaksha, direction=right -*, 0.510, 0.237 - teleport, direction=down -*, 0.530, 0.342 - shikigami, direction=left -*, 0.500, 0.342, frequency=3, counter=1 - goto, main2 -*, 0.500, 0.342 - goto, main1 - -@, boss2 -*, 0.539, 0.237 - yaksha, direction=right -*, 0.510, 0.237 - teleport, direction=down -*, 0.530, 0.342 - shikigami, direction=left -*, 0.500, 0.342 - goto, loot_with_domain - - - -@, loot_with_domain -*, 0.120, 0.303, adjust=True - shikigami, direction=right - yukimusume -*, 0.138, 0.414 - shikigami, direction=right -*, 0.336, 0.414, adjust=True - domain -*, 0.461, 0.414 - ninetails -*, 0.737, 0.414 - kishin -*, 0.868, 0.414, adjust=True -*, 0.685, 0.414, adjust=True - teleport, direction=up -*, 0.750, 0.303 - walk, right, 0.15 -*, 0.865, 0.303, adjust=True - shikigami, direction=left, attacks=3 - wait, 0.1 -*, 0.865, 0.138 - shikigami, direction=left - walk, left, 0.3 -*, 0.645, 0.138, adjust=True - wait, 0.1 - teleport, direction=down -*, 0.539, 0.237 - vanquisher -*, 0.480, 0.237, adjust=True -*, 0.580, 0.237, adjust=True - walk, left, 0.15 - walk, right, 0.1 - walk, left, 0.1 - walk, right, 0.1 - walk, left, 0.15 - walk, right, 0.1 - walk, left, 0.1 - walk, right, 0.1 - walk, left, 0.15 - walk, right, 0.1 - walk, left, 0.1 - walk, right, 0.1 - walk, left, 0.15 - walk, right, 0.1 - walk, left, 0.1 - walk, right, 0.1 -*, 0.600, 0.237, adjust=True - vanquisher - wait, 0.15 - exorcist - wait, 0.3 - teleport, direction=right -*, 0.715, 0.138 - goto, main3 - -@, main3 -*, 0.715, 0.138 - shikigami, direction=left, attacks=3 - wait, 0.15 - shikigami, direction=right, attacks=3 -*, 0.715, 0.138, adjust=True - teleport, direction=down -*, 0.715, 0.303 - shikigami, direction=left, attacks=3 - wait, 0.15 - shikigami, direction=right, attacks=3 - wait, 0.1 -*, 0.715, 0.303, frequency=3, adjust=True - exorcist - wait, 0.3 -*, 0.715, 0.303, frequency=12, counter=1 - teleport, direction=left, jump=True - goto, boss3 -*, 0.715, 0.303 - goto, main3 - -@, boss3 -*, 0.539, 0.237 - yaksha, direction=right -*, 0.510, 0.237 - teleport, direction=down -*, 0.530, 0.342 - shikigami, direction=left -*, 0.500, 0.342, frequency=2, counter=1 - goto, loot_no_domain -*, 0.500, 0.342 - goto, main4 - -@, main4 -*, 0.283, 0.303, adjust=True - shikigami, direction=left - shikigami, direction=right, attacks=3 -*, 0.283, 0.303 - wait, 0.2 - exorcist - teleport, direction=up - wait, 0.3 - shikigami, direction=right, attacks=3 - teleport, direction=down -*, 0.460, 0.342, frequency=5, counter=1 - shikigami, direction=right -*, 0.500, 0.342, frequency=5, counter=1 - goto, boss3 -*, 0.283, 0.303 - teleport, direction=right - shikigami, direction=right -*, 0.283, 0.303 - goto, main4 diff --git a/routines/os3.csv b/routines/os3.csv deleted file mode 100644 index 3d7340d9..00000000 --- a/routines/os3.csv +++ /dev/null @@ -1,100 +0,0 @@ -$, target=move_tolerance, value=0.075 -*, x=0.49, y=0.16, frequency=1, skip=False, adjust=True - Yaksha, direction=left - -@, label=kishin -*, x=0.360, y=0.257, frequency=1, skip=False, adjust=False - Kishin -*, x=0.543, y=0.257, frequency=1, skip=False, adjust=False - Shikigami, direction=right, attacks=2, repetitions=1 -*, x=0.754, y=0.257, frequency=1, skip=False, adjust=True - Shikigami, direction=right, attacks=2, repetitions=1 - -@, label=main1 -*, x=0.754, y=0.183, frequency=1, skip=False, adjust=False - Shikigami, direction=left, attacks=3, repetitions=1 - Shikigami, direction=right, attacks=3, repetitions=1 - Exorcist, jump=False - Wait, duration=0.4 - Teleport, direction=up, jump=False -*, x=0.754, y=0.097, frequency=2, skip=False, adjust=False - Shikigami, direction=left, attacks=3, repetitions=1 - Shikigami, direction=right, attacks=2, repetitions=1 -*, x=0.754, y=0.097, frequency=2, skip=False, adjust=True - Teleport, direction=down, jump=False -*, x=0.754, y=0.097, frequency=2, skip=True, adjust=False - Shikigami, direction=right, attacks=3, repetitions=1 - Shikigami, direction=left, attacks=2, repetitions=1 -*, x=0.754, y=0.097, frequency=2, skip=True, adjust=True - Teleport, direction=down, jump=False ->, label=boss1, frequency=5, skip=True ->, label=main1, frequency=1, skip=False - -@, label=boss1 -*, x=0.49, y=0.16, frequency=1, skip=False, adjust=True - Yaksha, direction=left ->, label=loot_domain, frequency=3, skip=True ->, label=main1, frequency=1, skip=False - -@, label=loot_domain -*, x=0.754, y=0.183, frequency=1, skip=False, adjust=False - Exorcist, jump=False - Wait, duration=0.6 -*, x=0.754, y=0.257, frequency=1, skip=False, adjust=True - Domain -*, x=0.846, y=0.257, frequency=1, skip=False, adjust=False - Shikigami, direction=right, attacks=3, repetitions=1 -*, x=0.869, y=0.177, frequency=1, skip=False, adjust=False - Shikigami, direction=right, attacks=3, repetitions=1 - Shikigami, direction=right, attacks=2, repetitions=1 -*, x=0.846, y=0.097, frequency=1, skip=False, adjust=False - Shikigami, direction=right, attacks=3, repetitions=1 - Shikigami, direction=left, attacks=2, repetitions=1 -*, x=0.650, y=0.097, frequency=1, skip=False, adjust=False - Shikigami, direction=left, attacks=3, repetitions=1 -*, x=0.474, y=0.063, frequency=1, skip=False, adjust=False - Shikigami, direction=left, attacks=3, repetitions=1 -*, x=0.280, y=0.080, frequency=1, skip=False, adjust=False - Shikigami, direction=left, attacks=3, repetitions=1 - Shikigami, direction=left, attacks=2, repetitions=1 - Teleport, direction=down, jump=False -*, x=0.28, y=0.16, frequency=1, skip=False, adjust=False - Shikigami, direction=left, attacks=2, repetitions=1 -*, x=0.46, y=0.16, frequency=1, skip=False, adjust=True - Yaksha, direction=right - Teleport, direction=down, jump=False -*, x=0.36, y=0.257, frequency=1, skip=False, adjust=False - Kishin -*, x=0.25, y=0.257, frequency=1, skip=False, adjust=True - Shikigami, direction=left, attacks=3, repetitions=1 - Teleport, direction=up, jump=False - -@, label=main2 -*, x=0.297, y=0.16, frequency=1, skip=False, adjust=False - Shikigami, direction=left, attacks=3, repetitions=1 - Shikigami, direction=right, attacks=3, repetitions=1 -*, x=0.297, y=0.16, frequency=1, skip=False, adjust=True - Teleport, direction=up, jump=False - Shikigami, direction=left, attacks=3, repetitions=1 - Shikigami, direction=left, attacks=2, repetitions=1 - Teleport, direction=down, jump=False ->, label=boss2, frequency=5, skip=True ->, label=main2, frequency=1, skip=False - -@, label=boss2 ->, label=loot_no_domain, frequency=2, skip=True -*, x=0.45, y=0.16, frequency=1, skip=False, adjust=False - Yaksha, direction=right ->, label=main2, frequency=1, skip=False - -@, label=loot_no_domain -*, x=0.49, y=0.160, frequency=1, skip=False, adjust=True - Yaksha, direction=left -*, x=0.486, y=0.063, frequency=1, skip=False, adjust=False - Shikigami, direction=left, attacks=2, repetitions=1 -*, x=0.280, y=0.080, frequency=1, skip=False, adjust=False - Shikigami, direction=left, attacks=2, repetitions=1 -*, x=0.28, y=0.257, frequency=1, skip=False, adjust=False -*, x=0.183, y=0.257, frequency=1, skip=False, adjust=False - Shikigami, direction=left, attacks=3, repetitions=1 ->, label=kishin, frequency=1, skip=False diff --git a/setup.py b/setup.py index 27d70981..65671a2a 100644 --- a/setup.py +++ b/setup.py @@ -1,39 +1,60 @@ """Creates a desktop shortcut that can run Auto Maple from anywhere.""" import os +import git import argparse import win32com.client as client -parser = argparse.ArgumentParser() -parser.add_argument('--stay', action='store_true') -args = parser.parse_args() - -# Create and save the shortcut using absolute paths -print('[~] Creating a desktop shortcut for Auto Maple:') -CWD = os.getcwd() -TARGET = os.path.join(os.environ['WINDIR'], 'System32', 'cmd.exe') -PATH = os.path.join(os.environ['USERPROFILE'], 'Desktop', 'Auto Maple.lnk') -flag = "/c" -if args.stay: - flag = "/k" - print(" ~ Leaving command prompt open after program finishes") - -shell = client.Dispatch('WScript.Shell') -shortcut = shell.CreateShortCut(PATH) -shortcut.Targetpath = TARGET -shortcut.Arguments = flag + f' \"cd {CWD} & python main.py\"' -shortcut.IconLocation = os.path.join(CWD, 'assets', 'icon.ico') -shortcut.save() - -# Enable "run as administrator" -with open(PATH, 'rb') as lnk: - arr = bytearray(lnk.read()) - -arr[0x15] = arr[0x15] | 0x20 # Set the 6th bit of 21st byte to 1 - -with open(PATH, 'wb') as lnk: - lnk.write(arr) - print(' ~ Enabled the "Run as Administrator" option') - -print('[~] Successfully created Auto Maple shortcut') +def create_desktop_shortcut(): + """Creates and saves a desktop shortcut using absolute paths""" + print('\n[~] Creating a desktop shortcut for Auto Maple:') + CWD = os.getcwd() + TARGET = os.path.join(os.environ['WINDIR'], 'System32', 'cmd.exe') + PATH = os.path.join(os.environ['USERPROFILE'], 'Desktop', 'Auto Maple.lnk') + flag = "/c" + if args.stay: + flag = "/k" + print(" - Leaving command prompt open after program finishes") + + shell = client.Dispatch('WScript.Shell') + shortcut = shell.CreateShortCut(PATH) + shortcut.Targetpath = TARGET + shortcut.Arguments = flag + f' \"cd {CWD} & python main.py\"' + shortcut.IconLocation = os.path.join(CWD, 'assets', 'icon.ico') + shortcut.save() + + # Enable "run as administrator" + with open(PATH, 'rb') as lnk: + arr = bytearray(lnk.read()) + + arr[0x15] = arr[0x15] | 0x20 # Set the 6th bit of 21st byte to 1 + + with open(PATH, 'wb') as lnk: + lnk.write(arr) + print(' - Enabled the "Run as Administrator" option') + print(' ~ Successfully created Auto Maple shortcut') + + +def update_submodules(): + print('\n[~] Updating submodules:') + repo = git.Repo() + output = repo.git.submodule('update', '--init', '--recursive') + changed = False + for line in output.split('\n'): + if line: + print(f' - {line}') + changed = True + if changed: + print(' ~ Finished updating submodules') + else: + print(' ~ No changes found in submodules') + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--stay', action='store_true') + args = parser.parse_args() + + create_desktop_shortcut() + update_submodules() diff --git a/config.py b/src/common/config.py similarity index 89% rename from config.py rename to src/common/config.py index a21bddef..45487299 100644 --- a/config.py +++ b/src/common/config.py @@ -1,6 +1,12 @@ """A collection of variables shared across multiple modules.""" +######################### +# Constants # +######################### +RESOURCES_DIR = 'resources' + + ################################# # Global Variables # ################################# diff --git a/src/common/interfaces.py b/src/common/interfaces.py new file mode 100644 index 00000000..ba833aa9 --- /dev/null +++ b/src/common/interfaces.py @@ -0,0 +1,32 @@ +import os +import pickle + +SETTINGS_DIR = '.settings' + + +class Configurable: + TARGET = 'default_configurable' + DEFAULT_CONFIG = { + 'Default configuration': 'None' + } + + def __init__(self, target): + self.TARGET = target + self.config = self.DEFAULT_CONFIG.copy() + self.load_config() + + def load_config(self): + path = os.path.join(SETTINGS_DIR, self.TARGET) + if os.path.isfile(path): + with open(path, 'rb') as file: + self.config = pickle.load(file) + else: + self.save_config() + + def save_config(self): + path = os.path.join(SETTINGS_DIR, self.TARGET) + directory = os.path.dirname(path) + if not os.path.exists(directory): + os.makedirs(directory) + with open(path, 'wb') as file: + pickle.dump(self.config, file) diff --git a/settings.py b/src/common/settings.py similarity index 99% rename from settings.py rename to src/common/settings.py index 7d496306..614363b2 100644 --- a/settings.py +++ b/src/common/settings.py @@ -95,3 +95,5 @@ def reset(): # The amount of time (in seconds) to wait between each call to the 'buff' command buff_cooldown = 180 + +reset() diff --git a/utils.py b/src/common/utils.py similarity index 99% rename from utils.py rename to src/common/utils.py index 527bd753..71d9957a 100644 --- a/utils.py +++ b/src/common/utils.py @@ -1,12 +1,11 @@ """A collection of functions and classes used across multiple modules.""" -import config -import settings import math import queue import cv2 import threading import numpy as np +from src.common import config, settings from random import random diff --git a/vkeys.py b/src/common/vkeys.py similarity index 99% rename from vkeys.py rename to src/common/vkeys.py index b6fa51c7..33fccc2d 100644 --- a/vkeys.py +++ b/src/common/vkeys.py @@ -2,9 +2,9 @@ import ctypes import time -import utils import win32con import win32api +from src.common import utils from ctypes import wintypes from random import random diff --git a/detection.py b/src/detection/detection.py similarity index 98% rename from detection.py rename to src/detection/detection.py index 96a11f82..623b35d5 100644 --- a/detection.py +++ b/src/detection/detection.py @@ -1,9 +1,9 @@ """A module for classifying directional arrows using TensorFlow.""" -import utils import cv2 import tensorflow as tf import numpy as np +from src.common import utils ######################### @@ -178,7 +178,7 @@ def merge_detection(model, image): # Script for testing the detection module by itself if __name__ == '__main__': - import config + from src.common import config, utils import mss config.enabled = True monitor = {'top': 0, 'left': 0, 'width': 1366, 'height': 768} diff --git a/src/gui_components/__init__.py b/src/gui_components/__init__.py new file mode 100644 index 00000000..417c0490 --- /dev/null +++ b/src/gui_components/__init__.py @@ -0,0 +1,9 @@ +import src.gui_components.menu.main as menu +import src.gui_components.view.main as view +import src.gui_components.edit.main as edit +import src.gui_components.settings.main as settings + +Menu = menu.Menu +View = view.View +Edit = edit.Edit +Settings = settings.Settings diff --git a/gui_components/edit/commands.py b/src/gui_components/edit/commands.py similarity index 96% rename from gui_components/edit/commands.py rename to src/gui_components/edit/commands.py index 1f241bbf..f4310f3c 100644 --- a/gui_components/edit/commands.py +++ b/src/gui_components/edit/commands.py @@ -1,8 +1,8 @@ import tkinter as tk -import config -from components import Point -from gui_components.interfaces import Frame +from src.common import config +from src.routine.components import Point +from src.gui_components.interfaces import Frame class Commands(Frame): diff --git a/gui_components/edit/components.py b/src/gui_components/edit/components.py similarity index 95% rename from gui_components/edit/components.py rename to src/gui_components/edit/components.py index 5196422b..6537f56e 100644 --- a/gui_components/edit/components.py +++ b/src/gui_components/edit/components.py @@ -1,8 +1,7 @@ import tkinter as tk - -import config -from components import Point -from gui_components.interfaces import Frame +from src.common import config +from src.routine.components import Point +from src.gui_components.interfaces import Frame class Components(Frame): diff --git a/gui_components/edit/controls.py b/src/gui_components/edit/controls.py similarity index 97% rename from gui_components/edit/controls.py rename to src/gui_components/edit/controls.py index c4ba47c7..ad72af6b 100644 --- a/gui_components/edit/controls.py +++ b/src/gui_components/edit/controls.py @@ -1,7 +1,6 @@ import tkinter as tk - -import config -from gui_components.interfaces import Frame +from src.common import config +from src.gui_components.interfaces import Frame class Controls(Frame): diff --git a/gui_components/edit/main.py b/src/gui_components/edit/main.py similarity index 97% rename from gui_components/edit/main.py rename to src/gui_components/edit/main.py index 5036a929..c83684d1 100644 --- a/gui_components/edit/main.py +++ b/src/gui_components/edit/main.py @@ -1,14 +1,14 @@ """Allows the user to edit routines while viewing each Point's location on the minimap.""" -import config +from src.common import config import inspect import tkinter as tk -from components import Point, Command -from gui_components.edit.minimap import Minimap -from gui_components.edit.record import Record -from gui_components.edit.routine import Routine -from gui_components.edit.status import Status -from gui_components.interfaces import Tab, Frame, LabelFrame +from src.routine.components import Point, Command +from src.gui_components.edit.minimap import Minimap +from src.gui_components.edit.record import Record +from src.gui_components.edit.routine import Routine +from src.gui_components.edit.status import Status +from src.gui_components.interfaces import Tab, Frame, LabelFrame class Edit(Tab): diff --git a/gui_components/edit/minimap.py b/src/gui_components/edit/minimap.py similarity index 95% rename from gui_components/edit/minimap.py rename to src/gui_components/edit/minimap.py index 07522b05..cb8a4e65 100644 --- a/gui_components/edit/minimap.py +++ b/src/gui_components/edit/minimap.py @@ -1,12 +1,9 @@ import tkinter as tk - import cv2 from PIL import ImageTk, Image - -import config -import utils -from components import Point -from gui_components.interfaces import LabelFrame +from src.common import config, utils +from src.routine.components import Point +from src.gui_components.interfaces import LabelFrame class Minimap(LabelFrame): diff --git a/gui_components/edit/record.py b/src/gui_components/edit/record.py similarity index 95% rename from gui_components/edit/record.py rename to src/gui_components/edit/record.py index a7b81dc9..a6b803c3 100644 --- a/gui_components/edit/record.py +++ b/src/gui_components/edit/record.py @@ -1,7 +1,6 @@ import tkinter as tk - -from components import Point -from gui_components.interfaces import LabelFrame +from src.routine.components import Point +from src.gui_components.interfaces import LabelFrame class Record(LabelFrame): diff --git a/gui_components/edit/routine.py b/src/gui_components/edit/routine.py similarity index 76% rename from gui_components/edit/routine.py rename to src/gui_components/edit/routine.py index fe03f1b1..c2f99ca7 100644 --- a/gui_components/edit/routine.py +++ b/src/gui_components/edit/routine.py @@ -1,9 +1,8 @@ import tkinter as tk - -from gui_components.edit.commands import Commands -from gui_components.edit.components import Components -from gui_components.edit.controls import Controls -from gui_components.interfaces import LabelFrame, Frame +from src.gui_components.edit.commands import Commands +from src.gui_components.edit.components import Components +from src.gui_components.edit.controls import Controls +from src.gui_components.interfaces import LabelFrame, Frame class Routine(LabelFrame): diff --git a/gui_components/edit/status.py b/src/gui_components/edit/status.py similarity index 87% rename from gui_components/edit/status.py rename to src/gui_components/edit/status.py index 2871c3fa..23e7ac85 100644 --- a/gui_components/edit/status.py +++ b/src/gui_components/edit/status.py @@ -1,7 +1,6 @@ import tkinter as tk - -import config -from gui_components.interfaces import LabelFrame +from src.common import config +from src.gui_components.interfaces import LabelFrame class Status(LabelFrame): diff --git a/gui_components/interfaces.py b/src/gui_components/interfaces.py similarity index 100% rename from gui_components/interfaces.py rename to src/gui_components/interfaces.py diff --git a/gui_components/menu/main.py b/src/gui_components/menu/main.py similarity index 87% rename from gui_components/menu/main.py rename to src/gui_components/menu/main.py index 414a6db2..692364c6 100644 --- a/gui_components/menu/main.py +++ b/src/gui_components/menu/main.py @@ -1,12 +1,11 @@ """A menu for loading routines and command books.""" -import config -import utils +import os import queue import tkinter as tk +from src.common import config, utils from tkinter.filedialog import askopenfilename, asksaveasfilename from tkinter.messagebox import askyesno -from bot import Bot class Menu(tk.Menu): @@ -26,7 +25,7 @@ def __init__(self, parent, **kwargs): self.add_cascade(label='File', menu=self.file) @staticmethod - @utils.run_if_disabled('\n[!] Cannot create a new routine while Auto Maple is enabled.') + @utils.run_if_disabled('\n[!] Cannot create a new routine while Auto Maple is enabled') def _new_routine(): if config.routine.dirty: if not askyesno(title='New Routine', @@ -38,9 +37,9 @@ def _new_routine(): config.routine.clear() @staticmethod - @utils.run_if_disabled('\n[!] Cannot save routines while Auto Maple is enabled.') + @utils.run_if_disabled('\n[!] Cannot save routines while Auto Maple is enabled') def _save_routine(): - file_path = asksaveasfilename(initialdir='./routines/', + file_path = asksaveasfilename(initialdir=os.path.join(config.RESOURCES_DIR, 'routines'), title='Save routine', filetypes=[('*.csv', '*.csv')], defaultextension='*.csv') @@ -48,7 +47,7 @@ def _save_routine(): config.routine.save(file_path) @staticmethod - @utils.run_if_disabled('\n[!] Cannot load routines while Auto Maple is enabled.') + @utils.run_if_disabled('\n[!] Cannot load routines while Auto Maple is enabled') def _load_routine(): if config.routine.dirty: if not askyesno(title='Load Routine', @@ -57,14 +56,14 @@ def _load_routine(): icon='warning'): return - file_path = askopenfilename(initialdir='./routines/', + file_path = askopenfilename(initialdir=os.path.join(config.RESOURCES_DIR, 'routines'), title='Select a routine', filetypes=[('*.csv', '*.csv')]) if file_path: config.routine.load(file_path) @staticmethod - @utils.run_if_disabled('\n[!] Cannot load command books while Auto Maple is enabled.') + @utils.run_if_disabled('\n[!] Cannot load command books while Auto Maple is enabled') def _load_commands(): if config.routine.dirty: if not askyesno(title='Load Command Book', @@ -73,7 +72,7 @@ def _load_commands(): icon='warning'): return - file_path = askopenfilename(initialdir='./command_books/', + file_path = askopenfilename(initialdir=os.path.join(config.RESOURCES_DIR, 'command_books'), title='Select a command book', filetypes=[('*.py', '*.py')]) if file_path: diff --git a/gui_components/settings/main.py b/src/gui_components/settings/keybindings.py similarity index 75% rename from gui_components/settings/main.py rename to src/gui_components/settings/keybindings.py index ba61adf8..e3039e57 100644 --- a/gui_components/settings/main.py +++ b/src/gui_components/settings/keybindings.py @@ -1,26 +1,15 @@ -"""Displays Auto Maple's current settings and allows the user to edit them.""" - -import config -import utils import tkinter as tk import keyboard as kb -from gui_components.interfaces import Tab, Frame, LabelFrame - - -class Settings(Tab): - def __init__(self, parent, **kwargs): - super().__init__(parent, 'Settings', **kwargs) - - self.columnconfigure(0, weight=1) - self.columnconfigure(2, weight=1) - - self.keybindings = KeyBindings(self) - self.keybindings.grid(row=0, column=1, sticky=tk.NSEW) +from src.gui_components.interfaces import LabelFrame, Frame +from src.common import utils +from src.common.interfaces import Configurable class KeyBindings(LabelFrame): - def __init__(self, parent, **kwargs): - super().__init__(parent, 'Key Bindings', **kwargs) + def __init__(self, parent, label, target, **kwargs): + super().__init__(parent, label, **kwargs) + assert isinstance(target, Configurable) + self.target = target self.columnconfigure(0, minsize=300) @@ -43,8 +32,8 @@ def create_edit_ui(self): self.contents = Frame(self) self.contents.grid(row=0, column=0, sticky=tk.NSEW, padx=5, pady=5) - if config.listener is not None: # For when running GUI only - for action, key in config.listener.key_binds.items(): + if self.target is not None: # For when running GUI only + for action, key in self.target.config.items(): self.forward[action] = key self.backward[key] = action self.create_entry(action, key) @@ -62,30 +51,29 @@ def refresh_edit_ui(self): self.contents.destroy() self.create_edit_ui() - @utils.run_if_disabled('\n[!] Cannot save key bindings while Auto Maple is enabled.') + @utils.run_if_disabled('\n[!] Cannot save key bindings while Auto Maple is enabled') def save(self): utils.print_separator() - print('[~] Saving key bindings...') + print(f"[~] Saving key bindings to '{self.target.TARGET}':") failures = 0 for action, key in self.forward.items(): if key != '': - config.listener.key_binds[action] = key + self.target.config[action] = key else: - print(f" ! Action '{action}' was not bound to a key.") + print(f" ! Action '{action}' was not bound to a key") failures += 1 - config.listener.save_keybindings() + self.target.save_config() if failures == 0: - print('[~] Successfully saved all key bindings.') + print(' ~ Successfully saved all key bindings') else: - print(f'[~] Successfully saved all except for {failures} key bindings.') + print(f' ~ Successfully saved all except for {failures} key bindings') self.create_edit_ui() def create_entry(self, action, key): """ - Creates an input row for a single key bind. KEY is the name of its action - while VALUE is its currently assigned key. + Creates an input row for a single key bind. ACTION is assigned to KEY. """ display_var = tk.StringVar(value=key) diff --git a/src/gui_components/settings/main.py b/src/gui_components/settings/main.py new file mode 100644 index 00000000..d014e147 --- /dev/null +++ b/src/gui_components/settings/main.py @@ -0,0 +1,27 @@ +"""Displays Auto Maple's current settings and allows the user to edit them.""" + +import tkinter as tk +from src.gui_components.settings.keybindings import KeyBindings +from src.gui_components.settings.pets import Pets +from src.gui_components.interfaces import Tab, Frame +from src.common import config + + +class Settings(Tab): + def __init__(self, parent, **kwargs): + super().__init__(parent, 'Settings', **kwargs) + + self.columnconfigure(0, weight=1) + self.columnconfigure(3, weight=1) + + column1 = Frame(self) + column1.grid(row=0, column=1, sticky=tk.N, padx=10, pady=10) + self.controls = KeyBindings(column1, 'Auto Maple Controls', config.listener) + self.controls.pack(side=tk.TOP, fill='x', expand=True) + + column2 = Frame(self) + column2.grid(row=0, column=2, sticky=tk.N, padx=10, pady=10) + self.key_bindings = KeyBindings(column2, 'In-game Keybindings', config.bot) + self.key_bindings.pack(side=tk.TOP, fill='x', expand=True) + self.pets = Pets(column2) + self.pets.pack(side=tk.TOP, fill='x', expand=True, pady=(10, 0)) diff --git a/src/gui_components/settings/pets.py b/src/gui_components/settings/pets.py new file mode 100644 index 00000000..d9a3c276 --- /dev/null +++ b/src/gui_components/settings/pets.py @@ -0,0 +1,57 @@ +import tkinter as tk +from src.gui_components.interfaces import LabelFrame, Frame +from src.common.interfaces import Configurable + + +class Pets(LabelFrame): + def __init__(self, parent, **kwargs): + super().__init__(parent, 'Pets', **kwargs) + + self.pet_settings = PetSettings('pets') + self.auto_feed = tk.BooleanVar(value=self.pet_settings.get('Auto-feed')) + self.num_pets = tk.IntVar(value=self.pet_settings.get('Num pets')) + + feed_row = Frame(self) + feed_row.pack(side=tk.TOP, fill='x', expand=True, pady=5, padx=5) + check = tk.Checkbutton( + feed_row, + variable=self.auto_feed, + text='Auto-feed', + command=self._on_change + ) + check.pack() + + num_row = Frame(self) + num_row.pack(side=tk.TOP, fill='x', expand=True, pady=(0, 5), padx=5) + label = tk.Label(num_row, text='Number of pets to feed:') + label.pack(side=tk.LEFT, padx=(0, 15)) + radio_group = Frame(num_row) + radio_group.pack(side=tk.LEFT) + for i in range(1, 4): + radio = tk.Radiobutton( + radio_group, + text=str(i), + variable=self.num_pets, + value=i, + command=self._on_change + ) + radio.pack(side=tk.LEFT, padx=(0, 10)) + + def _on_change(self): + self.pet_settings.set('Auto-feed', self.auto_feed.get()) + self.pet_settings.set('Num pets', self.num_pets.get()) + self.pet_settings.save_config() + + +class PetSettings(Configurable): + DEFAULT_CONFIG = { + 'Auto-feed': False, + 'Num pets': 1 + } + + def get(self, key): + return self.config[key] + + def set(self, key, value): + assert key in self.config + self.config[key] = value diff --git a/src/gui_components/view/details.py b/src/gui_components/view/details.py new file mode 100644 index 00000000..a9cdccfc --- /dev/null +++ b/src/gui_components/view/details.py @@ -0,0 +1,60 @@ +import tkinter as tk +from src.gui_components.interfaces import LabelFrame +from src.common import config + + +class Details(LabelFrame): + def __init__(self, parent, **kwargs): + super().__init__(parent, 'Details', **kwargs) + self.name_var = tk.StringVar() + + self.name = tk.Entry(self, textvariable=self.name_var, justify=tk.CENTER, state=tk.DISABLED) + self.name.pack(pady=(5, 2)) + + self.scroll = tk.Scrollbar(self) + self.scroll.pack(side=tk.RIGHT, fill=tk.Y, pady=5) + + self.text = tk.Text(self, width=1, height=10, + yscrollcommand=self.scroll.set, + state=tk.DISABLED, wrap=tk.WORD) + self.text.pack(side=tk.LEFT, expand=True, fill='both', padx=(5, 0), pady=(0, 5)) + + self.scroll.config(command=self.text.yview) + + def show_details(self, e): + """Callback for updating the Details section everytime Listbox selection changes.""" + + selections = e.widget.curselection() + if len(selections) > 0: + index = int(selections[0]) + self.display_info(index) + + def update_details(self): + """Updates Details to show info about the current selection.""" + + selects = self.parent.routine.listbox.curselection() + if len(selects) > 0: + self.display_info(int(selects[0])) + else: + self.clear_info() + + def display_info(self, index): + """Updates the Details section to show info about the Component at position INDEX.""" + + self.text.config(state=tk.NORMAL) + + info = config.routine[index].info() + self.name_var.set(info['name']) + arr = [] + for key, value in info['vars'].items(): + arr.append(f'{key}: {value}') + self.text.delete(1.0, 'end') + self.text.insert(1.0, '\n'.join(arr)) + + self.text.config(state=tk.DISABLED) + + def clear_info(self): + self.name_var.set('') + self.text.config(state=tk.NORMAL) + self.text.delete(1.0, 'end') + self.text.config(state=tk.DISABLED) diff --git a/src/gui_components/view/main.py b/src/gui_components/view/main.py new file mode 100644 index 00000000..d96d0485 --- /dev/null +++ b/src/gui_components/view/main.py @@ -0,0 +1,28 @@ +"""Displays the current minimap as well as various information regarding the current routine.""" + +import tkinter as tk +from src.gui_components.view.details import Details +from src.gui_components.view.minimap import Minimap +from src.gui_components.view.routine import Routine +from src.gui_components.view.status import Status +from src.gui_components.interfaces import Tab + + +class View(Tab): + def __init__(self, parent, **kwargs): + super().__init__(parent, 'View', **kwargs) + + self.grid_columnconfigure(0, weight=1) + self.grid_columnconfigure(3, weight=1) + + self.minimap = Minimap(self) + self.minimap.grid(row=0, column=2, sticky=tk.NSEW, padx=10, pady=10) + + self.status = Status(self) + self.status.grid(row=1, column=2, sticky=tk.NSEW, padx=10, pady=10) + + self.details = Details(self) + self.details.grid(row=2, column=2, sticky=tk.NSEW, padx=10, pady=10) + + self.routine = Routine(self) + self.routine.grid(row=0, column=1, rowspan=3, sticky=tk.NSEW, padx=10, pady=10) diff --git a/src/gui_components/view/minimap.py b/src/gui_components/view/minimap.py new file mode 100644 index 00000000..9b55557c --- /dev/null +++ b/src/gui_components/view/minimap.py @@ -0,0 +1,82 @@ +import cv2 +import tkinter as tk +from PIL import ImageTk, Image +from src.gui_components.interfaces import LabelFrame +from src.common import config, utils +from src.routine.components import Point + + +class Minimap(LabelFrame): + def __init__(self, parent, **kwargs): + super().__init__(parent, 'Minimap', **kwargs) + + self.WIDTH = 400 + self.HEIGHT = 300 + self.canvas = tk.Canvas(self, bg='black', + width=self.WIDTH, height=self.HEIGHT, + borderwidth=0, highlightthickness=0) + self.canvas.pack(expand=True, fill='both', padx=5, pady=5) + self.container = None + + def display_minimap(self): + """Updates the Main page with the current minimap.""" + + minimap = config.capture.minimap + if minimap: + rune_active = minimap['rune_active'] + rune_pos = minimap['rune_pos'] + path = minimap['path'] + player_pos = minimap['player_pos'] + + img = cv2.cvtColor(minimap['minimap'], cv2.COLOR_BGR2RGB) + height, width, _ = img.shape + + # Resize minimap to fit the Canvas + ratio = min(self.WIDTH / width, self.HEIGHT / height) + new_width = int(width * ratio) + new_height = int(height * ratio) + if new_height * new_width > 0: + img = cv2.resize(img, (new_width, new_height), interpolation=cv2.INTER_AREA) + + # Mark the position of the active rune + if rune_active: + cv2.circle(img, + utils.convert_to_absolute(rune_pos, img), + 3, + (128, 0, 128), + -1) + + # Draw the current path that the program is taking + if config.enabled and len(path) > 1: + for i in range(len(path) - 1): + start = utils.convert_to_absolute(path[i], img) + end = utils.convert_to_absolute(path[i + 1], img) + cv2.line(img, start, end, (0, 255, 255), 1) + + # Draw each Point in the routine as a circle + for p in config.routine.sequence: + if isinstance(p, Point): + utils.draw_location(img, + p.location, + (0, 255, 0) if config.enabled else (255, 0, 0)) + + # Display the current Layout + if config.layout: + config.layout.draw(img) + + # Draw the player's position on top of everything + cv2.circle(img, + utils.convert_to_absolute(player_pos, img), + 3, + (0, 0, 255), + -1) + + # Display the minimap in the Canvas + img = ImageTk.PhotoImage(Image.fromarray(img)) + if self.container is None: + self.container = self.canvas.create_image(self.WIDTH // 2, + self.HEIGHT // 2, + image=img, anchor=tk.CENTER) + else: + self.canvas.itemconfig(self.container, image=img) + self._img = img # Prevent garbage collection diff --git a/src/gui_components/view/routine.py b/src/gui_components/view/routine.py new file mode 100644 index 00000000..3c54a6f4 --- /dev/null +++ b/src/gui_components/view/routine.py @@ -0,0 +1,30 @@ +import tkinter as tk +from src.gui_components.interfaces import LabelFrame +from src.common import config + + +class Routine(LabelFrame): + def __init__(self, parent, **kwargs): + super().__init__(parent, 'Routine', **kwargs) + + self.scroll = tk.Scrollbar(self) + self.scroll.pack(side=tk.RIGHT, fill='both', pady=5) + + self.listbox = tk.Listbox(self, width=25, + listvariable=config.gui.routine_var, + exportselection=False, + activestyle='none', + yscrollcommand=self.scroll.set) + self.listbox.bind('', lambda e: 'break') + self.listbox.bind('', lambda e: 'break') + self.listbox.bind('', lambda e: 'break') + self.listbox.bind('', lambda e: 'break') + self.listbox.bind('<>', parent.details.show_details) + self.listbox.pack(side=tk.LEFT, expand=True, fill='both', padx=(5, 0), pady=5) + + self.scroll.config(command=self.listbox.yview) + + def select(self, i): + self.listbox.selection_clear(0, 'end') + self.listbox.selection_set(i) + self.listbox.see(i) diff --git a/src/gui_components/view/status.py b/src/gui_components/view/status.py new file mode 100644 index 00000000..30b22f8e --- /dev/null +++ b/src/gui_components/view/status.py @@ -0,0 +1,29 @@ +import tkinter as tk +from src.gui_components.interfaces import LabelFrame + + +class Status(LabelFrame): + def __init__(self, parent, **kwargs): + super().__init__(parent, 'Status', **kwargs) + + self.grid_columnconfigure(0, weight=1) + self.grid_columnconfigure(3, weight=1) + + self.curr_cb = tk.StringVar() + self.curr_routine = tk.StringVar() + + self.cb_label = tk.Label(self, text='Command Book:') + self.cb_label.grid(row=0, column=1, padx=5, pady=(5, 0), sticky=tk.E) + self.cb_entry = tk.Entry(self, textvariable=self.curr_cb, state=tk.DISABLED) + self.cb_entry.grid(row=0, column=2, padx=(0, 5), pady=(5, 0), sticky=tk.EW) + + self.r_label = tk.Label(self, text='Routine:') + self.r_label.grid(row=1, column=1, padx=5, pady=(0, 5), sticky=tk.E) + self.r_entry = tk.Entry(self, textvariable=self.curr_routine, state=tk.DISABLED) + self.r_entry.grid(row=1, column=2, padx=(0, 5), pady=(0, 5), sticky=tk.EW) + + def set_cb(self, string): + self.curr_cb.set(string) + + def set_routine(self, string): + self.curr_routine.set(string) diff --git a/bot.py b/src/modules/bot.py similarity index 78% rename from bot.py rename to src/modules/bot.py index 042674c2..e1087e01 100644 --- a/bot.py +++ b/src/modules/bot.py @@ -1,31 +1,35 @@ """An interpreter that reads and executes user-created routines.""" -import config -import detection import threading import time import cv2 -import utils import inspect -import components -import numpy as np -from PIL import ImageGrab from os.path import splitext, basename -from routine import Routine -from components import Point -from vkeys import press, click +from src.common import config, utils +from src.detection import detection +from src.routine import components +from src.routine.routine import Routine +from src.routine.components import Point +from src.common.vkeys import press, click +from src.common.interfaces import Configurable # The rune's buff icon RUNE_BUFF_TEMPLATE = cv2.imread('assets/rune_buff_template.jpg', 0) -class Bot: +class Bot(Configurable): """A class that interprets and executes user-defined routines.""" + DEFAULT_CONFIG = { + 'Interact': 'y', + 'Feed pet': '9' + } + def __init__(self): """Loads a user-defined routine on start up and initializes this Bot's main thread.""" + super().__init__('keybindings') config.bot = self self.rune_active = False @@ -50,7 +54,7 @@ def start(self): :return: None """ - print('\n[~] Started main bot loop.') + print('\n[~] Started main bot loop') self.thread.start() def _main(self): @@ -59,17 +63,24 @@ def _main(self): :return: None """ - print('\n[~] Initializing detection algorithm...\n') + print('\n[~] Initializing detection algorithm:\n') model = detection.load_model() - print('\n[~] Initialized detection algorithm.') + print('\n[~] Initialized detection algorithm') - # mss.windows.CAPTUREBLT = 0 - # with mss.mss() as sct: self.ready = True config.listener.enabled = True + last_fed = time.time() while True: if config.enabled and len(config.routine) > 0: + # Buff and feed pets self.buff.main() + pet_settings = config.gui.settings.pets + auto_feed = pet_settings.auto_feed.get() + num_pets = pet_settings.num_pets.get() + now = time.time() + if auto_feed and now - last_fed > 1200 / num_pets: + press(self.config['Feed pet'], 1) + last_fed = now # Highlight the current Point config.gui.view.routine.select(config.routine.index) @@ -99,30 +110,36 @@ def _solve_rune(self, model): adjust = self.command_book['adjust'] adjust(*self.rune_pos).execute() time.sleep(0.2) - press('y', 1, down_time=0.2) # Press 'y' to interact with rune in-game + press(self.config['Interact'], 1, down_time=0.2) # Inherited from Configurable print('\nSolving rune:') inferences = [] for _ in range(15): - frame = np.array(ImageGrab.grab(config.capture.window)) + frame = config.capture.screenshot() + if frame is None: + continue frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR) solution = detection.merge_detection(model, frame) if solution: print(', '.join(solution)) if solution in inferences: - print('Solution found, entering result.') + print('Solution found, entering result') for arrow in solution: press(arrow, 1, down_time=0.1) time.sleep(1) for _ in range(3): time.sleep(0.3) - frame = np.array(ImageGrab.grab(config.capture.window)) + frame = config.capture.screenshot() + if frame is None: + continue frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR) - rune_buff = utils.multi_match(frame[:frame.shape[0]//8, :], + rune_buff = utils.multi_match(frame[:frame.shape[0] // 8, :], RUNE_BUFF_TEMPLATE, threshold=0.9) if rune_buff: rune_buff_pos = min(rune_buff, key=lambda p: p[0]) - click(rune_buff_pos, button='right') + target = tuple(round(rune_buff_pos[i] + config.capture.window[i]) + for i in range(2)) + click(target, button='right') break elif len(solution) == 4: inferences.append(solution) @@ -146,7 +163,7 @@ def load_commands(self, file): # Import the desired command book file module_name = splitext(basename(file))[0] - module = __import__(f'command_books.{module_name}', fromlist=['']) + module = __import__(f'resources.command_books.{module_name}', fromlist=['']) # Check if the 'step' function has been implemented step_found = False diff --git a/capture.py b/src/modules/capture.py similarity index 86% rename from capture.py rename to src/modules/capture.py index 913f3185..21cf4016 100644 --- a/capture.py +++ b/src/modules/capture.py @@ -1,12 +1,11 @@ """A module for tracking useful in-game information.""" -import config -import utils import time import cv2 import threading import ctypes import numpy as np +from src.common import config, utils from ctypes import wintypes from PIL import ImageGrab user32 = ctypes.windll.user32 @@ -52,6 +51,7 @@ def __init__(self): self.minimap_ratio = 1 self.minimap_sample = None self.window = (0, 0, 1366, 768) + self.scale = 1.0 self.ready = False self.calibrated = False @@ -61,7 +61,7 @@ def __init__(self): def start(self): """Starts this Capture's thread.""" - print('\n[~] Started video capture.') + print('\n[~] Started video capture') self.thread.start() def _main(self): @@ -76,6 +76,7 @@ def _main(self): rect = tuple(max(0, x) for x in rect) # Preliminary window to template match minimap + self.scale = ctypes.windll.shcore.GetScaleFactorForDevice(0) / 100 self.window = ( rect[0], rect[1], @@ -84,7 +85,9 @@ def _main(self): ) # Calibrate by finding the bottom right corner of the minimap - self.frame = np.array(ImageGrab.grab(self.window)) + self.frame = self.screenshot() + if self.frame is None: + continue self.frame = cv2.cvtColor(self.frame, cv2.COLOR_RGB2BGR) tl, _ = utils.single_match(self.frame, MM_TL_TEMPLATE) _, br = utils.single_match(self.frame, MM_BR_TEMPLATE) @@ -109,7 +112,9 @@ def _main(self): self.calibrated = True # Take screenshot - self.frame = np.array(ImageGrab.grab(self.window)) + self.frame = self.screenshot() + if self.frame is None: + continue self.frame = cv2.cvtColor(self.frame, cv2.COLOR_RGB2BGR) # Crop the frame to only show the minimap @@ -132,3 +137,11 @@ def _main(self): if not self.ready: self.ready = True time.sleep(0.001) + + def screenshot(self, delay=1): + try: + return np.array(ImageGrab.grab(self.window)) + except OSError: + print(f'\n[!] Error while taking screenshot, retrying in {delay} second' + + ('s' if delay != 1 else '')) + time.sleep(delay) diff --git a/gui.py b/src/modules/gui.py similarity index 75% rename from gui.py rename to src/modules/gui.py index 78ff30a4..90e7200f 100644 --- a/gui.py +++ b/src/modules/gui.py @@ -1,13 +1,15 @@ """User friendly GUI to interact with Auto Maple.""" -import config +import time +import threading import tkinter as tk from tkinter import ttk -import settings -from gui_components import Menu, View, Edit, Settings +from src.common import config, settings +from src.gui_components import Menu, View, Edit, Settings class GUI: + DISPLAY_FRAME_RATE = 30 RESOLUTIONS = { 'DEFAULT': '800x800', 'Edit': '1400x800' @@ -72,16 +74,29 @@ def _resize_window(self, e): def start(self): """Starts the GUI as well as any scheduled functions.""" - self.view.minimap.display_minimap() - self._save_layout() + display_thread = threading.Thread(target=self._display_minimap) + display_thread.daemon = True + display_thread.start() + + layout_thread = threading.Thread(target=self._save_layout) + layout_thread.daemon = True + layout_thread.start() + self.root.mainloop() + def _display_minimap(self): + delay = 1 / GUI.DISPLAY_FRAME_RATE + while True: + self.view.minimap.display_minimap() + time.sleep(delay) + def _save_layout(self): """Periodically saves the current Layout object.""" - if config.layout is not None and settings.record_layout: - config.layout.save() - self.root.after(5000, self._save_layout) + while True: + if config.layout is not None and settings.record_layout: + config.layout.save() + time.sleep(5) if __name__ == '__main__': diff --git a/listener.py b/src/modules/listener.py similarity index 74% rename from listener.py rename to src/modules/listener.py index af51b2b1..4e54c847 100644 --- a/listener.py +++ b/src/modules/listener.py @@ -1,19 +1,16 @@ """A keyboard listener to track user inputs.""" -import config import time -import utils import threading import winsound -import pickle import keyboard as kb -from os.path import isfile +from src.common.interfaces import Configurable +from src.common import config, utils from datetime import datetime -class Listener: - TARGET = '.keybinds' - DEFAULT_KEYBINDS = { +class Listener(Configurable): + DEFAULT_CONFIG = { 'Start/stop': 'insert', 'Reload routine': 'f6', 'Record position': 'f7' @@ -22,12 +19,10 @@ class Listener: def __init__(self): """Initializes this Listener object's main thread.""" + super().__init__('controls') config.listener = self self.enabled = False - self.key_binds = Listener.DEFAULT_KEYBINDS.copy() - self.load_keybindings() - self.ready = False self.thread = threading.Thread(target=self._main) self.thread.daemon = True @@ -38,7 +33,7 @@ def start(self): :return: None """ - print('\n[~] Started keyboard listener.') + print('\n[~] Started keyboard listener') self.thread.start() def _main(self): @@ -50,11 +45,11 @@ def _main(self): self.ready = True while True: if self.enabled: - if kb.is_pressed(self.key_binds['Start/stop']): + if kb.is_pressed(self.config['Start/stop']): Listener.toggle_enabled() - elif kb.is_pressed(self.key_binds['Reload routine']): + elif kb.is_pressed(self.config['Reload routine']): Listener.reload_routine() - elif kb.is_pressed(self.key_binds['Record position']): + elif kb.is_pressed(self.config['Record position']): Listener.record_position() time.sleep(0.01) @@ -99,16 +94,5 @@ def record_position(): pos = tuple('{:.3f}'.format(round(i, 3)) for i in config.player_pos) now = datetime.now().strftime('%I:%M:%S %p') config.gui.edit.record.add_entry(now, pos) - print(f'\n[~] Recorded position ({pos[0]}, {pos[1]}) at {now}.') + print(f'\n[~] Recorded position ({pos[0]}, {pos[1]}) at {now}') time.sleep(0.6) - - def load_keybindings(self): - if isfile(Listener.TARGET): - with open(Listener.TARGET, 'rb') as file: - self.key_binds = pickle.load(file) - else: - self.save_keybindings() - - def save_keybindings(self): - with open(Listener.TARGET, 'wb') as file: - pickle.dump(self.key_binds, file) diff --git a/notifier.py b/src/modules/notifier.py similarity index 75% rename from notifier.py rename to src/modules/notifier.py index 89e3389f..16a2e4e0 100644 --- a/notifier.py +++ b/src/modules/notifier.py @@ -1,14 +1,14 @@ """A module for detecting and notifying the user of dangerous in-game events.""" -import config -import utils +from src.common import config, utils import time +import os import cv2 import pygame import threading import numpy as np import keyboard as kb -from components import Point +from src.routine.components import Point # A rune's symbol on the minimap @@ -29,7 +29,13 @@ ELITE_TEMPLATE = cv2.imread('assets/elite_template.jpg', 0) +def get_alert_path(name): + return os.path.join(Notifier.ALERTS_DIR, f'{name}.mp3') + + class Notifier: + ALERTS_DIR = os.path.join('assets', 'alerts') + def __init__(self): """Initializes this Notifier object's main thread.""" @@ -40,15 +46,19 @@ def __init__(self): self.thread = threading.Thread(target=self._main) self.thread.daemon = True + self.room_change_threshold = 0.9 + self.rune_alert_delay = 270 # 4.5 minutes + def start(self): """Starts this Notifier's thread.""" - print('\n[~] Started notifier.') + print('\n[~] Started notifier') self.thread.start() def _main(self): self.ready = True prev_others = 0 + rune_start_time = time.time() while True: if config.enabled: frame = config.capture.frame @@ -57,14 +67,14 @@ def _main(self): # Check for unexpected black screen gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) - if np.count_nonzero(gray < 15) / height / width > 0.75: - self._alert() + if np.count_nonzero(gray < 15) / height / width > self.room_change_threshold: + self._alert('siren') # Check for elite warning elite_frame = frame[height // 4:3 * height // 4, width // 4:3 * width // 4] elite = utils.multi_match(elite_frame, ELITE_TEMPLATE, threshold=0.9) if len(elite) > 0: - self._alert() + self._alert('siren') # Check for other players entering the map filtered = utils.filter_color(minimap, OTHER_RANGES) @@ -72,13 +82,15 @@ def _main(self): config.stage_fright = others > 0 if others != prev_others: if others > prev_others: - self._ding() + self._ping('ding') prev_others = others # Check for rune + now = time.time() if not config.bot.rune_active: filtered = utils.filter_color(minimap, RUNE_RANGES) matches = utils.multi_match(filtered, RUNE_TEMPLATE, threshold=0.9) + rune_start_time = now if matches and config.routine.sequence: abs_rune_pos = (matches[0][0], matches[0][1]) config.bot.rune_pos = utils.convert_to_relative(abs_rune_pos, minimap) @@ -86,9 +98,13 @@ def _main(self): index = np.argmin(distances) config.bot.rune_closest_pos = config.routine[index].location config.bot.rune_active = True + self._ping('rune_appeared', volume=0.75) + elif now - rune_start_time > self.rune_alert_delay: # Alert if rune hasn't been solved + config.bot.rune_active = False + self._alert('siren') time.sleep(0.05) - def _alert(self): + def _alert(self, name, volume=0.75): """ Plays an alert to notify user of a dangerous event. Stops the alert once the key bound to 'Start/stop' is pressed. @@ -96,20 +112,20 @@ def _alert(self): config.enabled = False config.listener.enabled = False - self.mixer.load('./assets/alert.mp3') - self.mixer.set_volume(0.75) + self.mixer.load(get_alert_path(name)) + self.mixer.set_volume(volume) self.mixer.play(-1) - while not kb.is_pressed(config.listener.key_binds['Start/stop']): + while not kb.is_pressed(config.listener.config['Start/stop']): time.sleep(0.1) self.mixer.stop() time.sleep(2) config.listener.enabled = True - def _ding(self): - """A quick notification for when another player enters the map.""" + def _ping(self, name, volume=0.5): + """A quick notification for non-dangerous events.""" - self.mixer.load('./assets/ding.mp3') - self.mixer.set_volume(0.50) + self.mixer.load(get_alert_path(name)) + self.mixer.set_volume(volume) self.mixer.play() diff --git a/components.py b/src/routine/components.py similarity index 96% rename from components.py rename to src/routine/components.py index 23df68ff..4b4d3456 100644 --- a/components.py +++ b/src/routine/components.py @@ -1,11 +1,9 @@ """A collection of classes used to execute a Routine.""" -import config -import settings -import utils import math import time -from vkeys import key_down, key_up, press +from src.common import config, settings, utils +from src.common.vkeys import key_down, key_up, press ################################# @@ -17,9 +15,9 @@ class Component: def __init__(self, *args, **kwargs): if len(args) > 1: - raise TypeError('Component superclass __init__ only accepts 1 (optional) argument: LOCALS.') + raise TypeError('Component superclass __init__ only accepts 1 (optional) argument: LOCALS') if len(kwargs) != 0: - raise TypeError('Component superclass __init__ does not accept any keyword arguments.') + raise TypeError('Component superclass __init__ does not accept any keyword arguments') if len(args) == 0: self.kwargs = {} elif type(args[0]) != dict: @@ -263,6 +261,8 @@ def main(self): key = 'right' self._new_direction(key) step(key, point) + if settings.record_layout: + config.layout.add(*config.player_pos) counter -= 1 if i < len(path) - 1: time.sleep(0.15) @@ -275,6 +275,8 @@ def main(self): key = 'down' self._new_direction(key) step(key, point) + if settings.record_layout: + config.layout.add(*config.player_pos) counter -= 1 if i < len(path) - 1: time.sleep(0.05) diff --git a/layout.py b/src/routine/layout.py similarity index 98% rename from layout.py rename to src/routine/layout.py index 89359227..8c77a14f 100644 --- a/layout.py +++ b/src/routine/layout.py @@ -1,11 +1,10 @@ """A module for saving map layouts and determining shortest paths.""" -import config -import settings -import utils +import os import cv2 import math import pickle +from src.common import config, settings, utils from os.path import join, isfile, splitext, basename from heapq import heappush, heappop @@ -65,7 +64,7 @@ def __iter__(self): class Layout: """Uses a quadtree to represent possible player positions in a map layout.""" - LAYOUTS_DIR = 'layouts' + LAYOUTS_DIR = os.path.join(config.RESOURCES_DIR, 'layouts') TOLERANCE = settings.move_tolerance / 2 def __init__(self, name): diff --git a/routine.py b/src/routine/routine.py similarity index 97% rename from routine.py rename to src/routine/routine.py index 7413d088..9d330f81 100644 --- a/routine.py +++ b/src/routine/routine.py @@ -1,12 +1,10 @@ """A collection of classes used in the 'machine code' generated by Auto Maple's compiler for each routine.""" -import config -import utils +from src.common import config, settings, utils import csv -import settings from os.path import splitext, basename -from components import Point, Label, Jump, Setting, Command, SYMBOLS -from layout import Layout +from src.routine.components import Point, Label, Jump, Setting, Command, SYMBOLS +from src.routine.layout import Layout def update(func): @@ -205,9 +203,9 @@ def load(self, file=None): if not file: if self.path: file = self.path - print(' * File path not provided, using previously loaded routine.') + print(' * File path not provided, using previously loaded routine') else: - print('[!] File path not provided, no routine was previously loaded either.') + print('[!] File path not provided, no routine was previously loaded either') return False ext = splitext(file)[1]