fix: Add Arch Master rank and Reset button to Skill Scanner

FIXES:
1. Added 'Arch Master' to the list of multi-word ranks
   - Multi-word ranks are now matched first to prevent partial matches
   - Changed rank matching order: ['Arch Master', 'Grand Master'] + single ranks
   - This fixes 'Laser Weaponry Technology Arch' being parsed incorrectly
   - Now correctly parses as: 'Laser Weaponry Technology', 'Arch Master', 8805

2. Added 'Reset Data' button to Skill Scanner plugin
   - Red button next to 'Scan Skills Window'
   - Shows confirmation dialog before clearing
   - Clears: skills_data, skill_gains, and the UI tables
   - Also clears the data file (skill_tracker.json)

3. Clean skill names by removing 'Skill' prefix
   - OCR sometimes reads 'Skill Laser Weaponry Technology'
   - Now strips 'Skill' or 'SKILL' prefix from skill names

4. Updated both Skill Scanner and Game Reader Test plugins
   - Both now use the same improved parsing logic
   - Both handle multi-word ranks correctly
This commit is contained in:
LemonNexus 2026-02-15 00:23:13 +00:00
parent bf42c2a1b7
commit 1538508b63
2 changed files with 72 additions and 21 deletions

View File

@ -674,14 +674,16 @@ class GameReaderTestPlugin(BasePlugin):
"""Parse skills from OCR text."""
skills = {}
# Ranks in Entropia Universe
RANKS = [
# Ranks in Entropia Universe - multi-word first for proper matching
SINGLE_RANKS = [
'Newbie', 'Inept', 'Beginner', 'Amateur', 'Average',
'Skilled', 'Expert', 'Professional', 'Master', 'Grand Master',
'Skilled', 'Expert', 'Professional', 'Master',
'Champion', 'Legendary', 'Guru', 'Astonishing', 'Remarkable',
'Outstanding', 'Marvelous', 'Prodigious', 'Amazing', 'Incredible', 'Awesome'
]
rank_pattern = '|'.join(RANKS)
MULTI_RANKS = ['Arch Master', 'Grand Master']
ALL_RANKS = MULTI_RANKS + SINGLE_RANKS
rank_pattern = '|'.join(ALL_RANKS)
# Clean text
text = text.replace('SKILLS', '').replace('ALL CATEGORIES', '')
@ -705,6 +707,9 @@ class GameReaderTestPlugin(BasePlugin):
rank = match.group(2)
points = int(match.group(3))
# Clean skill name - remove "Skill" prefix
skill_name = re.sub(r'^(Skill|SKILL)\s*', '', skill_name, flags=re.IGNORECASE)
if points > 0 and skill_name:
skills[skill_name] = {'rank': rank, 'points': points}

View File

@ -54,26 +54,26 @@ class SkillOCRThread(QThread):
"""Parse skill data from OCR text with improved handling for 3-column layout."""
skills = {}
# Ranks in Entropia Universe (in order)
RANKS = [
# Ranks in Entropia Universe (including multi-word ranks)
# Single word ranks
SINGLE_RANKS = [
'Newbie', 'Inept', 'Beginner', 'Amateur', 'Average',
'Skilled', 'Expert', 'Professional', 'Master', 'Grand Master',
'Skilled', 'Expert', 'Professional', 'Master',
'Champion', 'Legendary', 'Guru', 'Astonishing', 'Remarkable',
'Outstanding', 'Marvelous', 'Prodigious', 'Amazing', 'Incredible', 'Awesome'
]
rank_pattern = '|'.join(RANKS)
# Multi-word ranks (must be checked first - longer matches first)
MULTI_RANKS = [
'Arch Master', 'Grand Master'
]
# Combine: multi-word first (so they match before single word), then single
ALL_RANKS = MULTI_RANKS + SINGLE_RANKS
rank_pattern = '|'.join(ALL_RANKS)
# Clean up the text - remove common headers and junk
text = text.replace('SKILLS', '').replace('ALL CATEGORIES', '')
text = text.replace('SKILL NAME', '').replace('RANK', '').replace('POINTS', '')
text = text.replace('Attributes', '').replace('COMBAT', '').replace('Design', '')
text = text.replace('Construction', '').replace('Defense', '').replace('General', '')
text = text.replace('Handgun', '').replace('Heavy Melee Weapons', '')
text = text.replace('Information', '').replace('Inflict Melee Damage', '')
text = text.replace('Inflict Ranged Damage', '').replace('Light Melee Weapons', '')
text = text.replace('Longblades', '').replace('Medical', '').replace('Mining', '')
text = text.replace('Science', '').replace('Social', '').replace('Beauty', '')
text = text.replace('Mindforce', '')
lines = text.split('\n')
@ -88,6 +88,7 @@ class SkillOCRThread(QThread):
# Try pattern: SkillName Rank Points
# More flexible pattern to handle merged text
# Skill name can be 2-50 chars, rank from our list, points 1-6 digits
match = re.search(
rf'([A-Za-z][A-Za-z\s]{{2,50}}?)\s+({rank_pattern})\s+(\d{{1,6}})(?:\s|$)',
line, re.IGNORECASE
@ -98,11 +99,12 @@ class SkillOCRThread(QThread):
rank = match.group(2)
points = int(match.group(3))
# Clean up skill name
# Clean up skill name - remove common words that might be prepended
skill_name = re.sub(r'^(Skill|SKILL)\s*', '', skill_name, flags=re.IGNORECASE)
skill_name = skill_name.strip()
# Validate - points should be reasonable (not too small)
if points > 0:
if points > 0 and skill_name:
skills[skill_name] = {
'rank': rank,
'points': points,
@ -112,7 +114,7 @@ class SkillOCRThread(QThread):
# Alternative parsing: try to find skill-rank-points triplets
if not skills:
skills = self._parse_skills_alternative(text, RANKS)
skills = self._parse_skills_alternative(text, ALL_RANKS)
return skills
@ -123,13 +125,16 @@ class SkillOCRThread(QThread):
# Find all rank positions in the text
for rank in ranks:
# Look for pattern: [text] [Rank] [number]
pattern = rf'([A-Z][a-z]{{2,}}(?:\s+[A-Z][a-z]{{2,}}){{0,3}})\s+{rank}\s+(\d{{1,6}})'
pattern = rf'([A-Z][a-z]{{2,}}(?:\s+[A-Z][a-z]{{2,}}){{0,3}})\s+{re.escape(rank)}\s+(\d{{1,6}})'
matches = re.finditer(pattern, text, re.IGNORECASE)
for match in matches:
skill_name = match.group(1).strip()
points = int(match.group(2))
# Clean skill name
skill_name = re.sub(r'^(Skill|SKILL)\s*', '', skill_name, flags=re.IGNORECASE)
if points > 0 and len(skill_name) > 2:
skills[skill_name] = {
'rank': rank,
@ -216,6 +221,9 @@ class SkillScannerPlugin(BasePlugin):
scan_group = QGroupBox("OCR Scan (Core Service)")
scan_layout = QVBoxLayout(scan_group)
# Buttons row
buttons_layout = QHBoxLayout()
scan_btn = QPushButton("Scan Skills Window")
scan_btn.setStyleSheet("""
QPushButton {
@ -228,7 +236,23 @@ class SkillScannerPlugin(BasePlugin):
}
""")
scan_btn.clicked.connect(self._scan_skills)
scan_layout.addWidget(scan_btn)
buttons_layout.addWidget(scan_btn)
reset_btn = QPushButton("Reset Data")
reset_btn.setStyleSheet("""
QPushButton {
background-color: #ff4757;
color: white;
padding: 12px;
border: none;
border-radius: 4px;
font-weight: bold;
}
""")
reset_btn.clicked.connect(self._reset_data)
buttons_layout.addWidget(reset_btn)
scan_layout.addLayout(buttons_layout)
self.scan_progress = QLabel("Ready to scan")
self.scan_progress.setStyleSheet("color: rgba(255,255,255,150);")
@ -312,8 +336,30 @@ class SkillScannerPlugin(BasePlugin):
self._refresh_skills_table()
self.scan_progress.setText(f"Found {len(skills_data)} skills")
def _reset_data(self):
"""Reset all skill data."""
from PyQt6.QtWidgets import QMessageBox
reply = QMessageBox.question(
None,
"Reset Skill Data",
"Are you sure you want to clear all scanned skill data?\n\nThis cannot be undone.",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No
)
if reply == QMessageBox.StandardButton.Yes:
self.skills_data = {}
self.skill_gains = []
self._save_data()
self._refresh_skills_table()
self.gains_text.clear()
self.total_gains_label.setText("Total gains: 0")
self.scan_progress.setText("Data cleared")
def _on_scan_error(self, error):
self.scan_progress.setText(f"Error: {error}")
self.scan_progress.setText(f"Error: {error}")
def _refresh_skills_table(self):
self.skills_table.setRowCount(len(self.skills_data))