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]