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:
LemonNexus 2026-02-15 00:07:28 +00:00
parent a30bcbaba7
commit b0f3b0b00c
1 changed files with 224 additions and 2 deletions

View File

@ -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()