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:
parent
bf42c2a1b7
commit
1538508b63
|
|
@ -674,14 +674,16 @@ class GameReaderTestPlugin(BasePlugin):
|
||||||
"""Parse skills from OCR text."""
|
"""Parse skills from OCR text."""
|
||||||
skills = {}
|
skills = {}
|
||||||
|
|
||||||
# Ranks in Entropia Universe
|
# Ranks in Entropia Universe - multi-word first for proper matching
|
||||||
RANKS = [
|
SINGLE_RANKS = [
|
||||||
'Newbie', 'Inept', 'Beginner', 'Amateur', 'Average',
|
'Newbie', 'Inept', 'Beginner', 'Amateur', 'Average',
|
||||||
'Skilled', 'Expert', 'Professional', 'Master', 'Grand Master',
|
'Skilled', 'Expert', 'Professional', 'Master',
|
||||||
'Champion', 'Legendary', 'Guru', 'Astonishing', 'Remarkable',
|
'Champion', 'Legendary', 'Guru', 'Astonishing', 'Remarkable',
|
||||||
'Outstanding', 'Marvelous', 'Prodigious', 'Amazing', 'Incredible', 'Awesome'
|
'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
|
# Clean text
|
||||||
text = text.replace('SKILLS', '').replace('ALL CATEGORIES', '')
|
text = text.replace('SKILLS', '').replace('ALL CATEGORIES', '')
|
||||||
|
|
@ -705,6 +707,9 @@ class GameReaderTestPlugin(BasePlugin):
|
||||||
rank = match.group(2)
|
rank = match.group(2)
|
||||||
points = int(match.group(3))
|
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:
|
if points > 0 and skill_name:
|
||||||
skills[skill_name] = {'rank': rank, 'points': points}
|
skills[skill_name] = {'rank': rank, 'points': points}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,26 +54,26 @@ class SkillOCRThread(QThread):
|
||||||
"""Parse skill data from OCR text with improved handling for 3-column layout."""
|
"""Parse skill data from OCR text with improved handling for 3-column layout."""
|
||||||
skills = {}
|
skills = {}
|
||||||
|
|
||||||
# Ranks in Entropia Universe (in order)
|
# Ranks in Entropia Universe (including multi-word ranks)
|
||||||
RANKS = [
|
# Single word ranks
|
||||||
|
SINGLE_RANKS = [
|
||||||
'Newbie', 'Inept', 'Beginner', 'Amateur', 'Average',
|
'Newbie', 'Inept', 'Beginner', 'Amateur', 'Average',
|
||||||
'Skilled', 'Expert', 'Professional', 'Master', 'Grand Master',
|
'Skilled', 'Expert', 'Professional', 'Master',
|
||||||
'Champion', 'Legendary', 'Guru', 'Astonishing', 'Remarkable',
|
'Champion', 'Legendary', 'Guru', 'Astonishing', 'Remarkable',
|
||||||
'Outstanding', 'Marvelous', 'Prodigious', 'Amazing', 'Incredible', 'Awesome'
|
'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
|
# Clean up the text - remove common headers and junk
|
||||||
text = text.replace('SKILLS', '').replace('ALL CATEGORIES', '')
|
text = text.replace('SKILLS', '').replace('ALL CATEGORIES', '')
|
||||||
text = text.replace('SKILL NAME', '').replace('RANK', '').replace('POINTS', '')
|
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')
|
lines = text.split('\n')
|
||||||
|
|
||||||
|
|
@ -88,6 +88,7 @@ class SkillOCRThread(QThread):
|
||||||
|
|
||||||
# Try pattern: SkillName Rank Points
|
# Try pattern: SkillName Rank Points
|
||||||
# More flexible pattern to handle merged text
|
# 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(
|
match = re.search(
|
||||||
rf'([A-Za-z][A-Za-z\s]{{2,50}}?)\s+({rank_pattern})\s+(\d{{1,6}})(?:\s|$)',
|
rf'([A-Za-z][A-Za-z\s]{{2,50}}?)\s+({rank_pattern})\s+(\d{{1,6}})(?:\s|$)',
|
||||||
line, re.IGNORECASE
|
line, re.IGNORECASE
|
||||||
|
|
@ -98,11 +99,12 @@ class SkillOCRThread(QThread):
|
||||||
rank = match.group(2)
|
rank = match.group(2)
|
||||||
points = int(match.group(3))
|
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()
|
skill_name = skill_name.strip()
|
||||||
|
|
||||||
# Validate - points should be reasonable (not too small)
|
# Validate - points should be reasonable (not too small)
|
||||||
if points > 0:
|
if points > 0 and skill_name:
|
||||||
skills[skill_name] = {
|
skills[skill_name] = {
|
||||||
'rank': rank,
|
'rank': rank,
|
||||||
'points': points,
|
'points': points,
|
||||||
|
|
@ -112,7 +114,7 @@ class SkillOCRThread(QThread):
|
||||||
|
|
||||||
# Alternative parsing: try to find skill-rank-points triplets
|
# Alternative parsing: try to find skill-rank-points triplets
|
||||||
if not skills:
|
if not skills:
|
||||||
skills = self._parse_skills_alternative(text, RANKS)
|
skills = self._parse_skills_alternative(text, ALL_RANKS)
|
||||||
|
|
||||||
return skills
|
return skills
|
||||||
|
|
||||||
|
|
@ -123,13 +125,16 @@ class SkillOCRThread(QThread):
|
||||||
# Find all rank positions in the text
|
# Find all rank positions in the text
|
||||||
for rank in ranks:
|
for rank in ranks:
|
||||||
# Look for pattern: [text] [Rank] [number]
|
# 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)
|
matches = re.finditer(pattern, text, re.IGNORECASE)
|
||||||
|
|
||||||
for match in matches:
|
for match in matches:
|
||||||
skill_name = match.group(1).strip()
|
skill_name = match.group(1).strip()
|
||||||
points = int(match.group(2))
|
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:
|
if points > 0 and len(skill_name) > 2:
|
||||||
skills[skill_name] = {
|
skills[skill_name] = {
|
||||||
'rank': rank,
|
'rank': rank,
|
||||||
|
|
@ -216,6 +221,9 @@ class SkillScannerPlugin(BasePlugin):
|
||||||
scan_group = QGroupBox("OCR Scan (Core Service)")
|
scan_group = QGroupBox("OCR Scan (Core Service)")
|
||||||
scan_layout = QVBoxLayout(scan_group)
|
scan_layout = QVBoxLayout(scan_group)
|
||||||
|
|
||||||
|
# Buttons row
|
||||||
|
buttons_layout = QHBoxLayout()
|
||||||
|
|
||||||
scan_btn = QPushButton("Scan Skills Window")
|
scan_btn = QPushButton("Scan Skills Window")
|
||||||
scan_btn.setStyleSheet("""
|
scan_btn.setStyleSheet("""
|
||||||
QPushButton {
|
QPushButton {
|
||||||
|
|
@ -228,7 +236,23 @@ class SkillScannerPlugin(BasePlugin):
|
||||||
}
|
}
|
||||||
""")
|
""")
|
||||||
scan_btn.clicked.connect(self._scan_skills)
|
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 = QLabel("Ready to scan")
|
||||||
self.scan_progress.setStyleSheet("color: rgba(255,255,255,150);")
|
self.scan_progress.setStyleSheet("color: rgba(255,255,255,150);")
|
||||||
|
|
@ -312,8 +336,30 @@ class SkillScannerPlugin(BasePlugin):
|
||||||
self._refresh_skills_table()
|
self._refresh_skills_table()
|
||||||
self.scan_progress.setText(f"Found {len(skills_data)} skills")
|
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):
|
def _on_scan_error(self, error):
|
||||||
self.scan_progress.setText(f"Error: {error}")
|
self.scan_progress.setText(f"Error: {error}")
|
||||||
|
self.scan_progress.setText(f"Error: {error}")
|
||||||
|
|
||||||
def _refresh_skills_table(self):
|
def _refresh_skills_table(self):
|
||||||
self.skills_table.setRowCount(len(self.skills_data))
|
self.skills_table.setRowCount(len(self.skills_data))
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue