Update borealis_overlay.py

This commit is contained in:
Nicole Rappe 2025-02-11 01:43:10 -07:00
parent 794376ca76
commit 57e0c8de00

View File

@ -16,10 +16,10 @@ from rich.console import Console
from rich.table import Table from rich.table import Table
from rich.progress import Progress, BarColumn, TextColumn from rich.progress import Progress, BarColumn, TextColumn
# ---------------- [ Global Config ] ---------------- # ---- [ Global Config ] ----
pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe" pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"
OCR_ENGINE = "Tesseract" # Hard-coded since we removed EasyOCR OCR_ENGINE = "Tesseract"
POLLING_RATE_MS = 1000 POLLING_RATE_MS = 1000
MAX_DATA_POINTS = 7 MAX_DATA_POINTS = 7
@ -60,9 +60,7 @@ def format_experience_value(value):
def format_duration(seconds): def format_duration(seconds):
""" """
Convert total seconds into hours / min / sec: Convert total seconds into hours/min/seconds (e.g. "Xh Ym Zs" or "Xm Ys").
- If hours > 0 => "Xh Ym Zs"
- Otherwise => "Xm Ys"
""" """
seconds = int(seconds) seconds = int(seconds)
hours = seconds // 3600 hours = seconds // 3600
@ -133,13 +131,13 @@ class OverlayCanvas(QWidget):
if event.button() == Qt.LeftButton: if event.button() == Qt.LeftButton:
for region in reversed(self.regions): for region in reversed(self.regions):
# Check handles # Check each handle
for i, handle in enumerate(region.resize_handles()): for i, handle in enumerate(region.resize_handles()):
if handle.contains(event.pos()): if handle.contains(event.pos()):
self.selected_region = region self.selected_region = region
self.selected_handle = i self.selected_handle = i
return return
# Check label or rect # Check label or main rect
if region.label_rect().contains(event.pos()): if region.label_rect().contains(event.pos()):
self.selected_region = region self.selected_region = region
self.selected_handle = None self.selected_handle = None
@ -156,7 +154,7 @@ class OverlayCanvas(QWidget):
return return
if self.selected_handle is None: if self.selected_handle is None:
# Drag rectangle # Drag entire rectangle
self.selected_region.x = event.x() - self.drag_offset.x() self.selected_region.x = event.x() - self.drag_offset.x()
self.selected_region.y = event.y() - self.drag_offset.y() self.selected_region.y = event.y() - self.drag_offset.y()
else: else:
@ -191,33 +189,29 @@ class OverlayCanvas(QWidget):
self.selected_handle = None self.selected_handle = None
class BorealisOverlay(QWidget): class BorealisOverlay(QWidget):
"""
Project Borealis with Tesseract + Rich table + Rich progress bar,
properly displayed with "with Progress(...) as progress".
"""
def __init__(self): def __init__(self):
super().__init__() super().__init__()
# Fullscreen overlay
screen_geometry = QApplication.primaryScreen().geometry() screen_geometry = QApplication.primaryScreen().geometry()
self.setGeometry(screen_geometry) self.setGeometry(screen_geometry)
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
self.setAttribute(Qt.WA_TranslucentBackground, True) self.setAttribute(Qt.WA_TranslucentBackground, True)
self.regions = [ self.regions = [ Region(250, 50, label="Experience") ]
Region(250, 50, label="Experience"),
]
self.canvas = OverlayCanvas(self.regions, self) self.canvas = OverlayCanvas(self.regions, self)
self.canvas.setGeometry(self.rect()) self.canvas.setGeometry(self.rect())
# Tesseract # Tesseract only
self.engine = pytesseract self.engine = pytesseract
self.points = [] self.points = []
self.timer = QTimer(self) self.timer = QTimer(self)
self.timer.timeout.connect(self.collect_ocr_data) self.timer.timeout.connect(self.collect_ocr_data)
self.timer.start(POLLING_RATE_MS) self.timer.start(POLLING_RATE_MS)
# ---------------------------------------------------------------------
# OCR & Data
# ---------------------------------------------------------------------
def collect_ocr_data(self): def collect_ocr_data(self):
if not self.engine: if not self.engine:
return return
@ -227,9 +221,9 @@ class BorealisOverlay(QWidget):
screenshot = ImageGrab.grab( screenshot = ImageGrab.grab(
bbox=(region.x, region.y, region.x + region.w, region.y + region.h) bbox=(region.x, region.y, region.x + region.w, region.y + region.h)
) )
processed_image = self.preprocess_image(screenshot) processed = self.preprocess_image(screenshot)
text = pytesseract.image_to_string(processed_image, config='--psm 6 --oem 1') text = pytesseract.image_to_string(processed, config='--psm 6 --oem 1')
region.data = text.strip() region.data = text.strip()
if region.label.lower() == "experience": if region.label.lower() == "experience":
@ -255,16 +249,20 @@ class BorealisOverlay(QWidget):
now = time.time() now = time.time()
if self.points: if self.points:
_, last_v = self.points[-1] _, last_v = self.points[-1]
# skip duplicates
if abs(new_val - last_v) < 1e-6: if abs(new_val - last_v) < 1e-6:
return return
# rollover
if new_val < last_v: if new_val < last_v:
self.points.clear() self.points.clear()
self.points.append((now, new_val)) self.points.append((now, new_val))
if len(self.points) > MAX_DATA_POINTS: if len(self.points) > MAX_DATA_POINTS:
self.points.pop(0) self.points.pop(0)
# ---------------------------------------------------------------------
# Table & Calculation
# ---------------------------------------------------------------------
def compute_time_to_100(self): def compute_time_to_100(self):
"""Return integer seconds until 100% or None if not feasible."""
n = len(self.points) n = len(self.points)
if n < 2: if n < 2:
return None return None
@ -292,6 +290,8 @@ class BorealisOverlay(QWidget):
return int(remain / rate_per_s) return int(remain / rate_per_s)
def display_ocr_data_in_terminal(self): def display_ocr_data_in_terminal(self):
from rich.progress import Progress, BarColumn, TextColumn
console = Console() console = Console()
os.system('cls' if os.name == 'nt' else 'clear') os.system('cls' if os.name == 'nt' else 'clear')
@ -299,9 +299,9 @@ class BorealisOverlay(QWidget):
console.print("[dim]Flyff Information Overlay[/dim]\n") console.print("[dim]Flyff Information Overlay[/dim]\n")
console.print(f"[bold]OCR Engine[/bold]: {OCR_ENGINE}") console.print(f"[bold]OCR Engine[/bold]: {OCR_ENGINE}")
console.print(f"[bold]Data Polling Rate[/bold]: {POLLING_RATE_MS / 1000}s\n") console.print(f"[bold]Data Polling Rate[/bold]: {POLLING_RATE_MS/1000}s\n")
# Build the historical table # Build table
table = Table(show_header=True, header_style=GREEN_HEADER_STYLE, style=None) table = Table(show_header=True, header_style=GREEN_HEADER_STYLE, style=None)
table.add_column("Historical EXP", justify="center", style="green") table.add_column("Historical EXP", justify="center", style="green")
table.add_column("Time Since Last Kill", justify="center", style="green") table.add_column("Time Since Last Kill", justify="center", style="green")
@ -309,6 +309,7 @@ class BorealisOverlay(QWidget):
table.add_column("Average Time Between Kills", justify="center", style="green") table.add_column("Average Time Between Kills", justify="center", style="green")
n = len(self.points) n = len(self.points)
# If we only have 1 data point => show single row
if n == 1: if n == 1:
t0, v0 = self.points[0] t0, v0 = self.points[0]
exp_str = f"[green]{format_experience_value(v0)}%[/green]" exp_str = f"[green]{format_experience_value(v0)}%[/green]"
@ -318,30 +319,46 @@ class BorealisOverlay(QWidget):
t_cur, v_cur = self.points[i] t_cur, v_cur = self.points[i]
t_prev, v_prev = self.points[i - 1] t_prev, v_prev = self.points[i - 1]
exp_str = f"[green]{format_experience_value(v_cur)}%[/green]" # Calculate a difference
delta_v = v_cur - v_prev
delta_str = f"{delta_v:+.4f}%"
# e.g. "v_cur + (delta in dark gray)"
# "72.8260% [dim](+0.0334%) [/dim]"
exp_main = format_experience_value(v_cur)
exp_str = (
f"[green]{exp_main}%[/green] "
f"[dim]({delta_str})[/dim]"
)
# Time since last kill
delta_t = t_cur - t_prev delta_t = t_cur - t_prev
t_since_str = f"{delta_t:.1f}s" t_since_str = f"{delta_t:.1f}s"
# average exp from first data point
diff_v = v_cur - self.points[0][1] diff_v = v_cur - self.points[0][1]
steps = i steps = i
avg_exp_str = f"{diff_v/steps:.4f}%" avg_exp_str = f"{diff_v/steps:.4f}%"
# average time from first data point
total_time = t_cur - self.points[0][0] total_time = t_cur - self.points[0][0]
avg_kill_time = total_time / steps avg_kill_time = total_time / steps
avg_time_str = f"{avg_kill_time:.1f}s" avg_time_str = f"{avg_kill_time:.1f}s"
table.add_row(exp_str, t_since_str, avg_exp_str, avg_time_str) table.add_row(
exp_str,
t_since_str,
avg_exp_str,
avg_time_str
)
console.print(table) console.print(table)
console.print() console.print()
# Prepare the progress bar # Progress bar
current_exp = self.points[-1][1] if self.points else 0.0 current_exp = self.points[-1][1] if self.points else 0.0
secs_left = self.compute_time_to_100() secs_left = self.compute_time_to_100()
time_str = format_duration(secs_left) if secs_left is not None else "???" time_str = format_duration(secs_left) if secs_left else "???"
# We'll display a single progress bar line in a context manager
# Setting transient=False ensures it remains in the console
with Progress( with Progress(
TextColumn("[bold white]Predicted Time to Level:[/bold white] "), TextColumn("[bold white]Predicted Time to Level:[/bold white] "),
BarColumn(bar_width=30), BarColumn(bar_width=30),
@ -351,16 +368,14 @@ class BorealisOverlay(QWidget):
transient=False, transient=False,
) as progress: ) as progress:
task_id = progress.add_task("", total=100, completed=current_exp) task_id = progress.add_task("", total=100, completed=current_exp)
# Force a manual refresh so the bar appears immediately
progress.refresh() progress.refresh()
def main(): def main():
app = QApplication(sys.argv) app = QApplication(sys.argv)
window = BorealisOverlay() window = BorealisOverlay()
window.setWindowTitle("Project Borealis Overlay (Fixed Progress Bar)") window.setWindowTitle("Project Borealis Overlay (Delta in Historical EXP)")
window.show() window.show()
sys.exit(app.exec_()) sys.exit(app.exec_())
if __name__ == "__main__": if __name__ == "__main__":
main() main()