diff --git a/requirements.txt b/requirements.txt
index 77c4e83..90e40a1 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -28,7 +28,7 @@ outcome==1.2.0
packaging==23.0
pathvalidate==3.0.0
pefile==2023.2.7
-Pillow>=10.0.1
+Pillow==10.1.0
protobuf==4.22.1
psutil==5.9.5
pycparser==2.21
@@ -47,14 +47,14 @@ pywin32==306
pywin32-ctypes==0.2.2
PyYAML==6.0
requests==2.31.0
-selenium==4.8.2
+selenium==4.15.2
six==1.16.0
sniffio==1.3.0
sortedcontainers==2.4.0
soupsieve==2.4
trio==0.22.0
trio-websocket==0.9.2
-urllib3>=1.26.18
-Werkzeug>=3.0.1
+urllib3==1.26.18
+Werkzeug==3.0.1
win32-setctime==1.1.0
wsproto==1.2.0
diff --git a/umalauncher/carrotjuicer.py b/umalauncher/carrotjuicer.py
index 40d7842..b00c815 100644
--- a/umalauncher/carrotjuicer.py
+++ b/umalauncher/carrotjuicer.py
@@ -638,7 +638,11 @@ def run(self):
while not self.should_stop:
time.sleep(0.25)
- if not self.threader.settings["s_enable_carrotjuicer"]:
+ if not self.threader.settings["s_enable_carrotjuicer"] or not self.threader.settings['s_enable_browser']:
+ if self.browser and self.browser.alive():
+ self.browser.quit()
+ if self.skill_browser and self.skill_browser.alive():
+ self.skill_browser.quit()
continue
if self.browser and self.browser.alive():
diff --git a/umalauncher/constants.py b/umalauncher/constants.py
index e671d71..d5410f2 100644
--- a/umalauncher/constants.py
+++ b/umalauncher/constants.py
@@ -143,35 +143,35 @@
30000: "Platinum 4"
}
-SCOUTING_SCORE_TO_RANK_DICT = {
- 0: "No rank",
- 60000: "E",
- 63000: "E1",
- 66000: "E2",
- 69000: "E3",
- 72000: "D",
- 76000: "D1",
- 80000: "D2",
- 85000: "D3",
- 90000: "C",
- 95000: "C1",
- 100000: "C2",
- 105000: "C3",
- 110000: "B",
- 115000: "B1",
- 120000: "B2",
- 125000: "B3",
- 130000: "A",
- 135000: "A1",
- 140000: "A2",
- 145000: "A3",
- 150000: "A4",
- 155000: "A5",
- 160000: "S",
- 165000: "S1",
- 170000: "S2",
- 180000: "S3",
- 190000: "S4",
- 200000: "S5",
- 210000: "SS"
-}
\ No newline at end of file
+SCOUTING_RANK_LIST = [
+ "No rank",
+ "E",
+ "E1",
+ "E2",
+ "E3",
+ "D",
+ "D1",
+ "D2",
+ "D3",
+ "C",
+ "C1",
+ "C2",
+ "C3",
+ "B",
+ "B1",
+ "B2",
+ "B3",
+ "A",
+ "A1",
+ "A2",
+ "A3",
+ "A4",
+ "A5",
+ "S",
+ "S1",
+ "S2",
+ "S3",
+ "S4",
+ "S5",
+ "SS"
+]
\ No newline at end of file
diff --git a/umalauncher/gui.py b/umalauncher/gui.py
index 166217f..efd9d89 100644
--- a/umalauncher/gui.py
+++ b/umalauncher/gui.py
@@ -586,6 +586,9 @@ def init_ui(self, settings_var, tab=" General", window_title="Change options", *
self.load_settings()
+ self.verticalSpacer = qtw.QSpacerItem(0, 0, qtw.QSizePolicy.Minimum, qtw.QSizePolicy.Expanding)
+ self.verticalLayout.addItem(self.verticalSpacer)
+
self.scrollArea.setWidget(self.scrollAreaWidgetContents)
self.btn_restore = qtw.QPushButton(self)
@@ -662,6 +665,18 @@ def save_settings(self):
return True
def add_group_box(self, setting):
+ # If the setting is a divider, add a horizontal line.
+ if setting.type == se.SettingType.DIVIDER:
+ line = qtw.QFrame(self.scrollAreaWidgetContents)
+ line.setObjectName(f"line_{setting.name}")
+ line.setMinimumHeight(16)
+ line.setLineWidth(0)
+ line.setMidLineWidth(2)
+ line.setFrameShape(qtw.QFrame.HLine)
+ line.setFrameShadow(qtw.QFrame.Sunken)
+ return line, None
+
+
grp_setting = qtw.QGroupBox(self.scrollAreaWidgetContents)
grp_setting.setObjectName(f"grp_setting_{setting.name}")
sizePolicy = qtw.QSizePolicy(qtw.QSizePolicy.Preferred, qtw.QSizePolicy.Fixed)
@@ -691,30 +706,30 @@ def add_group_box(self, setting):
horizontalLayout.addWidget(lbl_setting_description)
- input_widgets = []
+ input_widgets = [None]
value_func = None
- if setting.type == se.SettingType.MESSAGE:
- input_widgets = [None]
- elif setting.type == se.SettingType.BOOL:
- input_widgets, value_func = self.add_checkbox(setting, grp_setting)
- elif setting.type == se.SettingType.INT:
- input_widgets, value_func = self.add_spinbox(setting, grp_setting)
- elif setting.type == se.SettingType.STRING:
- input_widgets, value_func = self.add_lineedit(setting, grp_setting)
- elif setting.type == se.SettingType.COMBOBOX:
- input_widgets, value_func = self.add_combobox(setting, grp_setting)
- elif setting.type == se.SettingType.COLOR:
- input_widgets, value_func = self.add_colorpicker(setting, grp_setting)
- elif setting.type == se.SettingType.RADIOBUTTONS:
- input_widgets, value_func = self.add_radiobuttons(setting, grp_setting)
- elif setting.type == se.SettingType.FILEDIALOG:
- input_widgets, value_func = self.add_filedialog(setting, grp_setting)
- elif setting.type == se.SettingType.FOLDERDIALOG:
- input_widgets, value_func = self.add_folderdialog(setting, grp_setting)
- elif setting.type == se.SettingType.XYWHSPINBOXES:
- input_widgets, value_func = self.add_multi_spinboxes(setting, grp_setting, ['Left', 'Top', 'Width', 'Height'])
- elif setting.type == se.SettingType.LRTBSPINBOXES:
- input_widgets, value_func = self.add_multi_spinboxes(setting, grp_setting, ['Left', 'Right', 'Top', 'Bottom'])
+
+ match setting.type:
+ case se.SettingType.BOOL:
+ input_widgets, value_func = self.add_checkbox(setting, grp_setting)
+ case se.SettingType.INT:
+ input_widgets, value_func = self.add_spinbox(setting, grp_setting)
+ case se.SettingType.STRING:
+ input_widgets, value_func = self.add_lineedit(setting, grp_setting)
+ case se.SettingType.COMBOBOX:
+ input_widgets, value_func = self.add_combobox(setting, grp_setting)
+ case se.SettingType.COLOR:
+ input_widgets, value_func = self.add_colorpicker(setting, grp_setting)
+ case se.SettingType.RADIOBUTTONS:
+ input_widgets, value_func = self.add_radiobuttons(setting, grp_setting)
+ case se.SettingType.FILEDIALOG:
+ input_widgets, value_func = self.add_filedialog(setting, grp_setting)
+ case se.SettingType.FOLDERDIALOG:
+ input_widgets, value_func = self.add_folderdialog(setting, grp_setting)
+ case se.SettingType.XYWHSPINBOXES:
+ input_widgets, value_func = self.add_multi_spinboxes(setting, grp_setting, ['Left', 'Top', 'Width', 'Height'])
+ case se.SettingType.LRTBSPINBOXES:
+ input_widgets, value_func = self.add_multi_spinboxes(setting, grp_setting, ['Left', 'Right', 'Top', 'Bottom'])
if not input_widgets:
logger.debug(f"{setting.type} not implemented for {setting.name}")
diff --git a/umalauncher/horsium.py b/umalauncher/horsium.py
index 0490efd..685e237 100644
--- a/umalauncher/horsium.py
+++ b/umalauncher/horsium.py
@@ -21,41 +21,86 @@
OLD_DRIVERS = []
-def firefox_setup(helper_url):
- firefox_service = FirefoxService()
+def firefox_setup(helper_url, settings):
+ driver_path = None
+ if settings['s_enable_browser_override']:
+ new_path = settings['s_browser_custom_driver']
+ if new_path:
+ driver_path = new_path
+
+ firefox_service = FirefoxService(executable_path=driver_path)
firefox_service.creation_flags = CREATE_NO_WINDOW
profile = webdriver.FirefoxProfile(util.get_asset("ff_profile"))
options = webdriver.FirefoxOptions()
- browser = webdriver.Firefox(service=firefox_service, firefox_profile=profile, options=options)
+ options.profile = profile
+
+ binary_path = None
+
+ if settings['s_enable_browser_override']:
+ binary_path = settings['s_browser_custom_binary']
+ if binary_path:
+ options.binary_location = binary_path
+
+ logger.debug(f"Firefox driver path: {driver_path}")
+ logger.debug(f"Firefox binary path: {binary_path}")
+
+ browser = webdriver.Firefox(service=firefox_service, options=options)
browser.get(helper_url)
return browser
-def chromium_setup(service, options_class, driver_class, profile, helper_url):
+def chromium_setup(service, options_class, driver_class, profile, helper_url, settings, binary_path=None):
service.creation_flags = CREATE_NO_WINDOW
options = options_class()
+
+ if binary_path:
+ options.binary_location = binary_path
+
options.add_argument("--user-data-dir=" + str(util.get_asset(profile)))
- options.add_argument("--app=" + helper_url)
options.add_argument("--remote-debugging-port=9222")
options.add_argument("--new-window")
+
+ if not settings['s_enable_browser_override']:
+ options.add_argument("--app=" + helper_url)
+
browser = driver_class(service=service, options=options)
+
+ if settings['s_enable_browser_override']:
+ browser.get(helper_url)
+
return browser
-def chrome_setup(helper_url):
+def chrome_setup(helper_url, settings):
+ driver_path = None
+ if settings['s_enable_browser_override']:
+ new_path = settings['s_browser_custom_driver']
+ if new_path:
+ driver_path = new_path
+
+ binary_path = None
+ if settings['s_enable_browser_override']:
+ binary_path = settings['s_browser_custom_binary']
+
+ logger.debug(f"Chrome driver path: {driver_path}")
+ logger.debug(f"Chrome binary path: {binary_path}")
+
return chromium_setup(
- service=ChromeService(),
+ service=ChromeService(executable_path=driver_path) if driver_path else ChromeService(),
options_class=webdriver.ChromeOptions,
driver_class=webdriver.Chrome,
profile="chr_profile",
- helper_url=helper_url
+ helper_url=helper_url,
+ settings=settings,
+ binary_path=binary_path
)
-def edge_setup(helper_url):
+def edge_setup(helper_url, settings):
return chromium_setup(
service=EdgeService(),
options_class=webdriver.EdgeOptions,
driver_class=webdriver.Edge,
profile="edg_profile",
- helper_url=helper_url
+ helper_url=helper_url,
+ settings=settings
)
BROWSER_LIST = {
@@ -77,24 +122,34 @@ class BrowserWindow:
def __init__(self, url, threader, rect=None, run_at_launch=None):
self.url = url
self.threader = threader
+ self.settings = threader.settings
self.driver: RemoteWebDriver = None
self.active_tab_handle = None
self.last_window_rect = {'x': rect[0], 'y': rect[1], 'width': rect[2], 'height': rect[3]} if rect else None
self.run_at_launch = run_at_launch
self.browser_name = "Auto"
+ self.latest_error = ""
self.ensure_tab_open()
def init_browser(self) -> RemoteWebDriver:
driver = None
+ if self.settings['s_enable_browser_override']:
+ selection = self.settings['s_custom_browser_type']
+ else:
+ selection = self.settings['s_selected_browser']
browser_name = [
browser
- for browser, selected in self.threader.settings['s_selected_browser'].items()
+ for browser, selected in selection.items()
if selected
][0]
self.browser_name = browser_name
+ # Hack to convert override Chromium to Chrome
+ if browser_name == 'Other (Chromium)':
+ browser_name = 'Chrome'
+
browser_list = []
if browser_name == "Auto":
browser_list = BROWSER_LIST.items()
@@ -105,14 +160,15 @@ def init_browser(self) -> RemoteWebDriver:
browser_name, browser_setup = browser_data
try:
logger.info("Attempting " + str(browser_setup.__name__))
- driver = browser_setup(self.url)
+ driver = browser_setup(self.url, self.settings)
self.browser_name = browser_name
break
- except Exception:
+ except Exception as e:
logger.error("Failed to start browser")
logger.error(traceback.format_exc())
- if not driver:
- util.show_warning_box("Uma Launcher: Unable to start browser.", "Selected webbrowser cannot be started.")
+ self.latest_error = traceback.format_exception_only(type(e), e)[-1]
+ # if not driver:
+ # util.show_warning_box("Uma Launcher: Unable to start browser.", "Selected webbrowser cannot be started.")
return driver
def alive(self):
@@ -189,7 +245,7 @@ def wrapper(self, *args, **kwargs):
if self.driver:
return func(self, *args, **kwargs)
- util.show_warning_box("Uma Launcher: Unable to reach browser.", "Webbrowser is unable to open.
If this problem persists, try restarting your computer
or selecting a different browser in the preferences.")
+ util.show_warning_box("Uma Launcher: Unable to reach browser.", f"Webbrowser is unable to open.
If this problem persists, try restarting your computer
or selecting a different browser in the preferences.
Extra info:
{self.latest_error}")
return wrapper
@ensure_focus
diff --git a/umalauncher/mdb.py b/umalauncher/mdb.py
index fdc0e5a..edf09d1 100644
--- a/umalauncher/mdb.py
+++ b/umalauncher/mdb.py
@@ -111,8 +111,23 @@ def _get_event_titles_default(story_id):
return [None]
return [row[0]]
+
+def convert_short_story_id(story_id):
+ with Connection() as (_, cursor):
+ cursor.execute(
+ """SELECT story_id FROM single_mode_story_data WHERE short_story_id = ? LIMIT 1""",
+ (story_id,)
+ )
+ row = cursor.fetchone()
+
+ if row is None:
+ return story_id
+
+ return row[0]
def get_event_titles(story_id, card_id):
+ story_id = convert_short_story_id(story_id)
+
str_event_title = str(story_id)
if str_event_title.startswith("40"):
@@ -303,6 +318,10 @@ def get_support_card_string_dict(force=False):
global SUPPORT_CARD_STRING_DICT
if force or not SUPPORT_CARD_STRING_DICT:
support_card_dict = get_support_card_dict()
+
+ # Forcefully clear the character name dict cache if needed.
+ util.get_character_name_dict(force=force)
+
SUPPORT_CARD_STRING_DICT.update({id: create_support_card_string(*data) for id, data in support_card_dict.items()})
return SUPPORT_CARD_STRING_DICT
@@ -414,6 +433,28 @@ def get_skill_id_dict(force=False):
return SKILL_ID_DICT
+SCOUTING_SCORE_TO_RANK_DICT = {}
+def get_scouting_score_to_rank_dict(force=False):
+ global SCOUTING_SCORE_TO_RANK_DICT
+ if force or not SCOUTING_SCORE_TO_RANK_DICT:
+ with Connection() as (_, cursor):
+ cursor.execute(
+ """SELECT team_min_value FROM team_building_rank"""
+ )
+ rows = cursor.fetchall()
+
+ tmp_dict = {}
+ for i, row in enumerate(rows):
+ min_score = row[0]
+ try:
+ rank = constants.SCOUTING_RANK_LIST[i]
+ except IndexError:
+ rank = constants.SCOUTING_RANK_LIST[-1]
+ tmp_dict[min_score] = rank
+
+ SCOUTING_SCORE_TO_RANK_DICT.update(tmp_dict)
+
+ return SCOUTING_SCORE_TO_RANK_DICT
def get_card_inherent_skills(card_id, level=99):
skills = []
@@ -483,6 +524,7 @@ def get_total_minigame_plushies(force=False):
return 3 * (total_plushies + len(total_charas))
UPDATE_FUNCS = [
+ get_chara_name_dict,
get_event_title_dict,
get_race_program_name_dict,
get_skill_name_dict,
@@ -491,9 +533,9 @@ def get_total_minigame_plushies(force=False):
get_outfit_name_dict,
get_support_card_dict,
get_support_card_string_dict,
- get_chara_name_dict,
get_mant_item_string_dict,
get_gl_lesson_dict,
get_group_card_effect_ids,
get_skill_id_dict,
+ get_scouting_score_to_rank_dict
]
\ No newline at end of file
diff --git a/umalauncher/screenstate.py b/umalauncher/screenstate.py
index fa51805..d438320 100644
--- a/umalauncher/screenstate.py
+++ b/umalauncher/screenstate.py
@@ -112,6 +112,7 @@ class ScreenStateHandler():
game_handle = None
carrotjuicer_closed = False
+ carrotjuicer_handle = None
should_stop = False
@@ -271,17 +272,30 @@ def run(self):
self.vpn = None
if not self.carrotjuicer_closed and self.threader.settings["s_hide_carrotjuicer"]:
- carrotjuicer_handle = util.get_window_handle("Umapyoi", type=util.EXACT)
- if carrotjuicer_handle:
+ self.carrotjuicer_handle = util.get_window_handle("Umapyoi", type=util.EXACT)
+ if self.carrotjuicer_handle:
logger.info("Attempting to minimize CarrotJuicer.")
- success1 = util.show_window(carrotjuicer_handle, win32con.SW_MINIMIZE)
- success2 = util.hide_window_from_taskbar(carrotjuicer_handle)
+ success1 = util.show_window(self.carrotjuicer_handle, win32con.SW_MINIMIZE)
+ success2 = util.hide_window_from_taskbar(self.carrotjuicer_handle)
success = success1 and success2
if not success:
logger.error("Failed to minimize CarrotJuicer")
else:
self.carrotjuicer_closed = True
time.sleep(0.25)
+
+ if self.carrotjuicer_closed and not self.threader.settings["s_hide_carrotjuicer"]:
+ logger.debug(f"CarrotJuicer handle: {self.carrotjuicer_handle}")
+ if self.carrotjuicer_handle:
+ logger.info("Attempting to restore CarrotJuicer.")
+ success1 = util.show_window(self.carrotjuicer_handle, win32con.SW_RESTORE)
+ success2 = util.unhide_window_from_taskbar(self.carrotjuicer_handle)
+ success = success1 and success2
+ if not success:
+ logger.error("Failed to restore CarrotJuicer")
+ else:
+ self.carrotjuicer_closed = False
+ time.sleep(0.25)
self.sleep_time = 2.0
diff --git a/umalauncher/settings.py b/umalauncher/settings.py
index 4e31cd3..329e8ad 100644
--- a/umalauncher/settings.py
+++ b/umalauncher/settings.py
@@ -145,8 +145,16 @@ def __init__(self):
priority=50,
tab="Position"
)
+ self.s_enable_browser = se.Setting(
+ "Enable browser",
+ "Enable the Automatic Training Event helper browser.",
+ True,
+ se.SettingType.BOOL,
+ priority=100,
+ tab="Event Helper"
+ )
self.s_selected_browser = se.Setting(
- "Selected browser",
+ "Browser type",
"Browser to use for the Automatic Training Event Helper.",
{
"Auto": True,
@@ -155,14 +163,67 @@ def __init__(self):
"Edge": False
},
se.SettingType.RADIOBUTTONS,
- priority=98
+ priority=98,
+ tab="Event Helper"
)
self.s_gametora_dark_mode = se.Setting(
"GameTora dark mode",
"Enable dark mode for GameTora.",
True,
se.SettingType.BOOL,
- priority=97
+ priority=97,
+ tab="Event Helper"
+ )
+ self.s_custom_browser_divider = se.Setting(
+ "Custom browser divider",
+ None,
+ None,
+ se.SettingType.DIVIDER,
+ priority=96,
+ tab="Event Helper"
+ )
+ self.s_custom_browser_message = se.Setting(
+ "Custom browser",
+ "
The following settings allow overriding of the browser binary and driver given to Selenium to control. You should only enable this if the browser fails to start, or you want to use a different Chromium-based browser.
", + None, + se.SettingType.MESSAGE, + priority=96, + tab="Event Helper" + ) + self.s_enable_browser_override = se.Setting( + "Enable browser override", + "Enable overriding of the browser binary and driver. This also disables app mode for Chromium-based browsers, so you can reach settings in case things don't work.", + False, + se.SettingType.BOOL, + priority=95, + tab="Event Helper" + ) + self.s_custom_browser_type = se.Setting( + "Browser override type", + "Browser to use for the Automatic Training Event Helper. If browser override is enabled, this will override the browser type setting above.", + { + "Firefox": True, + "Other (Chromium)": False + }, + se.SettingType.RADIOBUTTONS, + priority=94, + tab="Event Helper" + ) + self.s_browser_custom_binary = se.Setting( + "Browser custom binary", + "Path to a custom browser executable.