diff --git a/plugins/game_reader_test/plugin.py b/plugins/game_reader_test/plugin.py index 7c82cde..57eaae0 100644 --- a/plugins/game_reader_test/plugin.py +++ b/plugins/game_reader_test/plugin.py @@ -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()