feat: Add Skills Parser tab to Game Reader Test plugin
NEW FEATURES: 1. Skills Parser Tab: - Dedicated tab for parsing EU Skills window - Captures screen and extracts skills automatically - Shows results in a 3-column table (Skill Name, Rank, Points) - Displays raw OCR text for debugging - Shows count of parsed skills 2. Improved Skills Parsing: - Better pattern matching for skill names with spaces - Recognizes all EU skill ranks - Filters out headers and category names - Validates points are reasonable numbers 3. UI Improvements: - Clear instructions on how to use - Visual feedback during capture - Color-coded status (cyan = working, red = error) - Table auto-sizes columns USAGE: 1. Open EU Skills window 2. Go to Game Reader Test → Skills Parser tab 3. Click 'Capture Skills Window' 4. View parsed skills in the table This makes it much easier to test skill scanning and verify that the OCR is parsing correctly!
This commit is contained in:
parent
a30bcbaba7
commit
b0f3b0b00c
|
|
@ -11,7 +11,8 @@ from PyQt6.QtWidgets import (
|
|||
QLabel, QPushButton, QComboBox, QCheckBox,
|
||||
QSpinBox, QGroupBox, QSplitter, QFrame,
|
||||
QTabWidget, QLineEdit, QProgressBar,
|
||||
QFileDialog, QMessageBox
|
||||
QFileDialog, QMessageBox, QTableWidget,
|
||||
QTableWidgetItem, QHeaderView
|
||||
)
|
||||
from PyQt6.QtCore import Qt, QTimer, QThread, pyqtSignal
|
||||
from PyQt6.QtGui import QPixmap, QImage, QColor
|
||||
|
|
@ -180,7 +181,10 @@ class GameReaderTestPlugin(BasePlugin):
|
|||
# Tab 3: History
|
||||
tabs.addTab(self._create_history_tab(), "History")
|
||||
|
||||
# Tab 4: Calibration
|
||||
# Tab 5: Skills Parser (NEW)
|
||||
tabs.addTab(self._create_skills_parser_tab(), "Skills Parser")
|
||||
|
||||
# Tab 6: Calibration
|
||||
tabs.addTab(self._create_calibration_tab(), "Calibration")
|
||||
|
||||
layout.addWidget(tabs, 1)
|
||||
|
|
@ -498,6 +502,224 @@ class GameReaderTestPlugin(BasePlugin):
|
|||
|
||||
return tab
|
||||
|
||||
def _create_skills_parser_tab(self):
|
||||
"""Create skills parser tab for testing skill window OCR."""
|
||||
tab = QWidget()
|
||||
layout = QVBoxLayout(tab)
|
||||
|
||||
info = QLabel("📊 Skills Window Parser - Extract skills from the EU Skills window")
|
||||
info.setStyleSheet("color: #888;")
|
||||
layout.addWidget(info)
|
||||
|
||||
# Instructions
|
||||
instructions = QLabel(
|
||||
"1. Open your Skills window in Entropia Universe\n"
|
||||
"2. Click 'Capture Skills Window' below\n"
|
||||
"3. View parsed skills in the table"
|
||||
)
|
||||
instructions.setStyleSheet("color: #666; font-size: 11px;")
|
||||
layout.addWidget(instructions)
|
||||
|
||||
# Capture button
|
||||
capture_btn = QPushButton("📷 Capture Skills Window")
|
||||
capture_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #ff8c42;
|
||||
color: #141f23;
|
||||
font-weight: bold;
|
||||
padding: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
""")
|
||||
capture_btn.clicked.connect(self._capture_and_parse_skills)
|
||||
layout.addWidget(capture_btn)
|
||||
|
||||
# Results table
|
||||
from PyQt6.QtWidgets import QTableWidget, QTableWidgetItem, QHeaderView
|
||||
|
||||
self.skills_table = QTableWidget()
|
||||
self.skills_table.setColumnCount(3)
|
||||
self.skills_table.setHorizontalHeaderLabels(["Skill Name", "Rank", "Points"])
|
||||
self.skills_table.horizontalHeader().setStretchLastSection(True)
|
||||
self.skills_table.setStyleSheet("""
|
||||
QTableWidget {
|
||||
background-color: #0d1117;
|
||||
border: 1px solid #333;
|
||||
}
|
||||
QTableWidget::item {
|
||||
padding: 6px;
|
||||
color: #c9d1d9;
|
||||
}
|
||||
QHeaderView::section {
|
||||
background-color: #1a1f2e;
|
||||
color: #ff8c42;
|
||||
padding: 8px;
|
||||
font-weight: bold;
|
||||
}
|
||||
""")
|
||||
layout.addWidget(self.skills_table, 1)
|
||||
|
||||
# Stats label
|
||||
self.skills_stats_label = QLabel("No skills captured yet")
|
||||
self.skills_stats_label.setStyleSheet("color: #888;")
|
||||
layout.addWidget(self.skills_stats_label)
|
||||
|
||||
# Raw text view
|
||||
raw_group = QGroupBox("Raw OCR Text (for debugging)")
|
||||
raw_layout = QVBoxLayout(raw_group)
|
||||
|
||||
self.skills_raw_text = QTextEdit()
|
||||
self.skills_raw_text.setReadOnly(True)
|
||||
self.skills_raw_text.setMaximumHeight(150)
|
||||
self.skills_raw_text.setStyleSheet("""
|
||||
QTextEdit {
|
||||
background-color: #0d1117;
|
||||
color: #666;
|
||||
font-family: Consolas, monospace;
|
||||
font-size: 10px;
|
||||
}
|
||||
""")
|
||||
raw_layout.addWidget(self.skills_raw_text)
|
||||
layout.addWidget(raw_group)
|
||||
|
||||
layout.addStretch()
|
||||
return tab
|
||||
|
||||
def _capture_and_parse_skills(self):
|
||||
"""Capture screen and parse skills."""
|
||||
from PyQt6.QtCore import Qt
|
||||
|
||||
self.skills_stats_label.setText("Capturing...")
|
||||
self.skills_stats_label.setStyleSheet("color: #4ecdc4;")
|
||||
|
||||
# Run in thread to not block UI
|
||||
from threading import Thread
|
||||
|
||||
def capture_and_parse():
|
||||
try:
|
||||
from PIL import ImageGrab
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
# Capture screen
|
||||
screenshot = ImageGrab.grab()
|
||||
|
||||
# Run OCR
|
||||
text = ""
|
||||
try:
|
||||
import easyocr
|
||||
reader = easyocr.Reader(['en'], gpu=False, verbose=False)
|
||||
import numpy as np
|
||||
result = reader.readtext(np.array(screenshot), detail=0, paragraph=False)
|
||||
text = '\n'.join(result)
|
||||
except Exception as e:
|
||||
# Fallback to raw OCR
|
||||
text = str(e)
|
||||
|
||||
# Parse skills
|
||||
skills = self._parse_skills_from_text(text)
|
||||
|
||||
# Update UI
|
||||
from PyQt6.QtCore import QMetaObject, Qt, Q_ARG
|
||||
QMetaObject.invokeMethod(
|
||||
self.skills_raw_text,
|
||||
"setPlainText",
|
||||
Qt.ConnectionType.QueuedConnection,
|
||||
Q_ARG(str, text)
|
||||
)
|
||||
|
||||
# Update table
|
||||
QMetaObject.invokeMethod(
|
||||
self, "_update_skills_table",
|
||||
Qt.ConnectionType.QueuedConnection,
|
||||
Q_ARG(object, skills)
|
||||
)
|
||||
|
||||
# Update stats
|
||||
stats_text = f"Found {len(skills)} skills"
|
||||
QMetaObject.invokeMethod(
|
||||
self.skills_stats_label,
|
||||
"setText",
|
||||
Qt.ConnectionType.QueuedConnection,
|
||||
Q_ARG(str, stats_text)
|
||||
)
|
||||
QMetaObject.invokeMethod(
|
||||
self.skills_stats_label,
|
||||
"setStyleSheet",
|
||||
Qt.ConnectionType.QueuedConnection,
|
||||
Q_ARG(str, "color: #4ecdc4;")
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
from PyQt6.QtCore import QMetaObject, Qt, Q_ARG
|
||||
QMetaObject.invokeMethod(
|
||||
self.skills_stats_label,
|
||||
"setText",
|
||||
Qt.ConnectionType.QueuedConnection,
|
||||
Q_ARG(str, f"Error: {str(e)}")
|
||||
)
|
||||
QMetaObject.invokeMethod(
|
||||
self.skills_stats_label,
|
||||
"setStyleSheet",
|
||||
Qt.ConnectionType.QueuedConnection,
|
||||
Q_ARG(str, "color: #ff6b6b;")
|
||||
)
|
||||
|
||||
thread = Thread(target=capture_and_parse)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
def _parse_skills_from_text(self, text):
|
||||
"""Parse skills from OCR text."""
|
||||
skills = {}
|
||||
|
||||
# Ranks in Entropia Universe
|
||||
RANKS = [
|
||||
'Newbie', 'Inept', 'Beginner', 'Amateur', 'Average',
|
||||
'Skilled', 'Expert', 'Professional', 'Master', 'Grand Master',
|
||||
'Champion', 'Legendary', 'Guru', 'Astonishing', 'Remarkable',
|
||||
'Outstanding', 'Marvelous', 'Prodigious', 'Amazing', 'Incredible', 'Awesome'
|
||||
]
|
||||
rank_pattern = '|'.join(RANKS)
|
||||
|
||||
# Clean text
|
||||
text = text.replace('SKILLS', '').replace('ALL CATEGORIES', '')
|
||||
text = text.replace('SKILL NAME', '').replace('RANK', '').replace('POINTS', '')
|
||||
|
||||
lines = text.split('\n')
|
||||
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if not line or len(line) < 10:
|
||||
continue
|
||||
|
||||
# Pattern: SkillName Rank Points
|
||||
match = re.search(
|
||||
rf'([A-Za-z][A-Za-z\s]{{2,50}}?)\s+({rank_pattern})\s+(\d{{1,6}})(?:\s|$)',
|
||||
line, re.IGNORECASE
|
||||
)
|
||||
|
||||
if match:
|
||||
skill_name = match.group(1).strip()
|
||||
rank = match.group(2)
|
||||
points = int(match.group(3))
|
||||
|
||||
if points > 0 and skill_name:
|
||||
skills[skill_name] = {'rank': rank, 'points': points}
|
||||
|
||||
return skills
|
||||
|
||||
def _update_skills_table(self, skills):
|
||||
"""Update the skills table with parsed data."""
|
||||
self.skills_table.setRowCount(len(skills))
|
||||
|
||||
for i, (skill_name, data) in enumerate(sorted(skills.items())):
|
||||
self.skills_table.setItem(i, 0, QTableWidgetItem(skill_name))
|
||||
self.skills_table.setItem(i, 1, QTableWidgetItem(data['rank']))
|
||||
self.skills_table.setItem(i, 2, QTableWidgetItem(str(data['points'])))
|
||||
|
||||
self.skills_table.resizeColumnsToContents()
|
||||
|
||||
def _create_calibration_tab(self):
|
||||
"""Create calibration tab."""
|
||||
tab = QWidget()
|
||||
|
|
|
|||
Loading…
Reference in New Issue