Skip to content
17 changes: 12 additions & 5 deletions pyclashbot/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import multiprocessing as mp
import os
import subprocess
import sys
from multiprocessing import Event, Queue
from os.path import expandvars, join
from typing import TYPE_CHECKING, Any
Expand Down Expand Up @@ -220,12 +221,18 @@ def open_recordings_folder() -> None:
def open_logs_folder() -> None:
folder_path = log_dir
os.makedirs(folder_path, exist_ok=True)
try:
os.startfile(folder_path)
except AttributeError:
import subprocess

subprocess.Popen(["xdg-open", folder_path])
try:
if sys.platform == "win32":
os.startfile(folder_path)
elif sys.platform == "darwin":
subprocess.Popen(["open", folder_path])
else:
# Linux and other Unix-like systems
subprocess.Popen(["xdg-open", folder_path])
except Exception as e:
# Log error but don't crash - user can manually navigate to the folder
print(f"Failed to open logs folder: {e}")


class BotApplication:
Expand Down
8 changes: 4 additions & 4 deletions pyclashbot/bot/fight.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,13 @@ def do_fight_state(
logger.change_status("Starting fight loop")
logger.log(f'This is the fight mode: "{fight_mode_choosed}"')

# Run regular fight loop if random mode not toggled
if not random_fight_mode and _fight_loop(emulator, logger, recording_flag) is False:
# Run random fight loop if random mode toggled
if random_fight_mode and _random_fight_loop(emulator, logger) is False:
logger.change_status("Failure in fight loop")
return False

# Run random fight loop if random mode toggled
if random_fight_mode and _random_fight_loop(emulator, logger) is False:
# Run regular fight loop if random mode not toggled
if not random_fight_mode and _fight_loop(emulator, logger, recording_flag) is False:
logger.change_status("Failure in fight loop")
return False

Expand Down
25 changes: 25 additions & 0 deletions pyclashbot/emulators/bluestacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,31 @@ def restart(self) -> bool:
return False # if this makes issues big problems
interruptible_sleep(1)

# Wait for Android to fully boot before checking for apps
boot_wait_start_time = time.time()
boot_timeout = 120
self.logger.change_status("Waiting for Android to finish booting...")
while time.time() - boot_wait_start_time < boot_timeout:
# Check if boot is complete
boot_result = self.adb("shell getprop sys.boot_completed")
if boot_result.returncode == 0 and boot_result.stdout and boot_result.stdout.strip() == "1":
# Verify pm list packages works and returns non-empty output
pm_result = self.adb("shell pm list packages")
if pm_result.returncode == 0 and pm_result.stdout:
# Check for critical system packages to ensure full initialization
packages_output = pm_result.stdout
critical_packages = [
"com.android.systemui",
"com.android.launcher",
"com.android.settings",
]
if any(pkg in packages_output for pkg in critical_packages):
self.logger.change_status("Android boot complete")
break
interruptible_sleep(1)
else:
self.logger.change_status("Warning: Android boot check timeout, continuing anyway...")

# Launch Clash Royale
clash_pkg = "com.supercell.clashroyale"
self.logger.change_status("Launching Clash Royale...")
Expand Down
14 changes: 12 additions & 2 deletions pyclashbot/interface/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,12 +361,16 @@ def add_job_checkbox(
bootstyle: str,
) -> None:
var = ttk.BooleanVar(value=job_defaults.get(field, False))

def on_checkbox_change():
self._notify_config_change()

checkbox = ttk.Checkbutton(
frame,
text=text,
variable=var,
bootstyle=bootstyle,
command=self._notify_config_change,
command=on_checkbox_change,
width=checkbox_width,
)
checkbox.grid(row=row_index, column=0, sticky="w", pady=2)
Expand Down Expand Up @@ -811,7 +815,13 @@ def _apply_theme(self, theme_name: str, skip_variable_update: bool = False) -> N
self.theme_var.set(selected)
finally:
self._suspend_traces -= 1
self._style.theme_use(selected)
try:
self._style.theme_use(selected)
except tk.TclError:
# Widgets may not be fully initialized yet, especially combobox popdowns
# This can happen during initialization when loading settings
# The theme will be applied correctly once widgets are fully created
pass
self._refresh_theme_colours()

def _label_foreground(self) -> str:
Expand Down
11 changes: 11 additions & 0 deletions pyclashbot/utils/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import time
import zipfile
from functools import wraps
import os
from os import listdir, makedirs, remove
from os.path import exists, getmtime, join

Expand Down Expand Up @@ -43,12 +44,22 @@ def initalize_pylogging() -> None:
"""Method to be called once to initalize python logging"""
if not exists(log_dir):
makedirs(log_dir)

# Clear existing handlers if logging was already configured
# This ensures we can properly set up file logging
if len(logging.root.handlers) > 0:
logging.root.handlers.clear()

# Force reconfiguration if logging was already set up (e.g., by another module)
# Use force=True (Python 3.8+) to override existing configuration
logging.basicConfig(
filename=log_name,
encoding="utf-8",
level=logging.DEBUG,
format="%(levelname)s:%(asctime)s %(message)s",
force=True, # Force reconfiguration even if logging was already configured
)

logging.info("Logging initialized for %s", __version__)
logging.info(
"""
Expand Down