diff --git a/.gitignore b/.gitignore index 6ac43b5..ce85457 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ config/reloadalas test.py test/ note.md +MCE/config.json resources/ locales/ *.pak diff --git a/Input Helper.py b/Input Helper.py new file mode 100644 index 0000000..9505ab2 --- /dev/null +++ b/Input Helper.py @@ -0,0 +1,102 @@ +import customtkinter +import json +from MCE.custom_widgets.ctk_scrollable_dropdown import CTkScrollableDropdown +import os +from tkinter import END + +class InputHelper(customtkinter.CTk): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.create_tabview() + self.create_invite_student_widgets() + self.create_lessons_widgets() + + + def create_tabview(self): + self.tabview = customtkinter.CTkTabview(master=self) + self.tabview.grid(row=0, column=0) + + self.cafe_tab = self.tabview.add("Cafe") # add tab at the end + self.lessons_tab = self.tabview.add("Lessons") # add tab at the end + self.tabview.set("Cafe") # set currently visible + + def create_invite_student_widgets(self): + self.invitation_label = customtkinter.CTkLabel(master=self.cafe_tab, text="Copy and paste this in AAS Invitation Settings:") + self.invitation_label.grid(row=0, column=0, padx=60) + + self.invitation_entry = customtkinter.CTkEntry(master=self.cafe_tab, width=500) + self.invitation_entry.grid(row=1, column=0) + + self.invite_copy_button = customtkinter.CTkButton(master=self.cafe_tab, text="Copy", width=40, command=lambda : self.copy_entry(self.invitation_entry, self.invite_copy_button)) + self.invite_copy_button.grid(row=1, column=3, padx=5) + + self.invite_clear_button = customtkinter.CTkButton(master=self.cafe_tab, text="Clear", width=40, fg_color="crimson", command=lambda : self.invitation_entry.delete(0, END)) + self.invite_clear_button.grid(row=1, column=4, padx=5) + + self.invite_frame = customtkinter.CTkFrame(master=self.cafe_tab, fg_color="transparent") + self.invite_frame.grid(row=2, column=0, padx=20, pady=20) + + self.server_dropdown = customtkinter.CTkOptionMenu(master=self.invite_frame, values=self.find_json_files("MCE/student_list"), command=self.switch_server, width=40) + self.server_dropdown.grid(row=0, column=0) + + self.student_entry = customtkinter.CTkComboBox(master=self.invite_frame, width=300) + self.student_entry.grid(row=0, column=1, padx=(50,0)) + + self.student_dropdown = CTkScrollableDropdown(self.student_entry, width=300, height=550, autocomplete=True, command=lambda choice: self.insert(choice, self.invitation_entry), values=[""]) + self.server_dropdown.set("EN") + self.switch_server("EN") + + def create_lessons_widgets(self): + self.lessons_label = customtkinter.CTkLabel(master=self.lessons_tab, text="Copy and paste this in AAS Lessons Settings:") + self.lessons_label.grid(row=0, column=0, padx=60) + + self.lessons_entry = customtkinter.CTkEntry(master=self.lessons_tab, width=500) + self.lessons_entry.grid(row=1, column=0) + + self.lessons_copy_button = customtkinter.CTkButton(master=self.lessons_tab, text="Copy", width=40, command=lambda : self.copy_entry(self.lessons_entry, self.lessons_copy_button)) + self.lessons_copy_button.grid(row=1, column=1, padx=5) + + self.lessons_clear_button = customtkinter.CTkButton(master=self.lessons_tab, text="Clear", width=40, fg_color="crimson", command=lambda : self.lessons_entry.delete(0, END)) + self.lessons_clear_button.grid(row=1, column=2, padx=5) + + self.lessons_buttons_frame = customtkinter.CTkFrame(master=self.lessons_tab, fg_color="transparent") + self.lessons_buttons_frame.grid(row=2, column=0, padx=20, pady=20) + + for i in range(9): + self.lesson_button = customtkinter.CTkButton(master=self.lessons_buttons_frame, text=str(i+1), command=lambda choice=str(i+1): self.insert(choice, self.lessons_entry), width=40) + self.lesson_button.grid(row=0, column=i, padx=5) + + + def find_json_files(self,folder_path): + json_files = [] + for root, dirs, files in os.walk(folder_path): + for file in files: + if file.endswith(".json"): + json_files.append(os.path.splitext(file)[0]) + return json_files + + def switch_server(self, server): + with open(f"MCE/student_list/{server}.json", "r") as f: + student_list = json.load(f) + self.student_dropdown.configure(values=student_list) + + def insert(self, value, entry): + entry.insert(index=END, string=value + " > ") + + def copy_entry(self, entry, button): + text_to_copy = entry.get() + + # Check if there is text to copy + if text_to_copy: + # Clear the clipboard and set the new text + self.clipboard_clear() + self.clipboard_append(text_to_copy) + self.update() # This is necessary on some systems to update the clipboard + button_color = button.cget("fg_color") + button.configure(fg_color="green") + self.after(2000, lambda : button.configure(fg_color=['#3B8ED0', '#1F6AA5'])) + +if __name__ == "__main__": + app = InputHelper() + app.title("Input Helper") + app.mainloop() diff --git a/MCE Manager.py b/MCE Manager.py new file mode 100644 index 0000000..cbb71e0 --- /dev/null +++ b/MCE Manager.py @@ -0,0 +1,442 @@ +import customtkinter +import tkinter as tk +import random +import re +from MCE.custom_widgets.ctkmessagebox import CTkMessagebox +from MCE.custom_widgets.ctk_tooltip import CTkToolTip +from MCE.custom_widgets.ctk_timeentry import CTkTimeEntry +from MCE.custom_widgets.ctk_integerspinbox import CTkIntegerSpinbox +from MCE.custom_widgets.ctk_templatedialog import CTkTemplateDialog +from MCE.custom_widgets.ctk_notification import CTkNotification +from MCE.custom_widgets.ctk_add_button import CTkAddButton +from MCE.utils import Linker, Config +from filelock import FileLock, Timeout +import threading +import time + +class MCE_Manager(customtkinter.CTk): + def __init__(self, linker, config, **kwargs): + super().__init__(**kwargs) + self.linker = linker + self.config = config + self.create_widgets() + # Load Template Data + self.load_template_data() + # Load queue Data + self.load_queue_data() + + def create_widgets(self): + + self.create_mission_commissions_widgets() + + + def create_mission_commissions_widgets(self): + # Create Mission/Commissions/Event Checkbox + self.create_mission_commissions_checkbox() + + # Create Reset Daily Widgets + self.create_reset_daily_widgets() + + # Create Recharge AP and Event Checkboxes + self.create_recharge_and_event_checkboxes() + + # Create Preferred Template Selection + self.create_preferred_template_selection() + + # Create Mission Tabview with Template and Queue Tabs + self.create_mission_tabview() + + # Create Top-Level Window for Template Editing + self.create_template_queue_editor() + + # Create Template Frame and Queue Frame + self.create_template_and_queue_frames() + + # Create Lists to Store Frame Widgets + self.create_frame_lists() + + # Initialize Preferred Template and Templates List + self.initialize_preferred_template() + + # Create OptionMenu for Selecting a Template + self.create_template_option_menu() + + # Create Delete Template Button + self.create_delete_template_button() + + # Helper method to create Mission/Commissions/Event Checkbox + def create_mission_commissions_checkbox(self): + self.mission_commissions_checkbox = customtkinter.CTkLabel(self, text="Mission/Commissions/Event", width=60, font=customtkinter.CTkFont(family="Inter", size=20, weight="bold")) + self.mission_commissions_checkbox.grid(row=11, column=0, sticky="nw", padx=20, pady=20) + self.notification = CTkNotification(master=self, text="Config saved") + self.notification.grid(row=11, column=1) + + # Helper method to create Reset Daily Widgets + def create_reset_daily_widgets(self): + self.reset_daily = customtkinter.CTkCheckBox(self, text="Reset Daily", font=customtkinter.CTkFont(family="Inter", size=16, underline=True), command=lambda x=["ResetDaily"]: self.config.save_to_json(x)) + self.reset_daily.grid(row=12, column=0, sticky="nw", padx=80) + self.reset_daily_tooltip = CTkToolTip(self.reset_daily, wraplength=400, + message="If enabled and if current time >= reset time,\ + the queue will automatically be cleared and repopulated with preferred template stages. Only activated once a day.") + + self.reset_daily_sub_label = customtkinter.CTkLabel(self, text="hh/mm/ss", font=customtkinter.CTkFont(family="Inter", size=12)) + self.reset_daily_sub_label.grid(row=13, column=0, padx=80) + + self.reset_time = CTkTimeEntry(self) + self.reset_time.grid(row=12, column=1) + self.reset_time.hour_entry.bind("", lambda event, x=["ResetTime"]: self.config.save_to_json(x)) + self.reset_time.minute_entry.bind("", lambda event, x=["ResetTime"]: self.config.save_to_json(x)) + self.reset_time.second_entry.bind("", lambda event, x=["ResetTime"]: self.config.save_to_json(x)) + + self.linker.widgets["ResetDaily"] = self.reset_daily + self.linker.widgets["ResetTime"] = self.reset_time + + # Helper method to create Recharge AP and Event Checkboxes + def create_recharge_and_event_checkboxes(self): + self.recharge_checkbox = customtkinter.CTkCheckBox(self, text="Recharge AP", command=lambda x=["RechargeAP"]: self.config.save_to_json(x), font=customtkinter.CTkFont(family="Inter", size=16, underline=True)) + self.recharge_checkbox.grid(row=14, column=0, sticky="nw", padx=80, pady=20) + self.linker.widgets["RechargeAP"] = self.recharge_checkbox + self.recharge_tooltip = CTkToolTip(self.recharge_checkbox, wraplength=400, + message="When enabled, recharge AP when low via cafe earnings, tasks, club and mailbox if they are enabled in their respective sections.") + self.event_checkbox = customtkinter.CTkCheckBox(self, text="Sweep Event Stages", command=lambda x=["Event"]: self.config.save_to_json(x), font=customtkinter.CTkFont(family="Inter", size=16, underline=True)) + self.event_tooltip = CTkToolTip(self.event_checkbox, wraplength=400, message="When enabled, the script will sweep event stages. Otherwise, it will ignore them.") + self.event_checkbox.grid(row=15, column=0, sticky="nw", padx=80) + self.linker.widgets["Event"] = self.event_checkbox + + # Helper method to create Preferred Template Selection + def create_preferred_template_selection(self): + self.templates = self.config.config_data["Templates"] + self.templates_list = list(self.templates.keys()) + + self.preferred_template_label = customtkinter.CTkLabel(self, text="Preferred Template:", font=customtkinter.CTkFont(family="Inter", size=16, underline=True)) + self.preferred_template_label.grid(row=16, column=0, pady=20) + self.preferred_template_tooltip = CTkToolTip(self.preferred_template_label, wraplength=400, + message="The template from which to repopulate the queue when it is empty or reset daily is activated") + self.preferred_template_optionmenu = customtkinter.CTkOptionMenu(self, values=self.templates_list, command=lambda x, y=["PreferredTemplate"]: self.config.save_to_json(y)) + self.preferred_template_optionmenu.grid(row=16, column=1, pady=20) + self.linker.widgets["PreferredTemplate"] = self.preferred_template_optionmenu + + # Helper method to create Mission Tabview with Template and Queue Tabs + def create_mission_tabview(self): + self.mission_tabview = customtkinter.CTkTabview(self) + self.mission_tabview.grid(row=17, column=0, columnspan=3, padx=20) + + self.tab_template = self.mission_tabview.add('Template') + self.tab_queue = self.mission_tabview.add('Queue') + + # Helper method to create Template Queue Editor + def create_template_queue_editor(self): + self.queue_buttons = [] + + for i in [self.tab_queue, self.tab_template]: + queue = True if i == self.tab_queue else False + + self.template_labels = customtkinter.CTkFrame(i) + self.template_labels.grid(row=0, column=0, sticky="ew") + + self.mode_label = customtkinter.CTkLabel(self.template_labels, text="Mode:", font=customtkinter.CTkFont(underline=True)) + self.mode_tooltip = CTkToolTip(self.mode_label, message="N : Mission Normal\nH : Mission Hard\nE : Event Quest\nXP : Commissions EXP\nCR : Commissions Credits\n", justify=tk.LEFT) + self.mode_label.grid(row=1, column=0, padx=(130, 0), pady=5) + + self.stage_label = customtkinter.CTkLabel(self.template_labels, text="Stage:", font=customtkinter.CTkFont(underline=True)) + self.stage_tooltip = CTkToolTip(self.stage_label, message="Valid format\nMission: 1-1, 3-A\nCommissions & Event: 01", justify=tk.LEFT) + self.stage_label.grid(row=1, column=1, padx=(40, 20), pady=5) + + self.run_times_label = customtkinter.CTkLabel(self.template_labels, text="Number of Sweeps:", font=customtkinter.CTkFont(underline=True)) + self.run_times_tooltip = CTkToolTip(self.run_times_label, message="How many times do you want to sweep the stage?") + self.run_times_label.grid(row=1, column=2, pady=5) + + self.template_buttons_frame = customtkinter.CTkFrame(i) + self.template_buttons_frame.grid(row=3, column=0) + + self.highlight_label = customtkinter.CTkLabel(self.template_buttons_frame, text="*You can double click an entry and press up or down arrow to change its position", font=customtkinter.CTkFont(family="Inter", size=12)) + self.highlight_label.grid(row=0, column=0, columnspan=3) + + self.add_button = CTkAddButton(master=self.template_buttons_frame) + self.add_button.button.configure(command=lambda queue=queue, button=self.add_button.button: self.add_frame(queue=queue, button=button)) + self.add_button.grid(row=1, column=0, padx=5, pady=5) + + # Clear button to clear all frames + self.clear_button = customtkinter.CTkButton(self.template_buttons_frame, text="Clear All", command=lambda queue=queue: self.clear_frames(queue=queue), fg_color="crimson") + self.clear_button.grid(row=1, column=1, padx=5, pady=5) + + # Save button to save data + self.save_button = customtkinter.CTkButton(self.template_buttons_frame, text="Save", command=lambda queue=queue: self.save_data(queue=queue), fg_color="#DC621D") + self.save_button.grid(row=1, column=2, padx=5, pady=5) + if queue: + self.queue_buttons = [self.add_button, self.clear_button, self.save_button] + + # Helper method to create Template Frame and Queue Frame + def create_template_and_queue_frames(self): + self.template_frame = customtkinter.CTkScrollableFrame(self.tab_template, width=435, height=350) + self.template_frame.grid(row=1, column=0, sticky="nsew") + + self.queue_frame = customtkinter.CTkScrollableFrame(self.tab_queue, width=435, height=350) + self.queue_frame.grid(row=1, column=0, sticky="nsew") + + # Helper method to create Lists to Store Frame Widgets + def create_frame_lists(self): + self.template_frames = [] + self.queue_frames = [] + self.highlighted_frame = None + + # Helper method to initialize Preferred Template and Templates List + def initialize_preferred_template(self): + self.preferred_template = self.config.config_data["PreferredTemplate"] + self.templates_list.append("Add New Template") + + # Helper method to create OptionMenu for Selecting a Template + def create_template_option_menu(self): + self.selected_template = tk.StringVar(self.template_frame) + self.selected_template.set(self.preferred_template) # Set the initial value to the preferred template + + self.template_optionmenu = customtkinter.CTkOptionMenu(self.template_labels, values=self.templates_list, variable=self.selected_template, command=lambda *args: self.load_template_data()) + self.template_optionmenu.grid(row=0, column=0, padx=5, pady=5) + + # Helper method to create Delete Template Button + def create_delete_template_button(self): + self.delete_template_button = customtkinter.CTkButton(self.template_labels, width=40, text="Delete", command=self.delete_template) + self.delete_template_button.grid(row=0, column=1) + + # Helper method to add frames from Configuration Data + def load_queue_data(self, state="normal"): + for entry in self.config.config_data['Queue']: + self.add_frame(entry, queue=True, state=state) + + # Function to load template data into frames + def load_template_data(self): + selected = self.selected_template.get() + if selected == "Add New Template": + dialog = CTkTemplateDialog(text="Type in new template name. Template name MUST be different from other templates!", title="Template Name", values=self.templates_list[:-1]) + template_name, template_import = dialog.get_input() + if template_name.replace(" ", "") == "": + self.template_optionmenu.set(self.previous_selected) + return + elif template_name in self.templates_list: + CTkMessagebox(title="Error", message="Name is invalid.", icon="MCE\icons\cancel.png") + self.template_optionmenu.set(self.previous_selected) + return + else: + if template_import != "": + self.templates[template_name] = self.templates[template_import] + else: + self.templates[template_name] = [] + self.templates_list[-1] = template_name + self.preferred_template_optionmenu.configure(values=self.templates_list) + selected = template_name + self.template_optionmenu.set(selected) + self.templates_list.append("Add New Template") + self.template_optionmenu.configure(values=self.templates_list) + self.clear_frames() + for entry in self.templates[selected]: + self.add_frame(entry) + self.previous_selected = selected + + def delete_template(self): + msg = CTkMessagebox(title="Template Deletetion", message=f"Are you sure you want to delete Template {self.previous_selected}?", + icon="MCE\icons\question.png", option_1="No", option_2="Yes") + response = msg.get() + if response=="Yes": + if len(self.templates) != 1: + del self.templates[self.previous_selected] + self.templates_list = list(self.templates.keys()) + self.preferred_template_optionmenu.configure(values=self.templates_list) + if self.preferred_template == self.previous_selected: + self.preferred_template = random.choice(self.templates_list) + self.config.config_data["PreferredTemplate"] = self.preferred_template + self.selected_template.set(self.preferred_template) # Set the initial value to the preferred template + self.preferred_template_optionmenu.set(self.preferred_template) + self.load_template_data() + self.config.save_file() + self.templates_list.append("Add New Template") + self.template_optionmenu.configure(values=self.templates_list) + self.template_optionmenu.set(self.preferred_template) + else: + CTkMessagebox(title="Error", message="At least one template must exist!!!", icon="MCE\icons\cancel.png") + return + +# Function to add a frame with widgets + def add_frame(self, inner_list=None, queue=False, state="normal", button=None): + position = button.cget("text") if button else "Add Down" + frames = self.queue_frames if queue else self.template_frames + parent_frame = self.queue_frame if queue else self.template_frame + row_index = len(frames) + 1 # Calculate the row for the new frame + # Create a frame + frame = tk.Frame(parent_frame, bg="gray17") + frame.grid(row=row_index, column=0, columnspan=4, padx=10, pady=10, sticky="w") + frames.append(frame) if position == "Add Down" else frames.insert(0, frame) + # "Up" button to move the frame up + up_button = customtkinter.CTkButton(frame, text="Up", width=5, command=lambda f=frame, queue=queue: self.move_frame_up(f, queue), state=state) + up_button.grid(row=0, column=0, padx=5, pady=5, sticky="w") + # "Down" button to move the frame down + down_button = customtkinter.CTkButton(frame, text="Down", width=5, command=lambda f=frame, queue=queue: self.move_frame_down(f, queue), state=state) + down_button.grid(row=0, column=1, padx=5, pady=5, sticky="w") + # Dropdown menu for mode + mode_optionmenu = customtkinter.CTkOptionMenu(frame, width=60, values=["N", "H", "E", "XP", "CR"], state=state) + mode_optionmenu.set(inner_list[0] if inner_list else "N") + mode_optionmenu.grid(row=0, column=2, padx=5, pady=5, sticky="w") + # Entry widget for stage + stage_var = tk.StringVar(value=inner_list[1] if inner_list else "") + stage_entry = customtkinter.CTkEntry(frame, width=60, textvariable=stage_var, state=state) + stage_entry.grid(row=0, column=3, padx=5, pady=5, sticky="w") + mode_optionmenu.configure(command=lambda choice, x=mode_optionmenu, y=stage_entry : self.check_entry(x,y)) + stage_entry.bind('', command=lambda event, x=mode_optionmenu, y=stage_entry : self.check_entry(x,y)) + self.check_entry(mode_optionmenu, stage_entry) + # Entry widget for run times (only accepts numbers) + run_times_spinbox = CTkIntegerSpinbox(frame, step_size=1, min_value=1) + run_times_spinbox.set(value=inner_list[2] if inner_list else 1) + run_times_spinbox.grid(row=0, column=4, padx=5, pady=5, sticky="w") + + # Delete button to delete the frame + delete_button = customtkinter.CTkButton(frame, text="Delete", width=5, command=lambda f=frame, queue=queue: self.delete_frame(f, queue), state=state) + delete_button.grid(row=0, column=5, padx=5, pady=5, sticky="w") + + frame.bind("", lambda event, f=frame: self.highlight_frame(f)) + if position == "Add Up": + self.update_frame_positions(queue=queue) + + # Function to clear all frames + def clear_frames(self, queue=False): + frames = self.queue_frames if queue else self.template_frames + for frame in frames: + frame.destroy() + frames.clear() + + # Function to save frames as data + def save_data(self, queue=False): + entries = [] + frames = self.queue_frames if queue else self.template_frames + name = "Queue" if queue else "Template" + for frame in frames: + mode_optionmenu = frame.winfo_children()[2] + stage_entry = frame.winfo_children()[3] + if not self.check_entry(mode_optionmenu, stage_entry): + CTkMessagebox(title="Error", message="Configuration not saved. Some entries are incomplete or have incorect input.", icon="MCE\icons\cancel.png") + return + mode = frame.winfo_children()[2].get() + stage = frame.winfo_children()[3].get().strip() + run_times = frame.winfo_children()[4].get() + entries.append([mode, stage, int(run_times)]) + if queue: + self.config.config_data['Queue'] = entries + else: + selected = self.selected_template.get() + self.templates[selected] = entries + self.config.save_file(name) + + def check_entry(self, mode_dropdown, stage_entry): + mode = mode_dropdown.get() + stage = stage_entry.get() + if mode in ["N", "H"]: + pattern = r'\d{1,2}-[0-9A-Z]' + else: + pattern = r"^\d{2}$" + if re.match(pattern, stage): + stage_entry.configure(border_color=['#979DA2', '#565B5E']) + return True + else: + stage_entry.configure(border_color='crimson') + return False + + # Function to move a frame up + def move_frame_up(self, frame, queue=False): + frames = self.queue_frames if queue else self.template_frames + index = frames.index(frame) + if index > 0: + frames[index], frames[index - 1] = frames[index - 1], frames[index] + self.update_frame_positions(queue=queue) + + # Function to move a frame down + def move_frame_down(self, frame, queue=False): + frames = self.queue_frames if queue else self.template_frames + index = frames.index(frame) + if index < len(frames) - 1: + frames[index], frames[index + 1] = frames[index + 1], frames[index] + self.update_frame_positions(queue=queue) + + # Function to update frame positions in the grid + def update_frame_positions(self, queue=False): + frames = self.queue_frames if queue else self.template_frames + for index, frame in enumerate(frames): + frame.grid(row=index, column=0, columnspan=4, padx=10, pady=10, sticky="w") + + # Function to delete a frame + def delete_frame(self, frame, queue=False): + if queue: + self.queue_frames.remove(frame) + else: + self.template_frames.remove(frame) + frame.destroy() + # Update the positions of remaining frames + self.update_frame_positions(queue=queue) + + def highlight_frame(self, frame): + try: + if self.highlighted_frame is not None: + self.highlighted_frame.unbind("") + self.highlighted_frame.unbind("") + self.highlighted_frame.config(bg="gray17") + except: + pass + + if self.highlighted_frame == frame: + self.highlighted_frame = None + + else: + up_button = frame.winfo_children()[0] + down_button = frame.winfo_children()[1] + frame.config(bg="yellow") + frame.bind("", lambda event: up_button.invoke()) + frame.bind("", lambda event: down_button.invoke()) + frame.focus_set() + self.highlighted_frame = frame + + def check_lock(self): + while 1: + try: + lock = FileLock("MCE\config.json.lock") + lock.acquire(timeout=1) + except Timeout: + if not self.config.locked: + self.after(10, lambda : (self.queue_changed(), self.update_queue(), self.switch_queue_state("disabled"))) + self.config.locked = True + elif self.config.locked and self.queue_changed(): + self.after(10, lambda : (self.update_queue(), self.switch_queue_state("disabled"))) + else: + lock.release() + if self.config.locked: + self.after(10, lambda : (self.queue_changed(), self.update_queue(), self.switch_queue_state("normal"))) + self.config.locked = False + finally: + time.sleep(2) + + def switch_queue_state(self, state): + for button in self.queue_buttons: + button.configure(state=state) + for frame in self.queue_frames: + for widget in frame.winfo_children(): + widget.configure(state=state) + + def update_queue(self): + self.clear_frames(queue=True) + for entry in self.config.config_data['Queue']: + self.add_frame(entry, queue=True) + + def queue_changed(self): + new_config_data = self.config.read() + changed = self.config.config_data["Queue"] != new_config_data["Queue"] or self.config.config_data["LastRun"] != new_config_data["LastRun"] + if changed: + self.config.config_data["LastRun"] = new_config_data["LastRun"] + self.config.config_data['Queue'] = new_config_data['Queue'] + return changed + +if __name__ == "__main__": + linker = Linker() + config = Config(linker, "MCE\config.json") + app = MCE_Manager(linker, config) + app.title("MCE Manager") + linker.sidebar = app + config.load_config() + daemon_thread = threading.Thread(target=app.check_lock, daemon=True) + daemon_thread.start() + app.mainloop() \ No newline at end of file diff --git a/MCE/custom_widgets/__init__.py b/MCE/custom_widgets/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/MCE/custom_widgets/ctk_add_button.py b/MCE/custom_widgets/ctk_add_button.py new file mode 100644 index 0000000..f589ea8 --- /dev/null +++ b/MCE/custom_widgets/ctk_add_button.py @@ -0,0 +1,19 @@ +import customtkinter + +class CTkAddButton(customtkinter.CTkFrame): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.button = customtkinter.CTkButton(self, text="Add Down", corner_radius=0, width=120) + self.button.grid(row=0, column=0) + self.option_menu = customtkinter.CTkOptionMenu( + self, values= ["Add Up", "Add Down"], width=10, command=self.set_button, corner_radius=0 + ) + self.option_menu.set("") + self.option_menu.grid(row=0, column=1) + + def set_button(self, value): + self.option_menu.set("") + self.button.configure(text=value) + + def configure(self, *args, **kwargs): + self.button.configure(*args, **kwargs) \ No newline at end of file diff --git a/MCE/custom_widgets/ctk_integerspinbox.py b/MCE/custom_widgets/ctk_integerspinbox.py new file mode 100644 index 0000000..d3a2c56 --- /dev/null +++ b/MCE/custom_widgets/ctk_integerspinbox.py @@ -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('', self.start_decrementing) + self.subtract_button.bind('', 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('', self.start_incrementing) + self.add_button.bind('', 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) \ No newline at end of file diff --git a/MCE/custom_widgets/ctk_notification.py b/MCE/custom_widgets/ctk_notification.py new file mode 100644 index 0000000..713ecfd --- /dev/null +++ b/MCE/custom_widgets/ctk_notification.py @@ -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") \ No newline at end of file diff --git a/MCE/custom_widgets/ctk_scrollable_dropdown.py b/MCE/custom_widgets/ctk_scrollable_dropdown.py new file mode 100644 index 0000000..212e969 --- /dev/null +++ b/MCE/custom_widgets/ctk_scrollable_dropdown.py @@ -0,0 +1,337 @@ +''' +Advanced Scrollable Dropdown class for customtkinter widgets +Author: Akash Bora +''' + +import customtkinter +import sys +import time +import difflib + +class CTkScrollableDropdown(customtkinter.CTkToplevel): + + def __init__(self, attach, x=None, y=None, button_color=None, height: int = 200, width: int = None, + fg_color=None, button_height: int = 20, justify="center", scrollbar_button_color=None, + scrollbar=True, scrollbar_button_hover_color=None, frame_border_width=2, values=[], + command=None, image_values=[], alpha: float = 0.97, frame_corner_radius=20, double_click=False, + resize=True, frame_border_color=None, text_color=None, autocomplete=False, **button_kwargs): + + super().__init__(takefocus=1) + + self.focus() + self.lift() + self.alpha = alpha + self.attach = attach + self.corner = frame_corner_radius + self.padding = 0 + self.focus_something = False + self.disable = True + self.update() + + if sys.platform.startswith("win"): + self.after(100, lambda: self.overrideredirect(True)) + self.transparent_color = self._apply_appearance_mode(self._fg_color) + self.attributes("-transparentcolor", self.transparent_color) + elif sys.platform.startswith("darwin"): + self.overrideredirect(True) + self.transparent_color = 'systemTransparent' + self.attributes("-transparent", True) + self.focus_something = True + else: + self.overrideredirect(True) + self.transparent_color = '#000001' + self.corner = 0 + self.padding = 18 + self.withdraw() + + self.hide = True + self.attach.bind('', lambda e: self._withdraw() if not self.disable else None, add="+") + self.attach.winfo_toplevel().bind('', lambda e: self._withdraw() if not self.disable else None, add="+") + self.attach.winfo_toplevel().bind("", lambda e: self._withdraw() if not self.disable else None, add="+") + + + self.attributes('-alpha', 0) + self.disable = False + self.fg_color = customtkinter.ThemeManager.theme["CTkFrame"]["fg_color"] if fg_color is None else fg_color + self.scroll_button_color = customtkinter.ThemeManager.theme["CTkScrollbar"]["button_color"] if scrollbar_button_color is None else scrollbar_button_color + self.scroll_hover_color = customtkinter.ThemeManager.theme["CTkScrollbar"]["button_hover_color"] if scrollbar_button_hover_color is None else scrollbar_button_hover_color + self.frame_border_color = customtkinter.ThemeManager.theme["CTkFrame"]["border_color"] if frame_border_color is None else frame_border_color + self.button_color = customtkinter.ThemeManager.theme["CTkFrame"]["top_fg_color"] if button_color is None else button_color + self.text_color = customtkinter.ThemeManager.theme["CTkLabel"]["text_color"] if text_color is None else text_color + + if scrollbar is False: + self.scroll_button_color = self.fg_color + self.scroll_hover_color = self.fg_color + + self.frame = customtkinter.CTkScrollableFrame(self, bg_color=self.transparent_color, fg_color=self.fg_color, + scrollbar_button_hover_color=self.scroll_hover_color, + corner_radius=self.corner, border_width=frame_border_width, + scrollbar_button_color=self.scroll_button_color, + border_color=self.frame_border_color) + self.frame._scrollbar.grid_configure(padx=3) + self.frame.pack(expand=True, fill="both") + self.dummy_entry = customtkinter.CTkEntry(self.frame, fg_color="transparent", border_width=0, height=1, width=1) + self.no_match = customtkinter.CTkLabel(self.frame, text="No Match") + self.height = height + self.height_new = height + self.width = width + self.command = command + self.fade = False + self.resize = resize + self.autocomplete = autocomplete + self.var_update = customtkinter.StringVar() + self.appear = False + + if justify.lower()=="left": + self.justify = "w" + elif justify.lower()=="right": + self.justify = "e" + else: + self.justify = "c" + + self.button_height = button_height + self.values = values + self.button_num = len(self.values) + self.image_values = None if len(image_values)!=len(self.values) else image_values + + self.resizable(width=False, height=False) + self.transient(self.master) + self._init_buttons(**button_kwargs) + + # Add binding for different ctk widgets + if double_click or self.attach.winfo_name().startswith("!ctkentry") or self.attach.winfo_name().startswith("!ctkcombobox"): + self.attach.bind('', lambda e: self._iconify(), add="+") + else: + self.attach.bind('', lambda e: self._iconify(), add="+") + + if self.attach.winfo_name().startswith("!ctkcombobox"): + self.attach._canvas.tag_bind("right_parts", "", lambda e: self._iconify()) + self.attach._canvas.tag_bind("dropdown_arrow", "", lambda e: self._iconify()) + if self.command is None: + self.command = self.attach.set + + if self.attach.winfo_name().startswith("!ctkoptionmenu"): + self.attach._canvas.bind("", lambda e: self._iconify()) + self.attach._text_label.bind("", lambda e: self._iconify()) + if self.command is None: + self.command = self.attach.set + + self.attach.bind("", lambda _: self._destroy(), add="+") + + self.update_idletasks() + self.x = x + self.y = y + + if self.autocomplete: + self.bind_autocomplete() + + self.deiconify() + self.withdraw() + + self.attributes("-alpha", self.alpha) + + def _destroy(self): + self.after(500, self.destroy_popup) + + def _withdraw(self): + if self.winfo_viewable() and self.hide: + self.withdraw() + + self.event_generate("<>") + self.hide = True + + def _update(self, a, b, c): + self.live_update(self.attach._entry.get()) + + def bind_autocomplete(self, ): + def appear(x): + self.appear = True + + if self.attach.winfo_name().startswith("!ctkcombobox"): + self.attach._entry.configure(textvariable=self.var_update) + self.attach._entry.bind("", appear) + self.attach.set(self.values[0]) + self.var_update.trace_add('write', self._update) + + if self.attach.winfo_name().startswith("!ctkentry"): + self.attach.configure(textvariable=self.var_update) + self.attach.bind("", appear) + self.var_update.trace_add('write', self._update) + + 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/100) + + def fade_in(self): + for i in range(0,100,10): + if not self.winfo_exists(): + break + self.attributes("-alpha", i/100) + self.update() + time.sleep(1/100) + + def _init_buttons(self, **button_kwargs): + self.i = 0 + self.widgets = {} + for row in self.values: + self.widgets[self.i] = customtkinter.CTkButton(self.frame, + text=row, + height=self.button_height, + fg_color=self.button_color, + text_color=self.text_color, + image=self.image_values[i] if self.image_values is not None else None, + anchor=self.justify, + command=lambda k=row: self._attach_key_press(k), **button_kwargs) + self.widgets[self.i].pack(fill="x", pady=2, padx=(self.padding, 0)) + self.i+=1 + + self.hide = False + + def destroy_popup(self): + self.destroy() + self.disable = True + + def place_dropdown(self): + self.x_pos = self.attach.winfo_rootx() if self.x is None else self.x + self.attach.winfo_rootx() + self.y_pos = self.attach.winfo_rooty() + self.attach.winfo_reqheight() + 5 if self.y is None else self.y + self.attach.winfo_rooty() + self.width_new = self.attach.winfo_width() if self.width is None else self.width + + if self.resize: + if self.button_num<=5: + self.height_new = self.button_height * self.button_num + 55 + else: + self.height_new = self.button_height * self.button_num + 35 + if self.height_new>self.height: + self.height_new = self.height + + self.geometry('{}x{}+{}+{}'.format(self.width_new, self.height_new, + self.x_pos, self.y_pos)) + self.fade_in() + self.attributes('-alpha', self.alpha) + self.attach.focus() + + def _iconify(self): + if self.disable: return + if self.hide: + self.event_generate("<>") + self._deiconify() + self.focus() + self.hide = False + self.place_dropdown() + if self.focus_something: + self.dummy_entry.pack() + self.dummy_entry.focus_set() + self.after(100, self.dummy_entry.pack_forget) + else: + self.withdraw() + self.hide = True + + def _attach_key_press(self, k): + self.event_generate("<>") + self.fade = True + if self.command: + self.command(k) + self.fade = False + self.fade_out() + self.withdraw() + self.hide = True + + def live_update(self, string=None): + if not self.appear: return + if self.disable: return + if self.fade: return + if string: + string = string.lower() + self._deiconify() + i=1 + for key in self.widgets.keys(): + s = self.widgets[key].cget("text").lower() + text_similarity = difflib.SequenceMatcher(None, s[0:len(string)], string).ratio() + similar = s.startswith(string) or text_similarity > 0.75 + if not similar: + self.widgets[key].pack_forget() + else: + self.widgets[key].pack(fill="x", pady=2, padx=(self.padding, 0)) + i+=1 + + if i==1: + self.no_match.pack(fill="x", pady=2, padx=(self.padding, 0)) + else: + self.no_match.pack_forget() + self.button_num = i + self.place_dropdown() + + else: + self.no_match.pack_forget() + self.button_num = len(self.values) + for key in self.widgets.keys(): + self.widgets[key].destroy() + self._init_buttons() + self.place_dropdown() + + self.frame._parent_canvas.yview_moveto(0.0) + self.appear = False + + def insert(self, value, **kwargs): + self.widgets[self.i] = customtkinter.CTkButton(self.frame, + text=value, + height=self.button_height, + fg_color=self.button_color, + text_color=self.text_color, + anchor=self.justify, + command=lambda k=value: self._attach_key_press(k), **kwargs) + self.widgets[self.i].pack(fill="x", pady=2, padx=(self.padding, 0)) + self.i+=1 + self.values.append(value) + + def _deiconify(self): + if len(self.values)>0: + self.deiconify() + + def popup(self, x=None, y=None): + self.x = x + self.y = y + self.hide = True + self._iconify() + + def configure(self, **kwargs): + if "height" in kwargs: + self.height = kwargs.pop("height") + self.height_new = self.height + + if "alpha" in kwargs: + self.alpha = kwargs.pop("alpha") + + if "width" in kwargs: + self.width = kwargs.pop("width") + + if "fg_color" in kwargs: + self.frame.configure(fg_color=kwargs.pop("fg_color")) + + if "values" in kwargs: + self.values = kwargs.pop("values") + self.image_values = None + self.button_num = len(self.values) + for key in self.widgets.keys(): + self.widgets[key].destroy() + self._init_buttons() + + if "image_values" in kwargs: + self.image_values = kwargs.pop("image_values") + self.image_values = None if len(self.image_values)!=len(self.values) else self.image_values + if self.image_values is not None: + i=0 + for key in self.widgets.keys(): + self.widgets[key].configure(image=self.image_values[i]) + i+=1 + + if "button_color" in kwargs: + for key in self.widgets.keys(): + self.widgets[key].configure(fg_color=kwargs.pop("button_color")) + + for key in self.widgets.keys(): + self.widgets[key].configure(**kwargs) diff --git a/MCE/custom_widgets/ctk_scrollable_dropdown_frame.py b/MCE/custom_widgets/ctk_scrollable_dropdown_frame.py new file mode 100644 index 0000000..609cf0c --- /dev/null +++ b/MCE/custom_widgets/ctk_scrollable_dropdown_frame.py @@ -0,0 +1,291 @@ +''' +Advanced Scrollable Dropdown Frame class for customtkinter widgets +Author: Akash Bora +''' + +import customtkinter +import sys +import difflib + +class CTkScrollableDropdownFrame(customtkinter.CTkFrame): + + def __init__(self, attach, x=None, y=None, button_color=None, height: int = 200, width: int = None, + fg_color=None, button_height: int = 20, justify="center", scrollbar_button_color=None, + scrollbar=True, scrollbar_button_hover_color=None, frame_border_width=2, values=[], + command=None, image_values=[], double_click=False, frame_corner_radius=True, resize=True, frame_border_color=None, + text_color=None, autocomplete=False, **button_kwargs): + + super().__init__(master=attach.winfo_toplevel(), bg_color=attach.cget("bg_color")) + + self.attach = attach + self.corner = 11 if frame_corner_radius else 0 + self.padding = 0 + self.disable = True + + self.hide = True + self.attach.bind('', lambda e: self._withdraw() if not self.disable else None, add="+") + self.attach.winfo_toplevel().bind("", lambda e: self._withdraw() if not self.disable else None, add="+") + + self.disable = False + self.fg_color = customtkinter.ThemeManager.theme["CTkFrame"]["fg_color"] if fg_color is None else fg_color + self.scroll_button_color = customtkinter.ThemeManager.theme["CTkScrollbar"]["button_color"] if scrollbar_button_color is None else scrollbar_button_color + self.scroll_hover_color = customtkinter.ThemeManager.theme["CTkScrollbar"]["button_hover_color"] if scrollbar_button_hover_color is None else scrollbar_button_hover_color + self.frame_border_color = customtkinter.ThemeManager.theme["CTkFrame"]["border_color"] if frame_border_color is None else frame_border_color + self.button_color = customtkinter.ThemeManager.theme["CTkFrame"]["top_fg_color"] if button_color is None else button_color + self.text_color = customtkinter.ThemeManager.theme["CTkLabel"]["text_color"] if text_color is None else text_color + + if scrollbar is False: + self.scroll_button_color = self.fg_color + self.scroll_hover_color = self.fg_color + + self.frame = customtkinter.CTkScrollableFrame(self, fg_color=self.fg_color, bg_color=attach.cget("bg_color"), + scrollbar_button_hover_color=self.scroll_hover_color, + corner_radius=self.corner, border_width=frame_border_width, + scrollbar_button_color=self.scroll_button_color, + border_color=self.frame_border_color) + self.frame._scrollbar.grid_configure(padx=3) + self.frame.pack(expand=True, fill="both") + + if self.corner==0: + self.corner = 21 + + self.dummy_entry = customtkinter.CTkEntry(self.frame, fg_color="transparent", border_width=0, height=1, width=1) + self.no_match = customtkinter.CTkLabel(self.frame, text="No Match") + self.height = height + self.height_new = height + self.width = width + self.command = command + self.fade = False + self.resize = resize + self.autocomplete = autocomplete + self.var_update = customtkinter.StringVar() + self.appear = False + + if justify.lower()=="left": + self.justify = "w" + elif justify.lower()=="right": + self.justify = "e" + else: + self.justify = "c" + + self.button_height = button_height + self.values = values + self.button_num = len(self.values) + self.image_values = None if len(image_values)!=len(self.values) else image_values + + self._init_buttons(**button_kwargs) + + # Add binding for different ctk widgets + if double_click or self.attach.winfo_name().startswith("!ctkentry") or self.attach.winfo_name().startswith("!ctkcombobox"): + self.attach.bind('', lambda e: self._iconify(), add="+") + self.attach._entry.bind('', lambda e: self._withdraw() if not self.disable else None, add="+") + else: + self.attach.bind('', lambda e: self._iconify(), add="+") + + if self.attach.winfo_name().startswith("!ctkcombobox"): + self.attach._canvas.tag_bind("right_parts", "", lambda e: self._iconify()) + self.attach._canvas.tag_bind("dropdown_arrow", "", lambda e: self._iconify()) + + if self.command is None: + self.command = self.attach.set + + if self.attach.winfo_name().startswith("!ctkoptionmenu"): + self.attach._canvas.bind("", lambda e: self._iconify()) + self.attach._text_label.bind("", lambda e: self._iconify()) + if self.command is None: + self.command = self.attach.set + + self.x = x + self.y = y + + self.attach.bind("", lambda _: self._destroy(), add="+") + + if self.autocomplete: + self.bind_autocomplete() + + def _destroy(self): + self.after(500, self.destroy_popup) + + def _withdraw(self): + if self.winfo_viewable() and self.hide: + self.place_forget() + + self.event_generate("<>") + self.hide = True + + def _update(self, a, b, c): + self.live_update(self.attach._entry.get()) + + def bind_autocomplete(self, ): + def appear(x): + self.appear = True + + if self.attach.winfo_name().startswith("!ctkcombobox"): + self.attach._entry.configure(textvariable=self.var_update) + self.attach.set(self.values[0]) + self.attach._entry.bind("", appear) + self.var_update.trace_add('write', self._update) + + if self.attach.winfo_name().startswith("!ctkentry"): + self.attach.configure(textvariable=self.var_update) + self.attach.bind("", appear) + self.var_update.trace_add('write', self._update) + + def _init_buttons(self, **button_kwargs): + self.i = 0 + self.widgets = {} + for row in self.values: + self.widgets[self.i] = customtkinter.CTkButton(self.frame, + text=row, + height=self.button_height, + fg_color=self.button_color, + text_color=self.text_color, + image=self.image_values[i] if self.image_values is not None else None, + anchor=self.justify, + command=lambda k=row: self._attach_key_press(k), **button_kwargs) + self.widgets[self.i].pack(fill="x", pady=2, padx=(self.padding, 0)) + self.i+=1 + + self.hide = False + + def destroy_popup(self): + self.destroy() + self.disable = True + + def place_dropdown(self): + self.x_pos = self.attach.winfo_x() if self.x is None else self.x + self.attach.winfo_rootx() + self.y_pos = self.attach.winfo_y() + self.attach.winfo_reqheight() + 5 if self.y is None else self.y + self.attach.winfo_rooty() + self.width_new = self.attach.winfo_width()-45+self.corner if self.width is None else self.width + + if self.resize: + if self.button_num<=5: + self.height_new = self.button_height * self.button_num + 55 + else: + self.height_new = self.button_height * self.button_num + 35 + if self.height_new>self.height: + self.height_new = self.height + + self.frame.configure(width=self.width_new, height=self.height_new) + self.place(x=self.x_pos, y=self.y_pos) + + if sys.platform.startswith("darwin"): + self.dummy_entry.pack() + self.after(100, self.dummy_entry.pack_forget()) + + self.lift() + self.attach.focus() + + def _iconify(self): + if self.disable: return + if self.hide: + self.event_generate("<>") + self.hide = False + self.place_dropdown() + else: + self.place_forget() + self.hide = True + + def _attach_key_press(self, k): + self.event_generate("<>") + self.fade = True + if self.command: + self.command(k) + self.fade = False + self.place_forget() + self.hide = True + + def live_update(self, string=None): + if not self.appear: return + if self.disable: return + if self.fade: return + if string: + string = string.lower() + self._deiconify() + i=1 + for key in self.widgets.keys(): + s = self.widgets[key].cget("text").lower() + text_similarity = difflib.SequenceMatcher(None, s[0:len(string)], string).ratio() + similar = s.startswith(string) or text_similarity > 0.75 + if not similar: + self.widgets[key].pack_forget() + else: + self.widgets[key].pack(fill="x", pady=2, padx=(self.padding, 0)) + i+=1 + + if i==1: + self.no_match.pack(fill="x", pady=2, padx=(self.padding, 0)) + else: + self.no_match.pack_forget() + self.button_num = i + self.place_dropdown() + + else: + self.no_match.pack_forget() + self.button_num = len(self.values) + for key in self.widgets.keys(): + self.widgets[key].destroy() + self._init_buttons() + self.place_dropdown() + + self.frame._parent_canvas.yview_moveto(0.0) + self.appear = False + + def insert(self, value, **kwargs): + self.widgets[self.i] = customtkinter.CTkButton(self.frame, + text=value, + height=self.button_height, + fg_color=self.button_color, + text_color=self.text_color, + anchor=self.justify, + command=lambda k=value: self._attach_key_press(k), **kwargs) + self.widgets[self.i].pack(fill="x", pady=2, padx=(self.padding, 0)) + self.i+=1 + self.values.append(value) + + def _deiconify(self): + if len(self.values)>0: + self.pack_forget() + + def popup(self, x=None, y=None): + self.x = x + self.y = y + self.hide = True + self._iconify() + + def configure(self, **kwargs): + if "height" in kwargs: + self.height = kwargs.pop("height") + self.height_new = self.height + + if "alpha" in kwargs: + self.alpha = kwargs.pop("alpha") + + if "width" in kwargs: + self.width = kwargs.pop("width") + + if "fg_color" in kwargs: + self.frame.configure(fg_color=kwargs.pop("fg_color")) + + if "values" in kwargs: + self.values = kwargs.pop("values") + self.image_values = None + self.button_num = len(self.values) + for key in self.widgets.keys(): + self.widgets[key].destroy() + self._init_buttons() + + if "image_values" in kwargs: + self.image_values = kwargs.pop("image_values") + self.image_values = None if len(self.image_values)!=len(self.values) else self.image_values + if self.image_values is not None: + i=0 + for key in self.widgets.keys(): + self.widgets[key].configure(image=self.image_values[i]) + i+=1 + + if "button_color" in kwargs: + for key in self.widgets.keys(): + self.widgets[key].configure(fg_color=kwargs.pop("button_color")) + + for key in self.widgets.keys(): + self.widgets[key].configure(**kwargs) diff --git a/MCE/custom_widgets/ctk_templatedialog.py b/MCE/custom_widgets/ctk_templatedialog.py new file mode 100644 index 0000000..553bed0 --- /dev/null +++ b/MCE/custom_widgets/ctk_templatedialog.py @@ -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("", 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 diff --git a/MCE/custom_widgets/ctk_timeentry.py b/MCE/custom_widgets/ctk_timeentry.py new file mode 100644 index 0000000..ee0aea0 --- /dev/null +++ b/MCE/custom_widgets/ctk_timeentry.py @@ -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)}" \ No newline at end of file diff --git a/MCE/custom_widgets/ctk_tooltip.py b/MCE/custom_widgets/ctk_tooltip.py new file mode 100644 index 0000000..7304965 --- /dev/null +++ b/MCE/custom_widgets/ctk_tooltip.py @@ -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("", self.on_enter, add="+") + self.widget.bind("", self.on_leave, add="+") + self.widget.bind("", self.on_enter, add="+") + self.widget.bind("", self.on_enter, add="+") + self.widget.bind("", 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) diff --git a/MCE/custom_widgets/ctkmessagebox.py b/MCE/custom_widgets/ctkmessagebox.py new file mode 100644 index 0000000..1a7465c --- /dev/null +++ b/MCE/custom_widgets/ctkmessagebox.py @@ -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("", self.move_window) + self.frame_top.bind("", 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("", self.move_window) + self.title_label.bind("", 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("", lambda event: self.button_event(self.option_text_1)) + + self.bind("", 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("", lambda event: self.button_event(self.selected_option)) + except AttributeError: + return + + self.bind("", lambda e: self.change_left()) + self.bind("", lambda e: self.change_right()) + + def change_left(self): + if self.option_focus==3: + return + + self.selected_button.unbind("") + 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("") + 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() diff --git a/MCE/icons/cancel.png b/MCE/icons/cancel.png new file mode 100644 index 0000000..dcae66e Binary files /dev/null and b/MCE/icons/cancel.png differ diff --git a/MCE/icons/check.png b/MCE/icons/check.png new file mode 100644 index 0000000..dc0892a Binary files /dev/null and b/MCE/icons/check.png differ diff --git a/MCE/icons/info.png b/MCE/icons/info.png new file mode 100644 index 0000000..4b38288 Binary files /dev/null and b/MCE/icons/info.png differ diff --git a/MCE/icons/question.png b/MCE/icons/question.png new file mode 100644 index 0000000..24f77c3 Binary files /dev/null and b/MCE/icons/question.png differ diff --git a/MCE/icons/warning.png b/MCE/icons/warning.png new file mode 100644 index 0000000..bc6aea6 Binary files /dev/null and b/MCE/icons/warning.png differ diff --git a/MCE/student_list/EN.json b/MCE/student_list/EN.json new file mode 100644 index 0000000..9facb11 --- /dev/null +++ b/MCE/student_list/EN.json @@ -0,0 +1,158 @@ +[ + "Airi", + "Akane", + "Akane (Bunny Girl)", + "Akari", + "Ako", + "Arisu", + "Arisu (Maid)", + "Aru", + "Aru (New Year)", + "Asuna", + "Asuna (Bunny Girl)", + "Atsuko", + "Ayane", + "Ayane (Swimsuit)", + "Azusa", + "Azusa (Swimsuit)", + "Cherino", + "Cherino (Hot Spring)", + "Chihiro", + "Chinatsu", + "Chinatsu (Hot Spring)", + "Chise", + "Chise (Swimsuit)", + "Eimi", + "Fubuki", + "Fuuka", + "Fuuka (New Year)", + "Hanae", + "Hanae (Christmas)", + "Hanako", + "Hanako (Swimsuit)", + "Hare", + "Haruka", + "Haruka (New Year)", + "Haruna", + "Haruna (New Year)", + "Haruna (Sportswear)", + "Hasumi", + "Hasumi (Sportswear)", + "Hatsune Miku", + "Hibiki", + "Hibiki (Cheerleader)", + "Hifumi", + "Hifumi (Swimsuit)", + "Himari", + "Hina", + "Hina (Swimsuit)", + "Hinata", + "Hinata (Swimsuit)", + "Hiyori", + "Hoshino", + "Hoshino (Swimsuit)", + "Iori", + "Iori (Swimsuit)", + "Iroha", + "Izumi", + "Izumi (Swimsuit)", + "Izuna", + "Izuna (Swimsuit)", + "Junko", + "Junko (New Year)", + "Juri", + "Kaede", + "Kaho", + "Kanna", + "Karin", + "Karin (Bunny Girl)", + "Kayoko", + "Kayoko (New Year)", + "Kazusa", + "Kirino", + "Koharu", + "Koharu (Swimsuit)", + "Kokona", + "Kotama", + "Kotori", + "Kotori (Cheerleader)", + "Koyuki", + "Maki", + "Mari", + "Mari (Sportswear)", + "Marina", + "Mashiro", + "Mashiro (Swimsuit)", + "Megu", + "Meru", + "Michiru", + "Midori", + "Mika", + "Mimori", + "Mimori (Swimsuit)", + "Mina", + "Mine", + "Minori", + "Misaki", + "Miyako", + "Miyako (Swimsuit)", + "Miyu", + "Miyu (Swimsuit)", + "Moe", + "Momiji", + "Momoi", + "Mutsuki", + "Mutsuki (New Year)", + "Nagisa", + "Natsu", + "Neru", + "Neru (Bunny Girl)", + "Noa", + "Nodoka", + "Nodoka (Hot Spring)", + "Nonomi", + "Nonomi (Swimsuit)", + "Pina", + "Reisa", + "Rumi", + "Saki", + "Saki (Swimsuit)", + "Sakurako", + "Saori", + "Saya", + "Saya (Casual)", + "Sena", + "Serika", + "Serika (New Year)", + "Serina", + "Serina (Christmas)", + "Shigure", + "Shimiko", + "Shiroko", + "Shiroko (Riding)", + "Shiroko (Swimsuit)", + "Shizuko", + "Shizuko (Swimsuit)", + "Shun", + "Shun (Kid)", + "Sumire", + "Suzumi", + "Toki", + "Toki (Bunny Girl)", + "Tomoe", + "Tsubaki", + "Tsukuyo", + "Tsurugi", + "Tsurugi (Swimsuit)", + "Ui", + "Ui (Swimsuit)", + "Utaha", + "Utaha (Cheerleader)", + "Wakamo", + "Wakamo (Swimsuit)", + "Yoshimi", + "Yuuka", + "Yuuka (Sportswear)", + "Yuzu", + "Yuzu (Maid)" +] \ No newline at end of file diff --git a/MCE/student_list/JP.json b/MCE/student_list/JP.json new file mode 100644 index 0000000..b0a03f3 --- /dev/null +++ b/MCE/student_list/JP.json @@ -0,0 +1,168 @@ +[ + "\u30a2\u30a4\u30ea", + "\u30a2\u30ab\u30cd", + "\u30a2\u30ab\u30cd\n\uff08\u30d0\u30cb\u30fc\u30ac\u30fc\u30eb\uff09", + "\u30a2\u30ab\u30ea", + "\u30a2\u30b3", + "\u30a2\u30b9\u30ca", + "\u30a2\u30b9\u30ca\n\uff08\u30d0\u30cb\u30fc\u30ac\u30fc\u30eb\uff09", + "\u30a2\u30ba\u30b5", + "\u30a2\u30ba\u30b5\uff08\u6c34\u7740\uff09", + "\u30a2\u30c4\u30b3", + "\u30a2\u30e4\u30cd", + "\u30a2\u30e4\u30cd\uff08\u6c34\u7740\uff09", + "\u30a2\u30ea\u30b9", + "\u30a2\u30ea\u30b9\uff08\u30e1\u30a4\u30c9\uff09", + "\u30a2\u30eb", + "\u30a2\u30eb\uff08\u6b63\u6708\uff09", + "\u30a4\u30aa\u30ea", + "\u30a4\u30aa\u30ea\uff08\u6c34\u7740\uff09", + "\u30a4\u30ba\u30ca", + "\u30a4\u30ba\u30ca\uff08\u6c34\u7740\uff09", + "\u30a4\u30ba\u30df", + "\u30a4\u30ba\u30df\uff08\u6c34\u7740\uff09", + "\u30a4\u30c1\u30ab", + "\u30a4\u30ed\u30cf", + "\u30a6\u30a4", + "\u30a6\u30a4\uff08\u6c34\u7740\uff09", + "\u30a6\u30bf\u30cf", + "\u30a6\u30bf\u30cf\uff08\u5fdc\u63f4\u56e3\uff09", + "\u30a8\u30a4\u30df", + "\u30a8\u30a4\u30df\uff08\u6c34\u7740\uff09", + "\u30ab\u30a8\u30c7", + "\u30ab\u30b9\u30df", + "\u30ab\u30ba\u30b5", + "\u30ab\u30db", + "\u30ab\u30e8\u30b3", + "\u30ab\u30e8\u30b3\uff08\u6b63\u6708\uff09", + "\u30ab\u30ea\u30f3", + "\u30ab\u30ea\u30f3\n\uff08\u30d0\u30cb\u30fc\u30ac\u30fc\u30eb\uff09", + "\u30ab\u30f3\u30ca", + "\u30ad\u30ad\u30e7\u30a6", + "\u30ad\u30ea\u30ce", + "\u30b3\u30b3\u30ca", + "\u30b3\u30bf\u30de", + "\u30b3\u30c8\u30ea", + "\u30b3\u30c8\u30ea\uff08\u5fdc\u63f4\u56e3\uff09", + "\u30b3\u30cf\u30eb", + "\u30b3\u30cf\u30eb\uff08\u6c34\u7740\uff09", + "\u30b3\u30e6\u30ad", + "\u30b5\u30aa\u30ea", + "\u30b5\u30ad", + "\u30b5\u30ad\uff08\u6c34\u7740\uff09", + "\u30b5\u30af\u30e9\u30b3", + "\u30b5\u30e4", + "\u30b5\u30e4\uff08\u79c1\u670d\uff09", + "\u30b7\u30b0\u30ec", + "\u30b7\u30b0\u30ec\uff08\u6e29\u6cc9\uff09", + "\u30b7\u30ba\u30b3", + "\u30b7\u30ba\u30b3\uff08\u6c34\u7740\uff09", + "\u30b7\u30df\u30b3", + "\u30b7\u30e5\u30f3", + "\u30b7\u30e5\u30f3\uff08\u5e7c\u5973\uff09", + "\u30b7\u30ed\u30b3", + "\u30b7\u30ed\u30b3\n\uff08\u30e9\u30a4\u30c7\u30a3\u30f3\u30b0\uff09", + "\u30b7\u30ed\u30b3\uff08\u6c34\u7740\uff09", + "\u30b8\u30e5\u30ea", + "\u30b8\u30e5\u30f3\u30b3", + "\u30b8\u30e5\u30f3\u30b3\uff08\u6b63\u6708\uff09", + "\u30b9\u30ba\u30df", + "\u30b9\u30df\u30ec", + "\u30bb\u30ca", + "\u30bb\u30ea\u30ab", + "\u30bb\u30ea\u30ab\uff08\u6b63\u6708\uff09", + "\u30bb\u30ea\u30ca", + "\u30bb\u30ea\u30ca\n\uff08\u30af\u30ea\u30b9\u30de\u30b9\uff09", + "\u30c1\u30a7\u30ea\u30ce", + "\u30c1\u30a7\u30ea\u30ce\uff08\u6e29\u6cc9\uff09", + "\u30c1\u30bb", + "\u30c1\u30bb\uff08\u6c34\u7740\uff09", + "\u30c1\u30ca\u30c4", + "\u30c1\u30ca\u30c4\uff08\u6e29\u6cc9\uff09", + "\u30c1\u30d2\u30ed", + "\u30c4\u30af\u30e8", + "\u30c4\u30d0\u30ad", + "\u30c4\u30eb\u30ae", + "\u30c4\u30eb\u30ae\uff08\u6c34\u7740\uff09", + "\u30c8\u30ad", + "\u30c8\u30ad\n\uff08\u30d0\u30cb\u30fc\u30ac\u30fc\u30eb\uff09", + "\u30c8\u30e2\u30a8", + "\u30ca\u30ae\u30b5", + "\u30ca\u30c4", + "\u30cd\u30eb", + "\u30cd\u30eb\n\uff08\u30d0\u30cb\u30fc\u30ac\u30fc\u30eb\uff09", + "\u30ce\u30a2", + "\u30ce\u30c9\u30ab", + "\u30ce\u30c9\u30ab\uff08\u6e29\u6cc9\uff09", + "\u30ce\u30ce\u30df", + "\u30ce\u30ce\u30df\uff08\u6c34\u7740\uff09", + "\u30cf\u30b9\u30df", + "\u30cf\u30b9\u30df\uff08\u4f53\u64cd\u670d\uff09", + "\u30cf\u30ca\u30a8", + "\u30cf\u30ca\u30a8\n\uff08\u30af\u30ea\u30b9\u30de\u30b9\uff09", + "\u30cf\u30ca\u30b3", + "\u30cf\u30ca\u30b3\uff08\u6c34\u7740\uff09", + "\u30cf\u30eb\u30ab", + "\u30cf\u30eb\u30ab\uff08\u6b63\u6708\uff09", + "\u30cf\u30eb\u30ca", + "\u30cf\u30eb\u30ca\uff08\u4f53\u64cd\u670d\uff09", + "\u30cf\u30eb\u30ca\uff08\u6b63\u6708\uff09", + "\u30cf\u30ec", + "\u30d2\u30ca", + "\u30d2\u30ca\u30bf", + "\u30d2\u30ca\u30bf\uff08\u6c34\u7740\uff09", + "\u30d2\u30ca\uff08\u6c34\u7740\uff09", + "\u30d2\u30d3\u30ad", + "\u30d2\u30d3\u30ad\uff08\u5fdc\u63f4\u56e3\uff09", + "\u30d2\u30d5\u30df", + "\u30d2\u30d5\u30df\uff08\u6c34\u7740\uff09", + "\u30d2\u30de\u30ea", + "\u30d2\u30e8\u30ea", + "\u30d5\u30a3\u30fc\u30ca", + "\u30d5\u30a6\u30ab", + "\u30d5\u30a6\u30ab\uff08\u6b63\u6708\uff09", + "\u30d5\u30d6\u30ad", + "\u30db\u30b7\u30ce", + "\u30db\u30b7\u30ce\uff08\u6c34\u7740\uff09", + "\u30de\u30ad", + "\u30de\u30b7\u30ed", + "\u30de\u30b7\u30ed\uff08\u6c34\u7740\uff09", + "\u30de\u30ea\u30ca", + "\u30de\u30ea\u30fc", + "\u30de\u30ea\u30fc\uff08\u4f53\u64cd\u670d\uff09", + "\u30df\u30ab", + "\u30df\u30b5\u30ad", + "\u30df\u30c1\u30eb", + "\u30df\u30c9\u30ea", + "\u30df\u30ca", + "\u30df\u30cd", + "\u30df\u30ce\u30ea", + "\u30df\u30e2\u30ea", + "\u30df\u30e2\u30ea\uff08\u6c34\u7740\uff09", + "\u30df\u30e4\u30b3", + "\u30df\u30e4\u30b3\uff08\u6c34\u7740\uff09", + "\u30df\u30e6", + "\u30df\u30e6\uff08\u6c34\u7740\uff09", + "\u30e0\u30c4\u30ad", + "\u30e0\u30c4\u30ad\uff08\u6b63\u6708\uff09", + "\u30e1\u30b0", + "\u30e1\u30eb", + "\u30e2\u30a8", + "\u30e2\u30df\u30b8", + "\u30e2\u30e2\u30a4", + "\u30e6\u30a6\u30ab", + "\u30e6\u30a6\u30ab\uff08\u4f53\u64cd\u670d\uff09", + "\u30e6\u30ab\u30ea", + "\u30e6\u30ba", + "\u30e6\u30ba\uff08\u30e1\u30a4\u30c9\uff09", + "\u30e8\u30b7\u30df", + "\u30eb\u30df", + "\u30ec\u30a4\u30b5", + "\u30ec\u30f3\u30b2", + "\u30ef\u30ab\u30e2", + "\u30ef\u30ab\u30e2\uff08\u6c34\u7740\uff09", + "\u4f50\u5929\u6d99\u5b50", + "\u521d\u97f3\u30df\u30af", + "\u5fa1\u5742\u7f8e\u7434", + "\u98df\u8702\u64cd\u7948" +] \ No newline at end of file diff --git a/MCE/utils.py b/MCE/utils.py new file mode 100644 index 0000000..139584d --- /dev/null +++ b/MCE/utils.py @@ -0,0 +1,109 @@ +import customtkinter +import json +import sys +import os + +class Config: + def __init__(self, linker, config_file): + self.default_config = { + "ResetDaily": False, + "LastRun": "2023-12-24 21:41:55", + "ResetTime": "11:21:30", + "RechargeAP": False, + "PreferredTemplate": "template1", + "Queue": [], + "Event": False, + "Templates": { + "template1": [] + } + } + self.linker = linker + self.config_file = config_file + + if not os.path.exists(self.config_file): + with open(self.config_file, "w") as f: + json.dump(self.default_config, f, indent=2) + + 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) diff --git a/README.en.md b/README.en.md index 8e18c84..6d6f153 100644 --- a/README.en.md +++ b/README.en.md @@ -12,7 +12,7 @@ The script is still under active development. The following features have been implemented: -- [x] **Cafe** Claim rewards / Interact / Second floor +- [x] **Cafe** Claim rewards / Interact / Invitation / Second floor - [x] **Club** Claim AP - [x] **Mailbox** Claim rewards - [x] **Bounty** Auto sweep @@ -22,7 +22,12 @@ The script is still under active development. The following features have been i Supported servers: - [x] JP -- [x] OVERSEA - Global +- [x] OVERSEA + +Supported in-game languages: + +- [x] Japanese +- [x] English ## Relative projects diff --git a/README.md b/README.md index 1017ec7..5228a49 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ 当前脚本还在活跃开发中,已经实现的功能有: -- [x] **咖啡厅** 领取奖励 / 互动 / 第二咖啡厅 +- [x] **咖啡厅** 领取奖励 / 互动 / 邀请 / 第二咖啡厅 - [x] **公会** 领取体力 - [x] **邮箱** 领取奖励 - [x] **悬赏通缉** 自动扫荡 @@ -22,12 +22,18 @@ 目前支持的服务器: - [x] 日服 -- [x] 国际服 - 全球 +- [x] 国际服 + +目前支持的游戏内语言: + +- [x] 日语 +- [x] 英语 ## 已知问题 -若愿意提供其他服务器支持,请开 PR 或 Issue。 +若愿意提供其他语言或国服支持,请开 PR 或 Issue。 +- **国际服登录的全屏通知**:未实现自动关闭,正在研究中 - **大小月卡**:未实现自动领取,~~因为没买过~~,可能不影响使用。愿意提供图片的请开 Issue - **月卡的额外悬赏券和学院交流券**:不太清楚月卡领取额外券的机制,~~因为没买过~~,可能影响相关任务使用券和体力的计算。愿意提供相关信息的请开 Issue diff --git a/aas.py b/aas.py index afe1205..0f9affe 100644 --- a/aas.py +++ b/aas.py @@ -50,11 +50,30 @@ class ArisuAutoSweeper(AzurLaneAutoScript): from tasks.tactical_challenge.tactical_challenge import TacticalChallenge TacticalChallenge(config=self.config, device=self.device).run() + def task(self): + from tasks.task.task import Task + Task(config=self.config, device=self.device).run() + + def shop(self): + from tasks.shop.shop import Shop + Shop(config=self.config, device=self.device).run() + + def momotalk(self): + 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 schedule(self): + from tasks.schedule.schedule import Schedule + Schedule(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() - if __name__ == '__main__': aas = ArisuAutoSweeper('aas') aas.loop() diff --git a/assets/en/base/page/CAFE_CHECK.png b/assets/en/base/page/CAFE_CHECK.png index 74aca7e..5532100 100644 Binary files a/assets/en/base/page/CAFE_CHECK.png and b/assets/en/base/page/CAFE_CHECK.png differ diff --git a/assets/en/base/page/EVENT_CHECK.png b/assets/en/base/page/EVENT_CHECK.png new file mode 100644 index 0000000..dde3a28 Binary files /dev/null and b/assets/en/base/page/EVENT_CHECK.png differ diff --git a/assets/en/base/page/MAIN_GO_TO_PURCHASE.png b/assets/en/base/page/MAIN_GO_TO_PURCHASE.png deleted file mode 100644 index 4e365b3..0000000 Binary files a/assets/en/base/page/MAIN_GO_TO_PURCHASE.png and /dev/null differ diff --git a/assets/en/base/page/MAIN_GO_TO_WORK.png b/assets/en/base/page/MAIN_GO_TO_WORK.png index 5aafe82..4737413 100644 Binary files a/assets/en/base/page/MAIN_GO_TO_WORK.png and b/assets/en/base/page/MAIN_GO_TO_WORK.png differ diff --git a/assets/en/base/page/WORK_GO_TO_BOUNTY.png b/assets/en/base/page/WORK_GO_TO_BOUNTY.png index 6f71cb2..be8754b 100644 Binary files a/assets/en/base/page/WORK_GO_TO_BOUNTY.png and b/assets/en/base/page/WORK_GO_TO_BOUNTY.png differ diff --git a/assets/en/base/page/WORK_GO_TO_COMMISSIONS.png b/assets/en/base/page/WORK_GO_TO_COMMISSIONS.png index 58089a3..29753c8 100644 Binary files a/assets/en/base/page/WORK_GO_TO_COMMISSIONS.png and b/assets/en/base/page/WORK_GO_TO_COMMISSIONS.png differ diff --git a/assets/en/base/page/WORK_GO_TO_EVENT.png b/assets/en/base/page/WORK_GO_TO_EVENT.png new file mode 100644 index 0000000..adb7552 Binary files /dev/null and b/assets/en/base/page/WORK_GO_TO_EVENT.png differ diff --git a/assets/en/base/page/WORK_GO_TO_MISSION.png b/assets/en/base/page/WORK_GO_TO_MISSION.png index 7c95823..d09f41d 100644 Binary files a/assets/en/base/page/WORK_GO_TO_MISSION.png and b/assets/en/base/page/WORK_GO_TO_MISSION.png differ diff --git a/assets/en/base/page/WORK_GO_TO_SCHOOL_EXCHANGE.png b/assets/en/base/page/WORK_GO_TO_SCHOOL_EXCHANGE.png index 8d70780..1985c0c 100644 Binary files a/assets/en/base/page/WORK_GO_TO_SCHOOL_EXCHANGE.png and b/assets/en/base/page/WORK_GO_TO_SCHOOL_EXCHANGE.png differ diff --git a/assets/en/base/page/WORK_GO_TO_STORY.png b/assets/en/base/page/WORK_GO_TO_STORY.png index 22c1149..c4e4898 100644 Binary files a/assets/en/base/page/WORK_GO_TO_STORY.png and b/assets/en/base/page/WORK_GO_TO_STORY.png differ diff --git a/assets/en/base/page/WORK_GO_TO_TACTICAL_CHALLENGE.png b/assets/en/base/page/WORK_GO_TO_TACTICAL_CHALLENGE.png index 8700d5a..cfcae4d 100644 Binary files a/assets/en/base/page/WORK_GO_TO_TACTICAL_CHALLENGE.png and b/assets/en/base/page/WORK_GO_TO_TACTICAL_CHALLENGE.png differ diff --git a/assets/en/base/popup/LEVEL_UP.BUTTON.png b/assets/en/base/popup/LEVEL_UP.BUTTON.png new file mode 100644 index 0000000..a8e8b85 Binary files /dev/null and b/assets/en/base/popup/LEVEL_UP.BUTTON.png differ diff --git a/assets/en/base/popup/LEVEL_UP.png b/assets/en/base/popup/LEVEL_UP.png new file mode 100644 index 0000000..19ded3d Binary files /dev/null and b/assets/en/base/popup/LEVEL_UP.png differ diff --git a/assets/en/base/popup/LOCATION_LEVEL_UP.BUTTON.png b/assets/en/base/popup/LOCATION_LEVEL_UP.BUTTON.png new file mode 100644 index 0000000..a9f6b33 Binary files /dev/null and b/assets/en/base/popup/LOCATION_LEVEL_UP.BUTTON.png differ diff --git a/assets/en/base/popup/LOCATION_LEVEL_UP.png b/assets/en/base/popup/LOCATION_LEVEL_UP.png new file mode 100644 index 0000000..cee4301 Binary files /dev/null and b/assets/en/base/popup/LOCATION_LEVEL_UP.png differ diff --git a/assets/en/base/popup/QUIT.BUTTON.png b/assets/en/base/popup/QUIT.BUTTON.png new file mode 100644 index 0000000..c1d211f Binary files /dev/null and b/assets/en/base/popup/QUIT.BUTTON.png differ diff --git a/assets/en/base/popup/QUIT.png b/assets/en/base/popup/QUIT.png new file mode 100644 index 0000000..effffef Binary files /dev/null and b/assets/en/base/popup/QUIT.png differ diff --git a/assets/en/bounty/CHECK_BOUNTY.png b/assets/en/bounty/CHECK_BOUNTY.png new file mode 100644 index 0000000..f066082 Binary files /dev/null and b/assets/en/bounty/CHECK_BOUNTY.png differ diff --git a/assets/en/bounty/CHECK_DESERT_RAILROAD.png b/assets/en/bounty/CHECK_DESERT_RAILROAD.png new file mode 100644 index 0000000..dca85c7 Binary files /dev/null and b/assets/en/bounty/CHECK_DESERT_RAILROAD.png differ diff --git a/assets/en/bounty/CHECK_HIGHWAY.png b/assets/en/bounty/CHECK_HIGHWAY.png new file mode 100644 index 0000000..9bc7523 Binary files /dev/null and b/assets/en/bounty/CHECK_HIGHWAY.png differ diff --git a/assets/en/bounty/CHECK_SCHOOLHOUSE.png b/assets/en/bounty/CHECK_SCHOOLHOUSE.png new file mode 100644 index 0000000..94d200a Binary files /dev/null and b/assets/en/bounty/CHECK_SCHOOLHOUSE.png differ diff --git a/assets/en/bounty/OCR_TICKET.png b/assets/en/bounty/OCR_TICKET.png new file mode 100644 index 0000000..39d4d47 Binary files /dev/null and b/assets/en/bounty/OCR_TICKET.png differ diff --git a/assets/en/bounty/SELECT_DESERT_RAILROAD.png b/assets/en/bounty/SELECT_DESERT_RAILROAD.png new file mode 100644 index 0000000..79b74dd Binary files /dev/null and b/assets/en/bounty/SELECT_DESERT_RAILROAD.png differ diff --git a/assets/en/bounty/SELECT_HIGHWAY.png b/assets/en/bounty/SELECT_HIGHWAY.png new file mode 100644 index 0000000..fb4d2c8 Binary files /dev/null and b/assets/en/bounty/SELECT_HIGHWAY.png differ diff --git a/assets/en/bounty/SELECT_SCHOOLHOUSE.png b/assets/en/bounty/SELECT_SCHOOLHOUSE.png new file mode 100644 index 0000000..9936494 Binary files /dev/null and b/assets/en/bounty/SELECT_SCHOOLHOUSE.png differ diff --git a/assets/en/cafe/CAFE_FIRST.png b/assets/en/cafe/CAFE_FIRST.png index a57fd71..f75a567 100644 Binary files a/assets/en/cafe/CAFE_FIRST.png and b/assets/en/cafe/CAFE_FIRST.png differ diff --git a/assets/en/cafe/CAFE_INVITE.png b/assets/en/cafe/CAFE_INVITE.png new file mode 100644 index 0000000..e6108e3 Binary files /dev/null and b/assets/en/cafe/CAFE_INVITE.png differ diff --git a/assets/en/cafe/CAFE_INVITED.png b/assets/en/cafe/CAFE_INVITED.png new file mode 100644 index 0000000..1500990 Binary files /dev/null and b/assets/en/cafe/CAFE_INVITED.png differ diff --git a/assets/en/cafe/CAFE_SECOND.png b/assets/en/cafe/CAFE_SECOND.png index 34bfc7b..299b389 100644 Binary files a/assets/en/cafe/CAFE_SECOND.png and b/assets/en/cafe/CAFE_SECOND.png differ diff --git a/assets/en/cafe/CHANGE_CAFE_NOT_SELECTED.png b/assets/en/cafe/CHANGE_CAFE_NOT_SELECTED.png deleted file mode 100644 index 4e976d7..0000000 Binary files a/assets/en/cafe/CHANGE_CAFE_NOT_SELECTED.png and /dev/null differ diff --git a/assets/en/cafe/CHANGE_CAFE_SELECTED.png b/assets/en/cafe/CHANGE_CAFE_SELECTED.png deleted file mode 100644 index ab5b185..0000000 Binary files a/assets/en/cafe/CHANGE_CAFE_SELECTED.png and /dev/null differ diff --git a/assets/jp/base/page/BACK.png b/assets/en/cafe/CHECK_MOMOTALK.png similarity index 63% rename from assets/jp/base/page/BACK.png rename to assets/en/cafe/CHECK_MOMOTALK.png index e36fd57..9c047b8 100644 Binary files a/assets/jp/base/page/BACK.png and b/assets/en/cafe/CHECK_MOMOTALK.png differ diff --git a/assets/en/cafe/CHECK_REWARD.BUTTON.png b/assets/en/cafe/CHECK_REWARD.BUTTON.png index 2931644..300949c 100644 Binary files a/assets/en/cafe/CHECK_REWARD.BUTTON.png and b/assets/en/cafe/CHECK_REWARD.BUTTON.png differ diff --git a/assets/en/cafe/CHECK_REWARD.png b/assets/en/cafe/CHECK_REWARD.png index caaa9d1..2c4eefd 100644 Binary files a/assets/en/cafe/CHECK_REWARD.png and b/assets/en/cafe/CHECK_REWARD.png differ diff --git a/assets/en/cafe/GET_REWARD.BUTTON.png b/assets/en/cafe/GET_REWARD.BUTTON.png index 9b68cf3..a98ae8a 100644 Binary files a/assets/en/cafe/GET_REWARD.BUTTON.png and b/assets/en/cafe/GET_REWARD.BUTTON.png differ diff --git a/assets/en/cafe/GET_REWARD.png b/assets/en/cafe/GET_REWARD.png index 296c380..62328cf 100644 Binary files a/assets/en/cafe/GET_REWARD.png and b/assets/en/cafe/GET_REWARD.png differ diff --git a/assets/en/cafe/GET_REWARD_CLOSE.png b/assets/en/cafe/GET_REWARD_CLOSE.png index 71d457f..785f373 100644 Binary files a/assets/en/cafe/GET_REWARD_CLOSE.png and b/assets/en/cafe/GET_REWARD_CLOSE.png differ diff --git a/assets/en/cafe/GOT_REWARD.png b/assets/en/cafe/GOT_REWARD.png index df06a32..5a76e31 100644 Binary files a/assets/en/cafe/GOT_REWARD.png and b/assets/en/cafe/GOT_REWARD.png differ diff --git a/assets/en/cafe/INVITE_CONFIRM.BUTTON.png b/assets/en/cafe/INVITE_CONFIRM.BUTTON.png new file mode 100644 index 0000000..aa16eb0 Binary files /dev/null and b/assets/en/cafe/INVITE_CONFIRM.BUTTON.png differ diff --git a/assets/en/cafe/INVITE_CONFIRM.png b/assets/en/cafe/INVITE_CONFIRM.png new file mode 100644 index 0000000..9d62c68 Binary files /dev/null and b/assets/en/cafe/INVITE_CONFIRM.png differ diff --git a/assets/en/cafe/INVITE_IN_SECOND.png b/assets/en/cafe/INVITE_IN_SECOND.png new file mode 100644 index 0000000..3a786ae Binary files /dev/null and b/assets/en/cafe/INVITE_IN_SECOND.png differ diff --git a/assets/en/cafe/INVITE_IN_SECOND_CLOSE.png b/assets/en/cafe/INVITE_IN_SECOND_CLOSE.png new file mode 100644 index 0000000..0bb17d2 Binary files /dev/null and b/assets/en/cafe/INVITE_IN_SECOND_CLOSE.png differ diff --git a/assets/en/cafe/INVITE_SUBSTITUTE.BUTTON.png b/assets/en/cafe/INVITE_SUBSTITUTE.BUTTON.png new file mode 100644 index 0000000..1093aaa Binary files /dev/null and b/assets/en/cafe/INVITE_SUBSTITUTE.BUTTON.png differ diff --git a/assets/en/cafe/INVITE_SUBSTITUTE.png b/assets/en/cafe/INVITE_SUBSTITUTE.png new file mode 100644 index 0000000..73ed16b Binary files /dev/null and b/assets/en/cafe/INVITE_SUBSTITUTE.png differ diff --git a/assets/en/cafe/INVITE_SUBSTITUTE_CLOSE.png b/assets/en/cafe/INVITE_SUBSTITUTE_CLOSE.png new file mode 100644 index 0000000..61a3704 Binary files /dev/null and b/assets/en/cafe/INVITE_SUBSTITUTE_CLOSE.png differ diff --git a/assets/en/base/page/MAIN_GO_TO_CIRCLE.png b/assets/en/cafe/MOMOTALK_INVITE.png similarity index 58% rename from assets/en/base/page/MAIN_GO_TO_CIRCLE.png rename to assets/en/cafe/MOMOTALK_INVITE.png index e4d1169..81e843e 100644 Binary files a/assets/en/base/page/MAIN_GO_TO_CIRCLE.png and b/assets/en/cafe/MOMOTALK_INVITE.png differ diff --git a/assets/en/cafe/MOMOTALK_ITEM.png b/assets/en/cafe/MOMOTALK_ITEM.png new file mode 100644 index 0000000..4d487cf Binary files /dev/null and b/assets/en/cafe/MOMOTALK_ITEM.png differ diff --git a/assets/en/cafe/OCR_CAFE.png b/assets/en/cafe/OCR_CAFE.png index c8513d6..12d8245 100644 Binary files a/assets/en/cafe/OCR_CAFE.png and b/assets/en/cafe/OCR_CAFE.png differ diff --git a/assets/en/cafe/OCR_NAME.png b/assets/en/cafe/OCR_NAME.png new file mode 100644 index 0000000..b4e7f9f Binary files /dev/null and b/assets/en/cafe/OCR_NAME.png differ diff --git a/assets/en/circle/CIRCLE.BUTTON.png b/assets/en/circle/CIRCLE.BUTTON.png new file mode 100644 index 0000000..77a396e Binary files /dev/null and b/assets/en/circle/CIRCLE.BUTTON.png differ diff --git a/assets/en/circle/CIRCLE.png b/assets/en/circle/CIRCLE.png new file mode 100644 index 0000000..be4e9de Binary files /dev/null and b/assets/en/circle/CIRCLE.png differ diff --git a/assets/en/login/OCR_YEAR.png b/assets/en/login/OCR_YEAR.png new file mode 100644 index 0000000..4db7b6f Binary files /dev/null and b/assets/en/login/OCR_YEAR.png differ diff --git a/assets/en/mission/CHECK_COMMISSIONS.png b/assets/en/mission/CHECK_COMMISSIONS.png new file mode 100644 index 0000000..a4e92e6 Binary files /dev/null and b/assets/en/mission/CHECK_COMMISSIONS.png differ diff --git a/assets/en/mission/CHECK_CR.png b/assets/en/mission/CHECK_CR.png new file mode 100644 index 0000000..6ba97cd Binary files /dev/null and b/assets/en/mission/CHECK_CR.png differ diff --git a/assets/en/mission/CHECK_MISSION_SWEEP.png b/assets/en/mission/CHECK_MISSION_SWEEP.png new file mode 100644 index 0000000..df4347e Binary files /dev/null and b/assets/en/mission/CHECK_MISSION_SWEEP.png differ diff --git a/assets/en/mission/CHECK_XP.png b/assets/en/mission/CHECK_XP.png new file mode 100644 index 0000000..c3413b1 Binary files /dev/null and b/assets/en/mission/CHECK_XP.png differ diff --git a/assets/en/mission/EVENT_INDEX.png b/assets/en/mission/EVENT_INDEX.png new file mode 100644 index 0000000..104fbc4 Binary files /dev/null and b/assets/en/mission/EVENT_INDEX.png differ diff --git a/assets/en/mission/EVENT_ITEM.png b/assets/en/mission/EVENT_ITEM.png new file mode 100644 index 0000000..2f2a2a7 Binary files /dev/null and b/assets/en/mission/EVENT_ITEM.png differ diff --git a/assets/en/mission/EVENT_LIST.png b/assets/en/mission/EVENT_LIST.png new file mode 100644 index 0000000..a3eb22d Binary files /dev/null and b/assets/en/mission/EVENT_LIST.png differ diff --git a/assets/en/mission/EVENT_STARS.png b/assets/en/mission/EVENT_STARS.png new file mode 100644 index 0000000..bdc774b Binary files /dev/null and b/assets/en/mission/EVENT_STARS.png differ diff --git a/assets/en/mission/HARD_OFF.png b/assets/en/mission/HARD_OFF.png new file mode 100644 index 0000000..e41bc2a Binary files /dev/null and b/assets/en/mission/HARD_OFF.png differ diff --git a/assets/en/mission/HARD_ON.png b/assets/en/mission/HARD_ON.png new file mode 100644 index 0000000..7f7dc90 Binary files /dev/null and b/assets/en/mission/HARD_ON.png differ diff --git a/assets/en/mission/LEFT.png b/assets/en/mission/LEFT.png new file mode 100644 index 0000000..f1bc365 Binary files /dev/null and b/assets/en/mission/LEFT.png differ diff --git a/assets/en/mission/NORMAL_OFF.png b/assets/en/mission/NORMAL_OFF.png new file mode 100644 index 0000000..2a5fab9 Binary files /dev/null and b/assets/en/mission/NORMAL_OFF.png differ diff --git a/assets/en/mission/NORMAL_ON.png b/assets/en/mission/NORMAL_ON.png new file mode 100644 index 0000000..39b0af3 Binary files /dev/null and b/assets/en/mission/NORMAL_ON.png differ diff --git a/assets/en/mission/OCR_AREA.png b/assets/en/mission/OCR_AREA.png new file mode 100644 index 0000000..7602382 Binary files /dev/null and b/assets/en/mission/OCR_AREA.png differ diff --git a/assets/en/mission/QUEST_OFF.png b/assets/en/mission/QUEST_OFF.png new file mode 100644 index 0000000..2461875 Binary files /dev/null and b/assets/en/mission/QUEST_OFF.png differ diff --git a/assets/en/mission/QUEST_ON.png b/assets/en/mission/QUEST_ON.png new file mode 100644 index 0000000..1c45adc Binary files /dev/null and b/assets/en/mission/QUEST_ON.png differ diff --git a/assets/en/mission/RIGHT.png b/assets/en/mission/RIGHT.png new file mode 100644 index 0000000..e4c0e74 Binary files /dev/null and b/assets/en/mission/RIGHT.png differ diff --git a/assets/en/mission/SELECT_CR.png b/assets/en/mission/SELECT_CR.png new file mode 100644 index 0000000..39c493d Binary files /dev/null and b/assets/en/mission/SELECT_CR.png differ diff --git a/assets/en/mission/SELECT_XP.png b/assets/en/mission/SELECT_XP.png new file mode 100644 index 0000000..b09f052 Binary files /dev/null and b/assets/en/mission/SELECT_XP.png differ diff --git a/assets/en/momotalk/BEGIN_STORY.png b/assets/en/momotalk/BEGIN_STORY.png new file mode 100644 index 0000000..6d43b89 Binary files /dev/null and b/assets/en/momotalk/BEGIN_STORY.png differ diff --git a/assets/en/momotalk/CHATTING.png b/assets/en/momotalk/CHATTING.png new file mode 100644 index 0000000..3908b0a Binary files /dev/null and b/assets/en/momotalk/CHATTING.png differ diff --git a/assets/en/momotalk/CHAT_AREA.png b/assets/en/momotalk/CHAT_AREA.png new file mode 100644 index 0000000..ed61a8e Binary files /dev/null and b/assets/en/momotalk/CHAT_AREA.png differ diff --git a/assets/en/momotalk/CONFIRM_SKIP.png b/assets/en/momotalk/CONFIRM_SKIP.png new file mode 100644 index 0000000..1da4357 Binary files /dev/null and b/assets/en/momotalk/CONFIRM_SKIP.png differ diff --git a/assets/en/momotalk/CONFIRM_SORT.png b/assets/en/momotalk/CONFIRM_SORT.png new file mode 100644 index 0000000..e34d453 Binary files /dev/null and b/assets/en/momotalk/CONFIRM_SORT.png differ diff --git a/assets/en/momotalk/FIRST_UNREAD.png b/assets/en/momotalk/FIRST_UNREAD.png new file mode 100644 index 0000000..deb3791 Binary files /dev/null and b/assets/en/momotalk/FIRST_UNREAD.png differ diff --git a/assets/en/momotalk/MENU.png b/assets/en/momotalk/MENU.png new file mode 100644 index 0000000..ee8bc4e Binary files /dev/null and b/assets/en/momotalk/MENU.png differ diff --git a/assets/en/momotalk/NOTIFICATION_BADGE.png b/assets/en/momotalk/NOTIFICATION_BADGE.png new file mode 100644 index 0000000..0753e80 Binary files /dev/null and b/assets/en/momotalk/NOTIFICATION_BADGE.png differ diff --git a/assets/en/momotalk/REPLY.png b/assets/en/momotalk/REPLY.png new file mode 100644 index 0000000..7ba2246 Binary files /dev/null and b/assets/en/momotalk/REPLY.png differ diff --git a/assets/en/momotalk/SELECT_STUDENT.png b/assets/en/momotalk/SELECT_STUDENT.png new file mode 100644 index 0000000..8dcc592 Binary files /dev/null and b/assets/en/momotalk/SELECT_STUDENT.png differ diff --git a/assets/en/momotalk/SKIP.png b/assets/en/momotalk/SKIP.png new file mode 100644 index 0000000..b46c299 Binary files /dev/null and b/assets/en/momotalk/SKIP.png differ diff --git a/assets/en/momotalk/SORT_ASCENDING.png b/assets/en/momotalk/SORT_ASCENDING.png new file mode 100644 index 0000000..617998d Binary files /dev/null and b/assets/en/momotalk/SORT_ASCENDING.png differ diff --git a/assets/en/momotalk/SORT_DESCENDING.png b/assets/en/momotalk/SORT_DESCENDING.png new file mode 100644 index 0000000..f97331a Binary files /dev/null and b/assets/en/momotalk/SORT_DESCENDING.png differ diff --git a/assets/en/momotalk/STORY.png b/assets/en/momotalk/STORY.png new file mode 100644 index 0000000..b9b8a50 Binary files /dev/null and b/assets/en/momotalk/STORY.png differ diff --git a/assets/jp/base/page/MOMOTALK_GO_TO_MAIN.png b/assets/en/momotalk/SWITCH_MESSAGE.png similarity index 63% rename from assets/jp/base/page/MOMOTALK_GO_TO_MAIN.png rename to assets/en/momotalk/SWITCH_MESSAGE.png index 6eda2d7..bce95dd 100644 Binary files a/assets/jp/base/page/MOMOTALK_GO_TO_MAIN.png and b/assets/en/momotalk/SWITCH_MESSAGE.png differ diff --git a/assets/en/momotalk/SWITCH_MESSAGE_CHECK.png b/assets/en/momotalk/SWITCH_MESSAGE_CHECK.png new file mode 100644 index 0000000..fa7dd35 Binary files /dev/null and b/assets/en/momotalk/SWITCH_MESSAGE_CHECK.png differ diff --git a/assets/en/momotalk/SWITCH_STUDENT.png b/assets/en/momotalk/SWITCH_STUDENT.png new file mode 100644 index 0000000..caf65f0 Binary files /dev/null and b/assets/en/momotalk/SWITCH_STUDENT.png differ diff --git a/assets/en/momotalk/SWITCH_STUDENT_CHECK.png b/assets/en/momotalk/SWITCH_STUDENT_CHECK.png new file mode 100644 index 0000000..3bb5f55 Binary files /dev/null and b/assets/en/momotalk/SWITCH_STUDENT_CHECK.png differ diff --git a/assets/en/momotalk/UNREAD.png b/assets/en/momotalk/UNREAD.png new file mode 100644 index 0000000..091294e Binary files /dev/null and b/assets/en/momotalk/UNREAD.png differ diff --git a/assets/en/momotalk/UNREAD_OFF.png b/assets/en/momotalk/UNREAD_OFF.png new file mode 100644 index 0000000..1038f12 Binary files /dev/null and b/assets/en/momotalk/UNREAD_OFF.png differ diff --git a/assets/en/momotalk/UNREAD_ON.png b/assets/en/momotalk/UNREAD_ON.png new file mode 100644 index 0000000..c8a43d0 Binary files /dev/null and b/assets/en/momotalk/UNREAD_ON.png differ diff --git a/assets/en/schedule/CONFIRM.png b/assets/en/schedule/CONFIRM.png new file mode 100644 index 0000000..6fa6985 Binary files /dev/null and b/assets/en/schedule/CONFIRM.png differ diff --git a/assets/en/schedule/FIRST_ITEM.png b/assets/en/schedule/FIRST_ITEM.png new file mode 100644 index 0000000..6de565e Binary files /dev/null and b/assets/en/schedule/FIRST_ITEM.png differ diff --git a/assets/en/schedule/LOCATIONS.png b/assets/en/schedule/LOCATIONS.png new file mode 100644 index 0000000..fe96764 Binary files /dev/null and b/assets/en/schedule/LOCATIONS.png differ diff --git a/assets/en/schedule/LOCATIONS_POPUP.png b/assets/en/schedule/LOCATIONS_POPUP.png new file mode 100644 index 0000000..199c52b Binary files /dev/null and b/assets/en/schedule/LOCATIONS_POPUP.png differ diff --git a/assets/en/schedule/OCR_TICKET.png b/assets/en/schedule/OCR_TICKET.png new file mode 100644 index 0000000..2b29ef4 Binary files /dev/null and b/assets/en/schedule/OCR_TICKET.png differ diff --git a/assets/en/schedule/START_LESSON.png b/assets/en/schedule/START_LESSON.png new file mode 100644 index 0000000..ff064d4 Binary files /dev/null and b/assets/en/schedule/START_LESSON.png differ diff --git a/assets/en/scrimmage/CHECK_GEHENNA.png b/assets/en/scrimmage/CHECK_GEHENNA.png new file mode 100644 index 0000000..6aad243 Binary files /dev/null and b/assets/en/scrimmage/CHECK_GEHENNA.png differ diff --git a/assets/en/scrimmage/CHECK_MILLENNIUM.png b/assets/en/scrimmage/CHECK_MILLENNIUM.png new file mode 100644 index 0000000..5703b75 Binary files /dev/null and b/assets/en/scrimmage/CHECK_MILLENNIUM.png differ diff --git a/assets/en/scrimmage/CHECK_SCRIMMAGE.png b/assets/en/scrimmage/CHECK_SCRIMMAGE.png new file mode 100644 index 0000000..da4ee47 Binary files /dev/null and b/assets/en/scrimmage/CHECK_SCRIMMAGE.png differ diff --git a/assets/en/scrimmage/CHECK_TRINITY.png b/assets/en/scrimmage/CHECK_TRINITY.png new file mode 100644 index 0000000..0b530b5 Binary files /dev/null and b/assets/en/scrimmage/CHECK_TRINITY.png differ diff --git a/assets/en/scrimmage/OCR_TICKET.png b/assets/en/scrimmage/OCR_TICKET.png new file mode 100644 index 0000000..51de98f Binary files /dev/null and b/assets/en/scrimmage/OCR_TICKET.png differ diff --git a/assets/en/scrimmage/SELECT_GEHENNA.png b/assets/en/scrimmage/SELECT_GEHENNA.png new file mode 100644 index 0000000..c10653f Binary files /dev/null and b/assets/en/scrimmage/SELECT_GEHENNA.png differ diff --git a/assets/en/scrimmage/SELECT_MILLENNIUM.png b/assets/en/scrimmage/SELECT_MILLENNIUM.png new file mode 100644 index 0000000..a2f6441 Binary files /dev/null and b/assets/en/scrimmage/SELECT_MILLENNIUM.png differ diff --git a/assets/en/scrimmage/SELECT_TRINITY.png b/assets/en/scrimmage/SELECT_TRINITY.png new file mode 100644 index 0000000..529d373 Binary files /dev/null and b/assets/en/scrimmage/SELECT_TRINITY.png differ diff --git a/assets/en/shop/CONFIRM_PURCHASE.BUTTON.png b/assets/en/shop/CONFIRM_PURCHASE.BUTTON.png new file mode 100644 index 0000000..131d1d2 Binary files /dev/null and b/assets/en/shop/CONFIRM_PURCHASE.BUTTON.png differ diff --git a/assets/en/shop/CONFIRM_PURCHASE.png b/assets/en/shop/CONFIRM_PURCHASE.png new file mode 100644 index 0000000..c81e0b9 Binary files /dev/null and b/assets/en/shop/CONFIRM_PURCHASE.png differ diff --git a/assets/en/shop/CONFIRM_REFRESH.BUTTON.png b/assets/en/shop/CONFIRM_REFRESH.BUTTON.png new file mode 100644 index 0000000..695912e Binary files /dev/null and b/assets/en/shop/CONFIRM_REFRESH.BUTTON.png differ diff --git a/assets/en/shop/CONFIRM_REFRESH.png b/assets/en/shop/CONFIRM_REFRESH.png new file mode 100644 index 0000000..ec30fc4 Binary files /dev/null and b/assets/en/shop/CONFIRM_REFRESH.png differ diff --git a/assets/en/shop/ITEM_LIST.png b/assets/en/shop/ITEM_LIST.png new file mode 100644 index 0000000..791f09c Binary files /dev/null and b/assets/en/shop/ITEM_LIST.png differ diff --git a/assets/en/shop/NORMAL_OFF.png b/assets/en/shop/NORMAL_OFF.png new file mode 100644 index 0000000..bb8b179 Binary files /dev/null and b/assets/en/shop/NORMAL_OFF.png differ diff --git a/assets/en/shop/NORMAL_ON.png b/assets/en/shop/NORMAL_ON.png new file mode 100644 index 0000000..d2a453a Binary files /dev/null and b/assets/en/shop/NORMAL_ON.png differ diff --git a/assets/en/shop/OCR_REFRESH.png b/assets/en/shop/OCR_REFRESH.png new file mode 100644 index 0000000..eb095d0 Binary files /dev/null and b/assets/en/shop/OCR_REFRESH.png differ diff --git a/assets/en/shop/PURCHASE.png b/assets/en/shop/PURCHASE.png new file mode 100644 index 0000000..ea6cffe Binary files /dev/null and b/assets/en/shop/PURCHASE.png differ diff --git a/assets/en/shop/REFRESH.png b/assets/en/shop/REFRESH.png new file mode 100644 index 0000000..d49e512 Binary files /dev/null and b/assets/en/shop/REFRESH.png differ diff --git a/assets/en/shop/TC_OFF.png b/assets/en/shop/TC_OFF.png new file mode 100644 index 0000000..2cf1d34 Binary files /dev/null and b/assets/en/shop/TC_OFF.png differ diff --git a/assets/en/shop/TC_ON.png b/assets/en/shop/TC_ON.png new file mode 100644 index 0000000..16f9d8a Binary files /dev/null and b/assets/en/shop/TC_ON.png differ diff --git a/assets/en/stage/list/OCR_INDEX.png b/assets/en/stage/list/OCR_INDEX.png new file mode 100644 index 0000000..7cd4625 Binary files /dev/null and b/assets/en/stage/list/OCR_INDEX.png differ diff --git a/assets/en/stage/list/STAGE_ENTER.png b/assets/en/stage/list/STAGE_ENTER.png new file mode 100644 index 0000000..fdf51ff Binary files /dev/null and b/assets/en/stage/list/STAGE_ENTER.png differ diff --git a/assets/en/stage/list/STAGE_ITEM.png b/assets/en/stage/list/STAGE_ITEM.png new file mode 100644 index 0000000..1df5b9f Binary files /dev/null and b/assets/en/stage/list/STAGE_ITEM.png differ diff --git a/assets/en/stage/list/STAGE_LIST.png b/assets/en/stage/list/STAGE_LIST.png new file mode 100644 index 0000000..1e8eaf4 Binary files /dev/null and b/assets/en/stage/list/STAGE_LIST.png differ diff --git a/assets/en/stage/list/STAGE_STARS.png b/assets/en/stage/list/STAGE_STARS.png new file mode 100644 index 0000000..07cc44a Binary files /dev/null and b/assets/en/stage/list/STAGE_STARS.png differ diff --git a/assets/en/stage/sweep/CHECK_SWEEP.png b/assets/en/stage/sweep/CHECK_SWEEP.png new file mode 100644 index 0000000..882b50d Binary files /dev/null and b/assets/en/stage/sweep/CHECK_SWEEP.png differ diff --git a/assets/en/stage/sweep/ENTER.png b/assets/en/stage/sweep/ENTER.png new file mode 100644 index 0000000..ff972a0 Binary files /dev/null and b/assets/en/stage/sweep/ENTER.png differ diff --git a/assets/en/stage/sweep/EXIT.png b/assets/en/stage/sweep/EXIT.png new file mode 100644 index 0000000..1f0b137 Binary files /dev/null and b/assets/en/stage/sweep/EXIT.png differ diff --git a/assets/en/stage/sweep/MAX.png b/assets/en/stage/sweep/MAX.png new file mode 100644 index 0000000..52f5d85 Binary files /dev/null and b/assets/en/stage/sweep/MAX.png differ diff --git a/assets/en/stage/sweep/MIN.png b/assets/en/stage/sweep/MIN.png new file mode 100644 index 0000000..f583aea Binary files /dev/null and b/assets/en/stage/sweep/MIN.png differ diff --git a/assets/en/stage/sweep/MINUS.png b/assets/en/stage/sweep/MINUS.png new file mode 100644 index 0000000..bd57844 Binary files /dev/null and b/assets/en/stage/sweep/MINUS.png differ diff --git a/assets/en/stage/sweep/OCR_NUM.png b/assets/en/stage/sweep/OCR_NUM.png new file mode 100644 index 0000000..ffb06ec Binary files /dev/null and b/assets/en/stage/sweep/OCR_NUM.png differ diff --git a/assets/en/stage/sweep/PLUS.png b/assets/en/stage/sweep/PLUS.png new file mode 100644 index 0000000..106e382 Binary files /dev/null and b/assets/en/stage/sweep/PLUS.png differ diff --git a/assets/en/stage/sweep/SKIP_OK_LOWER.png b/assets/en/stage/sweep/SKIP_OK_LOWER.png new file mode 100644 index 0000000..347c3ba Binary files /dev/null and b/assets/en/stage/sweep/SKIP_OK_LOWER.png differ diff --git a/assets/en/stage/sweep/SKIP_OK_UPPER.png b/assets/en/stage/sweep/SKIP_OK_UPPER.png new file mode 100644 index 0000000..c2ed859 Binary files /dev/null and b/assets/en/stage/sweep/SKIP_OK_UPPER.png differ diff --git a/assets/en/stage/sweep/SKIP_SKIP.png b/assets/en/stage/sweep/SKIP_SKIP.png new file mode 100644 index 0000000..f6ae70a Binary files /dev/null and b/assets/en/stage/sweep/SKIP_SKIP.png differ diff --git a/assets/en/stage/sweep/SWEEP.png b/assets/en/stage/sweep/SWEEP.png new file mode 100644 index 0000000..d6914ed Binary files /dev/null and b/assets/en/stage/sweep/SWEEP.png differ diff --git a/assets/en/stage/sweep/SWEEP_CONFIRM.BUTTON.png b/assets/en/stage/sweep/SWEEP_CONFIRM.BUTTON.png new file mode 100644 index 0000000..0df011f Binary files /dev/null and b/assets/en/stage/sweep/SWEEP_CONFIRM.BUTTON.png differ diff --git a/assets/en/stage/sweep/SWEEP_CONFIRM.png b/assets/en/stage/sweep/SWEEP_CONFIRM.png new file mode 100644 index 0000000..40195de Binary files /dev/null and b/assets/en/stage/sweep/SWEEP_CONFIRM.png differ diff --git a/assets/en/tactical_challenge/PLAYER_SELECT_FIRST.png b/assets/en/tactical_challenge/PLAYER_SELECT_FIRST.png index 8508a54..8dd05b7 100644 Binary files a/assets/en/tactical_challenge/PLAYER_SELECT_FIRST.png and b/assets/en/tactical_challenge/PLAYER_SELECT_FIRST.png differ diff --git a/assets/en/tactical_challenge/PLAYER_SELECT_SECOND.png b/assets/en/tactical_challenge/PLAYER_SELECT_SECOND.png index 0336841..98a635c 100644 Binary files a/assets/en/tactical_challenge/PLAYER_SELECT_SECOND.png and b/assets/en/tactical_challenge/PLAYER_SELECT_SECOND.png differ diff --git a/assets/en/tactical_challenge/PLAYER_SELECT_THIRD.png b/assets/en/tactical_challenge/PLAYER_SELECT_THIRD.png index 8f5ed67..1a490fc 100644 Binary files a/assets/en/tactical_challenge/PLAYER_SELECT_THIRD.png and b/assets/en/tactical_challenge/PLAYER_SELECT_THIRD.png differ diff --git a/assets/en/task/CLAIM.png b/assets/en/task/CLAIM.png new file mode 100644 index 0000000..c0cb101 Binary files /dev/null and b/assets/en/task/CLAIM.png differ diff --git a/assets/en/task/CLAIMED.png b/assets/en/task/CLAIMED.png new file mode 100644 index 0000000..161f6cc Binary files /dev/null and b/assets/en/task/CLAIMED.png differ diff --git a/assets/en/task/CLAIMED_ALL.png b/assets/en/task/CLAIMED_ALL.png new file mode 100644 index 0000000..d556ddb Binary files /dev/null and b/assets/en/task/CLAIMED_ALL.png differ diff --git a/assets/en/task/CLAIM_ALL.png b/assets/en/task/CLAIM_ALL.png new file mode 100644 index 0000000..f5a69a4 Binary files /dev/null and b/assets/en/task/CLAIM_ALL.png differ diff --git a/assets/en/task/COMPLETE.png b/assets/en/task/COMPLETE.png new file mode 100644 index 0000000..3068a8d Binary files /dev/null and b/assets/en/task/COMPLETE.png differ diff --git a/assets/gui/css/alas.css b/assets/gui/css/alas.css index d907940..3f25c87 100644 --- a/assets/gui/css/alas.css +++ b/assets/gui/css/alas.css @@ -377,6 +377,7 @@ pre.rich-traceback-code { #pywebio-scope-scheduler-bar, #pywebio-scope-log-bar, #pywebio-scope-log, +#pywebio-scope-daemon-log-bar, #pywebio-scope-daemon-overview #pywebio-scope-groups { font-weight: 500; margin: 0.3125rem; diff --git a/assets/gui/css/dark-alas.css b/assets/gui/css/dark-alas.css index c2d76e7..62fc2e2 100644 --- a/assets/gui/css/dark-alas.css +++ b/assets/gui/css/dark-alas.css @@ -133,12 +133,13 @@ pre.rich-traceback-code { color: #c9d1d9; } -#pywebio-scope-scheduler-bar, -#pywebio-scope-log-bar, -#pywebio-scope-log, #pywebio-scope-running, #pywebio-scope-pending, #pywebio-scope-waiting, +#pywebio-scope-scheduler-bar, +#pywebio-scope-log-bar, +#pywebio-scope-log, +#pywebio-scope-daemon-log-bar, #pywebio-scope-daemon-overview #pywebio-scope-groups { background-color: #2f3136; border: 1px solid #21262d; diff --git a/assets/gui/css/light-alas.css b/assets/gui/css/light-alas.css index 171d565..6a8be72 100644 --- a/assets/gui/css/light-alas.css +++ b/assets/gui/css/light-alas.css @@ -133,12 +133,13 @@ pre.rich-traceback-code { border: 1px solid lightgrey; } -#pywebio-scope-scheduler-bar, -#pywebio-scope-log-bar, -#pywebio-scope-log, #pywebio-scope-running, #pywebio-scope-pending, #pywebio-scope-waiting, +#pywebio-scope-scheduler-bar, +#pywebio-scope-log-bar, +#pywebio-scope-log, +#pywebio-scope-daemon-log-bar, #pywebio-scope-daemon-overview #pywebio-scope-groups { background-color: white; border: 1px solid lightgrey; diff --git a/assets/jp/base/page/CAFE_CHECK.png b/assets/jp/base/page/CAFE_CHECK.png index 9701c4e..adbf37a 100644 Binary files a/assets/jp/base/page/CAFE_CHECK.png and b/assets/jp/base/page/CAFE_CHECK.png differ diff --git a/assets/jp/base/page/EVENT_CHECK.png b/assets/jp/base/page/EVENT_CHECK.png new file mode 100644 index 0000000..ec30691 Binary files /dev/null and b/assets/jp/base/page/EVENT_CHECK.png differ diff --git a/assets/jp/base/page/HOME.png b/assets/jp/base/page/HOME.png index 6cc85e1..106c4f6 100644 Binary files a/assets/jp/base/page/HOME.png and b/assets/jp/base/page/HOME.png differ diff --git a/assets/jp/base/page/MAIN_GO_TO_CAFE.png b/assets/jp/base/page/MAIN_GO_TO_CAFE.png index 5547831..d4a95a0 100644 Binary files a/assets/jp/base/page/MAIN_GO_TO_CAFE.png and b/assets/jp/base/page/MAIN_GO_TO_CAFE.png differ diff --git a/assets/jp/base/page/MAIN_GO_TO_CIRCLE.png b/assets/jp/base/page/MAIN_GO_TO_CIRCLE.png index e4d1169..8fd21a0 100644 Binary files a/assets/jp/base/page/MAIN_GO_TO_CIRCLE.png and b/assets/jp/base/page/MAIN_GO_TO_CIRCLE.png differ diff --git a/assets/jp/base/page/MAIN_GO_TO_CRAFTING.png b/assets/jp/base/page/MAIN_GO_TO_CRAFTING.png index e607c6a..51586fd 100644 Binary files a/assets/jp/base/page/MAIN_GO_TO_CRAFTING.png and b/assets/jp/base/page/MAIN_GO_TO_CRAFTING.png differ diff --git a/assets/jp/base/page/MAIN_GO_TO_GACHA.png b/assets/jp/base/page/MAIN_GO_TO_GACHA.png index a7e8d33..10b2db5 100644 Binary files a/assets/jp/base/page/MAIN_GO_TO_GACHA.png and b/assets/jp/base/page/MAIN_GO_TO_GACHA.png differ diff --git a/assets/jp/base/page/MAIN_GO_TO_MAIL.png b/assets/jp/base/page/MAIN_GO_TO_MAIL.png index 202ae87..0eec906 100644 Binary files a/assets/jp/base/page/MAIN_GO_TO_MAIL.png and b/assets/jp/base/page/MAIN_GO_TO_MAIL.png differ diff --git a/assets/jp/base/page/MAIN_GO_TO_MOMOTALK.png b/assets/jp/base/page/MAIN_GO_TO_MOMOTALK.png index 58d3cf1..31be026 100644 Binary files a/assets/jp/base/page/MAIN_GO_TO_MOMOTALK.png and b/assets/jp/base/page/MAIN_GO_TO_MOMOTALK.png differ diff --git a/assets/jp/base/page/MAIN_GO_TO_PURCHASE.png b/assets/jp/base/page/MAIN_GO_TO_PURCHASE.png index 4e365b3..29ddd32 100644 Binary files a/assets/jp/base/page/MAIN_GO_TO_PURCHASE.png and b/assets/jp/base/page/MAIN_GO_TO_PURCHASE.png differ diff --git a/assets/jp/base/page/MAIN_GO_TO_SCHEDULE.png b/assets/jp/base/page/MAIN_GO_TO_SCHEDULE.png index 146282b..8a2c74e 100644 Binary files a/assets/jp/base/page/MAIN_GO_TO_SCHEDULE.png and b/assets/jp/base/page/MAIN_GO_TO_SCHEDULE.png differ diff --git a/assets/jp/base/page/MAIN_GO_TO_SHOP.png b/assets/jp/base/page/MAIN_GO_TO_SHOP.png index a8e8d89..0d9b8ef 100644 Binary files a/assets/jp/base/page/MAIN_GO_TO_SHOP.png and b/assets/jp/base/page/MAIN_GO_TO_SHOP.png differ diff --git a/assets/jp/base/page/MAIN_GO_TO_TASK.png b/assets/jp/base/page/MAIN_GO_TO_TASK.png index 7dae9e8..9c91d1f 100644 Binary files a/assets/jp/base/page/MAIN_GO_TO_TASK.png and b/assets/jp/base/page/MAIN_GO_TO_TASK.png differ diff --git a/assets/jp/base/page/MAIN_GO_TO_WORK.png b/assets/jp/base/page/MAIN_GO_TO_WORK.png index 5b5560b..5e7e5b5 100644 Binary files a/assets/jp/base/page/MAIN_GO_TO_WORK.png and b/assets/jp/base/page/MAIN_GO_TO_WORK.png differ diff --git a/assets/jp/base/page/WORK_GO_TO_BOUNTY.png b/assets/jp/base/page/WORK_GO_TO_BOUNTY.png index 71f03e1..cddf9db 100644 Binary files a/assets/jp/base/page/WORK_GO_TO_BOUNTY.png and b/assets/jp/base/page/WORK_GO_TO_BOUNTY.png differ diff --git a/assets/jp/base/page/WORK_GO_TO_COMMISSIONS.png b/assets/jp/base/page/WORK_GO_TO_COMMISSIONS.png index 256a5ef..a1d9693 100644 Binary files a/assets/jp/base/page/WORK_GO_TO_COMMISSIONS.png and b/assets/jp/base/page/WORK_GO_TO_COMMISSIONS.png differ diff --git a/assets/jp/base/page/WORK_GO_TO_EVENT.png b/assets/jp/base/page/WORK_GO_TO_EVENT.png new file mode 100644 index 0000000..5b5f1a7 Binary files /dev/null and b/assets/jp/base/page/WORK_GO_TO_EVENT.png differ diff --git a/assets/jp/base/page/WORK_GO_TO_MISSION.png b/assets/jp/base/page/WORK_GO_TO_MISSION.png index 226c930..288bc94 100644 Binary files a/assets/jp/base/page/WORK_GO_TO_MISSION.png and b/assets/jp/base/page/WORK_GO_TO_MISSION.png differ diff --git a/assets/jp/base/page/WORK_GO_TO_SCHOOL_EXCHANGE.png b/assets/jp/base/page/WORK_GO_TO_SCHOOL_EXCHANGE.png index 8f48008..ea08e8d 100644 Binary files a/assets/jp/base/page/WORK_GO_TO_SCHOOL_EXCHANGE.png and b/assets/jp/base/page/WORK_GO_TO_SCHOOL_EXCHANGE.png differ diff --git a/assets/jp/base/page/WORK_GO_TO_STORY.png b/assets/jp/base/page/WORK_GO_TO_STORY.png index 7275e22..8c34715 100644 Binary files a/assets/jp/base/page/WORK_GO_TO_STORY.png and b/assets/jp/base/page/WORK_GO_TO_STORY.png differ diff --git a/assets/jp/base/page/WORK_GO_TO_TACTICAL_CHALLENGE.png b/assets/jp/base/page/WORK_GO_TO_TACTICAL_CHALLENGE.png index 44b0fe6..280ae10 100644 Binary files a/assets/jp/base/page/WORK_GO_TO_TACTICAL_CHALLENGE.png and b/assets/jp/base/page/WORK_GO_TO_TACTICAL_CHALLENGE.png differ diff --git a/assets/jp/base/popup/AP_EXCEED.png b/assets/jp/base/popup/AP_EXCEED.png index 43693c7..9c6b200 100644 Binary files a/assets/jp/base/popup/AP_EXCEED.png and b/assets/jp/base/popup/AP_EXCEED.png differ diff --git a/assets/jp/base/popup/DAILY_NEWS.png b/assets/jp/base/popup/DAILY_NEWS.png deleted file mode 100644 index 2f9da4b..0000000 Binary files a/assets/jp/base/popup/DAILY_NEWS.png and /dev/null differ diff --git a/assets/jp/base/popup/DAILY_REWARD.BUTTON.png b/assets/jp/base/popup/DAILY_REWARD.BUTTON.png deleted file mode 100644 index 1989880..0000000 Binary files a/assets/jp/base/popup/DAILY_REWARD.BUTTON.png and /dev/null differ diff --git a/assets/jp/base/popup/DAILY_REWARD.png b/assets/jp/base/popup/DAILY_REWARD.png deleted file mode 100644 index 57eb310..0000000 Binary files a/assets/jp/base/popup/DAILY_REWARD.png and /dev/null differ diff --git a/assets/jp/base/popup/GET_NEW_STUDENT.BUTTON.png b/assets/jp/base/popup/GET_NEW_STUDENT.BUTTON.png deleted file mode 100644 index da6e87e..0000000 Binary files a/assets/jp/base/popup/GET_NEW_STUDENT.BUTTON.png and /dev/null differ diff --git a/assets/jp/base/popup/GET_NEW_STUDENT.png b/assets/jp/base/popup/GET_NEW_STUDENT.png deleted file mode 100644 index 7e9fe54..0000000 Binary files a/assets/jp/base/popup/GET_NEW_STUDENT.png and /dev/null differ diff --git a/assets/jp/base/popup/INSUFFICIENT_INVENTORY.png b/assets/jp/base/popup/INSUFFICIENT_INVENTORY.png index 662c298..0f5b15f 100644 Binary files a/assets/jp/base/popup/INSUFFICIENT_INVENTORY.png and b/assets/jp/base/popup/INSUFFICIENT_INVENTORY.png differ diff --git a/assets/jp/base/popup/LEVEL_UP.BUTTON.png b/assets/jp/base/popup/LEVEL_UP.BUTTON.png new file mode 100644 index 0000000..a8e8b85 Binary files /dev/null and b/assets/jp/base/popup/LEVEL_UP.BUTTON.png differ diff --git a/assets/jp/base/popup/LEVEL_UP.png b/assets/jp/base/popup/LEVEL_UP.png new file mode 100644 index 0000000..19ded3d Binary files /dev/null and b/assets/jp/base/popup/LEVEL_UP.png differ diff --git a/assets/jp/base/popup/LOCATION_LEVEL_UP.BUTTON.png b/assets/jp/base/popup/LOCATION_LEVEL_UP.BUTTON.png new file mode 100644 index 0000000..a9f6b33 Binary files /dev/null and b/assets/jp/base/popup/LOCATION_LEVEL_UP.BUTTON.png differ diff --git a/assets/jp/base/popup/LOCATION_LEVEL_UP.png b/assets/jp/base/popup/LOCATION_LEVEL_UP.png new file mode 100644 index 0000000..cee4301 Binary files /dev/null and b/assets/jp/base/popup/LOCATION_LEVEL_UP.png differ diff --git a/assets/jp/base/popup/NETWORK_RECONNECT_OK.BUTTON.png b/assets/jp/base/popup/NETWORK_RECONNECT_OK.BUTTON.png index 265d2e0..094d0a8 100644 Binary files a/assets/jp/base/popup/NETWORK_RECONNECT_OK.BUTTON.png and b/assets/jp/base/popup/NETWORK_RECONNECT_OK.BUTTON.png differ diff --git a/assets/jp/base/popup/QUIT.BUTTON.png b/assets/jp/base/popup/QUIT.BUTTON.png new file mode 100644 index 0000000..094d0a8 Binary files /dev/null and b/assets/jp/base/popup/QUIT.BUTTON.png differ diff --git a/assets/jp/base/popup/QUIT.png b/assets/jp/base/popup/QUIT.png new file mode 100644 index 0000000..ac4e79d Binary files /dev/null and b/assets/jp/base/popup/QUIT.png differ diff --git a/assets/jp/bounty/OCR_TICKET.png b/assets/jp/bounty/OCR_TICKET.png index 0362308..a6c4f42 100644 Binary files a/assets/jp/bounty/OCR_TICKET.png and b/assets/jp/bounty/OCR_TICKET.png differ diff --git a/assets/jp/cafe/BOX_CAFE.png b/assets/jp/cafe/BOX_CAFE.png deleted file mode 100644 index 6a883b1..0000000 Binary files a/assets/jp/cafe/BOX_CAFE.png and /dev/null differ diff --git a/assets/jp/cafe/CAFE_FIRST.png b/assets/jp/cafe/CAFE_FIRST.png index a57fd71..c533b41 100644 Binary files a/assets/jp/cafe/CAFE_FIRST.png and b/assets/jp/cafe/CAFE_FIRST.png differ diff --git a/assets/jp/cafe/CAFE_INVITE.png b/assets/jp/cafe/CAFE_INVITE.png new file mode 100644 index 0000000..e6108e3 Binary files /dev/null and b/assets/jp/cafe/CAFE_INVITE.png differ diff --git a/assets/jp/cafe/CAFE_INVITED.png b/assets/jp/cafe/CAFE_INVITED.png new file mode 100644 index 0000000..1500990 Binary files /dev/null and b/assets/jp/cafe/CAFE_INVITED.png differ diff --git a/assets/jp/cafe/CAFE_SECOND.png b/assets/jp/cafe/CAFE_SECOND.png index 34bfc7b..e12e734 100644 Binary files a/assets/jp/cafe/CAFE_SECOND.png and b/assets/jp/cafe/CAFE_SECOND.png differ diff --git a/assets/jp/cafe/CHANGE_CAFE_NOT_SELECTED.png b/assets/jp/cafe/CHANGE_CAFE_NOT_SELECTED.png deleted file mode 100644 index 4e976d7..0000000 Binary files a/assets/jp/cafe/CHANGE_CAFE_NOT_SELECTED.png and /dev/null differ diff --git a/assets/jp/cafe/CHANGE_CAFE_SELECTED.png b/assets/jp/cafe/CHANGE_CAFE_SELECTED.png deleted file mode 100644 index ab5b185..0000000 Binary files a/assets/jp/cafe/CHANGE_CAFE_SELECTED.png and /dev/null differ diff --git a/assets/jp/cafe/CHECK_MOMOTALK.png b/assets/jp/cafe/CHECK_MOMOTALK.png new file mode 100644 index 0000000..9c047b8 Binary files /dev/null and b/assets/jp/cafe/CHECK_MOMOTALK.png differ diff --git a/assets/jp/cafe/CHECK_REWARD.BUTTON.png b/assets/jp/cafe/CHECK_REWARD.BUTTON.png index 516bad7..2fbeaf9 100644 Binary files a/assets/jp/cafe/CHECK_REWARD.BUTTON.png and b/assets/jp/cafe/CHECK_REWARD.BUTTON.png differ diff --git a/assets/jp/cafe/CHECK_REWARD.png b/assets/jp/cafe/CHECK_REWARD.png index 6e991d5..86a3c3f 100644 Binary files a/assets/jp/cafe/CHECK_REWARD.png and b/assets/jp/cafe/CHECK_REWARD.png differ diff --git a/assets/jp/cafe/CLICKABLE_TEMPLATE.png b/assets/jp/cafe/CLICKABLE_TEMPLATE.png deleted file mode 100644 index fb6344b..0000000 Binary files a/assets/jp/cafe/CLICKABLE_TEMPLATE.png and /dev/null differ diff --git a/assets/jp/cafe/INVITE_CONFIRM.BUTTON.png b/assets/jp/cafe/INVITE_CONFIRM.BUTTON.png new file mode 100644 index 0000000..ede6695 Binary files /dev/null and b/assets/jp/cafe/INVITE_CONFIRM.BUTTON.png differ diff --git a/assets/jp/cafe/INVITE_CONFIRM.png b/assets/jp/cafe/INVITE_CONFIRM.png new file mode 100644 index 0000000..8d2d706 Binary files /dev/null and b/assets/jp/cafe/INVITE_CONFIRM.png differ diff --git a/assets/jp/cafe/INVITE_IN_SECOND.png b/assets/jp/cafe/INVITE_IN_SECOND.png new file mode 100644 index 0000000..3a786ae Binary files /dev/null and b/assets/jp/cafe/INVITE_IN_SECOND.png differ diff --git a/assets/jp/cafe/INVITE_IN_SECOND_CLOSE.png b/assets/jp/cafe/INVITE_IN_SECOND_CLOSE.png new file mode 100644 index 0000000..0bb17d2 Binary files /dev/null and b/assets/jp/cafe/INVITE_IN_SECOND_CLOSE.png differ diff --git a/assets/jp/cafe/INVITE_SUBSTITUTE.BUTTON.png b/assets/jp/cafe/INVITE_SUBSTITUTE.BUTTON.png new file mode 100644 index 0000000..707425b Binary files /dev/null and b/assets/jp/cafe/INVITE_SUBSTITUTE.BUTTON.png differ diff --git a/assets/jp/cafe/INVITE_SUBSTITUTE.png b/assets/jp/cafe/INVITE_SUBSTITUTE.png new file mode 100644 index 0000000..399911c Binary files /dev/null and b/assets/jp/cafe/INVITE_SUBSTITUTE.png differ diff --git a/assets/jp/cafe/INVITE_SUBSTITUTE_CLOSE.png b/assets/jp/cafe/INVITE_SUBSTITUTE_CLOSE.png new file mode 100644 index 0000000..61a3704 Binary files /dev/null and b/assets/jp/cafe/INVITE_SUBSTITUTE_CLOSE.png differ diff --git a/assets/jp/cafe/MOMOTALK_INVITE.png b/assets/jp/cafe/MOMOTALK_INVITE.png new file mode 100644 index 0000000..3c266a6 Binary files /dev/null and b/assets/jp/cafe/MOMOTALK_INVITE.png differ diff --git a/assets/jp/cafe/MOMOTALK_ITEM.png b/assets/jp/cafe/MOMOTALK_ITEM.png new file mode 100644 index 0000000..4d487cf Binary files /dev/null and b/assets/jp/cafe/MOMOTALK_ITEM.png differ diff --git a/assets/jp/cafe/OCR_CAFE.png b/assets/jp/cafe/OCR_CAFE.png index 080ba07..12d8245 100644 Binary files a/assets/jp/cafe/OCR_CAFE.png and b/assets/jp/cafe/OCR_CAFE.png differ diff --git a/assets/jp/cafe/OCR_NAME.png b/assets/jp/cafe/OCR_NAME.png new file mode 100644 index 0000000..b4e7f9f Binary files /dev/null and b/assets/jp/cafe/OCR_NAME.png differ diff --git a/assets/jp/circle/CIRCLE.BUTTON.png b/assets/jp/circle/CIRCLE.BUTTON.png new file mode 100644 index 0000000..cb57602 Binary files /dev/null and b/assets/jp/circle/CIRCLE.BUTTON.png differ diff --git a/assets/jp/circle/CIRCLE.png b/assets/jp/circle/CIRCLE.png new file mode 100644 index 0000000..ac2697f Binary files /dev/null and b/assets/jp/circle/CIRCLE.png differ diff --git a/assets/jp/item/data/OCR_AP.png b/assets/jp/item/data/OCR_AP.png new file mode 100644 index 0000000..88fe939 Binary files /dev/null and b/assets/jp/item/data/OCR_AP.png differ diff --git a/assets/jp/item/data/OCR_DATA.png b/assets/jp/item/data/OCR_DATA.png new file mode 100644 index 0000000..4178063 Binary files /dev/null and b/assets/jp/item/data/OCR_DATA.png differ diff --git a/assets/jp/login/OCR_YEAR.png b/assets/jp/login/OCR_YEAR.png new file mode 100644 index 0000000..4db7b6f Binary files /dev/null and b/assets/jp/login/OCR_YEAR.png differ diff --git a/assets/jp/mission/CHECK_COMMISSIONS.png b/assets/jp/mission/CHECK_COMMISSIONS.png new file mode 100644 index 0000000..ccab72d Binary files /dev/null and b/assets/jp/mission/CHECK_COMMISSIONS.png differ diff --git a/assets/jp/mission/CHECK_CR.png b/assets/jp/mission/CHECK_CR.png new file mode 100644 index 0000000..55a21df Binary files /dev/null and b/assets/jp/mission/CHECK_CR.png differ diff --git a/assets/jp/mission/CHECK_MISSION_SWEEP.png b/assets/jp/mission/CHECK_MISSION_SWEEP.png new file mode 100644 index 0000000..4ac1c5d Binary files /dev/null and b/assets/jp/mission/CHECK_MISSION_SWEEP.png differ diff --git a/assets/jp/mission/CHECK_XP.png b/assets/jp/mission/CHECK_XP.png new file mode 100644 index 0000000..59ef189 Binary files /dev/null and b/assets/jp/mission/CHECK_XP.png differ diff --git a/assets/jp/mission/EVENT_INDEX.png b/assets/jp/mission/EVENT_INDEX.png new file mode 100644 index 0000000..104fbc4 Binary files /dev/null and b/assets/jp/mission/EVENT_INDEX.png differ diff --git a/assets/jp/mission/EVENT_ITEM.png b/assets/jp/mission/EVENT_ITEM.png new file mode 100644 index 0000000..2f2a2a7 Binary files /dev/null and b/assets/jp/mission/EVENT_ITEM.png differ diff --git a/assets/jp/mission/EVENT_LIST.png b/assets/jp/mission/EVENT_LIST.png new file mode 100644 index 0000000..a3eb22d Binary files /dev/null and b/assets/jp/mission/EVENT_LIST.png differ diff --git a/assets/jp/mission/EVENT_STARS.png b/assets/jp/mission/EVENT_STARS.png new file mode 100644 index 0000000..bdc774b Binary files /dev/null and b/assets/jp/mission/EVENT_STARS.png differ diff --git a/assets/jp/mission/HARD_OFF.png b/assets/jp/mission/HARD_OFF.png new file mode 100644 index 0000000..a2ba018 Binary files /dev/null and b/assets/jp/mission/HARD_OFF.png differ diff --git a/assets/jp/mission/HARD_ON.png b/assets/jp/mission/HARD_ON.png new file mode 100644 index 0000000..98bddd2 Binary files /dev/null and b/assets/jp/mission/HARD_ON.png differ diff --git a/assets/jp/mission/LEFT.png b/assets/jp/mission/LEFT.png new file mode 100644 index 0000000..f1bc365 Binary files /dev/null and b/assets/jp/mission/LEFT.png differ diff --git a/assets/en/base/page/MAIN_GO_TO_TASK.png b/assets/jp/mission/NORMAL_OFF.png similarity index 56% rename from assets/en/base/page/MAIN_GO_TO_TASK.png rename to assets/jp/mission/NORMAL_OFF.png index 7dae9e8..3c409f2 100644 Binary files a/assets/en/base/page/MAIN_GO_TO_TASK.png and b/assets/jp/mission/NORMAL_OFF.png differ diff --git a/assets/jp/mission/NORMAL_ON.png b/assets/jp/mission/NORMAL_ON.png new file mode 100644 index 0000000..208f895 Binary files /dev/null and b/assets/jp/mission/NORMAL_ON.png differ diff --git a/assets/jp/mission/OCR_AREA.png b/assets/jp/mission/OCR_AREA.png new file mode 100644 index 0000000..d9a8316 Binary files /dev/null and b/assets/jp/mission/OCR_AREA.png differ diff --git a/assets/jp/mission/QUEST_OFF.png b/assets/jp/mission/QUEST_OFF.png new file mode 100644 index 0000000..21e2241 Binary files /dev/null and b/assets/jp/mission/QUEST_OFF.png differ diff --git a/assets/jp/mission/QUEST_ON.png b/assets/jp/mission/QUEST_ON.png new file mode 100644 index 0000000..b727c8b Binary files /dev/null and b/assets/jp/mission/QUEST_ON.png differ diff --git a/assets/jp/mission/RIGHT.png b/assets/jp/mission/RIGHT.png new file mode 100644 index 0000000..e4c0e74 Binary files /dev/null and b/assets/jp/mission/RIGHT.png differ diff --git a/assets/jp/mission/SELECT_CR.png b/assets/jp/mission/SELECT_CR.png new file mode 100644 index 0000000..33750b4 Binary files /dev/null and b/assets/jp/mission/SELECT_CR.png differ diff --git a/assets/jp/mission/SELECT_XP.png b/assets/jp/mission/SELECT_XP.png new file mode 100644 index 0000000..0ba5cf6 Binary files /dev/null and b/assets/jp/mission/SELECT_XP.png differ diff --git a/assets/jp/momotalk/BEGIN_STORY.png b/assets/jp/momotalk/BEGIN_STORY.png new file mode 100644 index 0000000..946ce85 Binary files /dev/null and b/assets/jp/momotalk/BEGIN_STORY.png differ diff --git a/assets/jp/momotalk/CHATTING.png b/assets/jp/momotalk/CHATTING.png new file mode 100644 index 0000000..048e79d Binary files /dev/null and b/assets/jp/momotalk/CHATTING.png differ diff --git a/assets/jp/momotalk/CHAT_AREA.png b/assets/jp/momotalk/CHAT_AREA.png new file mode 100644 index 0000000..d82d019 Binary files /dev/null and b/assets/jp/momotalk/CHAT_AREA.png differ diff --git a/assets/jp/momotalk/CONFIRM_SKIP.png b/assets/jp/momotalk/CONFIRM_SKIP.png new file mode 100644 index 0000000..594ad39 Binary files /dev/null and b/assets/jp/momotalk/CONFIRM_SKIP.png differ diff --git a/assets/jp/momotalk/CONFIRM_SORT.png b/assets/jp/momotalk/CONFIRM_SORT.png new file mode 100644 index 0000000..ab6ed64 Binary files /dev/null and b/assets/jp/momotalk/CONFIRM_SORT.png differ diff --git a/assets/jp/momotalk/FIRST_UNREAD.png b/assets/jp/momotalk/FIRST_UNREAD.png new file mode 100644 index 0000000..bafb1b5 Binary files /dev/null and b/assets/jp/momotalk/FIRST_UNREAD.png differ diff --git a/assets/jp/momotalk/MENU.png b/assets/jp/momotalk/MENU.png new file mode 100644 index 0000000..c022024 Binary files /dev/null and b/assets/jp/momotalk/MENU.png differ diff --git a/assets/jp/momotalk/NOTIFICATION_BADGE.png b/assets/jp/momotalk/NOTIFICATION_BADGE.png new file mode 100644 index 0000000..9202809 Binary files /dev/null and b/assets/jp/momotalk/NOTIFICATION_BADGE.png differ diff --git a/assets/jp/momotalk/REPLY.png b/assets/jp/momotalk/REPLY.png new file mode 100644 index 0000000..02aa666 Binary files /dev/null and b/assets/jp/momotalk/REPLY.png differ diff --git a/assets/jp/momotalk/SELECT_STUDENT.png b/assets/jp/momotalk/SELECT_STUDENT.png new file mode 100644 index 0000000..47d9930 Binary files /dev/null and b/assets/jp/momotalk/SELECT_STUDENT.png differ diff --git a/assets/jp/momotalk/SKIP.png b/assets/jp/momotalk/SKIP.png new file mode 100644 index 0000000..c1796b8 Binary files /dev/null and b/assets/jp/momotalk/SKIP.png differ diff --git a/assets/en/base/page/MAIN_GO_TO_MOMOTALK.png b/assets/jp/momotalk/SORT_ASCENDING.png similarity index 65% rename from assets/en/base/page/MAIN_GO_TO_MOMOTALK.png rename to assets/jp/momotalk/SORT_ASCENDING.png index 58d3cf1..29124fe 100644 Binary files a/assets/en/base/page/MAIN_GO_TO_MOMOTALK.png and b/assets/jp/momotalk/SORT_ASCENDING.png differ diff --git a/assets/jp/momotalk/SORT_DESCENDING.png b/assets/jp/momotalk/SORT_DESCENDING.png new file mode 100644 index 0000000..8aa4e34 Binary files /dev/null and b/assets/jp/momotalk/SORT_DESCENDING.png differ diff --git a/assets/jp/momotalk/STORY.png b/assets/jp/momotalk/STORY.png new file mode 100644 index 0000000..a6ad08d Binary files /dev/null and b/assets/jp/momotalk/STORY.png differ diff --git a/assets/jp/momotalk/SWITCH_MESSAGE.png b/assets/jp/momotalk/SWITCH_MESSAGE.png new file mode 100644 index 0000000..0198f32 Binary files /dev/null and b/assets/jp/momotalk/SWITCH_MESSAGE.png differ diff --git a/assets/jp/momotalk/SWITCH_MESSAGE_CHECK.png b/assets/jp/momotalk/SWITCH_MESSAGE_CHECK.png new file mode 100644 index 0000000..658cfd7 Binary files /dev/null and b/assets/jp/momotalk/SWITCH_MESSAGE_CHECK.png differ diff --git a/assets/jp/momotalk/SWITCH_STUDENT.png b/assets/jp/momotalk/SWITCH_STUDENT.png new file mode 100644 index 0000000..40252dd Binary files /dev/null and b/assets/jp/momotalk/SWITCH_STUDENT.png differ diff --git a/assets/jp/momotalk/SWITCH_STUDENT_CHECK.png b/assets/jp/momotalk/SWITCH_STUDENT_CHECK.png new file mode 100644 index 0000000..d8131ae Binary files /dev/null and b/assets/jp/momotalk/SWITCH_STUDENT_CHECK.png differ diff --git a/assets/jp/momotalk/UNREAD.png b/assets/jp/momotalk/UNREAD.png new file mode 100644 index 0000000..8cfadb9 Binary files /dev/null and b/assets/jp/momotalk/UNREAD.png differ diff --git a/assets/jp/momotalk/UNREAD_OFF.png b/assets/jp/momotalk/UNREAD_OFF.png new file mode 100644 index 0000000..fc884d5 Binary files /dev/null and b/assets/jp/momotalk/UNREAD_OFF.png differ diff --git a/assets/jp/momotalk/UNREAD_ON.png b/assets/jp/momotalk/UNREAD_ON.png new file mode 100644 index 0000000..44afed7 Binary files /dev/null and b/assets/jp/momotalk/UNREAD_ON.png differ diff --git a/assets/jp/schedule/CONFIRM.png b/assets/jp/schedule/CONFIRM.png new file mode 100644 index 0000000..450a966 Binary files /dev/null and b/assets/jp/schedule/CONFIRM.png differ diff --git a/assets/jp/schedule/FIRST_ITEM.png b/assets/jp/schedule/FIRST_ITEM.png new file mode 100644 index 0000000..7b0d858 Binary files /dev/null and b/assets/jp/schedule/FIRST_ITEM.png differ diff --git a/assets/jp/schedule/LOCATIONS.png b/assets/jp/schedule/LOCATIONS.png new file mode 100644 index 0000000..88d66fa Binary files /dev/null and b/assets/jp/schedule/LOCATIONS.png differ diff --git a/assets/jp/schedule/LOCATIONS_POPUP.png b/assets/jp/schedule/LOCATIONS_POPUP.png new file mode 100644 index 0000000..5b40a34 Binary files /dev/null and b/assets/jp/schedule/LOCATIONS_POPUP.png differ diff --git a/assets/jp/schedule/OCR_TICKET.png b/assets/jp/schedule/OCR_TICKET.png new file mode 100644 index 0000000..ff08412 Binary files /dev/null and b/assets/jp/schedule/OCR_TICKET.png differ diff --git a/assets/jp/schedule/SCROLL.png b/assets/jp/schedule/SCROLL.png index 02308f9..35dbd26 100644 Binary files a/assets/jp/schedule/SCROLL.png and b/assets/jp/schedule/SCROLL.png differ diff --git a/assets/jp/schedule/START_LESSON.png b/assets/jp/schedule/START_LESSON.png new file mode 100644 index 0000000..1dd22b7 Binary files /dev/null and b/assets/jp/schedule/START_LESSON.png differ diff --git a/assets/jp/scrimmage/OCR_TICKET.png b/assets/jp/scrimmage/OCR_TICKET.png index c3736db..4dc786e 100644 Binary files a/assets/jp/scrimmage/OCR_TICKET.png and b/assets/jp/scrimmage/OCR_TICKET.png differ diff --git a/assets/jp/shop/CONFIRM_PURCHASE.BUTTON.png b/assets/jp/shop/CONFIRM_PURCHASE.BUTTON.png new file mode 100644 index 0000000..1ad67e1 Binary files /dev/null and b/assets/jp/shop/CONFIRM_PURCHASE.BUTTON.png differ diff --git a/assets/jp/shop/CONFIRM_PURCHASE.png b/assets/jp/shop/CONFIRM_PURCHASE.png new file mode 100644 index 0000000..c1a0298 Binary files /dev/null and b/assets/jp/shop/CONFIRM_PURCHASE.png differ diff --git a/assets/jp/shop/CONFIRM_REFRESH.BUTTON.png b/assets/jp/shop/CONFIRM_REFRESH.BUTTON.png new file mode 100644 index 0000000..3b1ca10 Binary files /dev/null and b/assets/jp/shop/CONFIRM_REFRESH.BUTTON.png differ diff --git a/assets/jp/shop/CONFIRM_REFRESH.png b/assets/jp/shop/CONFIRM_REFRESH.png new file mode 100644 index 0000000..cb0128c Binary files /dev/null and b/assets/jp/shop/CONFIRM_REFRESH.png differ diff --git a/assets/jp/shop/ITEM_LIST.png b/assets/jp/shop/ITEM_LIST.png new file mode 100644 index 0000000..d0e9d18 Binary files /dev/null and b/assets/jp/shop/ITEM_LIST.png differ diff --git a/assets/jp/shop/NORMAL_OFF.png b/assets/jp/shop/NORMAL_OFF.png new file mode 100644 index 0000000..d34b1f5 Binary files /dev/null and b/assets/jp/shop/NORMAL_OFF.png differ diff --git a/assets/jp/shop/NORMAL_ON.png b/assets/jp/shop/NORMAL_ON.png new file mode 100644 index 0000000..aaa1185 Binary files /dev/null and b/assets/jp/shop/NORMAL_ON.png differ diff --git a/assets/jp/shop/OCR_REFRESH.png b/assets/jp/shop/OCR_REFRESH.png new file mode 100644 index 0000000..643f9be Binary files /dev/null and b/assets/jp/shop/OCR_REFRESH.png differ diff --git a/assets/jp/shop/PURCHASE.png b/assets/jp/shop/PURCHASE.png new file mode 100644 index 0000000..3e4aa3b Binary files /dev/null and b/assets/jp/shop/PURCHASE.png differ diff --git a/assets/jp/base/popup/DAILY_NEWS.BUTTON.png b/assets/jp/shop/REFRESH.png similarity index 51% rename from assets/jp/base/popup/DAILY_NEWS.BUTTON.png rename to assets/jp/shop/REFRESH.png index 33a3f89..2a0b705 100644 Binary files a/assets/jp/base/popup/DAILY_NEWS.BUTTON.png and b/assets/jp/shop/REFRESH.png differ diff --git a/assets/jp/shop/TC_OFF.png b/assets/jp/shop/TC_OFF.png new file mode 100644 index 0000000..5f8264d Binary files /dev/null and b/assets/jp/shop/TC_OFF.png differ diff --git a/assets/jp/shop/TC_ON.png b/assets/jp/shop/TC_ON.png new file mode 100644 index 0000000..d3a79b3 Binary files /dev/null and b/assets/jp/shop/TC_ON.png differ diff --git a/assets/jp/task/CLAIM.png b/assets/jp/task/CLAIM.png new file mode 100644 index 0000000..aa3d956 Binary files /dev/null and b/assets/jp/task/CLAIM.png differ diff --git a/assets/jp/task/CLAIMED.png b/assets/jp/task/CLAIMED.png new file mode 100644 index 0000000..8321630 Binary files /dev/null and b/assets/jp/task/CLAIMED.png differ diff --git a/assets/jp/task/CLAIMED_ALL.png b/assets/jp/task/CLAIMED_ALL.png new file mode 100644 index 0000000..360dc06 Binary files /dev/null and b/assets/jp/task/CLAIMED_ALL.png differ diff --git a/assets/jp/task/CLAIM_ALL.png b/assets/jp/task/CLAIM_ALL.png new file mode 100644 index 0000000..8ed10b5 Binary files /dev/null and b/assets/jp/task/CLAIM_ALL.png differ diff --git a/assets/jp/task/COMPLETE.png b/assets/jp/task/COMPLETE.png new file mode 100644 index 0000000..377ef54 Binary files /dev/null and b/assets/jp/task/COMPLETE.png differ diff --git a/assets/en/base/page/BACK.png b/assets/share/base/page/BACK.png similarity index 100% rename from assets/en/base/page/BACK.png rename to assets/share/base/page/BACK.png diff --git a/assets/en/base/page/HOME.png b/assets/share/base/page/HOME.png similarity index 100% rename from assets/en/base/page/HOME.png rename to assets/share/base/page/HOME.png diff --git a/assets/en/base/page/LOADING_CHECK.png b/assets/share/base/page/LOADING_CHECK.png similarity index 100% rename from assets/en/base/page/LOADING_CHECK.png rename to assets/share/base/page/LOADING_CHECK.png diff --git a/assets/en/base/page/MAIN_GO_TO_CAFE.png b/assets/share/base/page/MAIN_GO_TO_CAFE.png similarity index 100% rename from assets/en/base/page/MAIN_GO_TO_CAFE.png rename to assets/share/base/page/MAIN_GO_TO_CAFE.png diff --git a/assets/share/base/page/MAIN_GO_TO_CIRCLE.png b/assets/share/base/page/MAIN_GO_TO_CIRCLE.png new file mode 100644 index 0000000..30014c0 Binary files /dev/null and b/assets/share/base/page/MAIN_GO_TO_CIRCLE.png differ diff --git a/assets/en/base/page/MAIN_GO_TO_CRAFTING.png b/assets/share/base/page/MAIN_GO_TO_CRAFTING.png similarity index 100% rename from assets/en/base/page/MAIN_GO_TO_CRAFTING.png rename to assets/share/base/page/MAIN_GO_TO_CRAFTING.png diff --git a/assets/en/base/page/MAIN_GO_TO_GACHA.png b/assets/share/base/page/MAIN_GO_TO_GACHA.png similarity index 100% rename from assets/en/base/page/MAIN_GO_TO_GACHA.png rename to assets/share/base/page/MAIN_GO_TO_GACHA.png diff --git a/assets/en/base/page/MAIN_GO_TO_MAIL.png b/assets/share/base/page/MAIN_GO_TO_MAIL.png similarity index 100% rename from assets/en/base/page/MAIN_GO_TO_MAIL.png rename to assets/share/base/page/MAIN_GO_TO_MAIL.png diff --git a/assets/share/base/page/MAIN_GO_TO_MOMOTALK.png b/assets/share/base/page/MAIN_GO_TO_MOMOTALK.png new file mode 100644 index 0000000..640cdfc Binary files /dev/null and b/assets/share/base/page/MAIN_GO_TO_MOMOTALK.png differ diff --git a/assets/share/base/page/MAIN_GO_TO_PURCHASE.png b/assets/share/base/page/MAIN_GO_TO_PURCHASE.png new file mode 100644 index 0000000..6402801 Binary files /dev/null and b/assets/share/base/page/MAIN_GO_TO_PURCHASE.png differ diff --git a/assets/en/base/page/MAIN_GO_TO_SCHEDULE.png b/assets/share/base/page/MAIN_GO_TO_SCHEDULE.png similarity index 100% rename from assets/en/base/page/MAIN_GO_TO_SCHEDULE.png rename to assets/share/base/page/MAIN_GO_TO_SCHEDULE.png diff --git a/assets/en/base/page/MAIN_GO_TO_SHOP.png b/assets/share/base/page/MAIN_GO_TO_SHOP.png similarity index 100% rename from assets/en/base/page/MAIN_GO_TO_SHOP.png rename to assets/share/base/page/MAIN_GO_TO_SHOP.png diff --git a/assets/share/base/page/MAIN_GO_TO_TASK.png b/assets/share/base/page/MAIN_GO_TO_TASK.png new file mode 100644 index 0000000..5c81155 Binary files /dev/null and b/assets/share/base/page/MAIN_GO_TO_TASK.png differ diff --git a/assets/en/base/page/MOMOTALK_CHECK.png b/assets/share/base/page/MOMOTALK_CHECK.png similarity index 100% rename from assets/en/base/page/MOMOTALK_CHECK.png rename to assets/share/base/page/MOMOTALK_CHECK.png diff --git a/assets/en/base/page/MOMOTALK_GO_TO_MAIN.png b/assets/share/base/page/MOMOTALK_GO_TO_MAIN.png similarity index 100% rename from assets/en/base/page/MOMOTALK_GO_TO_MAIN.png rename to assets/share/base/page/MOMOTALK_GO_TO_MAIN.png diff --git a/assets/en/base/popup/DAILY_NEWS.BUTTON.png b/assets/share/base/popup/DAILY_NEWS.BUTTON.png similarity index 100% rename from assets/en/base/popup/DAILY_NEWS.BUTTON.png rename to assets/share/base/popup/DAILY_NEWS.BUTTON.png diff --git a/assets/en/base/popup/DAILY_NEWS.png b/assets/share/base/popup/DAILY_NEWS.png similarity index 100% rename from assets/en/base/popup/DAILY_NEWS.png rename to assets/share/base/popup/DAILY_NEWS.png diff --git a/assets/en/base/popup/DAILY_REWARD.BUTTON.png b/assets/share/base/popup/DAILY_REWARD.BUTTON.png similarity index 100% rename from assets/en/base/popup/DAILY_REWARD.BUTTON.png rename to assets/share/base/popup/DAILY_REWARD.BUTTON.png diff --git a/assets/en/base/popup/DAILY_REWARD.png b/assets/share/base/popup/DAILY_REWARD.png similarity index 100% rename from assets/en/base/popup/DAILY_REWARD.png rename to assets/share/base/popup/DAILY_REWARD.png diff --git a/assets/en/base/popup/GET_NEW_STUDENT.BUTTON.png b/assets/share/base/popup/GET_NEW_STUDENT.BUTTON.png similarity index 100% rename from assets/en/base/popup/GET_NEW_STUDENT.BUTTON.png rename to assets/share/base/popup/GET_NEW_STUDENT.BUTTON.png diff --git a/assets/en/base/popup/GET_NEW_STUDENT.png b/assets/share/base/popup/GET_NEW_STUDENT.png similarity index 100% rename from assets/en/base/popup/GET_NEW_STUDENT.png rename to assets/share/base/popup/GET_NEW_STUDENT.png diff --git a/assets/en/cafe/BOX_CAFE.png b/assets/share/cafe/BOX_CAFE.png similarity index 100% rename from assets/en/cafe/BOX_CAFE.png rename to assets/share/cafe/BOX_CAFE.png diff --git a/assets/share/cafe/BOX_SEARCH.png b/assets/share/cafe/BOX_SEARCH.png new file mode 100644 index 0000000..82cd614 Binary files /dev/null and b/assets/share/cafe/BOX_SEARCH.png differ diff --git a/assets/en/cafe/CLICKABLE_TEMPLATE.png b/assets/share/cafe/CLICKABLE_TEMPLATE.png similarity index 100% rename from assets/en/cafe/CLICKABLE_TEMPLATE.png rename to assets/share/cafe/CLICKABLE_TEMPLATE.png diff --git a/assets/share/item/data/OCR_AP.png b/assets/share/item/data/OCR_AP.png index 44cf99a..08b2893 100644 Binary files a/assets/share/item/data/OCR_AP.png and b/assets/share/item/data/OCR_AP.png differ diff --git a/assets/share/item/data/OCR_DATA.png b/assets/share/item/data/OCR_DATA.png index 0e60519..b84cb10 100644 Binary files a/assets/share/item/data/OCR_DATA.png and b/assets/share/item/data/OCR_DATA.png differ diff --git a/assets/zht/base/page/ACCOUNT_INFO_CHECK.png b/assets/zht/base/page/ACCOUNT_INFO_CHECK.png new file mode 100644 index 0000000..d6ba78c Binary files /dev/null and b/assets/zht/base/page/ACCOUNT_INFO_CHECK.png differ diff --git a/assets/zht/base/page/BOUNTY_CHECK.png b/assets/zht/base/page/BOUNTY_CHECK.png new file mode 100644 index 0000000..13bf61c Binary files /dev/null and b/assets/zht/base/page/BOUNTY_CHECK.png differ diff --git a/assets/zht/base/page/CAFE_CHECK.png b/assets/zht/base/page/CAFE_CHECK.png new file mode 100644 index 0000000..27b8926 Binary files /dev/null and b/assets/zht/base/page/CAFE_CHECK.png differ diff --git a/assets/zht/base/page/CIRCLE_CHECK.png b/assets/zht/base/page/CIRCLE_CHECK.png new file mode 100644 index 0000000..dac7a88 Binary files /dev/null and b/assets/zht/base/page/CIRCLE_CHECK.png differ diff --git a/assets/zht/base/page/COMMISSIONS_CHECK.png b/assets/zht/base/page/COMMISSIONS_CHECK.png new file mode 100644 index 0000000..f12170d Binary files /dev/null and b/assets/zht/base/page/COMMISSIONS_CHECK.png differ diff --git a/assets/zht/base/page/CRAFTING_CHECK.png b/assets/zht/base/page/CRAFTING_CHECK.png new file mode 100644 index 0000000..5b569e7 Binary files /dev/null and b/assets/zht/base/page/CRAFTING_CHECK.png differ diff --git a/assets/zht/base/page/EVENT_CHECK.png b/assets/zht/base/page/EVENT_CHECK.png new file mode 100644 index 0000000..d7c1550 Binary files /dev/null and b/assets/zht/base/page/EVENT_CHECK.png differ diff --git a/assets/zht/base/page/GACHA_CHECK.png b/assets/zht/base/page/GACHA_CHECK.png new file mode 100644 index 0000000..b3a3854 Binary files /dev/null and b/assets/zht/base/page/GACHA_CHECK.png differ diff --git a/assets/zht/base/page/MAIL_CHECK.png b/assets/zht/base/page/MAIL_CHECK.png new file mode 100644 index 0000000..bcdfb20 Binary files /dev/null and b/assets/zht/base/page/MAIL_CHECK.png differ diff --git a/assets/zht/base/page/MAIN_GO_TO_WORK.png b/assets/zht/base/page/MAIN_GO_TO_WORK.png new file mode 100644 index 0000000..eb2e9b5 Binary files /dev/null and b/assets/zht/base/page/MAIN_GO_TO_WORK.png differ diff --git a/assets/zht/base/page/MISSION_CHECK.png b/assets/zht/base/page/MISSION_CHECK.png new file mode 100644 index 0000000..def2658 Binary files /dev/null and b/assets/zht/base/page/MISSION_CHECK.png differ diff --git a/assets/zht/base/page/SCHEDULE_CHECK.png b/assets/zht/base/page/SCHEDULE_CHECK.png new file mode 100644 index 0000000..ee7e560 Binary files /dev/null and b/assets/zht/base/page/SCHEDULE_CHECK.png differ diff --git a/assets/zht/base/page/SCHOOL_EXCHANGE_CHECK.png b/assets/zht/base/page/SCHOOL_EXCHANGE_CHECK.png new file mode 100644 index 0000000..945a696 Binary files /dev/null and b/assets/zht/base/page/SCHOOL_EXCHANGE_CHECK.png differ diff --git a/assets/zht/base/page/SHOP_CHECK.png b/assets/zht/base/page/SHOP_CHECK.png new file mode 100644 index 0000000..c9c64a3 Binary files /dev/null and b/assets/zht/base/page/SHOP_CHECK.png differ diff --git a/assets/zht/base/page/STORY_CHECK.png b/assets/zht/base/page/STORY_CHECK.png new file mode 100644 index 0000000..e832cbf Binary files /dev/null and b/assets/zht/base/page/STORY_CHECK.png differ diff --git a/assets/zht/base/page/TACTICAL_CHALLENGE_CHECK.png b/assets/zht/base/page/TACTICAL_CHALLENGE_CHECK.png new file mode 100644 index 0000000..33bae5e Binary files /dev/null and b/assets/zht/base/page/TACTICAL_CHALLENGE_CHECK.png differ diff --git a/assets/zht/base/page/TASK_CHECK.png b/assets/zht/base/page/TASK_CHECK.png new file mode 100644 index 0000000..d76313c Binary files /dev/null and b/assets/zht/base/page/TASK_CHECK.png differ diff --git a/assets/zht/base/page/WORK_CHECK.png b/assets/zht/base/page/WORK_CHECK.png new file mode 100644 index 0000000..32e4aae Binary files /dev/null and b/assets/zht/base/page/WORK_CHECK.png differ diff --git a/assets/zht/base/page/WORK_GO_TO_BOUNTY.png b/assets/zht/base/page/WORK_GO_TO_BOUNTY.png new file mode 100644 index 0000000..f7dc1e1 Binary files /dev/null and b/assets/zht/base/page/WORK_GO_TO_BOUNTY.png differ diff --git a/assets/zht/base/page/WORK_GO_TO_COMMISSIONS.png b/assets/zht/base/page/WORK_GO_TO_COMMISSIONS.png new file mode 100644 index 0000000..988ade0 Binary files /dev/null and b/assets/zht/base/page/WORK_GO_TO_COMMISSIONS.png differ diff --git a/assets/zht/base/page/WORK_GO_TO_EVENT.png b/assets/zht/base/page/WORK_GO_TO_EVENT.png new file mode 100644 index 0000000..1f810ca Binary files /dev/null and b/assets/zht/base/page/WORK_GO_TO_EVENT.png differ diff --git a/assets/zht/base/page/WORK_GO_TO_MISSION.png b/assets/zht/base/page/WORK_GO_TO_MISSION.png new file mode 100644 index 0000000..fc8e0ae Binary files /dev/null and b/assets/zht/base/page/WORK_GO_TO_MISSION.png differ diff --git a/assets/zht/base/page/WORK_GO_TO_SCHOOL_EXCHANGE.png b/assets/zht/base/page/WORK_GO_TO_SCHOOL_EXCHANGE.png new file mode 100644 index 0000000..bbd3350 Binary files /dev/null and b/assets/zht/base/page/WORK_GO_TO_SCHOOL_EXCHANGE.png differ diff --git a/assets/zht/base/page/WORK_GO_TO_STORY.png b/assets/zht/base/page/WORK_GO_TO_STORY.png new file mode 100644 index 0000000..2165892 Binary files /dev/null and b/assets/zht/base/page/WORK_GO_TO_STORY.png differ diff --git a/assets/zht/base/page/WORK_GO_TO_TACTICAL_CHALLENGE.png b/assets/zht/base/page/WORK_GO_TO_TACTICAL_CHALLENGE.png new file mode 100644 index 0000000..4e9acc9 Binary files /dev/null and b/assets/zht/base/page/WORK_GO_TO_TACTICAL_CHALLENGE.png differ diff --git a/assets/zht/base/popup/AFFECTION_LEVEL_UP.BUTTON.png b/assets/zht/base/popup/AFFECTION_LEVEL_UP.BUTTON.png new file mode 100644 index 0000000..a331e9a Binary files /dev/null and b/assets/zht/base/popup/AFFECTION_LEVEL_UP.BUTTON.png differ diff --git a/assets/zht/base/popup/AFFECTION_LEVEL_UP.png b/assets/zht/base/popup/AFFECTION_LEVEL_UP.png new file mode 100644 index 0000000..bb3d614 Binary files /dev/null and b/assets/zht/base/popup/AFFECTION_LEVEL_UP.png differ diff --git a/assets/zht/base/popup/AP_EXCEED.BUTTON.png b/assets/zht/base/popup/AP_EXCEED.BUTTON.png new file mode 100644 index 0000000..b8c04af Binary files /dev/null and b/assets/zht/base/popup/AP_EXCEED.BUTTON.png differ diff --git a/assets/zht/base/popup/AP_EXCEED.png b/assets/zht/base/popup/AP_EXCEED.png new file mode 100644 index 0000000..5686706 Binary files /dev/null and b/assets/zht/base/popup/AP_EXCEED.png differ diff --git a/assets/zht/base/popup/GET_REWARD.BUTTON.png b/assets/zht/base/popup/GET_REWARD.BUTTON.png new file mode 100644 index 0000000..163e9c0 Binary files /dev/null and b/assets/zht/base/popup/GET_REWARD.BUTTON.png differ diff --git a/assets/zht/base/popup/GET_REWARD.png b/assets/zht/base/popup/GET_REWARD.png new file mode 100644 index 0000000..ba1ee07 Binary files /dev/null and b/assets/zht/base/popup/GET_REWARD.png differ diff --git a/assets/zht/base/popup/GET_REWARD_SKIP.png b/assets/zht/base/popup/GET_REWARD_SKIP.png new file mode 100644 index 0000000..abfdfed Binary files /dev/null and b/assets/zht/base/popup/GET_REWARD_SKIP.png differ diff --git a/assets/zht/base/popup/INSUFFICIENT_INVENTORY.BUTTON.png b/assets/zht/base/popup/INSUFFICIENT_INVENTORY.BUTTON.png new file mode 100644 index 0000000..1379d3c Binary files /dev/null and b/assets/zht/base/popup/INSUFFICIENT_INVENTORY.BUTTON.png differ diff --git a/assets/zht/base/popup/INSUFFICIENT_INVENTORY.png b/assets/zht/base/popup/INSUFFICIENT_INVENTORY.png new file mode 100644 index 0000000..c268a02 Binary files /dev/null and b/assets/zht/base/popup/INSUFFICIENT_INVENTORY.png differ diff --git a/assets/zht/base/popup/ITEM_EXPIRED.BUTTON.png b/assets/zht/base/popup/ITEM_EXPIRED.BUTTON.png new file mode 100644 index 0000000..40a515d Binary files /dev/null and b/assets/zht/base/popup/ITEM_EXPIRED.BUTTON.png differ diff --git a/assets/zht/base/popup/ITEM_EXPIRED.png b/assets/zht/base/popup/ITEM_EXPIRED.png new file mode 100644 index 0000000..53841f9 Binary files /dev/null and b/assets/zht/base/popup/ITEM_EXPIRED.png differ diff --git a/assets/zht/base/popup/LEVEL_UP.BUTTON.png b/assets/zht/base/popup/LEVEL_UP.BUTTON.png new file mode 100644 index 0000000..a8e8b85 Binary files /dev/null and b/assets/zht/base/popup/LEVEL_UP.BUTTON.png differ diff --git a/assets/zht/base/popup/LEVEL_UP.png b/assets/zht/base/popup/LEVEL_UP.png new file mode 100644 index 0000000..19ded3d Binary files /dev/null and b/assets/zht/base/popup/LEVEL_UP.png differ diff --git a/assets/zht/base/popup/LOCATION_LEVEL_UP.BUTTON.png b/assets/zht/base/popup/LOCATION_LEVEL_UP.BUTTON.png new file mode 100644 index 0000000..cd14bc8 Binary files /dev/null and b/assets/zht/base/popup/LOCATION_LEVEL_UP.BUTTON.png differ diff --git a/assets/zht/base/popup/LOCATION_LEVEL_UP.png b/assets/zht/base/popup/LOCATION_LEVEL_UP.png new file mode 100644 index 0000000..9ff58c4 Binary files /dev/null and b/assets/zht/base/popup/LOCATION_LEVEL_UP.png differ diff --git a/assets/zht/base/popup/NETWORK_RECONNECT.BUTTON.png b/assets/zht/base/popup/NETWORK_RECONNECT.BUTTON.png new file mode 100644 index 0000000..de4c8e9 Binary files /dev/null and b/assets/zht/base/popup/NETWORK_RECONNECT.BUTTON.png differ diff --git a/assets/zht/base/popup/NETWORK_RECONNECT.png b/assets/zht/base/popup/NETWORK_RECONNECT.png new file mode 100644 index 0000000..afbd04a Binary files /dev/null and b/assets/zht/base/popup/NETWORK_RECONNECT.png differ diff --git a/assets/zht/base/popup/NETWORK_RECONNECT_OK.BUTTON.png b/assets/zht/base/popup/NETWORK_RECONNECT_OK.BUTTON.png new file mode 100644 index 0000000..dcb56e4 Binary files /dev/null and b/assets/zht/base/popup/NETWORK_RECONNECT_OK.BUTTON.png differ diff --git a/assets/zht/base/popup/NETWORK_RECONNECT_OK.png b/assets/zht/base/popup/NETWORK_RECONNECT_OK.png new file mode 100644 index 0000000..df19d4d Binary files /dev/null and b/assets/zht/base/popup/NETWORK_RECONNECT_OK.png differ diff --git a/assets/zht/base/popup/QUIT.BUTTON.png b/assets/zht/base/popup/QUIT.BUTTON.png new file mode 100644 index 0000000..8cc11c4 Binary files /dev/null and b/assets/zht/base/popup/QUIT.BUTTON.png differ diff --git a/assets/zht/base/popup/QUIT.png b/assets/zht/base/popup/QUIT.png new file mode 100644 index 0000000..fd74e66 Binary files /dev/null and b/assets/zht/base/popup/QUIT.png differ diff --git a/assets/zht/bounty/CHECK_BOUNTY.png b/assets/zht/bounty/CHECK_BOUNTY.png new file mode 100644 index 0000000..9c714bb Binary files /dev/null and b/assets/zht/bounty/CHECK_BOUNTY.png differ diff --git a/assets/zht/bounty/CHECK_DESERT_RAILROAD.png b/assets/zht/bounty/CHECK_DESERT_RAILROAD.png new file mode 100644 index 0000000..e6d745d Binary files /dev/null and b/assets/zht/bounty/CHECK_DESERT_RAILROAD.png differ diff --git a/assets/zht/bounty/CHECK_HIGHWAY.png b/assets/zht/bounty/CHECK_HIGHWAY.png new file mode 100644 index 0000000..8e41f7d Binary files /dev/null and b/assets/zht/bounty/CHECK_HIGHWAY.png differ diff --git a/assets/zht/bounty/CHECK_SCHOOLHOUSE.png b/assets/zht/bounty/CHECK_SCHOOLHOUSE.png new file mode 100644 index 0000000..6c8917b Binary files /dev/null and b/assets/zht/bounty/CHECK_SCHOOLHOUSE.png differ diff --git a/assets/zht/bounty/OCR_TICKET.png b/assets/zht/bounty/OCR_TICKET.png new file mode 100644 index 0000000..ebfa4ee Binary files /dev/null and b/assets/zht/bounty/OCR_TICKET.png differ diff --git a/assets/zht/bounty/SELECT_DESERT_RAILROAD.png b/assets/zht/bounty/SELECT_DESERT_RAILROAD.png new file mode 100644 index 0000000..13ea8c0 Binary files /dev/null and b/assets/zht/bounty/SELECT_DESERT_RAILROAD.png differ diff --git a/assets/zht/bounty/SELECT_HIGHWAY.png b/assets/zht/bounty/SELECT_HIGHWAY.png new file mode 100644 index 0000000..fc47324 Binary files /dev/null and b/assets/zht/bounty/SELECT_HIGHWAY.png differ diff --git a/assets/zht/bounty/SELECT_SCHOOLHOUSE.png b/assets/zht/bounty/SELECT_SCHOOLHOUSE.png new file mode 100644 index 0000000..9cd6bac Binary files /dev/null and b/assets/zht/bounty/SELECT_SCHOOLHOUSE.png differ diff --git a/assets/zht/cafe/CAFE_FIRST.png b/assets/zht/cafe/CAFE_FIRST.png new file mode 100644 index 0000000..86d5bce Binary files /dev/null and b/assets/zht/cafe/CAFE_FIRST.png differ diff --git a/assets/zht/cafe/CAFE_INVITE.png b/assets/zht/cafe/CAFE_INVITE.png new file mode 100644 index 0000000..e6108e3 Binary files /dev/null and b/assets/zht/cafe/CAFE_INVITE.png differ diff --git a/assets/zht/cafe/CAFE_INVITED.png b/assets/zht/cafe/CAFE_INVITED.png new file mode 100644 index 0000000..1500990 Binary files /dev/null and b/assets/zht/cafe/CAFE_INVITED.png differ diff --git a/assets/zht/cafe/CAFE_SECOND.png b/assets/zht/cafe/CAFE_SECOND.png new file mode 100644 index 0000000..311f96e Binary files /dev/null and b/assets/zht/cafe/CAFE_SECOND.png differ diff --git a/assets/zht/cafe/CHECK_MOMOTALK.png b/assets/zht/cafe/CHECK_MOMOTALK.png new file mode 100644 index 0000000..9c047b8 Binary files /dev/null and b/assets/zht/cafe/CHECK_MOMOTALK.png differ diff --git a/assets/zht/cafe/CHECK_REWARD.BUTTON.png b/assets/zht/cafe/CHECK_REWARD.BUTTON.png new file mode 100644 index 0000000..2206593 Binary files /dev/null and b/assets/zht/cafe/CHECK_REWARD.BUTTON.png differ diff --git a/assets/zht/cafe/CHECK_REWARD.png b/assets/zht/cafe/CHECK_REWARD.png new file mode 100644 index 0000000..8a6dc4d Binary files /dev/null and b/assets/zht/cafe/CHECK_REWARD.png differ diff --git a/assets/zht/cafe/GET_REWARD.BUTTON.png b/assets/zht/cafe/GET_REWARD.BUTTON.png new file mode 100644 index 0000000..eaafff1 Binary files /dev/null and b/assets/zht/cafe/GET_REWARD.BUTTON.png differ diff --git a/assets/zht/cafe/GET_REWARD.png b/assets/zht/cafe/GET_REWARD.png new file mode 100644 index 0000000..4bad8e4 Binary files /dev/null and b/assets/zht/cafe/GET_REWARD.png differ diff --git a/assets/zht/cafe/GET_REWARD_CLOSE.png b/assets/zht/cafe/GET_REWARD_CLOSE.png new file mode 100644 index 0000000..292dc92 Binary files /dev/null and b/assets/zht/cafe/GET_REWARD_CLOSE.png differ diff --git a/assets/zht/cafe/GOT_REWARD.png b/assets/zht/cafe/GOT_REWARD.png new file mode 100644 index 0000000..51ab08a Binary files /dev/null and b/assets/zht/cafe/GOT_REWARD.png differ diff --git a/assets/zht/cafe/INVENTORY.BUTTON.png b/assets/zht/cafe/INVENTORY.BUTTON.png new file mode 100644 index 0000000..9b2c86a Binary files /dev/null and b/assets/zht/cafe/INVENTORY.BUTTON.png differ diff --git a/assets/zht/cafe/INVENTORY.png b/assets/zht/cafe/INVENTORY.png new file mode 100644 index 0000000..589ad34 Binary files /dev/null and b/assets/zht/cafe/INVENTORY.png differ diff --git a/assets/zht/cafe/INVITE_CONFIRM.BUTTON.png b/assets/zht/cafe/INVITE_CONFIRM.BUTTON.png new file mode 100644 index 0000000..6a97528 Binary files /dev/null and b/assets/zht/cafe/INVITE_CONFIRM.BUTTON.png differ diff --git a/assets/zht/cafe/INVITE_CONFIRM.png b/assets/zht/cafe/INVITE_CONFIRM.png new file mode 100644 index 0000000..b65f9c4 Binary files /dev/null and b/assets/zht/cafe/INVITE_CONFIRM.png differ diff --git a/assets/zht/cafe/INVITE_IN_SECOND.png b/assets/zht/cafe/INVITE_IN_SECOND.png new file mode 100644 index 0000000..368bcd7 Binary files /dev/null and b/assets/zht/cafe/INVITE_IN_SECOND.png differ diff --git a/assets/zht/cafe/INVITE_IN_SECOND_CLOSE.png b/assets/zht/cafe/INVITE_IN_SECOND_CLOSE.png new file mode 100644 index 0000000..0bb17d2 Binary files /dev/null and b/assets/zht/cafe/INVITE_IN_SECOND_CLOSE.png differ diff --git a/assets/zht/cafe/INVITE_SUBSTITUTE.BUTTON.png b/assets/zht/cafe/INVITE_SUBSTITUTE.BUTTON.png new file mode 100644 index 0000000..b55ea73 Binary files /dev/null and b/assets/zht/cafe/INVITE_SUBSTITUTE.BUTTON.png differ diff --git a/assets/zht/cafe/INVITE_SUBSTITUTE.png b/assets/zht/cafe/INVITE_SUBSTITUTE.png new file mode 100644 index 0000000..cc9dd0e Binary files /dev/null and b/assets/zht/cafe/INVITE_SUBSTITUTE.png differ diff --git a/assets/zht/cafe/INVITE_SUBSTITUTE_CLOSE.png b/assets/zht/cafe/INVITE_SUBSTITUTE_CLOSE.png new file mode 100644 index 0000000..61a3704 Binary files /dev/null and b/assets/zht/cafe/INVITE_SUBSTITUTE_CLOSE.png differ diff --git a/assets/zht/cafe/MOMOTALK_CLOSE.png b/assets/zht/cafe/MOMOTALK_CLOSE.png new file mode 100644 index 0000000..6a220ac Binary files /dev/null and b/assets/zht/cafe/MOMOTALK_CLOSE.png differ diff --git a/assets/zht/cafe/MOMOTALK_INVITE.png b/assets/zht/cafe/MOMOTALK_INVITE.png new file mode 100644 index 0000000..59b5576 Binary files /dev/null and b/assets/zht/cafe/MOMOTALK_INVITE.png differ diff --git a/assets/zht/cafe/MOMOTALK_ITEM.png b/assets/zht/cafe/MOMOTALK_ITEM.png new file mode 100644 index 0000000..47baae7 Binary files /dev/null and b/assets/zht/cafe/MOMOTALK_ITEM.png differ diff --git a/assets/zht/cafe/OCR_CAFE.png b/assets/zht/cafe/OCR_CAFE.png new file mode 100644 index 0000000..12d8245 Binary files /dev/null and b/assets/zht/cafe/OCR_CAFE.png differ diff --git a/assets/zht/cafe/OCR_NAME.png b/assets/zht/cafe/OCR_NAME.png new file mode 100644 index 0000000..b4e7f9f Binary files /dev/null and b/assets/zht/cafe/OCR_NAME.png differ diff --git a/assets/zht/cafe/STUDENT_LIST.BUTTON.png b/assets/zht/cafe/STUDENT_LIST.BUTTON.png new file mode 100644 index 0000000..9c65bb8 Binary files /dev/null and b/assets/zht/cafe/STUDENT_LIST.BUTTON.png differ diff --git a/assets/zht/cafe/STUDENT_LIST.png b/assets/zht/cafe/STUDENT_LIST.png new file mode 100644 index 0000000..93999fd Binary files /dev/null and b/assets/zht/cafe/STUDENT_LIST.png differ diff --git a/assets/zht/circle/CIRCLE.BUTTON.png b/assets/zht/circle/CIRCLE.BUTTON.png new file mode 100644 index 0000000..6534c5b Binary files /dev/null and b/assets/zht/circle/CIRCLE.BUTTON.png differ diff --git a/assets/zht/circle/CIRCLE.png b/assets/zht/circle/CIRCLE.png new file mode 100644 index 0000000..be4e9de Binary files /dev/null and b/assets/zht/circle/CIRCLE.png differ diff --git a/assets/zht/circle/GET_REWARD_AP.BUTTON.png b/assets/zht/circle/GET_REWARD_AP.BUTTON.png new file mode 100644 index 0000000..c83d9c2 Binary files /dev/null and b/assets/zht/circle/GET_REWARD_AP.BUTTON.png differ diff --git a/assets/zht/circle/GET_REWARD_AP.png b/assets/zht/circle/GET_REWARD_AP.png new file mode 100644 index 0000000..5c36bb8 Binary files /dev/null and b/assets/zht/circle/GET_REWARD_AP.png differ diff --git a/assets/zht/login/LOGIN_CONFIRM.BUTTON.png b/assets/zht/login/LOGIN_CONFIRM.BUTTON.png new file mode 100644 index 0000000..359f813 Binary files /dev/null and b/assets/zht/login/LOGIN_CONFIRM.BUTTON.png differ diff --git a/assets/zht/login/LOGIN_CONFIRM.png b/assets/zht/login/LOGIN_CONFIRM.png new file mode 100644 index 0000000..ea778c3 Binary files /dev/null and b/assets/zht/login/LOGIN_CONFIRM.png differ diff --git a/assets/zht/login/LOGIN_LOADING.png b/assets/zht/login/LOGIN_LOADING.png new file mode 100644 index 0000000..3d4424c Binary files /dev/null and b/assets/zht/login/LOGIN_LOADING.png differ diff --git a/assets/zht/login/OCR_YEAR.png b/assets/zht/login/OCR_YEAR.png new file mode 100644 index 0000000..4db7b6f Binary files /dev/null and b/assets/zht/login/OCR_YEAR.png differ diff --git a/assets/en/login/SURVEY.BUTTON.png b/assets/zht/login/SURVEY.BUTTON.png similarity index 100% rename from assets/en/login/SURVEY.BUTTON.png rename to assets/zht/login/SURVEY.BUTTON.png diff --git a/assets/en/login/SURVEY.png b/assets/zht/login/SURVEY.png similarity index 100% rename from assets/en/login/SURVEY.png rename to assets/zht/login/SURVEY.png diff --git a/assets/zht/login/UPDATE.BUTTON.png b/assets/zht/login/UPDATE.BUTTON.png new file mode 100644 index 0000000..d1c1b3d Binary files /dev/null and b/assets/zht/login/UPDATE.BUTTON.png differ diff --git a/assets/zht/login/UPDATE.png b/assets/zht/login/UPDATE.png new file mode 100644 index 0000000..1fe6fb2 Binary files /dev/null and b/assets/zht/login/UPDATE.png differ diff --git a/assets/zht/mail/MAIL_RECEIVE.png b/assets/zht/mail/MAIL_RECEIVE.png new file mode 100644 index 0000000..e7b5220 Binary files /dev/null and b/assets/zht/mail/MAIL_RECEIVE.png differ diff --git a/assets/zht/mail/MAIL_RECEIVED.png b/assets/zht/mail/MAIL_RECEIVED.png new file mode 100644 index 0000000..fd119f7 Binary files /dev/null and b/assets/zht/mail/MAIL_RECEIVED.png differ diff --git a/assets/zht/mission/CHECK_COMMISSIONS.png b/assets/zht/mission/CHECK_COMMISSIONS.png new file mode 100644 index 0000000..bb2baae Binary files /dev/null and b/assets/zht/mission/CHECK_COMMISSIONS.png differ diff --git a/assets/zht/mission/CHECK_CR.png b/assets/zht/mission/CHECK_CR.png new file mode 100644 index 0000000..048a186 Binary files /dev/null and b/assets/zht/mission/CHECK_CR.png differ diff --git a/assets/zht/mission/CHECK_MISSION_SWEEP.png b/assets/zht/mission/CHECK_MISSION_SWEEP.png new file mode 100644 index 0000000..bf6864a Binary files /dev/null and b/assets/zht/mission/CHECK_MISSION_SWEEP.png differ diff --git a/assets/zht/mission/CHECK_XP.png b/assets/zht/mission/CHECK_XP.png new file mode 100644 index 0000000..192abf6 Binary files /dev/null and b/assets/zht/mission/CHECK_XP.png differ diff --git a/assets/zht/mission/EVENT_INDEX.png b/assets/zht/mission/EVENT_INDEX.png new file mode 100644 index 0000000..104fbc4 Binary files /dev/null and b/assets/zht/mission/EVENT_INDEX.png differ diff --git a/assets/zht/mission/EVENT_ITEM.png b/assets/zht/mission/EVENT_ITEM.png new file mode 100644 index 0000000..2f2a2a7 Binary files /dev/null and b/assets/zht/mission/EVENT_ITEM.png differ diff --git a/assets/zht/mission/EVENT_LIST.png b/assets/zht/mission/EVENT_LIST.png new file mode 100644 index 0000000..a3eb22d Binary files /dev/null and b/assets/zht/mission/EVENT_LIST.png differ diff --git a/assets/zht/mission/EVENT_STARS.png b/assets/zht/mission/EVENT_STARS.png new file mode 100644 index 0000000..bdc774b Binary files /dev/null and b/assets/zht/mission/EVENT_STARS.png differ diff --git a/assets/zht/mission/HARD_OFF.png b/assets/zht/mission/HARD_OFF.png new file mode 100644 index 0000000..e41bc2a Binary files /dev/null and b/assets/zht/mission/HARD_OFF.png differ diff --git a/assets/zht/mission/HARD_ON.png b/assets/zht/mission/HARD_ON.png new file mode 100644 index 0000000..7f7dc90 Binary files /dev/null and b/assets/zht/mission/HARD_ON.png differ diff --git a/assets/zht/mission/LEFT.png b/assets/zht/mission/LEFT.png new file mode 100644 index 0000000..f1bc365 Binary files /dev/null and b/assets/zht/mission/LEFT.png differ diff --git a/assets/zht/mission/NORMAL_OFF.png b/assets/zht/mission/NORMAL_OFF.png new file mode 100644 index 0000000..2a5fab9 Binary files /dev/null and b/assets/zht/mission/NORMAL_OFF.png differ diff --git a/assets/zht/mission/NORMAL_ON.png b/assets/zht/mission/NORMAL_ON.png new file mode 100644 index 0000000..39b0af3 Binary files /dev/null and b/assets/zht/mission/NORMAL_ON.png differ diff --git a/assets/zht/mission/OCR_AREA.png b/assets/zht/mission/OCR_AREA.png new file mode 100644 index 0000000..7602382 Binary files /dev/null and b/assets/zht/mission/OCR_AREA.png differ diff --git a/assets/zht/mission/QUEST_OFF.png b/assets/zht/mission/QUEST_OFF.png new file mode 100644 index 0000000..2461875 Binary files /dev/null and b/assets/zht/mission/QUEST_OFF.png differ diff --git a/assets/zht/mission/QUEST_ON.png b/assets/zht/mission/QUEST_ON.png new file mode 100644 index 0000000..1c45adc Binary files /dev/null and b/assets/zht/mission/QUEST_ON.png differ diff --git a/assets/zht/mission/RIGHT.png b/assets/zht/mission/RIGHT.png new file mode 100644 index 0000000..e4c0e74 Binary files /dev/null and b/assets/zht/mission/RIGHT.png differ diff --git a/assets/zht/mission/SELECT_CR.png b/assets/zht/mission/SELECT_CR.png new file mode 100644 index 0000000..47dce76 Binary files /dev/null and b/assets/zht/mission/SELECT_CR.png differ diff --git a/assets/zht/mission/SELECT_XP.png b/assets/zht/mission/SELECT_XP.png new file mode 100644 index 0000000..4e972bb Binary files /dev/null and b/assets/zht/mission/SELECT_XP.png differ diff --git a/assets/zht/momotalk/BEGIN_STORY.png b/assets/zht/momotalk/BEGIN_STORY.png new file mode 100644 index 0000000..93fd84b Binary files /dev/null and b/assets/zht/momotalk/BEGIN_STORY.png differ diff --git a/assets/zht/momotalk/CHATTING.png b/assets/zht/momotalk/CHATTING.png new file mode 100644 index 0000000..3908b0a Binary files /dev/null and b/assets/zht/momotalk/CHATTING.png differ diff --git a/assets/zht/momotalk/CHAT_AREA.png b/assets/zht/momotalk/CHAT_AREA.png new file mode 100644 index 0000000..ed61a8e Binary files /dev/null and b/assets/zht/momotalk/CHAT_AREA.png differ diff --git a/assets/zht/momotalk/CONFIRM_SKIP.png b/assets/zht/momotalk/CONFIRM_SKIP.png new file mode 100644 index 0000000..596f389 Binary files /dev/null and b/assets/zht/momotalk/CONFIRM_SKIP.png differ diff --git a/assets/zht/momotalk/CONFIRM_SORT.png b/assets/zht/momotalk/CONFIRM_SORT.png new file mode 100644 index 0000000..ec30469 Binary files /dev/null and b/assets/zht/momotalk/CONFIRM_SORT.png differ diff --git a/assets/zht/momotalk/FIRST_UNREAD.png b/assets/zht/momotalk/FIRST_UNREAD.png new file mode 100644 index 0000000..deb3791 Binary files /dev/null and b/assets/zht/momotalk/FIRST_UNREAD.png differ diff --git a/assets/zht/momotalk/MENU.png b/assets/zht/momotalk/MENU.png new file mode 100644 index 0000000..0fe3caa Binary files /dev/null and b/assets/zht/momotalk/MENU.png differ diff --git a/assets/zht/momotalk/NOTIFICATION_BADGE.png b/assets/zht/momotalk/NOTIFICATION_BADGE.png new file mode 100644 index 0000000..0753e80 Binary files /dev/null and b/assets/zht/momotalk/NOTIFICATION_BADGE.png differ diff --git a/assets/zht/momotalk/REPLY.png b/assets/zht/momotalk/REPLY.png new file mode 100644 index 0000000..22ac500 Binary files /dev/null and b/assets/zht/momotalk/REPLY.png differ diff --git a/assets/zht/momotalk/SELECT_STUDENT.png b/assets/zht/momotalk/SELECT_STUDENT.png new file mode 100644 index 0000000..1ff7589 Binary files /dev/null and b/assets/zht/momotalk/SELECT_STUDENT.png differ diff --git a/assets/zht/momotalk/SKIP.png b/assets/zht/momotalk/SKIP.png new file mode 100644 index 0000000..a65e2c1 Binary files /dev/null and b/assets/zht/momotalk/SKIP.png differ diff --git a/assets/zht/momotalk/SORT_ASCENDING.png b/assets/zht/momotalk/SORT_ASCENDING.png new file mode 100644 index 0000000..617998d Binary files /dev/null and b/assets/zht/momotalk/SORT_ASCENDING.png differ diff --git a/assets/zht/momotalk/SORT_DESCENDING.png b/assets/zht/momotalk/SORT_DESCENDING.png new file mode 100644 index 0000000..f97331a Binary files /dev/null and b/assets/zht/momotalk/SORT_DESCENDING.png differ diff --git a/assets/zht/momotalk/STORY.png b/assets/zht/momotalk/STORY.png new file mode 100644 index 0000000..a821609 Binary files /dev/null and b/assets/zht/momotalk/STORY.png differ diff --git a/assets/zht/momotalk/SWITCH_MESSAGE.png b/assets/zht/momotalk/SWITCH_MESSAGE.png new file mode 100644 index 0000000..bce95dd Binary files /dev/null and b/assets/zht/momotalk/SWITCH_MESSAGE.png differ diff --git a/assets/zht/momotalk/SWITCH_MESSAGE_CHECK.png b/assets/zht/momotalk/SWITCH_MESSAGE_CHECK.png new file mode 100644 index 0000000..e3ea702 Binary files /dev/null and b/assets/zht/momotalk/SWITCH_MESSAGE_CHECK.png differ diff --git a/assets/zht/momotalk/SWITCH_STUDENT.png b/assets/zht/momotalk/SWITCH_STUDENT.png new file mode 100644 index 0000000..caf65f0 Binary files /dev/null and b/assets/zht/momotalk/SWITCH_STUDENT.png differ diff --git a/assets/zht/momotalk/SWITCH_STUDENT_CHECK.png b/assets/zht/momotalk/SWITCH_STUDENT_CHECK.png new file mode 100644 index 0000000..24191a7 Binary files /dev/null and b/assets/zht/momotalk/SWITCH_STUDENT_CHECK.png differ diff --git a/assets/zht/momotalk/UNREAD.png b/assets/zht/momotalk/UNREAD.png new file mode 100644 index 0000000..8d61179 Binary files /dev/null and b/assets/zht/momotalk/UNREAD.png differ diff --git a/assets/zht/momotalk/UNREAD_OFF.png b/assets/zht/momotalk/UNREAD_OFF.png new file mode 100644 index 0000000..0a980a6 Binary files /dev/null and b/assets/zht/momotalk/UNREAD_OFF.png differ diff --git a/assets/zht/momotalk/UNREAD_ON.png b/assets/zht/momotalk/UNREAD_ON.png new file mode 100644 index 0000000..6c16678 Binary files /dev/null and b/assets/zht/momotalk/UNREAD_ON.png differ diff --git a/assets/zht/schedule/CONFIRM.png b/assets/zht/schedule/CONFIRM.png new file mode 100644 index 0000000..d8a4d9c Binary files /dev/null and b/assets/zht/schedule/CONFIRM.png differ diff --git a/assets/zht/schedule/FIRST_ITEM.png b/assets/zht/schedule/FIRST_ITEM.png new file mode 100644 index 0000000..6de565e Binary files /dev/null and b/assets/zht/schedule/FIRST_ITEM.png differ diff --git a/assets/zht/schedule/LOCATIONS.png b/assets/zht/schedule/LOCATIONS.png new file mode 100644 index 0000000..d68babe Binary files /dev/null and b/assets/zht/schedule/LOCATIONS.png differ diff --git a/assets/zht/schedule/LOCATIONS_POPUP.png b/assets/zht/schedule/LOCATIONS_POPUP.png new file mode 100644 index 0000000..5630c4a Binary files /dev/null and b/assets/zht/schedule/LOCATIONS_POPUP.png differ diff --git a/assets/zht/schedule/OCR_TICKET.png b/assets/zht/schedule/OCR_TICKET.png new file mode 100644 index 0000000..2532168 Binary files /dev/null and b/assets/zht/schedule/OCR_TICKET.png differ diff --git a/assets/zht/schedule/SCROLL.png b/assets/zht/schedule/SCROLL.png new file mode 100644 index 0000000..c426e8d Binary files /dev/null and b/assets/zht/schedule/SCROLL.png differ diff --git a/assets/zht/schedule/START_LESSON.png b/assets/zht/schedule/START_LESSON.png new file mode 100644 index 0000000..ea75dad Binary files /dev/null and b/assets/zht/schedule/START_LESSON.png differ diff --git a/assets/zht/scrimmage/CHECK_GEHENNA.png b/assets/zht/scrimmage/CHECK_GEHENNA.png new file mode 100644 index 0000000..eda286d Binary files /dev/null and b/assets/zht/scrimmage/CHECK_GEHENNA.png differ diff --git a/assets/zht/scrimmage/CHECK_MILLENNIUM.png b/assets/zht/scrimmage/CHECK_MILLENNIUM.png new file mode 100644 index 0000000..f377868 Binary files /dev/null and b/assets/zht/scrimmage/CHECK_MILLENNIUM.png differ diff --git a/assets/zht/scrimmage/CHECK_SCRIMMAGE.png b/assets/zht/scrimmage/CHECK_SCRIMMAGE.png new file mode 100644 index 0000000..1b22ef2 Binary files /dev/null and b/assets/zht/scrimmage/CHECK_SCRIMMAGE.png differ diff --git a/assets/zht/scrimmage/CHECK_TRINITY.png b/assets/zht/scrimmage/CHECK_TRINITY.png new file mode 100644 index 0000000..8a4eb11 Binary files /dev/null and b/assets/zht/scrimmage/CHECK_TRINITY.png differ diff --git a/assets/zht/scrimmage/OCR_TICKET.png b/assets/zht/scrimmage/OCR_TICKET.png new file mode 100644 index 0000000..ef1de72 Binary files /dev/null and b/assets/zht/scrimmage/OCR_TICKET.png differ diff --git a/assets/zht/scrimmage/SELECT_GEHENNA.png b/assets/zht/scrimmage/SELECT_GEHENNA.png new file mode 100644 index 0000000..398df9e Binary files /dev/null and b/assets/zht/scrimmage/SELECT_GEHENNA.png differ diff --git a/assets/zht/scrimmage/SELECT_MILLENNIUM.png b/assets/zht/scrimmage/SELECT_MILLENNIUM.png new file mode 100644 index 0000000..22adea5 Binary files /dev/null and b/assets/zht/scrimmage/SELECT_MILLENNIUM.png differ diff --git a/assets/zht/scrimmage/SELECT_TRINITY.png b/assets/zht/scrimmage/SELECT_TRINITY.png new file mode 100644 index 0000000..be4a06f Binary files /dev/null and b/assets/zht/scrimmage/SELECT_TRINITY.png differ diff --git a/assets/zht/shop/CONFIRM_PURCHASE.BUTTON.png b/assets/zht/shop/CONFIRM_PURCHASE.BUTTON.png new file mode 100644 index 0000000..47d8b24 Binary files /dev/null and b/assets/zht/shop/CONFIRM_PURCHASE.BUTTON.png differ diff --git a/assets/zht/shop/CONFIRM_PURCHASE.png b/assets/zht/shop/CONFIRM_PURCHASE.png new file mode 100644 index 0000000..e53ccd8 Binary files /dev/null and b/assets/zht/shop/CONFIRM_PURCHASE.png differ diff --git a/assets/zht/shop/CONFIRM_REFRESH.BUTTON.png b/assets/zht/shop/CONFIRM_REFRESH.BUTTON.png new file mode 100644 index 0000000..f7a8348 Binary files /dev/null and b/assets/zht/shop/CONFIRM_REFRESH.BUTTON.png differ diff --git a/assets/zht/shop/CONFIRM_REFRESH.png b/assets/zht/shop/CONFIRM_REFRESH.png new file mode 100644 index 0000000..dc9f5ca Binary files /dev/null and b/assets/zht/shop/CONFIRM_REFRESH.png differ diff --git a/assets/zht/shop/ITEM_LIST.png b/assets/zht/shop/ITEM_LIST.png new file mode 100644 index 0000000..791f09c Binary files /dev/null and b/assets/zht/shop/ITEM_LIST.png differ diff --git a/assets/zht/shop/NORMAL_OFF.png b/assets/zht/shop/NORMAL_OFF.png new file mode 100644 index 0000000..b57ccd0 Binary files /dev/null and b/assets/zht/shop/NORMAL_OFF.png differ diff --git a/assets/zht/shop/NORMAL_ON.png b/assets/zht/shop/NORMAL_ON.png new file mode 100644 index 0000000..e73ecba Binary files /dev/null and b/assets/zht/shop/NORMAL_ON.png differ diff --git a/assets/zht/shop/OCR_REFRESH.png b/assets/zht/shop/OCR_REFRESH.png new file mode 100644 index 0000000..018c8bf Binary files /dev/null and b/assets/zht/shop/OCR_REFRESH.png differ diff --git a/assets/zht/shop/PURCHASE.png b/assets/zht/shop/PURCHASE.png new file mode 100644 index 0000000..acbe756 Binary files /dev/null and b/assets/zht/shop/PURCHASE.png differ diff --git a/assets/zht/shop/REFRESH.png b/assets/zht/shop/REFRESH.png new file mode 100644 index 0000000..39005b7 Binary files /dev/null and b/assets/zht/shop/REFRESH.png differ diff --git a/assets/zht/shop/TC_OFF.png b/assets/zht/shop/TC_OFF.png new file mode 100644 index 0000000..e061a7b Binary files /dev/null and b/assets/zht/shop/TC_OFF.png differ diff --git a/assets/zht/shop/TC_ON.png b/assets/zht/shop/TC_ON.png new file mode 100644 index 0000000..57a7d27 Binary files /dev/null and b/assets/zht/shop/TC_ON.png differ diff --git a/assets/zht/stage/list/OCR_INDEX.png b/assets/zht/stage/list/OCR_INDEX.png new file mode 100644 index 0000000..7cd4625 Binary files /dev/null and b/assets/zht/stage/list/OCR_INDEX.png differ diff --git a/assets/zht/stage/list/STAGE_ENTER.png b/assets/zht/stage/list/STAGE_ENTER.png new file mode 100644 index 0000000..ba7f61f Binary files /dev/null and b/assets/zht/stage/list/STAGE_ENTER.png differ diff --git a/assets/zht/stage/list/STAGE_ITEM.png b/assets/zht/stage/list/STAGE_ITEM.png new file mode 100644 index 0000000..1df5b9f Binary files /dev/null and b/assets/zht/stage/list/STAGE_ITEM.png differ diff --git a/assets/zht/stage/list/STAGE_LIST.png b/assets/zht/stage/list/STAGE_LIST.png new file mode 100644 index 0000000..1e8eaf4 Binary files /dev/null and b/assets/zht/stage/list/STAGE_LIST.png differ diff --git a/assets/zht/stage/list/STAGE_STARS.png b/assets/zht/stage/list/STAGE_STARS.png new file mode 100644 index 0000000..07cc44a Binary files /dev/null and b/assets/zht/stage/list/STAGE_STARS.png differ diff --git a/assets/zht/stage/sweep/CHECK_SWEEP.png b/assets/zht/stage/sweep/CHECK_SWEEP.png new file mode 100644 index 0000000..813b3a3 Binary files /dev/null and b/assets/zht/stage/sweep/CHECK_SWEEP.png differ diff --git a/assets/zht/stage/sweep/ENTER.png b/assets/zht/stage/sweep/ENTER.png new file mode 100644 index 0000000..0621937 Binary files /dev/null and b/assets/zht/stage/sweep/ENTER.png differ diff --git a/assets/zht/stage/sweep/EXIT.png b/assets/zht/stage/sweep/EXIT.png new file mode 100644 index 0000000..1f0b137 Binary files /dev/null and b/assets/zht/stage/sweep/EXIT.png differ diff --git a/assets/zht/stage/sweep/MAX.png b/assets/zht/stage/sweep/MAX.png new file mode 100644 index 0000000..52f5d85 Binary files /dev/null and b/assets/zht/stage/sweep/MAX.png differ diff --git a/assets/zht/stage/sweep/MIN.png b/assets/zht/stage/sweep/MIN.png new file mode 100644 index 0000000..f583aea Binary files /dev/null and b/assets/zht/stage/sweep/MIN.png differ diff --git a/assets/zht/stage/sweep/MINUS.png b/assets/zht/stage/sweep/MINUS.png new file mode 100644 index 0000000..bd57844 Binary files /dev/null and b/assets/zht/stage/sweep/MINUS.png differ diff --git a/assets/zht/stage/sweep/OCR_NUM.png b/assets/zht/stage/sweep/OCR_NUM.png new file mode 100644 index 0000000..ffb06ec Binary files /dev/null and b/assets/zht/stage/sweep/OCR_NUM.png differ diff --git a/assets/zht/stage/sweep/PLUS.png b/assets/zht/stage/sweep/PLUS.png new file mode 100644 index 0000000..106e382 Binary files /dev/null and b/assets/zht/stage/sweep/PLUS.png differ diff --git a/assets/zht/stage/sweep/SKIP_OK_LOWER.png b/assets/zht/stage/sweep/SKIP_OK_LOWER.png new file mode 100644 index 0000000..e957d90 Binary files /dev/null and b/assets/zht/stage/sweep/SKIP_OK_LOWER.png differ diff --git a/assets/zht/stage/sweep/SKIP_OK_UPPER.png b/assets/zht/stage/sweep/SKIP_OK_UPPER.png new file mode 100644 index 0000000..e46c7ce Binary files /dev/null and b/assets/zht/stage/sweep/SKIP_OK_UPPER.png differ diff --git a/assets/zht/stage/sweep/SKIP_SKIP.png b/assets/zht/stage/sweep/SKIP_SKIP.png new file mode 100644 index 0000000..1ccacfd Binary files /dev/null and b/assets/zht/stage/sweep/SKIP_SKIP.png differ diff --git a/assets/zht/stage/sweep/SWEEP.png b/assets/zht/stage/sweep/SWEEP.png new file mode 100644 index 0000000..acbe3a2 Binary files /dev/null and b/assets/zht/stage/sweep/SWEEP.png differ diff --git a/assets/zht/stage/sweep/SWEEP_CONFIRM.BUTTON.png b/assets/zht/stage/sweep/SWEEP_CONFIRM.BUTTON.png new file mode 100644 index 0000000..b36c3a6 Binary files /dev/null and b/assets/zht/stage/sweep/SWEEP_CONFIRM.BUTTON.png differ diff --git a/assets/zht/stage/sweep/SWEEP_CONFIRM.png b/assets/zht/stage/sweep/SWEEP_CONFIRM.png new file mode 100644 index 0000000..285565d Binary files /dev/null and b/assets/zht/stage/sweep/SWEEP_CONFIRM.png differ diff --git a/assets/zht/tactical_challenge/CHALLENGE_LOSE.BUTTON.png b/assets/zht/tactical_challenge/CHALLENGE_LOSE.BUTTON.png new file mode 100644 index 0000000..7f5ba69 Binary files /dev/null and b/assets/zht/tactical_challenge/CHALLENGE_LOSE.BUTTON.png differ diff --git a/assets/zht/tactical_challenge/CHALLENGE_LOSE.png b/assets/zht/tactical_challenge/CHALLENGE_LOSE.png new file mode 100644 index 0000000..f896b77 Binary files /dev/null and b/assets/zht/tactical_challenge/CHALLENGE_LOSE.png differ diff --git a/assets/zht/tactical_challenge/CHALLENGE_WIN.BUTTON.png b/assets/zht/tactical_challenge/CHALLENGE_WIN.BUTTON.png new file mode 100644 index 0000000..10c30bc Binary files /dev/null and b/assets/zht/tactical_challenge/CHALLENGE_WIN.BUTTON.png differ diff --git a/assets/zht/tactical_challenge/CHALLENGE_WIN.png b/assets/zht/tactical_challenge/CHALLENGE_WIN.png new file mode 100644 index 0000000..d371d74 Binary files /dev/null and b/assets/zht/tactical_challenge/CHALLENGE_WIN.png differ diff --git a/assets/zht/tactical_challenge/GET_REWARD_CREDIT.png b/assets/zht/tactical_challenge/GET_REWARD_CREDIT.png new file mode 100644 index 0000000..4bbc18f Binary files /dev/null and b/assets/zht/tactical_challenge/GET_REWARD_CREDIT.png differ diff --git a/assets/zht/tactical_challenge/GET_REWARD_DAILY.png b/assets/zht/tactical_challenge/GET_REWARD_DAILY.png new file mode 100644 index 0000000..6262ed7 Binary files /dev/null and b/assets/zht/tactical_challenge/GET_REWARD_DAILY.png differ diff --git a/assets/zht/tactical_challenge/GOT_REWARD_CREDIT.png b/assets/zht/tactical_challenge/GOT_REWARD_CREDIT.png new file mode 100644 index 0000000..2bd9619 Binary files /dev/null and b/assets/zht/tactical_challenge/GOT_REWARD_CREDIT.png differ diff --git a/assets/zht/tactical_challenge/GOT_REWARD_DAILY.png b/assets/zht/tactical_challenge/GOT_REWARD_DAILY.png new file mode 100644 index 0000000..d231e39 Binary files /dev/null and b/assets/zht/tactical_challenge/GOT_REWARD_DAILY.png differ diff --git a/assets/zht/tactical_challenge/OCR_TICKET.png b/assets/zht/tactical_challenge/OCR_TICKET.png new file mode 100644 index 0000000..ecc895d Binary files /dev/null and b/assets/zht/tactical_challenge/OCR_TICKET.png differ diff --git a/assets/zht/tactical_challenge/PLAYER_SELECT_FIRST.BUTTON.png b/assets/zht/tactical_challenge/PLAYER_SELECT_FIRST.BUTTON.png new file mode 100644 index 0000000..e80cb42 Binary files /dev/null and b/assets/zht/tactical_challenge/PLAYER_SELECT_FIRST.BUTTON.png differ diff --git a/assets/jp/base/page/LOADING_CHECK.png b/assets/zht/tactical_challenge/PLAYER_SELECT_FIRST.png similarity index 66% rename from assets/jp/base/page/LOADING_CHECK.png rename to assets/zht/tactical_challenge/PLAYER_SELECT_FIRST.png index 25fed20..f08781b 100644 Binary files a/assets/jp/base/page/LOADING_CHECK.png and b/assets/zht/tactical_challenge/PLAYER_SELECT_FIRST.png differ diff --git a/assets/zht/tactical_challenge/PLAYER_SELECT_SECOND.BUTTON.png b/assets/zht/tactical_challenge/PLAYER_SELECT_SECOND.BUTTON.png new file mode 100644 index 0000000..6923724 Binary files /dev/null and b/assets/zht/tactical_challenge/PLAYER_SELECT_SECOND.BUTTON.png differ diff --git a/assets/zht/tactical_challenge/PLAYER_SELECT_SECOND.png b/assets/zht/tactical_challenge/PLAYER_SELECT_SECOND.png new file mode 100644 index 0000000..6d1548a Binary files /dev/null and b/assets/zht/tactical_challenge/PLAYER_SELECT_SECOND.png differ diff --git a/assets/zht/tactical_challenge/PLAYER_SELECT_THIRD.BUTTON.png b/assets/zht/tactical_challenge/PLAYER_SELECT_THIRD.BUTTON.png new file mode 100644 index 0000000..a06c13a Binary files /dev/null and b/assets/zht/tactical_challenge/PLAYER_SELECT_THIRD.BUTTON.png differ diff --git a/assets/zht/tactical_challenge/PLAYER_SELECT_THIRD.png b/assets/zht/tactical_challenge/PLAYER_SELECT_THIRD.png new file mode 100644 index 0000000..2038593 Binary files /dev/null and b/assets/zht/tactical_challenge/PLAYER_SELECT_THIRD.png differ diff --git a/assets/zht/tactical_challenge/PREPARE_CHALLENGE.BUTTON.png b/assets/zht/tactical_challenge/PREPARE_CHALLENGE.BUTTON.png new file mode 100644 index 0000000..85b6976 Binary files /dev/null and b/assets/zht/tactical_challenge/PREPARE_CHALLENGE.BUTTON.png differ diff --git a/assets/zht/tactical_challenge/PREPARE_CHALLENGE.png b/assets/zht/tactical_challenge/PREPARE_CHALLENGE.png new file mode 100644 index 0000000..74d9fff Binary files /dev/null and b/assets/zht/tactical_challenge/PREPARE_CHALLENGE.png differ diff --git a/assets/zht/tactical_challenge/SKIP_OFF.BUTTON.png b/assets/zht/tactical_challenge/SKIP_OFF.BUTTON.png new file mode 100644 index 0000000..5f78de6 Binary files /dev/null and b/assets/zht/tactical_challenge/SKIP_OFF.BUTTON.png differ diff --git a/assets/zht/tactical_challenge/SKIP_OFF.png b/assets/zht/tactical_challenge/SKIP_OFF.png new file mode 100644 index 0000000..2dc4722 Binary files /dev/null and b/assets/zht/tactical_challenge/SKIP_OFF.png differ diff --git a/assets/zht/tactical_challenge/SKIP_ON.BUTTON.png b/assets/zht/tactical_challenge/SKIP_ON.BUTTON.png new file mode 100644 index 0000000..3404c96 Binary files /dev/null and b/assets/zht/tactical_challenge/SKIP_ON.BUTTON.png differ diff --git a/assets/zht/tactical_challenge/SKIP_ON.png b/assets/zht/tactical_challenge/SKIP_ON.png new file mode 100644 index 0000000..7785391 Binary files /dev/null and b/assets/zht/tactical_challenge/SKIP_ON.png differ diff --git a/assets/zht/tactical_challenge/START_CHALLENGE.BUTTON.png b/assets/zht/tactical_challenge/START_CHALLENGE.BUTTON.png new file mode 100644 index 0000000..0b3e8b2 Binary files /dev/null and b/assets/zht/tactical_challenge/START_CHALLENGE.BUTTON.png differ diff --git a/assets/zht/tactical_challenge/START_CHALLENGE.png b/assets/zht/tactical_challenge/START_CHALLENGE.png new file mode 100644 index 0000000..2927ea4 Binary files /dev/null and b/assets/zht/tactical_challenge/START_CHALLENGE.png differ diff --git a/assets/zht/task/CLAIM.png b/assets/zht/task/CLAIM.png new file mode 100644 index 0000000..93ac3d6 Binary files /dev/null and b/assets/zht/task/CLAIM.png differ diff --git a/assets/zht/task/CLAIMED.png b/assets/zht/task/CLAIMED.png new file mode 100644 index 0000000..858b44a Binary files /dev/null and b/assets/zht/task/CLAIMED.png differ diff --git a/assets/zht/task/CLAIMED_ALL.png b/assets/zht/task/CLAIMED_ALL.png new file mode 100644 index 0000000..844be29 Binary files /dev/null and b/assets/zht/task/CLAIMED_ALL.png differ diff --git a/assets/zht/task/CLAIM_ALL.png b/assets/zht/task/CLAIM_ALL.png new file mode 100644 index 0000000..08f2724 Binary files /dev/null and b/assets/zht/task/CLAIM_ALL.png differ diff --git a/assets/zht/task/COMPLETE.png b/assets/zht/task/COMPLETE.png new file mode 100644 index 0000000..b5d1074 Binary files /dev/null and b/assets/zht/task/COMPLETE.png differ diff --git a/config/deploy.template-cn.yaml b/config/deploy.template-cn.yaml index 6f79910..c36ca93 100644 --- a/config/deploy.template-cn.yaml +++ b/config/deploy.template-cn.yaml @@ -128,8 +128,8 @@ Deploy: WebuiHost: 0.0.0.0 # --port. Port to listen # You will be able to access webui via `http://{host}:{port}` - # [In most cases] Default to 22367 - WebuiPort: 22367 + # [In most cases] Default to 23467 + WebuiPort: 23467 # Language to use on web ui # 'zh-CN' for Chinese simplified # 'en-US' for English diff --git a/config/deploy.template.yaml b/config/deploy.template.yaml index f124e51..ad137cb 100644 --- a/config/deploy.template.yaml +++ b/config/deploy.template.yaml @@ -128,8 +128,8 @@ Deploy: WebuiHost: 0.0.0.0 # --port. Port to listen # You will be able to access webui via `http://{host}:{port}` - # [In most cases] Default to 22367 - WebuiPort: 22367 + # [In most cases] Default to 23467 + WebuiPort: 23467 # Language to use on web ui # 'zh-CN' for Chinese simplified # 'en-US' for English diff --git a/config/template.json b/config/template.json index 69525b4..cbe76d9 100644 --- a/config/template.json +++ b/config/template.json @@ -33,93 +33,6 @@ "ServerUpdate": "04:00" } }, - "Cafe": { - "Scheduler": { - "Enable": true, - "NextRun": "2020-01-01 00:00:00", - "Command": "Cafe", - "ServerUpdate": "04:00, 16:00" - }, - "Cafe": { - "Reward": true, - "Touch": true, - "AutoAdjust": true, - "SecondCafe": true - } - }, - "Mail": { - "Scheduler": { - "Enable": true, - "NextRun": "2020-01-01 00:00:00", - "Command": "Mail", - "ServerUpdate": "04:00" - } - }, - "Circle": { - "Scheduler": { - "Enable": true, - "NextRun": "2020-01-01 00:00:00", - "Command": "Circle", - "ServerUpdate": "04:00" - } - }, - "Bounty": { - "Scheduler": { - "Enable": true, - "NextRun": "2020-01-01 00:00:00", - "Command": "Bounty", - "ServerUpdate": "04:00" - }, - "Bounty": { - "OnError": "skip" - }, - "Highway": { - "Stage": 1, - "Count": 2 - }, - "DesertRailroad": { - "Stage": 1, - "Count": 2 - }, - "Schoolhouse": { - "Stage": 1, - "Count": 2 - } - }, - "Scrimmage": { - "Scheduler": { - "Enable": true, - "NextRun": "2020-01-01 00:00:00", - "Command": "Scrimmage", - "ServerUpdate": "04:00" - }, - "Scrimmage": { - "OnError": "skip" - }, - "Trinity": { - "Stage": 1, - "Count": 2 - }, - "Gehenna": { - "Stage": 1, - "Count": 2 - }, - "Millennium": { - "Stage": 1, - "Count": 2 - } - }, - "TacticalChallenge": { - "Scheduler": { - "Enable": true, - "NextRun": "2020-01-01 00:00:00", - "Command": "TacticalChallenge", - "ServerUpdate": "15:00" - }, - "TacticalChallenge": { - "PlayerSelect": 0 - } - }, "DataUpdate": { "Scheduler": { "Enable": true, @@ -135,5 +48,210 @@ "ScrimmageTicket": {}, "TacticalChallengeTicket": {} } + }, + "Cafe": { + "Scheduler": { + "Enable": true, + "NextRun": "2020-01-01 00:00:00", + "Command": "Cafe", + "ServerUpdate": "04:00, 16:00" + }, + "Cafe": { + "Reward": true, + "Touch": true, + "AutoAdjust": true, + "SecondCafe": true + }, + "Invitation": { + "Enable": true, + "WaitingHour": 0, + "Choice": "list_top", + "Name": null, + "Substitute": false + } + }, + "Schedule": { + "Scheduler": { + "Enable": false, + "NextRun": "2020-01-01 00:00:00", + "Command": "Schedule", + "ServerUpdate": "04:00" + }, + "Schedule": { + "OnError": "skip" + }, + "Choice1": { + "Location": "None", + "Classrooms": null + }, + "Choice2": { + "Location": "None", + "Classrooms": null + }, + "Choice3": { + "Location": "None", + "Classrooms": null + }, + "Choice4": { + "Location": "None", + "Classrooms": null + }, + "Choice5": { + "Location": "None", + "Classrooms": null + }, + "Choice6": { + "Location": "None", + "Classrooms": null + }, + "Choice7": { + "Location": "None", + "Classrooms": null + } + }, + "Shop": { + "Scheduler": { + "Enable": false, + "NextRun": "2020-01-01 00:00:00", + "Command": "Shop", + "ServerUpdate": "04:00" + }, + "NormalShop": { + "Enable": false, + "Purchases": 1, + "1": false, + "2": false, + "3": false, + "4": false, + "5": false, + "6": false, + "7": false, + "8": false, + "9": false, + "10": false, + "11": false, + "12": false, + "13": false, + "14": false, + "15": false, + "16": false, + "17": false, + "18": false, + "19": false, + "20": false + }, + "TacticalChallengeShop": { + "Enable": false, + "Purchases": 1, + "1": false, + "2": false, + "3": false, + "4": false, + "5": false, + "6": false, + "7": false, + "8": false, + "9": false, + "10": false, + "11": false, + "12": false, + "13": false, + "14": false, + "15": false + } + }, + "Bounty": { + "Scheduler": { + "Enable": true, + "NextRun": "2020-01-01 00:00:00", + "Command": "Bounty", + "ServerUpdate": "04:00" + }, + "Bounty": { + "OnError": "skip" + }, + "Highway": { + "Stage": 0, + "Count": 2 + }, + "DesertRailroad": { + "Stage": 0, + "Count": 2 + }, + "Schoolhouse": { + "Stage": 0, + "Count": 2 + } + }, + "Scrimmage": { + "Scheduler": { + "Enable": true, + "NextRun": "2020-01-01 00:00:00", + "Command": "Scrimmage", + "ServerUpdate": "04:00" + }, + "Scrimmage": { + "OnError": "skip" + }, + "Trinity": { + "Stage": 0, + "Count": 2 + }, + "Gehenna": { + "Stage": 0, + "Count": 2 + }, + "Millennium": { + "Stage": 0, + "Count": 2 + } + }, + "TacticalChallenge": { + "Scheduler": { + "Enable": true, + "NextRun": "2020-01-01 00:00:00", + "Command": "TacticalChallenge", + "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, + "NextRun": "2020-01-01 00:00:00", + "Command": "Circle", + "ServerUpdate": "04:00" + } + }, + "Task": { + "Scheduler": { + "Enable": false, + "NextRun": "2020-01-01 00:00:00", + "Command": "Task", + "ServerUpdate": "04:00" + } + }, + "Mail": { + "Scheduler": { + "Enable": true, + "NextRun": "2020-01-01 00:00:00", + "Command": "Mail", + "ServerUpdate": "04:00" + } + }, + "Momotalk": { + "Momotalk": { + "Enable": true + } } } \ No newline at end of file diff --git a/deploy/Windows/config.py b/deploy/Windows/config.py index 78d4bf6..b6b528e 100644 --- a/deploy/Windows/config.py +++ b/deploy/Windows/config.py @@ -55,7 +55,7 @@ class ConfigModel: # Webui WebuiHost: str = "0.0.0.0" - WebuiPort: int = 22367 + WebuiPort: int = 23467 Language: str = "en-US" Theme: str = "default" DpiScaling: bool = True @@ -77,6 +77,12 @@ class DeployConfig(ConfigModel): self.config_template = {} self.read() + self.set_repo() + + self.write() + self.show_config() + + def set_repo(self): # Bypass webui.config.DeployConfig.__setattr__() # Don't write these into deploy.yaml if self.Repository == 'cn': @@ -84,9 +90,6 @@ class DeployConfig(ConfigModel): if self.Repository == 'global': super().__setattr__('Repository', 'https://github.com/TheFunny/ArisuAutoSweeper') - self.write() - self.show_config() - def show_config(self): logger.hr("Show deploy config", 1) for k, v in self.config.items(): diff --git a/deploy/Windows/template.yaml b/deploy/Windows/template.yaml index a245bff..2dab174 100644 --- a/deploy/Windows/template.yaml +++ b/deploy/Windows/template.yaml @@ -128,8 +128,8 @@ Deploy: WebuiHost: 0.0.0.0 # --port. Port to listen # You will be able to access webui via `http://{host}:{port}` - # [In most cases] Default to 22367 - WebuiPort: 22367 + # [In most cases] Default to 23467 + WebuiPort: 23467 # Language to use on web ui # 'zh-CN' for Chinese simplified # 'en-US' for English diff --git a/gui.py b/gui.py index 2ecc6b2..776ed9f 100644 --- a/gui.py +++ b/gui.py @@ -49,7 +49,7 @@ def func(ev: threading.Event): args, _ = parser.parse_known_args() host = args.host or State.deploy_config.WebuiHost or "0.0.0.0" - port = args.port or int(State.deploy_config.WebuiPort) or 22367 + port = args.port or int(State.deploy_config.WebuiPort) or 23467 State.electron = args.electron logger.hr("Launcher config") diff --git a/licenses/BAAH-license b/licenses/BAAH-license new file mode 100644 index 0000000..c2885de --- /dev/null +++ b/licenses/BAAH-license @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015-present Electron React Boilerplate + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licenses/CTkMessagebox-license b/licenses/CTkMessagebox-license new file mode 100644 index 0000000..0e259d4 --- /dev/null +++ b/licenses/CTkMessagebox-license @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/licenses/CTkScrollableDropdown-license b/licenses/CTkScrollableDropdown-license new file mode 100644 index 0000000..c136acb --- /dev/null +++ b/licenses/CTkScrollableDropdown-license @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Akash Bora + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licenses/CTkToolTip-license b/licenses/CTkToolTip-license new file mode 100644 index 0000000..0e259d4 --- /dev/null +++ b/licenses/CTkToolTip-license @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/module/alas.py b/module/alas.py index 34d1d7b..659d310 100644 --- a/module/alas.py +++ b/module/alas.py @@ -14,6 +14,9 @@ from module.exception import * from module.logger import logger from module.notify import handle_notify +from MCE.custom_widgets.ctkmessagebox import CTkMessagebox +import subprocess +import platform class AzurLaneAutoScript: stop_event: threading.Event = None @@ -26,6 +29,7 @@ class AzurLaneAutoScript: # Failure count of tasks # Key: str, task name, value: int, failure count self.failure_record = {} + self.operating_system = platform.system() @cached_property def config(self): @@ -245,6 +249,46 @@ class AzurLaneAutoScript: if not self.wait_until(task.next_run): del_cached_property(self, 'config') continue + elif method == 'exit_aas': + if abs(task.next_run - datetime.now()) >= timedelta(minutes=2): # ensure tactical challenge is fully ran + self.config.Optimization_WhenTaskQueueEmpty = 'goto_main' + self.exit_aas() + exit(1) + release_resources() + self.device.release_during_wait() + if not self.wait_until(task.next_run): + del_cached_property(self, 'config') + continue + elif method == 'exit_emulator': + if abs(task.next_run - datetime.now()) >= timedelta(minutes=2): + self.config.Optimization_WhenTaskQueueEmpty = 'goto_main' + self.exit_emulator() + exit(1) # stops AAS from restarting emulator + release_resources() + self.device.release_during_wait() + if not self.wait_until(task.next_run): + del_cached_property(self, 'config') + continue + elif method == 'exit_aas_emulator': + if abs(task.next_run - datetime.now()) >= timedelta(minutes=2): + self.config.Optimization_WhenTaskQueueEmpty = 'goto_main' + self.exit_emulator() + self.exit_aas() + exit(1) + release_resources() + self.device.release_during_wait() + if not self.wait_until(task.next_run): + del_cached_property(self, 'config') + continue + elif method == 'shutdown': + if abs(task.next_run - datetime.now()) >= timedelta(minutes=2): + self.config.Optimization_WhenTaskQueueEmpty = 'goto_main' + self.shutdown() + release_resources() + self.device.release_during_wait() + if not self.wait_until(task.next_run): + del_cached_property(self, 'config') + continue else: logger.warning(f'Invalid Optimization_WhenTaskQueueEmpty: {method}, fallback to stay_there') release_resources() @@ -325,6 +369,55 @@ class AzurLaneAutoScript: self.checker.check_now() continue + def exit_emulator(self): + if self.operating_system != 'Windows': + logger.error("Exiting emulator is only supported on Windows") + return + try: + from module.device.platform.platform_windows import PlatformWindows + PlatformWindows(self.config).emulator_stop() + except: + logger.error("Failed to stop emulator. It may be due to a lack of administrator privileges.") + + def exit_aas(self): + if self.operating_system != 'Windows': + logger.error("Exiting AAS is only supported on Windows") + return + try: + from module.device.platform.platform_windows import PlatformWindows + from deploy.Windows.alas import AlasManager + PlatformWindows(self.config).kill_process_by_regex("aas\.exe") + AlasManager().alas_kill() + except: + logger.error("Failed to stop AAS. It may be due to a lack of administrator privileges.") + + def shutdown(self): + if self.operating_system not in ["Windows", "Linux", "Darwin"]: + logger.error("Shutdown set during wait but operating system not supported") + else: + logger.info('Shutdown during wait') + try: + self.start_shutdown() + msg = CTkMessagebox(title="AAS: Cancel Shutdown?", message="All tasks have been completed: shutting down. Do you want to cancel?", + icon="MCE\icons\question.png", option_1="Cancel") + response = msg.get() + if response=="Cancel": + self.cancel_shutdown() + except: + logger.error("Failed to shutdown. It may be due to a lack of administrator privileges.") + + def start_shutdown(self): + logger.info("Running Shutting down") + if self.operating_system == "Windows": + subprocess.run(["shutdown", "-s", "-t", "60"]) + elif self.operating_system in ["Linux", "Darwin"]: + subprocess.run(["shutdown", "-h", "+1"]) + + def cancel_shutdown(self): + if self.operating_system == "Windows": + subprocess.run(["shutdown", "-a"]) + elif self.operating_system in ["Linux", "Darwin"]: + subprocess.run(["shutdown", "-c"]) if __name__ == '__main__': alas = AzurLaneAutoScript() diff --git a/module/config/argument/args.json b/module/config/argument/args.json index fa1d829..0912d4b 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -12,6 +12,10 @@ "option": [ "auto", "JP-Official", + "OVERSEA-TWHKMO", + "OVERSEA-Korea", + "OVERSEA-Asia", + "OVERSEA-America", "OVERSEA-Global" ] }, @@ -21,7 +25,8 @@ "option": [ "auto", "jp", - "en" + "en", + "zht" ] }, "ScreenshotMethod": { @@ -125,7 +130,11 @@ "option": [ "stay_there", "goto_main", - "close_game" + "close_game", + "exit_aas", + "exit_emulator", + "exit_aas_emulator", + "shutdown" ] } } @@ -159,329 +168,6 @@ } } }, - "Cafe": { - "Scheduler": { - "Enable": { - "type": "checkbox", - "value": true, - "option": [ - true, - false - ] - }, - "NextRun": { - "type": "datetime", - "value": "2020-01-01 00:00:00", - "validate": "datetime" - }, - "Command": { - "type": "input", - "value": "Cafe", - "display": "hide" - }, - "ServerUpdate": { - "type": "input", - "value": "04:00, 16:00", - "display": "hide" - } - }, - "Cafe": { - "Reward": { - "type": "checkbox", - "value": true - }, - "Touch": { - "type": "checkbox", - "value": true - }, - "AutoAdjust": { - "type": "checkbox", - "value": true - }, - "SecondCafe": { - "type": "checkbox", - "value": true - } - } - }, - "Mail": { - "Scheduler": { - "Enable": { - "type": "checkbox", - "value": true, - "option": [ - true, - false - ] - }, - "NextRun": { - "type": "datetime", - "value": "2020-01-01 00:00:00", - "validate": "datetime" - }, - "Command": { - "type": "input", - "value": "Mail", - "display": "hide" - }, - "ServerUpdate": { - "type": "input", - "value": "04:00", - "display": "hide" - } - } - }, - "Circle": { - "Scheduler": { - "Enable": { - "type": "checkbox", - "value": true, - "option": [ - true, - false - ] - }, - "NextRun": { - "type": "datetime", - "value": "2020-01-01 00:00:00", - "validate": "datetime" - }, - "Command": { - "type": "input", - "value": "Circle", - "display": "hide" - }, - "ServerUpdate": { - "type": "input", - "value": "04:00", - "display": "hide" - } - } - }, - "Bounty": { - "Scheduler": { - "Enable": { - "type": "checkbox", - "value": true, - "option": [ - true, - false - ] - }, - "NextRun": { - "type": "datetime", - "value": "2020-01-01 00:00:00", - "validate": "datetime" - }, - "Command": { - "type": "input", - "value": "Bounty", - "display": "hide" - }, - "ServerUpdate": { - "type": "input", - "value": "04:00", - "display": "hide" - } - }, - "Bounty": { - "OnError": { - "type": "select", - "value": "skip", - "option": [ - "stop", - "skip" - ] - } - }, - "Highway": { - "Stage": { - "type": "select", - "value": 1, - "option": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9 - ] - }, - "Count": { - "type": "input", - "value": 2 - } - }, - "DesertRailroad": { - "Stage": { - "type": "select", - "value": 1, - "option": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9 - ] - }, - "Count": { - "type": "input", - "value": 2 - } - }, - "Schoolhouse": { - "Stage": { - "type": "select", - "value": 1, - "option": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9 - ] - }, - "Count": { - "type": "input", - "value": 2 - } - } - }, - "Scrimmage": { - "Scheduler": { - "Enable": { - "type": "checkbox", - "value": true, - "option": [ - true, - false - ] - }, - "NextRun": { - "type": "datetime", - "value": "2020-01-01 00:00:00", - "validate": "datetime" - }, - "Command": { - "type": "input", - "value": "Scrimmage", - "display": "hide" - }, - "ServerUpdate": { - "type": "input", - "value": "04:00", - "display": "hide" - } - }, - "Scrimmage": { - "OnError": { - "type": "select", - "value": "skip", - "option": [ - "stop", - "skip" - ] - } - }, - "Trinity": { - "Stage": { - "type": "select", - "value": 1, - "option": [ - 1, - 2, - 3, - 4 - ] - }, - "Count": { - "type": "input", - "value": 2 - } - }, - "Gehenna": { - "Stage": { - "type": "select", - "value": 1, - "option": [ - 1, - 2, - 3, - 4 - ] - }, - "Count": { - "type": "input", - "value": 2 - } - }, - "Millennium": { - "Stage": { - "type": "select", - "value": 1, - "option": [ - 1, - 2, - 3, - 4 - ] - }, - "Count": { - "type": "input", - "value": 2 - } - } - }, - "TacticalChallenge": { - "Scheduler": { - "Enable": { - "type": "checkbox", - "value": true, - "option": [ - true, - false - ] - }, - "NextRun": { - "type": "datetime", - "value": "2020-01-01 00:00:00", - "validate": "datetime" - }, - "Command": { - "type": "input", - "value": "TacticalChallenge", - "display": "hide" - }, - "ServerUpdate": { - "type": "input", - "value": "15:00", - "display": "hide" - } - }, - "TacticalChallenge": { - "PlayerSelect": { - "type": "select", - "value": 0, - "option": [ - 0, - 1, - 2, - 3 - ] - } - } - }, "DataUpdate": { "Scheduler": { "Enable": { @@ -560,5 +246,829 @@ "color": "#7ac8e5" } } + }, + "Cafe": { + "Scheduler": { + "Enable": { + "type": "checkbox", + "value": true, + "option": [ + true, + false + ] + }, + "NextRun": { + "type": "datetime", + "value": "2020-01-01 00:00:00", + "validate": "datetime" + }, + "Command": { + "type": "input", + "value": "Cafe", + "display": "hide" + }, + "ServerUpdate": { + "type": "input", + "value": "04:00, 16:00", + "display": "hide" + } + }, + "Cafe": { + "Reward": { + "type": "checkbox", + "value": true + }, + "Touch": { + "type": "checkbox", + "value": true + }, + "AutoAdjust": { + "type": "checkbox", + "value": true + }, + "SecondCafe": { + "type": "checkbox", + "value": true + } + }, + "Invitation": { + "Enable": { + "type": "checkbox", + "value": true + }, + "WaitingHour": { + "type": "select", + "value": 0, + "option": [ + 0, + 3, + 6, + 9 + ] + }, + "Choice": { + "type": "select", + "value": "list_top", + "option": [ + "list_top", + "by_name" + ] + }, + "Name": { + "type": "textarea", + "value": null + }, + "Substitute": { + "type": "checkbox", + "value": false + } + } + }, + "Schedule": { + "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": "Schedule", + "display": "hide" + }, + "ServerUpdate": { + "type": "input", + "value": "04:00", + "display": "hide" + } + }, + "Schedule": { + "OnError": { + "type": "select", + "value": "skip", + "option": [ + "stop", + "skip" + ] + } + }, + "Choice1": { + "Location": { + "type": "select", + "value": "None", + "option": [ + "None", + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ] + }, + "Classrooms": { + "type": "textarea", + "value": null + } + }, + "Choice2": { + "Location": { + "type": "select", + "value": "None", + "option": [ + "None", + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ] + }, + "Classrooms": { + "type": "textarea", + "value": null + } + }, + "Choice3": { + "Location": { + "type": "select", + "value": "None", + "option": [ + "None", + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ] + }, + "Classrooms": { + "type": "textarea", + "value": null + } + }, + "Choice4": { + "Location": { + "type": "select", + "value": "None", + "option": [ + "None", + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ] + }, + "Classrooms": { + "type": "textarea", + "value": null + } + }, + "Choice5": { + "Location": { + "type": "select", + "value": "None", + "option": [ + "None", + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ] + }, + "Classrooms": { + "type": "textarea", + "value": null + } + }, + "Choice6": { + "Location": { + "type": "select", + "value": "None", + "option": [ + "None", + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ] + }, + "Classrooms": { + "type": "textarea", + "value": null + } + }, + "Choice7": { + "Location": { + "type": "select", + "value": "None", + "option": [ + "None", + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ] + }, + "Classrooms": { + "type": "textarea", + "value": null + } + } + }, + "Shop": { + "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": "Shop", + "display": "hide" + }, + "ServerUpdate": { + "type": "input", + "value": "04:00", + "display": "hide" + } + }, + "NormalShop": { + "Enable": { + "type": "checkbox", + "value": false + }, + "Purchases": { + "type": "select", + "value": 1, + "option": [ + 1, + 2, + 3, + 4 + ] + }, + "1": { + "type": "checkbox", + "value": false + }, + "2": { + "type": "checkbox", + "value": false + }, + "3": { + "type": "checkbox", + "value": false + }, + "4": { + "type": "checkbox", + "value": false + }, + "5": { + "type": "checkbox", + "value": false + }, + "6": { + "type": "checkbox", + "value": false + }, + "7": { + "type": "checkbox", + "value": false + }, + "8": { + "type": "checkbox", + "value": false + }, + "9": { + "type": "checkbox", + "value": false + }, + "10": { + "type": "checkbox", + "value": false + }, + "11": { + "type": "checkbox", + "value": false + }, + "12": { + "type": "checkbox", + "value": false + }, + "13": { + "type": "checkbox", + "value": false + }, + "14": { + "type": "checkbox", + "value": false + }, + "15": { + "type": "checkbox", + "value": false + }, + "16": { + "type": "checkbox", + "value": false + }, + "17": { + "type": "checkbox", + "value": false + }, + "18": { + "type": "checkbox", + "value": false + }, + "19": { + "type": "checkbox", + "value": false + }, + "20": { + "type": "checkbox", + "value": false + } + }, + "TacticalChallengeShop": { + "Enable": { + "type": "checkbox", + "value": false + }, + "Purchases": { + "type": "select", + "value": 1, + "option": [ + 1, + 2, + 3, + 4 + ] + }, + "1": { + "type": "checkbox", + "value": false + }, + "2": { + "type": "checkbox", + "value": false + }, + "3": { + "type": "checkbox", + "value": false + }, + "4": { + "type": "checkbox", + "value": false + }, + "5": { + "type": "checkbox", + "value": false + }, + "6": { + "type": "checkbox", + "value": false + }, + "7": { + "type": "checkbox", + "value": false + }, + "8": { + "type": "checkbox", + "value": false + }, + "9": { + "type": "checkbox", + "value": false + }, + "10": { + "type": "checkbox", + "value": false + }, + "11": { + "type": "checkbox", + "value": false + }, + "12": { + "type": "checkbox", + "value": false + }, + "13": { + "type": "checkbox", + "value": false + }, + "14": { + "type": "checkbox", + "value": false + }, + "15": { + "type": "checkbox", + "value": false + } + } + }, + "Bounty": { + "Scheduler": { + "Enable": { + "type": "checkbox", + "value": true, + "option": [ + true, + false + ] + }, + "NextRun": { + "type": "datetime", + "value": "2020-01-01 00:00:00", + "validate": "datetime" + }, + "Command": { + "type": "input", + "value": "Bounty", + "display": "hide" + }, + "ServerUpdate": { + "type": "input", + "value": "04:00", + "display": "hide" + } + }, + "Bounty": { + "OnError": { + "type": "select", + "value": "skip", + "option": [ + "stop", + "skip" + ] + } + }, + "Highway": { + "Stage": { + "type": "select", + "value": 0, + "option": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ] + }, + "Count": { + "type": "input", + "value": 2 + } + }, + "DesertRailroad": { + "Stage": { + "type": "select", + "value": 0, + "option": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ] + }, + "Count": { + "type": "input", + "value": 2 + } + }, + "Schoolhouse": { + "Stage": { + "type": "select", + "value": 0, + "option": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ] + }, + "Count": { + "type": "input", + "value": 2 + } + } + }, + "Scrimmage": { + "Scheduler": { + "Enable": { + "type": "checkbox", + "value": true, + "option": [ + true, + false + ] + }, + "NextRun": { + "type": "datetime", + "value": "2020-01-01 00:00:00", + "validate": "datetime" + }, + "Command": { + "type": "input", + "value": "Scrimmage", + "display": "hide" + }, + "ServerUpdate": { + "type": "input", + "value": "04:00", + "display": "hide" + } + }, + "Scrimmage": { + "OnError": { + "type": "select", + "value": "skip", + "option": [ + "stop", + "skip" + ] + } + }, + "Trinity": { + "Stage": { + "type": "select", + "value": 0, + "option": [ + 0, + 1, + 2, + 3, + 4 + ] + }, + "Count": { + "type": "input", + "value": 2 + } + }, + "Gehenna": { + "Stage": { + "type": "select", + "value": 0, + "option": [ + 0, + 1, + 2, + 3, + 4 + ] + }, + "Count": { + "type": "input", + "value": 2 + } + }, + "Millennium": { + "Stage": { + "type": "select", + "value": 0, + "option": [ + 0, + 1, + 2, + 3, + 4 + ] + }, + "Count": { + "type": "input", + "value": 2 + } + } + }, + "TacticalChallenge": { + "Scheduler": { + "Enable": { + "type": "checkbox", + "value": true, + "option": [ + true, + false + ] + }, + "NextRun": { + "type": "datetime", + "value": "2020-01-01 00:00:00", + "validate": "datetime" + }, + "Command": { + "type": "input", + "value": "TacticalChallenge", + "display": "hide" + }, + "ServerUpdate": { + "type": "input", + "value": "14:00", + "display": "hide" + } + }, + "TacticalChallenge": { + "PlayerSelect": { + "type": "select", + "value": 0, + "option": [ + 0, + 1, + 2, + 3 + ] + } + } + }, + "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": { + "type": "checkbox", + "value": true, + "option": [ + true, + false + ] + }, + "NextRun": { + "type": "datetime", + "value": "2020-01-01 00:00:00", + "validate": "datetime" + }, + "Command": { + "type": "input", + "value": "Circle", + "display": "hide" + }, + "ServerUpdate": { + "type": "input", + "value": "04:00", + "display": "hide" + } + } + }, + "Task": { + "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": "Task", + "display": "hide" + }, + "ServerUpdate": { + "type": "input", + "value": "04:00", + "display": "hide" + } + } + }, + "Mail": { + "Scheduler": { + "Enable": { + "type": "checkbox", + "value": true, + "option": [ + true, + false + ] + }, + "NextRun": { + "type": "datetime", + "value": "2020-01-01 00:00:00", + "validate": "datetime" + }, + "Command": { + "type": "input", + "value": "Mail", + "display": "hide" + }, + "ServerUpdate": { + "type": "input", + "value": "04:00", + "display": "hide" + } + } + }, + "Momotalk": { + "Momotalk": { + "Enable": { + "type": "checkbox", + "value": true, + "display": "hide" + } + } } } \ No newline at end of file diff --git a/module/config/argument/argument.yaml b/module/config/argument/argument.yaml index ff57ef0..9e9e2a3 100644 --- a/module/config/argument/argument.yaml +++ b/module/config/argument/argument.yaml @@ -23,7 +23,7 @@ Emulator: option: [ auto, ] GameLanguage: value: auto - option: [ auto, jp, en ] + option: [ auto, jp, en, zht ] ScreenshotMethod: value: auto option: [ auto, ADB, ADB_nc, uiautomator2, aScreenCap, aScreenCap_nc, DroidCast, DroidCast_raw, scrcpy, nemu_ipc ] @@ -71,7 +71,7 @@ Optimization: CombatScreenshotInterval: 1.0 WhenTaskQueueEmpty: value: goto_main - option: [ stay_there, goto_main, close_game ] + option: [ stay_there, goto_main, close_game, exit_aas, exit_emulator, exit_aas_emulator, shutdown ] # ==================== Daily ==================== @@ -80,6 +80,73 @@ Cafe: Touch: true AutoAdjust: true SecondCafe: true +Invitation: + Enable: true + WaitingHour: + value: 0 + option: [ 0, 3, 6, 9 ] + Choice: + value: list_top + option: [ list_top, by_name ] + Name: + value: null + type: textarea + Substitute: false + +Schedule: + OnError: + value: skip + option: [ stop, skip ] + +Choice1: + Location: + value: None + option: [ None, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] + Classrooms: + value: null + type: textarea +Choice2: + Location: + value: None + option: [ None, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] + Classrooms: + value: null + type: textarea +Choice3: + Location: + value: None + option: [ None, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] + Classrooms: + value: null + type: textarea +Choice4: + Location: + value: None + option: [ None, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] + Classrooms: + value: null + type: textarea +Choice5: + Location: + value: None + option: [ None, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] + Classrooms: + value: null + type: textarea +Choice6: + Location: + value: None + option: [ None, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] + Classrooms: + value: null + type: textarea +Choice7: + Location: + value: None + option: [ None, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] + Classrooms: + value: null + type: textarea Bounty: OnError: @@ -88,18 +155,18 @@ Bounty: Highway: Stage: - value: 1 - option: [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] + value: 0 + option: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] Count: 2 DesertRailroad: Stage: - value: 1 - option: [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] + value: 0 + option: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] Count: 2 Schoolhouse: Stage: - value: 1 - option: [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] + value: 0 + option: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] Count: 2 Scrimmage: @@ -109,18 +176,18 @@ Scrimmage: Trinity: Stage: - value: 1 - option: [ 1, 2, 3, 4 ] + value: 0 + option: [ 0, 1, 2, 3, 4 ] Count: 2 Gehenna: Stage: - value: 1 - option: [ 1, 2, 3, 4 ] + value: 0 + option: [ 0, 1, 2, 3, 4 ] Count: 2 Millennium: Stage: - value: 1 - option: [ 1, 2, 3, 4 ] + value: 0 + option: [ 0, 1, 2, 3, 4 ] Count: 2 TacticalChallenge: @@ -128,6 +195,53 @@ TacticalChallenge: value: 0 option: [ 0, 1, 2, 3 ] +NormalShop: + Enable: false + Purchases: + value: 1 + option: [ 1, 2, 3, 4 ] + "1": false + "2": false + "3": false + "4": false + "5": false + "6": false + "7": false + "8": false + "9": false + "10": false + "11": false + "12": false + "13": false + "14": false + "15": false + "16": false + "17": false + "18": false + "19": false + "20": false + +TacticalChallengeShop: + Enable: false + Purchases: + value: 1 + option: [ 1, 2, 3, 4 ] + "1": false + "2": false + "3": false + "4": false + "5": false + "6": false + "7": false + "8": false + "9": false + "10": false + "11": false + "12": false + "13": false + "14": false + "15": false + ItemStorage: AP: stored: StoredAP @@ -153,3 +267,10 @@ ItemStorage: stored: StoredTacticalChallengeTicket order: 6 color: "#7ac8e5" + +# ==================== Momotalk ==================== + +Momotalk: + Enable: + value: true + display: hide \ No newline at end of file diff --git a/module/config/argument/menu.json b/module/config/argument/menu.json index 378272d..b871831 100644 --- a/module/config/argument/menu.json +++ b/module/config/argument/menu.json @@ -4,20 +4,43 @@ "page": "setting", "tasks": [ "Alas", - "Restart" + "Restart", + "DataUpdate" ] }, "Daily": { - "menu": "list", + "menu": "collapse", "page": "setting", "tasks": [ "Cafe", - "Mail", - "Circle", + "Schedule", + "Shop" + ] + }, + "Farm": { + "menu": "collapse", + "page": "setting", + "tasks": [ "Bounty", "Scrimmage", "TacticalChallenge", - "DataUpdate" + "Mission" + ] + }, + "Reward": { + "menu": "collapse", + "page": "setting", + "tasks": [ + "Circle", + "Task", + "Mail" + ] + }, + "Tool": { + "menu": "collapse", + "page": "tool", + "tasks": [ + "Momotalk" ] } } \ No newline at end of file diff --git a/module/config/argument/override.yaml b/module/config/argument/override.yaml index c9bfe23..2afb6ff 100644 --- a/module/config/argument/override.yaml +++ b/module/config/argument/override.yaml @@ -29,7 +29,7 @@ Cafe: TacticalChallenge: Scheduler: - ServerUpdate: "15:00" + ServerUpdate: "14:00" DataUpdate: Scheduler: diff --git a/module/config/argument/task.yaml b/module/config/argument/task.yaml index 19b634b..8f9e611 100644 --- a/module/config/argument/task.yaml +++ b/module/config/argument/task.yaml @@ -15,20 +15,41 @@ Alas: - Optimization Restart: - Scheduler + DataUpdate: + - Scheduler + - ItemStorage # ==================== Daily ==================== Daily: - menu: 'list' + menu: 'collapse' page: 'setting' tasks: Cafe: - Scheduler - Cafe - Mail: + - Invitation + Schedule: - Scheduler - Circle: + - Schedule + - Choice1 + - Choice2 + - Choice3 + - Choice4 + - Choice5 + - Choice6 + - Choice7 + Shop: - Scheduler + - NormalShop + - TacticalChallengeShop + +# ==================== Farm ==================== + +Farm: + menu: 'collapse' + page: 'setting' + tasks: Bounty: - Scheduler - Bounty @@ -44,6 +65,27 @@ Daily: TacticalChallenge: - Scheduler - TacticalChallenge - DataUpdate: + Mission: - Scheduler - - ItemStorage \ No newline at end of file + +# ==================== Rewards ==================== + +Reward: + menu: 'collapse' + page: 'setting' + tasks: + Circle: + - Scheduler + Task: + - Scheduler + Mail: + - Scheduler + +# ==================== Tool ==================== + +Tool: + menu: 'collapse' + page: 'tool' + tasks: + Momotalk: + - Momotalk \ No newline at end of file diff --git a/module/config/config_generated.py b/module/config/config_generated.py index ecbf4da..8e6669e 100644 --- a/module/config/config_generated.py +++ b/module/config/config_generated.py @@ -16,9 +16,8 @@ class GeneratedConfig: Scheduler_ServerUpdate = '04:00' # Group `Emulator` - Emulator_Serial = 'auto' - Emulator_PackageName = 'auto' # auto, JP-Official, OVERSEA-Global - Emulator_GameLanguage = 'auto' # auto, jp, en + Emulator_PackageName = 'auto' # auto, JP-Official, OVERSEA-TWHKMO, OVERSEA-Korea, OVERSEA-Asia, OVERSEA-America, OVERSEA-Global + Emulator_GameLanguage = 'auto' # auto, jp, en, zht Emulator_ScreenshotMethod = 'auto' # auto, ADB, ADB_nc, uiautomator2, aScreenCap, aScreenCap_nc, DroidCast, DroidCast_raw, scrcpy, nemu_ipc Emulator_ControlMethod = 'MaaTouch' # minitouch, MaaTouch Emulator_AdbRestart = False @@ -37,7 +36,7 @@ class GeneratedConfig: # Group `Optimization` Optimization_ScreenshotInterval = 0.3 Optimization_CombatScreenshotInterval = 1.0 - Optimization_WhenTaskQueueEmpty = 'goto_main' # stay_there, goto_main, close_game + Optimization_WhenTaskQueueEmpty = 'goto_main' # stay_there, goto_main, close_game, exit_aas, exit_emulator, exit_aas_emulator, shutdown # Group `Cafe` Cafe_Reward = True @@ -45,39 +44,120 @@ class GeneratedConfig: Cafe_AutoAdjust = True Cafe_SecondCafe = True + # Group `Invitation` + Invitation_Enable = True + Invitation_WaitingHour = 0 # 0, 3, 6, 9 + Invitation_Choice = 'list_top' # list_top, by_name + Invitation_Name = None + Invitation_Substitute = False + + # Group `Schedule` + Schedule_OnError = 'skip' # stop, skip + + # Group `Choice1` + Choice1_Location = 'None' # None, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + Choice1_Classrooms = None + + # Group `Choice2` + Choice2_Location = 'None' # None, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + Choice2_Classrooms = None + + # Group `Choice3` + Choice3_Location = 'None' # None, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + Choice3_Classrooms = None + + # Group `Choice4` + Choice4_Location = 'None' # None, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + Choice4_Classrooms = None + + # Group `Choice5` + Choice5_Location = 'None' # None, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + Choice5_Classrooms = None + + # Group `Choice6` + Choice6_Location = 'None' # None, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + Choice6_Classrooms = None + + # Group `Choice7` + Choice7_Location = 'None' # None, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + Choice7_Classrooms = None + # Group `Bounty` Bounty_OnError = 'skip' # stop, skip # Group `Highway` - Highway_Stage = 1 # 1, 2, 3, 4, 5, 6, 7, 8, 9 + Highway_Stage = 0 # 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 Highway_Count = 2 # Group `DesertRailroad` - DesertRailroad_Stage = 1 # 1, 2, 3, 4, 5, 6, 7, 8, 9 + DesertRailroad_Stage = 0 # 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 DesertRailroad_Count = 2 # Group `Schoolhouse` - Schoolhouse_Stage = 1 # 1, 2, 3, 4, 5, 6, 7, 8, 9 + Schoolhouse_Stage = 0 # 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 Schoolhouse_Count = 2 # Group `Scrimmage` Scrimmage_OnError = 'skip' # stop, skip # Group `Trinity` - Trinity_Stage = 1 # 1, 2, 3, 4 + Trinity_Stage = 0 # 0, 1, 2, 3, 4 Trinity_Count = 2 # Group `Gehenna` - Gehenna_Stage = 1 # 1, 2, 3, 4 + Gehenna_Stage = 0 # 0, 1, 2, 3, 4 Gehenna_Count = 2 # Group `Millennium` - Millennium_Stage = 1 # 1, 2, 3, 4 + Millennium_Stage = 0 # 0, 1, 2, 3, 4 Millennium_Count = 2 # Group `TacticalChallenge` TacticalChallenge_PlayerSelect = 0 # 0, 1, 2, 3 + # Group `NormalShop` + NormalShop_Enable = False + NormalShop_Purchases = 1 # 1, 2, 3, 4 + NormalShop_1 = False + NormalShop_2 = False + NormalShop_3 = False + NormalShop_4 = False + NormalShop_5 = False + NormalShop_6 = False + NormalShop_7 = False + NormalShop_8 = False + NormalShop_9 = False + NormalShop_10 = False + NormalShop_11 = False + NormalShop_12 = False + NormalShop_13 = False + NormalShop_14 = False + NormalShop_15 = False + NormalShop_16 = False + NormalShop_17 = False + NormalShop_18 = False + NormalShop_19 = False + NormalShop_20 = False + + # Group `TacticalChallengeShop` + TacticalChallengeShop_Enable = False + TacticalChallengeShop_Purchases = 1 # 1, 2, 3, 4 + TacticalChallengeShop_1 = False + TacticalChallengeShop_2 = False + TacticalChallengeShop_3 = False + TacticalChallengeShop_4 = False + TacticalChallengeShop_5 = False + TacticalChallengeShop_6 = False + TacticalChallengeShop_7 = False + TacticalChallengeShop_8 = False + TacticalChallengeShop_9 = False + TacticalChallengeShop_10 = False + TacticalChallengeShop_11 = False + TacticalChallengeShop_12 = False + TacticalChallengeShop_13 = False + TacticalChallengeShop_14 = False + TacticalChallengeShop_15 = False + # Group `ItemStorage` ItemStorage_AP = {} ItemStorage_Credit = {} @@ -85,3 +165,6 @@ class GeneratedConfig: ItemStorage_BountyTicket = {} ItemStorage_ScrimmageTicket = {} ItemStorage_TacticalChallengeTicket = {} + + # Group `Momotalk` + Momotalk_Enable = True diff --git a/module/config/config_manual.py b/module/config/config_manual.py index f1395df..071ced1 100644 --- a/module/config/config_manual.py +++ b/module/config/config_manual.py @@ -8,8 +8,8 @@ class ManualConfig: SCHEDULER_PRIORITY = """ Restart - > Cafe > Circle > Mail > DataUpdate > Bounty - > Scrimmage > TacticalChallenge + > Cafe > TacticalChallenge > Circle > Mail + > DataUpdate > Bounty > Scrimmage > Schedule > Task > Shop > Mission > Momotalk """ """ diff --git a/module/config/i18n/en-US.json b/module/config/i18n/en-US.json index 05f6713..d7a317e 100644 --- a/module/config/i18n/en-US.json +++ b/module/config/i18n/en-US.json @@ -7,6 +7,18 @@ "Daily": { "name": "Daily", "help": "" + }, + "Farm": { + "name": "Farm", + "help": "" + }, + "Reward": { + "name": "Reward", + "help": "" + }, + "Tool": { + "name": "Tools", + "help": "" } }, "Task": { @@ -18,16 +30,20 @@ "name": "Error Handling", "help": "" }, + "DataUpdate": { + "name": "Dashboard Upd", + "help": "" + }, "Cafe": { "name": "Cafe", "help": "" }, - "Mail": { - "name": "Mailbox", - "help": "" + "Schedule": { + "name": "Lesson", + "help": "AAS will execute Lesson starting from Choice 1 to Choice 7.\nIt will ignore any Choice that have Location set as None or the text area for classrooms is empty.\nIf any of the active Choices have incorrect input, it will perform the action set in Error handling." }, - "Circle": { - "name": "Club", + "Shop": { + "name": "Shop", "help": "" }, "Bounty": { @@ -42,8 +58,24 @@ "name": "Tactical Challenge", "help": "" }, - "DataUpdate": { - "name": "Dashboard Upd", + "Mission": { + "name": "Mission/Commissions/Event", + "help": "Open MCE Manager for additional settings. Must be opened if it's your first time!" + }, + "Circle": { + "name": "Club", + "help": "" + }, + "Task": { + "name": "Tasks", + "help": "" + }, + "Mail": { + "name": "Mailbox", + "help": "" + }, + "Momotalk": { + "name": "MomoTalk", "help": "" } }, @@ -85,14 +117,19 @@ "help": "Can't distinguish different regions of oversea servers, please select the server manually.", "auto": "Auto-detect", "JP-Official": "[JP]-Official", + "OVERSEA-TWHKMO": "[OVERSEA]-TW/HK/MO", + "OVERSEA-Korea": "[OVERSEA]-Korea", + "OVERSEA-Asia": "[OVERSEA]-Asia", + "OVERSEA-America": "[OVERSEA]-North America", "OVERSEA-Global": "[OVERSEA]-Global" }, "GameLanguage": { "name": "In-game Text Language", - "help": "", + "help": "Can't detect language automatically, please select the language manually.", "auto": "Auto-detect", "jp": "Japanese", - "en": "English" + "en": "English", + "zht": "Traditional Chinese" }, "ScreenshotMethod": { "name": "Screenshot Method", @@ -190,10 +227,14 @@ }, "WhenTaskQueueEmpty": { "name": "When Task Queue is Empty", - "help": "Close AL when there are no pending tasks, can help reduce CPU", + "help": "Close Blue Archive when there are no pending tasks, can help reduce CPU.\nExit AAS, emulator and shutdown should be set while AAS is running tasks otherwise it won't start; after their execution it will be reset to Goto Main Page.\nExit AAS and emulator are only available on Windows.\n\nExit emulator works using the values in Emulator Settings.\nFor accuracy, you should run AAS without emulator and AAS will list all detected instances in the Log. Copy and paste the name and path of the instance into the Emulator Settings.", "stay_there": "Stay There", "goto_main": "Goto Main Page", - "close_game": "Close Game" + "close_game": "Close Game", + "exit_aas": "Exit AAS", + "exit_emulator": "Exit Emulator", + "exit_aas_emulator": "Exit AAS & Emulator", + "shutdown": "Shutdown" } }, "Cafe": { @@ -215,7 +256,226 @@ }, "SecondCafe": { "name": "Second Floor", - "help": "JP server only\nEnable auto switch to second floor and perform interaction" + "help": "Enable auto switch to second floor and perform interaction" + } + }, + "Invitation": { + "_info": { + "name": "Invitation Settings", + "help": "" + }, + "Enable": { + "name": "Enable", + "help": "Enable cafe invitation" + }, + "WaitingHour": { + "name": "Invitation Condition", + "help": "Choose whether to invite students based on the remaining time before next cafe refresh\nThis is for obtaining maximum student interation and maximum affection when AFK. Recommended option:\n24*7 AFK:\t\t Choose \"If 9 more hours remaining\"\nNormal usage:\t Choose \"Invite immediately\"", + "0": "Invite immediately", + "3": "If 3 more hours remaining", + "6": "If 6 more hours remaining", + "9": "If 9 more hours remaining" + }, + "Choice": { + "name": "Choose Inviting Student", + "help": "Top first student of list: Invite the student at the top first of the list\nBy name: Invite the student with the name you set below", + "list_top": "Top first student of list", + "by_name": "By name" + }, + "Name": { + "name": "Inviting Student Name", + "help": "It is recommended to use Input Helper to avoid any mistakes and paste it here using Ctrl + V.\nFill in the name of the student to be invited. Use in-game text language. Use > to connect multiple students. Example:\nJP: ホシノ(水着) > 御坂美琴 > ユズ\nOVERSEA: Hoshino(Swimsuit) > Yuuka > Kayoko(New Year)" + }, + "Substitute": { + "name": "Replace existing student", + "help": "Whether to replace the existing student with their alt.\nIf not, try to match the next student" + } + }, + "Schedule": { + "_info": { + "name": "Lesson Settings", + "help": "" + }, + "OnError": { + "name": "Error Handling", + "help": "Perform the selected action when an error occurs (location not unlocked or any invalid setting)", + "stop": "Stop script", + "skip": "Skip current task" + } + }, + "Choice1": { + "_info": { + "name": "Choice 1", + "help": "" + }, + "Location": { + "name": "Location", + "help": "", + "None": "None", + "0": "Schale Office", + "1": "Schale Residence", + "2": "Gehenna", + "3": "Abydos", + "4": "Millennium", + "5": "Trinity", + "6": "Red Winter", + "7": "Hyakkiyako", + "8": "D.U. Shiratori", + "9": "Shanhaijing" + }, + "Classrooms": { + "name": "Classrooms", + "help": "It is recommended to use Input Helper to avoid any mistakes and paste it here using Ctrl + V.\nType a number from 1 to 9 that represents the classroom position in the locations popup.\nUse > to connect multiple classrooms and AAS will select them following the order they appear.Example:\n8 > 7 > 6 > 5 > 4 > 3 > 2 > 1" + } + }, + "Choice2": { + "_info": { + "name": "Choice 2", + "help": "" + }, + "Location": { + "name": "Location", + "help": "", + "None": "None", + "0": "Schale Office", + "1": "Schale Residence", + "2": "Gehenna", + "3": "Abydos", + "4": "Millennium", + "5": "Trinity", + "6": "Red Winter", + "7": "Hyakkiyako", + "8": "D.U. Shiratori", + "9": "Shanhaijing" + }, + "Classrooms": { + "name": "Classrooms", + "help": "" + } + }, + "Choice3": { + "_info": { + "name": "Choice 3", + "help": "" + }, + "Location": { + "name": "Location", + "help": "", + "None": "None", + "0": "Schale Office", + "1": "Schale Residence", + "2": "Gehenna", + "3": "Abydos", + "4": "Millennium", + "5": "Trinity", + "6": "Red Winter", + "7": "Hyakkiyako", + "8": "D.U. Shiratori", + "9": "Shanhaijing" + }, + "Classrooms": { + "name": "Classrooms", + "help": "" + } + }, + "Choice4": { + "_info": { + "name": "Choice 4", + "help": "" + }, + "Location": { + "name": "Location", + "help": "", + "None": "None", + "0": "Schale Office", + "1": "Schale Residence", + "2": "Gehenna", + "3": "Abydos", + "4": "Millennium", + "5": "Trinity", + "6": "Red Winter", + "7": "Hyakkiyako", + "8": "D.U. Shiratori", + "9": "Shanhaijing" + }, + "Classrooms": { + "name": "Classrooms", + "help": "" + } + }, + "Choice5": { + "_info": { + "name": "Choice 5", + "help": "" + }, + "Location": { + "name": "Location", + "help": "", + "None": "None", + "0": "Schale Office", + "1": "Schale Residence", + "2": "Gehenna", + "3": "Abydos", + "4": "Millennium", + "5": "Trinity", + "6": "Red Winter", + "7": "Hyakkiyako", + "8": "D.U. Shiratori", + "9": "Shanhaijing" + }, + "Classrooms": { + "name": "Classrooms", + "help": "" + } + }, + "Choice6": { + "_info": { + "name": "Choice 6", + "help": "" + }, + "Location": { + "name": "Location", + "help": "", + "None": "None", + "0": "Schale Office", + "1": "Schale Residence", + "2": "Gehenna", + "3": "Abydos", + "4": "Millennium", + "5": "Trinity", + "6": "Red Winter", + "7": "Hyakkiyako", + "8": "D.U. Shiratori", + "9": "Shanhaijing" + }, + "Classrooms": { + "name": "Classrooms", + "help": "" + } + }, + "Choice7": { + "_info": { + "name": "Choice 7", + "help": "" + }, + "Location": { + "name": "Location", + "help": "", + "None": "None", + "0": "Schale Office", + "1": "Schale Residence", + "2": "Gehenna", + "3": "Abydos", + "4": "Millennium", + "5": "Trinity", + "6": "Red Winter", + "7": "Hyakkiyako", + "8": "D.U. Shiratori", + "9": "Shanhaijing" + }, + "Classrooms": { + "name": "Classrooms", + "help": "" } }, "Bounty": { @@ -238,6 +498,7 @@ "Stage": { "name": "Select Stage", "help": "", + "0": "Auto select", "1": "01 - Overpass A", "2": "02 - Overpass B", "3": "03 - Overpass C", @@ -246,7 +507,8 @@ "6": "06 - Overpass F", "7": "07 - Overpass G", "8": "08 - Overpass H", - "9": "09 - Overpass I" + "9": "09 - Overpass I", + "10": "10 - Overpass J" }, "Count": { "name": "Sweep X times", @@ -261,6 +523,7 @@ "Stage": { "name": "Select Stage", "help": "", + "0": "Auto select", "1": "01 - Abandoned Train A", "2": "02 - Abandoned Train B", "3": "03 - Abandoned Train C", @@ -269,7 +532,8 @@ "6": "06 - Abandoned Train F", "7": "07 - Abandoned Train G", "8": "08 - Abandoned Train H", - "9": "09 - Abandoned Train I" + "9": "09 - Abandoned Train I", + "10": "10 - Abandoned Train J" }, "Count": { "name": "Sweep X times", @@ -284,6 +548,7 @@ "Stage": { "name": "Select Stage", "help": "", + "0": "Auto select", "1": "01 - Besieged Classroom A", "2": "02 - Besieged Classroom B", "3": "03 - Besieged Classroom C", @@ -292,7 +557,8 @@ "6": "06 - Besieged Classroom F", "7": "07 - Besieged Classroom G", "8": "08 - Besieged Classroom H", - "9": "09 - Besieged Classroom I" + "9": "09 - Besieged Classroom I", + "10": "10 - Besieged Classroom J" }, "Count": { "name": "Sweep X times", @@ -319,6 +585,7 @@ "Stage": { "name": "Select Stage", "help": "", + "0": "Auto select", "1": "01 - Trinity A", "2": "02 - Trinity B", "3": "03 - Trinity C", @@ -337,6 +604,7 @@ "Stage": { "name": "Select Stage", "help": "", + "0": "Auto select", "1": "01 - Gehenna A", "2": "02 - Gehenna B", "3": "03 - Gehenna C", @@ -355,6 +623,7 @@ "Stage": { "name": "Select Stage", "help": "", + "0": "Auto select", "1": "01 - Millennium A", "2": "02 - Millennium B", "3": "03 - Millennium C", @@ -379,6 +648,182 @@ "3": "Third" } }, + "NormalShop": { + "_info": { + "name": "Normal Shop Settings", + "help": "" + }, + "Enable": { + "name": "Enable", + "help": "" + }, + "Purchases": { + "name": "Number of Purchases", + "help": "Default can be purchased once + number of refreshes = number of purchases, for example, 2 purchases = 1 default purchase + 1 refresh purchase", + "1": "1", + "2": "2", + "3": "3", + "4": "4" + }, + "1": { + "name": "1", + "help": "x5 Novice Activity Report - 12,500 Credits" + }, + "2": { + "name": "2", + "help": "x5 Normal Activity Report - 125,000 Credits" + }, + "3": { + "name": "3", + "help": "x3 Advanced Activity Report - 300,000 Credits" + }, + "4": { + "name": "4", + "help": "x1 Superior Activity Report - 500,000 Credits" + }, + "5": { + "name": "5", + "help": "x5 Lesser Enhancement Stone - 10,000 Credits" + }, + "6": { + "name": "6", + "help": "x5 Normal Enhancement Stone - 40,000 Credits" + }, + "7": { + "name": "7", + "help": "x3 Advanced Enhancement Stone - 96,000 Credits" + }, + "8": { + "name": "8", + "help": "x1 Superior Enhancement Stone - 128,000 Credits" + }, + "9": { + "name": "9", + "help": "x5 Lesser Enhancement Stone - 10,000 Credits" + }, + "10": { + "name": "10", + "help": "x5 Normal Enhancement Stone - 40,000 Credits" + }, + "11": { + "name": "11", + "help": "x3 Advanced Enhancement Stone - 96,000 Credits" + }, + "12": { + "name": "12", + "help": "x1 Superior Enhancement Stone - 128,000 Credits" + }, + "13": { + "name": "13", + "help": "x10 Lesser Enhancement Stone - 20,000 Credits" + }, + "14": { + "name": "14", + "help": "x10 Normal Enhancement Stone - 80,000 Credits" + }, + "15": { + "name": "15", + "help": "x6 Advanced Enhancement Stone - 192,000 Credits" + }, + "16": { + "name": "16", + "help": "x2 Superior Enhancement Stone - 256,000 Credits" + }, + "17": { + "name": "17", + "help": "x1 Random Selection - 8,000 Credits" + }, + "18": { + "name": "18", + "help": "x1 Random Selection - 8,000 Credits" + }, + "19": { + "name": "19", + "help": "x1 Random Selection - 25,000 Credits" + }, + "20": { + "name": "20", + "help": "x1 Random Selection - 25,000 Credits" + } + }, + "TacticalChallengeShop": { + "_info": { + "name": "Tactical Challenge Shop Settings", + "help": "" + }, + "Enable": { + "name": "Enable", + "help": "" + }, + "Purchases": { + "name": "Number of Purchases", + "help": "", + "1": "1", + "2": "2", + "3": "3", + "4": "4" + }, + "1": { + "name": "1", + "help": "x5 Shizuko's Eleph - 50 Coins" + }, + "2": { + "name": "2", + "help": "x5 Mashiro's Eleph - 50 Coins" + }, + "3": { + "name": "3", + "help": "x5 Saya's Eleph - 50 Coins" + }, + "4": { + "name": "4", + "help": "x5 Fuuka's Eleph - 50 Coins" + }, + "5": { + "name": "5", + "help": "x5 Utaha's Eleph - 50 Coins" + }, + "6": { + "name": "6", + "help": "x1 Lesser Drink(30 AP) - 15 Coins" + }, + "7": { + "name": "7", + "help": "x1 Normal Drink(60 AP) - 30 Coins" + }, + "8": { + "name": "8", + "help": "x10 Novice Activity Report - 5 Coins" + }, + "9": { + "name": "9", + "help": "x5 Normal Activity Report - 25 Coins" + }, + "10": { + "name": "10", + "help": "x3 Advanced Activity Report - 60 Coins" + }, + "11": { + "name": "11", + "help": "x1 Superior Activity Report - 100 Coins" + }, + "12": { + "name": "12", + "help": "x5000 Credits - 4 Coins" + }, + "13": { + "name": "13", + "help": "x25k Credits - 20 Coins" + }, + "14": { + "name": "14", + "help": "x75k Credits - 60 Coins" + }, + "15": { + "name": "15", + "help": "x125k Credits - 100 Coins" + } + }, "ItemStorage": { "_info": { "name": "ItemStorage._info.name", @@ -409,6 +854,16 @@ "help": "" } }, + "Momotalk": { + "_info": { + "name": "Momotalk", + "help": "Tools need to stop the scheduler and then run independently" + }, + "Enable": { + "name": "", + "help": "" + } + }, "Gui": { "Aside": { "Install": "Install", diff --git a/module/config/i18n/zh-CN.json b/module/config/i18n/zh-CN.json index 6ac651a..2fa4474 100644 --- a/module/config/i18n/zh-CN.json +++ b/module/config/i18n/zh-CN.json @@ -7,6 +7,18 @@ "Daily": { "name": "每日", "help": "" + }, + "Farm": { + "name": "扫荡", + "help": "" + }, + "Reward": { + "name": "收菜", + "help": "" + }, + "Tool": { + "name": "工具", + "help": "" } }, "Task": { @@ -18,17 +30,21 @@ "name": "异常处理", "help": "" }, + "DataUpdate": { + "name": "仪表盘更新", + "help": "" + }, "Cafe": { "name": "咖啡厅", "help": "" }, - "Mail": { - "name": "邮箱", - "help": "" + "Schedule": { + "name": "课程表", + "help": "按照选项顺序执行,忽略设置为“无”或教室未填写的选项" }, - "Circle": { - "name": "公会", - "help": "社团 / 小组" + "Shop": { + "name": "商店", + "help": "" }, "Bounty": { "name": "悬赏通缉", @@ -42,8 +58,24 @@ "name": "战术对抗赛", "help": "战术大赛 / 竞技场" }, - "DataUpdate": { - "name": "仪表盘更新", + "Mission": { + "name": "任务/特别委托/活动", + "help": "打开 MCE 管理器" + }, + "Circle": { + "name": "公会", + "help": "社团 / 小组" + }, + "Task": { + "name": "任务", + "help": "" + }, + "Mail": { + "name": "邮箱", + "help": "" + }, + "Momotalk": { + "name": "Momotalk", "help": "" } }, @@ -85,14 +117,19 @@ "help": "无法区分国际服的不同地区,请手动选择服务器", "auto": "自动检测", "JP-Official": "[日服]-官服", + "OVERSEA-TWHKMO": "[国际服]-港澳台", + "OVERSEA-Korea": "[国际服]-韩国", + "OVERSEA-Asia": "[国际服]-亚洲", + "OVERSEA-America": "[国际服]-北美", "OVERSEA-Global": "[国际服]-全球" }, "GameLanguage": { "name": "游戏内文本语言", - "help": "", + "help": "无法自动检测语言,请手动选择语言", "auto": "自动检测", "jp": "日语", - "en": "英语" + "en": "英语", + "zht": "繁体中文" }, "ScreenshotMethod": { "name": "模拟器截图方案", @@ -190,10 +227,14 @@ }, "WhenTaskQueueEmpty": { "name": "当任务队列清空后", - "help": "无任务时关闭游戏,能在收菜期间降低 CPU 占用", + "help": "无任务时关闭游戏,能在收菜期间降低 CPU 占用\n关闭 AAS / 关闭模拟器 / 关机选项仅在 AAS 启动后设置方可生效,执行后自动重置为”前往主界面“\n关闭 AAS / 关闭模拟器仅在 Windows 系统下生效\n\n关闭模拟器使用模拟器实例名称和安装路径设置。为确保功能正常,请使用根据端口号的模拟器自动填写,或不使用 AAS 启动模拟器,并将日志中的模拟器名称与路径填写在设置中", "stay_there": "停在原处", "goto_main": "前往主界面", - "close_game": "关闭游戏" + "close_game": "关闭游戏", + "exit_aas": "关闭 AAS", + "exit_emulator": "关闭模拟器", + "exit_aas_emulator": "关闭 AAS 和模拟器", + "shutdown": "关机" } }, "Cafe": { @@ -215,7 +256,226 @@ }, "SecondCafe": { "name": "第二咖啡厅", - "help": "仅支持日服\n自动切换第二咖啡厅进行互动点击" + "help": "自动切换第二咖啡厅进行互动点击" + } + }, + "Invitation": { + "_info": { + "name": "邀请设置", + "help": "" + }, + "Enable": { + "name": "启用", + "help": "是否启用咖啡厅邀请功能" + }, + "WaitingHour": { + "name": "邀请条件", + "help": "根据距离下一次咖啡厅刷新的时长,选择是否邀请学生\n此项设立是为了后台挂机最大化收益,使得邀请券能被最大化利用。推荐选项:\n常驻挂机:\t选择“离下次刷新 9 小时以上”\n非常驻挂机:\t选择“不等待刷新,立即邀请”,或根据自己挂机时长选择", + "0": "不等待刷新,立即邀请", + "3": "离下次刷新 3 小时以上", + "6": "离下次刷新 6 小时以上", + "9": "离下次刷新 9 小时以上" + }, + "Choice": { + "name": "选择邀请学生", + "help": "按列表第一个:优先邀请列表中第一个学生,若失败则则尝试下一个\n按名字:优先邀请下方填写学生名的第一个,若失败则尝试下一个", + "list_top": "按列表第一个", + "by_name": "按名字" + }, + "Name": { + "name": "邀请学生名", + "help": "填写要邀请的学生的名字,使用游戏内语言填写,多个学生使用 > 连接。例:\n日服:\tホシノ(水着) > 御坂美琴 > ユズ\n国际服:\tHoshino(Swimsuit) > Yuuka > Kayoko(New Year)" + }, + "Substitute": { + "name": "是否替换已存在学生", + "help": "若咖啡厅已存在所邀请学生的不同服装,选择是否替换该学生\n若不替换,则尝试匹配下一位学生" + } + }, + "Schedule": { + "_info": { + "name": "课程表设置", + "help": "" + }, + "OnError": { + "name": "错误处理", + "help": "当出现区域未解锁或设置有误等错误时,执行该操作", + "stop": "停止脚本运行", + "skip": "跳过当前任务" + } + }, + "Choice1": { + "_info": { + "name": "选项 1", + "help": "" + }, + "Location": { + "name": "区域", + "help": "", + "None": "无", + "0": "夏莱 办公区", + "1": "夏莱 居住区", + "2": "格黑娜学园 中央区", + "3": "阿拜多斯高中", + "4": "千年 学习区", + "5": "三一 广场", + "6": "赤冬联邦学园", + "7": "百鬼夜行中心区域", + "8": "D.U.白鸟区", + "9": "山海经中央特区" + }, + "Classrooms": { + "name": "教室", + "help": "填写教室序号(1 ~ 9),多个教室使用 > 连接。例:\n5 > 3 > 2 > 4 > 1" + } + }, + "Choice2": { + "_info": { + "name": "选项 2", + "help": "" + }, + "Location": { + "name": "区域", + "help": "", + "None": "无", + "0": "夏莱 办公区", + "1": "夏莱 居住区", + "2": "格黑娜学园 中央区", + "3": "阿拜多斯高中", + "4": "千年 学习区", + "5": "三一 广场", + "6": "赤冬联邦学园", + "7": "百鬼夜行中心区域", + "8": "D.U.白鸟区", + "9": "山海经中央特区" + }, + "Classrooms": { + "name": "教室", + "help": "填写教室序号(1 ~ 9),多个教室使用 > 连接。例:\n5 > 3 > 2 > 4 > 1" + } + }, + "Choice3": { + "_info": { + "name": "选项 3", + "help": "" + }, + "Location": { + "name": "区域", + "help": "", + "None": "无", + "0": "夏莱 办公区", + "1": "夏莱 居住区", + "2": "格黑娜学园 中央区", + "3": "阿拜多斯高中", + "4": "千年 学习区", + "5": "三一 广场", + "6": "赤冬联邦学园", + "7": "百鬼夜行中心区域", + "8": "D.U.白鸟区", + "9": "山海经中央特区" + }, + "Classrooms": { + "name": "教室", + "help": "填写教室序号(1 ~ 9),多个教室使用 > 连接。例:\n5 > 3 > 2 > 4 > 1" + } + }, + "Choice4": { + "_info": { + "name": "选项 4", + "help": "" + }, + "Location": { + "name": "区域", + "help": "", + "None": "无", + "0": "夏莱 办公区", + "1": "夏莱 居住区", + "2": "格黑娜学园 中央区", + "3": "阿拜多斯高中", + "4": "千年 学习区", + "5": "三一 广场", + "6": "赤冬联邦学园", + "7": "百鬼夜行中心区域", + "8": "D.U.白鸟区", + "9": "山海经中央特区" + }, + "Classrooms": { + "name": "教室", + "help": "填写教室序号(1 ~ 9),多个教室使用 > 连接。例:\n5 > 3 > 2 > 4 > 1" + } + }, + "Choice5": { + "_info": { + "name": "选项 5", + "help": "" + }, + "Location": { + "name": "区域", + "help": "", + "None": "无", + "0": "夏莱 办公区", + "1": "夏莱 居住区", + "2": "格黑娜学园 中央区", + "3": "阿拜多斯高中", + "4": "千年 学习区", + "5": "三一 广场", + "6": "赤冬联邦学园", + "7": "百鬼夜行中心区域", + "8": "D.U.白鸟区", + "9": "山海经中央特区" + }, + "Classrooms": { + "name": "教室", + "help": "填写教室序号(1 ~ 9),多个教室使用 > 连接。例:\n5 > 3 > 2 > 4 > 1" + } + }, + "Choice6": { + "_info": { + "name": "选项 6", + "help": "" + }, + "Location": { + "name": "区域", + "help": "", + "None": "无", + "0": "夏莱 办公区", + "1": "夏莱 居住区", + "2": "格黑娜学园 中央区", + "3": "阿拜多斯高中", + "4": "千年 学习区", + "5": "三一 广场", + "6": "赤冬联邦学园", + "7": "百鬼夜行中心区域", + "8": "D.U.白鸟区", + "9": "山海经中央特区" + }, + "Classrooms": { + "name": "教室", + "help": "填写教室序号(1 ~ 9),多个教室使用 > 连接。例:\n5 > 3 > 2 > 4 > 1" + } + }, + "Choice7": { + "_info": { + "name": "选项 7", + "help": "" + }, + "Location": { + "name": "区域", + "help": "", + "None": "无", + "0": "夏莱 办公区", + "1": "夏莱 居住区", + "2": "格黑娜学园 中央区", + "3": "阿拜多斯高中", + "4": "千年 学习区", + "5": "三一 广场", + "6": "赤冬联邦学园", + "7": "百鬼夜行中心区域", + "8": "D.U.白鸟区", + "9": "山海经中央特区" + }, + "Classrooms": { + "name": "教室", + "help": "填写教室序号(1 ~ 9),多个教室使用 > 连接。例:\n5 > 3 > 2 > 4 > 1" } }, "Bounty": { @@ -238,6 +498,7 @@ "Stage": { "name": "选择关卡", "help": "", + "0": "自动选择", "1": "01 - 高架公路 A", "2": "02 - 高架公路 B", "3": "03 - 高架公路 C", @@ -246,7 +507,8 @@ "6": "06 - 高架公路 F", "7": "07 - 高架公路 G", "8": "08 - 高架公路 H", - "9": "09 - 高架公路 I" + "9": "09 - 高架公路 I", + "10": "10 - 高架公路 J" }, "Count": { "name": "扫荡次数", @@ -261,6 +523,7 @@ "Stage": { "name": "选择关卡", "help": "", + "0": "自动选择", "1": "01 - 被遗弃的列车 A", "2": "02 - 被遗弃的列车 B", "3": "03 - 被遗弃的列车 C", @@ -269,7 +532,8 @@ "6": "06 - 被遗弃的列车 F", "7": "07 - 被遗弃的列车 G", "8": "08 - 被遗弃的列车 H", - "9": "09 - 被遗弃的列车 I" + "9": "09 - 被遗弃的列车 I", + "10": "10 - 被遗弃的列车 J" }, "Count": { "name": "扫荡次数", @@ -284,6 +548,7 @@ "Stage": { "name": "选择关卡", "help": "", + "0": "自动选择", "1": "01 - 被袭击的教室 A", "2": "02 - 被袭击的教室 B", "3": "03 - 被袭击的教室 C", @@ -292,7 +557,8 @@ "6": "06 - 被袭击的教室 F", "7": "07 - 被袭击的教室 G", "8": "08 - 被袭击的教室 H", - "9": "09 - 被袭击的教室 I" + "9": "09 - 被袭击的教室 I", + "10": "10 - 被袭击的教室 J" }, "Count": { "name": "扫荡次数", @@ -319,6 +585,7 @@ "Stage": { "name": "选择关卡", "help": "", + "0": "自动选择", "1": "01 - 三一 A", "2": "02 - 三一 B", "3": "03 - 三一 C", @@ -337,6 +604,7 @@ "Stage": { "name": "选择关卡", "help": "", + "0": "自动选择", "1": "01 - 格黑娜 A", "2": "02 - 格黑娜 B", "3": "03 - 格黑娜 C", @@ -355,6 +623,7 @@ "Stage": { "name": "选择关卡", "help": "", + "0": "自动选择", "1": "01 - 千年 A", "2": "02 - 千年 B", "3": "03 - 千年 C", @@ -379,6 +648,182 @@ "3": "第三位" } }, + "NormalShop": { + "_info": { + "name": "普通商店", + "help": "" + }, + "Enable": { + "name": "启用", + "help": "" + }, + "Purchases": { + "name": "购买次数", + "help": "指定物品购买 X 次。例:\n购买 2 次 = 商店默认 1 次 + 刷新购买 1 次", + "1": "1", + "2": "2", + "3": "3", + "4": "4" + }, + "1": { + "name": "1", + "help": "x5 初级活动报告书 - 12,500 信用点" + }, + "2": { + "name": "2", + "help": "x5 中级活动报告书 - 125,000 信用点" + }, + "3": { + "name": "3", + "help": "x3 高级活动报告书 - 300,000 信用点" + }, + "4": { + "name": "4", + "help": "x1 特级活动报告书 - 500,000 信用点" + }, + "5": { + "name": "5", + "help": "x5 低级强化珠 - 10,000 信用点" + }, + "6": { + "name": "6", + "help": "x5 中级强化珠 - 40,000 信用点" + }, + "7": { + "name": "7", + "help": "x3 高级强化珠 - 96,000 信用点" + }, + "8": { + "name": "8", + "help": "x1 特级强化珠 - 128,000 信用点" + }, + "9": { + "name": "9", + "help": "x5 低级强化珠 - 10,000 信用点" + }, + "10": { + "name": "10", + "help": "x5 中级强化珠 - 40,000 信用点" + }, + "11": { + "name": "11", + "help": "x3 高级强化珠 - 96,000 信用点" + }, + "12": { + "name": "12", + "help": "x1 特级强化珠 - 128,000 信用点" + }, + "13": { + "name": "13", + "help": "x5 低级强化珠 - 10,000 信用点" + }, + "14": { + "name": "14", + "help": "x5 中级强化珠 - 40,000 信用点" + }, + "15": { + "name": "15", + "help": "x3 高级强化珠 - 96,000 信用点" + }, + "16": { + "name": "16", + "help": "x1 特级强化珠 - 128,000 信用点" + }, + "17": { + "name": "17", + "help": "x1 随机材料 - 8,000 信用点" + }, + "18": { + "name": "18", + "help": "x1 随机材料 - 8,000 信用点" + }, + "19": { + "name": "19", + "help": "x1 随机材料 - 8,000 信用点" + }, + "20": { + "name": "20", + "help": "x1 随机材料 - 8,000 信用点" + } + }, + "TacticalChallengeShop": { + "_info": { + "name": "战术对抗赛商店", + "help": "" + }, + "Enable": { + "name": "启用", + "help": "" + }, + "Purchases": { + "name": "购买次数", + "help": "指定物品购买 X 次。例:\n购买 2 次 = 商店默认 1 次 + 刷新购买 1 次", + "1": "1", + "2": "2", + "3": "3", + "4": "4" + }, + "1": { + "name": "1", + "help": "x5 静子的神名文字 - 50 战术硬币" + }, + "2": { + "name": "2", + "help": "x5 真白的神名文字 - 50 战术硬币" + }, + "3": { + "name": "3", + "help": "x5 纱绫的神名文字 - 50 战术硬币" + }, + "4": { + "name": "4", + "help": "x5 枫香的神名文字 - 50 战术硬币" + }, + "5": { + "name": "5", + "help": "x5 歌原的神名文字 - 50 战术硬币" + }, + "6": { + "name": "6", + "help": "x1 低级能量饮料 (30 体力) - 15 战术硬币" + }, + "7": { + "name": "7", + "help": "x1 中级能量饮料 (60 体力) - 30 战术硬币" + }, + "8": { + "name": "8", + "help": "x5 初级活动报告书 - 5 战术硬币" + }, + "9": { + "name": "9", + "help": "x5 中级活动报告书 - 25 战术硬币" + }, + "10": { + "name": "10", + "help": "x3 高级活动报告书 - 60 战术硬币" + }, + "11": { + "name": "11", + "help": "x1 特级活动报告书 - 100 战术硬币" + }, + "12": { + "name": "12", + "help": "x5000 信用点 - 4 战术硬币" + }, + "13": { + "name": "13", + "help": "x25k 信用点 - 20 战术硬币" + }, + "14": { + "name": "14", + "help": "x75k 信用点 - 60 战术硬币" + }, + "15": { + "name": "15", + "help": "x125k 信用点 - 100 战术硬币" + } + }, "ItemStorage": { "_info": { "name": "ItemStorage._info.name", @@ -409,6 +854,16 @@ "help": "" } }, + "Momotalk": { + "_info": { + "name": "Momotalk", + "help": "工具需要停止调度器再单独运行" + }, + "Enable": { + "name": "", + "help": "" + } + }, "Gui": { "Aside": { "Install": "安装", diff --git a/module/config/server.py b/module/config/server.py index cd50b12..54fc737 100644 --- a/module/config/server.py +++ b/module/config/server.py @@ -5,9 +5,13 @@ Use 'import module.config.server as server' to import, don't use 'from xxx impor lang = 'jp' # Setting default to cn, will avoid errors when using dev_tools server = 'JP-Official' -VALID_LANG = ['jp', 'en'] +VALID_LANG = ['jp', 'en', 'zht'] VALID_SERVER = { 'JP-Official': 'com.YostarJP.BlueArchive', + 'OVERSEA-TWHKMO': 'com.nexon.bluearchive', + 'OVERSEA-Korea': 'com.nexon.bluearchive', + 'OVERSEA-Asia': 'com.nexon.bluearchive', + 'OVERSEA-America': 'com.nexon.bluearchive', 'OVERSEA-Global': 'com.nexon.bluearchive', } VALID_PACKAGE = set(list(VALID_SERVER.values())) diff --git a/module/config/stored/stored_generated.py b/module/config/stored/stored_generated.py index 64feb48..4e991de 100644 --- a/module/config/stored/stored_generated.py +++ b/module/config/stored/stored_generated.py @@ -1,6 +1,10 @@ from module.config.stored.classes import ( StoredAP, + StoredBase, StoredBountyTicket, + StoredCounter, + StoredExpiredAt0400, + StoredExpiredAtMonday0400, StoredInt, StoredScrimmageTicket, StoredTacticalChallengeTicket, diff --git a/module/config/utils.py b/module/config/utils.py index fe2d731..ef98afc 100644 --- a/module/config/utils.py +++ b/module/config/utils.py @@ -13,7 +13,11 @@ from module.config.atomicwrites import atomic_write LANGUAGES = ['zh-CN', 'en-US'] SERVER_TO_TIMEZONE = { 'JP-Official': timedelta(hours=9), - 'OVERSEA-Global': timedelta(hours=0), + 'OVERSEA-TWHKMO': timedelta(hours=9), + 'OVERSEA-Korea': timedelta(hours=9), + 'OVERSEA-Asia': timedelta(hours=9), + 'OVERSEA-America': timedelta(hours=9), + 'OVERSEA-Global': timedelta(hours=9), } DEFAULT_TIME = datetime(2020, 1, 1, 0, 0) diff --git a/module/device/device.py b/module/device/device.py index e99c572..de4909b 100644 --- a/module/device/device.py +++ b/module/device/device.py @@ -252,3 +252,7 @@ class Device(Screenshot, Control, AppControl): super().app_stop() self.stuck_record_clear() self.click_record_clear() + + def back(self): + logger.info("Sending Back...") + self.adb_shell("input keyevent 4") diff --git a/module/device/platform/platform_windows.py b/module/device/platform/platform_windows.py index e18fb98..671d1dd 100644 --- a/module/device/platform/platform_windows.py +++ b/module/device/platform/platform_windows.py @@ -12,6 +12,7 @@ from module.device.platform.emulator_windows import Emulator, EmulatorInstance, from module.device.platform.platform_base import PlatformBase from module.logger import logger +import os class EmulatorUnknown(Exception): pass @@ -99,10 +100,15 @@ class PlatformWindows(PlatformBase, EmulatorManager): self.execute(f'"{exe}" -clone:{instance.name}') elif instance == Emulator.BlueStacks5: # HD-Player.exe -instance Pie64 - self.execute(f'"{exe}" -instance {instance.name}') + self.execute(f'"{exe}" --instance {instance.name}') elif instance == Emulator.BlueStacks4: # BlueStacks\Client\Bluestacks.exe -vmname Android_1 self.execute(f'"{exe}" -vmname {instance.name}') + elif instance == Emulator.LDPlayer9: + directory, filename = os.path.split(exe) + new_filename = 'ldconsole.exe' + exe = os.path.join(directory, new_filename) + self.execute(f'"{exe}" launch --index {instance.name.replace("leidian", "")}') else: raise EmulatorUnknown(f'Cannot start an unknown emulator instance: {instance}') @@ -157,6 +163,13 @@ class PlatformWindows(PlatformBase, EmulatorManager): elif instance == Emulator.NoxPlayerFamily: # Nox.exe -clone:Nox_1 -quit self.execute(f'"{exe}" -clone:{instance.name} -quit') + elif instance == Emulator.BlueStacks5: + self.execute(f'taskkill /fi "WINDOWTITLE eq {instance.name}" /IM "HD-Player.exe" /F') + elif instance == Emulator.LDPlayer9: + directory, filename = os.path.split(exe) + new_filename = 'ldconsole.exe' + exe = os.path.join(directory, new_filename) + self.execute(f'"{exe}" quit --index {instance.name.replace("leidian", "")}') else: raise EmulatorUnknown(f'Cannot stop an unknown emulator instance: {instance}') diff --git a/module/ocr/ocr.py b/module/ocr/ocr.py index 82fb32d..819363d 100644 --- a/module/ocr/ocr.py +++ b/module/ocr/ocr.py @@ -352,6 +352,11 @@ class DigitCounter(Ocr): def is_format_matched(cls, result) -> bool: return '/' in result + def after_process(self, result): + result = super().after_process(result) + result = result.replace('%', '/') + return result + def format_result(self, result) -> tuple[int, int, int]: """ Do OCR on a counter, such as `14/15`, and returns 14, 1, 15 @@ -359,7 +364,7 @@ class DigitCounter(Ocr): Returns: int: """ - result = super().after_process(result) + result = self.after_process(result) logger.attr(name=self.name, text=str(result)) res = re.search(r'(\d+)\s*/\s*(\d+)', result) diff --git a/module/webui/process_manager.py b/module/webui/process_manager.py index 60b2d1a..338505b 100644 --- a/module/webui/process_manager.py +++ b/module/webui/process_manager.py @@ -6,11 +6,12 @@ from multiprocessing import Process from typing import Dict, List, Union from filelock import FileLock +from rich.console import Console, ConsoleRenderable + from module.config.utils import filepath_config from module.logger import logger, set_file_logger, set_func_logger from module.webui.fake import get_config_mod, mod_instance from module.webui.setting import State -from rich.console import Console, ConsoleRenderable class ProcessManager: @@ -147,6 +148,10 @@ class ProcessManager: if e is not None: AzurLaneAutoScript.stop_event = e ArisuAutoSweeper(config_name=config_name).loop() + elif func == "Momotalk": + from tasks.momotalk.momotalk import MomoTalk + + MomoTalk(config=config_name, task="Momotalk").run() else: logger.critical(f"No function matched: {func}") logger.info(f"[{config_name}] exited. Reason: Finish\n") diff --git a/module/webui/remote_access.py b/module/webui/remote_access.py index 163cbd1..754af96 100644 --- a/module/webui/remote_access.py +++ b/module/webui/remote_access.py @@ -16,8 +16,8 @@ import time from subprocess import PIPE, Popen from typing import TYPE_CHECKING -from module.logger import logger from module.config.utils import random_id +from module.logger import logger from module.webui.setting import State if TYPE_CHECKING: @@ -41,7 +41,7 @@ def am_i_the_only_thread() -> bool: def remote_access_service( local_host="127.0.0.1", - local_port=22367, + local_port=23467, server="app.pywebio.online", server_port=1022, remote_port="/", diff --git a/module/webui/updater.py b/module/webui/updater.py index a580703..6757c64 100644 --- a/module/webui/updater.py +++ b/module/webui/updater.py @@ -5,6 +5,7 @@ import time from typing import Generator, List, Tuple import requests + from deploy.Windows.config import ExecutionError from deploy.Windows.git import GitManager from deploy.Windows.pip import PipManager @@ -20,6 +21,7 @@ from module.webui.utils import TaskHandler, get_next_time class Updater(DeployConfig, GitManager, PipManager): def __init__(self, file=DEPLOY_CONFIG): super().__init__(file=file) + self.set_repo() self.state = 0 self.event: threading.Event = None @@ -194,6 +196,7 @@ class Updater(DeployConfig, GitManager, PipManager): def update(self): logger.hr("Run update") + self.set_repo() try: self.git_install() self.pip_install() diff --git a/requirements-in.txt b/requirements-in.txt index df82239..597ae04 100644 --- a/requirements-in.txt +++ b/requirements-in.txt @@ -32,6 +32,9 @@ starlette==0.14.2 uvicorn[standard]==0.17.6 aiofiles +# GUI +customtkinter + # For dev # pip-tools pynput diff --git a/requirements.txt b/requirements.txt index 30895d3..2c2151e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,6 +20,8 @@ cigam==0.0.3 # via apkutils2 click==8.1.3 # via uvicorn colorama==0.4.6 # via click, logzero, tqdm, uvicorn coloredlogs==15.0.1 # via onnxruntime +customtkinter==5.2.1 # via -r requirements-in.txt +darkdetect==0.8.0 # via customtkinter decorator==5.1.1 # via retry deprecated==1.2.13 # via uiautomator2 deprecation==2.1.0 # via adbutils diff --git a/tasks/base/assets/assets_base_page.py b/tasks/base/assets/assets_base_page.py index a33d9ac..3cebbfb 100644 --- a/tasks/base/assets/assets_base_page.py +++ b/tasks/base/assets/assets_base_page.py @@ -19,18 +19,18 @@ ACCOUNT_INFO_CHECK = ButtonWrapper( color=(192, 200, 209), button=(103, 10, 219, 39), ), + zht=Button( + file='./assets/zht/base/page/ACCOUNT_INFO_CHECK.png', + area=(92, 0, 222, 41), + search=(72, 0, 242, 61), + color=(191, 201, 210), + button=(92, 0, 222, 41), + ), ) BACK = ButtonWrapper( name='BACK', - jp=Button( - file='./assets/jp/base/page/BACK.png', - area=(34, 19, 81, 56), - search=(14, 0, 101, 76), - color=(93, 118, 164), - button=(34, 19, 81, 56), - ), - en=Button( - file='./assets/en/base/page/BACK.png', + share=Button( + file='./assets/share/base/page/BACK.png', area=(34, 19, 81, 56), search=(14, 0, 101, 76), color=(93, 118, 164), @@ -53,22 +53,36 @@ BOUNTY_CHECK = ButtonWrapper( color=(200, 208, 216), button=(101, 5, 206, 42), ), + zht=Button( + file='./assets/zht/base/page/BOUNTY_CHECK.png', + area=(92, 6, 224, 41), + search=(72, 0, 244, 61), + color=(181, 191, 201), + button=(92, 6, 224, 41), + ), ) CAFE_CHECK = ButtonWrapper( name='CAFE_CHECK', jp=Button( file='./assets/jp/base/page/CAFE_CHECK.png', - area=(264, 11, 337, 35), - search=(244, 0, 357, 55), - color=(188, 197, 205), - button=(264, 11, 337, 35), + area=(108, 11, 182, 36), + search=(88, 0, 202, 56), + color=(191, 200, 208), + button=(108, 11, 182, 36), ), en=Button( file='./assets/en/base/page/CAFE_CHECK.png', - area=(196, 8, 259, 37), - search=(176, 0, 279, 57), - color=(188, 197, 206), - button=(196, 8, 259, 37), + area=(106, 12, 167, 36), + search=(86, 0, 187, 56), + color=(173, 184, 194), + button=(106, 12, 167, 36), + ), + zht=Button( + file='./assets/zht/base/page/CAFE_CHECK.png', + area=(107, 9, 190, 38), + search=(87, 0, 210, 58), + color=(154, 166, 178), + button=(107, 9, 190, 38), ), ) CIRCLE_CHECK = ButtonWrapper( @@ -87,6 +101,13 @@ CIRCLE_CHECK = ButtonWrapper( color=(190, 199, 208), button=(102, 8, 168, 38), ), + zht=Button( + file='./assets/zht/base/page/CIRCLE_CHECK.png', + area=(106, 10, 161, 38), + search=(86, 0, 181, 58), + color=(154, 167, 180), + button=(106, 10, 161, 38), + ), ) COMMISSIONS_CHECK = ButtonWrapper( name='COMMISSIONS_CHECK', @@ -104,6 +125,13 @@ COMMISSIONS_CHECK = ButtonWrapper( color=(191, 200, 209), button=(103, 7, 286, 37), ), + zht=Button( + file='./assets/zht/base/page/COMMISSIONS_CHECK.png', + area=(93, 5, 222, 42), + search=(73, 0, 242, 62), + color=(186, 196, 205), + button=(93, 5, 222, 42), + ), ) CRAFTING_CHECK = ButtonWrapper( name='CRAFTING_CHECK', @@ -121,6 +149,37 @@ CRAFTING_CHECK = ButtonWrapper( color=(202, 210, 217), button=(103, 4, 226, 41), ), + zht=Button( + file='./assets/zht/base/page/CRAFTING_CHECK.png', + area=(97, 1, 196, 44), + search=(77, 0, 216, 64), + color=(200, 207, 206), + button=(97, 1, 196, 44), + ), +) +EVENT_CHECK = ButtonWrapper( + name='EVENT_CHECK', + jp=Button( + file='./assets/jp/base/page/EVENT_CHECK.png', + area=(107, 11, 210, 37), + search=(87, 0, 230, 57), + color=(204, 211, 218), + button=(107, 11, 210, 37), + ), + en=Button( + file='./assets/en/base/page/EVENT_CHECK.png', + area=(102, 6, 187, 40), + search=(82, 0, 207, 60), + color=(200, 209, 216), + button=(102, 6, 187, 40), + ), + zht=Button( + file='./assets/zht/base/page/EVENT_CHECK.png', + area=(96, 1, 171, 40), + search=(76, 0, 191, 60), + color=(195, 205, 213), + button=(96, 1, 171, 40), + ), ) GACHA_CHECK = ButtonWrapper( name='GACHA_CHECK', @@ -138,18 +197,25 @@ GACHA_CHECK = ButtonWrapper( color=(189, 197, 206), button=(217, 8, 385, 37), ), + zht=Button( + file='./assets/zht/base/page/GACHA_CHECK.png', + area=(94, 0, 221, 40), + search=(74, 0, 241, 60), + color=(191, 200, 209), + button=(94, 0, 221, 40), + ), ) HOME = ButtonWrapper( name='HOME', jp=Button( file='./assets/jp/base/page/HOME.png', - area=(1218, 8, 1253, 41), - search=(1198, 0, 1273, 61), - color=(168, 182, 205), - button=(1218, 8, 1253, 41), + area=(1224, 10, 1250, 35), + search=(1204, 0, 1270, 55), + color=(162, 176, 202), + button=(1224, 10, 1250, 35), ), - en=Button( - file='./assets/en/base/page/HOME.png', + share=Button( + file='./assets/share/base/page/HOME.png', area=(1218, 8, 1253, 41), search=(1198, 0, 1273, 61), color=(168, 182, 205), @@ -158,15 +224,8 @@ HOME = ButtonWrapper( ) LOADING_CHECK = ButtonWrapper( name='LOADING_CHECK', - jp=Button( - file='./assets/jp/base/page/LOADING_CHECK.png', - area=(1084, 659, 1120, 674), - search=(1064, 639, 1140, 694), - color=(173, 196, 219), - button=(1084, 659, 1120, 674), - ), - en=Button( - file='./assets/en/base/page/LOADING_CHECK.png', + share=Button( + file='./assets/share/base/page/LOADING_CHECK.png', area=(1084, 659, 1120, 674), search=(1064, 639, 1140, 694), color=(173, 196, 219), @@ -189,18 +248,25 @@ MAIL_CHECK = ButtonWrapper( color=(174, 184, 196), button=(107, 11, 216, 37), ), + zht=Button( + file='./assets/zht/base/page/MAIL_CHECK.png', + area=(89, 2, 174, 40), + search=(69, 0, 194, 60), + color=(197, 207, 215), + button=(89, 2, 174, 40), + ), ) MAIN_GO_TO_CAFE = ButtonWrapper( name='MAIN_GO_TO_CAFE', jp=Button( file='./assets/jp/base/page/MAIN_GO_TO_CAFE.png', - area=(81, 638, 102, 668), - search=(61, 618, 122, 688), - color=(156, 209, 233), - button=(81, 638, 102, 668), + area=(86, 632, 113, 670), + search=(66, 612, 133, 690), + color=(174, 222, 246), + button=(86, 632, 113, 670), ), - en=Button( - file='./assets/en/base/page/MAIN_GO_TO_CAFE.png', + share=Button( + file='./assets/share/base/page/MAIN_GO_TO_CAFE.png', area=(81, 638, 102, 668), search=(61, 618, 122, 688), color=(156, 209, 233), @@ -211,30 +277,30 @@ MAIN_GO_TO_CIRCLE = ButtonWrapper( name='MAIN_GO_TO_CIRCLE', jp=Button( file='./assets/jp/base/page/MAIN_GO_TO_CIRCLE.png', - area=(540, 631, 583, 660), - search=(520, 611, 603, 680), - color=(131, 204, 234), - button=(540, 631, 583, 660), + area=(531, 631, 561, 659), + search=(511, 611, 581, 679), + color=(182, 230, 254), + button=(531, 631, 561, 659), ), - en=Button( - file='./assets/en/base/page/MAIN_GO_TO_CIRCLE.png', - area=(540, 631, 583, 660), - search=(520, 611, 603, 680), - color=(131, 204, 234), - button=(540, 631, 583, 660), + share=Button( + file='./assets/share/base/page/MAIN_GO_TO_CIRCLE.png', + area=(542, 630, 581, 665), + search=(522, 610, 601, 685), + color=(163, 225, 242), + button=(542, 630, 581, 665), ), ) MAIN_GO_TO_CRAFTING = ButtonWrapper( name='MAIN_GO_TO_CRAFTING', jp=Button( file='./assets/jp/base/page/MAIN_GO_TO_CRAFTING.png', - area=(665, 622, 693, 664), - search=(645, 602, 713, 684), - color=(192, 229, 241), - button=(665, 622, 693, 664), + area=(645, 627, 667, 662), + search=(625, 607, 687, 682), + color=(206, 235, 251), + button=(645, 627, 667, 662), ), - en=Button( - file='./assets/en/base/page/MAIN_GO_TO_CRAFTING.png', + share=Button( + file='./assets/share/base/page/MAIN_GO_TO_CRAFTING.png', area=(665, 622, 693, 664), search=(645, 602, 713, 684), color=(192, 229, 241), @@ -245,13 +311,13 @@ MAIN_GO_TO_GACHA = ButtonWrapper( name='MAIN_GO_TO_GACHA', jp=Button( file='./assets/jp/base/page/MAIN_GO_TO_GACHA.png', - area=(900, 623, 924, 670), - search=(880, 603, 944, 690), - color=(157, 219, 241), - button=(900, 623, 924, 670), + area=(871, 626, 893, 671), + search=(851, 606, 913, 691), + color=(172, 223, 242), + button=(871, 626, 893, 671), ), - en=Button( - file='./assets/en/base/page/MAIN_GO_TO_GACHA.png', + share=Button( + file='./assets/share/base/page/MAIN_GO_TO_GACHA.png', area=(900, 623, 924, 670), search=(880, 603, 944, 690), color=(157, 219, 241), @@ -262,13 +328,13 @@ MAIN_GO_TO_MAIL = ButtonWrapper( name='MAIN_GO_TO_MAIL', jp=Button( file='./assets/jp/base/page/MAIN_GO_TO_MAIL.png', - area=(1130, 29, 1156, 49), - search=(1110, 9, 1176, 69), - color=(94, 121, 166), - button=(1130, 29, 1156, 49), + area=(1144, 27, 1165, 44), + search=(1124, 7, 1185, 64), + color=(102, 125, 168), + button=(1144, 27, 1165, 44), ), - en=Button( - file='./assets/en/base/page/MAIN_GO_TO_MAIL.png', + share=Button( + file='./assets/share/base/page/MAIN_GO_TO_MAIL.png', area=(1130, 29, 1156, 49), search=(1110, 9, 1176, 69), color=(94, 121, 166), @@ -279,47 +345,47 @@ MAIN_GO_TO_MOMOTALK = ButtonWrapper( name='MAIN_GO_TO_MOMOTALK', jp=Button( file='./assets/jp/base/page/MAIN_GO_TO_MOMOTALK.png', - area=(154, 134, 177, 158), - search=(134, 114, 197, 178), - color=(255, 219, 227), - button=(154, 134, 177, 158), + area=(125, 118, 146, 151), + search=(105, 98, 166, 171), + color=(244, 200, 211), + button=(125, 118, 146, 151), ), - en=Button( - file='./assets/en/base/page/MAIN_GO_TO_MOMOTALK.png', - area=(154, 134, 177, 158), - search=(134, 114, 197, 178), - color=(255, 219, 227), - button=(154, 134, 177, 158), + share=Button( + file='./assets/share/base/page/MAIN_GO_TO_MOMOTALK.png', + area=(138, 130, 159, 161), + search=(118, 110, 179, 181), + color=(251, 215, 224), + button=(138, 130, 159, 161), ), ) MAIN_GO_TO_PURCHASE = ButtonWrapper( name='MAIN_GO_TO_PURCHASE', jp=Button( file='./assets/jp/base/page/MAIN_GO_TO_PURCHASE.png', - area=(148, 204, 183, 253), - search=(128, 184, 203, 273), - color=(172, 214, 239), - button=(148, 204, 183, 253), + area=(122, 209, 148, 243), + search=(102, 189, 168, 263), + color=(190, 222, 246), + button=(122, 209, 148, 243), ), - en=Button( - file='./assets/en/base/page/MAIN_GO_TO_PURCHASE.png', - area=(148, 204, 183, 253), - search=(128, 184, 203, 273), - color=(172, 214, 239), - button=(148, 204, 183, 253), + share=Button( + file='./assets/share/base/page/MAIN_GO_TO_PURCHASE.png', + area=(136, 210, 165, 250), + search=(116, 190, 185, 270), + color=(175, 216, 240), + button=(136, 210, 165, 250), ), ) MAIN_GO_TO_SCHEDULE = ButtonWrapper( name='MAIN_GO_TO_SCHEDULE', jp=Button( file='./assets/jp/base/page/MAIN_GO_TO_SCHEDULE.png', - area=(194, 638, 216, 672), - search=(174, 618, 236, 692), - color=(149, 194, 222), - button=(194, 638, 216, 672), + area=(198, 634, 217, 667), + search=(178, 614, 237, 687), + color=(176, 222, 243), + button=(198, 634, 217, 667), ), - en=Button( - file='./assets/en/base/page/MAIN_GO_TO_SCHEDULE.png', + share=Button( + file='./assets/share/base/page/MAIN_GO_TO_SCHEDULE.png', area=(194, 638, 216, 672), search=(174, 618, 236, 692), color=(149, 194, 222), @@ -330,13 +396,13 @@ MAIN_GO_TO_SHOP = ButtonWrapper( name='MAIN_GO_TO_SHOP', jp=Button( file='./assets/jp/base/page/MAIN_GO_TO_SHOP.png', - area=(773, 630, 816, 667), - search=(753, 610, 836, 687), - color=(146, 208, 235), - button=(773, 630, 816, 667), + area=(752, 628, 786, 665), + search=(732, 608, 806, 685), + color=(168, 221, 248), + button=(752, 628, 786, 665), ), - en=Button( - file='./assets/en/base/page/MAIN_GO_TO_SHOP.png', + share=Button( + file='./assets/share/base/page/MAIN_GO_TO_SHOP.png', area=(773, 630, 816, 667), search=(753, 610, 836, 687), color=(146, 208, 235), @@ -347,34 +413,41 @@ MAIN_GO_TO_TASK = ButtonWrapper( name='MAIN_GO_TO_TASK', jp=Button( file='./assets/jp/base/page/MAIN_GO_TO_TASK.png', - area=(52, 220, 78, 248), - search=(32, 200, 98, 268), - color=(226, 207, 203), - button=(52, 220, 78, 248), + area=(30, 214, 55, 248), + search=(10, 194, 75, 268), + color=(186, 218, 245), + button=(30, 214, 55, 248), ), - en=Button( - file='./assets/en/base/page/MAIN_GO_TO_TASK.png', - area=(52, 220, 78, 248), - search=(32, 200, 98, 268), - color=(226, 207, 203), - button=(52, 220, 78, 248), + share=Button( + file='./assets/share/base/page/MAIN_GO_TO_TASK.png', + area=(47, 219, 70, 246), + search=(27, 199, 90, 266), + color=(212, 204, 208), + button=(47, 219, 70, 246), ), ) MAIN_GO_TO_WORK = ButtonWrapper( name='MAIN_GO_TO_WORK', jp=Button( file='./assets/jp/base/page/MAIN_GO_TO_WORK.png', - area=(1167, 605, 1241, 632), - search=(1147, 585, 1261, 652), - color=(135, 149, 169), - button=(1167, 605, 1241, 632), + area=(1167, 668, 1222, 688), + search=(1147, 648, 1242, 708), + color=(141, 156, 174), + button=(1167, 668, 1222, 688), ), en=Button( file='./assets/en/base/page/MAIN_GO_TO_WORK.png', - area=(1127, 605, 1250, 637), - search=(1107, 585, 1270, 657), - color=(90, 112, 141), - button=(1127, 605, 1250, 637), + area=(1143, 591, 1241, 613), + search=(1123, 571, 1261, 633), + color=(110, 129, 154), + button=(1143, 591, 1241, 613), + ), + zht=Button( + file='./assets/zht/base/page/MAIN_GO_TO_WORK.png', + area=(1188, 586, 1241, 613), + search=(1168, 566, 1261, 633), + color=(141, 155, 174), + button=(1188, 586, 1241, 613), ), ) MISSION_CHECK = ButtonWrapper( @@ -393,6 +466,13 @@ MISSION_CHECK = ButtonWrapper( color=(194, 202, 210), button=(105, 7, 255, 39), ), + zht=Button( + file='./assets/zht/base/page/MISSION_CHECK.png', + area=(97, 3, 222, 37), + search=(77, 0, 242, 57), + color=(174, 185, 196), + button=(97, 3, 222, 37), + ), ) MOMOTALK_CHECK = ButtonWrapper( name='MOMOTALK_CHECK', @@ -403,8 +483,8 @@ MOMOTALK_CHECK = ButtonWrapper( color=(253, 211, 219), button=(144, 107, 169, 130), ), - en=Button( - file='./assets/en/base/page/MOMOTALK_CHECK.png', + share=Button( + file='./assets/share/base/page/MOMOTALK_CHECK.png', area=(144, 107, 169, 130), search=(124, 87, 189, 150), color=(253, 211, 219), @@ -413,15 +493,8 @@ MOMOTALK_CHECK = ButtonWrapper( ) MOMOTALK_GO_TO_MAIN = ButtonWrapper( name='MOMOTALK_GO_TO_MAIN', - jp=Button( - file='./assets/jp/base/page/MOMOTALK_GO_TO_MAIN.png', - area=(1108, 105, 1134, 131), - search=(1088, 85, 1154, 151), - color=(252, 182, 194), - button=(1108, 105, 1134, 131), - ), - en=Button( - file='./assets/en/base/page/MOMOTALK_GO_TO_MAIN.png', + share=Button( + file='./assets/share/base/page/MOMOTALK_GO_TO_MAIN.png', area=(1108, 105, 1134, 131), search=(1088, 85, 1154, 151), color=(252, 182, 194), @@ -444,6 +517,13 @@ SCHEDULE_CHECK = ButtonWrapper( color=(202, 210, 217), button=(101, 5, 200, 40), ), + zht=Button( + file='./assets/zht/base/page/SCHEDULE_CHECK.png', + area=(94, 2, 196, 40), + search=(74, 0, 216, 60), + color=(186, 196, 205), + button=(94, 2, 196, 40), + ), ) SCHOOL_EXCHANGE_CHECK = ButtonWrapper( name='SCHOOL_EXCHANGE_CHECK', @@ -461,6 +541,13 @@ SCHOOL_EXCHANGE_CHECK = ButtonWrapper( color=(200, 208, 215), button=(99, 6, 263, 41), ), + zht=Button( + file='./assets/zht/base/page/SCHOOL_EXCHANGE_CHECK.png', + area=(99, 3, 244, 42), + search=(79, 0, 264, 62), + color=(187, 196, 205), + button=(99, 3, 244, 42), + ), ) SHOP_CHECK = ButtonWrapper( name='SHOP_CHECK', @@ -478,6 +565,13 @@ SHOP_CHECK = ButtonWrapper( color=(188, 197, 207), button=(103, 11, 176, 41), ), + zht=Button( + file='./assets/zht/base/page/SHOP_CHECK.png', + area=(97, 1, 166, 41), + search=(77, 0, 186, 61), + color=(194, 203, 212), + button=(97, 1, 166, 41), + ), ) STORY_CHECK = ButtonWrapper( name='STORY_CHECK', @@ -495,6 +589,13 @@ STORY_CHECK = ButtonWrapper( color=(188, 197, 207), button=(104, 11, 178, 40), ), + zht=Button( + file='./assets/zht/base/page/STORY_CHECK.png', + area=(100, 3, 163, 36), + search=(80, 0, 183, 56), + color=(173, 184, 196), + button=(100, 3, 163, 36), + ), ) TACTICAL_CHALLENGE_CHECK = ButtonWrapper( name='TACTICAL_CHALLENGE_CHECK', @@ -512,6 +613,13 @@ TACTICAL_CHALLENGE_CHECK = ButtonWrapper( color=(190, 199, 207), button=(104, 10, 214, 38), ), + zht=Button( + file='./assets/zht/base/page/TACTICAL_CHALLENGE_CHECK.png', + area=(97, 2, 222, 39), + search=(77, 0, 242, 59), + color=(185, 195, 204), + button=(97, 2, 222, 39), + ), ) TASK_CHECK = ButtonWrapper( name='TASK_CHECK', @@ -529,6 +637,13 @@ TASK_CHECK = ButtonWrapper( color=(185, 194, 204), button=(102, 11, 185, 37), ), + zht=Button( + file='./assets/zht/base/page/TASK_CHECK.png', + area=(536, 83, 607, 128), + search=(516, 63, 627, 148), + color=(77, 92, 87), + button=(536, 83, 607, 128), + ), ) WORK_CHECK = ButtonWrapper( name='WORK_CHECK', @@ -546,106 +661,179 @@ WORK_CHECK = ButtonWrapper( color=(197, 205, 213), button=(105, 5, 244, 41), ), + zht=Button( + file='./assets/zht/base/page/WORK_CHECK.png', + area=(105, 9, 164, 39), + search=(85, 0, 184, 59), + color=(165, 176, 188), + button=(105, 9, 164, 39), + ), ) WORK_GO_TO_BOUNTY = ButtonWrapper( name='WORK_GO_TO_BOUNTY', jp=Button( file='./assets/jp/base/page/WORK_GO_TO_BOUNTY.png', - area=(669, 412, 765, 436), - search=(649, 392, 785, 456), - color=(165, 181, 208), - button=(669, 412, 765, 436), + area=(679, 401, 741, 417), + search=(659, 381, 761, 437), + color=(155, 164, 186), + button=(679, 401, 741, 417), ), en=Button( file='./assets/en/base/page/WORK_GO_TO_BOUNTY.png', - area=(671, 409, 760, 440), - search=(651, 389, 780, 460), - color=(183, 202, 227), - button=(671, 409, 760, 440), + area=(668, 385, 756, 412), + search=(648, 365, 776, 432), + color=(180, 198, 223), + button=(668, 385, 756, 412), + ), + zht=Button( + file='./assets/zht/base/page/WORK_GO_TO_BOUNTY.png', + area=(667, 383, 769, 410), + search=(647, 363, 789, 430), + color=(169, 184, 209), + button=(667, 383, 769, 410), ), ) WORK_GO_TO_COMMISSIONS = ButtonWrapper( name='WORK_GO_TO_COMMISSIONS', jp=Button( file='./assets/jp/base/page/WORK_GO_TO_COMMISSIONS.png', - area=(655, 494, 751, 518), - search=(635, 474, 771, 538), - color=(165, 179, 204), - button=(655, 494, 751, 518), + area=(661, 504, 724, 521), + search=(641, 484, 744, 541), + color=(155, 163, 184), + button=(661, 504, 724, 521), ), en=Button( file='./assets/en/base/page/WORK_GO_TO_COMMISSIONS.png', - area=(656, 494, 803, 517), - search=(636, 474, 823, 537), - color=(192, 204, 221), - button=(656, 494, 803, 517), + area=(653, 477, 787, 496), + search=(633, 457, 807, 516), + color=(187, 199, 218), + button=(653, 477, 787, 496), + ), + zht=Button( + file='./assets/zht/base/page/WORK_GO_TO_COMMISSIONS.png', + area=(654, 474, 753, 503), + search=(634, 454, 773, 523), + color=(179, 192, 213), + button=(654, 474, 753, 503), + ), +) +WORK_GO_TO_EVENT = ButtonWrapper( + name='WORK_GO_TO_EVENT', + jp=Button( + file='./assets/jp/base/page/WORK_GO_TO_EVENT.png', + area=(64, 124, 140, 207), + search=(44, 104, 160, 227), + color=(191, 172, 183), + button=(64, 124, 140, 207), + ), + en=Button( + file='./assets/en/base/page/WORK_GO_TO_EVENT.png', + area=(67, 131, 142, 201), + search=(47, 111, 162, 221), + color=(134, 153, 166), + button=(67, 131, 142, 201), + ), + zht=Button( + file='./assets/zht/base/page/WORK_GO_TO_EVENT.png', + area=(65, 126, 140, 206), + search=(45, 106, 160, 226), + color=(142, 137, 138), + button=(65, 126, 140, 206), ), ) WORK_GO_TO_MISSION = ButtonWrapper( name='WORK_GO_TO_MISSION', jp=Button( file='./assets/jp/base/page/WORK_GO_TO_MISSION.png', - area=(720, 160, 803, 199), - search=(700, 140, 823, 219), - color=(165, 178, 204), - button=(720, 160, 803, 199), + area=(710, 220, 769, 249), + search=(690, 200, 789, 269), + color=(137, 153, 180), + button=(710, 220, 769, 249), ), en=Button( file='./assets/en/base/page/WORK_GO_TO_MISSION.png', - area=(720, 160, 871, 206), - search=(700, 140, 891, 226), - color=(192, 204, 221), - button=(720, 160, 871, 206), + area=(721, 158, 873, 197), + search=(701, 138, 893, 217), + color=(180, 195, 215), + button=(721, 158, 873, 197), + ), + zht=Button( + file='./assets/zht/base/page/WORK_GO_TO_MISSION.png', + area=(720, 152, 812, 200), + search=(700, 132, 832, 220), + color=(181, 195, 215), + button=(720, 152, 812, 200), ), ) WORK_GO_TO_SCHOOL_EXCHANGE = ButtonWrapper( name='WORK_GO_TO_SCHOOL_EXCHANGE', jp=Button( file='./assets/jp/base/page/WORK_GO_TO_SCHOOL_EXCHANGE.png', - area=(641, 575, 758, 599), - search=(621, 555, 778, 619), - color=(165, 179, 204), - button=(641, 575, 758, 599), + area=(642, 606, 721, 624), + search=(622, 586, 741, 644), + color=(167, 174, 192), + button=(642, 606, 721, 624), ), en=Button( file='./assets/en/base/page/WORK_GO_TO_SCHOOL_EXCHANGE.png', - area=(643, 572, 778, 602), - search=(623, 552, 798, 622), - color=(190, 203, 221), - button=(643, 572, 778, 602), + area=(635, 569, 769, 597), + search=(615, 549, 789, 617), + color=(198, 204, 216), + button=(635, 569, 769, 597), + ), + zht=Button( + file='./assets/zht/base/page/WORK_GO_TO_SCHOOL_EXCHANGE.png', + area=(635, 567, 761, 594), + search=(615, 547, 781, 614), + color=(181, 191, 209), + button=(635, 567, 761, 594), ), ) WORK_GO_TO_STORY = ButtonWrapper( name='WORK_GO_TO_STORY', jp=Button( file='./assets/jp/base/page/WORK_GO_TO_STORY.png', - area=(995, 163, 1032, 197), - search=(975, 143, 1052, 217), - color=(191, 201, 219), - button=(995, 163, 1032, 197), + area=(993, 220, 1081, 250), + search=(973, 200, 1101, 270), + color=(209, 214, 224), + button=(993, 220, 1081, 250), ), en=Button( file='./assets/en/base/page/WORK_GO_TO_STORY.png', - area=(994, 159, 1109, 211), - search=(974, 139, 1129, 231), - color=(208, 215, 228), - button=(994, 159, 1109, 211), + area=(996, 159, 1112, 205), + search=(976, 139, 1132, 225), + color=(200, 209, 225), + button=(996, 159, 1112, 205), + ), + zht=Button( + file='./assets/zht/base/page/WORK_GO_TO_STORY.png', + area=(991, 152, 1093, 202), + search=(971, 132, 1113, 222), + color=(190, 200, 218), + button=(991, 152, 1093, 202), ), ) WORK_GO_TO_TACTICAL_CHALLENGE = ButtonWrapper( name='WORK_GO_TO_TACTICAL_CHALLENGE', jp=Button( file='./assets/jp/base/page/WORK_GO_TO_TACTICAL_CHALLENGE.png', - area=(1012, 535, 1151, 562), - search=(992, 515, 1171, 582), - color=(159, 174, 200), - button=(1012, 535, 1151, 562), + area=(821, 600, 911, 620), + search=(801, 580, 931, 640), + color=(156, 164, 185), + button=(821, 600, 911, 620), ), 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=(803, 566, 922, 623), + search=(783, 546, 942, 643), + color=(186, 201, 220), + button=(803, 566, 922, 623), + ), + zht=Button( + file='./assets/zht/base/page/WORK_GO_TO_TACTICAL_CHALLENGE.png', + area=(801, 564, 908, 595), + search=(781, 544, 928, 615), + color=(179, 192, 213), + button=(801, 564, 908, 595), ), ) diff --git a/tasks/base/assets/assets_base_popup.py b/tasks/base/assets/assets_base_popup.py index 6c9bc43..db15eed 100644 --- a/tasks/base/assets/assets_base_popup.py +++ b/tasks/base/assets/assets_base_popup.py @@ -19,14 +19,21 @@ AFFECTION_LEVEL_UP = ButtonWrapper( color=(217, 227, 243), button=(882, 244, 1176, 476), ), + zht=Button( + file='./assets/zht/base/popup/AFFECTION_LEVEL_UP.png', + area=(655, 596, 745, 647), + search=(635, 576, 765, 667), + color=(199, 220, 242), + button=(882, 244, 1176, 476), + ), ) AP_EXCEED = ButtonWrapper( name='AP_EXCEED', jp=Button( file='./assets/jp/base/popup/AP_EXCEED.png', - area=(611, 147, 669, 177), - search=(591, 127, 689, 197), - color=(143, 156, 170), + area=(400, 288, 879, 377), + search=(380, 268, 899, 397), + color=(201, 202, 202), button=(539, 470, 743, 533), ), en=Button( @@ -36,18 +43,18 @@ AP_EXCEED = ButtonWrapper( color=(197, 205, 212), button=(537, 470, 747, 530), ), + zht=Button( + file='./assets/zht/base/popup/AP_EXCEED.png', + area=(583, 138, 693, 178), + search=(563, 118, 713, 198), + color=(197, 205, 212), + button=(537, 470, 747, 530), + ), ) DAILY_NEWS = ButtonWrapper( name='DAILY_NEWS', - jp=Button( - file='./assets/jp/base/popup/DAILY_NEWS.png', - area=(120, 89, 326, 113), - search=(100, 69, 346, 133), - color=(150, 204, 253), - button=(1128, 89, 1156, 117), - ), - en=Button( - file='./assets/en/base/popup/DAILY_NEWS.png', + share=Button( + file='./assets/share/base/popup/DAILY_NEWS.png', area=(120, 89, 326, 113), search=(100, 69, 346, 133), color=(150, 204, 253), @@ -56,15 +63,8 @@ DAILY_NEWS = ButtonWrapper( ) DAILY_REWARD = ButtonWrapper( name='DAILY_REWARD', - jp=Button( - file='./assets/jp/base/popup/DAILY_REWARD.png', - area=(416, 165, 434, 216), - search=(396, 145, 454, 236), - color=(203, 227, 237), - button=(920, 632, 1140, 712), - ), - en=Button( - file='./assets/en/base/popup/DAILY_REWARD.png', + share=Button( + file='./assets/share/base/popup/DAILY_REWARD.png', area=(416, 165, 434, 216), search=(396, 145, 454, 236), color=(203, 227, 237), @@ -73,15 +73,8 @@ DAILY_REWARD = ButtonWrapper( ) GET_NEW_STUDENT = ButtonWrapper( name='GET_NEW_STUDENT', - jp=Button( - file='./assets/jp/base/popup/GET_NEW_STUDENT.png', - area=(32, 93, 158, 114), - search=(12, 73, 178, 134), - color=(125, 132, 92), - button=(934, 643, 1263, 714), - ), - en=Button( - file='./assets/en/base/popup/GET_NEW_STUDENT.png', + share=Button( + file='./assets/share/base/popup/GET_NEW_STUDENT.png', area=(32, 93, 158, 114), search=(12, 73, 178, 134), color=(125, 132, 92), @@ -104,6 +97,13 @@ GET_REWARD = ButtonWrapper( color=(211, 210, 90), button=(578, 617, 707, 646), ), + zht=Button( + file='./assets/zht/base/popup/GET_REWARD.png', + area=(587, 131, 630, 155), + search=(567, 111, 650, 175), + color=(225, 224, 92), + button=(574, 610, 713, 651), + ), ) GET_REWARD_SKIP = ButtonWrapper( name='GET_REWARD_SKIP', @@ -121,14 +121,21 @@ GET_REWARD_SKIP = ButtonWrapper( color=(197, 200, 205), button=(1137, 34, 1243, 65), ), + zht=Button( + file='./assets/zht/base/popup/GET_REWARD_SKIP.png', + area=(1137, 34, 1243, 65), + search=(1117, 14, 1263, 85), + color=(197, 200, 205), + button=(1137, 34, 1243, 65), + ), ) INSUFFICIENT_INVENTORY = ButtonWrapper( name='INSUFFICIENT_INVENTORY', jp=Button( file='./assets/jp/base/popup/INSUFFICIENT_INVENTORY.png', - area=(610, 124, 669, 153), - search=(590, 104, 689, 173), - color=(139, 153, 168), + area=(371, 392, 896, 451), + search=(351, 372, 916, 471), + color=(213, 218, 222), button=(535, 494, 746, 565), ), en=Button( @@ -138,6 +145,13 @@ INSUFFICIENT_INVENTORY = ButtonWrapper( color=(202, 209, 216), button=(535, 502, 747, 560), ), + zht=Button( + file='./assets/zht/base/popup/INSUFFICIENT_INVENTORY.png', + area=(578, 117, 703, 152), + search=(558, 97, 723, 172), + color=(202, 209, 216), + button=(535, 502, 747, 560), + ), ) ITEM_EXPIRED = ButtonWrapper( name='ITEM_EXPIRED', @@ -155,6 +169,61 @@ ITEM_EXPIRED = ButtonWrapper( color=(131, 145, 162), button=(537, 487, 742, 553), ), + zht=Button( + file='./assets/zht/base/popup/ITEM_EXPIRED.png', + area=(612, 147, 668, 175), + search=(592, 127, 688, 195), + color=(131, 145, 162), + button=(537, 487, 742, 553), + ), +) +LEVEL_UP = ButtonWrapper( + name='LEVEL_UP', + jp=Button( + file='./assets/jp/base/popup/LEVEL_UP.png', + area=(722, 210, 746, 238), + search=(702, 190, 766, 258), + color=(184, 202, 219), + button=(571, 559, 713, 601), + ), + en=Button( + file='./assets/en/base/popup/LEVEL_UP.png', + area=(722, 210, 746, 238), + search=(702, 190, 766, 258), + color=(184, 202, 219), + button=(571, 559, 713, 601), + ), + zht=Button( + file='./assets/zht/base/popup/LEVEL_UP.png', + area=(722, 210, 746, 238), + search=(702, 190, 766, 258), + color=(184, 202, 219), + button=(571, 559, 713, 601), + ), +) +LOCATION_LEVEL_UP = ButtonWrapper( + name='LOCATION_LEVEL_UP', + jp=Button( + file='./assets/jp/base/popup/LOCATION_LEVEL_UP.png', + area=(430, 141, 463, 180), + search=(410, 121, 483, 200), + color=(177, 194, 207), + button=(577, 616, 707, 647), + ), + en=Button( + file='./assets/en/base/popup/LOCATION_LEVEL_UP.png', + area=(430, 141, 463, 180), + search=(410, 121, 483, 200), + color=(177, 194, 207), + button=(577, 616, 707, 647), + ), + zht=Button( + file='./assets/zht/base/popup/LOCATION_LEVEL_UP.png', + area=(571, 146, 624, 172), + search=(551, 126, 644, 192), + color=(198, 219, 239), + button=(573, 614, 706, 650), + ), ) NETWORK_RECONNECT = ButtonWrapper( name='NETWORK_RECONNECT', @@ -172,6 +241,13 @@ NETWORK_RECONNECT = ButtonWrapper( color=(99, 180, 213), button=(664, 469, 871, 534), ), + zht=Button( + file='./assets/zht/base/popup/NETWORK_RECONNECT.png', + area=(688, 484, 852, 517), + search=(668, 464, 872, 537), + color=(99, 180, 213), + button=(664, 469, 871, 534), + ), ) NETWORK_RECONNECT_OK = ButtonWrapper( name='NETWORK_RECONNECT_OK', @@ -180,7 +256,7 @@ NETWORK_RECONNECT_OK = ButtonWrapper( area=(744, 487, 791, 515), search=(724, 467, 811, 535), color=(91, 165, 196), - button=(665, 468, 870, 536), + button=(442, 487, 592, 521), ), en=Button( file='./assets/en/base/popup/NETWORK_RECONNECT_OK.png', @@ -189,4 +265,35 @@ NETWORK_RECONNECT_OK = ButtonWrapper( color=(103, 189, 222), button=(540, 471, 744, 530), ), + zht=Button( + file='./assets/zht/base/popup/NETWORK_RECONNECT_OK.png', + area=(569, 483, 708, 521), + search=(549, 463, 728, 541), + color=(103, 189, 222), + button=(540, 471, 744, 530), + ), +) +QUIT = ButtonWrapper( + name='QUIT', + jp=Button( + file='./assets/jp/base/popup/QUIT.png', + area=(497, 313, 779, 353), + search=(477, 293, 799, 373), + color=(210, 210, 210), + button=(442, 487, 592, 521), + ), + en=Button( + file='./assets/en/base/popup/QUIT.png', + area=(587, 309, 688, 357), + search=(567, 289, 708, 377), + color=(225, 225, 225), + button=(410, 473, 623, 532), + ), + zht=Button( + file='./assets/zht/base/popup/QUIT.png', + area=(556, 310, 713, 364), + search=(536, 290, 733, 384), + color=(215, 215, 215), + button=(411, 472, 620, 535), + ), ) diff --git a/tasks/base/popup.py b/tasks/base/popup.py index 2559c91..10804d3 100644 --- a/tasks/base/popup.py +++ b/tasks/base/popup.py @@ -1,7 +1,7 @@ from module.base.base import ModuleBase from module.base.timer import Timer -from tasks.base.assets.assets_base_popup import * from tasks.base.assets.assets_base_page import LOADING_CHECK +from tasks.base.assets.assets_base_popup import * class PopupHandler(ModuleBase): @@ -105,3 +105,21 @@ class PopupHandler(ModuleBase): return True return False + + def handle_location_level_up(self, interval=5) -> bool: + if self.appear_then_click(LOCATION_LEVEL_UP, interval=interval): + return True + + return False + + def handle_level_up(self, interval=5) -> bool: + if self.appear_then_click(LEVEL_UP, interval=interval): + return True + + return False + + def handle_quit(self, interval=5) -> bool: + if self.appear_then_click(QUIT, interval=interval): + return True + + return False diff --git a/tasks/base/ui.py b/tasks/base/ui.py index b89219b..3c89e33 100644 --- a/tasks/base/ui.py +++ b/tasks/base/ui.py @@ -1,18 +1,36 @@ from module.base.button import ButtonWrapper -from module.base.decorator import run_once +from module.base.decorator import Config, run_once from module.base.timer import Timer -from module.exception import GameNotRunningError, GamePageUnknownError +from module.base.utils import get_color +from module.exception import GameNotRunningError, GamePageUnknownError, RequestHumanTakeover from module.logger import logger -from module.ocr.ocr import Ocr +from module.ocr.ocr import Digit, Ocr +from tasks.base.assets.assets_base_page import BACK from tasks.base.main_page import MainPage from tasks.base.page import Page, page_main -from tasks.base.assets.assets_base_page import BACK +from tasks.login.assets.assets_login import LOGIN_LOADING, OCR_YEAR class UI(MainPage): ui_current: Page ui_main_confirm_timer = Timer(0.2, count=2) + @Config.when(Emulator_GameLanguage='zhs') + def appear_trademark_year(self): + ocr_year = Digit(OCR_YEAR).ocr_single_line(self.device.image) + return ocr_year == 2023 + + # temporary block check for jp + @Config.when(Emulator_GameLanguage='jp') + def appear_trademark_year(self): + # this disables using back button in jp due to a login issue + return True + + @Config.when(Emulator_GameLanguage=None) + def appear_trademark_year(self): + ocr_year = Digit(OCR_YEAR).ocr_single_line(self.device.image) + return ocr_year == 2021 + def ui_page_appear(self, page): """ Args: @@ -50,6 +68,7 @@ class UI(MainPage): timeout = Timer(10, count=20).start() back_timer = Timer(0.5, count=2) + u2_back = True while 1: if skip_first_screenshot: skip_first_screenshot = False @@ -78,8 +97,20 @@ class UI(MainPage): timeout.reset() continue if back_timer.reached_and_reset(): + # this might be bad but it works + if self.match_color(LOGIN_LOADING, interval=5, threshold=80) or self.appear_trademark_year(): + from tasks.login.login import Login + Login(self.config, self.device).handle_app_login() + timeout.reset() + continue logger.info("Unknown page, try to back") - self.device.click(BACK) + # allows TooManyClicks to be triggered in case something goes wrong + if u2_back: + self.device.back() + u2_back = False + else: + self.device.click(BACK) + u2_back = True app_check() minicap_check() @@ -107,7 +138,7 @@ class UI(MainPage): self.interval_clear(list(Page.iter_check_buttons())) # loading_timer = Timer(0.5) - + back_timer = Timer(15, 15) logger.hr(f"UI goto {destination}") while 1: if skip_first_screenshot: @@ -126,6 +157,7 @@ class UI(MainPage): # Destination page if self.ui_page_appear(destination): logger.info(f'Page arrive: {destination}') + self.close_popup(destination.check_button) if self.ui_page_confirm(destination): logger.info(f'Page arrive confirm {destination}') break @@ -137,6 +169,7 @@ class UI(MainPage): continue if self.appear(page.check_button, interval=5): logger.info(f'Page switch: {page} -> {page.parent}') + self.close_popup(page.check_button) # self.handle_lang_check(page) if self.ui_page_confirm(page): logger.info(f'Page arrive confirm {page}') @@ -152,6 +185,18 @@ class UI(MainPage): if self.ui_additional(): continue + back_timer.start() + if back_timer.reached(): + if self.match_color(LOGIN_LOADING, interval=5, threshold=80) or self.appear_trademark_year(): + from tasks.login.login import Login + Login(self.config, self.device).handle_app_login() + # don't click back when screen is black. + # Useful for loading screen after switching between pages + elif [x for x in get_color(self.device.image, BACK.area) if x > 50]: + self.device.back() + logger.info("Unknown page, try to back") + back_timer.reset() + # Reset connection Page.clear_connection() @@ -176,6 +221,7 @@ class UI(MainPage): if self.ui_current == destination: logger.info("Already at %s" % destination) + self.close_popup(destination.check_button) return False else: logger.info("Goto %s" % destination) @@ -337,18 +383,22 @@ class UI(MainPage): return True if self.handle_daily_news(): return True + if self.handle_quit(): + return True if self.handle_network_reconnect(): return True if self.handle_affection_level_up(): return True if self.handle_new_student(): return True - if self.handle_ap_exceed(): - return True - if self.handle_insufficient_inventory(): - return True - if self.handle_item_expired(): - return True + # disabled because will exit the game if quit appears + + # if self.handle_ap_exceed(): + # return True + # if self.handle_insufficient_inventory(): + # return True + # if self.handle_item_expired(): + # return True return False @@ -399,3 +449,18 @@ class UI(MainPage): button (Button): """ pass + + def close_popup(self, check_button): + if not self.match_color(check_button): + timer = Timer(5, 5).start() + wait = Timer(1).start() + while 1: + self.device.screenshot() + if self.match_color(check_button) or not self.appear(check_button): + break + self.device.back() + if timer.reached(): + logger.error("Failed to close popup") + raise RequestHumanTakeover + while not wait.reached(): + pass diff --git a/tasks/bounty/assets/assets_bounty.py b/tasks/bounty/assets/assets_bounty.py index 118d4ef..45de6c4 100644 --- a/tasks/bounty/assets/assets_bounty.py +++ b/tasks/bounty/assets/assets_bounty.py @@ -12,7 +12,20 @@ CHECK_BOUNTY = ButtonWrapper( color=(88, 113, 139), button=(654, 87, 889, 129), ), - en=None, + en=Button( + file='./assets/en/bounty/CHECK_BOUNTY.png', + area=(654, 87, 891, 126), + search=(634, 67, 911, 146), + color=(92, 116, 141), + button=(654, 87, 891, 126), + ), + zht=Button( + file='./assets/zht/bounty/CHECK_BOUNTY.png', + area=(651, 84, 891, 130), + search=(631, 64, 911, 150), + color=(81, 107, 134), + button=(651, 84, 891, 130), + ), ) CHECK_DESERT_RAILROAD = ButtonWrapper( name='CHECK_DESERT_RAILROAD', @@ -23,7 +36,20 @@ CHECK_DESERT_RAILROAD = ButtonWrapper( color=(172, 179, 183), button=(106, 147, 401, 179), ), - en=None, + en=Button( + file='./assets/en/bounty/CHECK_DESERT_RAILROAD.png', + area=(105, 144, 490, 183), + search=(85, 124, 510, 203), + color=(200, 207, 211), + button=(105, 144, 490, 183), + ), + zht=Button( + file='./assets/zht/bounty/CHECK_DESERT_RAILROAD.png', + area=(97, 133, 402, 184), + search=(77, 113, 422, 204), + color=(199, 206, 210), + button=(97, 133, 402, 184), + ), ) CHECK_HIGHWAY = ButtonWrapper( name='CHECK_HIGHWAY', @@ -34,7 +60,20 @@ CHECK_HIGHWAY = ButtonWrapper( color=(191, 199, 203), button=(107, 147, 400, 179), ), - en=None, + en=Button( + file='./assets/en/bounty/CHECK_HIGHWAY.png', + area=(104, 143, 391, 188), + search=(84, 123, 411, 208), + color=(206, 213, 217), + button=(104, 143, 391, 188), + ), + zht=Button( + file='./assets/zht/bounty/CHECK_HIGHWAY.png', + area=(94, 131, 408, 186), + search=(74, 111, 428, 206), + color=(204, 211, 214), + button=(94, 131, 408, 186), + ), ) CHECK_SCHOOLHOUSE = ButtonWrapper( name='CHECK_SCHOOLHOUSE', @@ -45,18 +84,44 @@ CHECK_SCHOOLHOUSE = ButtonWrapper( color=(176, 183, 187), button=(106, 147, 314, 179), ), - en=None, + en=Button( + file='./assets/en/bounty/CHECK_SCHOOLHOUSE.png', + area=(102, 144, 408, 185), + search=(82, 124, 428, 205), + color=(203, 210, 214), + button=(102, 144, 408, 185), + ), + zht=Button( + file='./assets/zht/bounty/CHECK_SCHOOLHOUSE.png', + area=(97, 139, 343, 183), + search=(77, 119, 363, 203), + color=(195, 201, 205), + button=(97, 139, 343, 183), + ), ) OCR_TICKET = ButtonWrapper( name='OCR_TICKET', jp=Button( file='./assets/jp/bounty/OCR_TICKET.png', - area=(195, 85, 237, 113), - search=(175, 65, 257, 133), - color=(197, 206, 213), - button=(195, 85, 237, 113), + area=(196, 87, 258, 113), + search=(176, 67, 278, 133), + color=(173, 197, 212), + button=(196, 87, 258, 113), + ), + en=Button( + file='./assets/en/bounty/OCR_TICKET.png', + area=(229, 89, 285, 112), + search=(209, 69, 305, 132), + color=(177, 194, 207), + button=(229, 89, 285, 112), + ), + zht=Button( + file='./assets/zht/bounty/OCR_TICKET.png', + area=(168, 84, 219, 121), + search=(148, 64, 239, 141), + color=(202, 210, 216), + button=(168, 84, 219, 121), ), - en=None, ) SELECT_DESERT_RAILROAD = ButtonWrapper( name='SELECT_DESERT_RAILROAD', @@ -67,7 +132,20 @@ SELECT_DESERT_RAILROAD = ButtonWrapper( color=(178, 188, 199), button=(1066, 271, 1224, 311), ), - en=None, + en=Button( + file='./assets/en/bounty/SELECT_DESERT_RAILROAD.png', + area=(989, 270, 1223, 308), + search=(969, 250, 1243, 328), + color=(196, 203, 212), + button=(989, 270, 1223, 308), + ), + zht=Button( + file='./assets/zht/bounty/SELECT_DESERT_RAILROAD.png', + area=(1082, 266, 1229, 312), + search=(1062, 246, 1249, 332), + color=(192, 200, 210), + button=(1082, 266, 1229, 312), + ), ) SELECT_HIGHWAY = ButtonWrapper( name='SELECT_HIGHWAY', @@ -78,7 +156,20 @@ SELECT_HIGHWAY = ButtonWrapper( color=(214, 221, 228), button=(1065, 165, 1223, 203), ), - en=None, + en=Button( + file='./assets/en/bounty/SELECT_HIGHWAY.png', + area=(1073, 167, 1223, 202), + search=(1053, 147, 1243, 222), + color=(192, 201, 211), + button=(1073, 167, 1223, 202), + ), + zht=Button( + file='./assets/zht/bounty/SELECT_HIGHWAY.png', + area=(1083, 157, 1231, 206), + search=(1063, 137, 1251, 226), + color=(197, 205, 215), + button=(1083, 157, 1231, 206), + ), ) SELECT_SCHOOLHOUSE = ButtonWrapper( name='SELECT_SCHOOLHOUSE', @@ -89,5 +180,18 @@ SELECT_SCHOOLHOUSE = ButtonWrapper( color=(173, 185, 198), button=(1154, 381, 1223, 417), ), - en=None, + en=Button( + file='./assets/en/bounty/SELECT_SCHOOLHOUSE.png', + area=(1055, 378, 1226, 415), + search=(1035, 358, 1246, 435), + color=(198, 206, 215), + button=(1055, 378, 1226, 415), + ), + zht=Button( + file='./assets/zht/bounty/SELECT_SCHOOLHOUSE.png', + area=(1143, 374, 1233, 420), + search=(1123, 354, 1253, 440), + color=(195, 206, 216), + button=(1143, 374, 1233, 420), + ), ) diff --git a/tasks/bounty/bounty.py b/tasks/bounty/bounty.py index d77a149..9b72bf8 100644 --- a/tasks/bounty/bounty.py +++ b/tasks/bounty/bounty.py @@ -41,8 +41,9 @@ class Bounty(BountyUI): if action == 'stop': raise RequestHumanTakeover elif action == 'skip': - self.config.task_delay(server_update=True) - self.config.task_stop() + with self.config.multi_set(): + self.config.task_delay(server_update=True) + self.config.task_stop() @property def is_ticket_enough(self) -> bool: diff --git a/tasks/bounty/ui.py b/tasks/bounty/ui.py index 54b3330..fda0e18 100644 --- a/tasks/bounty/ui.py +++ b/tasks/bounty/ui.py @@ -22,6 +22,8 @@ class BountyUI(UI): return False def enter_stage(self, index: int) -> bool: + if not index: + index = BOUNTY_LIST.insight_max_sweepable_index(self) if BOUNTY_LIST.select_index_enter(self, index): return True return False diff --git a/tasks/cafe/assets/assets_cafe.py b/tasks/cafe/assets/assets_cafe.py index 0a65744..4601153 100644 --- a/tasks/cafe/assets/assets_cafe.py +++ b/tasks/cafe/assets/assets_cafe.py @@ -5,117 +5,172 @@ from module.base.button import Button, ButtonWrapper BOX_CAFE = ButtonWrapper( name='BOX_CAFE', - jp=Button( - file='./assets/jp/cafe/BOX_CAFE.png', + share=Button( + file='./assets/share/cafe/BOX_CAFE.png', area=(33, 130, 1247, 569), search=(13, 110, 1267, 589), color=(175, 181, 186), button=(33, 130, 1247, 569), ), - en=Button( - file='./assets/en/cafe/BOX_CAFE.png', - area=(33, 130, 1247, 569), - search=(13, 110, 1267, 589), - color=(175, 181, 186), - button=(33, 130, 1247, 569), +) +BOX_SEARCH = ButtonWrapper( + name='BOX_SEARCH', + share=Button( + file='./assets/share/cafe/BOX_SEARCH.png', + area=(12, 71, 1176, 654), + search=(0, 51, 1196, 674), + color=(175, 183, 190), + button=(12, 71, 1176, 654), ), ) CAFE_FIRST = ButtonWrapper( name='CAFE_FIRST', jp=Button( file='./assets/jp/cafe/CAFE_FIRST.png', - area=(82, 152, 136, 175), - search=(62, 132, 156, 195), - color=(111, 127, 147), - button=(82, 152, 136, 175), + area=(96, 92, 111, 110), + search=(76, 72, 131, 130), + color=(185, 192, 200), + button=(96, 92, 111, 110), ), en=Button( file='./assets/en/cafe/CAFE_FIRST.png', - area=(82, 152, 136, 175), - search=(62, 132, 156, 195), - color=(111, 127, 147), - button=(82, 152, 136, 175), + area=(205, 96, 215, 107), + search=(185, 76, 235, 127), + color=(176, 184, 194), + button=(205, 96, 215, 107), + ), + zht=Button( + file='./assets/zht/cafe/CAFE_FIRST.png', + area=(153, 93, 164, 109), + search=(133, 73, 184, 129), + color=(172, 180, 190), + button=(153, 93, 164, 109), + ), +) +CAFE_INVITE = ButtonWrapper( + name='CAFE_INVITE', + jp=Button( + file='./assets/jp/cafe/CAFE_INVITE.png', + area=(870, 637, 903, 663), + search=(850, 617, 923, 683), + color=(253, 217, 235), + button=(870, 637, 903, 663), + ), + en=Button( + file='./assets/en/cafe/CAFE_INVITE.png', + area=(870, 637, 903, 663), + search=(850, 617, 923, 683), + color=(253, 217, 235), + button=(870, 637, 903, 663), + ), + zht=Button( + file='./assets/zht/cafe/CAFE_INVITE.png', + area=(870, 637, 903, 663), + search=(850, 617, 923, 683), + color=(253, 217, 235), + button=(870, 637, 903, 663), + ), +) +CAFE_INVITED = ButtonWrapper( + name='CAFE_INVITED', + jp=Button( + file='./assets/jp/cafe/CAFE_INVITED.png', + area=(870, 637, 903, 663), + search=(850, 617, 923, 683), + color=(111, 109, 109), + button=(870, 637, 903, 663), + ), + en=Button( + file='./assets/en/cafe/CAFE_INVITED.png', + area=(870, 637, 903, 663), + search=(850, 617, 923, 683), + color=(111, 109, 109), + button=(870, 637, 903, 663), + ), + zht=Button( + file='./assets/zht/cafe/CAFE_INVITED.png', + area=(870, 637, 903, 663), + search=(850, 617, 923, 683), + color=(111, 109, 109), + button=(870, 637, 903, 663), ), ) CAFE_SECOND = ButtonWrapper( name='CAFE_SECOND', jp=Button( file='./assets/jp/cafe/CAFE_SECOND.png', - area=(219, 152, 279, 175), - search=(199, 132, 299, 195), - color=(110, 126, 146), - button=(219, 152, 279, 175), + area=(99, 92, 108, 110), + search=(79, 72, 128, 130), + color=(193, 200, 209), + button=(99, 92, 108, 110), ), en=Button( file='./assets/en/cafe/CAFE_SECOND.png', - area=(219, 152, 279, 175), - search=(199, 132, 299, 195), - color=(110, 126, 146), - button=(219, 152, 279, 175), + area=(207, 96, 214, 107), + search=(187, 76, 234, 127), + color=(174, 182, 192), + button=(207, 96, 214, 107), + ), + zht=Button( + file='./assets/zht/cafe/CAFE_SECOND.png', + area=(153, 93, 162, 110), + search=(133, 73, 182, 130), + color=(169, 178, 189), + button=(153, 93, 162, 110), ), ) -CHANGE_CAFE_NOT_SELECTED = ButtonWrapper( - name='CHANGE_CAFE_NOT_SELECTED', +CHECK_MOMOTALK = ButtonWrapper( + name='CHECK_MOMOTALK', jp=Button( - file='./assets/jp/cafe/CHANGE_CAFE_NOT_SELECTED.png', - area=(84, 89, 178, 109), - search=(64, 69, 198, 129), - color=(185, 193, 203), - button=(84, 89, 178, 109), + file='./assets/jp/cafe/CHECK_MOMOTALK.png', + area=(421, 83, 447, 108), + search=(401, 63, 467, 128), + color=(253, 203, 212), + button=(421, 83, 447, 108), ), en=Button( - file='./assets/en/cafe/CHANGE_CAFE_NOT_SELECTED.png', - area=(84, 89, 178, 109), - search=(64, 69, 198, 129), - color=(185, 193, 203), - button=(84, 89, 178, 109), + file='./assets/en/cafe/CHECK_MOMOTALK.png', + area=(421, 83, 447, 108), + search=(401, 63, 467, 128), + color=(253, 203, 212), + button=(421, 83, 447, 108), ), -) -CHANGE_CAFE_SELECTED = ButtonWrapper( - name='CHANGE_CAFE_SELECTED', - jp=Button( - file='./assets/jp/cafe/CHANGE_CAFE_SELECTED.png', - area=(40, 87, 191, 112), - search=(20, 67, 211, 132), - color=(82, 105, 130), - button=(40, 87, 191, 112), - ), - en=Button( - file='./assets/en/cafe/CHANGE_CAFE_SELECTED.png', - area=(40, 87, 191, 112), - search=(20, 67, 211, 132), - color=(82, 105, 130), - button=(40, 87, 191, 112), + zht=Button( + file='./assets/zht/cafe/CHECK_MOMOTALK.png', + area=(421, 83, 447, 108), + search=(401, 63, 467, 128), + color=(253, 203, 212), + button=(421, 83, 447, 108), ), ) CHECK_REWARD = ButtonWrapper( name='CHECK_REWARD', jp=Button( file='./assets/jp/cafe/CHECK_REWARD.png', - area=(1095, 621, 1146, 637), - search=(1075, 601, 1166, 657), - color=(82, 105, 129), - button=(1086, 607, 1225, 685), + area=(1120, 635, 1166, 651), + search=(1100, 615, 1186, 671), + color=(79, 102, 127), + button=(1109, 627, 1239, 691), ), en=Button( file='./assets/en/cafe/CHECK_REWARD.png', - area=(1090, 613, 1229, 644), - search=(1070, 593, 1249, 664), - color=(64, 88, 115), - button=(1086, 611, 1229, 685), + area=(1116, 636, 1218, 654), + search=(1096, 616, 1238, 674), + color=(77, 100, 125), + button=(1113, 625, 1239, 692), + ), + zht=Button( + file='./assets/zht/cafe/CHECK_REWARD.png', + area=(1125, 634, 1209, 651), + search=(1105, 614, 1229, 671), + color=(107, 128, 149), + button=(1113, 626, 1237, 692), ), ) CLICKABLE_TEMPLATE = ButtonWrapper( name='CLICKABLE_TEMPLATE', - jp=Button( - file='./assets/jp/cafe/CLICKABLE_TEMPLATE.png', - area=(619, 332, 660, 387), - search=(599, 312, 680, 407), - color=(79, 65, 1), - button=(619, 332, 660, 387), - ), - en=Button( - file='./assets/en/cafe/CLICKABLE_TEMPLATE.png', + share=Button( + file='./assets/share/cafe/CLICKABLE_TEMPLATE.png', area=(619, 332, 660, 387), search=(599, 312, 680, 407), color=(79, 65, 1), @@ -133,10 +188,17 @@ GET_REWARD = ButtonWrapper( ), en=Button( file='./assets/en/cafe/GET_REWARD.png', - area=(581, 503, 692, 536), - search=(561, 483, 712, 556), - color=(208, 190, 63), - button=(539, 491, 741, 555), + area=(593, 506, 687, 548), + search=(573, 486, 707, 568), + color=(211, 193, 64), + button=(543, 492, 736, 564), + ), + zht=Button( + file='./assets/zht/cafe/GET_REWARD.png', + area=(604, 508, 676, 546), + search=(584, 488, 696, 566), + color=(189, 169, 57), + button=(544, 493, 736, 564), ), ) GET_REWARD_CLOSE = ButtonWrapper( @@ -150,10 +212,17 @@ GET_REWARD_CLOSE = ButtonWrapper( ), en=Button( file='./assets/en/cafe/GET_REWARD_CLOSE.png', - area=(883, 134, 926, 178), - search=(863, 114, 946, 198), - color=(215, 220, 224), - button=(883, 134, 926, 178), + area=(968, 132, 997, 162), + search=(948, 112, 1017, 182), + color=(187, 193, 201), + button=(968, 132, 997, 162), + ), + zht=Button( + file='./assets/zht/cafe/GET_REWARD_CLOSE.png', + area=(969, 133, 997, 161), + search=(949, 113, 1017, 181), + color=(181, 188, 196), + button=(969, 133, 997, 161), ), ) GOT_REWARD = ButtonWrapper( @@ -167,10 +236,17 @@ GOT_REWARD = ButtonWrapper( ), en=Button( file='./assets/en/cafe/GOT_REWARD.png', - area=(543, 489, 741, 558), - search=(523, 469, 761, 578), - color=(211, 212, 212), - button=(543, 489, 741, 558), + area=(544, 493, 735, 563), + search=(524, 473, 755, 583), + color=(213, 213, 212), + button=(544, 493, 735, 563), + ), + zht=Button( + file='./assets/zht/cafe/GOT_REWARD.png', + area=(545, 494, 735, 563), + search=(525, 474, 755, 583), + color=(212, 212, 211), + button=(545, 494, 735, 563), ), ) INVENTORY = ButtonWrapper( @@ -189,6 +265,133 @@ INVENTORY = ButtonWrapper( color=(197, 204, 212), button=(1123, 90, 1165, 130), ), + zht=Button( + file='./assets/zht/cafe/INVENTORY.png', + area=(603, 87, 679, 128), + search=(583, 67, 699, 148), + color=(191, 199, 207), + button=(1123, 90, 1165, 130), + ), +) +INVITE_CONFIRM = ButtonWrapper( + name='INVITE_CONFIRM', + jp=Button( + file='./assets/jp/cafe/INVITE_CONFIRM.png', + area=(609, 147, 671, 178), + search=(589, 127, 691, 198), + color=(152, 164, 177), + button=(665, 471, 870, 533), + ), + en=Button( + file='./assets/en/cafe/INVITE_CONFIRM.png', + area=(592, 149, 689, 176), + search=(572, 129, 709, 196), + color=(169, 179, 191), + button=(664, 470, 872, 534), + ), + zht=Button( + file='./assets/zht/cafe/INVITE_CONFIRM.png', + area=(595, 142, 677, 181), + search=(575, 122, 697, 201), + color=(186, 195, 203), + button=(663, 474, 874, 532), + ), +) +INVITE_IN_SECOND = ButtonWrapper( + name='INVITE_IN_SECOND', + jp=Button( + file='./assets/jp/cafe/INVITE_IN_SECOND.png', + area=(482, 147, 799, 177), + search=(462, 127, 819, 197), + color=(166, 177, 188), + button=(482, 147, 799, 177), + ), + en=Button( + file='./assets/en/cafe/INVITE_IN_SECOND.png', + area=(482, 147, 799, 177), + search=(462, 127, 819, 197), + color=(166, 177, 188), + button=(482, 147, 799, 177), + ), + zht=Button( + file='./assets/zht/cafe/INVITE_IN_SECOND.png', + area=(487, 146, 793, 178), + search=(467, 126, 813, 198), + color=(153, 165, 178), + button=(487, 146, 793, 178), + ), +) +INVITE_IN_SECOND_CLOSE = ButtonWrapper( + name='INVITE_IN_SECOND_CLOSE', + jp=Button( + file='./assets/jp/cafe/INVITE_IN_SECOND_CLOSE.png', + area=(874, 150, 900, 176), + search=(854, 130, 920, 196), + color=(180, 189, 198), + button=(874, 150, 900, 176), + ), + en=Button( + file='./assets/en/cafe/INVITE_IN_SECOND_CLOSE.png', + area=(874, 150, 900, 176), + search=(854, 130, 920, 196), + color=(180, 189, 198), + button=(874, 150, 900, 176), + ), + zht=Button( + file='./assets/zht/cafe/INVITE_IN_SECOND_CLOSE.png', + area=(874, 150, 900, 176), + search=(854, 130, 920, 196), + color=(180, 189, 198), + button=(874, 150, 900, 176), + ), +) +INVITE_SUBSTITUTE = ButtonWrapper( + name='INVITE_SUBSTITUTE', + jp=Button( + file='./assets/jp/cafe/INVITE_SUBSTITUTE.png', + area=(582, 154, 698, 184), + search=(562, 134, 718, 204), + color=(154, 166, 179), + button=(673, 478, 858, 541), + ), + en=Button( + file='./assets/en/cafe/INVITE_SUBSTITUTE.png', + area=(506, 157, 775, 186), + search=(486, 137, 795, 206), + color=(176, 186, 196), + button=(673, 477, 857, 542), + ), + zht=Button( + file='./assets/zht/cafe/INVITE_SUBSTITUTE.png', + area=(580, 154, 701, 183), + search=(560, 134, 721, 203), + color=(147, 160, 174), + button=(672, 475, 857, 544), + ), +) +INVITE_SUBSTITUTE_CLOSE = ButtonWrapper( + name='INVITE_SUBSTITUTE_CLOSE', + jp=Button( + file='./assets/jp/cafe/INVITE_SUBSTITUTE_CLOSE.png', + area=(867, 158, 893, 184), + search=(847, 138, 913, 204), + color=(180, 189, 199), + button=(867, 158, 893, 184), + ), + en=Button( + file='./assets/en/cafe/INVITE_SUBSTITUTE_CLOSE.png', + area=(867, 158, 893, 184), + search=(847, 138, 913, 204), + color=(180, 189, 199), + button=(867, 158, 893, 184), + ), + zht=Button( + file='./assets/zht/cafe/INVITE_SUBSTITUTE_CLOSE.png', + area=(867, 158, 893, 184), + search=(847, 138, 913, 204), + color=(180, 189, 199), + button=(867, 158, 893, 184), + ), ) MOMOTALK_CLOSE = ButtonWrapper( name='MOMOTALK_CLOSE', @@ -206,22 +409,108 @@ MOMOTALK_CLOSE = ButtonWrapper( color=(252, 182, 194), button=(824, 82, 850, 108), ), + zht=Button( + file='./assets/zht/cafe/MOMOTALK_CLOSE.png', + area=(824, 82, 850, 108), + search=(804, 62, 870, 128), + color=(252, 182, 194), + button=(824, 82, 850, 108), + ), +) +MOMOTALK_INVITE = ButtonWrapper( + name='MOMOTALK_INVITE', + jp=Button( + file='./assets/jp/cafe/MOMOTALK_INVITE.png', + area=(764, 211, 809, 234), + search=(744, 191, 829, 254), + color=(90, 163, 195), + button=(764, 211, 809, 234), + ), + en=Button( + file='./assets/en/cafe/MOMOTALK_INVITE.png', + area=(755, 210, 817, 233), + search=(735, 190, 837, 253), + color=(98, 179, 211), + button=(755, 210, 817, 233), + ), + zht=Button( + file='./assets/zht/cafe/MOMOTALK_INVITE.png', + area=(757, 204, 818, 239), + search=(737, 184, 838, 259), + color=(103, 189, 222), + button=(757, 204, 818, 239), + ), +) +MOMOTALK_ITEM = ButtonWrapper( + name='MOMOTALK_ITEM', + jp=Button( + file='./assets/jp/cafe/MOMOTALK_ITEM.png', + area=(489, 193, 864, 259), + search=(469, 173, 884, 279), + color=(203, 230, 240), + button=(489, 193, 864, 259), + ), + en=Button( + file='./assets/en/cafe/MOMOTALK_ITEM.png', + area=(489, 193, 864, 259), + search=(469, 173, 884, 279), + color=(203, 230, 240), + button=(489, 193, 864, 259), + ), + zht=Button( + file='./assets/zht/cafe/MOMOTALK_ITEM.png', + area=(491, 186, 872, 264), + search=(471, 166, 892, 284), + color=(209, 232, 241), + button=(491, 186, 872, 264), + ), ) OCR_CAFE = ButtonWrapper( name='OCR_CAFE', jp=Button( file='./assets/jp/cafe/OCR_CAFE.png', - area=(1103, 642, 1202, 672), - search=(1083, 622, 1222, 692), - color=(87, 107, 129), - button=(1103, 642, 1202, 672), + area=(1127, 657, 1219, 682), + search=(1107, 637, 1239, 702), + color=(93, 112, 134), + button=(1127, 657, 1219, 682), ), en=Button( file='./assets/en/cafe/OCR_CAFE.png', - area=(1105, 639, 1195, 674), - search=(1085, 619, 1215, 694), - color=(84, 104, 127), - button=(1105, 639, 1195, 674), + area=(1127, 657, 1219, 682), + search=(1107, 637, 1239, 702), + color=(93, 112, 134), + button=(1127, 657, 1219, 682), + ), + zht=Button( + file='./assets/zht/cafe/OCR_CAFE.png', + area=(1127, 657, 1219, 682), + search=(1107, 637, 1239, 702), + color=(93, 112, 134), + button=(1127, 657, 1219, 682), + ), +) +OCR_NAME = ButtonWrapper( + name='OCR_NAME', + jp=Button( + file='./assets/jp/cafe/OCR_NAME.png', + area=(488, 194, 704, 588), + search=(468, 174, 724, 608), + color=(237, 239, 241), + button=(488, 194, 704, 588), + ), + en=Button( + file='./assets/en/cafe/OCR_NAME.png', + area=(488, 194, 704, 588), + search=(468, 174, 724, 608), + color=(237, 239, 241), + button=(488, 194, 704, 588), + ), + zht=Button( + file='./assets/zht/cafe/OCR_NAME.png', + area=(488, 194, 704, 588), + search=(468, 174, 724, 608), + color=(237, 239, 241), + button=(488, 194, 704, 588), ), ) STUDENT_LIST = ButtonWrapper( @@ -240,4 +529,11 @@ STUDENT_LIST = ButtonWrapper( color=(195, 202, 210), button=(548, 426, 739, 493), ), + zht=Button( + file='./assets/zht/cafe/STUDENT_LIST.png', + area=(600, 168, 677, 204), + search=(580, 148, 697, 224), + color=(178, 187, 197), + button=(540, 425, 742, 492), + ), ) diff --git a/tasks/cafe/cafe.py b/tasks/cafe/cafe.py index f6105fb..e5e9535 100644 --- a/tasks/cafe/cafe.py +++ b/tasks/cafe/cafe.py @@ -1,22 +1,12 @@ -import cv2 -import numpy as np from enum import Enum -from module.logger import logger from module.base.timer import Timer -from module.base.button import ClickButton -from module.base.decorator import Config -from module.base.utils.utils import area_offset -from module.ocr.ocr import Digit +from module.logger import logger from module.ui.switch import Switch from tasks.base.page import page_cafe -from tasks.base.ui import UI from tasks.cafe.assets.assets_cafe import * - - -SWITCH_CAFE = Switch('Cafe_switch') -SWITCH_CAFE.add_state('off', CHANGE_CAFE_NOT_SELECTED) -SWITCH_CAFE.add_state('on', CHANGE_CAFE_SELECTED) +from tasks.cafe.invitation import handle_invitation +from tasks.cafe.ui import CafeUI SWITCH_CAFE_SELECT = Switch('Cafe_switch_select') SWITCH_CAFE_SELECT.add_state('1', CAFE_FIRST) @@ -28,107 +18,16 @@ class CafeStatus(Enum): OCR = 1 REWARD = 2 GOT = 3 - CLICK = 4 - CHECK = 5 + INVITATION = 4 + CLICK = 5 + CHECK = 6 FINISHED = -1 -class Cafe(UI): - template = CLICKABLE_TEMPLATE - - @staticmethod - def merge_points(points, threshold=3): - if len(points) <= 1: - return points - result = [] - for point in points: - if not result: - result.append(point) - continue - if point[0] - result[-1][0] < threshold and point[1] - result[-1][1] < threshold: - result[-1] = ((point[0] + result[-1][0]) // 2, (point[1] + result[-1][1]) // 2) - continue - result.append(point) - return result - - @staticmethod - def _extract_clickable_from_image(image): - # convert to hsv for better color matching - hsv = cv2.cvtColor(image, cv2.COLOR_RGB2HSV) - # set color range - lower_hsv = np.array([18, 200, 220]) - upper_hsv = np.array([30, 255, 255]) - # get mask - mask = cv2.inRange(hsv, lower_hsv, upper_hsv) - # generate result - return cv2.bitwise_and(image, image, mask=mask) - - def _match_clickable_points(self, image, threshold=0.8): - image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY) - template = cv2.cvtColor(self.template.matched_button.image, cv2.COLOR_RGB2GRAY) - - res = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED) - loc = np.where(res >= threshold) - return [point for point in zip(*loc[::-1])] - - def _get_clickable_buttons(self, threshold=0.8, offset=(0, 0)): - image = self.device.image - h, w = image.shape[:2] - image = cv2.rectangle(image, (0, 10), (w - 25, h - 10), (0, 0, 0), 50) - image = self._extract_clickable_from_image(image) - points = self._match_clickable_points(image, threshold) - points = self.merge_points(points) - if not points: - return [] - area = area_offset((0, 0, self.template.width, self.template.height), offset) - return [ - ClickButton( - button=area_offset(area, offset=point), - name=self.template.name - ) - for point in points - ] - - def _reset_cafe_position(self, direction: str): - width = BOX_CAFE.width - height = BOX_CAFE.height - r = np.random.uniform(0.6, 0.8) - vector_down = (width * r, height * r) - vector_up = (width * r, -height * r) - vector_left = (-width * r, 0) - vector_right = (width * r, 0) - random_r = (-5, -5, 5, 5) - match direction: - case 'init': - self.device.pinch() - self.device.swipe_vector(vector_down, box=BOX_CAFE.area, random_range=random_r, padding=5) - self.device.swipe_vector(vector_right, box=BOX_CAFE.area, random_range=random_r, padding=5) - self.device.swipe_vector(vector_up, box=BOX_CAFE.area, random_range=random_r, padding=5) - self.device.swipe_vector(vector_up, box=BOX_CAFE.area, random_range=random_r, padding=5) - case 'left': - self.device.swipe_vector(vector_left, box=BOX_CAFE.area, random_range=random_r, padding=5) - self.device.swipe_vector(vector_left, box=BOX_CAFE.area, random_range=random_r, padding=5) - case 'right': - self.device.swipe_vector(vector_right, box=BOX_CAFE.area, random_range=random_r, padding=5) - self.device.swipe_vector(vector_right, box=BOX_CAFE.area, random_range=random_r, padding=5) - # solve too much swipe causing restart - self.device.click_record_clear() - - def _get_reward_num(self): - ocr = Digit(OCR_CAFE) - num = ocr.detect_and_ocr(self.device.image) - if len(num) != 1: - logger.warning(f'Invalid reward num: {num}') - num = float(num[0].ocr_text.rstrip('%')) - logger.attr('Reward', num) - return num - - def _cafe_additional(self) -> bool: - if self.appear_then_click(INVENTORY): - return True - if self.appear_then_click(MOMOTALK_CLOSE): - return True - return False +class Cafe(CafeUI): + @property + def is_second_cafe_on(self): + return self.config.Cafe_SecondCafe def _handle_cafe(self, status): match status: @@ -137,7 +36,7 @@ class Cafe(UI): if not self.appear(STUDENT_LIST): return CafeStatus.OCR case CafeStatus.OCR: - reward = self._get_reward_num() + reward = self.get_reward_num() if reward == 0: return CafeStatus.GOT if self.appear_then_click(CHECK_REWARD): @@ -153,18 +52,24 @@ class Cafe(UI): self.click_with_interval(GET_REWARD) case CafeStatus.GOT: logger.info('Cafe reward have been got') - self.appear_then_click(GET_REWARD_CLOSE) - if not self.appear(GET_REWARD_CLOSE): + if self.appear(GET_REWARD_CLOSE): + self.click_with_interval(GET_REWARD_CLOSE, 1) + return status + return CafeStatus.INVITATION + case CafeStatus.INVITATION: + if handle_invitation(self): return CafeStatus.CLICK case CafeStatus.CLICK: - buttons = self._get_clickable_buttons(offset=(45, 10)) + buttons = self.get_clickable_buttons() self.click = len(buttons) logger.attr('Clickable', self.click) if not buttons: return CafeStatus.CHECK self.click_with_interval(buttons[0], interval=1) case CafeStatus.CHECK: - buttons = self._get_clickable_buttons() + if self.appear_then_click(GET_REWARD_CLOSE): + return status + buttons = self.get_clickable_buttons() if not self.is_adjust_on: if not buttons: return CafeStatus.FINISHED @@ -177,11 +82,11 @@ class Cafe(UI): return CafeStatus.CLICK match self.check: case 1: - self._reset_cafe_position('left') + self.reset_cafe_position('left') case 2: - self._reset_cafe_position('right') + self.reset_cafe_position('right') case 3: - self._reset_cafe_position('init') + self.reset_cafe_position('init') case 4: return CafeStatus.FINISHED case CafeStatus.FINISHED: @@ -190,16 +95,6 @@ class Cafe(UI): logger.warning(f'Invalid status: {status}') return status - @Config.when(Emulator_GameLanguage='jp') - def is_second_cafe_on(self): - return self.config.Cafe_SecondCafe - - @Config.when(Emulator_GameLanguage=None) - def is_second_cafe_on(self): - return False - - is_second_cafe_on = property(is_second_cafe_on) - def run(self): self.click = 0 self.check = 0 @@ -212,8 +107,7 @@ class Cafe(UI): status = CafeStatus.STUDENT_LIST loading_timer = Timer(2).start() - action_timer = Timer(1, count=1) # cant be too fast - is_list = False + action_timer = Timer(1, count=1) is_reset = False is_second = False is_enable = is_reward_on or is_touch_on @@ -224,17 +118,12 @@ class Cafe(UI): self.device.screenshot() - if self.ui_additional() or self._cafe_additional(): + if self.ui_additional() or self.cafe_additional(): continue if not loading_timer.reached(): continue - if not is_list and status == CafeStatus.STUDENT_LIST and self.appear(STUDENT_LIST): - is_list = True - loading_timer = Timer(3).start() - continue - if not is_reward_on and status == CafeStatus.OCR: logger.info('Skip reward') status = CafeStatus.CLICK @@ -246,31 +135,24 @@ class Cafe(UI): continue if is_touch_on and not is_reset and status == CafeStatus.CLICK: - self._reset_cafe_position('init') + self.reset_cafe_position('init') is_reset = True continue if self.is_second_cafe_on and not is_second and status == CafeStatus.FINISHED: - if not SWITCH_CAFE.appear(main=self): - logger.warning('Cafe switch not found') - continue - if SWITCH_CAFE.get(main=self) == 'off': - SWITCH_CAFE.set('on', main=self) - logger.info('Switching to second cafe') if not SWITCH_CAFE_SELECT.appear(main=self): - logger.info('Cafe switch select not found') + logger.info('Cafe switch not found') continue - match (SWITCH_CAFE_SELECT.get(main=self)): + match SWITCH_CAFE_SELECT.get(main=self): case '1': - if self.click_with_interval(CAFE_SECOND): + if self.click_with_interval(CAFE_FIRST): continue case '2': logger.info('Cafe second arrived') - SWITCH_CAFE.set('off', main=self) status = CafeStatus.STUDENT_LIST - is_list = False is_second = True self.check = 0 + loading_timer.reset().start() if action_timer.reached_and_reset(): logger.attr('Status', status) diff --git a/tasks/cafe/invitation.py b/tasks/cafe/invitation.py new file mode 100644 index 0000000..2e9af7a --- /dev/null +++ b/tasks/cafe/invitation.py @@ -0,0 +1,314 @@ +import re +from datetime import datetime, timedelta +from enum import Enum + +import numpy as np + +from module.base.base import ModuleBase +from module.base.timer import Timer +from module.base.utils import area_offset, area_size +from module.config.utils import get_server_next_update +from module.logger import logger +from module.ocr.ocr import Ocr +from tasks.cafe.assets.assets_cafe import * + + +class InvitationOcr(Ocr): + def after_process(self, result): + result = super().after_process(result) + result = result.replace('モI', 'モエ') + return result + + +class InvitationStatus(Enum): + MOMOTALK = 0 + OCR = 1 + SELECT = 2 + CONFIRM = 3 + SUBSTITUTE = 4 + IN_SECOND = 5 + INVITED = 6 + FAILED = 7 + FINISHED = -1 + + +class Invitation: + swipe_vector_range = (0.65, 0.85) + cafe_update = ["04:00", "16:00"] + + def __init__(self, name: str): + self.name = name + self.ocr = InvitationOcr(OCR_NAME) + self.list = OCR_NAME + self.item = MOMOTALK_ITEM + self.invite = MOMOTALK_INVITE + + self.target_names = [] + self.waiting_hour = None + self.substitute = None + self.choice = None + + self.invited = [] + self.current_names = [] + + def __str__(self): + return f'Invitation({self.name})' + + __repr__ = __str__ + + def __eq__(self, other): + return str(self) == str(other) + + def __hash__(self): + return hash(self.name) + + def swipe_page(self, main: ModuleBase, vector_range=None, reverse=False): + """ + Args: + 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.list.button) + vector = (0, -vector * height) + + if reverse: + vector = (-vector[0], -vector[1]) + name = f'{self.name}_SWIPE' + main.device.swipe_vector(vector, self.list.button, name=name) + main.device.click_record_remove(name) + + def load_names(self, main: ModuleBase): + names = self.ocr.detect_and_ocr(main.device.image) + if not names: + logger.warning(f'No valid names in {self.ocr.name}') + return + self.current_names = [] + for name_ in names: + name: str = name_.ocr_text.replace(' ', '') + if name.isdigit(): + continue + if name.startswith('('): + if not self.current_names: + continue + n = self.current_names.pop(len(self.current_names) - 1) + self.current_names.append((n[0] + name, n[1])) + continue + self.current_names.append((name, name_.box)) + + @property + def is_invitation(self) -> bool: + return get_server_next_update(self.cafe_update) - datetime.now() > timedelta(hours=self.waiting_hour) + + @property + def names(self): + return list(map(lambda x: x[0], self.current_names)) + + @property + def target_name(self): + return self.target_names[0] if self.target_names else None + + def on_success(self): + logger.info(f'Invited {self.target_name}') + self.invited.append(self.target_name) + self.target_names.pop(0) + + def on_failed(self): + logger.warning(f'Failed to invite {self.target_name}') + self.target_names.pop(0) + + def insight_name(self, name: str, main: ModuleBase, skip_first_screenshot=True) -> bool: + """ + Args: + name: + main: + skip_first_screenshot: + """ + logger.info(f'Insight name: {name}') + last_names: set[str] = set() + + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + main.device.screenshot() + + self.load_names(main) + + if name in self.names: + return True + + names = self.names + if names and last_names == set(names): + logger.warning(f'Name not found: {name}') + return False + last_names = set(names) + + self.swipe_page(main) + + main.wait_until_stable( + self.list.button, + timer=Timer(0, 0), + timeout=Timer(1.5, 5) + ) + + def select_name_invite( + self, + main: ModuleBase, + name: str = None, + insight: bool = True, + skip_first_screenshot: bool = True + ) -> bool: + """ + Args: + main: + name: + insight: + skip_first_screenshot: + + Returns: + If success + """ + if name is None: + if not self.target_name: + return False + name = self.target_name + if insight and not self.insight_name(name, main, skip_first_screenshot): + return False + + logger.info(f'Select name: {name}') + click_interval = Timer(1, 2) + load_names_interval = Timer(1, 5) + timeout = Timer(10, 10).start() + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + main.device.screenshot() + + if load_names_interval.reached_and_reset() and not insight: + self.load_names(main) + + name_box = next(filter(lambda x: x[0] == name, self.current_names), None) + if name_box is None: + logger.warning(f'No name {name} in {self.ocr.name}') + continue + + search_box = area_offset((0, 0, *area_size(self.item.area)), name_box[1][:2]) + self.invite.load_search(search_box) + click_button = self.invite.match_multi_template(main.device.image) + + if not click_button: + logger.warning(f'No clickable {self.invite.name}') + continue + + if click_interval.reached_and_reset(): + main.device.click(click_button[0]) + return True + + if timeout.reached(): + logger.warning(f'No clickable {self.invite.name}') + return False + + +invitation = Invitation('CafeInvitation') + + +def handle_invitation_status(status: InvitationStatus, main: ModuleBase) -> InvitationStatus: + match status: + case InvitationStatus.MOMOTALK: + if not invitation.is_invitation: + logger.info('Invitation waiting until next refresh') + return InvitationStatus.FINISHED + if main.match_color(CAFE_INVITED): + logger.info('Invitation in cooldown') + return InvitationStatus.FINISHED + if invitation.choice != 'list_top' and invitation.target_name is None: + logger.warning('No student to be invited or all invitations failed') + return InvitationStatus.FINISHED + if main.appear(CHECK_MOMOTALK): + return InvitationStatus.OCR + main.appear_then_click(CAFE_INVITE) + case InvitationStatus.OCR: + if invitation.choice == 'list_top': + invitation.load_names(main) + invitation.target_names = invitation.names + invitation.choice = 'list' + if invitation.select_name_invite(main): + return InvitationStatus.SELECT + return InvitationStatus.FAILED + case InvitationStatus.SELECT: + if main.appear(INVITE_CONFIRM): + return InvitationStatus.CONFIRM + if main.appear(INVITE_IN_SECOND): + return InvitationStatus.IN_SECOND + if main.appear(INVITE_SUBSTITUTE): + return InvitationStatus.SUBSTITUTE + case InvitationStatus.CONFIRM: + main.appear_then_click(INVITE_CONFIRM) + if not main.appear(INVITE_CONFIRM): + invitation.on_success() + return InvitationStatus.INVITED + case InvitationStatus.IN_SECOND: + main.appear_then_click(INVITE_IN_SECOND_CLOSE) + return InvitationStatus.FAILED + case InvitationStatus.SUBSTITUTE: + if not invitation.substitute: + main.appear_then_click(INVITE_SUBSTITUTE_CLOSE) + return InvitationStatus.FAILED + else: + main.appear_then_click(INVITE_SUBSTITUTE) + if not main.appear(INVITE_SUBSTITUTE): + return InvitationStatus.INVITED + case InvitationStatus.INVITED: + main.appear_then_click(MOMOTALK_CLOSE) + if not main.appear(MOMOTALK_CLOSE): + return InvitationStatus.FINISHED + case InvitationStatus.FAILED: + main.appear_then_click(MOMOTALK_CLOSE) + if not main.appear(CHECK_MOMOTALK): + invitation.on_failed() + return InvitationStatus.MOMOTALK + case InvitationStatus.FINISHED: + pass + case _: + logger.warning(f'Invalid status: {status}') + return status + + +def handle_invitation(main: ModuleBase): + if not main.config.Invitation_Enable: + logger.info('Invitation disabled') + return True + invitation.waiting_hour = main.config.Invitation_WaitingHour + invitation.substitute = main.config.Invitation_Substitute + if invitation.choice is None: + invitation.choice = main.config.Invitation_Choice + if invitation.choice == 'by_name' and not invitation.target_names: + name = main.config.Invitation_Name + if name is None: + logger.warning('Choose By Name but Inviting Student Name is blank') + return True + name = re.sub(r'[ \t\r\n]', '', name) + name = re.sub(r'[>﹥›˃ᐳ❯]', '>', name) + name = re.sub(r'(', '(', name) + name = re.sub(r')', ')', name) + invitation.target_names = name.split('>') + status = InvitationStatus.MOMOTALK + action_timer = Timer(1, 1) + loading_timer = Timer(1, 1) + while 1: + main.device.screenshot() + + if not loading_timer.reached(): + continue + + if action_timer.reached_and_reset(): + logger.attr('Status', status) + status = handle_invitation_status(status, main) + + if status == InvitationStatus.FINISHED: + return True diff --git a/tasks/cafe/ui.py b/tasks/cafe/ui.py new file mode 100644 index 0000000..430ef23 --- /dev/null +++ b/tasks/cafe/ui.py @@ -0,0 +1,73 @@ +import cv2 +import numpy as np + +from module.logger import logger +from module.ocr.ocr import Digit +from tasks.base.ui import UI +from tasks.cafe.assets.assets_cafe import * + + +class CafeUI(UI): + template = CLICKABLE_TEMPLATE + + def get_reward_num(self): + ocr = Digit(OCR_CAFE) + num = ocr.detect_and_ocr(self.device.image) + if len(num) != 1: + logger.warning(f'Invalid reward num: {num}') + num = float(num[0].ocr_text.rstrip('%')) + logger.attr('Reward', num) + return num + + @staticmethod + def extract_clickable_from_image(image): + # convert to hsv for better color matching + hsv = cv2.cvtColor(image, cv2.COLOR_RGB2HSV) + # set color range + lower_hsv = np.array([18, 200, 220]) + upper_hsv = np.array([30, 255, 255]) + # get mask + mask = cv2.inRange(hsv, lower_hsv, upper_hsv) + # generate result + return cv2.bitwise_and(image, image, mask=mask) + + def get_clickable_buttons(self, similarity=0.8, offset=(40, 10)): + image = self.extract_clickable_from_image(self.device.image) + self.template.matched_button._button_offset = offset + self.template.load_offset(self.template) + self.template.load_search(BOX_SEARCH.area) + points = self.template.match_multi_template(image, similarity) + return points + + def reset_cafe_position(self, direction: str): + width = BOX_CAFE.width + height = BOX_CAFE.height + r = np.random.uniform(0.6, 0.8) + vector_down = (width * r, height * r) + vector_up = (width * r, -height * r) + vector_left = (-width * r, 0) + vector_right = (width * r, 0) + random_r = (-5, -5, 5, 5) + name = 'CAFE_SWIPE' + match direction: + case 'init': + self.device.pinch() + self.device.swipe_vector(vector_down, box=BOX_CAFE.area, random_range=random_r, padding=5, name=name) + self.device.swipe_vector(vector_right, box=BOX_CAFE.area, random_range=random_r, padding=5, name=name) + self.device.swipe_vector(vector_up, box=BOX_CAFE.area, random_range=random_r, padding=5, name=name) + self.device.swipe_vector(vector_up, box=BOX_CAFE.area, random_range=random_r, padding=5, name=name) + case 'left': + self.device.swipe_vector(vector_left, box=BOX_CAFE.area, random_range=random_r, padding=5, name=name) + self.device.swipe_vector(vector_left, box=BOX_CAFE.area, random_range=random_r, padding=5, name=name) + case 'right': + self.device.swipe_vector(vector_right, box=BOX_CAFE.area, random_range=random_r, padding=5, name=name) + self.device.swipe_vector(vector_right, box=BOX_CAFE.area, random_range=random_r, padding=5, name=name) + # solve too much swipe causing restart + self.device.click_record_remove(name) + + def cafe_additional(self) -> bool: + if self.appear_then_click(INVENTORY): + return True + if self.appear_then_click(MOMOTALK_CLOSE): + return True + return False diff --git a/tasks/circle/assets/assets_circle.py b/tasks/circle/assets/assets_circle.py index 2a5f4e3..9cbf9dd 100644 --- a/tasks/circle/assets/assets_circle.py +++ b/tasks/circle/assets/assets_circle.py @@ -3,6 +3,30 @@ 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 ``` +CIRCLE = ButtonWrapper( + name='CIRCLE', + jp=Button( + file='./assets/jp/circle/CIRCLE.png', + area=(191, 314, 233, 356), + search=(171, 294, 253, 376), + color=(191, 224, 240), + button=(188, 299, 435, 456), + ), + en=Button( + file='./assets/en/circle/CIRCLE.png', + area=(196, 314, 235, 355), + search=(176, 294, 255, 375), + color=(183, 221, 238), + button=(191, 298, 434, 457), + ), + zht=Button( + file='./assets/zht/circle/CIRCLE.png', + area=(196, 314, 235, 355), + search=(176, 294, 255, 375), + color=(183, 221, 238), + button=(190, 296, 433, 457), + ), +) GET_REWARD_AP = ButtonWrapper( name='GET_REWARD_AP', jp=Button( @@ -19,4 +43,11 @@ GET_REWARD_AP = ButtonWrapper( color=(204, 212, 219), button=(545, 460, 741, 521), ), + zht=Button( + file='./assets/zht/circle/GET_REWARD_AP.png', + area=(539, 145, 740, 175), + search=(519, 125, 760, 195), + color=(160, 172, 184), + button=(540, 456, 741, 521), + ), ) diff --git a/tasks/circle/circle.py b/tasks/circle/circle.py index 5e7caf4..6fee4e3 100644 --- a/tasks/circle/circle.py +++ b/tasks/circle/circle.py @@ -2,7 +2,7 @@ from enum import Enum from module.base.timer import Timer from module.logger import logger -from tasks.base.page import page_circle +from tasks.base.page import CIRCLE_CHECK, MAIN_GO_TO_CIRCLE from tasks.base.ui import UI from tasks.circle.assets.assets_circle import * @@ -17,6 +17,19 @@ class CircleStatus(Enum): class Circle(UI): + def _enter_circle(self): + self.ui_goto_main() + action_timer = Timer(1, 8) + while not self.appear(CIRCLE_CHECK): + self.device.screenshot() + if not action_timer.reached_and_reset(): + continue + if self.appear(CIRCLE): + self.click_with_interval(CIRCLE, 3) + continue + else: + self.appear_then_click(MAIN_GO_TO_CIRCLE, 3) + def _handle_circle(self, status): match status: case CircleStatus.REWARD: @@ -31,7 +44,7 @@ class Circle(UI): return status def run(self): - self.ui_ensure(page_circle) + self._enter_circle() status = CircleStatus.REWARD action_timer = Timer(0.5) diff --git a/tasks/item/assets/assets_item_data.py b/tasks/item/assets/assets_item_data.py index 6860b3d..4314b44 100644 --- a/tasks/item/assets/assets_item_data.py +++ b/tasks/item/assets/assets_item_data.py @@ -5,21 +5,35 @@ from module.base.button import Button, ButtonWrapper OCR_AP = ButtonWrapper( name='OCR_AP', + jp=Button( + file='./assets/jp/item/data/OCR_AP.png', + area=(512, 13, 597, 32), + search=(492, 0, 617, 52), + color=(199, 204, 208), + button=(512, 13, 597, 32), + ), share=Button( file='./assets/share/item/data/OCR_AP.png', - area=(560, 11, 667, 37), - search=(540, 0, 687, 57), - color=(211, 216, 219), - button=(560, 11, 667, 37), + area=(535, 13, 660, 36), + search=(515, 0, 680, 56), + color=(207, 215, 220), + button=(535, 13, 660, 36), ), ) OCR_DATA = ButtonWrapper( name='OCR_DATA', + jp=Button( + file='./assets/jp/item/data/OCR_DATA.png', + area=(716, 14, 1058, 33), + search=(696, 0, 1078, 53), + color=(211, 219, 224), + button=(716, 14, 1058, 33), + ), share=Button( file='./assets/share/item/data/OCR_DATA.png', - area=(768, 12, 1072, 37), - search=(748, 0, 1092, 57), - color=(212, 220, 224), - button=(768, 12, 1072, 37), + area=(745, 11, 1069, 39), + search=(725, 0, 1089, 59), + color=(216, 223, 227), + button=(745, 11, 1069, 39), ), ) diff --git a/tasks/item/data_update.py b/tasks/item/data_update.py index 1d1a482..f6c8692 100644 --- a/tasks/item/data_update.py +++ b/tasks/item/data_update.py @@ -12,7 +12,10 @@ class DataUpdate(UI): Page: in: page_work """ - ap = DigitCounter(OCR_AP).ocr_single_line(self.device.image) + ap, _, ap_total = DigitCounter(OCR_AP).ocr_single_line(self.device.image) + if ap_total == 0: + logger.warning('Invalid AP') + return False # Data for Credit and Pyroxene ocr = Digit(OCR_DATA) timeout = Timer(2, count=6).start() @@ -21,27 +24,28 @@ class DataUpdate(UI): if len(data) != 2: data = [data[0], data[-1]] logger.attr('Data', data) - credit, pyroxene = [int(''.join([v for v in d.ocr_text if v.isdigit()])) for d in data] + credit, pyroxene = [int(''.join(filter(lambda x: x.isdigit(), d.ocr_text))) for d in data] if credit > 0 or pyroxene > 0: break logger.warning(f'Invalid credit and pyroxene: {data}') if timeout.reached(): logger.warning('Get data timeout') - break + return False logger.attr('Credit', credit) logger.attr('Pyroxene', pyroxene) with self.config.multi_set(): - self.config.stored.AP.set(ap[0], ap[2]) + self.config.stored.AP.set(ap, ap_total) self.config.stored.Credit.value = credit self.config.stored.Pyroxene.value = pyroxene - return ap, credit, pyroxene + return True def run(self): self.ui_ensure(page_work, acquire_lang_checked=False) - with self.config.multi_set(): - self._get_data() + if self._get_data(): self.config.task_delay(server_update=True) + else: + self.config.task_delay(minute=1) diff --git a/tasks/login/assets/assets_login.py b/tasks/login/assets/assets_login.py index 878bfb0..3c14588 100644 --- a/tasks/login/assets/assets_login.py +++ b/tasks/login/assets/assets_login.py @@ -19,6 +19,13 @@ LOGIN_CONFIRM = ButtonWrapper( color=(216, 220, 225), button=(990, 354, 1232, 506), ), + zht=Button( + file='./assets/zht/login/LOGIN_CONFIRM.png', + area=(35, 622, 96, 654), + search=(15, 602, 116, 674), + color=(212, 216, 221), + button=(990, 354, 1232, 506), + ), ) LOGIN_LOADING = ButtonWrapper( name='LOGIN_LOADING', @@ -36,12 +43,44 @@ LOGIN_LOADING = ButtonWrapper( color=(8, 66, 96), button=(34, 682, 60, 707), ), + zht=Button( + file='./assets/zht/login/LOGIN_LOADING.png', + area=(34, 682, 60, 707), + search=(14, 662, 80, 720), + color=(8, 66, 96), + button=(34, 682, 60, 707), + ), +) +OCR_YEAR = ButtonWrapper( + name='OCR_YEAR', + jp=Button( + file='./assets/jp/login/OCR_YEAR.png', + area=(377, 689, 414, 701), + search=(357, 669, 434, 720), + color=(160, 157, 158), + button=(377, 689, 414, 701), + ), + en=Button( + file='./assets/en/login/OCR_YEAR.png', + area=(377, 689, 414, 701), + search=(357, 669, 434, 720), + color=(160, 157, 158), + button=(377, 689, 414, 701), + ), + zht=Button( + file='./assets/zht/login/OCR_YEAR.png', + area=(377, 689, 414, 701), + search=(357, 669, 434, 720), + color=(160, 157, 158), + button=(377, 689, 414, 701), + ), ) SURVEY = ButtonWrapper( name='SURVEY', jp=None, - en=Button( - file='./assets/en/login/SURVEY.png', + en=None, + zht=Button( + file='./assets/zht/login/SURVEY.png', area=(946, 269, 994, 307), search=(926, 249, 1014, 327), color=(211, 223, 166), @@ -64,4 +103,11 @@ UPDATE = ButtonWrapper( color=(216, 216, 216), button=(665, 472, 872, 531), ), + zht=Button( + file='./assets/zht/login/UPDATE.png', + area=(446, 258, 686, 301), + search=(426, 238, 706, 321), + color=(216, 216, 216), + button=(665, 472, 872, 531), + ), ) diff --git a/tasks/login/login.py b/tasks/login/login.py index a518cf3..ae37ba9 100644 --- a/tasks/login/login.py +++ b/tasks/login/login.py @@ -1,10 +1,9 @@ from module.base.timer import Timer -from module.base.decorator import Config from module.exception import GameNotRunningError from module.logger import logger from tasks.base.page import page_main from tasks.base.ui import UI -from tasks.login.assets.assets_login import LOGIN_CONFIRM, LOGIN_LOADING, UPDATE, SURVEY +from tasks.login.assets.assets_login import LOGIN_CONFIRM, LOGIN_LOADING, UPDATE class Login(UI): @@ -19,6 +18,7 @@ class Login(UI): GameTooManyClickError: GameNotRunningError: """ + def _page_main_twice_confirm(): if self.ui_page_appear(page_main): timer = Timer(1).start() @@ -30,20 +30,11 @@ class Login(UI): return True return False - @Config.when(Emulator_GameLanguage='en') - def _handle_survey(self): - if self.appear_then_click(SURVEY): - return True - return False - - @Config.when(Emulator_GameLanguage=None) - def _handle_survey(self): - pass - logger.hr('App login') orientation_timer = Timer(5) startup_timer = Timer(5).start() app_timer = Timer(5).start() + back_timer = Timer(2).start() login_success = False while 1: @@ -72,10 +63,11 @@ class Login(UI): break # Watch resource downloading and loading + loading = False if self.match_color(LOGIN_LOADING, interval=5, threshold=45): logger.info('Game resources downloading or loading') self.device.stuck_record_clear() - + loading = True # Login if self.appear_then_click(LOGIN_CONFIRM): login_success = True @@ -89,10 +81,10 @@ class Login(UI): # continue if self.appear_then_click(UPDATE): continue - if _handle_survey(self): - continue if self.ui_additional(): continue + if not loading and back_timer.reached_and_reset() and not self.appear_trademark_year(): + self.device.back() return True diff --git a/tasks/mail/assets/assets_mail.py b/tasks/mail/assets/assets_mail.py index 26049be..234af31 100644 --- a/tasks/mail/assets/assets_mail.py +++ b/tasks/mail/assets/assets_mail.py @@ -19,6 +19,13 @@ MAIL_RECEIVE = ButtonWrapper( color=(207, 188, 63), button=(1047, 643, 1231, 700), ), + zht=Button( + file='./assets/zht/mail/MAIL_RECEIVE.png', + area=(1045, 641, 1229, 696), + search=(1025, 621, 1249, 716), + color=(231, 214, 68), + button=(1045, 641, 1229, 696), + ), ) MAIL_RECEIVED = ButtonWrapper( name='MAIL_RECEIVED', @@ -36,4 +43,11 @@ MAIL_RECEIVED = ButtonWrapper( color=(204, 205, 206), button=(1072, 649, 1197, 686), ), + zht=Button( + file='./assets/zht/mail/MAIL_RECEIVED.png', + area=(1042, 643, 1230, 704), + search=(1022, 623, 1250, 720), + color=(209, 210, 211), + button=(1042, 643, 1230, 704), + ), ) diff --git a/tasks/mission/assets/assets_mission.py b/tasks/mission/assets/assets_mission.py new file mode 100644 index 0000000..1edb15a --- /dev/null +++ b/tasks/mission/assets/assets_mission.py @@ -0,0 +1,461 @@ +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_COMMISSIONS = ButtonWrapper( + name='CHECK_COMMISSIONS', + jp=Button( + file='./assets/jp/mission/CHECK_COMMISSIONS.png', + area=(654, 93, 881, 128), + search=(634, 73, 901, 148), + color=(108, 129, 151), + button=(654, 93, 881, 128), + ), + 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), + ), + zht=Button( + file='./assets/zht/mission/CHECK_COMMISSIONS.png', + area=(650, 83, 886, 136), + search=(630, 63, 906, 156), + color=(78, 104, 131), + button=(650, 83, 886, 136), + ), +) +CHECK_CR = ButtonWrapper( + name='CHECK_CR', + jp=Button( + file='./assets/jp/mission/CHECK_CR.png', + area=(108, 147, 313, 180), + search=(88, 127, 333, 200), + color=(195, 201, 205), + button=(108, 147, 313, 180), + ), + en=Button( + file='./assets/en/mission/CHECK_CR.png', + area=(97, 137, 340, 191), + search=(77, 117, 360, 211), + color=(213, 220, 223), + button=(97, 137, 340, 191), + ), + zht=Button( + file='./assets/zht/mission/CHECK_CR.png', + area=(93, 143, 304, 183), + search=(73, 123, 324, 203), + color=(188, 194, 198), + button=(93, 143, 304, 183), + ), +) +CHECK_MISSION_SWEEP = ButtonWrapper( + name='CHECK_MISSION_SWEEP', + jp=Button( + file='./assets/jp/mission/CHECK_MISSION_SWEEP.png', + area=(637, 188, 722, 213), + search=(617, 168, 742, 233), + color=(169, 180, 193), + button=(637, 188, 722, 213), + ), + 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), + ), + zht=Button( + file='./assets/zht/mission/CHECK_MISSION_SWEEP.png', + area=(656, 187, 698, 207), + search=(636, 167, 718, 227), + color=(201, 207, 214), + button=(656, 187, 698, 207), + ), +) +CHECK_XP = ButtonWrapper( + name='CHECK_XP', + jp=Button( + file='./assets/jp/mission/CHECK_XP.png', + area=(106, 147, 225, 180), + search=(86, 127, 245, 200), + color=(167, 172, 176), + button=(106, 147, 225, 180), + ), + en=Button( + file='./assets/en/mission/CHECK_XP.png', + area=(94, 135, 325, 194), + search=(74, 115, 345, 214), + color=(208, 215, 220), + button=(94, 135, 325, 194), + ), + zht=Button( + file='./assets/zht/mission/CHECK_XP.png', + area=(98, 141, 240, 183), + search=(78, 121, 260, 203), + color=(188, 194, 197), + button=(98, 141, 240, 183), + ), +) +EVENT_INDEX = ButtonWrapper( + name='EVENT_INDEX', + jp=Button( + file='./assets/jp/mission/EVENT_INDEX.png', + area=(704, 135, 761, 694), + search=(684, 115, 781, 714), + color=(189, 197, 195), + button=(704, 135, 761, 694), + ), + en=Button( + file='./assets/en/mission/EVENT_INDEX.png', + area=(704, 135, 761, 694), + search=(684, 115, 781, 714), + color=(189, 197, 195), + button=(704, 135, 761, 694), + ), + zht=Button( + file='./assets/zht/mission/EVENT_INDEX.png', + area=(704, 135, 761, 694), + search=(684, 115, 781, 714), + color=(189, 197, 195), + button=(704, 135, 761, 694), + ), +) +EVENT_ITEM = ButtonWrapper( + name='EVENT_ITEM', + jp=Button( + file='./assets/jp/mission/EVENT_ITEM.png', + area=(691, 136, 1198, 232), + search=(671, 116, 1218, 252), + color=(201, 219, 224), + button=(691, 136, 1198, 232), + ), + en=Button( + file='./assets/en/mission/EVENT_ITEM.png', + area=(691, 136, 1198, 232), + search=(671, 116, 1218, 252), + color=(201, 219, 224), + button=(691, 136, 1198, 232), + ), + zht=Button( + file='./assets/zht/mission/EVENT_ITEM.png', + area=(691, 136, 1198, 232), + search=(671, 116, 1218, 252), + color=(201, 219, 224), + button=(691, 136, 1198, 232), + ), +) +EVENT_LIST = ButtonWrapper( + name='EVENT_LIST', + jp=Button( + file='./assets/jp/mission/EVENT_LIST.png', + area=(695, 137, 1196, 688), + search=(675, 117, 1216, 708), + color=(171, 185, 190), + button=(695, 137, 1196, 688), + ), + en=Button( + file='./assets/en/mission/EVENT_LIST.png', + area=(695, 137, 1196, 688), + search=(675, 117, 1216, 708), + color=(171, 185, 190), + button=(695, 137, 1196, 688), + ), + zht=Button( + file='./assets/zht/mission/EVENT_LIST.png', + area=(695, 137, 1196, 688), + search=(675, 117, 1216, 708), + color=(171, 185, 190), + button=(695, 137, 1196, 688), + ), +) +EVENT_STARS = ButtonWrapper( + name='EVENT_STARS', + jp=Button( + file='./assets/jp/mission/EVENT_STARS.png', + area=(704, 186, 761, 218), + search=(684, 166, 781, 238), + color=(228, 223, 194), + button=(704, 186, 761, 218), + ), + en=Button( + file='./assets/en/mission/EVENT_STARS.png', + area=(704, 186, 761, 218), + search=(684, 166, 781, 238), + color=(228, 223, 194), + button=(704, 186, 761, 218), + ), + zht=Button( + file='./assets/zht/mission/EVENT_STARS.png', + area=(704, 186, 761, 218), + search=(684, 166, 781, 238), + color=(228, 223, 194), + button=(704, 186, 761, 218), + ), +) +HARD_OFF = ButtonWrapper( + name='HARD_OFF', + jp=Button( + file='./assets/jp/mission/HARD_OFF.png', + area=(1034, 144, 1095, 172), + search=(1014, 124, 1115, 192), + color=(204, 211, 218), + button=(1034, 144, 1095, 172), + ), + 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), + ), + zht=Button( + file='./assets/zht/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=Button( + file='./assets/jp/mission/HARD_ON.png', + area=(1032, 143, 1095, 172), + search=(1012, 123, 1115, 192), + color=(211, 104, 54), + button=(1032, 143, 1095, 172), + ), + 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), + ), + zht=Button( + file='./assets/zht/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=Button( + file='./assets/jp/mission/LEFT.png', + area=(0, 301, 89, 408), + search=(0, 281, 109, 428), + color=(193, 224, 241), + button=(0, 301, 89, 408), + ), + 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), + ), + zht=Button( + file='./assets/zht/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=Button( + file='./assets/jp/mission/NORMAL_OFF.png', + area=(759, 146, 847, 171), + search=(739, 126, 867, 191), + color=(197, 205, 214), + button=(759, 146, 847, 171), + ), + 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), + ), + zht=Button( + file='./assets/zht/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=Button( + file='./assets/jp/mission/NORMAL_ON.png', + area=(759, 145, 845, 172), + search=(739, 125, 865, 192), + color=(107, 115, 72), + button=(759, 145, 845, 172), + ), + 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), + ), + zht=Button( + file='./assets/zht/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=Button( + file='./assets/jp/mission/OCR_AREA.png', + area=(111, 178, 174, 212), + search=(91, 158, 194, 232), + color=(218, 222, 227), + button=(111, 178, 174, 212), + ), + 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), + ), + zht=Button( + file='./assets/zht/mission/OCR_AREA.png', + area=(108, 176, 176, 219), + search=(88, 156, 196, 239), + color=(237, 238, 240), + button=(108, 176, 176, 219), + ), +) +QUEST_OFF = ButtonWrapper( + name='QUEST_OFF', + jp=Button( + file='./assets/jp/mission/QUEST_OFF.png', + area=(903, 80, 989, 115), + search=(883, 60, 1009, 135), + color=(212, 217, 221), + button=(903, 80, 989, 115), + ), + en=Button( + file='./assets/en/mission/QUEST_OFF.png', + area=(859, 69, 1025, 125), + search=(839, 49, 1045, 145), + color=(231, 235, 235), + button=(859, 69, 1025, 125), + ), + zht=Button( + file='./assets/zht/mission/QUEST_OFF.png', + area=(859, 69, 1025, 125), + search=(839, 49, 1045, 145), + color=(231, 235, 235), + button=(859, 69, 1025, 125), + ), +) +QUEST_ON = ButtonWrapper( + name='QUEST_ON', + jp=Button( + file='./assets/jp/mission/QUEST_ON.png', + area=(903, 82, 987, 113), + search=(883, 62, 1007, 133), + color=(84, 93, 69), + button=(903, 82, 987, 113), + ), + en=Button( + file='./assets/en/mission/QUEST_ON.png', + area=(861, 71, 1025, 124), + search=(841, 51, 1045, 144), + color=(49, 68, 76), + button=(861, 71, 1025, 124), + ), + zht=Button( + file='./assets/zht/mission/QUEST_ON.png', + area=(861, 71, 1025, 124), + search=(841, 51, 1045, 144), + color=(49, 68, 76), + button=(861, 71, 1025, 124), + ), +) +RIGHT = ButtonWrapper( + name='RIGHT', + jp=Button( + file='./assets/jp/mission/RIGHT.png', + area=(1202, 311, 1280, 412), + search=(1182, 291, 1280, 432), + color=(193, 223, 241), + button=(1202, 311, 1280, 412), + ), + 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), + ), + zht=Button( + file='./assets/zht/mission/RIGHT.png', + area=(1202, 311, 1280, 412), + search=(1182, 291, 1280, 432), + color=(193, 223, 241), + button=(1202, 311, 1280, 412), + ), +) +SELECT_CR = ButtonWrapper( + name='SELECT_CR', + jp=Button( + file='./assets/jp/mission/SELECT_CR.png', + area=(1009, 271, 1226, 312), + search=(989, 251, 1246, 332), + color=(207, 213, 221), + button=(1009, 271, 1226, 312), + ), + en=Button( + file='./assets/en/mission/SELECT_CR.png', + area=(1004, 267, 1237, 321), + search=(984, 247, 1257, 341), + color=(214, 220, 227), + button=(1004, 267, 1237, 321), + ), + zht=Button( + file='./assets/zht/mission/SELECT_CR.png', + area=(1029, 272, 1225, 311), + search=(1009, 252, 1245, 331), + color=(178, 187, 198), + button=(1029, 272, 1225, 311), + ), +) +SELECT_XP = ButtonWrapper( + name='SELECT_XP', + jp=Button( + file='./assets/jp/mission/SELECT_XP.png', + area=(1095, 166, 1224, 205), + search=(1075, 146, 1244, 225), + color=(175, 185, 198), + button=(1095, 166, 1224, 205), + ), + en=Button( + file='./assets/en/mission/SELECT_XP.png', + area=(1016, 165, 1227, 211), + search=(996, 145, 1247, 231), + color=(205, 212, 220), + button=(1016, 165, 1227, 211), + ), + zht=Button( + file='./assets/zht/mission/SELECT_XP.png', + area=(1085, 163, 1230, 203), + search=(1065, 143, 1250, 223), + color=(179, 189, 201), + button=(1085, 163, 1230, 203), + ), +) diff --git a/tasks/mission/mission.py b/tasks/mission/mission.py new file mode 100644 index 0000000..286c26b --- /dev/null +++ b/tasks/mission/mission.py @@ -0,0 +1,283 @@ +import json +import math +from datetime import datetime, timedelta +from enum import Enum + +from filelock import FileLock + +from module.base.timer import Timer +from module.logger import logger +from tasks.cafe.cafe import Cafe +from tasks.circle.circle import Circle +from tasks.item.data_update import DataUpdate +from tasks.mail.mail import Mail +from tasks.mission.ui import MissionUI, CommissionsUI, SWITCH_QUEST +from tasks.task.task import Task + + +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): + @property + def stage_ap(self): + match self.current_mode: + case "N": + return 10 + case "H": + return 20 + case "E": + if self.current_stage >= "09": + return 20 + elif self.current_stage <= "04": + return 10 + else: + return 15 + case "XP" | "CR": + if self.current_stage >= "08": + return 40 + else: + return int(self.current_stage, base=10) * 5 + + @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 + "CR" : Item Retrieval / Commission where you get credit + "XP" : 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() + + # Check if the difference between the current date and last run date is 2 or greater days + if (current_date - last_run_datetime.date()).days >= 2: + # Set self.last_run to yesterday's date with time as reset_time + yesterday_datetime = current_datetime - timedelta(days=1) + yesterday_date = yesterday_datetime.date() + self.last_run = str(datetime.combine(yesterday_date, reset_time)) + logger.info("Reset Daily activated") + return True + + # Check if the current date is different from the last run date and the current time is greater than or equal to the reset time + elif 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 ["CR", "XP"]: + return self.select_commission(self.current_mode) + elif self.current_mode == "E": + return self.select_mode(SWITCH_QUEST) + else: + logger.error("Uknown mode") + return False + + def get_realistic_count(self) -> int: + """ + Calculate the possible number of sweeps based on the current AP + """ + possible_count = math.floor(self.current_ap / self.stage_ap) + return min(possible_count, self.current_count) + + 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 = self.config.stored.AP + ap_old = ap.value + ap_new = ap_old - self.stage_ap * 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_mode, 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 + if self.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 + + # delay mission to 7 hours if there are still stages in the queue + self.config.task_delay(minute=420) if self.task else self.config.task_delay(server_update=True) diff --git a/tasks/mission/ui.py b/tasks/mission/ui.py new file mode 100644 index 0000000..2651262 --- /dev/null +++ b/tasks/mission/ui.py @@ -0,0 +1,155 @@ +from module.base.timer import Timer +from module.logger import logger +from module.ocr.ocr import Digit +from module.ui.switch import Switch +from tasks.base.assets.assets_base_page import BACK, MISSION_CHECK, EVENT_CHECK, WORK_GO_TO_EVENT +from tasks.base.page import page_mission, page_commissions, page_work # ,page_event +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') +EVENT_LIST = StageList('EventList', EVENT_LIST, EVENT_INDEX, EVENT_ITEM, button_stars=EVENT_STARS) +SHARED_SWEEP = StageSweep('MissionSweep', 99) +SHARED_SWEEP.set_button(button_check=CHECK_MISSION_SWEEP) # Check sweep is different for mission, event +COMMISSIONS_SWEEP = StageSweep('CommissionsSweep', 99) + +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 = Switch("QUEST_switch") +SWITCH_QUEST.add_state("on", QUEST_ON) +SWITCH_QUEST.add_state("off", QUEST_OFF) + +""" +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), + "XP": (CHECK_XP, page_commissions), + "CR": (CHECK_CR, page_commissions), + "E": (EVENT_CHECK, None) # page_event +} + + +class MissionUI(UI, AP): + def select_mission(self, mode, stage): + area = int(stage.split("-")[0]) + switch = SWITCH_HARD if mode == "H" else SWITCH_NORMAL + if self.select_area(area) and self.select_mode(switch): + return True + return False + + 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 > 3: + logger.warning(f"Area {num} not found") + 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, mode, index: int) -> bool: + list = EVENT_LIST if mode == "E" else SHARED_LIST + if list.select_index_enter(self, index): + return True + return False + + def do_sweep(self, mode, num: int) -> bool: + if mode in ["XP", "CR"]: + return COMMISSIONS_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 ["XP", "CR"] and next in ["XP", "CR"]: + self.go_back(CHECK_COMMISSIONS) + else: + self.goto_event() if next == "E" 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) + + def goto_event(self): + """ + Should be removed after implementing ui_ensure(page_event) + """ + self.ui_ensure(page_work) + timer = Timer(1).start() + while 1: + self.device.screenshot() + if self.appear(EVENT_CHECK): + break + self.appear_then_click(WORK_GO_TO_EVENT) + self.device.swipe((40, 160), (260, 40)) + while not timer.reached_and_reset(): + pass + + +class CommissionsUI(UI, AP): + """Works the same way as select_bounty""" + + def select_commission(self, mode): + if mode == "CR": + dest_enter, dest_check = SELECT_CR, CHECK_CR + else: + dest_enter, dest_check = SELECT_XP, CHECK_XP + 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 diff --git a/tasks/momotalk/assets/assets_momotalk.py b/tasks/momotalk/assets/assets_momotalk.py new file mode 100644 index 0000000..e6f98a2 --- /dev/null +++ b/tasks/momotalk/assets/assets_momotalk.py @@ -0,0 +1,509 @@ +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 ``` + +BEGIN_STORY = ButtonWrapper( + name='BEGIN_STORY', + jp=Button( + file='./assets/jp/momotalk/BEGIN_STORY.png', + area=(729, 533, 1110, 600), + search=(709, 513, 1130, 620), + color=(112, 210, 244), + button=(729, 533, 1110, 600), + ), + en=Button( + file='./assets/en/momotalk/BEGIN_STORY.png', + area=(796, 540, 1059, 591), + search=(776, 520, 1079, 611), + color=(107, 197, 230), + button=(796, 540, 1059, 591), + ), + zht=Button( + file='./assets/zht/momotalk/BEGIN_STORY.png', + area=(729, 533, 1114, 599), + search=(709, 513, 1134, 619), + color=(110, 205, 239), + button=(729, 533, 1114, 599), + ), +) +CHATTING = ButtonWrapper( + name='CHATTING', + jp=Button( + file='./assets/jp/momotalk/CHATTING.png', + area=(776, 520, 817, 545), + search=(756, 500, 837, 565), + color=(89, 103, 121), + button=(776, 520, 817, 545), + ), + en=Button( + file='./assets/en/momotalk/CHATTING.png', + area=(774, 563, 821, 585), + search=(754, 543, 841, 605), + color=(89, 102, 121), + button=(774, 563, 821, 585), + ), + zht=Button( + file='./assets/zht/momotalk/CHATTING.png', + area=(774, 563, 821, 585), + search=(754, 543, 841, 605), + color=(89, 102, 121), + button=(774, 563, 821, 585), + ), +) +CHAT_AREA = ButtonWrapper( + name='CHAT_AREA', + jp=Button( + file='./assets/jp/momotalk/CHAT_AREA.png', + area=(686, 153, 1146, 626), + search=(666, 133, 1166, 646), + color=(190, 186, 195), + button=(686, 153, 1146, 626), + ), + en=Button( + file='./assets/en/momotalk/CHAT_AREA.png', + area=(760, 149, 1149, 628), + search=(740, 129, 1169, 648), + color=(206, 212, 217), + button=(760, 149, 1149, 628), + ), + zht=Button( + file='./assets/zht/momotalk/CHAT_AREA.png', + area=(760, 149, 1149, 628), + search=(740, 129, 1169, 648), + color=(206, 212, 217), + button=(760, 149, 1149, 628), + ), +) +CONFIRM_SKIP = ButtonWrapper( + name='CONFIRM_SKIP', + jp=Button( + file='./assets/jp/momotalk/CONFIRM_SKIP.png', + area=(664, 491, 871, 554), + search=(644, 471, 891, 574), + color=(113, 214, 249), + button=(664, 491, 871, 554), + ), + en=Button( + file='./assets/en/momotalk/CONFIRM_SKIP.png', + area=(674, 486, 871, 555), + search=(654, 466, 891, 575), + color=(112, 207, 241), + button=(674, 486, 871, 555), + ), + zht=Button( + file='./assets/zht/momotalk/CONFIRM_SKIP.png', + area=(665, 492, 875, 555), + search=(645, 472, 895, 575), + color=(111, 209, 244), + button=(665, 492, 875, 555), + ), +) +CONFIRM_SORT = ButtonWrapper( + name='CONFIRM_SORT', + jp=Button( + file='./assets/jp/momotalk/CONFIRM_SORT.png', + area=(243, 403, 656, 440), + search=(223, 383, 676, 460), + color=(252, 253, 253), + button=(243, 403, 656, 440), + ), + en=Button( + file='./assets/en/momotalk/CONFIRM_SORT.png', + area=(239, 407, 657, 439), + search=(219, 387, 677, 459), + color=(248, 249, 249), + button=(239, 407, 657, 439), + ), + zht=Button( + file='./assets/zht/momotalk/CONFIRM_SORT.png', + area=(240, 401, 656, 440), + search=(220, 381, 676, 460), + color=(249, 250, 250), + button=(240, 401, 656, 440), + ), +) +FIRST_UNREAD = ButtonWrapper( + name='FIRST_UNREAD', + jp=Button( + file='./assets/jp/momotalk/FIRST_UNREAD.png', + area=(635, 244, 660, 272), + search=(615, 224, 680, 292), + color=(251, 85, 43), + button=(635, 244, 660, 272), + ), + en=Button( + file='./assets/en/momotalk/FIRST_UNREAD.png', + area=(636, 239, 661, 265), + search=(616, 219, 681, 285), + color=(251, 86, 45), + button=(636, 239, 661, 265), + ), + zht=Button( + file='./assets/zht/momotalk/FIRST_UNREAD.png', + area=(636, 239, 661, 265), + search=(616, 219, 681, 285), + color=(251, 86, 45), + button=(636, 239, 661, 265), + ), +) +MENU = ButtonWrapper( + name='MENU', + jp=Button( + file='./assets/jp/momotalk/MENU.png', + area=(1159, 28, 1249, 55), + search=(1139, 8, 1269, 75), + color=(184, 192, 201), + button=(1159, 28, 1249, 55), + ), + en=Button( + file='./assets/en/momotalk/MENU.png', + area=(1156, 15, 1251, 63), + search=(1136, 0, 1271, 83), + color=(215, 219, 222), + button=(1156, 15, 1251, 63), + ), + zht=Button( + file='./assets/zht/momotalk/MENU.png', + area=(1151, 20, 1256, 61), + search=(1131, 0, 1276, 81), + color=(214, 218, 223), + button=(1151, 20, 1256, 61), + ), +) +NOTIFICATION_BADGE = ButtonWrapper( + name='NOTIFICATION_BADGE', + jp=Button( + file='./assets/jp/momotalk/NOTIFICATION_BADGE.png', + area=(163, 107, 196, 129), + search=(143, 87, 216, 149), + color=(240, 110, 65), + button=(163, 107, 196, 129), + ), + en=Button( + file='./assets/en/momotalk/NOTIFICATION_BADGE.png', + area=(171, 109, 200, 128), + search=(151, 89, 220, 148), + color=(242, 101, 47), + button=(171, 109, 200, 128), + ), + zht=Button( + file='./assets/zht/momotalk/NOTIFICATION_BADGE.png', + area=(171, 109, 200, 128), + search=(151, 89, 220, 148), + color=(242, 101, 47), + button=(171, 109, 200, 128), + ), +) +REPLY = ButtonWrapper( + name='REPLY', + jp=Button( + file='./assets/jp/momotalk/REPLY.png', + area=(792, 509, 868, 530), + search=(772, 489, 888, 550), + color=(172, 184, 193), + button=(792, 509, 868, 530), + ), + en=Button( + file='./assets/en/momotalk/REPLY.png', + area=(791, 431, 855, 462), + search=(771, 411, 875, 482), + color=(198, 210, 216), + button=(791, 431, 855, 462), + ), + zht=Button( + file='./assets/zht/momotalk/REPLY.png', + area=(783, 428, 838, 460), + search=(763, 408, 858, 480), + color=(189, 203, 211), + button=(783, 428, 838, 460), + ), +) +SELECT_STUDENT = ButtonWrapper( + name='SELECT_STUDENT', + jp=Button( + file='./assets/jp/momotalk/SELECT_STUDENT.png', + area=(801, 374, 1023, 399), + search=(781, 354, 1043, 419), + color=(226, 227, 228), + button=(801, 374, 1023, 399), + ), + en=Button( + file='./assets/en/momotalk/SELECT_STUDENT.png', + area=(839, 369, 998, 403), + search=(819, 349, 1018, 423), + color=(241, 242, 242), + button=(839, 369, 998, 403), + ), + zht=Button( + file='./assets/zht/momotalk/SELECT_STUDENT.png', + area=(849, 365, 980, 406), + search=(829, 345, 1000, 426), + color=(236, 237, 238), + button=(849, 365, 980, 406), + ), +) +SKIP = ButtonWrapper( + name='SKIP', + jp=Button( + file='./assets/jp/momotalk/SKIP.png', + area=(1188, 96, 1233, 142), + search=(1168, 76, 1253, 162), + color=(87, 107, 130), + button=(1188, 96, 1233, 142), + ), + en=Button( + file='./assets/en/momotalk/SKIP.png', + area=(1192, 103, 1229, 141), + search=(1172, 83, 1249, 161), + color=(108, 125, 145), + button=(1192, 103, 1229, 141), + ), + zht=Button( + file='./assets/zht/momotalk/SKIP.png', + area=(1186, 95, 1231, 144), + search=(1166, 75, 1251, 164), + color=(85, 105, 129), + button=(1186, 95, 1231, 144), + ), +) +SORT_ASCENDING = ButtonWrapper( + name='SORT_ASCENDING', + jp=Button( + file='./assets/jp/momotalk/SORT_ASCENDING.png', + area=(633, 166, 646, 186), + search=(613, 146, 666, 206), + color=(205, 216, 224), + button=(633, 166, 646, 186), + ), + en=Button( + file='./assets/en/momotalk/SORT_ASCENDING.png', + area=(631, 166, 648, 188), + search=(611, 146, 668, 208), + color=(221, 228, 233), + button=(631, 166, 648, 188), + ), + zht=Button( + file='./assets/zht/momotalk/SORT_ASCENDING.png', + area=(631, 166, 648, 188), + search=(611, 146, 668, 208), + color=(221, 228, 233), + button=(631, 166, 648, 188), + ), +) +SORT_DESCENDING = ButtonWrapper( + name='SORT_DESCENDING', + jp=Button( + file='./assets/jp/momotalk/SORT_DESCENDING.png', + area=(633, 168, 646, 187), + search=(613, 148, 666, 207), + color=(202, 212, 221), + button=(633, 168, 646, 187), + ), + en=Button( + file='./assets/en/momotalk/SORT_DESCENDING.png', + area=(631, 166, 648, 189), + search=(611, 146, 668, 209), + color=(221, 228, 234), + button=(631, 166, 648, 189), + ), + zht=Button( + file='./assets/zht/momotalk/SORT_DESCENDING.png', + area=(631, 166, 648, 189), + search=(611, 146, 668, 209), + color=(221, 228, 234), + button=(631, 166, 648, 189), + ), +) +STORY = ButtonWrapper( + name='STORY', + jp=Button( + file='./assets/jp/momotalk/STORY.png', + area=(792, 531, 887, 553), + search=(772, 511, 907, 573), + color=(218, 206, 212), + button=(792, 531, 887, 553), + ), + en=Button( + file='./assets/en/momotalk/STORY.png', + area=(790, 529, 979, 557), + search=(770, 509, 999, 577), + color=(220, 208, 214), + button=(790, 529, 979, 557), + ), + zht=Button( + file='./assets/zht/momotalk/STORY.png', + area=(781, 527, 878, 555), + search=(761, 507, 898, 575), + color=(208, 197, 204), + button=(781, 527, 878, 555), + ), +) +SWITCH_MESSAGE = ButtonWrapper( + name='SWITCH_MESSAGE', + jp=Button( + file='./assets/jp/momotalk/SWITCH_MESSAGE.png', + area=(150, 271, 193, 297), + search=(130, 251, 213, 317), + color=(194, 198, 206), + button=(150, 271, 193, 297), + ), + en=Button( + file='./assets/en/momotalk/SWITCH_MESSAGE.png', + area=(147, 271, 194, 297), + search=(127, 251, 214, 317), + color=(187, 191, 201), + button=(147, 271, 194, 297), + ), + zht=Button( + file='./assets/zht/momotalk/SWITCH_MESSAGE.png', + area=(147, 271, 194, 297), + search=(127, 251, 214, 317), + color=(187, 191, 201), + button=(147, 271, 194, 297), + ), +) +SWITCH_MESSAGE_CHECK = ButtonWrapper( + name='SWITCH_MESSAGE_CHECK', + jp=Button( + file='./assets/jp/momotalk/SWITCH_MESSAGE_CHECK.png', + area=(231, 167, 379, 190), + search=(211, 147, 399, 210), + color=(197, 201, 202), + button=(231, 167, 379, 190), + ), + en=Button( + file='./assets/en/momotalk/SWITCH_MESSAGE_CHECK.png', + area=(228, 165, 391, 194), + search=(208, 145, 411, 214), + color=(211, 215, 217), + button=(228, 165, 391, 194), + ), + zht=Button( + file='./assets/zht/momotalk/SWITCH_MESSAGE_CHECK.png', + area=(226, 161, 320, 194), + search=(206, 141, 340, 214), + color=(199, 203, 205), + button=(226, 161, 320, 194), + ), +) +SWITCH_STUDENT = ButtonWrapper( + name='SWITCH_STUDENT', + jp=Button( + file='./assets/jp/momotalk/SWITCH_STUDENT.png', + area=(151, 168, 193, 213), + search=(131, 148, 213, 233), + color=(169, 176, 188), + button=(151, 168, 193, 213), + ), + en=Button( + file='./assets/en/momotalk/SWITCH_STUDENT.png', + area=(148, 167, 194, 217), + search=(128, 147, 214, 237), + color=(157, 166, 179), + button=(148, 167, 194, 217), + ), + zht=Button( + file='./assets/zht/momotalk/SWITCH_STUDENT.png', + area=(148, 167, 194, 217), + search=(128, 147, 214, 237), + color=(157, 166, 179), + button=(148, 167, 194, 217), + ), +) +SWITCH_STUDENT_CHECK = ButtonWrapper( + name='SWITCH_STUDENT_CHECK', + jp=Button( + file='./assets/jp/momotalk/SWITCH_STUDENT_CHECK.png', + area=(232, 168, 275, 190), + search=(212, 148, 295, 210), + color=(169, 174, 176), + button=(232, 168, 275, 190), + ), + en=Button( + file='./assets/en/momotalk/SWITCH_STUDENT_CHECK.png', + area=(229, 166, 320, 192), + search=(209, 146, 340, 212), + color=(199, 203, 205), + button=(229, 166, 320, 192), + ), + zht=Button( + file='./assets/zht/momotalk/SWITCH_STUDENT_CHECK.png', + area=(221, 161, 281, 197), + search=(201, 141, 301, 217), + color=(210, 214, 216), + button=(221, 161, 281, 197), + ), +) +UNREAD = ButtonWrapper( + name='UNREAD', + jp=Button( + file='./assets/jp/momotalk/UNREAD.png', + area=(454, 158, 570, 194), + search=(434, 138, 590, 214), + color=(242, 243, 243), + button=(454, 158, 570, 194), + ), + en=Button( + file='./assets/en/momotalk/UNREAD.png', + area=(454, 160, 568, 193), + search=(434, 140, 588, 213), + color=(241, 242, 243), + button=(454, 160, 568, 193), + ), + zht=Button( + file='./assets/zht/momotalk/UNREAD.png', + area=(453, 160, 567, 195), + search=(433, 140, 587, 215), + color=(241, 242, 243), + button=(453, 160, 567, 195), + ), +) +UNREAD_OFF = ButtonWrapper( + name='UNREAD_OFF', + jp=Button( + file='./assets/jp/momotalk/UNREAD_OFF.png', + area=(458, 273, 655, 314), + search=(438, 253, 675, 334), + color=(252, 252, 252), + button=(458, 273, 655, 314), + ), + en=Button( + file='./assets/en/momotalk/UNREAD_OFF.png', + area=(456, 273, 658, 316), + search=(436, 253, 678, 336), + color=(252, 252, 251), + button=(456, 273, 658, 316), + ), + zht=Button( + file='./assets/zht/momotalk/UNREAD_OFF.png', + area=(459, 274, 658, 314), + search=(439, 254, 678, 334), + color=(251, 251, 251), + button=(459, 274, 658, 314), + ), +) +UNREAD_ON = ButtonWrapper( + name='UNREAD_ON', + jp=Button( + file='./assets/jp/momotalk/UNREAD_ON.png', + area=(463, 274, 653, 314), + search=(443, 254, 673, 334), + color=(245, 118, 143), + button=(463, 274, 653, 314), + ), + en=Button( + file='./assets/en/momotalk/UNREAD_ON.png', + area=(456, 272, 658, 314), + search=(436, 252, 678, 334), + color=(245, 120, 144), + button=(456, 272, 658, 314), + ), + zht=Button( + file='./assets/zht/momotalk/UNREAD_ON.png', + area=(455, 272, 658, 314), + search=(435, 252, 678, 334), + color=(244, 118, 143), + button=(455, 272, 658, 314), + ), +) diff --git a/tasks/momotalk/momotalk.py b/tasks/momotalk/momotalk.py new file mode 100644 index 0000000..f2988ad --- /dev/null +++ b/tasks/momotalk/momotalk.py @@ -0,0 +1,61 @@ +from enum import Enum + +from module.base.timer import Timer +from module.logger import logger +from tasks.momotalk.ui import MomoTalkUI + + +class MomoTalkStatus(Enum): + OPEN = 0 + SORT = 1 + CHECK = 2 + CHAT = 3 + STORY = 4 + FINISHED = -1 + + +class MomoTalk(MomoTalkUI): + def handle_momotalk(self, status): + match status: + case MomoTalkStatus.OPEN: + if self.open_momotalk(): + return MomoTalkStatus.SORT + return MomoTalkStatus.FINISHED + case MomoTalkStatus.SORT: + if self.sort_messages(): + return MomoTalkStatus.CHECK + case MomoTalkStatus.CHECK: + if self.check_first_student(): + return MomoTalkStatus.CHAT + return MomoTalkStatus.FINISHED + case MomoTalkStatus.CHAT: + if self.chat(): + return MomoTalkStatus.STORY + return MomoTalkStatus.OPEN + case MomoTalkStatus.STORY: + if self.skip_story(): + return MomoTalkStatus.CHAT + case MomoTalkStatus.FINISHED: + return status + case _: + logger.warning(f'Invalid status: {status}') + return status + + def run(self): + action_timer = Timer(0.5, 1) + status = MomoTalkStatus.OPEN + + while 1: + self.device.screenshot() + + if self.ui_additional(): + continue + + if action_timer.reached_and_reset(): + logger.attr('Status', status) + status = self.handle_momotalk(status) + + if status == MomoTalkStatus.FINISHED: + break + + self.config.task_delay(server_update=True) diff --git a/tasks/momotalk/ui.py b/tasks/momotalk/ui.py new file mode 100644 index 0000000..708f009 --- /dev/null +++ b/tasks/momotalk/ui.py @@ -0,0 +1,176 @@ +import numpy as np + +from module.base.timer import Timer +from module.base.utils import area_size +from module.logger import logger +from module.ui.switch import Switch +from tasks.base.page import page_main, page_momo_talk +from tasks.base.ui import UI +from tasks.momotalk.assets.assets_momotalk import * + +SWITCH_SIDEBAR = Switch("Sidebar_switch", is_selector=True) +SWITCH_SIDEBAR.add_state("student", SWITCH_STUDENT_CHECK, SWITCH_STUDENT) +SWITCH_SIDEBAR.add_state("message", SWITCH_MESSAGE_CHECK, SWITCH_MESSAGE) + +SWITCH_UNREAD = Switch("Unread_switch") +SWITCH_UNREAD.add_state("on", UNREAD_ON) +SWITCH_UNREAD.add_state("off", UNREAD_OFF) + +SWITCH_SORT = Switch("Sort_switch") +SWITCH_SORT.add_state("ascending", SORT_ASCENDING) +SWITCH_SORT.add_state("descending", SORT_DESCENDING) + +"""Required for template matching as reply and story +button can be found in different locations""" +REPLY_TEMPLATE = REPLY +STORY_TEMPLATE = STORY +CHATTING_TEMPLATE = CHATTING + + +class MomoTalkUI(UI): + swipe_vector_range = (0.65, 0.85) + list = CHAT_AREA + + def swipe_page(self, direction: str, vector_range=None, reverse=False): + """ + Args: + direction: up, down + 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.list.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]) + self.device.swipe_vector(vector, self.list.button) + + def select_then_disappear(self, dest_enter: ButtonWrapper, dest_check: ButtonWrapper): + timer = Timer(5, 10).start() + while 1: + self.device.screenshot() + self.click_with_interval(dest_enter, interval=1) + if not self.appear(dest_check): + return True + if timer.reached(): + return False + + def set_switch(self, switch, state='on'): + """ + Set switch to on. However, unsure why is inaccurate in momotalk. + 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(state, main=self) + + return True + + def click_all(self, template: ButtonWrapper, offset: tuple[int, int] = (0, 0)) -> bool: + """ + Find the all the locations of the template adding an offset if specified and click them. + If after filter, no coords then swipe. + """ + template.load_search(self.list.area) + template.matched_button._button_offset = offset + seen = template.match_multi_template(self.device.image, similarity=0.8) + if seen: + if any(offset): + for button in seen: + self.device.click(button) + self.swipe_page("down") + return True + return False + + def open_momotalk(self): + """ + Go to main and check if there are any red notifications in momotalk. + If yes, open it otherwise it means no student available for interaction. + """ + self.ui_ensure(page_main) + if self.match_color(NOTIFICATION_BADGE, threshold=80): + self.ui_ensure(page_momo_talk) + while SWITCH_SIDEBAR.get(self) != "message": + SWITCH_SIDEBAR.set("message", self) + return True + logger.warning("No students available for interaction") + return False + + def sort_messages(self): + """ + Switch from newest to unread and sort the messages in descending order + """ + while 1: + self.device.screenshot() + if self.set_switch(SWITCH_UNREAD): + self.click_with_interval(CONFIRM_SORT, interval=2) + continue + if self.appear(UNREAD, similarity=0.95): + break + self.click_with_interval(UNREAD, interval=2) + return self.set_switch(SWITCH_SORT, "descending") + + def check_first_student(self): + """ + If the first student has a red notification return True and start chat. + Otherwise it means no students are available for interaction. + """ + if self.match_color(FIRST_UNREAD, threshold=80) and self.select_then_disappear(FIRST_UNREAD, SELECT_STUDENT): + return True + logger.warning("No students available for interaction") + return False + + def chat(self): + """ + Waits for the chat area to be stable and then + check if a reply or story button is found and click them. + If the begin story button is found skip story. + """ + timer = Timer(8, 5).start() + logger.info("Chatting with student...") + while 1: + self.device.screenshot() + if self.appear(BEGIN_STORY): + logger.info("Begin Story detected") + return True + elif self.click_all(CHATTING_TEMPLATE): + timer.reset() + elif self.click_all(REPLY_TEMPLATE, offset=(0, 62)): + logger.info("Clicked on reply") + timer.reset() + elif self.click_all(STORY_TEMPLATE, offset=(0, 62)): + logger.info("Clicked on story") + timer.reset() + elif timer.reached(): + logger.info("No new message detected") + return False + + def skip_story(self): + """ + Skip story by executing a series of steps. Returns True if the confirm skip + button is clicked and disappears + """ + logger.info("Attempting to skip story...") + steps = [CONFIRM_SKIP, SKIP, MENU, BEGIN_STORY] + timer = Timer(1).start() + while 1: + self.device.screenshot() + if self.handle_reward(): + logger.info("Skipped story successfully") + return True + for step in steps: + if self.appear_then_click(step): + while not timer.reached_and_reset(): + pass + break diff --git a/tasks/schedule/assets/assets_schedule.py b/tasks/schedule/assets/assets_schedule.py index 8e923b2..34d3b26 100644 --- a/tasks/schedule/assets/assets_schedule.py +++ b/tasks/schedule/assets/assets_schedule.py @@ -3,14 +3,134 @@ 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 ``` +CONFIRM = ButtonWrapper( + name='CONFIRM', + jp=Button( + file='./assets/jp/schedule/CONFIRM.png', + area=(532, 524, 742, 591), + search=(512, 504, 762, 611), + color=(114, 214, 249), + button=(532, 524, 742, 591), + ), + en=Button( + file='./assets/en/schedule/CONFIRM.png', + area=(532, 528, 748, 589), + search=(512, 508, 768, 609), + color=(110, 207, 241), + button=(532, 528, 748, 589), + ), + zht=Button( + file='./assets/zht/schedule/CONFIRM.png', + area=(529, 526, 746, 587), + search=(509, 506, 766, 607), + color=(111, 209, 244), + button=(529, 526, 746, 587), + ), +) +FIRST_ITEM = ButtonWrapper( + name='FIRST_ITEM', + jp=Button( + file='./assets/jp/schedule/FIRST_ITEM.png', + area=(751, 137, 1133, 236), + search=(731, 117, 1153, 256), + color=(199, 210, 222), + button=(751, 137, 1133, 236), + ), + en=Button( + file='./assets/en/schedule/FIRST_ITEM.png', + area=(727, 137, 1103, 239), + search=(707, 117, 1123, 259), + color=(200, 209, 220), + button=(727, 137, 1103, 239), + ), + zht=Button( + file='./assets/zht/schedule/FIRST_ITEM.png', + area=(727, 137, 1103, 239), + search=(707, 117, 1123, 259), + color=(200, 209, 220), + button=(727, 137, 1103, 239), + ), +) +LOCATIONS = ButtonWrapper( + name='LOCATIONS', + jp=Button( + file='./assets/jp/schedule/LOCATIONS.png', + area=(1085, 637, 1249, 693), + search=(1065, 617, 1269, 713), + color=(107, 201, 237), + button=(1085, 637, 1249, 693), + ), + en=Button( + file='./assets/en/schedule/LOCATIONS.png', + area=(1075, 638, 1256, 693), + search=(1055, 618, 1276, 713), + color=(107, 202, 237), + button=(1075, 638, 1256, 693), + ), + zht=Button( + file='./assets/zht/schedule/LOCATIONS.png', + area=(1078, 639, 1251, 692), + search=(1058, 619, 1271, 712), + color=(103, 193, 228), + button=(1078, 639, 1251, 692), + ), +) +LOCATIONS_POPUP = ButtonWrapper( + name='LOCATIONS_POPUP', + jp=Button( + file='./assets/jp/schedule/LOCATIONS_POPUP.png', + area=(489, 99, 792, 135), + search=(469, 79, 812, 155), + color=(202, 209, 216), + button=(489, 99, 792, 135), + ), + en=Button( + file='./assets/en/schedule/LOCATIONS_POPUP.png', + area=(534, 101, 750, 135), + search=(514, 81, 770, 155), + color=(194, 202, 210), + button=(534, 101, 750, 135), + ), + zht=Button( + file='./assets/zht/schedule/LOCATIONS_POPUP.png', + area=(542, 95, 732, 136), + search=(522, 75, 752, 156), + color=(184, 192, 202), + button=(542, 95, 732, 136), + ), +) +OCR_TICKET = ButtonWrapper( + name='OCR_TICKET', + jp=Button( + file='./assets/jp/schedule/OCR_TICKET.png', + area=(187, 86, 235, 114), + search=(167, 66, 255, 134), + color=(220, 230, 232), + button=(187, 86, 235, 114), + ), + en=Button( + file='./assets/en/schedule/OCR_TICKET.png', + area=(220, 79, 266, 121), + search=(200, 59, 286, 141), + color=(214, 225, 229), + button=(220, 79, 266, 121), + ), + zht=Button( + file='./assets/zht/schedule/OCR_TICKET.png', + area=(152, 82, 202, 120), + search=(132, 62, 222, 140), + color=(217, 228, 231), + button=(152, 82, 202, 120), + ), +) SCROLL = ButtonWrapper( name='SCROLL', jp=Button( file='./assets/jp/schedule/SCROLL.png', - area=(742, 136, 1101, 671), - search=(722, 116, 1121, 691), - color=(198, 214, 210), - button=(742, 136, 1101, 671), + area=(749, 140, 1130, 669), + search=(729, 120, 1150, 689), + color=(197, 215, 212), + button=(749, 140, 1130, 669), ), en=Button( file='./assets/en/schedule/SCROLL.png', @@ -19,4 +139,35 @@ SCROLL = ButtonWrapper( color=(198, 214, 210), button=(727, 137, 1103, 671), ), + zht=Button( + file='./assets/zht/schedule/SCROLL.png', + area=(727, 137, 1103, 671), + search=(707, 117, 1123, 691), + color=(198, 214, 210), + button=(727, 137, 1103, 671), + ), +) +START_LESSON = ButtonWrapper( + name='START_LESSON', + jp=Button( + file='./assets/jp/schedule/START_LESSON.png', + area=(513, 534, 770, 568), + search=(493, 514, 790, 588), + color=(101, 187, 220), + button=(513, 534, 770, 568), + ), + en=Button( + file='./assets/en/schedule/START_LESSON.png', + area=(506, 523, 773, 585), + search=(486, 503, 793, 605), + color=(110, 205, 239), + button=(506, 523, 773, 585), + ), + zht=Button( + file='./assets/zht/schedule/START_LESSON.png', + area=(506, 527, 774, 577), + search=(486, 507, 794, 597), + color=(104, 194, 228), + button=(506, 527, 774, 577), + ), ) diff --git a/tasks/schedule/schedule.py b/tasks/schedule/schedule.py new file mode 100644 index 0000000..5cbf730 --- /dev/null +++ b/tasks/schedule/schedule.py @@ -0,0 +1,135 @@ +import re +from enum import Flag + +from module.base.timer import Timer +from module.exception import RequestHumanTakeover +from module.logger import logger +from tasks.base.assets.assets_base_page import BACK +from tasks.base.assets.assets_base_page import SCHEDULE_CHECK +from tasks.base.page import page_schedule +from tasks.schedule.ui import ScheduleUI + + +class ScheduleStatus(Flag): + OCR = 0 + ENTER = 1 + SELECT = 2 + END = 3 + FINISH = 4 + + +class Schedule(ScheduleUI): + @property + def schedule_info(self): + info = [] + input_valid = True + schedule_config = self.config.cross_get("Schedule") + choices = ["Choice1", "Choice2", "Choice3", "Choice4", "Choice5", "Choice6", "Choice7"] + + for choice in choices: + location, classrooms = schedule_config[choice]["Location"], schedule_config[choice]["Classrooms"] + if location == "None" or not classrooms or ( + isinstance(classrooms, str) and classrooms.replace(" ", "") == ""): + continue + elif isinstance(classrooms, int): + classrooms_list = [str(classrooms)] + else: + classrooms = classrooms.strip() + classrooms = re.sub(r'[ \t\r\n]', '', classrooms) + classrooms = (re.sub(r'[>﹥›˃ᐳ❯]', '>', classrooms)).split('>') + classrooms_list = [] + # tried to convert to set to remove duplicates but doesn't maintain order + [classrooms_list.append(x) for x in classrooms if x not in classrooms_list] + + if self.valid_classroom(classrooms_list): + info.append([location, classrooms_list]) + else: + logger.error(f"Failed to read {choice}") + input_valid = False + + return info if input_valid else [] + + def valid_classroom(self, classrooms_list): + if not classrooms_list: + return False + for classroom in classrooms_list: + if not classroom.isdigit(): + return False + if not 1 <= int(classroom) <= 9: + return False + return True + + @property + def valid_task(self) -> list: + task = self.schedule_info + if not task: + logger.warning('Lessons enabled but no task set') + self.error_handler() + return task + + def error_handler(self): + action = self.config.Schedule_OnError + if action == 'stop': + raise RequestHumanTakeover + elif action == 'skip': + with self.config.multi_set(): + self.config.task_delay(server_update=True) + self.config.task_stop() + + @property + def current_location(self): + return self.task[0][0] + + @property + def current_classrooms(self): + return self.task[0][1] + + def handle_schedule(self, status): + match status: + case ScheduleStatus.OCR: + if self.task: + self.ticket = self.get_ticket() + if self.ticket not in [0, None]: + return ScheduleStatus.ENTER + return ScheduleStatus.FINISH + case ScheduleStatus.ENTER: + if self.enter_location(self.current_location): + return ScheduleStatus.SELECT + else: + self.error_handler() + case ScheduleStatus.SELECT: + if self.select_classrooms(self.ticket, self.current_classrooms): + self.task.pop(0) + return ScheduleStatus.END + return ScheduleStatus.FINISH + case ScheduleStatus.END: + if self.appear(SCHEDULE_CHECK): + return ScheduleStatus.OCR + self.click_with_interval(BACK, interval=2) + case ScheduleStatus.FINISH: + return status + case _: + logger.warning(f'Invalid status: {status}') + return status + + def run(self): + self.ui_ensure(page_schedule) + self.task = self.valid_task + self.set_clickx() + action_timer = Timer(0.5, 1) + status = ScheduleStatus.OCR + + while 1: + self.device.screenshot() + + if self.ui_additional(): + continue + + if action_timer.reached_and_reset(): + logger.attr('Status', status) + status = self.handle_schedule(status) + + if status == ScheduleStatus.FINISH: + break + + self.config.task_delay(server_update=True) diff --git a/tasks/schedule/scroll_select.py b/tasks/schedule/scroll_select.py new file mode 100644 index 0000000..ef85395 --- /dev/null +++ b/tasks/schedule/scroll_select.py @@ -0,0 +1,154 @@ +""" +Original Author: sanmusen214(https://github.com/sanmusen214) +Adapted from https://github.com/sanmusen214/BAAH/blob/1.2/modules/AllTask/SubTask/ScrollSelect.py +""" + +from module.base.timer import Timer +from module.logger import logger + + +class ScrollSelect: + """ + Scroll and select the corresponding level by clicking on the right-side window. + + Parameters + ---------- + targetind : int + Index of the target level + window_starty: + Y-coordinate of the upper edge of the window + first_item_endy: + Y-coordinate of the lower edge of the first item + window_endy: + Y-coordinate of the lower edge of the window + clickx: int + Base X-coordinate for sliding and clicking the button + hasexpectimage: function + Function to determine the appearance of the expected image after clicking, returns a boolean + swipeoffsetx: int + X offset of the base X-coordinate during sliding to prevent accidental button clicks + finalclick: bool + Whether to click on clickx and the last row after the sliding ends + """ + + def __init__(self, window_button, first_item_button, expected_button, clickx, swipeoffsetx=-100, + finalclick=True) -> None: + # TODO: Actually, only concerned about the height of one element, completely displaying the Y of the first button, completely displaying the Y of the bottom button, the number of complete elements that the window can contain, the height of the last element in the window, and the left offset and response distance. + self.window_starty = window_button.area[1] + self.window_endy = window_button.area[3] + self.first_item_endy = first_item_button.area[3] + self.windowheight = window_button.height + self.itemheight = first_item_button.height + self.clickx = clickx + self.expected_button = expected_button + self.swipeoffsetx = swipeoffsetx + self.responsey = 40 + self.finalclick = finalclick + + def compute_swipe(self, main, x1, y1, distance, responsey): + """ + Swipe vertically from bottom to top, actual swipe distance calculated based on the distance between two target points, considering inertia. + """ + distance = abs(distance) + logger.info(f"Swipe distance: {distance}") + # 0-50 + if distance < 50: + main.device.swipe((x1, y1), (x1, y1 - (distance + responsey)), duration=2) + else: + # Effective swipe distance for the Chinese server is 60 + main.device.swipe((x1, y1), (x1, int(y1 - (distance + responsey - 4 * (1 + distance / 100)))), + duration=1 + distance / 100) + + def select_location(self, main, target_index) -> None: + click_coords = main.device.click_methods.get(main.config.Emulator_ControlMethod, main.device.click_adb) + logger.info("Scroll and select the {}-th level".format(target_index + 1)) + self.scroll_right_up(main, scrollx=self.clickx + self.swipeoffsetx) + # Calculate how many complete elements are on one page + itemcount = self.windowheight // self.itemheight + # Calculate how much height the last incomplete element on this page occupies + lastitemheight = self.windowheight % self.itemheight + # Height below the incomplete element + hiddenlastitemheight = self.itemheight - lastitemheight + # Center point of the height of the first element + start_center_y = self.window_starty + self.itemheight // 2 + # Center point of the last complete element on this page + end_center_y = start_center_y + (itemcount - 1) * self.itemheight + # If the target element is on the current page + if target_index < itemcount: + # Center point of the target element + target_center_y = start_center_y + self.itemheight * target_index + self.run_until(main, + lambda: click_coords(self.clickx, target_center_y), + lambda: main.appear(self.expected_button), + ) + else: + # Start scrolling from the gap in the middle of the levels + scroll_start_from_y = self.window_endy - self.itemheight // 2 + # The target element is on subsequent pages + # Calculate how much the page should be scrolled + scrolltotal_distance = (target_index - itemcount) * self.itemheight + hiddenlastitemheight + logger.info("Height hidden by the last element: %d" % hiddenlastitemheight) + # First, slide up the hidden part, add a little distance to let the system recognize it as a swipe event + self.compute_swipe(main, self.clickx + self.swipeoffsetx, scroll_start_from_y, hiddenlastitemheight, + self.responsey) + logger.info(f"Swipe distance: {hiddenlastitemheight}") + # Update scrolltotal_distance + scrolltotal_distance -= hiddenlastitemheight + # Still need to scroll up (target_index - itemcount) * self.itemheight + # Important: slide the height of (itemcount - 1) elements each time + if itemcount == 1: + scroll_distance = itemcount * self.itemheight + else: + scroll_distance = (itemcount - 1) * self.itemheight + while scroll_distance <= scrolltotal_distance: + self.compute_swipe(main, self.clickx + self.swipeoffsetx, scroll_start_from_y, scroll_distance, + self.responsey) + scrolltotal_distance -= scroll_distance + if scrolltotal_distance > 5: + # Last slide + self.compute_swipe(main, self.clickx + self.swipeoffsetx, scroll_start_from_y, scrolltotal_distance, + self.responsey) + if self.finalclick: + # Click on the last row + self.run_until(main, + lambda: click_coords(self.clickx, self.window_endy - self.itemheight // 2), + lambda: main.appear(self.expected_button) + ) + + def run_until(self, main, func1, func2, times=6, sleeptime=1.5) -> bool: + """ + Repeat the execution of func1 up to a maximum of times or until func2 evaluates to True. + + func1 should perform a single valid operation or internally call a screenshot function. + A screenshot is triggered before evaluating func2. + + After each execution of func1, wait for sleeptime seconds. + + If func2 evaluates to True, exit and return True. Otherwise, return False. + + Note: The comment assumes that func1 produces a meaningful operation or internally calls a screenshot function, + and func2 is evaluated after each execution of func1. + """ + for i in range(times): + main.device.screenshot() + if func2(): + return True + func1() + timer = Timer(sleeptime).start() + while not timer.reached_and_reset(): + pass + main.device.screenshot() + if func2(): + return True + logger.warning("run_until exceeded max times") + return False + + def scroll_right_up(self, main, scrollx=928, times=3): + """ + scroll to top + """ + for i in range(times): + main.device.swipe((scrollx, 226), (scrollx, 561), duration=0.2) + timer = Timer(0.5).start() + while not timer.reached_and_reset(): + pass diff --git a/tasks/schedule/ui.py b/tasks/schedule/ui.py new file mode 100644 index 0000000..354235d --- /dev/null +++ b/tasks/schedule/ui.py @@ -0,0 +1,92 @@ +import numpy as np + +from module.base.decorator import Config +from module.base.timer import Timer +from module.logger import logger +from module.ocr.ocr import DigitCounter +from tasks.base.assets.assets_base_page import SCHEDULE_CHECK +from tasks.base.ui import UI +from tasks.schedule.assets.assets_schedule import * +from tasks.schedule.scroll_select import ScrollSelect + +SCROLL_SELECT = ScrollSelect(window_button=SCROLL, first_item_button=FIRST_ITEM, expected_button=LOCATIONS, clickx=1116) +xs = np.linspace(299, 995, 3, dtype=int) +ys = np.linspace(268, 573, 3, dtype=int) + + +class ScheduleUI(UI): + @Config.when(Emulator_GameLanguage='jp') + def set_clickx(self): + SCROLL_SELECT.clickx = 1114 + + @Config.when(Emulator_GameLanguage=None) + def set_clickx(self): + pass + + def select_then_check(self, dest_enter: ButtonWrapper, dest_check: ButtonWrapper): + timer = Timer(8, 10).start() + while 1: + self.device.screenshot() + self.appear_then_click(dest_enter, interval=1) + if self.handle_affection_level_up() or self.handle_location_level_up(): + timer.reset() + if self.appear(dest_check): + return True + + if timer.reached(): + return False + + def click_then_check(self, coords, dest_check: ButtonWrapper): + click_coords = self.device.click_methods.get(self.config.Emulator_ControlMethod, self.device.click_adb) + timer = Timer(3, 2).start() + wait = Timer(1).start() + while 1: + click_coords(*coords) + self.device.screenshot() + if self.appear_then_click(dest_check): + return True + while not wait.reached_and_reset(): + pass + if timer.reached(): + return False + + def enter_location(self, location): + SCROLL_SELECT.select_location(self, location) + if not self.appear(LOCATIONS): + logger.error("Unable to navigate to page for location {}".format(location + 1)) + return False + return self.select_then_check(LOCATIONS, LOCATIONS_POPUP) + + def select_classrooms(self, ticket, classrooms): + for classroom in classrooms: + if ticket == 0: + return False + classroom = int(classroom) - 1 + col = int(classroom % len(xs)) + row = int((classroom - col) / len(ys)) + targetloc = (xs[col], ys[row]) + if not self.click_then_check(targetloc, START_LESSON): + logger.info(f"Classroom {classroom + 1} does not exist or has already been clicked") + continue + if self.select_then_check(START_LESSON, CONFIRM): + ticket -= 1 + if not self.select_then_check(CONFIRM, LOCATIONS_POPUP): + logger.error(f"An unexpected error occurred. Skipping location") + break + return True + + def get_ticket(self): + """ + Page: + in: page_bounty + """ + if not self.appear(SCHEDULE_CHECK): + logger.warning('OCR failed due to invalid page') + return False + ticket, _, total = DigitCounter(OCR_TICKET).ocr_single_line(self.device.image) + if total == 0: + logger.warning('Invalid ticket') + return False + logger.attr('ScheduleTicket', ticket) + # self.config.stored.BountyTicket.set(ticket) + return ticket diff --git a/tasks/scrimmage/assets/assets_scrimmage.py b/tasks/scrimmage/assets/assets_scrimmage.py index d463885..b6d4c97 100644 --- a/tasks/scrimmage/assets/assets_scrimmage.py +++ b/tasks/scrimmage/assets/assets_scrimmage.py @@ -12,7 +12,20 @@ CHECK_GEHENNA = ButtonWrapper( color=(201, 205, 206), button=(109, 149, 195, 177), ), - en=None, + en=Button( + file='./assets/en/scrimmage/CHECK_GEHENNA.png', + area=(103, 145, 252, 182), + search=(83, 125, 272, 202), + color=(202, 206, 208), + button=(103, 145, 252, 182), + ), + zht=Button( + file='./assets/zht/scrimmage/CHECK_GEHENNA.png', + area=(96, 141, 213, 182), + search=(76, 121, 233, 202), + color=(191, 194, 197), + button=(96, 141, 213, 182), + ), ) CHECK_MILLENNIUM = ButtonWrapper( name='CHECK_MILLENNIUM', @@ -23,7 +36,20 @@ CHECK_MILLENNIUM = ButtonWrapper( color=(202, 206, 208), button=(108, 148, 254, 178), ), - en=None, + en=Button( + file='./assets/en/scrimmage/CHECK_MILLENNIUM.png', + area=(104, 145, 283, 182), + search=(84, 125, 303, 202), + color=(199, 202, 204), + button=(104, 145, 283, 182), + ), + zht=Button( + file='./assets/zht/scrimmage/CHECK_MILLENNIUM.png', + area=(97, 136, 185, 181), + search=(77, 116, 205, 201), + color=(216, 219, 220), + button=(97, 136, 185, 181), + ), ) CHECK_SCRIMMAGE = ButtonWrapper( name='CHECK_SCRIMMAGE', @@ -34,7 +60,20 @@ CHECK_SCRIMMAGE = ButtonWrapper( color=(96, 119, 144), button=(651, 90, 889, 130), ), - en=None, + en=Button( + file='./assets/en/scrimmage/CHECK_SCRIMMAGE.png', + area=(651, 88, 891, 130), + search=(631, 68, 911, 150), + color=(92, 116, 141), + button=(651, 88, 891, 130), + ), + zht=Button( + file='./assets/zht/scrimmage/CHECK_SCRIMMAGE.png', + area=(649, 82, 890, 129), + search=(629, 62, 910, 149), + color=(85, 110, 136), + button=(649, 82, 890, 129), + ), ) CHECK_TRINITY = ButtonWrapper( name='CHECK_TRINITY', @@ -45,18 +84,44 @@ CHECK_TRINITY = ButtonWrapper( color=(204, 208, 210), button=(115, 148, 250, 178), ), - en=None, + en=Button( + file='./assets/en/scrimmage/CHECK_TRINITY.png', + area=(100, 143, 219, 187), + search=(80, 123, 239, 207), + color=(212, 216, 217), + button=(100, 143, 219, 187), + ), + zht=Button( + file='./assets/zht/scrimmage/CHECK_TRINITY.png', + area=(98, 138, 182, 183), + search=(78, 118, 202, 203), + color=(226, 228, 229), + button=(98, 138, 182, 183), + ), ) OCR_TICKET = ButtonWrapper( name='OCR_TICKET', jp=Button( file='./assets/jp/scrimmage/OCR_TICKET.png', - area=(195, 85, 235, 113), - search=(175, 65, 255, 133), - color=(206, 211, 215), - button=(195, 85, 235, 113), + area=(194, 87, 258, 112), + search=(174, 67, 278, 132), + color=(207, 213, 218), + button=(194, 87, 258, 112), + ), + en=Button( + file='./assets/en/scrimmage/OCR_TICKET.png', + area=(229, 88, 289, 112), + search=(209, 68, 309, 132), + color=(199, 206, 212), + button=(229, 88, 289, 112), + ), + zht=Button( + file='./assets/zht/scrimmage/OCR_TICKET.png', + area=(170, 87, 220, 119), + search=(150, 67, 240, 139), + color=(208, 213, 216), + button=(170, 87, 220, 119), ), - en=None, ) SELECT_GEHENNA = ButtonWrapper( name='SELECT_GEHENNA', @@ -67,7 +132,20 @@ SELECT_GEHENNA = ButtonWrapper( color=(207, 217, 225), button=(1125, 275, 1224, 310), ), - en=None, + en=Button( + file='./assets/en/scrimmage/SELECT_GEHENNA.png', + area=(1076, 273, 1223, 313), + search=(1056, 253, 1243, 333), + color=(199, 208, 216), + button=(1076, 273, 1223, 313), + ), + zht=Button( + file='./assets/zht/scrimmage/SELECT_GEHENNA.png', + area=(1115, 268, 1228, 313), + search=(1095, 248, 1248, 333), + color=(186, 196, 207), + button=(1115, 268, 1228, 313), + ), ) SELECT_MILLENNIUM = ButtonWrapper( name='SELECT_MILLENNIUM', @@ -78,7 +156,20 @@ SELECT_MILLENNIUM = ButtonWrapper( color=(206, 214, 222), button=(1069, 381, 1217, 417), ), - en=None, + en=Button( + file='./assets/en/scrimmage/SELECT_MILLENNIUM.png', + area=(1045, 380, 1224, 417), + search=(1025, 360, 1244, 437), + color=(193, 202, 211), + button=(1045, 380, 1224, 417), + ), + zht=Button( + file='./assets/zht/scrimmage/SELECT_MILLENNIUM.png', + area=(1144, 375, 1225, 420), + search=(1124, 355, 1245, 440), + color=(207, 217, 225), + button=(1144, 375, 1225, 420), + ), ) SELECT_TRINITY = ButtonWrapper( name='SELECT_TRINITY', @@ -89,5 +180,18 @@ SELECT_TRINITY = ButtonWrapper( color=(213, 221, 228), button=(1074, 165, 1221, 204), ), - en=None, + en=Button( + file='./assets/en/scrimmage/SELECT_TRINITY.png', + area=(1118, 164, 1224, 205), + search=(1098, 144, 1244, 225), + color=(200, 209, 218), + button=(1118, 164, 1224, 205), + ), + zht=Button( + file='./assets/zht/scrimmage/SELECT_TRINITY.png', + area=(1148, 162, 1225, 202), + search=(1128, 142, 1245, 222), + color=(216, 226, 234), + button=(1148, 162, 1225, 202), + ), ) diff --git a/tasks/scrimmage/scrimmage.py b/tasks/scrimmage/scrimmage.py index 14a8db1..171e75f 100644 --- a/tasks/scrimmage/scrimmage.py +++ b/tasks/scrimmage/scrimmage.py @@ -7,6 +7,7 @@ from tasks.base.assets.assets_base_page import BACK from tasks.base.page import page_school_exchange from tasks.scrimmage.assets.assets_scrimmage import * from tasks.scrimmage.ui import ScrimmageUI +from tasks.stage.ap import AP class ScrimmageStatus(Enum): @@ -18,15 +19,20 @@ class ScrimmageStatus(Enum): FINISH = 5 -class Scrimmage(ScrimmageUI): +class Scrimmage(ScrimmageUI, AP): + _stage_ap = [10, 15, 15, 15] + + @property + def stage_ap(self): + return self._stage_ap + @property def scrimmage_info(self): - bounty = (SELECT_TRINITY, SELECT_GEHENNA, SELECT_MILLENNIUM) + scrimmage = (SELECT_TRINITY, SELECT_GEHENNA, SELECT_MILLENNIUM) check = (CHECK_TRINITY, CHECK_GEHENNA, CHECK_MILLENNIUM) stage = (self.config.Trinity_Stage, self.config.Gehenna_Stage, self.config.Millennium_Stage) count = (self.config.Trinity_Count, self.config.Gehenna_Count, self.config.Millennium_Count) - ap = (10 if stage == 1 else 15 for stage in stage) - info = zip(bounty, check, stage, count, ap) + info = zip(scrimmage, check, stage, count) return filter(lambda x: x[3] > 0, info) @property @@ -38,21 +44,18 @@ class Scrimmage(ScrimmageUI): return task def error_handler(self): - action = self.config.Bounty_OnError + action = self.config.Scrimmage_OnError if action == 'stop': raise RequestHumanTakeover elif action == 'skip': - self.config.task_delay(server_update=True) - self.config.task_stop() + with self.config.multi_set(): + self.config.task_delay(server_update=True) + self.config.task_stop() @property def is_ticket_enough(self) -> bool: return self.current_ticket >= self.current_count - @property - def is_ap_enough(self) -> bool: - return self.current_ap >= self.current_task_ap - @property def current_scrimmage(self): return self.task[0][:2] @@ -65,25 +68,10 @@ class Scrimmage(ScrimmageUI): def current_count(self): return self.task[0][3] - @property - def current_task_ap(self): - return self.task[0][4] - @property def current_ticket(self): return self.config.stored.ScrimmageTicket.value - @property - def current_ap(self): - return self.config.stored.AP.value - - def update_ap(self): - ap = self.config.stored.AP - ap_old = ap.value - ap_new = ap_old - self.current_task_ap - ap.set(ap_new, ap.total) - logger.info(f'Set AP: {ap_old} -> {ap_new}') - def handle_scrimmage(self, status): match status: case ScrimmageStatus.OCR: @@ -95,19 +83,16 @@ class Scrimmage(ScrimmageUI): if not self.is_ticket_enough: logger.warning('Scrimmage ticket not enough') self.error_handler() - if not self.is_ap_enough: - logger.warning('AP not enough') - self.error_handler() if self.select_scrimmage(*self.current_scrimmage): return ScrimmageStatus.ENTER case ScrimmageStatus.ENTER: - if self.enter_stage(self.current_stage): + if self.enter_stage(self.current_stage) and self.is_ap_enough(self.current_count, self.current_stage): return ScrimmageStatus.SWEEP else: self.error_handler() case ScrimmageStatus.SWEEP: if self.do_sweep(self.current_count): - self.update_ap() + self.update_ap(self.current_count, self.current_stage) self.task.pop(0) return ScrimmageStatus.END return ScrimmageStatus.ENTER diff --git a/tasks/scrimmage/ui.py b/tasks/scrimmage/ui.py index ddb9383..badffdd 100644 --- a/tasks/scrimmage/ui.py +++ b/tasks/scrimmage/ui.py @@ -3,6 +3,7 @@ from module.logger import logger from module.ocr.ocr import DigitCounter from tasks.base.ui import UI from tasks.scrimmage.assets.assets_scrimmage import * +from tasks.stage.ap import AP from tasks.stage.list import StageList from tasks.stage.sweep import StageSweep @@ -10,7 +11,7 @@ SCRIMMAGE_LIST = StageList('ScrimmageList') SCRIMMAGE_SWEEP = StageSweep('ScrimmageSweep', 6) -class ScrimmageUI(UI): +class ScrimmageUI(UI, AP): def select_scrimmage(self, dest_enter: ButtonWrapper, dest_check: ButtonWrapper): timer = Timer(5, 10).start() while 1: @@ -22,6 +23,10 @@ class ScrimmageUI(UI): return False def enter_stage(self, index: int) -> bool: + if not index: + index = SCRIMMAGE_LIST.insight_max_sweepable_index(self) + # set AP stage + self.set_stage(index) if SCRIMMAGE_LIST.select_index_enter(self, index, insight=False): return True return False diff --git a/tasks/shop/assets/assets_shop.py b/tasks/shop/assets/assets_shop.py new file mode 100644 index 0000000..e5bd337 --- /dev/null +++ b/tasks/shop/assets/assets_shop.py @@ -0,0 +1,245 @@ +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 ``` + +CONFIRM_PURCHASE = ButtonWrapper( + name='CONFIRM_PURCHASE', + jp=Button( + file='./assets/jp/shop/CONFIRM_PURCHASE.png', + area=(465, 231, 809, 307), + search=(445, 211, 829, 327), + color=(213, 214, 215), + button=(670, 458, 865, 516), + ), + en=Button( + file='./assets/en/shop/CONFIRM_PURCHASE.png', + area=(467, 231, 807, 309), + search=(447, 211, 827, 329), + color=(217, 218, 219), + button=(668, 458, 865, 514), + ), + zht=Button( + file='./assets/zht/shop/CONFIRM_PURCHASE.png', + area=(523, 224, 755, 320), + search=(503, 204, 775, 340), + color=(214, 215, 215), + button=(669, 456, 873, 518), + ), +) +CONFIRM_REFRESH = ButtonWrapper( + name='CONFIRM_REFRESH', + jp=Button( + file='./assets/jp/shop/CONFIRM_REFRESH.png', + area=(451, 271, 825, 304), + search=(431, 251, 845, 324), + color=(200, 201, 202), + button=(677, 430, 861, 498), + ), + en=Button( + file='./assets/en/shop/CONFIRM_REFRESH.png', + area=(474, 271, 806, 306), + search=(454, 251, 826, 326), + color=(202, 203, 204), + button=(675, 434, 863, 500), + ), + zht=Button( + file='./assets/zht/shop/CONFIRM_REFRESH.png', + area=(504, 260, 782, 308), + search=(484, 240, 802, 328), + color=(205, 206, 207), + button=(671, 432, 866, 498), + ), +) +ITEM_LIST = ButtonWrapper( + name='ITEM_LIST', + jp=Button( + file='./assets/jp/shop/ITEM_LIST.png', + area=(622, 126, 1241, 612), + search=(602, 106, 1261, 632), + color=(194, 207, 215), + button=(622, 126, 1241, 612), + ), + en=Button( + file='./assets/en/shop/ITEM_LIST.png', + area=(625, 127, 1244, 610), + search=(605, 107, 1264, 630), + color=(193, 206, 213), + button=(625, 127, 1244, 610), + ), + zht=Button( + file='./assets/zht/shop/ITEM_LIST.png', + area=(625, 127, 1244, 610), + search=(605, 107, 1264, 630), + color=(193, 206, 213), + button=(625, 127, 1244, 610), + ), +) +NORMAL_OFF = ButtonWrapper( + name='NORMAL_OFF', + jp=Button( + file='./assets/jp/shop/NORMAL_OFF.png', + area=(6, 109, 206, 167), + search=(0, 89, 226, 187), + color=(240, 241, 240), + button=(6, 109, 206, 167), + ), + en=Button( + file='./assets/en/shop/NORMAL_OFF.png', + area=(4, 111, 213, 167), + search=(0, 91, 233, 187), + color=(248, 248, 245), + button=(4, 111, 213, 167), + ), + zht=Button( + file='./assets/zht/shop/NORMAL_OFF.png', + area=(4, 104, 214, 155), + search=(0, 84, 234, 175), + color=(249, 248, 245), + button=(4, 104, 214, 155), + ), +) +NORMAL_ON = ButtonWrapper( + name='NORMAL_ON', + jp=Button( + file='./assets/jp/shop/NORMAL_ON.png', + area=(9, 114, 205, 164), + search=(0, 94, 225, 184), + color=(71, 88, 91), + button=(9, 114, 205, 164), + ), + en=Button( + file='./assets/en/shop/NORMAL_ON.png', + area=(4, 109, 212, 170), + search=(0, 89, 232, 190), + color=(57, 78, 96), + button=(4, 109, 212, 170), + ), + zht=Button( + file='./assets/zht/shop/NORMAL_ON.png', + area=(2, 109, 206, 167), + search=(0, 89, 226, 187), + color=(56, 77, 97), + button=(2, 109, 206, 167), + ), +) +OCR_REFRESH = ButtonWrapper( + name='OCR_REFRESH', + jp=Button( + file='./assets/jp/shop/OCR_REFRESH.png', + area=(674, 308, 717, 332), + search=(654, 288, 737, 352), + color=(211, 212, 213), + button=(674, 308, 717, 332), + ), + en=Button( + file='./assets/en/shop/OCR_REFRESH.png', + area=(712, 302, 762, 344), + search=(692, 282, 782, 364), + color=(225, 225, 226), + button=(712, 302, 762, 344), + ), + zht=Button( + file='./assets/zht/shop/OCR_REFRESH.png', + area=(673, 303, 731, 341), + search=(653, 283, 751, 361), + color=(226, 227, 227), + button=(673, 303, 731, 341), + ), +) +PURCHASE = ButtonWrapper( + name='PURCHASE', + jp=Button( + file='./assets/jp/shop/PURCHASE.png', + area=(1106, 637, 1223, 685), + search=(1086, 617, 1243, 705), + color=(235, 217, 68), + button=(1106, 637, 1223, 685), + ), + en=Button( + file='./assets/en/shop/PURCHASE.png', + area=(1102, 640, 1227, 684), + search=(1082, 620, 1247, 704), + color=(226, 206, 65), + button=(1102, 640, 1227, 684), + ), + zht=Button( + file='./assets/zht/shop/PURCHASE.png', + area=(1105, 640, 1231, 681), + search=(1085, 620, 1251, 701), + color=(212, 191, 62), + button=(1105, 640, 1231, 681), + ), +) +REFRESH = ButtonWrapper( + name='REFRESH', + jp=Button( + file='./assets/jp/shop/REFRESH.png', + area=(1096, 641, 1223, 683), + search=(1076, 621, 1243, 703), + color=(233, 235, 238), + button=(1096, 641, 1223, 683), + ), + en=Button( + file='./assets/en/shop/REFRESH.png', + area=(1098, 643, 1223, 682), + search=(1078, 623, 1243, 702), + color=(231, 234, 237), + button=(1098, 643, 1223, 682), + ), + zht=Button( + file='./assets/zht/shop/REFRESH.png', + area=(1096, 644, 1226, 681), + search=(1076, 624, 1246, 701), + color=(221, 224, 229), + button=(1096, 644, 1226, 681), + ), +) +TC_OFF = ButtonWrapper( + name='TC_OFF', + jp=Button( + file='./assets/jp/shop/TC_OFF.png', + area=(9, 499, 199, 553), + search=(0, 479, 219, 573), + color=(230, 234, 237), + button=(9, 499, 199, 553), + ), + en=Button( + file='./assets/en/shop/TC_OFF.png', + area=(2, 503, 209, 558), + search=(0, 483, 229, 578), + color=(239, 242, 244), + button=(2, 503, 209, 558), + ), + zht=Button( + file='./assets/zht/shop/TC_OFF.png', + area=(4, 503, 211, 553), + search=(0, 483, 231, 573), + color=(235, 239, 241), + button=(4, 503, 211, 553), + ), +) +TC_ON = ButtonWrapper( + name='TC_ON', + jp=Button( + file='./assets/jp/shop/TC_ON.png', + area=(9, 502, 199, 550), + search=(0, 482, 219, 570), + color=(77, 95, 94), + button=(9, 502, 199, 550), + ), + en=Button( + file='./assets/en/shop/TC_ON.png', + area=(3, 493, 208, 548), + search=(0, 473, 228, 568), + color=(62, 84, 99), + button=(3, 493, 208, 548), + ), + zht=Button( + file='./assets/zht/shop/TC_ON.png', + area=(0, 492, 211, 548), + search=(0, 472, 231, 568), + color=(65, 86, 98), + button=(0, 492, 211, 548), + ), +) diff --git a/tasks/shop/shop.py b/tasks/shop/shop.py new file mode 100644 index 0000000..957f6a9 --- /dev/null +++ b/tasks/shop/shop.py @@ -0,0 +1,118 @@ +from enum import Flag + +from module.base.timer import Timer +from module.logger import logger +from module.ui.switch import Switch +from tasks.base.assets.assets_base_page import BACK +from tasks.base.page import page_main, page_shop +from tasks.shop.assets.assets_shop import * +from tasks.shop.ui import ShopUI + + +class ShopStatus(Flag): + SELECT_SHOP = 0 + SELECT_ITEMS = 1 + PURCHASE = 2 + REFRESH = 3 + END = 4 + FINISH = -1 + + +class Shop(ShopUI): + @property + def shop_info(self): + """Similiar to bounty_info and scrimmage_info. + Returns a list with elements the select button, check button, how many times do make purchases and the list of items""" + info = [] + if self.config.NormalShop_Enable: + normal_config = self.config.cross_get(["Shop", "NormalShop"]) + normal_items = [num for num in range(1, 21) if normal_config[str(num)]] + if normal_items: + SWITCH_NORMAL = Switch('NormalShop_Switch') + SWITCH_NORMAL.add_state('on', NORMAL_ON) + SWITCH_NORMAL.add_state('off', NORMAL_OFF) + info.append([SWITCH_NORMAL, self.config.NormalShop_Purchases, normal_items]) + + if self.config.TacticalChallengeShop_Enable: + tc_config = self.config.cross_get(["Shop", "TacticalChallengeShop"]) + tc_items = [num for num in range(1, 16) if tc_config[str(num)]] + if tc_items: + SWITCH_TC = Switch('TacticalChallengeShop_Switch') + SWITCH_TC.add_state('on', TC_ON) + SWITCH_TC.add_state('off', TC_OFF) + info.append([SWITCH_TC, self.config.TacticalChallengeShop_Purchases, tc_items]) + + return info + + @property + def valid_task(self) -> list: + task = self.shop_info + if not task: + logger.warning('Shop enabled but no task set') + return task + + @property + def current_shop(self): + return self.task[0][0] + + @property + def current_purchase_count(self): + return self.task[0][1] + + @property + def current_item_list(self): + return self.task[0][2] + + def handle_shop(self, status): + match status: + case ShopStatus.SELECT_SHOP: + if not self.task: + return ShopStatus.FINISH + if self.select_shop(self.current_shop): + self.reset_swipe_flags() + return ShopStatus.SELECT_ITEMS + case ShopStatus.SELECT_ITEMS: + self.select_items(self.current_item_list) + return ShopStatus.PURCHASE + case ShopStatus.PURCHASE: + if self.make_purchase() and self.current_purchase_count > 1: + return ShopStatus.REFRESH + return ShopStatus.END + case ShopStatus.REFRESH: + if self.refresh_shop(self.current_purchase_count): + return ShopStatus.SELECT_SHOP + return ShopStatus.END + case ShopStatus.END: + if self.appear(page_shop.check_button) and self.match_color(page_shop.check_button): + self.task.pop(0) + return ShopStatus.SELECT_SHOP + self.click_with_interval(BACK, interval=2) + case ShopStatus.FINISH: + return status + case _: + logger.warning(f'Invalid status: {status}') + return status + + def run(self): + """Reset the shop and items position by going main and then shop""" + self.ui_ensure(page_main) + self.ui_ensure(page_shop) + + self.task = self.valid_task + action_timer = Timer(0.5, 1) + status = ShopStatus.SELECT_SHOP + + while 1: + self.device.screenshot() + + if self.ui_additional(): + continue + + if action_timer.reached_and_reset(): + logger.attr('Status', status) + status = self.handle_shop(status) + + if status == ShopStatus.FINISH: + break + + self.config.task_delay(server_update=True) diff --git a/tasks/shop/ui.py b/tasks/shop/ui.py new file mode 100644 index 0000000..1232075 --- /dev/null +++ b/tasks/shop/ui.py @@ -0,0 +1,139 @@ +import numpy as np + +from module.base.base import ModuleBase +from module.base.timer import Timer +from module.base.utils import area_size +from module.logger import logger +from module.ocr.ocr import DigitCounter +from tasks.base.ui import UI +from tasks.shop.assets.assets_shop import * + +ITEM_POSITIONS = { + 1: (650, 200), 2: (805, 200), 3: (960, 200), 4: (1110, 200), + 5: (650, 460), 6: (805, 460), 7: (960, 460), 8: (1110, 460), + 9: (650, 200), 10: (805, 200), 11: (960, 200), 12: (1110, 200), + 13: (650, 460), 14: (805, 460), 15: (960, 460), 16: (1110, 460), + 17: (650, 460), 18: (805, 460), 19: (960, 460), 20: (1110, 460), +} + + +class ShopUI(UI): + def __init__(self, config, device): + super().__init__(config, device) + + self.click_coords = self.device.click_methods.get(self.config.Emulator_ControlMethod, self.device.click_adb) + self.swipe_vector_range = (0.85, 0.9) + self.swipe_flags = {8: False, 16: False} + self.list = ITEM_LIST + + 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.list.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.list.button) + + def select_then_check(self, dest_enter: ButtonWrapper, dest_check: ButtonWrapper): + 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 + + def select_shop(self, shop_switch): + """ + Set skip switch to on + Returns: + True if switch is set, False if switch not found + """ + if not shop_switch.appear(main=self): + logger.info(f'{shop_switch.name} not found') + return False + shop_switch.set('on', main=self) + + return True + + def select_items(self, item_list): + """ + Select items in the item list checking if swipe is required each time. + However, swipes are inaccurate and clicks too fast. + """ + timer = Timer(1).start() + for item in item_list: + if self.should_swipe(item): + self.swipe_page('down', self) + + self.wait_until_stable( + self.list.button, + timer=Timer(3, 0), + timeout=Timer(1.5, 5) + ) + while not timer.reached_and_reset(): + pass + self.click_coords(*ITEM_POSITIONS[item]) + + def should_swipe(self, item): + """ + Return True based on two checkpoints: + one at 8 and the other at 16. + Only once for each checkpoint. + """ + if (9 <= item <= 16) and not self.swipe_flags[8]: + self.swipe_flags[8] = True + return True + elif item >= 17 and not self.swipe_flags[16]: + self.swipe_flags[16] = True + return True + return False + + def reset_swipe_flags(self): + self.swipe_flags[8], self.swipe_flags[16] = False, False + + def make_purchase(self): + if self.match_color(PURCHASE) and self.select_then_check(PURCHASE, CONFIRM_PURCHASE) and self.appear_then_click( + CONFIRM_PURCHASE): + return True + logger.warning("No items were selected. Unable to purchase.") + return False + + def refresh_shop(self, need_count): + """ + Refresh the shop + """ + refresh_count = self.get_refresh_count() + if refresh_count: + purchased_count = 4 - refresh_count + if need_count > purchased_count and self.appear_then_click(CONFIRM_REFRESH): + logger.info("Refreshed the shop") + return True + return False + + def get_refresh_count(self): + if not self.select_then_check(REFRESH, CONFIRM_REFRESH): + logger.warning('OCR failed due to invalid page') + return False + count, _, total = DigitCounter(OCR_REFRESH).ocr_single_line(self.device.image) + if total == 0: + logger.warning('Invalid count') + return False + return count diff --git a/tasks/stage/ap.py b/tasks/stage/ap.py new file mode 100644 index 0000000..e63a97f --- /dev/null +++ b/tasks/stage/ap.py @@ -0,0 +1,48 @@ +from module.base.base import ModuleBase +from module.logger import logger + + +class AP(ModuleBase): + _stage = 0 + + @property + def stage(self) -> int: + return self._stage + + @classmethod + def set_stage(cls, stage: int): + cls._stage = stage + + @property + def stage_ap(self) -> int | list: + """ + To be redefined in subclass. + + Returns: + Task ap + """ + return 0 + + @property + def current_ap(self): + return self.config.stored.AP.value + + def update_ap(self, count: int, stage: int = None): + ap = self.config.stored.AP + ap_old = ap.value + ap_new = ap_old - self.stage_ap_cost(stage) * count + ap.set(ap_new, ap.total) + logger.info(f'Set AP: {ap_old} -> {ap_new}') + + def stage_ap_cost(self, stage: int = None) -> int: + if isinstance(self.stage_ap, int): + return self.stage_ap + if isinstance(self.stage_ap, list): + if not stage: + stage = self.stage if self.stage else len(self.stage_ap) + return self.stage_ap[stage - 1] + + def is_ap_enough(self, count: int, stage: int = None) -> bool: + cost = self.stage_ap_cost(stage) * count + logger.info(f'Check AP: {self.current_ap} / {cost}') + return self.current_ap >= cost diff --git a/tasks/stage/assets/assets_stage_list.py b/tasks/stage/assets/assets_stage_list.py index 4b17ea6..63b3ae6 100644 --- a/tasks/stage/assets/assets_stage_list.py +++ b/tasks/stage/assets/assets_stage_list.py @@ -12,7 +12,20 @@ OCR_INDEX = ButtonWrapper( color=(195, 196, 193), button=(701, 149, 740, 656), ), - en=None, + en=Button( + file='./assets/en/stage/list/OCR_INDEX.png', + area=(701, 149, 740, 656), + search=(681, 129, 760, 676), + color=(195, 196, 193), + button=(701, 149, 740, 656), + ), + zht=Button( + file='./assets/zht/stage/list/OCR_INDEX.png', + area=(701, 149, 740, 656), + search=(681, 129, 760, 676), + color=(195, 196, 193), + button=(701, 149, 740, 656), + ), ) STAGE_ENTER = ButtonWrapper( name='STAGE_ENTER', @@ -23,7 +36,20 @@ STAGE_ENTER = ButtonWrapper( color=(106, 171, 200), button=(1093, 173, 1142, 199), ), - en=None, + en=Button( + file='./assets/en/stage/list/STAGE_ENTER.png', + area=(1080, 168, 1156, 204), + search=(1060, 148, 1176, 224), + color=(118, 195, 226), + button=(1080, 168, 1156, 204), + ), + zht=Button( + file='./assets/zht/stage/list/STAGE_ENTER.png', + area=(1075, 170, 1159, 202), + search=(1055, 150, 1179, 222), + color=(119, 197, 227), + button=(1075, 170, 1159, 202), + ), ) STAGE_ITEM = ButtonWrapper( name='STAGE_ITEM', @@ -34,7 +60,20 @@ STAGE_ITEM = ButtonWrapper( color=(212, 228, 233), button=(687, 148, 1181, 227), ), - en=None, + en=Button( + file='./assets/en/stage/list/STAGE_ITEM.png', + area=(687, 148, 1181, 227), + search=(667, 128, 1201, 247), + color=(212, 228, 233), + button=(687, 148, 1181, 227), + ), + zht=Button( + file='./assets/zht/stage/list/STAGE_ITEM.png', + area=(687, 148, 1181, 227), + search=(667, 128, 1201, 247), + color=(212, 228, 233), + button=(687, 148, 1181, 227), + ), ) STAGE_LIST = ButtonWrapper( name='STAGE_LIST', @@ -45,7 +84,20 @@ STAGE_LIST = ButtonWrapper( color=(194, 204, 209), button=(675, 136, 1190, 676), ), - en=None, + en=Button( + file='./assets/en/stage/list/STAGE_LIST.png', + area=(675, 136, 1190, 676), + search=(655, 116, 1210, 696), + color=(194, 204, 209), + button=(675, 136, 1190, 676), + ), + zht=Button( + file='./assets/zht/stage/list/STAGE_LIST.png', + area=(675, 136, 1190, 676), + search=(655, 116, 1210, 696), + color=(194, 204, 209), + button=(675, 136, 1190, 676), + ), ) STAGE_STARS = ButtonWrapper( name='STAGE_STARS', @@ -56,5 +108,18 @@ STAGE_STARS = ButtonWrapper( color=(225, 214, 166), button=(693, 192, 746, 212), ), - en=None, + en=Button( + file='./assets/en/stage/list/STAGE_STARS.png', + area=(693, 192, 746, 212), + search=(673, 172, 766, 232), + color=(225, 214, 166), + button=(693, 192, 746, 212), + ), + zht=Button( + file='./assets/zht/stage/list/STAGE_STARS.png', + area=(693, 192, 746, 212), + search=(673, 172, 766, 232), + color=(225, 214, 166), + button=(693, 192, 746, 212), + ), ) diff --git a/tasks/stage/assets/assets_stage_sweep.py b/tasks/stage/assets/assets_stage_sweep.py index c6c0590..a610588 100644 --- a/tasks/stage/assets/assets_stage_sweep.py +++ b/tasks/stage/assets/assets_stage_sweep.py @@ -12,7 +12,20 @@ CHECK_SWEEP = ButtonWrapper( color=(174, 184, 197), button=(638, 188, 722, 212), ), - en=None, + en=Button( + file='./assets/en/stage/sweep/CHECK_SWEEP.png', + area=(654, 187, 706, 215), + search=(634, 167, 726, 235), + color=(169, 180, 194), + button=(654, 187, 706, 215), + ), + zht=Button( + file='./assets/zht/stage/sweep/CHECK_SWEEP.png', + area=(651, 182, 702, 210), + search=(631, 162, 722, 230), + color=(201, 207, 215), + button=(651, 182, 702, 210), + ), ) ENTER = ButtonWrapper( name='ENTER', @@ -23,7 +36,20 @@ ENTER = ButtonWrapper( color=(223, 207, 68), button=(791, 514, 1080, 568), ), - en=None, + en=Button( + file='./assets/en/stage/sweep/ENTER.png', + area=(781, 508, 1085, 572), + search=(761, 488, 1105, 592), + color=(226, 211, 69), + button=(781, 508, 1085, 572), + ), + zht=Button( + file='./assets/zht/stage/sweep/ENTER.png', + area=(764, 518, 1109, 574), + search=(744, 498, 1129, 594), + color=(226, 210, 67), + button=(764, 518, 1109, 574), + ), ) EXIT = ButtonWrapper( name='EXIT', @@ -34,7 +60,20 @@ EXIT = ButtonWrapper( color=(185, 193, 203), button=(1114, 127, 1141, 154), ), - en=None, + en=Button( + file='./assets/en/stage/sweep/EXIT.png', + area=(1114, 127, 1141, 154), + search=(1094, 107, 1161, 174), + color=(185, 193, 203), + button=(1114, 127, 1141, 154), + ), + zht=Button( + file='./assets/zht/stage/sweep/EXIT.png', + area=(1114, 127, 1141, 154), + search=(1094, 107, 1161, 174), + color=(185, 193, 203), + button=(1114, 127, 1141, 154), + ), ) MAX = ButtonWrapper( name='MAX', @@ -45,7 +84,20 @@ MAX = ButtonWrapper( color=(216, 222, 228), button=(1054, 279, 1111, 321), ), - en=None, + en=Button( + file='./assets/en/stage/sweep/MAX.png', + area=(1054, 279, 1111, 321), + search=(1034, 259, 1131, 341), + color=(216, 222, 228), + button=(1054, 279, 1111, 321), + ), + zht=Button( + file='./assets/zht/stage/sweep/MAX.png', + area=(1054, 279, 1111, 321), + search=(1034, 259, 1131, 341), + color=(216, 222, 228), + button=(1054, 279, 1111, 321), + ), ) MIN = ButtonWrapper( name='MIN', @@ -56,7 +108,20 @@ MIN = ButtonWrapper( color=(194, 194, 194), button=(760, 278, 816, 322), ), - en=None, + en=Button( + file='./assets/en/stage/sweep/MIN.png', + area=(760, 278, 816, 322), + search=(740, 258, 836, 342), + color=(194, 194, 194), + button=(760, 278, 816, 322), + ), + zht=Button( + file='./assets/zht/stage/sweep/MIN.png', + area=(760, 278, 816, 322), + search=(740, 258, 836, 342), + color=(194, 194, 194), + button=(760, 278, 816, 322), + ), ) MINUS = ButtonWrapper( name='MINUS', @@ -67,7 +132,20 @@ MINUS = ButtonWrapper( color=(221, 222, 222), button=(838, 279, 876, 320), ), - en=None, + en=Button( + file='./assets/en/stage/sweep/MINUS.png', + area=(838, 279, 876, 320), + search=(818, 259, 896, 340), + color=(221, 222, 222), + button=(838, 279, 876, 320), + ), + zht=Button( + file='./assets/zht/stage/sweep/MINUS.png', + area=(838, 279, 876, 320), + search=(818, 259, 896, 340), + color=(221, 222, 222), + button=(838, 279, 876, 320), + ), ) OCR_NUM = ButtonWrapper( name='OCR_NUM', @@ -78,7 +156,20 @@ OCR_NUM = ButtonWrapper( color=(81, 94, 113), button=(896, 281, 975, 323), ), - en=None, + en=Button( + file='./assets/en/stage/sweep/OCR_NUM.png', + area=(896, 281, 975, 323), + search=(876, 261, 995, 343), + color=(81, 94, 113), + button=(896, 281, 975, 323), + ), + zht=Button( + file='./assets/zht/stage/sweep/OCR_NUM.png', + area=(896, 281, 975, 323), + search=(876, 261, 995, 343), + color=(81, 94, 113), + button=(896, 281, 975, 323), + ), ) PLUS = ButtonWrapper( name='PLUS', @@ -89,7 +180,20 @@ PLUS = ButtonWrapper( color=(233, 243, 246), button=(995, 278, 1034, 322), ), - en=None, + en=Button( + file='./assets/en/stage/sweep/PLUS.png', + area=(995, 278, 1034, 322), + search=(975, 258, 1054, 342), + color=(233, 243, 246), + button=(995, 278, 1034, 322), + ), + zht=Button( + file='./assets/zht/stage/sweep/PLUS.png', + area=(995, 278, 1034, 322), + search=(975, 258, 1054, 342), + color=(233, 243, 246), + button=(995, 278, 1034, 322), + ), ) SKIP_OK_LOWER = ButtonWrapper( name='SKIP_OK_LOWER', @@ -100,7 +204,20 @@ SKIP_OK_LOWER = ButtonWrapper( color=(112, 212, 247), button=(541, 551, 740, 616), ), - en=None, + en=Button( + file='./assets/en/stage/sweep/SKIP_OK_LOWER.png', + area=(540, 548, 741, 619), + search=(520, 528, 761, 639), + color=(108, 204, 240), + button=(540, 548, 741, 619), + ), + zht=Button( + file='./assets/zht/stage/sweep/SKIP_OK_LOWER.png', + area=(542, 552, 739, 620), + search=(522, 532, 759, 640), + color=(109, 206, 241), + button=(542, 552, 739, 620), + ), ) SKIP_OK_UPPER = ButtonWrapper( name='SKIP_OK_UPPER', @@ -111,7 +228,20 @@ SKIP_OK_UPPER = ButtonWrapper( color=(112, 212, 248), button=(542, 474, 738, 545), ), - en=None, + en=Button( + file='./assets/en/stage/sweep/SKIP_OK_UPPER.png', + area=(541, 476, 741, 542), + search=(521, 456, 761, 562), + color=(107, 203, 239), + button=(541, 476, 741, 542), + ), + zht=Button( + file='./assets/zht/stage/sweep/SKIP_OK_UPPER.png', + area=(535, 479, 745, 542), + search=(515, 459, 765, 562), + color=(108, 206, 241), + button=(535, 479, 745, 542), + ), ) SKIP_SKIP = ButtonWrapper( name='SKIP_SKIP', @@ -122,7 +252,20 @@ SKIP_SKIP = ButtonWrapper( color=(110, 207, 243), button=(545, 475, 736, 540), ), - en=None, + en=Button( + file='./assets/en/stage/sweep/SKIP_SKIP.png', + area=(543, 477, 739, 542), + search=(523, 457, 759, 562), + color=(110, 209, 244), + button=(543, 477, 739, 542), + ), + zht=Button( + file='./assets/zht/stage/sweep/SKIP_SKIP.png', + area=(539, 479, 743, 538), + search=(519, 459, 763, 558), + color=(109, 208, 243), + button=(539, 479, 743, 538), + ), ) SWEEP = ButtonWrapper( name='SWEEP', @@ -133,7 +276,20 @@ SWEEP = ButtonWrapper( color=(109, 202, 235), button=(796, 385, 1067, 427), ), - en=None, + en=Button( + file='./assets/en/stage/sweep/SWEEP.png', + area=(795, 382, 1066, 431), + search=(775, 362, 1086, 451), + color=(108, 202, 236), + button=(795, 382, 1066, 431), + ), + zht=Button( + file='./assets/zht/stage/sweep/SWEEP.png', + area=(776, 391, 1093, 434), + search=(756, 371, 1113, 454), + color=(105, 199, 233), + button=(776, 391, 1093, 434), + ), ) SWEEP_CONFIRM = ButtonWrapper( name='SWEEP_CONFIRM', @@ -144,5 +300,18 @@ SWEEP_CONFIRM = ButtonWrapper( color=(143, 156, 170), button=(664, 470, 871, 534), ), - en=None, + en=Button( + file='./assets/en/stage/sweep/SWEEP_CONFIRM.png', + area=(587, 143, 695, 182), + search=(567, 123, 715, 202), + color=(196, 203, 211), + button=(665, 471, 871, 535), + ), + zht=Button( + file='./assets/zht/stage/sweep/SWEEP_CONFIRM.png', + area=(596, 142, 678, 180), + search=(576, 122, 698, 200), + color=(185, 193, 202), + button=(662, 474, 869, 534), + ), ) diff --git a/tasks/stage/list.py b/tasks/stage/list.py index 1154d59..705a6a8 100644 --- a/tasks/stage/list.py +++ b/tasks/stage/list.py @@ -1,17 +1,17 @@ -import cv2 +import re + import numpy as np from module.base.base import ModuleBase -from module.base.button import ClickButton, match_template from module.base.timer import Timer -from module.base.utils import area_pad, area_size, area_offset, random_rectangle_vector_opted +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: - drag_vector_range = (0.65, 0.85) + swipe_vector_range = (0.65, 0.85) def __init__( self, @@ -21,7 +21,7 @@ class StageList: button_item: ButtonWrapper = None, button_enter: ButtonWrapper = None, button_stars: ButtonWrapper = None, - drag_direction: str = "down" + swipe_direction: str = "down" ): self.name = name self.stage = button_list if button_list else STAGE_LIST @@ -29,11 +29,11 @@ class StageList: 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.drag_direction = drag_direction + self.swipe_direction = swipe_direction self.current_index_min = 1 self.current_index_max = 1 - self.current_indexes = [] + self.current_indexes: list[tuple[str, tuple]] = [] def __str__(self): return f'StageList({self.name})' @@ -48,11 +48,14 @@ class StageList: @property def _indexes(self) -> list[int]: - return list(map(lambda x: int(x.ocr_text), self.current_indexes)) + return [int(x[0]) for x in self.current_indexes] def load_stage_indexes(self, main: ModuleBase): self.current_indexes = list( - filter(lambda x: x.ocr_text.isdigit(), self.index_ocr.detect_and_ocr(main.device.image)) + 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}') @@ -63,7 +66,7 @@ class StageList: self.current_index_max = max(indexes) logger.attr(self.index_ocr.name, f'Index range: {self.current_index_min} - {self.current_index_max}') - def drag_page(self, direction: str, main: ModuleBase, vector_range=None, reverse=False): + def swipe_page(self, direction: str, main: ModuleBase, vector_range=None, reverse=False): """ Args: direction: up, down @@ -72,7 +75,7 @@ class StageList: reverse (bool): """ if vector_range is None: - vector_range = self.drag_vector_range + vector_range = self.swipe_vector_range vector = np.random.uniform(*vector_range) width, height = area_size(self.stage.button) if direction == 'up': @@ -80,13 +83,12 @@ class StageList: elif direction == 'down': vector = (0, -vector * height) else: - logger.warning(f'Unknown drag direction: {direction}') + logger.warning(f'Unknown swipe direction: {direction}') return if reverse: vector = (-vector[0], -vector[1]) - p1, p2 = random_rectangle_vector_opted(vector, box=self.stage.button) - main.device.drag(p1, p2, name=f'{self.name}_DRAG') + 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: """ @@ -110,18 +112,7 @@ class StageList: self.load_stage_indexes(main=main) if self.current_index_min <= index <= self.current_index_max: - break - - if index < self.current_index_min: - self.drag_page(self.drag_direction, main, reverse=True) - elif index > self.current_index_max: - self.drag_page(self.drag_direction, main) - - main.wait_until_stable( - self.stage.button, - timer=Timer(0, 0), - timeout=Timer(1.5, 5) - ) + return True indexes = self._indexes if indexes and last_indexes == set(indexes): @@ -129,22 +120,82 @@ class StageList: return False last_indexes = set(indexes) - return True + 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) - @staticmethod - def _match_clickable_points(image, template, threshold=0.85): - image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY) - template = cv2.cvtColor(template, cv2.COLOR_RGB2GRAY) + main.wait_until_stable( + self.stage.button, + timer=Timer(0, 0), + timeout=Timer(1.5, 5) + ) - res = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED) - loc = np.where(res >= threshold) - return [point for point in zip(*loc[::-1])] + def insight_max_sweepable_index(self, main: ModuleBase, skip_first_screenshot=True) -> int: + """ + Args: + main: + skip_first_screenshot: - def is_sweepable(self, image, main: ModuleBase, skip_first_screenshot=True) -> bool: - if not skip_first_screenshot: - main.device.screenshot() + Returns: + Index of max sweepable stage + """ + logger.info('Insight sweepable index') + max_sweepable_index = 0 + last_max_sweepable_index = 0 - return match_template(image, self.sweepable.matched_button.image) + 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, @@ -152,7 +203,7 @@ class StageList: index: int, insight: bool = True, sweepable: bool = True, - offset: tuple[int, int] = (-20, -15), + padding: tuple[int, int] = (-20, -15), skip_first_screenshot: bool = True, interval: int = 1.5 ) -> bool: @@ -170,35 +221,30 @@ class StageList: main.device.screenshot() # load index if not insight - if load_index_interval.reached_and_reset() and 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: int(x.ocr_text) == index, self.current_indexes), None) + index_box = next(filter(lambda x: int(x[0]) == index, self.current_indexes), None) if index_box is None: logger.warning(f'No index {index} in {self.index_ocr.name}') continue - stage_item_box = area_pad((*offset, *area_size(self.stage_item))) - search_box = area_offset(stage_item_box, index_box.box[:2]) - search_image = main.image_crop(search_box) - - if sweepable and not self.is_sweepable(search_image, main, skip_first_screenshot): + 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 - points = self._match_clickable_points(search_image, self.enter.matched_button.image) + self.enter.load_search(search_box) + click_button = self.enter.match_multi_template(main.device.image) - if not points: + if not click_button: logger.warning(f'No clickable {self.enter.name}') continue - point = area_offset((0, 0, *area_size(self.enter.button)), points[0]) - click_button = ClickButton(area_offset(point, search_box[:2]), name=self.enter.name) - if click_interval.reached_and_reset(): - main.device.click(click_button) + main.device.click(click_button[0]) return True if timeout.reached(): diff --git a/tasks/stage/mission_list.py b/tasks/stage/mission_list.py new file mode 100644 index 0000000..c622ca6 --- /dev/null +++ b/tasks/stage/mission_list.py @@ -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='zhs') + 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}-?[\dA-Z]$', 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 diff --git a/tasks/stage/sweep.py b/tasks/stage/sweep.py index e98b076..5f96753 100644 --- a/tasks/stage/sweep.py +++ b/tasks/stage/sweep.py @@ -119,18 +119,7 @@ class StageSweep: return main.appear(self.skip_skip) or main.appear(self.skip_ok_upper) or main.appear(self.skip_ok_lower) def load_sweep_num(self, main: ModuleBase): - timer = Timer(0.5, 2).start() - while 1: - main.device.screenshot() - if not timer.reached_and_reset(): - continue - ocr_result = list(filter(lambda x: x.ocr_text.isdigit(), self.num.detect_and_ocr(main.device.image))) - if not ocr_result: - logger.warning(f'No valid num in {self.num.name}') - continue - if len(ocr_result) == 1: - self.current_sweep = int(ocr_result[0].ocr_text) - return + self.current_sweep = self.num.ocr_single_line(main.device.image) def set_sweep_num(self, main: ModuleBase, skip_first_screenshot=True) -> bool: num = self.sweep_num @@ -215,7 +204,7 @@ class StageSweep: if not self.set_mode(mode, num): return False timer = Timer(0.5, 1) - timer_stable = Timer(0.5, 1).start() + timer_stable = Timer(1, 1).start() status = SweepStatus.SELECT while 1: if not timer_stable.reached(): diff --git a/tasks/tactical_challenge/assets/assets_tactical_challenge.py b/tasks/tactical_challenge/assets/assets_tactical_challenge.py index 6b03847..a88d89c 100644 --- a/tasks/tactical_challenge/assets/assets_tactical_challenge.py +++ b/tasks/tactical_challenge/assets/assets_tactical_challenge.py @@ -19,6 +19,13 @@ CHALLENGE_LOSE = ButtonWrapper( color=(206, 213, 219), button=(544, 435, 741, 496), ), + zht=Button( + file='./assets/zht/tactical_challenge/CHALLENGE_LOSE.png', + area=(567, 190, 707, 226), + search=(547, 170, 727, 246), + color=(177, 186, 196), + button=(544, 434, 738, 494), + ), ) CHALLENGE_WIN = ButtonWrapper( name='CHALLENGE_WIN', @@ -36,6 +43,13 @@ CHALLENGE_WIN = ButtonWrapper( color=(193, 201, 210), button=(549, 500, 742, 555), ), + zht=Button( + file='./assets/zht/tactical_challenge/CHALLENGE_WIN.png', + area=(559, 123, 710, 163), + search=(539, 103, 730, 183), + color=(190, 200, 210), + button=(546, 499, 740, 562), + ), ) GET_REWARD_CREDIT = ButtonWrapper( name='GET_REWARD_CREDIT', @@ -53,6 +67,13 @@ GET_REWARD_CREDIT = ButtonWrapper( color=(233, 213, 66), button=(307, 361, 402, 414), ), + zht=Button( + file='./assets/zht/tactical_challenge/GET_REWARD_CREDIT.png', + area=(304, 361, 403, 413), + search=(284, 341, 423, 433), + color=(215, 193, 60), + button=(304, 361, 403, 413), + ), ) GET_REWARD_DAILY = ButtonWrapper( name='GET_REWARD_DAILY', @@ -70,6 +91,13 @@ GET_REWARD_DAILY = ButtonWrapper( color=(232, 212, 65), button=(307, 442, 399, 494), ), + zht=Button( + file='./assets/zht/tactical_challenge/GET_REWARD_DAILY.png', + area=(302, 441, 401, 492), + search=(282, 421, 421, 512), + color=(215, 193, 60), + button=(302, 441, 401, 492), + ), ) GOT_REWARD_CREDIT = ButtonWrapper( name='GOT_REWARD_CREDIT', @@ -87,6 +115,13 @@ GOT_REWARD_CREDIT = ButtonWrapper( color=(209, 211, 210), button=(307, 361, 400, 416), ), + zht=Button( + file='./assets/zht/tactical_challenge/GOT_REWARD_CREDIT.png', + area=(304, 363, 403, 413), + search=(284, 343, 423, 433), + color=(202, 204, 202), + button=(304, 363, 403, 413), + ), ) GOT_REWARD_DAILY = ButtonWrapper( name='GOT_REWARD_DAILY', @@ -104,6 +139,13 @@ GOT_REWARD_DAILY = ButtonWrapper( color=(210, 210, 210), button=(305, 439, 402, 497), ), + zht=Button( + file='./assets/zht/tactical_challenge/GOT_REWARD_DAILY.png', + area=(304, 440, 400, 498), + search=(284, 420, 420, 518), + color=(203, 203, 204), + button=(304, 440, 400, 498), + ), ) OCR_TICKET = ButtonWrapper( name='OCR_TICKET', @@ -121,6 +163,13 @@ OCR_TICKET = ButtonWrapper( color=(223, 226, 228), button=(211, 472, 251, 505), ), + zht=Button( + file='./assets/zht/tactical_challenge/OCR_TICKET.png', + area=(156, 473, 218, 504), + search=(136, 453, 238, 524), + color=(215, 216, 219), + button=(156, 473, 218, 504), + ), ) PLAYER_SELECT_FIRST = ButtonWrapper( name='PLAYER_SELECT_FIRST', @@ -133,11 +182,18 @@ PLAYER_SELECT_FIRST = ButtonWrapper( ), en=Button( file='./assets/en/tactical_challenge/PLAYER_SELECT_FIRST.png', - area=(470, 293, 503, 322), - search=(450, 273, 523, 342), - color=(206, 214, 220), + area=(468, 296, 493, 315), + search=(448, 276, 513, 335), + color=(191, 202, 207), button=(606, 181, 1100, 256), ), + zht=Button( + file='./assets/zht/tactical_challenge/PLAYER_SELECT_FIRST.png', + area=(467, 294, 495, 315), + search=(447, 274, 515, 335), + color=(190, 201, 206), + button=(606, 173, 1099, 260), + ), ) PLAYER_SELECT_SECOND = ButtonWrapper( name='PLAYER_SELECT_SECOND', @@ -150,11 +206,18 @@ PLAYER_SELECT_SECOND = ButtonWrapper( ), en=Button( file='./assets/en/tactical_challenge/PLAYER_SELECT_SECOND.png', - area=(467, 450, 507, 482), - search=(447, 430, 527, 502), - color=(210, 219, 225), + area=(467, 454, 493, 473), + search=(447, 434, 513, 493), + color=(191, 202, 207), button=(610, 338, 1098, 407), ), + zht=Button( + file='./assets/zht/tactical_challenge/PLAYER_SELECT_SECOND.png', + area=(471, 451, 504, 480), + search=(451, 431, 524, 500), + color=(201, 209, 215), + button=(606, 334, 1102, 416), + ), ) PLAYER_SELECT_THIRD = ButtonWrapper( name='PLAYER_SELECT_THIRD', @@ -167,11 +230,18 @@ PLAYER_SELECT_THIRD = ButtonWrapper( ), en=Button( file='./assets/en/tactical_challenge/PLAYER_SELECT_THIRD.png', - area=(472, 612, 504, 641), - search=(452, 592, 524, 661), - color=(208, 216, 222), + area=(468, 613, 492, 631), + search=(448, 593, 512, 651), + color=(188, 199, 204), button=(609, 500, 1101, 565), ), + zht=Button( + file='./assets/zht/tactical_challenge/PLAYER_SELECT_THIRD.png', + area=(470, 609, 503, 638), + search=(450, 589, 523, 658), + color=(201, 209, 215), + button=(613, 490, 1099, 572), + ), ) PREPARE_CHALLENGE = ButtonWrapper( name='PREPARE_CHALLENGE', @@ -189,6 +259,13 @@ PREPARE_CHALLENGE = ButtonWrapper( color=(191, 199, 208), button=(538, 555, 745, 602), ), + zht=Button( + file='./assets/zht/tactical_challenge/PREPARE_CHALLENGE.png', + area=(573, 74, 701, 113), + search=(553, 54, 721, 133), + color=(176, 186, 196), + button=(535, 548, 744, 602), + ), ) SKIP_OFF = ButtonWrapper( name='SKIP_OFF', @@ -206,6 +283,13 @@ SKIP_OFF = ButtonWrapper( color=(96, 137, 171), button=(1088, 585, 1249, 623), ), + zht=Button( + file='./assets/zht/tactical_challenge/SKIP_OFF.png', + area=(1121, 583, 1162, 625), + search=(1101, 563, 1182, 645), + color=(91, 131, 165), + button=(1123, 582, 1250, 625), + ), ) SKIP_ON = ButtonWrapper( name='SKIP_ON', @@ -223,6 +307,13 @@ SKIP_ON = ButtonWrapper( color=(99, 177, 212), button=(1090, 582, 1252, 625), ), + zht=Button( + file='./assets/zht/tactical_challenge/SKIP_ON.png', + area=(1123, 584, 1161, 622), + search=(1103, 564, 1181, 642), + color=(91, 147, 182), + button=(1123, 584, 1251, 622), + ), ) START_CHALLENGE = ButtonWrapper( name='START_CHALLENGE', @@ -240,4 +331,11 @@ START_CHALLENGE = ButtonWrapper( color=(190, 199, 209), button=(1089, 637, 1250, 704), ), + zht=Button( + file='./assets/zht/tactical_challenge/START_CHALLENGE.png', + area=(105, 8, 217, 39), + search=(85, 0, 237, 59), + color=(161, 172, 185), + button=(1089, 640, 1253, 701), + ), ) diff --git a/tasks/tactical_challenge/tactical_challenge.py b/tasks/tactical_challenge/tactical_challenge.py index 16d4fc2..8540c14 100644 --- a/tasks/tactical_challenge/tactical_challenge.py +++ b/tasks/tactical_challenge/tactical_challenge.py @@ -33,10 +33,10 @@ class TacticalChallenge(TacticalChallengeUI): return self.config.stored.TacticalChallengeTicket.value def _player_select(self, select): - if select: + if not select: return random.choice(self.select_players) else: - return self.select_players[select] + return self.select_players[select - 1] def _handle_challenge(self, status): match status: @@ -54,7 +54,7 @@ class TacticalChallenge(TacticalChallengeUI): return TCStatus.PREPARE case TCStatus.PREPARE: self.appear_then_click(PREPARE_CHALLENGE) - if not self.appear(PREPARE_CHALLENGE): + if self.appear(START_CHALLENGE): return TCStatus.SKIP case TCStatus.SKIP: if not self.set_skip(): @@ -62,7 +62,7 @@ class TacticalChallenge(TacticalChallengeUI): return TCStatus.START case TCStatus.START: self.appear_then_click(START_CHALLENGE) - if not self.appear(START_CHALLENGE): + if self.appear(CHALLENGE_WIN) or self.appear(CHALLENGE_LOSE): return TCStatus.RESULT case TCStatus.RESULT: if self.appear_then_click(CHALLENGE_WIN): diff --git a/tasks/tactical_challenge/ui.py b/tasks/tactical_challenge/ui.py index 62e85e9..db94765 100644 --- a/tasks/tactical_challenge/ui.py +++ b/tasks/tactical_challenge/ui.py @@ -26,17 +26,26 @@ class TacticalChallengeUI(UI): def get_reward(self): timer = Timer(10, 10).start() + action_timer = Timer(0.5, 1) while 1: self.device.screenshot() - self.ui_additional() + if not action_timer.reached_and_reset(): + continue + if self.handle_insufficient_inventory(): + logger.warning('Insufficient inventory, skip reward') + return True + if self.ui_additional(): + continue if self.match_color(GOT_REWARD_DAILY) and self.match_color(GOT_REWARD_CREDIT): return True if self.match_color(GET_REWARD_DAILY): - self.device.click(GET_REWARD_DAILY) + self.click_with_interval(GET_REWARD_DAILY, 0.3) logger.info('Get daily reward') + continue if self.match_color(GET_REWARD_CREDIT): - self.device.click(GET_REWARD_CREDIT) + self.click_with_interval(GET_REWARD_CREDIT, 0.3) logger.info('Get credit reward') + continue if timer.reached(): return False diff --git a/tasks/task/assets/assets_task.py b/tasks/task/assets/assets_task.py new file mode 100644 index 0000000..1cf85bf --- /dev/null +++ b/tasks/task/assets/assets_task.py @@ -0,0 +1,125 @@ +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 ``` + +CLAIM = ButtonWrapper( + name='CLAIM', + 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), + search=(915, 619, 1035, 718), + color=(234, 214, 69), + button=(935, 639, 1015, 698), + ), + zht=Button( + file='./assets/zht/task/CLAIM.png', + area=(929, 647, 1016, 693), + search=(909, 627, 1036, 713), + color=(230, 210, 62), + button=(929, 647, 1016, 693), + ), +) +CLAIMED = ButtonWrapper( + name='CLAIMED', + 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), + search=(917, 621, 1030, 716), + color=(212, 212, 211), + button=(937, 641, 1010, 696), + ), + zht=Button( + file='./assets/zht/task/CLAIMED.png', + area=(929, 645, 1014, 699), + search=(909, 625, 1034, 719), + color=(211, 211, 210), + button=(929, 645, 1014, 699), + ), +) +CLAIMED_ALL = ButtonWrapper( + name='CLAIMED_ALL', + 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), + search=(1038, 621, 1260, 720), + color=(193, 193, 196), + button=(1058, 641, 1240, 701), + ), + zht=Button( + file='./assets/zht/task/CLAIMED_ALL.png', + area=(1058, 646, 1246, 701), + search=(1038, 626, 1266, 720), + color=(190, 190, 193), + button=(1058, 646, 1246, 701), + ), +) +CLAIM_ALL = ButtonWrapper( + name='CLAIM_ALL', + 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), + search=(1034, 622, 1263, 720), + color=(236, 219, 67), + button=(1054, 642, 1243, 700), + ), + zht=Button( + file='./assets/zht/task/CLAIM_ALL.png', + area=(1050, 645, 1248, 699), + search=(1030, 625, 1268, 719), + color=(231, 213, 65), + button=(1050, 645, 1248, 699), + ), +) +COMPLETE = ButtonWrapper( + name='COMPLETE', + jp=Button( + file='./assets/jp/task/COMPLETE.png', + area=(936, 644, 1014, 693), + search=(916, 624, 1034, 713), + color=(128, 131, 135), + button=(936, 644, 1014, 693), + ), + en=Button( + file='./assets/en/task/COMPLETE.png', + area=(921, 643, 1026, 692), + search=(901, 623, 1046, 712), + color=(127, 131, 135), + button=(921, 643, 1026, 692), + ), + zht=Button( + file='./assets/zht/task/COMPLETE.png', + area=(933, 644, 1016, 697), + search=(913, 624, 1036, 717), + color=(127, 131, 135), + button=(933, 644, 1016, 697), + ), +) diff --git a/tasks/task/task.py b/tasks/task/task.py new file mode 100644 index 0000000..97009ad --- /dev/null +++ b/tasks/task/task.py @@ -0,0 +1,30 @@ +from module.base.timer import Timer +from module.logger import logger +from tasks.base.page import page_task +from tasks.base.ui import UI +from tasks.task.assets.assets_task import * + + +class Task(UI): + def run(self): + self.ui_ensure(page_task) + action_timer = Timer(1).start() + + while 1: + self.device.screenshot() + if self.ui_additional(): + continue + if action_timer.reached_and_reset(): + if self.match_color(CLAIM_ALL): + self.device.click(CLAIM_ALL) + logger.info("Click Claim All") + continue + if self.match_color(CLAIM): + self.device.click(CLAIM) + logger.info("Click Claim") + continue + if self.match_color(CLAIMED_ALL) and not self.match_color(CLAIM): + logger.info("All claimed") + break + + self.config.task_delay(minute=120) diff --git a/webapp/packages/main/public/deploy.yaml.tpl b/webapp/packages/main/public/deploy.yaml.tpl index 945fdfd..0b823a6 100644 --- a/webapp/packages/main/public/deploy.yaml.tpl +++ b/webapp/packages/main/public/deploy.yaml.tpl @@ -128,8 +128,8 @@ Deploy: WebuiHost: 0.0.0.0 # --port. Port to listen # You will be able to access webui via `http://{host}:{port}` - # [In most cases] Default to 22367 - WebuiPort: 22367 + # [In most cases] Default to 23467 + WebuiPort: 23467 # Language to use on web ui # 'zh-CN' for Chinese simplified # 'en-US' for English diff --git a/webapp/packages/main/src/createApp.ts b/webapp/packages/main/src/createApp.ts index 71ca042..4e62dc8 100644 --- a/webapp/packages/main/src/createApp.ts +++ b/webapp/packages/main/src/createApp.ts @@ -5,6 +5,7 @@ import logger from '/@/logger'; import {app, nativeImage, Tray} from 'electron'; import {join} from 'node:path'; import {isMacintosh} from '@common/utils'; + export const createApp = async () => { logger.info('-----createApp-----'); logger.info('-----createMainWindow-----'); @@ -42,7 +43,7 @@ export const createApp = async () => { }, }, ]); - tray.setToolTip('SRC'); + tray.setToolTip('AAS'); tray.setContextMenu(contextMenu); tray.on('click', () => { if (mainWindow?.isVisible()) { diff --git a/webapp/packages/renderer/src/logics/initAppConfigStore.ts b/webapp/packages/renderer/src/logics/initAppConfigStore.ts index 61e6083..b4fd390 100644 --- a/webapp/packages/renderer/src/logics/initAppConfigStore.ts +++ b/webapp/packages/renderer/src/logics/initAppConfigStore.ts @@ -9,6 +9,6 @@ export async function initAppConfigStore() { appStore.setRepository( (repositoryValueMap[config?.repository] as 'global' | 'china') ?? 'global', ); - appStore.setWebuiUrl(config?.webuiUrl ?? '127.0.0.1:22367'); + appStore.setWebuiUrl(config?.webuiUrl ?? '127.0.0.1:23467'); appStore.setAlasPath(config?.alasPath ?? ''); }