feat: Add Log Parser and Game Reader Test Plugins
NEW TESTING PLUGINS:
1. Log Parser Test Plugin (plugins/log_parser_test/)
- Real-time event monitoring with counters
- Visual event history table (last 100 events)
- Raw log line viewer
- Simulated event buttons for testing:
* Skill Gain
* Loot
* Damage
- Event type filtering and statistics
- Color-coded event types
- Auto-scroll and refresh
2. Game Reader Test Plugin (plugins/game_reader_test/)
- Quick OCR test with progress bar
- Region-specific OCR testing with presets:
* Chat Window
* Skills Window
* Inventory
* Mission Tracker
- OCR backend selection (Auto/EasyOCR/Tesseract/PaddleOCR)
- Processing time tracking
- Test history with results
- Save/copy OCR results
- Calibration tools:
* Display DPI detection
* Backend status checker
* OCR tips and best practices
FEATURES FOR BOTH:
- Modern EU-styled UI
- Tabbed interface
- Error handling and logging
- Background processing (no UI freeze)
- Real-time updates
These plugins serve as both testing tools and demonstrations
of the Log Reader and OCR core services capabilities.
This commit is contained in:
parent
8aad153c11
commit
219d0847b2
|
|
@ -0,0 +1,2 @@
|
|||
"""Game Reader Test Plugin."""
|
||||
from .plugin import GameReaderTestPlugin
|
||||
|
|
@ -0,0 +1,628 @@
|
|||
"""
|
||||
EU-Utility - Game Reader Test Plugin
|
||||
|
||||
Debug and test tool for OCR and game reading functionality.
|
||||
Tests screen capture, OCR accuracy, and text extraction.
|
||||
"""
|
||||
|
||||
from PyQt6.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QTextEdit,
|
||||
QLabel, QPushButton, QComboBox, QCheckBox,
|
||||
QSpinBox, QGroupBox, QSplitter, QFrame,
|
||||
QTabWidget, QLineEdit, QProgressBar,
|
||||
QFileDialog, QMessageBox
|
||||
)
|
||||
from PyQt6.QtCore import Qt, QTimer, QThread, pyqtSignal
|
||||
from PyQt6.QtGui import QPixmap, QImage, QColor
|
||||
|
||||
from plugins.base_plugin import BasePlugin
|
||||
|
||||
|
||||
class OCRTestThread(QThread):
|
||||
"""Background thread for OCR testing."""
|
||||
result_ready = pyqtSignal(dict)
|
||||
progress_update = pyqtSignal(int, str)
|
||||
|
||||
def __init__(self, region=None, backend='auto'):
|
||||
super().__init__()
|
||||
self.region = region
|
||||
self.backend = backend
|
||||
|
||||
def run(self):
|
||||
"""Run OCR test."""
|
||||
import time
|
||||
results = {
|
||||
'success': False,
|
||||
'text': '',
|
||||
'backend_used': '',
|
||||
'processing_time': 0,
|
||||
'error': None
|
||||
}
|
||||
|
||||
try:
|
||||
start_time = time.time()
|
||||
self.progress_update.emit(10, "Capturing screen...")
|
||||
|
||||
# Capture screen
|
||||
from PIL import Image, ImageGrab
|
||||
if self.region:
|
||||
screenshot = ImageGrab.grab(bbox=self.region)
|
||||
else:
|
||||
screenshot = ImageGrab.grab()
|
||||
|
||||
self.progress_update.emit(30, "Running OCR...")
|
||||
|
||||
# Try OCR backends
|
||||
text = ""
|
||||
backend_used = "none"
|
||||
|
||||
if self.backend in ('auto', 'easyocr'):
|
||||
try:
|
||||
import easyocr
|
||||
self.progress_update.emit(50, "Loading EasyOCR...")
|
||||
reader = easyocr.Reader(['en'], gpu=False, verbose=False)
|
||||
self.progress_update.emit(70, "Processing with EasyOCR...")
|
||||
ocr_result = reader.readtext(
|
||||
screenshot,
|
||||
detail=0,
|
||||
paragraph=True
|
||||
)
|
||||
text = '\n'.join(ocr_result)
|
||||
backend_used = "easyocr"
|
||||
except Exception as e:
|
||||
if self.backend == 'easyocr':
|
||||
raise e
|
||||
|
||||
if not text and self.backend in ('auto', 'tesseract'):
|
||||
try:
|
||||
import pytesseract
|
||||
self.progress_update.emit(70, "Processing with Tesseract...")
|
||||
text = pytesseract.image_to_string(screenshot)
|
||||
backend_used = "tesseract"
|
||||
except Exception as e:
|
||||
if self.backend == 'tesseract':
|
||||
raise e
|
||||
|
||||
if not text and self.backend in ('auto', 'paddle'):
|
||||
try:
|
||||
from paddleocr import PaddleOCR
|
||||
self.progress_update.emit(70, "Processing with PaddleOCR...")
|
||||
ocr = PaddleOCR(use_angle_cls=True, lang='en', show_log=False)
|
||||
result = ocr.ocr(screenshot, cls=True)
|
||||
texts = [line[1][0] for line in result[0]]
|
||||
text = '\n'.join(texts)
|
||||
backend_used = "paddleocr"
|
||||
except Exception as e:
|
||||
if self.backend == 'paddle':
|
||||
raise e
|
||||
|
||||
processing_time = time.time() - start_time
|
||||
|
||||
results.update({
|
||||
'success': True,
|
||||
'text': text or "No text detected",
|
||||
'backend_used': backend_used,
|
||||
'processing_time': processing_time
|
||||
})
|
||||
|
||||
self.progress_update.emit(100, "Complete!")
|
||||
|
||||
except Exception as e:
|
||||
results['error'] = str(e)
|
||||
self.progress_update.emit(100, f"Error: {e}")
|
||||
|
||||
self.result_ready.emit(results)
|
||||
|
||||
|
||||
class GameReaderTestPlugin(BasePlugin):
|
||||
"""Test and debug tool for game reading/OCR functionality."""
|
||||
|
||||
name = "Game Reader Test"
|
||||
version = "1.0.0"
|
||||
author = "EU-Utility"
|
||||
description = "Debug tool for testing OCR and screen reading"
|
||||
|
||||
def __init__(self, overlay_window, config):
|
||||
super().__init__(overlay_window, config)
|
||||
self.test_history = []
|
||||
self.max_history = 50
|
||||
self.ocr_thread = None
|
||||
|
||||
def initialize(self):
|
||||
"""Initialize plugin."""
|
||||
self.log_info("Game Reader Test initialized")
|
||||
|
||||
def get_ui(self):
|
||||
"""Create plugin UI."""
|
||||
widget = QWidget()
|
||||
layout = QVBoxLayout(widget)
|
||||
layout.setSpacing(12)
|
||||
|
||||
# Header
|
||||
header = QLabel("📷 Game Reader Test & Debug Tool")
|
||||
header.setStyleSheet("font-size: 16px; font-weight: bold; color: #ff8c42;")
|
||||
layout.addWidget(header)
|
||||
|
||||
# Create tabs
|
||||
tabs = QTabWidget()
|
||||
|
||||
# Tab 1: Quick Test
|
||||
tabs.addTab(self._create_quick_test_tab(), "Quick Test")
|
||||
|
||||
# Tab 2: Region Test
|
||||
tabs.addTab(self._create_region_test_tab(), "Region Test")
|
||||
|
||||
# Tab 3: History
|
||||
tabs.addTab(self._create_history_tab(), "History")
|
||||
|
||||
# Tab 4: Calibration
|
||||
tabs.addTab(self._create_calibration_tab(), "Calibration")
|
||||
|
||||
layout.addWidget(tabs, 1)
|
||||
|
||||
# Status bar
|
||||
status_frame = QFrame()
|
||||
status_frame.setStyleSheet("background-color: #1a1f2e; border-radius: 6px; padding: 8px;")
|
||||
status_layout = QHBoxLayout(status_frame)
|
||||
|
||||
self.status_label = QLabel("Ready - Select a tab to begin testing")
|
||||
self.status_label.setStyleSheet("color: #4ecdc4;")
|
||||
status_layout.addWidget(self.status_label)
|
||||
|
||||
layout.addWidget(status_frame)
|
||||
|
||||
return widget
|
||||
|
||||
def _create_quick_test_tab(self):
|
||||
"""Create quick test tab."""
|
||||
tab = QWidget()
|
||||
layout = QVBoxLayout(tab)
|
||||
|
||||
# Info
|
||||
info = QLabel("Quick OCR Test - Captures full screen and extracts text")
|
||||
info.setStyleSheet("color: #888;")
|
||||
layout.addWidget(info)
|
||||
|
||||
# Backend selection
|
||||
backend_layout = QHBoxLayout()
|
||||
backend_layout.addWidget(QLabel("OCR Backend:"))
|
||||
self.backend_combo = QComboBox()
|
||||
self.backend_combo.addItems(["Auto (try all)", "EasyOCR", "Tesseract", "PaddleOCR"])
|
||||
backend_layout.addWidget(self.backend_combo)
|
||||
backend_layout.addStretch()
|
||||
layout.addLayout(backend_layout)
|
||||
|
||||
# Progress bar
|
||||
self.progress_bar = QProgressBar()
|
||||
self.progress_bar.setRange(0, 100)
|
||||
self.progress_bar.setValue(0)
|
||||
self.progress_bar.setStyleSheet("""
|
||||
QProgressBar {
|
||||
border: 1px solid #333;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
QProgressBar::chunk {
|
||||
background-color: #ff8c42;
|
||||
}
|
||||
""")
|
||||
layout.addWidget(self.progress_bar)
|
||||
|
||||
# Test button
|
||||
self.test_btn = QPushButton("▶ Run OCR Test")
|
||||
self.test_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #ff8c42;
|
||||
color: #141f23;
|
||||
font-weight: bold;
|
||||
padding: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #ffa05c;
|
||||
}
|
||||
""")
|
||||
self.test_btn.clicked.connect(self._run_quick_test)
|
||||
layout.addWidget(self.test_btn)
|
||||
|
||||
# Results
|
||||
results_group = QGroupBox("OCR Results")
|
||||
results_layout = QVBoxLayout(results_group)
|
||||
|
||||
self.results_text = QTextEdit()
|
||||
self.results_text.setReadOnly(True)
|
||||
self.results_text.setPlaceholderText("OCR results will appear here...")
|
||||
self.results_text.setStyleSheet("""
|
||||
QTextEdit {
|
||||
background-color: #0d1117;
|
||||
color: #c9d1d9;
|
||||
font-family: Consolas, monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
""")
|
||||
results_layout.addWidget(self.results_text)
|
||||
|
||||
# Stats
|
||||
self.stats_label = QLabel("Backend: - | Time: - | Status: Waiting")
|
||||
self.stats_label.setStyleSheet("color: #888;")
|
||||
results_layout.addWidget(self.stats_label)
|
||||
|
||||
layout.addWidget(results_group)
|
||||
|
||||
# Save buttons
|
||||
btn_layout = QHBoxLayout()
|
||||
|
||||
save_text_btn = QPushButton("💾 Save Text")
|
||||
save_text_btn.clicked.connect(self._save_text)
|
||||
btn_layout.addWidget(save_text_btn)
|
||||
|
||||
copy_btn = QPushButton("📋 Copy to Clipboard")
|
||||
copy_btn.clicked.connect(self._copy_to_clipboard)
|
||||
btn_layout.addWidget(copy_btn)
|
||||
|
||||
clear_btn = QPushButton("🗑 Clear")
|
||||
clear_btn.clicked.connect(self._clear_results)
|
||||
btn_layout.addWidget(clear_btn)
|
||||
|
||||
btn_layout.addStretch()
|
||||
layout.addLayout(btn_layout)
|
||||
|
||||
layout.addStretch()
|
||||
return tab
|
||||
|
||||
def _create_region_test_tab(self):
|
||||
"""Create region test tab."""
|
||||
tab = QWidget()
|
||||
layout = QVBoxLayout(tab)
|
||||
|
||||
info = QLabel("Test OCR on specific screen region")
|
||||
info.setStyleSheet("color: #888;")
|
||||
layout.addWidget(info)
|
||||
|
||||
# Region input
|
||||
region_group = QGroupBox("Screen Region (pixels)")
|
||||
region_layout = QHBoxLayout(region_group)
|
||||
|
||||
self.x_input = QSpinBox()
|
||||
self.x_input.setRange(0, 10000)
|
||||
self.x_input.setValue(100)
|
||||
region_layout.addWidget(QLabel("X:"))
|
||||
region_layout.addWidget(self.x_input)
|
||||
|
||||
self.y_input = QSpinBox()
|
||||
self.y_input.setRange(0, 10000)
|
||||
self.y_input.setValue(100)
|
||||
region_layout.addWidget(QLabel("Y:"))
|
||||
region_layout.addWidget(self.y_input)
|
||||
|
||||
self.w_input = QSpinBox()
|
||||
self.w_input.setRange(100, 10000)
|
||||
self.w_input.setValue(400)
|
||||
region_layout.addWidget(QLabel("Width:"))
|
||||
region_layout.addWidget(self.w_input)
|
||||
|
||||
self.h_input = QSpinBox()
|
||||
self.h_input.setRange(100, 10000)
|
||||
self.h_input.setValue(300)
|
||||
region_layout.addWidget(QLabel("Height:"))
|
||||
region_layout.addWidget(self.h_input)
|
||||
|
||||
layout.addWidget(region_group)
|
||||
|
||||
# Presets
|
||||
preset_layout = QHBoxLayout()
|
||||
preset_layout.addWidget(QLabel("Quick Presets:"))
|
||||
|
||||
presets = [
|
||||
("Chat Window", 10, 800, 600, 200),
|
||||
("Skills Window", 100, 100, 500, 400),
|
||||
("Inventory", 1200, 200, 600, 500),
|
||||
("Mission Tracker", 1600, 100, 300, 600),
|
||||
]
|
||||
|
||||
for name, x, y, w, h in presets:
|
||||
btn = QPushButton(name)
|
||||
btn.clicked.connect(lambda checked, px=x, py=y, pw=w, ph=h: self._set_region(px, py, pw, ph))
|
||||
preset_layout.addWidget(btn)
|
||||
|
||||
preset_layout.addStretch()
|
||||
layout.addLayout(preset_layout)
|
||||
|
||||
# Test button
|
||||
region_test_btn = QPushButton("▶ Test Region OCR")
|
||||
region_test_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #4ecdc4;
|
||||
color: #141f23;
|
||||
font-weight: bold;
|
||||
padding: 10px;
|
||||
}
|
||||
""")
|
||||
region_test_btn.clicked.connect(self._run_region_test)
|
||||
layout.addWidget(region_test_btn)
|
||||
|
||||
# Results
|
||||
self.region_results = QTextEdit()
|
||||
self.region_results.setReadOnly(True)
|
||||
self.region_results.setPlaceholderText("Region OCR results will appear here...")
|
||||
self.region_results.setStyleSheet("""
|
||||
QTextEdit {
|
||||
background-color: #0d1117;
|
||||
color: #c9d1d9;
|
||||
font-family: Consolas, monospace;
|
||||
}
|
||||
""")
|
||||
layout.addWidget(self.region_results)
|
||||
|
||||
layout.addStretch()
|
||||
return tab
|
||||
|
||||
def _create_history_tab(self):
|
||||
"""Create history tab."""
|
||||
tab = QWidget()
|
||||
layout = QVBoxLayout(tab)
|
||||
|
||||
info = QLabel("History of OCR tests")
|
||||
info.setStyleSheet("color: #888;")
|
||||
layout.addWidget(info)
|
||||
|
||||
self.history_text = QTextEdit()
|
||||
self.history_text.setReadOnly(True)
|
||||
self.history_text.setStyleSheet("""
|
||||
QTextEdit {
|
||||
background-color: #0d1117;
|
||||
color: #c9d1d9;
|
||||
}
|
||||
""")
|
||||
layout.addWidget(self.history_text)
|
||||
|
||||
# Controls
|
||||
btn_layout = QHBoxLayout()
|
||||
|
||||
refresh_btn = QPushButton("🔄 Refresh")
|
||||
refresh_btn.clicked.connect(self._update_history)
|
||||
btn_layout.addWidget(refresh_btn)
|
||||
|
||||
clear_hist_btn = QPushButton("🗑 Clear History")
|
||||
clear_hist_btn.clicked.connect(self._clear_history)
|
||||
btn_layout.addWidget(clear_hist_btn)
|
||||
|
||||
btn_layout.addStretch()
|
||||
layout.addLayout(btn_layout)
|
||||
|
||||
return tab
|
||||
|
||||
def _create_calibration_tab(self):
|
||||
"""Create calibration tab."""
|
||||
tab = QWidget()
|
||||
layout = QVBoxLayout(tab)
|
||||
|
||||
info = QLabel("Calibration tools for optimizing OCR accuracy")
|
||||
info.setStyleSheet("color: #888;")
|
||||
layout.addWidget(info)
|
||||
|
||||
# DPI awareness
|
||||
dpi_group = QGroupBox("Display Settings")
|
||||
dpi_layout = QVBoxLayout(dpi_group)
|
||||
|
||||
self.dpi_label = QLabel("Detecting display DPI...")
|
||||
dpi_layout.addWidget(self.dpi_label)
|
||||
|
||||
detect_dpi_btn = QPushButton("Detect Display Settings")
|
||||
detect_dpi_btn.clicked.connect(self._detect_display_settings)
|
||||
dpi_layout.addWidget(detect_dpi_btn)
|
||||
|
||||
layout.addWidget(dpi_group)
|
||||
|
||||
# Backend status
|
||||
backend_group = QGroupBox("OCR Backend Status")
|
||||
backend_layout = QVBoxLayout(backend_group)
|
||||
|
||||
self.backend_status = QTextEdit()
|
||||
self.backend_status.setReadOnly(True)
|
||||
self.backend_status.setMaximumHeight(150)
|
||||
self._check_backends()
|
||||
backend_layout.addWidget(self.backend_status)
|
||||
|
||||
layout.addWidget(backend_group)
|
||||
|
||||
# Tips
|
||||
tips_group = QGroupBox("Tips for Best Results")
|
||||
tips_layout = QVBoxLayout(tips_group)
|
||||
|
||||
tips_text = QLabel("""
|
||||
• Make sure text is clearly visible and not blurry
|
||||
• Use region selection to focus on specific text areas
|
||||
• Higher resolution screens work better for OCR
|
||||
• Close other windows to reduce background noise
|
||||
• Ensure good contrast between text and background
|
||||
""")
|
||||
tips_text.setWordWrap(True)
|
||||
tips_text.setStyleSheet("color: #aaa;")
|
||||
tips_layout.addWidget(tips_text)
|
||||
|
||||
layout.addWidget(tips_group)
|
||||
|
||||
layout.addStretch()
|
||||
return tab
|
||||
|
||||
def _set_region(self, x, y, w, h):
|
||||
"""Set region values."""
|
||||
self.x_input.setValue(x)
|
||||
self.y_input.setValue(y)
|
||||
self.w_input.setValue(w)
|
||||
self.h_input.setValue(h)
|
||||
|
||||
def _run_quick_test(self):
|
||||
"""Run quick OCR test."""
|
||||
if self.ocr_thread and self.ocr_thread.isRunning():
|
||||
return
|
||||
|
||||
backend_map = {
|
||||
0: 'auto',
|
||||
1: 'easyocr',
|
||||
2: 'tesseract',
|
||||
3: 'paddle'
|
||||
}
|
||||
backend = backend_map.get(self.backend_combo.currentIndex(), 'auto')
|
||||
|
||||
self.test_btn.setEnabled(False)
|
||||
self.test_btn.setText("⏳ Running...")
|
||||
self.progress_bar.setValue(0)
|
||||
|
||||
self.ocr_thread = OCRTestThread(backend=backend)
|
||||
self.ocr_thread.progress_update.connect(self._update_progress)
|
||||
self.ocr_thread.result_ready.connect(self._on_ocr_complete)
|
||||
self.ocr_thread.start()
|
||||
|
||||
def _update_progress(self, value, message):
|
||||
"""Update progress bar."""
|
||||
self.progress_bar.setValue(value)
|
||||
self.status_label.setText(message)
|
||||
|
||||
def _on_ocr_complete(self, results):
|
||||
"""Handle OCR completion."""
|
||||
self.test_btn.setEnabled(True)
|
||||
self.test_btn.setText("▶ Run OCR Test")
|
||||
|
||||
if results['success']:
|
||||
self.results_text.setPlainText(results['text'])
|
||||
self.stats_label.setText(
|
||||
f"Backend: {results['backend_used']} | "
|
||||
f"Time: {results['processing_time']:.2f}s | "
|
||||
f"Status: ✅ Success"
|
||||
)
|
||||
|
||||
# Add to history
|
||||
self._add_to_history(results)
|
||||
else:
|
||||
self.results_text.setPlainText(f"Error: {results.get('error', 'Unknown error')}")
|
||||
self.stats_label.setText(f"Backend: {results.get('backend_used', '-')} | Status: ❌ Failed")
|
||||
|
||||
def _run_region_test(self):
|
||||
"""Run region OCR test."""
|
||||
region = (
|
||||
self.x_input.value(),
|
||||
self.y_input.value(),
|
||||
self.w_input.value(),
|
||||
self.h_input.value()
|
||||
)
|
||||
|
||||
self.region_results.setPlainText("Running OCR on region...")
|
||||
|
||||
# Use api's ocr_capture if available
|
||||
try:
|
||||
result = self.ocr_capture(region=region)
|
||||
self.region_results.setPlainText(result.get('text', 'No text detected'))
|
||||
except Exception as e:
|
||||
self.region_results.setPlainText(f"Error: {e}")
|
||||
|
||||
def _save_text(self):
|
||||
"""Save OCR text to file."""
|
||||
text = self.results_text.toPlainText()
|
||||
if not text:
|
||||
QMessageBox.warning(None, "Save Error", "No text to save!")
|
||||
return
|
||||
|
||||
file_path, _ = QFileDialog.getSaveFileName(
|
||||
None, "Save OCR Text", "ocr_result.txt", "Text Files (*.txt)"
|
||||
)
|
||||
if file_path:
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
f.write(text)
|
||||
self.status_label.setText(f"Saved to {file_path}")
|
||||
|
||||
def _copy_to_clipboard(self):
|
||||
"""Copy text to clipboard."""
|
||||
text = self.results_text.toPlainText()
|
||||
if text:
|
||||
self.copy_to_clipboard(text)
|
||||
self.status_label.setText("Copied to clipboard!")
|
||||
|
||||
def _clear_results(self):
|
||||
"""Clear results."""
|
||||
self.results_text.clear()
|
||||
self.stats_label.setText("Backend: - | Time: - | Status: Waiting")
|
||||
self.progress_bar.setValue(0)
|
||||
|
||||
def _add_to_history(self, results):
|
||||
"""Add result to history."""
|
||||
from datetime import datetime
|
||||
entry = {
|
||||
'time': datetime.now().strftime("%H:%M:%S"),
|
||||
'backend': results['backend_used'],
|
||||
'time_taken': results['processing_time'],
|
||||
'text_preview': results['text'][:100] + "..." if len(results['text']) > 100 else results['text']
|
||||
}
|
||||
self.test_history.insert(0, entry)
|
||||
if len(self.test_history) > self.max_history:
|
||||
self.test_history = self.test_history[:self.max_history]
|
||||
self._update_history()
|
||||
|
||||
def _update_history(self):
|
||||
"""Update history display."""
|
||||
lines = []
|
||||
for entry in self.test_history:
|
||||
lines.append(f"[{entry['time']}] {entry['backend']} ({entry['time_taken']:.2f}s)")
|
||||
lines.append(f" {entry['text_preview']}")
|
||||
lines.append("")
|
||||
self.history_text.setPlainText('\n'.join(lines) if lines else "No history yet")
|
||||
|
||||
def _clear_history(self):
|
||||
"""Clear history."""
|
||||
self.test_history.clear()
|
||||
self._update_history()
|
||||
|
||||
def _detect_display_settings(self):
|
||||
"""Detect display settings."""
|
||||
try:
|
||||
from PyQt6.QtWidgets import QApplication
|
||||
from PyQt6.QtGui import QScreen
|
||||
|
||||
app = QApplication.instance()
|
||||
if app:
|
||||
screens = app.screens()
|
||||
info = []
|
||||
for i, screen in enumerate(screens):
|
||||
geo = screen.geometry()
|
||||
dpi = screen.logicalDotsPerInch()
|
||||
info.append(f"Screen {i+1}: {geo.width()}x{geo.height()} @ {dpi:.0f} DPI")
|
||||
self.dpi_label.setText('\n'.join(info))
|
||||
except Exception as e:
|
||||
self.dpi_label.setText(f"Error: {e}")
|
||||
|
||||
def _check_backends(self):
|
||||
"""Check OCR backend availability."""
|
||||
statuses = []
|
||||
|
||||
try:
|
||||
import easyocr
|
||||
statuses.append("✅ EasyOCR - Available")
|
||||
except ImportError:
|
||||
statuses.append("❌ EasyOCR - Not installed (pip install easyocr)")
|
||||
|
||||
try:
|
||||
import pytesseract
|
||||
statuses.append("✅ Tesseract - Available")
|
||||
except ImportError:
|
||||
statuses.append("❌ Tesseract - Not installed (pip install pytesseract)")
|
||||
|
||||
try:
|
||||
from paddleocr import PaddleOCR
|
||||
statuses.append("✅ PaddleOCR - Available")
|
||||
except ImportError:
|
||||
statuses.append("❌ PaddleOCR - Not installed (pip install paddleocr)")
|
||||
|
||||
try:
|
||||
from PIL import ImageGrab
|
||||
statuses.append("✅ Screen Capture - Available")
|
||||
except ImportError:
|
||||
statuses.append("❌ Screen Capture - PIL not available")
|
||||
|
||||
self.backend_status.setPlainText('\n'.join(statuses))
|
||||
|
||||
def shutdown(self):
|
||||
"""Clean up."""
|
||||
if self.ocr_thread and self.ocr_thread.isRunning():
|
||||
self.ocr_thread.wait(1000)
|
||||
super().shutdown()
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
"""Log Parser Test Plugin."""
|
||||
from .plugin import LogParserTestPlugin
|
||||
|
|
@ -0,0 +1,366 @@
|
|||
"""
|
||||
EU-Utility - Log Parser Test Plugin
|
||||
|
||||
Debug and test tool for the Log Reader core service.
|
||||
Monitors log parsing in real-time with detailed output.
|
||||
"""
|
||||
|
||||
from PyQt6.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QTextEdit,
|
||||
QLabel, QPushButton, QComboBox, QCheckBox,
|
||||
QSpinBox, QGroupBox, QSplitter, QFrame,
|
||||
QTableWidget, QTableWidgetItem, QHeaderView
|
||||
)
|
||||
from PyQt6.QtCore import Qt, QTimer
|
||||
from PyQt6.QtGui import QColor, QFont
|
||||
|
||||
from plugins.base_plugin import BasePlugin
|
||||
from core.event_bus import SkillGainEvent, LootEvent, DamageEvent, GlobalEvent
|
||||
|
||||
|
||||
class LogParserTestPlugin(BasePlugin):
|
||||
"""Test and debug tool for log parsing functionality."""
|
||||
|
||||
name = "Log Parser Test"
|
||||
version = "1.0.0"
|
||||
author = "EU-Utility"
|
||||
description = "Debug tool for testing log parsing and event detection"
|
||||
|
||||
def __init__(self, overlay_window, config):
|
||||
super().__init__(overlay_window, config)
|
||||
self.event_counts = {
|
||||
'skill_gain': 0,
|
||||
'loot': 0,
|
||||
'global': 0,
|
||||
'damage': 0,
|
||||
'damage_taken': 0,
|
||||
'heal': 0,
|
||||
'mission_complete': 0,
|
||||
'tier_increase': 0,
|
||||
'enhancer_break': 0,
|
||||
'unknown': 0
|
||||
}
|
||||
self.recent_events = []
|
||||
self.max_events = 100
|
||||
self.auto_scroll = True
|
||||
|
||||
def initialize(self):
|
||||
"""Initialize plugin and subscribe to events."""
|
||||
# Subscribe to all event types for testing
|
||||
self.subscribe_typed(SkillGainEvent, self._on_skill_gain)
|
||||
self.subscribe_typed(LootEvent, self._on_loot)
|
||||
self.subscribe_typed(DamageEvent, self._on_damage)
|
||||
self.subscribe_typed(GlobalEvent, self._on_global)
|
||||
|
||||
# Also subscribe to raw log events via API
|
||||
api = self._get_api()
|
||||
if api:
|
||||
api.subscribe('log_event', self._on_raw_log_event)
|
||||
|
||||
self.log_info("Log Parser Test initialized - monitoring events...")
|
||||
|
||||
def _get_api(self):
|
||||
"""Get PluginAPI instance."""
|
||||
try:
|
||||
from core.plugin_api import get_api
|
||||
return get_api()
|
||||
except:
|
||||
return None
|
||||
|
||||
def _on_skill_gain(self, event: SkillGainEvent):
|
||||
"""Handle skill gain events."""
|
||||
self.event_counts['skill_gain'] += 1
|
||||
self._add_event("Skill Gain", f"{event.skill_name}: +{event.points} pts", "#4ecdc4")
|
||||
|
||||
def _on_loot(self, event: LootEvent):
|
||||
"""Handle loot events."""
|
||||
self.event_counts['loot'] += 1
|
||||
value_str = f" ({event.value:.2f} PED)" if event.value else ""
|
||||
self._add_event("Loot", f"{event.item_name} x{event.quantity}{value_str}", "#ff8c42")
|
||||
|
||||
def _on_damage(self, event: DamageEvent):
|
||||
"""Handle damage events."""
|
||||
if event.is_critical:
|
||||
self.event_counts['damage'] += 1
|
||||
self._add_event("Damage", f"{event.amount} damage ({event.damage_type})", "#ff6b6b")
|
||||
else:
|
||||
self.event_counts['damage_taken'] += 1
|
||||
self._add_event("Damage Taken", f"{event.amount} damage", "#ff4757")
|
||||
|
||||
def _on_global(self, event: GlobalEvent):
|
||||
"""Handle global events."""
|
||||
self.event_counts['global'] += 1
|
||||
self._add_event("Global", f"{event.player} found {event.item} worth {event.value} PED!", "#ffd93d")
|
||||
|
||||
def _on_raw_log_event(self, event_data):
|
||||
"""Handle raw log events."""
|
||||
event_type = event_data.get('event_type', 'unknown')
|
||||
self.event_counts[event_type] = self.event_counts.get(event_type, 0) + 1
|
||||
|
||||
def _add_event(self, event_type: str, details: str, color: str):
|
||||
"""Add event to recent events list."""
|
||||
from datetime import datetime
|
||||
timestamp = datetime.now().strftime("%H:%M:%S")
|
||||
|
||||
self.recent_events.insert(0, {
|
||||
'time': timestamp,
|
||||
'type': event_type,
|
||||
'details': details,
|
||||
'color': color
|
||||
})
|
||||
|
||||
# Trim to max
|
||||
if len(self.recent_events) > self.max_events:
|
||||
self.recent_events = self.recent_events[:self.max_events]
|
||||
|
||||
# Update UI if available
|
||||
if hasattr(self, 'events_table'):
|
||||
self._update_events_table()
|
||||
|
||||
def get_ui(self):
|
||||
"""Create plugin UI."""
|
||||
widget = QWidget()
|
||||
layout = QVBoxLayout(widget)
|
||||
layout.setSpacing(12)
|
||||
|
||||
# Header
|
||||
header = QLabel("📊 Log Parser Test & Debug Tool")
|
||||
header.setStyleSheet("font-size: 16px; font-weight: bold; color: #ff8c42;")
|
||||
layout.addWidget(header)
|
||||
|
||||
# Status bar
|
||||
status_frame = QFrame()
|
||||
status_frame.setStyleSheet("background-color: #1a1f2e; border-radius: 6px; padding: 8px;")
|
||||
status_layout = QHBoxLayout(status_frame)
|
||||
|
||||
self.status_label = QLabel("Status: Monitoring...")
|
||||
self.status_label.setStyleSheet("color: #4ecdc4;")
|
||||
status_layout.addWidget(self.status_label)
|
||||
status_layout.addStretch()
|
||||
|
||||
self.log_path_label = QLabel()
|
||||
self._update_log_path()
|
||||
self.log_path_label.setStyleSheet("color: #666;")
|
||||
status_layout.addWidget(self.log_path_label)
|
||||
|
||||
layout.addWidget(status_frame)
|
||||
|
||||
# Event counters
|
||||
counters_group = QGroupBox("Event Counters")
|
||||
counters_layout = QHBoxLayout(counters_group)
|
||||
|
||||
self.counter_labels = {}
|
||||
for event_type, color in [
|
||||
('skill_gain', '#4ecdc4'),
|
||||
('loot', '#ff8c42'),
|
||||
('global', '#ffd93d'),
|
||||
('damage', '#ff6b6b'),
|
||||
('damage_taken', '#ff4757')
|
||||
]:
|
||||
frame = QFrame()
|
||||
frame.setStyleSheet(f"background-color: #1a1f2e; border-left: 3px solid {color}; padding: 8px;")
|
||||
frame_layout = QVBoxLayout(frame)
|
||||
|
||||
name_label = QLabel(event_type.replace('_', ' ').title())
|
||||
name_label.setStyleSheet("color: #888; font-size: 10px;")
|
||||
frame_layout.addWidget(name_label)
|
||||
|
||||
count_label = QLabel("0")
|
||||
count_label.setStyleSheet(f"color: {color}; font-size: 20px; font-weight: bold;")
|
||||
frame_layout.addWidget(count_label)
|
||||
|
||||
self.counter_labels[event_type] = count_label
|
||||
counters_layout.addWidget(frame)
|
||||
|
||||
layout.addWidget(counters_group)
|
||||
|
||||
# Recent events table
|
||||
events_group = QGroupBox("Recent Events (last 100)")
|
||||
events_layout = QVBoxLayout(events_group)
|
||||
|
||||
self.events_table = QTableWidget()
|
||||
self.events_table.setColumnCount(3)
|
||||
self.events_table.setHorizontalHeaderLabels(["Time", "Type", "Details"])
|
||||
self.events_table.horizontalHeader().setStretchLastSection(True)
|
||||
self.events_table.setStyleSheet("""
|
||||
QTableWidget {
|
||||
background-color: #141f23;
|
||||
border: 1px solid #333;
|
||||
}
|
||||
QTableWidget::item {
|
||||
padding: 6px;
|
||||
}
|
||||
""")
|
||||
events_layout.addWidget(self.events_table)
|
||||
|
||||
# Auto-scroll checkbox
|
||||
self.auto_scroll_cb = QCheckBox("Auto-scroll to new events")
|
||||
self.auto_scroll_cb.setChecked(True)
|
||||
events_layout.addWidget(self.auto_scroll_cb)
|
||||
|
||||
layout.addWidget(events_group, 1) # Stretch factor 1
|
||||
|
||||
# Test controls
|
||||
controls_group = QGroupBox("Test Controls")
|
||||
controls_layout = QHBoxLayout(controls_group)
|
||||
|
||||
# Simulate event buttons
|
||||
btn_layout = QHBoxLayout()
|
||||
|
||||
test_skill_btn = QPushButton("Test: Skill Gain")
|
||||
test_skill_btn.clicked.connect(self._simulate_skill_gain)
|
||||
btn_layout.addWidget(test_skill_btn)
|
||||
|
||||
test_loot_btn = QPushButton("Test: Loot")
|
||||
test_loot_btn.clicked.connect(self._simulate_loot)
|
||||
btn_layout.addWidget(test_loot_btn)
|
||||
|
||||
test_damage_btn = QPushButton("Test: Damage")
|
||||
test_damage_btn.clicked.connect(self._simulate_damage)
|
||||
btn_layout.addWidget(test_damage_btn)
|
||||
|
||||
clear_btn = QPushButton("Clear Events")
|
||||
clear_btn.clicked.connect(self._clear_events)
|
||||
btn_layout.addWidget(clear_btn)
|
||||
|
||||
controls_layout.addLayout(btn_layout)
|
||||
controls_layout.addStretch()
|
||||
|
||||
layout.addWidget(controls_group)
|
||||
|
||||
# Raw log view
|
||||
raw_group = QGroupBox("Raw Log Lines (last 50)")
|
||||
raw_layout = QVBoxLayout(raw_group)
|
||||
|
||||
self.raw_log_text = QTextEdit()
|
||||
self.raw_log_text.setReadOnly(True)
|
||||
self.raw_log_text.setStyleSheet("""
|
||||
QTextEdit {
|
||||
background-color: #0d1117;
|
||||
color: #c9d1d9;
|
||||
font-family: Consolas, monospace;
|
||||
font-size: 11px;
|
||||
}
|
||||
""")
|
||||
raw_layout.addWidget(self.raw_log_text)
|
||||
|
||||
layout.addWidget(raw_group)
|
||||
|
||||
# Start update timer
|
||||
self.update_timer = QTimer()
|
||||
self.update_timer.timeout.connect(self._update_counters)
|
||||
self.update_timer.start(1000) # Update every second
|
||||
|
||||
# Subscribe to raw log lines
|
||||
self.read_log(lines=50) # Pre-fill with recent log
|
||||
|
||||
return widget
|
||||
|
||||
def _update_log_path(self):
|
||||
"""Update log path display."""
|
||||
try:
|
||||
from core.log_reader import LogReader
|
||||
reader = LogReader()
|
||||
if reader.log_path:
|
||||
self.log_path_label.setText(f"Log: {reader.log_path}")
|
||||
else:
|
||||
self.log_path_label.setText("Log: Not found")
|
||||
except Exception as e:
|
||||
self.log_path_label.setText(f"Log: Error - {e}")
|
||||
|
||||
def _update_counters(self):
|
||||
"""Update counter displays."""
|
||||
for event_type, label in self.counter_labels.items():
|
||||
count = self.event_counts.get(event_type, 0)
|
||||
label.setText(str(count))
|
||||
|
||||
def _update_events_table(self):
|
||||
"""Update events table with recent events."""
|
||||
self.events_table.setRowCount(len(self.recent_events))
|
||||
|
||||
for i, event in enumerate(self.recent_events):
|
||||
# Time
|
||||
time_item = QTableWidgetItem(event['time'])
|
||||
time_item.setForeground(QColor("#888"))
|
||||
self.events_table.setItem(i, 0, time_item)
|
||||
|
||||
# Type
|
||||
type_item = QTableWidgetItem(event['type'])
|
||||
type_item.setForeground(QColor(event['color']))
|
||||
type_item.setFont(QFont("Segoe UI", weight=QFont.Weight.Bold))
|
||||
self.events_table.setItem(i, 1, type_item)
|
||||
|
||||
# Details
|
||||
details_item = QTableWidgetItem(event['details'])
|
||||
details_item.setForeground(QColor("#c9d1d9"))
|
||||
self.events_table.setItem(i, 2, details_item)
|
||||
|
||||
# Auto-scroll
|
||||
if self.auto_scroll_cb.isChecked() and self.recent_events:
|
||||
self.events_table.scrollToTop()
|
||||
|
||||
def _simulate_skill_gain(self):
|
||||
"""Simulate a skill gain event for testing."""
|
||||
from datetime import datetime
|
||||
event = SkillGainEvent(
|
||||
timestamp=datetime.now(),
|
||||
skill_name="Rifle",
|
||||
points=0.1234,
|
||||
new_value=1234.56
|
||||
)
|
||||
self._on_skill_gain(event)
|
||||
self.log_info("Simulated skill gain event")
|
||||
|
||||
def _simulate_loot(self):
|
||||
"""Simulate a loot event for testing."""
|
||||
from datetime import datetime
|
||||
event = LootEvent(
|
||||
timestamp=datetime.now(),
|
||||
item_name="Shrapnel",
|
||||
quantity=100,
|
||||
value=1.0,
|
||||
mob_name="Atrox"
|
||||
)
|
||||
self._on_loot(event)
|
||||
self.log_info("Simulated loot event")
|
||||
|
||||
def _simulate_damage(self):
|
||||
"""Simulate a damage event for testing."""
|
||||
from datetime import datetime
|
||||
event = DamageEvent(
|
||||
timestamp=datetime.now(),
|
||||
amount=45,
|
||||
damage_type="Impact",
|
||||
is_critical=True
|
||||
)
|
||||
self._on_damage(event)
|
||||
self.log_info("Simulated damage event")
|
||||
|
||||
def _clear_events(self):
|
||||
"""Clear all events."""
|
||||
self.recent_events.clear()
|
||||
for key in self.event_counts:
|
||||
self.event_counts[key] = 0
|
||||
self._update_events_table()
|
||||
self._update_counters()
|
||||
self.log_info("Cleared all events")
|
||||
|
||||
def read_log(self, lines=50):
|
||||
"""Read recent log lines."""
|
||||
try:
|
||||
log_lines = self.read_log(lines=lines)
|
||||
if log_lines:
|
||||
self.raw_log_text.setPlainText('\n'.join(log_lines))
|
||||
except Exception as e:
|
||||
self.raw_log_text.setPlainText(f"Error reading log: {e}")
|
||||
|
||||
def on_show(self):
|
||||
"""Called when plugin is shown."""
|
||||
self._update_events_table()
|
||||
self._update_counters()
|
||||
|
||||
def shutdown(self):
|
||||
"""Clean up."""
|
||||
if hasattr(self, 'update_timer'):
|
||||
self.update_timer.stop()
|
||||
super().shutdown()
|
||||
Loading…
Reference in New Issue