1
0
mirror of https://github.com/TheFunny/ArisuAutoSweeper synced 2025-12-16 19:55:12 +00:00

feat: mission/commissions/event

This commit is contained in:
RedDeadDepresso 2023-12-25 11:35:05 +00:00 committed by YoursFunny
parent 0d66a5c424
commit f1b6d4cd09
42 changed files with 2072 additions and 12 deletions

136
MCE/config.json Normal file
View File

@ -0,0 +1,136 @@
{
"ResetDaily": true,
"LastRun": "2023-12-24 21:41:55",
"ResetTime": "20:21:30",
"RechargeAP": true,
"PreferredTemplate": "commissions",
"Queue": [
[
"N",
"7-2",
17
],
[
"H",
"7-3",
6
],
[
"BD",
"01",
1
],
[
"N",
"7-2",
1
],
[
"IR",
"02",
1
],
[
"N",
"7-2",
1
],
[
"N",
"7-3",
1
]
],
"Event": false,
"Templates": {
"template1": [
[
"H",
"5-1",
3
],
[
"H",
"4-3",
3
],
[
"H",
"5-3",
3
],
[
"H",
"3-3",
3
],
[
"H",
"2-2",
3
],
[
"H",
"1-1",
3
],
[
"H",
"1-3",
3
],
[
"E",
"08",
30
],
[
"E",
"08",
30
],
[
"E",
"30",
30
]
],
"commissions": [
[
"N",
"7-2",
20
],
[
"H",
"7-3",
6
],
[
"BD",
"01",
1
],
[
"N",
"7-2",
1
],
[
"IR",
"02",
1
],
[
"N",
"7-2",
1
],
[
"N",
"7-3",
1
]
]
}
}

View File

View File

@ -0,0 +1,95 @@
import customtkinter
import re
from typing import Callable
class CTkIntegerSpinbox(customtkinter.CTkFrame):
def __init__(self, *args,
width: int = 100,
height: int = 32,
step_size: int = 1,
min_value: int = 0,
command: Callable = None,
**kwargs):
super().__init__(*args, width=width, height=height, **kwargs)
self.step_size = step_size
self.min_value = min_value
self.command = command
self.after_id = None
self.grid_columnconfigure((0, 2), weight=0)
self.grid_columnconfigure(1, weight=1)
self.subtract_button = customtkinter.CTkButton(self, text="-", width=height-6, height=height-6)
self.subtract_button.grid(row=0, column=0, padx=(3, 0), pady=3)
self.subtract_button.bind('<ButtonPress-1>', self.start_decrementing)
self.subtract_button.bind('<ButtonRelease-1>', self.stop_decrementing)
self.entry = customtkinter.CTkEntry(self, width=width-(2*height), height=height-6, border_width=0)
self.entry.grid(row=0, column=1, columnspan=1, padx=3, pady=3, sticky="ew")
self.add_button = customtkinter.CTkButton(self, text="+", width=height-6, height=height-6)
self.add_button.grid(row=0, column=2, padx=(0, 3), pady=3)
self.add_button.bind('<ButtonPress-1>', self.start_incrementing)
self.add_button.bind('<ButtonRelease-1>', self.stop_incrementing)
self.entry.insert(0, "0")
# Configure validatecommand to allow only integers
vcmd = (self.entry.register(self.validate_input), '%P')
self.entry.configure(validate='key', validatecommand=vcmd)
def start_incrementing(self, event):
self.increment()
self.after_id = self.after(150, self.start_incrementing, event)
def stop_incrementing(self, event):
if self.after_id:
self.after_cancel(self.after_id)
self.after_id = None
def start_decrementing(self, event):
self.decrement()
self.after_id = self.after(150, self.start_decrementing, event)
def stop_decrementing(self, event):
if self.after_id:
self.after_cancel(self.after_id)
self.after_id = None
def increment(self):
value = int(self.entry.get()) + self.step_size
self.entry.delete(0, "end")
self.entry.insert(0, max(self.min_value, value)) # Ensure the value is not less than 1
if self.command is not None:
self.command()
def decrement(self):
value = int(self.entry.get()) - self.step_size
self.entry.delete(0, "end")
self.entry.insert(0, max(self.min_value, value)) # Ensure the value is not less than 0
if self.command is not None:
self.command()
def validate_input(self, new_value):
# Validate that the input is a non-negative integer
return re.match(r'^\d*$', new_value) is not None
def get(self) -> int:
try:
return int(self.entry.get())
except ValueError:
return 0
def set(self, value: int):
self.entry.delete(0, "end")
self.entry.insert(0, max(self.min_value, value)) # Ensure the value is not less than 0
def configure(self, **kwargs):
state = kwargs.get("state", None)
if state is not None:
self.subtract_button.configure(state=state)
self.add_button.configure(state=state)
self.entry.configure(state=state)
kwargs.pop("state")
super().configure(**kwargs)

View File

@ -0,0 +1,27 @@
import customtkinter
class CTkNotification(customtkinter.CTkFrame):
def __init__(self, text, master, **kwargs):
self.master_color = master.cget("fg_color")
super().__init__(master=master, **kwargs, fg_color=self.master_color)
self.label = customtkinter.CTkLabel(self, text=text, text_color=self.master_color, width=200, wraplength=200, font=("Inter", 16))
self.label.grid(row=0, column=0, sticky="nsew")
self.close_button = customtkinter.CTkButton(
self, width=40, text="X", text_color_disabled=self.master_color, command=self.hide, fg_color="transparent", state="disabled")
self.close_button.grid(row=0, column=1)
self.progress_bar = customtkinter.CTkProgressBar(self, determinate_speed=0.4, fg_color=self.master_color, progress_color=self.master_color)
self.progress_bar.grid(row=1, column=0, columnspan=2, sticky="nsew")
def hide(self):
self.configure(fg_color="transparent")
self.progress_bar.stop()
self.progress_bar.configure(progress_color=self.master_color)
self.close_button.configure(state="disabled")
self.label.configure(text_color=self.master_color)
def show(self):
self.configure(fg_color="green")
self.progress_bar.configure(progress_color="white")
self.progress_bar.set(0)
self.progress_bar.start()
self.close_button.configure(state="normal")
self.label.configure(text_color="white")

View File

@ -0,0 +1,133 @@
from typing import Union, Tuple, Optional
from customtkinter import CTkLabel, CTkEntry, CTkButton, ThemeManager, CTkToplevel, CTkFont, CTkOptionMenu
class CTkTemplateDialog(CTkToplevel):
"""
Dialog with extra window, message, entry widget, cancel and ok button.
For detailed information check out the documentation.
"""
def __init__(self,
fg_color: Optional[Union[str, Tuple[str, str]]] = None,
text_color: Optional[Union[str, Tuple[str, str]]] = None,
button_fg_color: Optional[Union[str, Tuple[str, str]]] = None,
button_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
button_text_color: Optional[Union[str, Tuple[str, str]]] = None,
entry_fg_color: Optional[Union[str, Tuple[str, str]]] = None,
entry_border_color: Optional[Union[str, Tuple[str, str]]] = None,
entry_text_color: Optional[Union[str, Tuple[str, str]]] = None,
title: str = "CTkDialog",
font: Optional[Union[tuple, CTkFont]] = None,
text: str = "CTkDialog",
values = []):
super().__init__(fg_color=fg_color)
self._fg_color = ThemeManager.theme["CTkToplevel"]["fg_color"] if fg_color is None else self._check_color_type(fg_color)
self._text_color = ThemeManager.theme["CTkLabel"]["text_color"] if text_color is None else self._check_color_type(button_hover_color)
self._button_fg_color = ThemeManager.theme["CTkButton"]["fg_color"] if button_fg_color is None else self._check_color_type(button_fg_color)
self._button_hover_color = ThemeManager.theme["CTkButton"]["hover_color"] if button_hover_color is None else self._check_color_type(button_hover_color)
self._button_text_color = ThemeManager.theme["CTkButton"]["text_color"] if button_text_color is None else self._check_color_type(button_text_color)
self._entry_fg_color = ThemeManager.theme["CTkEntry"]["fg_color"] if entry_fg_color is None else self._check_color_type(entry_fg_color)
self._entry_border_color = ThemeManager.theme["CTkEntry"]["border_color"] if entry_border_color is None else self._check_color_type(entry_border_color)
self._entry_text_color = ThemeManager.theme["CTkEntry"]["text_color"] if entry_text_color is None else self._check_color_type(entry_text_color)
self._user_input = ("", "")
self._running: bool = False
self._title = title
self._text = text
self._font = font
self._values = [""] + values
self.title(self._title)
self.lift() # lift window on top
self.attributes("-topmost", True) # stay on top
self.protocol("WM_DELETE_WINDOW", self._on_closing)
self.after(10, self._create_widgets) # create widgets with slight delay, to avoid white flickering of background
self.resizable(False, False)
self.grab_set() # make other windows not clickable
def _create_widgets(self):
self.grid_columnconfigure((0, 1), weight=1)
self.rowconfigure(0, weight=1)
self._label = CTkLabel(master=self,
width=300,
wraplength=300,
fg_color="transparent",
text_color=self._text_color,
text=self._text,
font=self._font)
self._label.grid(row=0, column=0, columnspan=2, padx=20, pady=20, sticky="ew")
self._entry = CTkEntry(master=self,
width=230,
fg_color=self._entry_fg_color,
border_color=self._entry_border_color,
text_color=self._entry_text_color,
font=self._font)
self._entry.grid(row=1, column=0, columnspan=2, padx=20, pady=(0, 20), sticky="ew")
self._label2 = CTkLabel(master=self,
width=100,
wraplength=100,
fg_color="transparent",
text_color=self._text_color,
text="Import stages from: ",
font=self._font)
self._label2.grid(row=2, column=0, columnspan=1, padx=(20, 10), pady=(0, 20), sticky="ew")
self._template_optionmenu = CTkOptionMenu(master=self,
width=100,
fg_color=self._button_fg_color,
button_hover_color=self._button_hover_color,
text_color=self._button_text_color,
font=self._font,
values=self._values
)
self._template_optionmenu.grid(row=2, column=1, columnspan=1, padx=(10, 20), pady=(0, 20), sticky="ew")
self._ok_button = CTkButton(master=self,
width=100,
border_width=0,
fg_color=self._button_fg_color,
hover_color=self._button_hover_color,
text_color=self._button_text_color,
text='Ok',
font=self._font,
command=self._ok_event)
self._ok_button.grid(row=3, column=0, columnspan=1, padx=(20, 10), pady=(0, 20), sticky="ew")
self._cancel_button = CTkButton(master=self,
width=100,
border_width=0,
fg_color=self._button_fg_color,
hover_color=self._button_hover_color,
text_color=self._button_text_color,
text='Cancel',
font=self._font,
command=self._cancel_event)
self._cancel_button.grid(row=3, column=1, columnspan=1, padx=(10, 20), pady=(0, 20), sticky="ew")
self.after(150, lambda: self._entry.focus()) # set focus to entry with slight delay, otherwise it won't work
self._entry.bind("<Return>", self._ok_event)
def _ok_event(self, event=None):
self._user_input = self._entry.get(), self._template_optionmenu.get()
self.grab_release()
self.destroy()
def _on_closing(self):
self.grab_release()
self.destroy()
def _cancel_event(self):
self.grab_release()
self.destroy()
def get_input(self):
self.master.wait_window(self)
return self._user_input

View File

@ -0,0 +1,36 @@
import customtkinter
import tkinter as tk
class CTkTimeEntry(customtkinter.CTkFrame):
def __init__(self, master=None, **kwargs):
super().__init__(master, **kwargs)
self.hour = tk.StringVar()
self.minute = tk.StringVar()
self.second = tk.StringVar()
self.hour_entry = customtkinter.CTkEntry(self, width=50, textvariable=self.hour, validate="key", validatecommand=(self.register(self.validate_hour), '%P'))
self.hour_entry.pack(side=tk.LEFT)
self.minute_entry = customtkinter.CTkEntry(self,width=50, textvariable=self.minute, validate="key", validatecommand=(self.register(self.validate_min_sec), '%P'))
self.minute_entry.pack(side=tk.LEFT)
self.second_entry = customtkinter.CTkEntry(self, width=50, textvariable=self.second, validate="key", validatecommand=(self.register(self.validate_min_sec), '%P'))
self.second_entry.pack(side=tk.LEFT)
def validate_hour(self, P):
return len(P) <= 2 and (P.isdigit() and int(P) <= 23 or P == "")
def validate_min_sec(self, P):
return len(P) <= 2 and (P.isdigit() and int(P) <= 59 or P == "")
def set(self, time_str):
h, m, s = map(str, time_str.split(':'))
self.hour.set(h)
self.minute.set(m)
self.second.set(s)
def get(self):
h = self.hour.get() if self.hour.get() else "00"
m = self.minute.get() if self.minute.get() else "00"
s = self.second.get() if self.second.get() else "00"
return f"{h.zfill(2)}:{m.zfill(2)}:{s.zfill(2)}"

View File

@ -0,0 +1,212 @@
"""
CTkToolTip Widget
version: 0.8
"""
import time
import sys
import customtkinter
from tkinter import Toplevel, Frame
class CTkToolTip(Toplevel):
"""
Creates a ToolTip (pop-up) widget for customtkinter.
"""
def __init__(
self,
widget: any = None,
message: str = None,
delay: float = 0.2,
follow: bool = True,
x_offset: int = +20,
y_offset: int = +10,
bg_color: str = None,
corner_radius: int = 10,
border_width: int = 0,
border_color: str = None,
alpha: float = 0.95,
padding: tuple = (10, 2),
**message_kwargs):
super().__init__()
self.widget = widget
self.withdraw()
# Disable ToolTip's title bar
self.overrideredirect(True)
if sys.platform.startswith("win"):
self.transparent_color = self.widget._apply_appearance_mode(
customtkinter.ThemeManager.theme["CTkToplevel"]["fg_color"])
self.attributes("-transparentcolor", self.transparent_color)
self.transient()
elif sys.platform.startswith("darwin"):
self.transparent_color = 'systemTransparent'
self.attributes("-transparent", True)
self.transient(self.master)
else:
self.transparent_color = '#000001'
corner_radius = 0
self.transient()
self.resizable(width=True, height=True)
# Make the background transparent
self.config(background=self.transparent_color)
# StringVar instance for msg string
self.messageVar = customtkinter.StringVar()
self.message = message
self.messageVar.set(self.message)
self.delay = delay
self.follow = follow
self.x_offset = x_offset
self.y_offset = y_offset
self.corner_radius = corner_radius
self.alpha = alpha
self.border_width = border_width
self.padding = padding
self.bg_color = customtkinter.ThemeManager.theme["CTkFrame"]["fg_color"] if bg_color is None else bg_color
self.border_color = border_color
self.disable = False
# visibility status of the ToolTip inside|outside|visible
self.status = "outside"
self.last_moved = 0
self.attributes('-alpha', self.alpha)
if sys.platform.startswith("win"):
if self.widget._apply_appearance_mode(self.bg_color) == self.transparent_color:
self.transparent_color = "#000001"
self.config(background=self.transparent_color)
self.attributes("-transparentcolor", self.transparent_color)
# Add the message widget inside the tooltip
self.transparent_frame = Frame(self, bg=self.transparent_color)
self.transparent_frame.pack(padx=0, pady=0, fill="both", expand=True)
self.frame = customtkinter.CTkFrame(self.transparent_frame, bg_color=self.transparent_color,
corner_radius=self.corner_radius,
border_width=self.border_width, fg_color=self.bg_color,
border_color=self.border_color)
self.frame.pack(padx=0, pady=0, fill="both", expand=True)
self.message_label = customtkinter.CTkLabel(self.frame, textvariable=self.messageVar, **message_kwargs)
self.message_label.pack(fill="both", padx=self.padding[0] + self.border_width,
pady=self.padding[1] + self.border_width, expand=True)
if self.widget.winfo_name() != "tk":
if self.frame.cget("fg_color") == self.widget.cget("bg_color"):
if not bg_color:
self._top_fg_color = self.frame._apply_appearance_mode(
customtkinter.ThemeManager.theme["CTkFrame"]["top_fg_color"])
if self._top_fg_color != self.transparent_color:
self.frame.configure(fg_color=self._top_fg_color)
# Add bindings to the widget without overriding the existing ones
self.widget.bind("<Enter>", self.on_enter, add="+")
self.widget.bind("<Leave>", self.on_leave, add="+")
self.widget.bind("<Motion>", self.on_enter, add="+")
self.widget.bind("<B1-Motion>", self.on_enter, add="+")
self.widget.bind("<Destroy>", lambda _: self.hide(), add="+")
def show(self) -> None:
"""
Enable the widget.
"""
self.disable = False
def on_enter(self, event) -> None:
"""
Processes motion within the widget including entering and moving.
"""
if self.disable:
return
self.last_moved = time.time()
# Set the status as inside for the very first time
if self.status == "outside":
self.status = "inside"
# If the follow flag is not set, motion within the widget will make the ToolTip dissapear
if not self.follow:
self.status = "inside"
self.withdraw()
# Calculate available space on the right side of the widget relative to the screen
root_width = self.winfo_screenwidth()
widget_x = event.x_root
space_on_right = root_width - widget_x
# Calculate the width of the tooltip's text based on the length of the message string
text_width = self.message_label.winfo_reqwidth()
# Calculate the offset based on available space and text width to avoid going off-screen on the right side
offset_x = self.x_offset
if space_on_right < text_width + 20: # Adjust the threshold as needed
offset_x = -text_width - 20 # Negative offset when space is limited on the right side
# Offsets the ToolTip using the coordinates od an event as an origin
self.geometry(f"+{event.x_root + offset_x}+{event.y_root + self.y_offset}")
# Time is in integer: milliseconds
self.after(int(self.delay * 1000), self._show)
def on_leave(self, event=None) -> None:
"""
Hides the ToolTip temporarily.
"""
if self.disable: return
self.status = "outside"
self.withdraw()
def _show(self) -> None:
"""
Displays the ToolTip.
"""
if not self.widget.winfo_exists():
self.hide()
self.destroy()
if self.status == "inside" and time.time() - self.last_moved >= self.delay:
self.status = "visible"
self.deiconify()
def hide(self) -> None:
"""
Disable the widget from appearing.
"""
if not self.winfo_exists():
return
self.withdraw()
self.disable = True
def is_disabled(self) -> None:
"""
Return the window state
"""
return self.disable
def get(self) -> None:
"""
Returns the text on the tooltip.
"""
return self.messageVar.get()
def configure(self, message: str = None, delay: float = None, bg_color: str = None, **kwargs):
"""
Set new message or configure the label parameters.
"""
if delay: self.delay = delay
if bg_color: self.frame.configure(fg_color=bg_color)
self.messageVar.set(message)
self.message_label.configure(**kwargs)

View File

@ -0,0 +1,448 @@
"""
CustomTkinter Messagebox
Author: Akash Bora
Version: 2.5
"""
import customtkinter
from PIL import Image, ImageTk
import os
import sys
import time
from typing import Literal
class CTkMessagebox(customtkinter.CTkToplevel):
ICONS = {
"check": None,
"cancel": None,
"info": None,
"question": None,
"warning": None
}
ICON_BITMAP = {}
def __init__(self,
master: any = None,
width: int = 400,
height: int = 200,
title: str = "CTkMessagebox",
message: str = "This is a CTkMessagebox!",
option_1: str = "OK",
option_2: str = None,
option_3: str = None,
options: list = [],
border_width: int = 1,
border_color: str = "default",
button_color: str = "default",
bg_color: str = "default",
fg_color: str = "default",
text_color: str = "default",
title_color: str = "default",
button_text_color: str = "default",
button_width: int = None,
button_height: int = None,
cancel_button_color: str = None,
cancel_button: str = None, # types: circle, cross or none
button_hover_color: str = "default",
icon: str = "info",
icon_size: tuple = None,
corner_radius: int = 15,
justify: str = "right",
font: tuple = None,
header: bool = False,
topmost: bool = True,
fade_in_duration: int = 0,
sound: bool = False,
option_focus: Literal[1, 2, 3] = None):
super().__init__()
self.master_window = master
self.width = 250 if width<250 else width
self.height = 150 if height<150 else height
if self.master_window is None:
self.spawn_x = int((self.winfo_screenwidth()-self.width)/2)
self.spawn_y = int((self.winfo_screenheight()-self.height)/2)
else:
self.spawn_x = int(self.master_window.winfo_width() * .5 + self.master_window.winfo_x() - .5 * self.width + 7)
self.spawn_y = int(self.master_window.winfo_height() * .5 + self.master_window.winfo_y() - .5 * self.height + 20)
self.after(10)
self.geometry(f"{self.width}x{self.height}+{self.spawn_x}+{self.spawn_y}")
self.title(title)
self.resizable(width=False, height=False)
self.fade = fade_in_duration
if self.fade:
self.fade = 20 if self.fade<20 else self.fade
self.attributes("-alpha", 0)
if not header:
self.overrideredirect(1)
if topmost:
self.attributes("-topmost", True)
else:
self.transient(self.master_window)
if sys.platform.startswith("win"):
self.transparent_color = self._apply_appearance_mode(self.cget("fg_color"))
self.attributes("-transparentcolor", self.transparent_color)
default_cancel_button = "cross"
elif sys.platform.startswith("darwin"):
self.transparent_color = 'systemTransparent'
self.attributes("-transparent", True)
default_cancel_button = "circle"
else:
self.transparent_color = '#000001'
corner_radius = 0
default_cancel_button = "cross"
self.lift()
self.config(background=self.transparent_color)
self.protocol("WM_DELETE_WINDOW", self.button_event)
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
self.x = self.winfo_x()
self.y = self.winfo_y()
self._title = title
self.message = message
self.font = font
self.justify = justify
self.sound = sound
self.cancel_button = cancel_button if cancel_button else default_cancel_button
self.round_corners = corner_radius if corner_radius<=30 else 30
self.button_width = button_width if button_width else self.width/4
self.button_height = button_height if button_height else 28
if self.fade: self.attributes("-alpha", 0)
if self.button_height>self.height/4: self.button_height = self.height/4 -20
self.dot_color = cancel_button_color
self.border_width = border_width if border_width<6 else 5
if type(options) is list and len(options)>0:
try:
option_1 = options[-1]
option_2 = options[-2]
option_3 = options[-3]
except IndexError: None
if bg_color=="default":
self.bg_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkFrame"]["fg_color"])
else:
self.bg_color = bg_color
if fg_color=="default":
self.fg_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkFrame"]["top_fg_color"])
else:
self.fg_color = fg_color
default_button_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkButton"]["fg_color"])
if sys.platform.startswith("win"):
if self.bg_color==self.transparent_color or self.fg_color==self.transparent_color:
self.configure(fg_color="#000001")
self.transparent_color = "#000001"
self.attributes("-transparentcolor", self.transparent_color)
if button_color=="default":
self.button_color = (default_button_color, default_button_color, default_button_color)
else:
if type(button_color) is tuple:
if len(button_color)==2:
self.button_color = (button_color[0], button_color[1], default_button_color)
elif len(button_color)==1:
self.button_color = (button_color[0], default_button_color, default_button_color)
else:
self.button_color = button_color
else:
self.button_color = (button_color, button_color, button_color)
if text_color=="default":
self.text_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkLabel"]["text_color"])
else:
self.text_color = text_color
if title_color=="default":
self.title_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkLabel"]["text_color"])
else:
self.title_color = title_color
if button_text_color=="default":
self.bt_text_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkButton"]["text_color"])
else:
self.bt_text_color = button_text_color
if button_hover_color=="default":
self.bt_hv_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkButton"]["hover_color"])
else:
self.bt_hv_color = button_hover_color
if border_color=="default":
self.border_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkFrame"]["border_color"])
else:
self.border_color = border_color
if icon_size:
self.size_height = icon_size[1] if icon_size[1]<=self.height-100 else self.height-100
self.size = (icon_size[0], self.size_height)
else:
self.size = (self.height/4, self.height/4)
self.icon = self.load_icon(icon, icon_size) if icon else None
self.frame_top = customtkinter.CTkFrame(self, corner_radius=self.round_corners, width=self.width, border_width=self.border_width,
bg_color=self.transparent_color, fg_color=self.bg_color, border_color=self.border_color)
self.frame_top.grid(sticky="nswe")
if button_width:
self.frame_top.grid_columnconfigure(0, weight=1)
else:
self.frame_top.grid_columnconfigure((1,2,3), weight=1)
if button_height:
self.frame_top.grid_rowconfigure((0,1,3), weight=1)
else:
self.frame_top.grid_rowconfigure((0,1,2), weight=1)
self.frame_top.bind("<B1-Motion>", self.move_window)
self.frame_top.bind("<ButtonPress-1>", self.oldxyset)
if self.cancel_button=="cross":
self.button_close = customtkinter.CTkButton(self.frame_top, corner_radius=10, width=0, height=0, hover=False, border_width=0,
text_color=self.dot_color if self.dot_color else self.title_color,
text="", fg_color="transparent", command=self.button_event)
self.button_close.grid(row=0, column=5, sticky="ne", padx=5+self.border_width, pady=5+self.border_width)
elif self.cancel_button=="circle":
self.button_close = customtkinter.CTkButton(self.frame_top, corner_radius=10, width=10, height=10, hover=False, border_width=0,
text="", fg_color=self.dot_color if self.dot_color else "#c42b1c", command=self.button_event)
self.button_close.grid(row=0, column=5, sticky="ne", padx=10, pady=10)
self.title_label = customtkinter.CTkLabel(self.frame_top, width=1, text=self._title, text_color=self.title_color, font=self.font)
self.title_label.grid(row=0, column=0, columnspan=6, sticky="nw", padx=(15,30), pady=5)
self.title_label.bind("<B1-Motion>", self.move_window)
self.title_label.bind("<ButtonPress-1>", self.oldxyset)
self.info = customtkinter.CTkButton(self.frame_top, width=1, height=self.height/2, corner_radius=0, text=self.message, font=self.font,
fg_color=self.fg_color, hover=False, text_color=self.text_color, image=self.icon)
self.info._text_label.configure(wraplength=self.width/2, justify="left")
self.info.grid(row=1, column=0, columnspan=6, sticky="nwes", padx=self.border_width)
if self.info._text_label.winfo_reqheight()>self.height/2:
height_offset = int((self.info._text_label.winfo_reqheight())-(self.height/2) + self.height)
self.geometry(f"{self.width}x{height_offset}")
self.option_text_1 = option_1
self.button_1 = customtkinter.CTkButton(self.frame_top, text=self.option_text_1, fg_color=self.button_color[0],
width=self.button_width, font=self.font, text_color=self.bt_text_color,
hover_color=self.bt_hv_color, height=self.button_height,
command=lambda: self.button_event(self.option_text_1))
self.option_text_2 = option_2
if option_2:
self.button_2 = customtkinter.CTkButton(self.frame_top, text=self.option_text_2, fg_color=self.button_color[1],
width=self.button_width, font=self.font, text_color=self.bt_text_color,
hover_color=self.bt_hv_color, height=self.button_height,
command=lambda: self.button_event(self.option_text_2))
self.option_text_3 = option_3
if option_3:
self.button_3 = customtkinter.CTkButton(self.frame_top, text=self.option_text_3, fg_color=self.button_color[2],
width=self.button_width, font=self.font, text_color=self.bt_text_color,
hover_color=self.bt_hv_color, height=self.button_height,
command=lambda: self.button_event(self.option_text_3))
if self.justify=="center":
if button_width:
columns = [4,3,2]
span = 1
else:
columns = [4,2,0]
span = 2
if option_3:
self.frame_top.columnconfigure((0,1,2,3,4,5), weight=1)
self.button_1.grid(row=2, column=columns[0], columnspan=span, sticky="news", padx=(0,10), pady=10)
self.button_2.grid(row=2, column=columns[1], columnspan=span, sticky="news", padx=10, pady=10)
self.button_3.grid(row=2, column=columns[2], columnspan=span, sticky="news", padx=(10,0), pady=10)
elif option_2:
self.frame_top.columnconfigure((0,5), weight=1)
columns = [2,3]
self.button_1.grid(row=2, column=columns[0], sticky="news", padx=(0,5), pady=10)
self.button_2.grid(row=2, column=columns[1], sticky="news", padx=(5,0), pady=10)
else:
if button_width:
self.frame_top.columnconfigure((0,1,2,3,4,5), weight=1)
else:
self.frame_top.columnconfigure((0,2,4), weight=2)
self.button_1.grid(row=2, column=columns[1], columnspan=span, sticky="news", padx=(0,10), pady=10)
elif self.justify=="left":
self.frame_top.columnconfigure((0,1,2,3,4,5), weight=1)
if button_width:
columns = [0,1,2]
span = 1
else:
columns = [0,2,4]
span = 2
if option_3:
self.button_1.grid(row=2, column=columns[2], columnspan=span, sticky="news", padx=(0,10), pady=10)
self.button_2.grid(row=2, column=columns[1], columnspan=span, sticky="news", padx=10, pady=10)
self.button_3.grid(row=2, column=columns[0], columnspan=span, sticky="news", padx=(10,0), pady=10)
elif option_2:
self.button_1.grid(row=2, column=columns[1], columnspan=span, sticky="news", padx=10, pady=10)
self.button_2.grid(row=2, column=columns[0], columnspan=span, sticky="news", padx=(10,0), pady=10)
else:
self.button_1.grid(row=2, column=columns[0], columnspan=span, sticky="news", padx=(10,0), pady=10)
else:
self.frame_top.columnconfigure((0,1,2,3,4,5), weight=1)
if button_width:
columns = [5,4,3]
span = 1
else:
columns = [4,2,0]
span = 2
self.button_1.grid(row=2, column=columns[0], columnspan=span, sticky="news", padx=(0,10), pady=10)
if option_2:
self.button_2.grid(row=2, column=columns[1], columnspan=span, sticky="news", padx=10, pady=10)
if option_3:
self.button_3.grid(row=2, column=columns[2], columnspan=span, sticky="news", padx=(10,0), pady=10)
if header:
self.title_label.configure(text="")
self.title_label.grid_configure(pady=0)
self.button_close.configure(text_color=self.bg_color)
self.frame_top.configure(corner_radius=0)
if self.winfo_exists():
self.grab_set()
if self.sound:
self.bell()
if self.fade:
self.fade_in()
if option_focus:
self.option_focus = option_focus
self.focus_button(self.option_focus)
else:
if not self.option_text_2 and not self.option_text_3:
self.button_1.focus()
self.button_1.bind("<Return>", lambda event: self.button_event(self.option_text_1))
self.bind("<Escape>", lambda e: self.button_event())
def focus_button(self, option_focus):
try:
self.selected_button = getattr(self, "button_"+str(option_focus))
self.selected_button.focus()
self.selected_button.configure(border_color=self.bt_hv_color, border_width=3)
self.selected_option = getattr(self, "option_text_"+str(option_focus))
self.selected_button.bind("<Return>", lambda event: self.button_event(self.selected_option))
except AttributeError:
return
self.bind("<Left>", lambda e: self.change_left())
self.bind("<Right>", lambda e: self.change_right())
def change_left(self):
if self.option_focus==3:
return
self.selected_button.unbind("<Return>")
self.selected_button.configure(border_width=0)
if self.option_focus==1:
if self.option_text_2:
self.option_focus = 2
elif self.option_focus==2:
if self.option_text_3:
self.option_focus = 3
self.focus_button(self.option_focus)
def change_right(self):
if self.option_focus==1:
return
self.selected_button.unbind("<Return>")
self.selected_button.configure(border_width=0)
if self.option_focus==2:
self.option_focus = 1
elif self.option_focus==3:
self.option_focus = 2
self.focus_button(self.option_focus)
def load_icon(self, icon, icon_size):
if icon not in self.ICONS or self.ICONS[icon] is None:
if icon in ["check", "cancel", "info", "question", "warning"]:
image_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'icons', icon + '.png')
else:
image_path = icon
if icon_size:
size_height = icon_size[1] if icon_size[1] <= self.height - 100 else self.height - 100
size = (icon_size[0], size_height)
else:
size = (self.height / 4, self.height / 4)
self.ICONS[icon] = customtkinter.CTkImage(Image.open(image_path), size=size)
self.ICON_BITMAP[icon] = ImageTk.PhotoImage(file=image_path)
self.after(200, lambda: self.iconphoto(False, self.ICON_BITMAP[icon]))
return self.ICONS[icon]
def fade_in(self):
for i in range(0,110,10):
if not self.winfo_exists():
break
self.attributes("-alpha", i/100)
self.update()
time.sleep(1/self.fade)
def fade_out(self):
for i in range(100,0,-10):
if not self.winfo_exists():
break
self.attributes("-alpha", i/100)
self.update()
time.sleep(1/self.fade)
def get(self):
if self.winfo_exists():
self.master.wait_window(self)
return self.event
def oldxyset(self, event):
self.oldx = event.x
self.oldy = event.y
def move_window(self, event):
self.y = event.y_root - self.oldy
self.x = event.x_root - self.oldx
self.geometry(f'+{self.x}+{self.y}')
def button_event(self, event=None):
try:
self.button_1.configure(state="disabled")
self.button_2.configure(state="disabled")
self.button_3.configure(state="disabled")
except AttributeError:
pass
if self.fade:
self.fade_out()
self.grab_release()
self.destroy()
self.event = event
if __name__ == "__main__":
app = CTkMessagebox()
app.mainloop()

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

92
MCE/utils.py Normal file
View File

@ -0,0 +1,92 @@
import customtkinter
import json
import sys
from MCE.custom_widgets.ctk_notification import CTkNotification
class Config:
def __init__(self, linker, config_file):
self.linker = linker
self.config_file = config_file
self.config_data = self.read()
self.linker.widgets = self.set_values_to_none(self.config_data)
self.locked = False
linker.config = self
def read(self):
# Read the JSON file
try:
with open(self.config_file, 'r') as json_file:
config_data = json.load(json_file)
return config_data
except FileNotFoundError:
print(f"Config file '{self.config_file}' not found.")
sys.exit(1)
except json.JSONDecodeError:
print(f"Invalid JSON format in '{self.config_file}'.")
sys.exit(1)
def set_values_to_none(self, input_dict):
result = {}
for key, value in input_dict.items():
if isinstance(value, dict):
result[key] = self.set_values_to_none(value)
else:
result[key] = None
return result
def load_config(self, widgets=None, config_data=None):
if widgets == None:
widgets = self.linker.widgets
config_data = self.config_data
for key in widgets:
if isinstance(widgets[key], dict) and isinstance(config_data[key], dict):
self.load_config(widgets[key], config_data[key])
else:
if widgets[key] is not None:
if isinstance(widgets[key], customtkinter.CTkCheckBox):
if config_data[key] == True:
widgets[key].select()
else:
widgets[key].deselect()
elif isinstance(widgets[key], customtkinter.CTkEntry):
widgets[key].insert(0, config_data[key])
else:
widgets[key].set(config_data[key])
def save_to_json(self, list_keys):
widget = self.linker.widgets
data = self.config_data
for i in list_keys[:-1]:
widget = widget[i]
data = data[i]
widget = widget[list_keys[-1]]
value = widget.get()
if isinstance(widget, customtkinter.CTkCheckBox):
value = True if value==1 else False
data[list_keys[-1]] = value
self.save_file("Configuration")
def save_file(self, name=None):
if self.locked:
with open("MCE\config.json", "r") as config_file:
new_config = json.load(config_file)
self.config_data["Queue"] = new_config["Queue"]
self.config_data["LastRun"] = new_config["LastRun"]
with open("MCE\config.json", "w") as config_file:
json.dump(self.config_data, config_file, indent=2)
if name:
self.linker.show_notification(name)
class Linker:
def __init__(self):
self.capitalise = lambda word: " ".join(x.title() for x in word.split("_"))
self.config = None
self.widgets = {}
self.sidebar = None
self.event_id = None
def show_notification(self, text):
if self.event_id:
self.sidebar.after_cancel(self.event_id)
self.sidebar.notification.show()
self.event_id = self.sidebar.after(2500, self.sidebar.notification.hide)

4
aas.py
View File

@ -58,6 +58,10 @@ class ArisuAutoSweeper(AzurLaneAutoScript):
from tasks.momotalk.momotalk import MomoTalk
MomoTalk(config=self.config, device=self.device).run()
def mission(self):
from tasks.mission.mission import Mission
Mission(config=self.config, device=self.device).run()
def data_update(self):
from tasks.item.data_update import DataUpdate
DataUpdate(config=self.config, device=self.device).run()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

BIN
assets/en/mission/LEFT.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

BIN
assets/en/mission/RIGHT.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -172,12 +172,20 @@
"Enable": true,
"NextRun": "2020-01-01 00:00:00",
"Command": "TacticalChallenge",
"ServerUpdate": "15:00"
"ServerUpdate": "14:00"
},
"TacticalChallenge": {
"PlayerSelect": 0
}
},
"Mission": {
"Scheduler": {
"Enable": false,
"NextRun": "2020-01-01 00:00:00",
"Command": "Mission",
"ServerUpdate": "04:00"
}
},
"Circle": {
"Scheduler": {
"Enable": true,

View File

@ -730,7 +730,7 @@
},
"ServerUpdate": {
"type": "input",
"value": "15:00",
"value": "14:00",
"display": "hide"
}
},
@ -747,6 +747,33 @@
}
}
},
"Mission": {
"Scheduler": {
"Enable": {
"type": "checkbox",
"value": false,
"option": [
true,
false
]
},
"NextRun": {
"type": "datetime",
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "input",
"value": "Mission",
"display": "hide"
},
"ServerUpdate": {
"type": "input",
"value": "04:00",
"display": "hide"
}
}
},
"Circle": {
"Scheduler": {
"Enable": {

View File

@ -22,7 +22,8 @@
"tasks": [
"Bounty",
"Scrimmage",
"TacticalChallenge"
"TacticalChallenge",
"Mission"
]
},
"Reward": {

View File

@ -55,6 +55,8 @@ Farm:
TacticalChallenge:
- Scheduler
- TacticalChallenge
Mission:
- Scheduler
# ==================== Rewards ====================

View File

@ -9,7 +9,7 @@ class ManualConfig:
SCHEDULER_PRIORITY = """
Restart
> Cafe > TacticalChallenge > Circle > Mail
> DataUpdate > Bounty > Scrimmage > Task > Shop > Momotalk
> DataUpdate > Bounty > Scrimmage > Task > Shop > Mission > Momotalk
"""
"""

View File

@ -50,6 +50,10 @@
"name": "Tactical Challenge",
"help": ""
},
"Mission": {
"name": "Mission/Commissions/Event",
"help": "Open MCE Manager for additional settings"
},
"Circle": {
"name": "Club",
"help": ""

View File

@ -50,6 +50,10 @@
"name": "战术对抗赛",
"help": "战术大赛 / 竞技场"
},
"Mission": {
"name": "Task.Mission.name",
"help": "Task.Mission.help"
},
"Circle": {
"name": "公会",
"help": "社团 / 小组"

View File

@ -538,9 +538,9 @@ WORK_GO_TO_TACTICAL_CHALLENGE = ButtonWrapper(
),
en=Button(
file='./assets/en/base/page/WORK_GO_TO_TACTICAL_CHALLENGE.png',
area=(1034, 435, 1162, 466),
search=(1014, 415, 1182, 486),
color=(179, 199, 221),
button=(1034, 435, 1162, 466),
area=(1012, 532, 1152, 591),
search=(992, 512, 1172, 611),
color=(199, 211, 227),
button=(1012, 532, 1152, 591),
),
)

View File

@ -0,0 +1,148 @@
from module.base.button import Button, ButtonWrapper
# This file was auto-generated, do not modify it manually. To generate:
# ``` python -m dev_tools.button_extract ```
CHECK_BD = ButtonWrapper(
name='CHECK_BD',
jp=None,
en=Button(
file='./assets/en/mission/CHECK_BD.png',
area=(94, 135, 325, 194),
search=(74, 115, 345, 214),
color=(208, 215, 220),
button=(94, 135, 325, 194),
),
)
CHECK_COMMISSIONS = ButtonWrapper(
name='CHECK_COMMISSIONS',
jp=None,
en=Button(
file='./assets/en/mission/CHECK_COMMISSIONS.png',
area=(646, 78, 909, 135),
search=(626, 58, 929, 155),
color=(70, 96, 124),
button=(646, 78, 909, 135),
),
)
CHECK_IR = ButtonWrapper(
name='CHECK_IR',
jp=None,
en=Button(
file='./assets/en/mission/CHECK_IR.png',
area=(97, 137, 340, 191),
search=(77, 117, 360, 211),
color=(213, 220, 223),
button=(97, 137, 340, 191),
),
)
CHECK_MISSION_SWEEP = ButtonWrapper(
name='CHECK_MISSION_SWEEP',
jp=None,
en=Button(
file='./assets/en/mission/CHECK_MISSION_SWEEP.png',
area=(654, 184, 703, 209),
search=(634, 164, 723, 229),
color=(208, 213, 219),
button=(654, 184, 703, 209),
),
)
HARD_OFF = ButtonWrapper(
name='HARD_OFF',
jp=None,
en=Button(
file='./assets/en/mission/HARD_OFF.png',
area=(947, 132, 1193, 182),
search=(927, 112, 1213, 202),
color=(242, 246, 248),
button=(947, 132, 1193, 182),
),
)
HARD_ON = ButtonWrapper(
name='HARD_ON',
jp=None,
en=Button(
file='./assets/en/mission/HARD_ON.png',
area=(940, 133, 1189, 186),
search=(920, 113, 1209, 206),
color=(200, 71, 63),
button=(940, 133, 1189, 186),
),
)
LEFT = ButtonWrapper(
name='LEFT',
jp=None,
en=Button(
file='./assets/en/mission/LEFT.png',
area=(0, 301, 89, 408),
search=(0, 281, 109, 428),
color=(193, 224, 241),
button=(0, 301, 89, 408),
),
)
NORMAL_OFF = ButtonWrapper(
name='NORMAL_OFF',
jp=None,
en=Button(
file='./assets/en/mission/NORMAL_OFF.png',
area=(682, 135, 927, 182),
search=(662, 115, 947, 202),
color=(238, 243, 246),
button=(682, 135, 927, 182),
),
)
NORMAL_ON = ButtonWrapper(
name='NORMAL_ON',
jp=None,
en=Button(
file='./assets/en/mission/NORMAL_ON.png',
area=(682, 137, 924, 185),
search=(662, 117, 944, 205),
color=(62, 81, 89),
button=(682, 137, 924, 185),
),
)
OCR_AREA = ButtonWrapper(
name='OCR_AREA',
jp=None,
en=Button(
file='./assets/en/mission/OCR_AREA.png',
area=(108, 176, 176, 219),
search=(88, 156, 196, 239),
color=(237, 238, 240),
button=(108, 176, 176, 219),
),
)
RIGHT = ButtonWrapper(
name='RIGHT',
jp=None,
en=Button(
file='./assets/en/mission/RIGHT.png',
area=(1202, 311, 1280, 412),
search=(1182, 291, 1280, 432),
color=(193, 223, 241),
button=(1202, 311, 1280, 412),
),
)
SELECT_BD = ButtonWrapper(
name='SELECT_BD',
jp=None,
en=Button(
file='./assets/en/mission/SELECT_BD.png',
area=(1016, 165, 1227, 211),
search=(996, 145, 1247, 231),
color=(205, 212, 220),
button=(1016, 165, 1227, 211),
),
)
SELECT_IR = ButtonWrapper(
name='SELECT_IR',
jp=None,
en=Button(
file='./assets/en/mission/SELECT_IR.png',
area=(1004, 267, 1237, 321),
search=(984, 247, 1257, 341),
color=(214, 220, 227),
button=(1004, 267, 1237, 321),
),
)

260
tasks/mission/mission.py Normal file
View File

@ -0,0 +1,260 @@
from enum import Enum
from module.base.timer import Timer
from module.exception import RequestHumanTakeover
from module.logger import logger
from tasks.mission.ui import MissionUI, CommissionsUI
from tasks.stage.ap import AP
from tasks.cafe.cafe import Cafe
from tasks.circle.circle import Circle
from tasks.task.task import Task
from tasks.mail.mail import Mail
from tasks.item.data_update import DataUpdate
import json
import math
from filelock import FileLock
from datetime import datetime
class MissionStatus(Enum):
AP = 0 # Calculate AP and decide to terminate Mission module or not
NAVIGATE = 1 # Navigate to the stage page for example the commissions page or mission page
SELECT = 2 # Select the stage mode for example hard or normal in mission
ENTER = 3 # Search and for the stage in the stage list and enter
SWEEP = 4 # Sweep the stage
RECHARGE = 5 # Recharge AP from other taks if they are enabled
FINISH = -1 # Inidicate termination of Mission module
class Mission(MissionUI, CommissionsUI):
_stage_ap = [10, 15, 15, 15]
@property
def stage_ap(self):
return self._stage_ap
@property
def mission_info(self) -> list:
"""
Read the config from MCE/config.json and extract the queue, a list of list.
If queue is empty repopulate from preferred template.
Format of each element in queue: [mode, stage, sweep_num]
e.g. ["N", "1-1", 3]
Mode Acronyms:
"N" : Normal Mission
"H" : Hard Mission
"E" : Event Quest
"IR" : Item Retrieval / Commission where you get credit
"BD" : Base Defense / Commission where you get exp
Returns:
list of list
"""
queue = []
try:
with open("MCE/config.json") as json_file:
config_data = json.load(json_file)
queue = config_data["Queue"]
self.recharge_AP = config_data["RechargeAP"]
self.reset_daily = config_data["ResetDaily"]
self.reset_time = config_data["ResetTime"]
self.last_run = config_data["LastRun"]
self.event = config_data["Event"]
if self.check_reset_daily() or not queue:
preferred_template = config_data["PreferredTemplate"]
queue = config_data["Templates"][preferred_template]
if not self.event:
queue = [x for x in queue if x[0] != "E"]
except:
logger.error("Failed to read configuration file")
finally:
return queue
def check_reset_daily(self):
# Check if it's time to reset the queue
if self.reset_daily:
current_datetime = datetime.now().replace(microsecond=0) # Round to the nearest second
current_date = current_datetime.date()
current_time = current_datetime.time()
last_run_datetime = datetime.strptime(self.last_run, "%Y-%m-%d %H:%M:%S")
reset_time = datetime.strptime(self.reset_time, "%H:%M:%S").time()
if current_date != last_run_datetime.date() and current_time >= reset_time:
self.last_run = str(datetime.now().replace(microsecond=0))
logger.info("Reset Daily activated.")
return True
return False
@property
def valid_task(self) -> list:
task = self.mission_info
if not task:
logger.warning('Mission enabled but no task set')
#self.error_handler()
return task
@property
def current_mode(self):
return self.task[0][0]
@property
def current_stage(self):
return self.task[0][1]
@property
def current_count(self):
if self.current_mode == "H" and self.task[0][2] > 3:
return 3
return self.task[0][2]
@current_count.setter
def current_count(self, value):
self.task[0][2] = value
def select(self) -> bool:
"""
A wrapper method to select the current_mode
by calling the specific method based on its type.
Return
True if selection happens without any problem, False otherwise.
"""
if self.current_mode in ["N", "H"]:
return self.select_mission(self.current_mode, self.current_stage)
elif self.current_mode in ["BD", "IR"]:
return self.select_commission(self.current_mode)
elif self.current_mode == "E":
#return self.select_mode(SWITCH_QUEST)
logger.error("Event not yet implemented")
return False
else:
logger.error("Uknown mode")
return False
def get_realistic_count(self) -> int:
"""
Calculate the possible number of sweeps based on the current AP
"""
ap_cost = 20 if self.current_mode == "H" else 10
required_ap = ap_cost * self.current_count
return math.floor(min(required_ap, self.current_ap) / ap_cost)
def update_task(self, failure=False):
"""
Update self.task and save the current state of the queue in
MCE/config.json.
"""
try:
if failure or self.current_count == self.realistic_count:
self.previous_mode = self.current_mode
self.task.pop(0)
else:
self.previous_mode = None
self.current_count -= self.realistic_count
with open("MCE/config.json", "r") as json_file:
config_data = json.load(json_file)
with open("MCE/config.json", "w") as json_file:
config_data["Queue"] = self.task
config_data["LastRun"] = self.last_run
json.dump(config_data, json_file, indent=2)
except:
logger.error("Failed to save configuration")
self.task = []
def update_ap(self):
ap_cost = 20 if self.current_mode == "H" else 10
ap = self.config.stored.AP
ap_old = ap.value
ap_new = ap_old - ap_cost * self.realistic_count
ap.set(ap_new, ap.total)
logger.info(f'Set AP: {ap_old} -> {ap_new}')
def recharge(self) -> bool:
"""
Check if AP related modules such as cafe, circle, task, mail are enabled and run them if they are.
task_call only works after the current task is finished so is not suitable.
"""
cafe_reward = self.config.cross_get(["Cafe", "Scheduler", "Enable"]) and self.config.cross_get(["Cafe", "Cafe", "Reward"])
circle = self.config.cross_get(["Circle", "Scheduler", "Enable"])
task = self.config.cross_get(["Task", "Scheduler", "Enable"])
mail = self.config.cross_get(["Mail", "Scheduler", "Enable"])
ap_tasks = [(cafe_reward,Cafe), (circle, Circle), (task, Task), (mail, Mail)]
modules = [x[1] for x in ap_tasks if x[0]]
if not modules:
logger.info("Recharge AP was enabled but no AP related modules were enabled")
return False
for module in modules:
module(config=self.config, device=self.device).run()
return True
def handle_mission(self, status):
match status:
case MissionStatus.AP:
if not self.task:
return MissionStatus.FINISH
self.realistic_count = self.get_realistic_count()
if self.realistic_count == 0 and self.recharge_AP:
self.recharge_AP = False
return MissionStatus.RECHARGE
elif self.realistic_count == 0 and not self.recharge_AP:
return MissionStatus.FINISH
else:
return MissionStatus.NAVIGATE
case MissionStatus.NAVIGATE:
self.navigate(self.previous_mode, self.current_mode)
return MissionStatus.SELECT
case MissionStatus.SELECT:
if self.select():
return MissionStatus.ENTER
self.update_task(failure=True)
return MissionStatus.AP
case MissionStatus.ENTER:
if self.enter_stage(self.current_stage):
return MissionStatus.SWEEP
self.update_task(failure=True)
return MissionStatus.AP
case MissionStatus.SWEEP:
if self.do_sweep(self.current_mode, self.realistic_count):
self.update_ap()
self.update_task()
else:
self.update_task(failure=True)
return MissionStatus.AP
case MissionStatus.RECHARGE:
if self.recharge():
DataUpdate(config=self.config, device=self.device).run()
return MissionStatus.AP
return MissionStatus.FINISH
case MissionStatus.FINISH:
return status
case _:
logger.warning(f'Invalid status: {status}')
return status
def run(self):
self.lock = FileLock("MCE/config.json.lock")
with self.lock.acquire():
self.previous_mode = None
self.task = self.valid_task
action_timer = Timer(0.5, 1)
status = MissionStatus.AP
"""Update the dashboard to accurately calculate AP"""
DataUpdate(config=self.config, device=self.device).run()
while 1:
self.device.screenshot()
if self.ui_additional():
continue
if action_timer.reached_and_reset():
logger.attr('Status', status)
status = self.handle_mission(status)
if status == MissionStatus.FINISH:
break
self.config.task_delay(server_update=True)

147
tasks/mission/ui.py Normal file
View File

@ -0,0 +1,147 @@
from module.base.timer import Timer
from module.logger import logger
from module.ui.switch import Switch
from module.ocr.ocr import Digit
from tasks.base.assets.assets_base_page import BACK, MISSION_CHECK
from tasks.base.page import page_mission, page_commissions
from tasks.base.ui import UI
from tasks.mission.assets.assets_mission import *
from tasks.stage.ap import AP
from tasks.stage.mission_list import StageList
from tasks.stage.sweep import StageSweep
SHARED_LIST = StageList('SharedList')
MISSION_SWEEP = StageSweep('MissionSweep', 60)
MISSION_SWEEP.set_button(button_check=CHECK_MISSION_SWEEP) # Check sweep is different for mission
SHARED_SWEEP = StageSweep('SharedSweep', 60)
SWITCH_NORMAL = Switch("Normal_switch")
SWITCH_NORMAL.add_state("on", NORMAL_ON)
SWITCH_NORMAL.add_state("off", NORMAL_OFF)
SWITCH_HARD = Switch("HARD_switch")
SWITCH_HARD.add_state("on", HARD_ON)
SWITCH_HARD.add_state("off", HARD_OFF)
SWITCH_QUEST = None
"""
A dictionary that maps the mode to a tuple where
the first element is an argument to go_back and second is for ui_ensure
Missing for "E" because there are no event in Global and no page_event
"""
MODE_TO_PAGE = {
"N": (MISSION_CHECK, page_mission),
"H": (MISSION_CHECK, page_mission),
"BD": (CHECK_BD, page_commissions),
"IR": (CHECK_IR, page_commissions),
"E" : ()
}
class MissionUI(UI, AP):
def select_mission(self, mode, stage):
area = int(stage.split("-")[0])
if not self.select_area(area):
logger.warning("Area not found")
return False
to_switch = {
"N": SWITCH_NORMAL,
"H": SWITCH_HARD
}
switch = to_switch[mode]
if not self.select_mode(switch) and not self.select_area(area):
return False
return True
def select_area(self, num):
""""
May require further error handling for these cases.
1. Fails to ocr area number
2. May trigger too many click exception when clicking left or right too many times
3. Area not unlocked. Simplest way if left or right button are still present
but problem is it is expensive to check every time and they always keep moving.
"""
tries = 0
ocr_area = Digit(OCR_AREA)
while 1:
try:
self.device.screenshot()
current_area = int(ocr_area.ocr_single_line(self.device.image))
if current_area == num:
return True
elif current_area > num:
[self.click_with_interval(LEFT, interval=1) for x in range(abs(current_area-num))]
elif current_area < num:
[self.click_with_interval(RIGHT, interval=1) for x in range(abs(current_area-num))]
except:
tries += 1
if tries > 5:
return False
def select_mode(self, switch):
"""
Set switch to on.
Returns:
True if switch is set, False if switch not found
"""
if not switch.appear(main=self):
logger.info(f'{switch.name} not found')
return False
switch.set('on', main=self)
return True
def enter_stage(self, index: int) -> bool:
if not index:
index = SHARED_LIST.insight_max_sweepable_index(self)
if SHARED_LIST.select_index_enter(self, index):
return True
return False
def do_sweep(self, mode, num: int) -> bool:
if mode in ["N", "H"]:
return MISSION_SWEEP.do_sweep(self, num=num)
else:
return SHARED_SWEEP.do_sweep(self, num=num)
def navigate(self, prev, next):
"""
go_back is called when the previous stage and next stage are in
the same game mode.
For example, "N" and "H" are in Mission so we call go_back.
If different, ui_ensure is called for example, "N" and "IR".
"""
if prev==next or (prev in ["N", "H"] and next in ["N", "H"]):
self.go_back(MODE_TO_PAGE[next][0])
elif prev in ["BD", "IR"] and next in ["BD", "IR"]:
self.go_back(CHECK_COMMISSIONS)
else:
self.ui_ensure(MODE_TO_PAGE[next][1])
def go_back(self, check):
while 1:
self.device.screenshot()
if self.match_color(check) and self.appear(check):
return True
self.click_with_interval(BACK, interval=2)
class CommissionsUI(UI, AP):
"""Works the same way as select_bounty"""
def select_commission(self, mode):
to_button = {
"IR": (SELECT_IR, CHECK_IR),
"BD": (SELECT_BD, CHECK_BD)
}
dest_enter, dest_check = to_button[mode]
timer = Timer(5, 10).start()
while 1:
self.device.screenshot()
self.appear_then_click(dest_enter, interval=1)
if self.appear(dest_check):
return True
if timer.reached():
return False

252
tasks/stage/mission_list.py Normal file
View File

@ -0,0 +1,252 @@
import re
import numpy as np
from module.base.base import ModuleBase
from module.base.timer import Timer
from module.base.utils import area_pad, area_size, area_offset
from module.logger import logger
from module.ocr.ocr import Ocr
from tasks.stage.assets.assets_stage_list import *
class StageList:
swipe_vector_range = (0.65, 0.70)
def __init__(
self,
name,
button_list: ButtonWrapper = None,
button_index: ButtonWrapper = None,
button_item: ButtonWrapper = None,
button_enter: ButtonWrapper = None,
button_stars: ButtonWrapper = None,
swipe_direction: str = "down"
):
self.name = name
self.stage = button_list if button_list else STAGE_LIST
self.index_ocr = Ocr(button_index if button_index else OCR_INDEX, lang='en')
self.stage_item = (button_item if button_item else STAGE_ITEM).button
self.enter = button_enter if button_enter else STAGE_ENTER
self.sweepable = button_stars if button_stars else STAGE_STARS
self.swipe_direction = swipe_direction
self.current_index_min = 1
self.current_index_max = 1
self.current_indexes: list[tuple[str, tuple]] = []
def __str__(self):
return f'StageList({self.name})'
__repr__ = __str__
def __eq__(self, other):
return str(self) == str(other)
def __hash__(self):
return hash(self.name)
@property
def _indexes(self) -> list[int]:
return [x[0] for x in self.current_indexes]
def load_stage_indexes(self, main: ModuleBase):
self.current_indexes = list(
filter(
lambda x: re.match(r'^\d{1,2}-?\d?$', x[0]) and x[0] != '00',
map(lambda x: (x.ocr_text, x.box), self.index_ocr.detect_and_ocr(main.device.image))
)
)
if not self.current_indexes:
logger.warning(f'No valid index in {self.index_ocr.name}')
return
indexes = self._indexes
self.current_index_min = min(indexes)
self.current_index_max = max(indexes)
logger.attr(self.index_ocr.name, f'Index range: {self.current_index_min} - {self.current_index_max}')
def swipe_page(self, direction: str, main: ModuleBase, vector_range=None, reverse=False):
"""
Args:
direction: up, down
main:
vector_range (tuple[float, float]):
reverse (bool):
"""
if vector_range is None:
vector_range = self.swipe_vector_range
vector = np.random.uniform(*vector_range)
width, height = area_size(self.stage.button)
if direction == 'up':
vector = (0, vector * height)
elif direction == 'down':
vector = (0, -vector * height)
else:
logger.warning(f'Unknown swipe direction: {direction}')
return
if reverse:
vector = (-vector[0], -vector[1])
main.device.swipe_vector(vector, self.stage.button, name=f'{self.name}_SWIPE')
def insight_index(self, index: int, main: ModuleBase, skip_first_screenshot=True) -> bool:
"""
Args:
index:
main:
skip_first_screenshot:
Returns:
If success
"""
logger.info(f'Insight index: {index}')
last_indexes: set[int] = set()
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
main.device.screenshot()
self.load_stage_indexes(main=main)
if self.current_index_min <= index <= self.current_index_max:
return True
indexes = self._indexes
if indexes and last_indexes == set(indexes):
logger.warning(f'No more index {index}')
return False
last_indexes = set(indexes)
if index < self.current_index_min:
self.swipe_page(self.swipe_direction, main, reverse=True)
elif index > self.current_index_max:
self.swipe_page(self.swipe_direction, main)
main.wait_until_stable(
self.stage.button,
timer=Timer(0, 0),
timeout=Timer(1.5, 5)
)
def insight_max_sweepable_index(self, main: ModuleBase, skip_first_screenshot=True) -> int:
"""
Args:
main:
skip_first_screenshot:
Returns:
Index of max sweepable stage
"""
logger.info('Insight sweepable index')
max_sweepable_index = 0
last_max_sweepable_index = 0
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
main.device.screenshot()
self.load_stage_indexes(main=main)
sweepable_index = next(
filter(
lambda x: not self.is_sweepable(main, self.search_box(x[-1][:2])),
self.current_indexes
), None
)
# all sweepable
if sweepable_index is None:
logger.info('All sweepable')
max_sweepable_index = self.current_index_max
self.swipe_page(self.swipe_direction, main)
if max_sweepable_index == last_max_sweepable_index:
logger.info(f'Max sweepable index: {max_sweepable_index}')
return max_sweepable_index
last_max_sweepable_index = max_sweepable_index
# all not sweepable
elif int(sweepable_index[0]) == self.current_index_min:
logger.info('All not sweepable')
if int(sweepable_index[0]) == 1:
logger.warning('No sweepable index')
return 0
self.swipe_page(self.swipe_direction, main, reverse=True)
else:
logger.info(f'Sweepable index: {int(sweepable_index[0]) - 1}')
return int(sweepable_index[0]) - 1
main.wait_until_stable(
self.stage.button,
timer=Timer(0, 0),
timeout=Timer(1.5, 5)
)
def is_sweepable(self, main: ModuleBase, search_box) -> bool:
self.sweepable.load_search(search_box)
return main.appear(self.sweepable, similarity=0.8)
def search_box(
self,
index_cord: tuple[int, int],
padding: tuple[int, int] = (-20, -15)
) -> tuple[int, int, int, int]:
stage_item_box = area_pad((*padding, *area_size(self.stage_item)))
return area_offset(stage_item_box, index_cord)
def select_index_enter(
self,
main: ModuleBase,
index: int,
insight: bool = True,
sweepable: bool = True,
padding: tuple[int, int] = (-20, -15),
skip_first_screenshot: bool = True,
interval: int = 1.5
) -> bool:
# insight index, if failed, return False
if insight and not self.insight_index(index, main, skip_first_screenshot):
return False
logger.info(f'Select index: {index}')
click_interval = Timer(interval)
load_index_interval = Timer(1)
timeout = Timer(15, 10).start()
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
main.device.screenshot()
# load index if not insight
if load_index_interval.reached_and_reset():
self.load_stage_indexes(main=main)
# find box of index
index_box = next(filter(lambda x: x[0] == index, self.current_indexes), None)
if index_box is None:
logger.warning(f'No index {index} in {self.index_ocr.name}')
continue
search_box = self.search_box(index_box[-1][:2], padding)
if sweepable and not self.is_sweepable(main, search_box):
logger.warning(f'Index {index} is not sweepable')
return False
self.enter.load_search(search_box)
click_button = self.enter.match_multi_template(main.device.image)
if not click_button:
logger.warning(f'No clickable {self.enter.name}')
continue
if click_interval.reached_and_reset():
main.device.click(click_button[0])
return True
if timeout.reached():
logger.warning(f'{self.enter.name} failed')
return False

View File

@ -5,7 +5,13 @@ from module.base.button import Button, ButtonWrapper
CLAIM = ButtonWrapper(
name='CLAIM',
jp=None,
jp=Button(
file='./assets/jp/task/CLAIM.png',
area=(936, 641, 1010, 696),
search=(916, 621, 1030, 716),
color=(230, 210, 63),
button=(936, 641, 1010, 696),
),
en=Button(
file='./assets/en/task/CLAIM.png',
area=(935, 639, 1015, 698),
@ -16,7 +22,13 @@ CLAIM = ButtonWrapper(
)
CLAIMED = ButtonWrapper(
name='CLAIMED',
jp=None,
jp=Button(
file='./assets/jp/task/CLAIMED.png',
area=(935, 641, 1010, 696),
search=(915, 621, 1030, 716),
color=(211, 211, 210),
button=(935, 641, 1010, 696),
),
en=Button(
file='./assets/en/task/CLAIMED.png',
area=(937, 641, 1010, 696),
@ -27,7 +39,13 @@ CLAIMED = ButtonWrapper(
)
CLAIMED_ALL = ButtonWrapper(
name='CLAIMED_ALL',
jp=None,
jp=Button(
file='./assets/jp/task/CLAIMED_ALL.png',
area=(1057, 640, 1242, 701),
search=(1037, 620, 1262, 720),
color=(192, 193, 196),
button=(1057, 640, 1242, 701),
),
en=Button(
file='./assets/en/task/CLAIMED_ALL.png',
area=(1058, 641, 1240, 701),
@ -38,7 +56,13 @@ CLAIMED_ALL = ButtonWrapper(
)
CLAIM_ALL = ButtonWrapper(
name='CLAIM_ALL',
jp=None,
jp=Button(
file='./assets/jp/task/CLAIM_ALL.png',
area=(1058, 641, 1242, 701),
search=(1038, 621, 1262, 720),
color=(235, 218, 67),
button=(1058, 641, 1242, 701),
),
en=Button(
file='./assets/en/task/CLAIM_ALL.png',
area=(1054, 642, 1243, 700),