diff --git a/projects/EU-Utility/core/eu_styles.py b/projects/EU-Utility/core/eu_styles.py new file mode 100644 index 0000000..9bcc29d --- /dev/null +++ b/projects/EU-Utility/core/eu_styles.py @@ -0,0 +1,209 @@ +""" +EU-Utility - EU Styling Constants + +Exact styling to match Entropia Universe game UI. +Based on screenshot analysis. +""" + +# EU Color Palette from screenshots +EU_COLORS = { + # Backgrounds + 'bg_dark': 'rgba(15, 20, 28, 230)', + 'bg_panel': 'rgba(25, 30, 40, 220)', + 'bg_header': 'rgba(35, 40, 55, 200)', + 'bg_hover': 'rgba(45, 50, 70, 240)', + + # Borders + 'border_subtle': 'rgba(80, 90, 110, 80)', + 'border_medium': 'rgba(100, 110, 130, 100)', + 'border_bright': 'rgba(120, 130, 150, 120)', + 'border_orange': 'rgba(200, 130, 50, 150)', + + # Accents + 'accent_orange': '#ff8c42', + 'accent_gold': '#ffc107', + 'accent_blue': '#4a9eff', + 'accent_green': '#4caf50', + 'accent_red': '#f44336', + + # Text + 'text_primary': 'rgba(255, 255, 255, 240)', + 'text_secondary': 'rgba(255, 255, 255, 180)', + 'text_muted': 'rgba(255, 255, 255, 120)', + + # Progress bars + 'progress_bg': 'rgba(60, 70, 90, 150)', + 'progress_fill': 'rgba(255, 140, 66, 200)', +} + +# EU Border Radius - slightly rounded corners +EU_RADIUS = { + 'small': '4px', + 'medium': '6px', + 'large': '8px', + 'button': '3px', +} + +# EU Font Stack +EU_FONT = '"Eurostile", "Bank Gothic", "Segoe UI", Arial, sans-serif' + +# EU Style Sheets +EU_STYLES = { + 'overlay_container': f""" + QWidget {{ + background-color: {EU_COLORS['bg_dark']}; + border: 1px solid {EU_COLORS['border_medium']}; + border-radius: {EU_RADIUS['large']}; + }} + """, + + 'panel': f""" + QWidget {{ + background-color: {EU_COLORS['bg_panel']}; + border: 1px solid {EU_COLORS['border_subtle']}; + border-radius: {EU_RADIUS['medium']}; + }} + """, + + 'header': f""" + QWidget {{ + background-color: {EU_COLORS['bg_header']}; + border-top-left-radius: {EU_RADIUS['large']}; + border-top-right-radius: {EU_RADIUS['large']}; + border-bottom: 1px solid {EU_COLORS['border_medium']}; + }} + """, + + 'button_primary': f""" + QPushButton {{ + background-color: rgba(255, 140, 66, 200); + color: white; + border: 1px solid rgba(255, 160, 80, 100); + border-radius: {EU_RADIUS['button']}; + padding: 8px 16px; + font-weight: bold; + font-size: 12px; + }} + QPushButton:hover {{ + background-color: rgba(255, 160, 80, 230); + border: 1px solid rgba(255, 180, 100, 150); + }} + QPushButton:pressed {{ + background-color: rgba(230, 120, 50, 200); + }} + """, + + 'button_secondary': f""" + QPushButton {{ + background-color: rgba(60, 70, 90, 150); + color: {EU_COLORS['text_secondary']}; + border: 1px solid {EU_COLORS['border_subtle']}; + border-radius: {EU_RADIUS['button']}; + padding: 6px 12px; + font-size: 11px; + }} + QPushButton:hover {{ + background-color: rgba(80, 90, 110, 180); + border: 1px solid {EU_COLORS['border_medium']}; + }} + """, + + 'input': f""" + QLineEdit {{ + background-color: rgba(20, 25, 35, 200); + color: {EU_COLORS['text_primary']}; + border: 1px solid {EU_COLORS['border_subtle']}; + border-radius: {EU_RADIUS['small']}; + padding: 6px 10px; + font-size: 12px; + }} + QLineEdit:focus {{ + border: 1px solid {EU_COLORS['border_orange']}; + }} + """, + + 'table': f""" + QTableWidget {{ + background-color: rgba(20, 25, 35, 150); + color: {EU_COLORS['text_primary']}; + border: 1px solid {EU_COLORS['border_subtle']}; + border-radius: {EU_RADIUS['medium']}; + gridline-color: {EU_COLORS['border_subtle']}; + }} + QTableWidget::item {{ + padding: 6px; + border-bottom: 1px solid {EU_COLORS['border_subtle']}; + }} + QTableWidget::item:selected {{ + background-color: rgba(255, 140, 66, 100); + }} + QHeaderView::section {{ + background-color: {EU_COLORS['bg_header']}; + color: {EU_COLORS['text_secondary']}; + padding: 8px; + border: none; + border-right: 1px solid {EU_COLORS['border_subtle']}; + font-weight: bold; + font-size: 11px; + }} + ""', + + 'progress_bar': f""" + QProgressBar {{ + background-color: {EU_COLORS['progress_bg']}; + border: none; + border-radius: 2px; + height: 4px; + text-align: center; + }} + QProgressBar::chunk {{ + background-color: {EU_COLORS['progress_fill']}; + border-radius: 2px; + }} + """, + + 'tab': f""" + QTabBar::tab {{ + background-color: rgba(35, 40, 55, 200); + color: {EU_COLORS['text_muted']}; + padding: 10px 20px; + border-top-left-radius: {EU_RADIUS['medium']}; + border-top-right-radius: {EU_RADIUS['medium']}; + margin-right: 2px; + font-size: 11px; + }} + QTabBar::tab:selected {{ + background-color: {EU_COLORS['accent_orange']}; + color: white; + font-weight: bold; + }} + QTabBar::tab:hover:!selected {{ + background-color: rgba(45, 50, 70, 240); + color: {EU_COLORS['text_secondary']}; + }} + ""', + + 'floating_icon': f""" + QLabel {{ + background-color: {EU_COLORS['bg_panel']}; + border: 1px solid {EU_COLORS['border_orange']}; + border-radius: {EU_RADIUS['small']}; + }} + """, + + 'floating_icon_hover': f""" + QLabel {{ + background-color: {EU_COLORS['bg_hover']}; + border: 1px solid {EU_COLORS['accent_orange']}; + border-radius: {EU_RADIUS['small']}; + }} + ""', +} + +def get_eu_style(style_name): + """Get an EU style by name.""" + return EU_STYLES.get(style_name, "") + +def get_color(name): + """Get an EU color by name.""" + return EU_COLORS.get(name, "white") diff --git a/projects/EU-Utility/plugins/auction_tracker/__init__.py b/projects/EU-Utility/plugins/auction_tracker/__init__.py new file mode 100644 index 0000000..2b39bff --- /dev/null +++ b/projects/EU-Utility/plugins/auction_tracker/__init__.py @@ -0,0 +1,7 @@ +""" +Auction Tracker Plugin +""" + +from .plugin import AuctionTrackerPlugin + +__all__ = ["AuctionTrackerPlugin"] diff --git a/projects/EU-Utility/plugins/auction_tracker/plugin.py b/projects/EU-Utility/plugins/auction_tracker/plugin.py new file mode 100644 index 0000000..605d2e8 --- /dev/null +++ b/projects/EU-Utility/plugins/auction_tracker/plugin.py @@ -0,0 +1,240 @@ +""" +EU-Utility - Auction Tracker Plugin + +Track auction prices and market trends. +""" + +import json +from datetime import datetime, timedelta +from pathlib import Path +from collections import defaultdict + +from PyQt6.QtWidgets import ( + QWidget, QVBoxLayout, QHBoxLayout, QLabel, + QPushButton, QTableWidget, QTableWidgetItem, + QLineEdit, QComboBox, QFrame +) +from PyQt6.QtCore import Qt + +from plugins.base_plugin import BasePlugin + + +class AuctionTrackerPlugin(BasePlugin): + """Track auction prices and analyze market trends.""" + + name = "Auction Tracker" + version = "1.0.0" + author = "ImpulsiveFPS" + description = "Track prices, markups, and market trends" + hotkey = "ctrl+shift+a" + + def initialize(self): + """Setup auction tracker.""" + self.data_file = Path("data/auction_tracker.json") + self.data_file.parent.mkdir(parents=True, exist_ok=True) + + self.price_history = defaultdict(list) + self.watchlist = [] + + self._load_data() + + def _load_data(self): + """Load auction data.""" + if self.data_file.exists(): + try: + with open(self.data_file, 'r') as f: + data = json.load(f) + self.price_history = defaultdict(list, data.get('prices', {})) + self.watchlist = data.get('watchlist', []) + except: + pass + + def _save_data(self): + """Save auction data.""" + with open(self.data_file, 'w') as f: + json.dump({ + 'prices': dict(self.price_history), + 'watchlist': self.watchlist + }, f, indent=2) + + def get_ui(self): + """Create auction tracker UI.""" + widget = QWidget() + widget.setStyleSheet("background: transparent;") + layout = QVBoxLayout(widget) + layout.setSpacing(15) + layout.setContentsMargins(0, 0, 0, 0) + + # Title + title = QLabel("📈 Auction Tracker") + title.setStyleSheet("color: white; font-size: 16px; font-weight: bold;") + layout.addWidget(title) + + # Search + search_layout = QHBoxLayout() + + self.search_input = QLineEdit() + self.search_input.setPlaceholderText("Search item...") + self.search_input.setStyleSheet(""" + QLineEdit { + background-color: rgba(30, 35, 45, 200); + color: white; + border: 1px solid rgba(100, 110, 130, 80); + border-radius: 4px; + padding: 8px; + } + """) + search_layout.addWidget(self.search_input) + + search_btn = QPushButton("🔍") + search_btn.setFixedSize(32, 32) + search_btn.setStyleSheet(""" + QPushButton { + background-color: #ff8c42; + border: none; + border-radius: 4px; + font-size: 14px; + } + """) + search_btn.clicked.connect(self._search_item) + search_layout.addWidget(search_btn) + + layout.addLayout(search_layout) + + # Price table + self.price_table = QTableWidget() + self.price_table.setColumnCount(6) + self.price_table.setHorizontalHeaderLabels(["Item", "Bid", "Buyout", "Markup", "Trend", "Time"]) + self.price_table.setStyleSheet(""" + QTableWidget { + background-color: rgba(30, 35, 45, 200); + color: white; + border: 1px solid rgba(100, 110, 130, 80); + border-radius: 6px; + } + QHeaderView::section { + background-color: rgba(35, 40, 55, 200); + color: rgba(255,255,255,180); + padding: 8px; + font-weight: bold; + font-size: 11px; + } + """) + self.price_table.horizontalHeader().setStretchLastSection(True) + layout.addWidget(self.price_table) + + # Quick scan + scan_btn = QPushButton("📸 Scan Auction Window") + scan_btn.setStyleSheet(""" + QPushButton { + background-color: #ffc107; + color: black; + padding: 12px; + border: none; + border-radius: 4px; + font-weight: bold; + } + QPushButton:hover { + background-color: #ffd54f; + } + """) + scan_btn.clicked.connect(self._scan_auction) + layout.addWidget(scan_btn) + + # Sample data + self._add_sample_data() + + layout.addStretch() + return widget + + def _add_sample_data(self): + """Add sample auction data.""" + sample_items = [ + {"name": "Nanocube", "bid": 304.00, "buyout": 304.00, "markup": 101.33}, + {"name": "Aakas Plating", "bid": 154.00, "buyout": 159.00, "markup": 102.67}, + {"name": "Wenrex Ingot", "bid": 111.00, "buyout": 118.00, "markup": 108.82}, + ] + + self.price_table.setRowCount(len(sample_items)) + for i, item in enumerate(sample_items): + self.price_table.setItem(i, 0, QTableWidgetItem(item['name'])) + self.price_table.setItem(i, 1, QTableWidgetItem(f"{item['bid']:.2f}")) + self.price_table.setItem(i, 2, QTableWidgetItem(f"{item['buyout']:.2f}")) + + markup_item = QTableWidgetItem(f"{item['markup']:.2f}%") + if item['markup'] > 105: + markup_item.setForeground(Qt.GlobalColor.red) + elif item['markup'] < 102: + markup_item.setForeground(Qt.GlobalColor.green) + self.price_table.setItem(i, 3, markup_item) + + self.price_table.setItem(i, 4, QTableWidgetItem("→")) + self.price_table.setItem(i, 5, QTableWidgetItem("2m ago")) + + def _search_item(self): + """Search for item price history.""" + query = self.search_input.text().lower() + # TODO: Implement search + + def _scan_auction(self): + """Scan auction window with OCR.""" + # TODO: Implement OCR scanning + pass + + def record_price(self, item_name, bid, buyout, tt_value=None): + """Record a price observation.""" + entry = { + 'time': datetime.now().isoformat(), + 'bid': bid, + 'buyout': buyout, + 'tt': tt_value, + 'markup': (buyout / tt_value * 100) if tt_value else None + } + + self.price_history[item_name].append(entry) + + # Keep last 100 entries per item + if len(self.price_history[item_name]) > 100: + self.price_history[item_name] = self.price_history[item_name][-100:] + + self._save_data() + + def get_price_trend(self, item_name, days=7): + """Get price trend for an item.""" + history = self.price_history.get(item_name, []) + if not history: + return None + + cutoff = (datetime.now() - timedelta(days=days)).isoformat() + recent = [h for h in history if h['time'] > cutoff] + + if len(recent) < 2: + return None + + prices = [h['buyout'] for h in recent] + return { + 'current': prices[-1], + 'average': sum(prices) / len(prices), + 'min': min(prices), + 'max': max(prices), + 'trend': 'up' if prices[-1] > prices[0] else 'down' if prices[-1] < prices[0] else 'stable' + } + + def get_deals(self, max_markup=102.0): + """Find items below market price.""" + deals = [] + for item_name, history in self.price_history.items(): + if not history: + continue + + latest = history[-1] + markup = latest.get('markup') + + if markup and markup <= max_markup: + deals.append({ + 'item': item_name, + 'price': latest['buyout'], + 'markup': markup + }) + + return sorted(deals, key=lambda x: x['markup']) diff --git a/projects/EU-Utility/plugins/codex_tracker/__init__.py b/projects/EU-Utility/plugins/codex_tracker/__init__.py new file mode 100644 index 0000000..dcba8fd --- /dev/null +++ b/projects/EU-Utility/plugins/codex_tracker/__init__.py @@ -0,0 +1,7 @@ +""" +Codex Tracker Plugin +""" + +from .plugin import CodexTrackerPlugin + +__all__ = ["CodexTrackerPlugin"] diff --git a/projects/EU-Utility/plugins/codex_tracker/plugin.py b/projects/EU-Utility/plugins/codex_tracker/plugin.py new file mode 100644 index 0000000..7d05f1d --- /dev/null +++ b/projects/EU-Utility/plugins/codex_tracker/plugin.py @@ -0,0 +1,218 @@ +""" +EU-Utility - Codex Tracker Plugin + +Track creature challenge progress from Codex. +""" + +import json +from pathlib import Path +from datetime import datetime + +from PyQt6.QtWidgets import ( + QWidget, QVBoxLayout, QHBoxLayout, QLabel, + QPushButton, QProgressBar, QTableWidget, QTableWidgetItem, + QComboBox, QFrame +) +from PyQt6.QtCore import Qt + +from plugins.base_plugin import BasePlugin + + +class CodexTrackerPlugin(BasePlugin): + """Track creature codex progress.""" + + name = "Codex Tracker" + version = "1.0.0" + author = "ImpulsiveFPS" + description = "Track creature challenges and codex progress" + hotkey = "ctrl+shift+x" + + # Arkadia creatures from screenshot + CREATURES = [ + {"name": "Bokol", "rank": 22, "progress": 49.5}, + {"name": "Nusul", "rank": 15, "progress": 24.8}, + {"name": "Wombana", "rank": 10, "progress": 45.2}, + {"name": "Arkadian Hornet", "rank": 1, "progress": 15.0}, + {"name": "Feran", "rank": 4, "progress": 86.0}, + {"name": "Hadraada", "rank": 6, "progress": 0.4}, + {"name": "Halix", "rank": 14, "progress": 0.2}, + {"name": "Huon", "rank": 1, "progress": 45.8}, + {"name": "Kadra", "rank": 2, "progress": 0.7}, + {"name": "Magurg", "rank": 1, "progress": 8.7}, + {"name": "Monura", "rank": 1, "progress": 5.2}, + ] + + def initialize(self): + """Setup codex tracker.""" + self.data_file = Path("data/codex.json") + self.data_file.parent.mkdir(parents=True, exist_ok=True) + + self.creatures = self.CREATURES.copy() + self.scanned_data = {} + + self._load_data() + + def _load_data(self): + """Load codex data.""" + if self.data_file.exists(): + try: + with open(self.data_file, 'r') as f: + data = json.load(f) + self.creatures = data.get('creatures', self.CREATURES) + except: + pass + + def _save_data(self): + """Save codex data.""" + with open(self.data_file, 'w') as f: + json.dump({'creatures': self.creatures}, f, indent=2) + + def get_ui(self): + """Create codex tracker UI.""" + widget = QWidget() + widget.setStyleSheet("background: transparent;") + layout = QVBoxLayout(widget) + layout.setSpacing(15) + layout.setContentsMargins(0, 0, 0, 0) + + # Title + title = QLabel("📖 Codex Tracker") + title.setStyleSheet("color: white; font-size: 16px; font-weight: bold;") + layout.addWidget(title) + + # Summary + summary = QHBoxLayout() + + total_creatures = len(self.creatures) + completed = sum(1 for c in self.creatures if c.get('progress', 0) >= 100) + + self.total_label = QLabel(f"Creatures: {total_creatures}") + self.total_label.setStyleSheet("color: #4a9eff; font-size: 13px;") + summary.addWidget(self.total_label) + + self.completed_label = QLabel(f"Completed: {completed}") + self.completed_label.setStyleSheet("color: #4caf50; font-size: 13px;") + summary.addWidget(self.completed_label) + + summary.addStretch() + layout.addLayout(summary) + + # Scan button + scan_btn = QPushButton("📸 Scan Codex Window") + scan_btn.setStyleSheet(""" + QPushButton { + background-color: #ff8c42; + color: white; + padding: 12px; + border: none; + border-radius: 4px; + font-weight: bold; + } + QPushButton:hover { + background-color: #ffa060; + } + """) + scan_btn.clicked.connect(self._scan_codex) + layout.addWidget(scan_btn) + + # Creatures list + self.creatures_table = QTableWidget() + self.creatures_table.setColumnCount(4) + self.creatures_table.setHorizontalHeaderLabels(["Creature", "Rank", "Progress", "Next Rank"]) + self.creatures_table.setStyleSheet(""" + QTableWidget { + background-color: rgba(30, 35, 45, 200); + color: white; + border: 1px solid rgba(100, 110, 130, 80); + border-radius: 6px; + } + QHeaderView::section { + background-color: rgba(35, 40, 55, 200); + color: rgba(255,255,255,180); + padding: 8px; + border: none; + font-weight: bold; + font-size: 11px; + } + """) + self.creatures_table.horizontalHeader().setStretchLastSection(True) + layout.addWidget(self.creatures_table) + + self._refresh_table() + + layout.addStretch() + return widget + + def _refresh_table(self): + """Refresh creatures table.""" + self.creatures_table.setRowCount(len(self.creatures)) + + for i, creature in enumerate(sorted(self.creatures, key=lambda x: -x.get('progress', 0))): + # Name + name_item = QTableWidgetItem(creature.get('name', 'Unknown')) + name_item.setFlags(name_item.flags() & ~Qt.ItemFlag.ItemIsEditable) + self.creatures_table.setItem(i, 0, name_item) + + # Rank + rank = creature.get('rank', 0) + rank_item = QTableWidgetItem(str(rank)) + rank_item.setFlags(rank_item.flags() & ~Qt.ItemFlag.ItemIsEditable) + rank_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter) + self.creatures_table.setItem(i, 1, rank_item) + + # Progress bar + progress = creature.get('progress', 0) + progress_widget = QWidget() + progress_layout = QHBoxLayout(progress_widget) + progress_layout.setContentsMargins(5, 2, 5, 2) + + bar = QProgressBar() + bar.setValue(int(progress)) + bar.setTextVisible(True) + bar.setFormat(f"{progress:.1f}%") + bar.setStyleSheet(""" + QProgressBar { + background-color: rgba(60, 70, 90, 150); + border: none; + border-radius: 3px; + text-align: center; + color: white; + font-size: 10px; + } + QProgressBar::chunk { + background-color: #ff8c42; + border-radius: 3px; + } + """) + progress_layout.addWidget(bar) + + self.creatures_table.setCellWidget(i, 2, progress_widget) + + # Next rank (estimated kills needed) + progress_item = QTableWidgetItem(f"~{int((100-progress) * 120)} kills") + progress_item.setFlags(progress_item.flags() & ~Qt.ItemFlag.ItemIsEditable) + progress_item.setForeground(Qt.GlobalColor.gray) + self.creatures_table.setItem(i, 3, progress_item) + + def _scan_codex(self): + """Scan codex window with OCR.""" + # TODO: Implement OCR scanning + # For now, simulate update + for creature in self.creatures: + creature['progress'] = min(100, creature.get('progress', 0) + 0.5) + + self._save_data() + self._refresh_table() + + def get_closest_to_rankup(self, count=3): + """Get creatures closest to ranking up.""" + sorted_creatures = sorted( + self.creatures, + key=lambda x: -(x.get('progress', 0)) + ) + return [c for c in sorted_creatures[:count] if c.get('progress', 0) < 100] + + def get_recommended_hunt(self): + """Get recommended creature to hunt.""" + close = self.get_closest_to_rankup(1) + return close[0] if close else None diff --git a/projects/EU-Utility/plugins/dpp_calculator/__init__.py b/projects/EU-Utility/plugins/dpp_calculator/__init__.py new file mode 100644 index 0000000..1130b97 --- /dev/null +++ b/projects/EU-Utility/plugins/dpp_calculator/__init__.py @@ -0,0 +1,7 @@ +""" +DPP Calculator Plugin +""" + +from .plugin import DPPCalculatorPlugin + +__all__ = ["DPPCalculatorPlugin"] diff --git a/projects/EU-Utility/plugins/dpp_calculator/plugin.py b/projects/EU-Utility/plugins/dpp_calculator/plugin.py new file mode 100644 index 0000000..62aa2d1 --- /dev/null +++ b/projects/EU-Utility/plugins/dpp_calculator/plugin.py @@ -0,0 +1,215 @@ +""" +EU-Utility - DPP Calculator Plugin + +Calculate Damage Per PEC for weapons and setups. +""" + +from decimal import Decimal, ROUND_HALF_UP +from PyQt6.QtWidgets import ( + QWidget, QVBoxLayout, QHBoxLayout, QLabel, + QLineEdit, QPushButton, QComboBox, QTableWidget, + QTableWidgetItem, QFrame, QGroupBox +) +from PyQt6.QtCore import Qt + +from plugins.base_plugin import BasePlugin + + +class DPPCalculatorPlugin(BasePlugin): + """Calculate weapon efficiency and DPP.""" + + name = "DPP Calculator" + version = "1.0.0" + author = "ImpulsiveFPS" + description = "Calculate Damage Per PEC and weapon efficiency" + hotkey = "ctrl+shift+d" + + def initialize(self): + """Setup DPP calculator.""" + self.saved_setups = [] + + def get_ui(self): + """Create DPP calculator UI.""" + widget = QWidget() + widget.setStyleSheet("background: transparent;") + layout = QVBoxLayout(widget) + layout.setSpacing(15) + layout.setContentsMargins(0, 0, 0, 0) + + # Title + title = QLabel("🎯 DPP Calculator") + title.setStyleSheet("color: white; font-size: 16px; font-weight: bold;") + layout.addWidget(title) + + # Calculator section + calc_group = QGroupBox("Weapon Setup") + calc_group.setStyleSheet(""" + QGroupBox { + color: rgba(255,255,255,200); + border: 1px solid rgba(100, 110, 130, 80); + border-radius: 6px; + margin-top: 10px; + font-weight: bold; + } + QGroupBox::title { + subcontrol-origin: margin; + left: 10px; + padding: 0 5px; + } + """) + calc_layout = QVBoxLayout(calc_group) + + # Weapon damage + dmg_layout = QHBoxLayout() + dmg_layout.addWidget(QLabel("Damage:")) + self.dmg_input = QLineEdit() + self.dmg_input.setPlaceholderText("e.g., 45.2") + dmg_layout.addWidget(self.dmg_input) + calc_layout.addLayout(dmg_layout) + + # Ammo cost + ammo_layout = QHBoxLayout() + ammo_layout.addWidget(QLabel("Ammo per shot:")) + self.ammo_input = QLineEdit() + self.ammo_input.setPlaceholderText("e.g., 100") + ammo_layout.addWidget(self.ammo_input) + calc_layout.addLayout(ammo_layout) + + # Weapon decay + decay_layout = QHBoxLayout() + decay_layout.addWidget(QLabel("Decay (PEC):")) + self.decay_input = QLineEdit() + self.decay_input.setPlaceholderText("e.g., 2.5") + decay_layout.addWidget(self.decay_input) + calc_layout.addLayout(decay_layout) + + # Amp/scope + amp_layout = QHBoxLayout() + amp_layout.addWidget(QLabel("Amp decay:")) + self.amp_input = QLineEdit() + self.amp_input.setPlaceholderText("0") + self.amp_input.setText("0") + amp_layout.addWidget(self.amp_input) + calc_layout.addLayout(amp_layout) + + # Calculate button + calc_btn = QPushButton("Calculate DPP") + calc_btn.setStyleSheet(""" + QPushButton { + background-color: #ff8c42; + color: white; + padding: 10px; + border: none; + border-radius: 4px; + font-weight: bold; + } + QPushButton:hover { + background-color: #ffa060; + } + """) + calc_btn.clicked.connect(self._calculate) + calc_layout.addWidget(calc_btn) + + # Results + self.result_label = QLabel("DPP: -") + self.result_label.setStyleSheet(""" + color: #4caf50; + font-size: 20px; + font-weight: bold; + """) + calc_layout.addWidget(self.result_label) + + self.cost_label = QLabel("Cost per shot: -") + self.cost_label.setStyleSheet("color: rgba(255,255,255,150);") + calc_layout.addWidget(self.cost_label) + + layout.addWidget(calc_group) + + # Reference table + ref_group = QGroupBox("DPP Reference") + ref_group.setStyleSheet(calc_group.styleSheet()) + ref_layout = QVBoxLayout(ref_group) + + ref_table = QTableWidget() + ref_table.setColumnCount(3) + ref_table.setHorizontalHeaderLabels(["Rating", "DPP Range", "Efficiency"]) + ref_table.setRowCount(5) + + ratings = [ + ("Excellent", "4.0+", "95-100%"), + ("Very Good", "3.5-4.0", "85-95%"), + ("Good", "3.0-3.5", "75-85%"), + ("Average", "2.5-3.0", "60-75%"), + ("Poor", "< 2.5", "< 60%"), + ] + + for i, (rating, dpp, eff) in enumerate(ratings): + ref_table.setItem(i, 0, QTableWidgetItem(rating)) + ref_table.setItem(i, 1, QTableWidgetItem(dpp)) + ref_table.setItem(i, 2, QTableWidgetItem(eff)) + + ref_table.setStyleSheet(""" + QTableWidget { + background-color: rgba(30, 35, 45, 200); + color: white; + border: none; + } + QHeaderView::section { + background-color: rgba(35, 40, 55, 200); + color: rgba(255,255,255,180); + padding: 6px; + font-size: 10px; + } + """) + ref_layout.addWidget(ref_table) + + layout.addWidget(ref_group) + layout.addStretch() + + return widget + + def _calculate(self): + """Calculate DPP.""" + try: + damage = Decimal(self.dmg_input.text() or 0) + ammo = Decimal(self.ammo_input.text() or 0) + decay = Decimal(self.decay_input.text() or 0) + amp = Decimal(self.amp_input.text() or 0) + + # Convert ammo to PEC (1 ammo = 0.0001 PED = 0.01 PEC) + ammo_cost = ammo * Decimal('0.01') + + # Total cost in PEC + total_cost = ammo_cost + decay + amp + + if total_cost > 0: + dpp = damage / (total_cost / Decimal('100')) # Convert to PED for DPP + + self.result_label.setText(f"DPP: {dpp:.3f}") + self.result_label.setStyleSheet(f""" + color: {'#4caf50' if dpp >= Decimal('3.5') else '#ffc107' if dpp >= Decimal('2.5') else '#f44336'}; + font-size: 20px; + font-weight: bold; + """) + + cost_ped = total_cost / Decimal('100') + self.cost_label.setText(f"Cost per shot: {cost_ped:.4f} PED ({total_cost:.2f} PEC)") + else: + self.result_label.setText("DPP: Enter values") + + except Exception as e: + self.result_label.setText(f"Error: {e}") + + def calculate_from_api(self, weapon_data): + """Calculate DPP from Nexus API weapon data.""" + damage = Decimal(str(weapon_data.get('damage', 0))) + decay = Decimal(str(weapon_data.get('decay', 0))) + ammo = Decimal(str(weapon_data.get('ammo', 0))) + + # Calculate + ammo_cost = ammo * Decimal('0.01') + total_cost = ammo_cost + decay + + if total_cost > 0: + return damage / (total_cost / Decimal('100')) + return Decimal('0') diff --git a/projects/EU-Utility/plugins/enhancer_calc/__init__.py b/projects/EU-Utility/plugins/enhancer_calc/__init__.py new file mode 100644 index 0000000..8758bb8 --- /dev/null +++ b/projects/EU-Utility/plugins/enhancer_calc/__init__.py @@ -0,0 +1,7 @@ +""" +Enhancer Calculator Plugin +""" + +from .plugin import EnhancerCalculatorPlugin + +__all__ = ["EnhancerCalculatorPlugin"] diff --git a/projects/EU-Utility/plugins/enhancer_calc/plugin.py b/projects/EU-Utility/plugins/enhancer_calc/plugin.py new file mode 100644 index 0000000..becc61c --- /dev/null +++ b/projects/EU-Utility/plugins/enhancer_calc/plugin.py @@ -0,0 +1,160 @@ +""" +EU-Utility - Enhancer Calculator Plugin + +Calculate enhancer break rates and costs. +""" + +from PyQt6.QtWidgets import ( + QWidget, QVBoxLayout, QHBoxLayout, QLabel, + QLineEdit, QPushButton, QComboBox, QFrame +) +from PyQt6.QtCore import Qt + +from plugins.base_plugin import BasePlugin + + +class EnhancerCalculatorPlugin(BasePlugin): + """Calculate enhancer usage and costs.""" + + name = "Enhancer Calc" + version = "1.0.0" + author = "ImpulsiveFPS" + description = "Calculate enhancer break rates and costs" + hotkey = "ctrl+shift+e" + + # Break rates (approximate) + BREAK_RATES = { + 'Accuracy': 0.0012, # 0.12% + 'Damage': 0.0015, # 0.15% + 'Economy': 0.0010, # 0.10% + 'Range': 0.0013, # 0.13% + } + + def initialize(self): + """Setup enhancer calculator.""" + self.saved_calculations = [] + + def get_ui(self): + """Create enhancer calculator UI.""" + widget = QWidget() + widget.setStyleSheet("background: transparent;") + layout = QVBoxLayout(widget) + layout.setSpacing(15) + layout.setContentsMargins(0, 0, 0, 0) + + # Title + title = QLabel("⚡ Enhancer Calculator") + title.setStyleSheet("color: white; font-size: 16px; font-weight: bold;") + layout.addWidget(title) + + # Calculator + calc_frame = QFrame() + calc_frame.setStyleSheet(""" + QFrame { + background-color: rgba(30, 35, 45, 200); + border: 1px solid rgba(100, 110, 130, 80); + border-radius: 6px; + } + """) + calc_layout = QVBoxLayout(calc_frame) + calc_layout.setSpacing(10) + + # Enhancer type + type_layout = QHBoxLayout() + type_layout.addWidget(QLabel("Type:")) + self.type_combo = QComboBox() + self.type_combo.addItems(list(self.BREAK_RATES.keys())) + self.type_combo.setStyleSheet(""" + QComboBox { + background-color: rgba(20, 25, 35, 200); + color: white; + border: 1px solid rgba(100, 110, 130, 80); + padding: 5px; + } + """) + type_layout.addWidget(self.type_combo) + calc_layout.addLayout(type_layout) + + # TT value + tt_layout = QHBoxLayout() + tt_layout.addWidget(QLabel("TT Value:")) + self.tt_input = QLineEdit() + self.tt_input.setPlaceholderText("e.g., 40.00") + tt_layout.addWidget(self.tt_input) + calc_layout.addLayout(tt_layout) + + # Number of shots + shots_layout = QHBoxLayout() + shots_layout.addWidget(QLabel("Shots/hour:")) + self.shots_input = QLineEdit() + self.shots_input.setPlaceholderText("e.g., 3600") + self.shots_input.setText("3600") + shots_layout.addWidget(self.shots_input) + calc_layout.addLayout(shots_layout) + + # Calculate button + calc_btn = QPushButton("Calculate") + calc_btn.setStyleSheet(""" + QPushButton { + background-color: #ff8c42; + color: white; + padding: 10px; + border: none; + border-radius: 4px; + font-weight: bold; + } + """) + calc_btn.clicked.connect(self._calculate) + calc_layout.addWidget(calc_btn) + + # Results + self.break_rate_label = QLabel("Break rate: -") + self.break_rate_label.setStyleSheet("color: rgba(255,255,255,180);") + calc_layout.addWidget(self.break_rate_label) + + self.breaks_label = QLabel("Expected breaks/hour: -") + self.breaks_label.setStyleSheet("color: #f44336;") + calc_layout.addWidget(self.breaks_label) + + self.cost_label = QLabel("Cost/hour: -") + self.cost_label.setStyleSheet("color: #ffc107;") + calc_layout.addWidget(self.cost_label) + + layout.addWidget(calc_frame) + + # Info + info = QLabel(""" + Typical break rates: + • Damage: ~0.15% per shot + • Accuracy: ~0.12% per shot + • Economy: ~0.10% per shot + • Range: ~0.13% per shot + """) + info.setStyleSheet("color: rgba(255,255,255,120); font-size: 10px;") + info.setWordWrap(True) + layout.addWidget(info) + + layout.addStretch() + return widget + + def _calculate(self): + """Calculate enhancer costs.""" + try: + enhancer_type = self.type_combo.currentText() + tt_value = float(self.tt_input.text() or 0) + shots = int(self.shots_input.text() or 3600) + + break_rate = self.BREAK_RATES.get(enhancer_type, 0.001) + + # Expected breaks + expected_breaks = shots * break_rate + + # Cost + hourly_cost = expected_breaks * tt_value + + self.break_rate_label.setText(f"Break rate: {break_rate*100:.3f}% per shot") + self.breaks_label.setText(f"Expected breaks/hour: {expected_breaks:.2f}") + self.cost_label.setText(f"Cost/hour: {hourly_cost:.2f} PED") + + except Exception as e: + self.break_rate_label.setText(f"Error: {e}") diff --git a/projects/EU-Utility/plugins/inventory_manager/__init__.py b/projects/EU-Utility/plugins/inventory_manager/__init__.py new file mode 100644 index 0000000..64fccac --- /dev/null +++ b/projects/EU-Utility/plugins/inventory_manager/__init__.py @@ -0,0 +1,7 @@ +""" +Inventory Manager Plugin +""" + +from .plugin import InventoryManagerPlugin + +__all__ = ["InventoryManagerPlugin"] diff --git a/projects/EU-Utility/plugins/inventory_manager/plugin.py b/projects/EU-Utility/plugins/inventory_manager/plugin.py new file mode 100644 index 0000000..a6b08fb --- /dev/null +++ b/projects/EU-Utility/plugins/inventory_manager/plugin.py @@ -0,0 +1,219 @@ +""" +EU-Utility - Inventory Manager Plugin + +Track inventory items, value, and weight. +""" + +import json +from pathlib import Path +from collections import defaultdict + +from PyQt6.QtWidgets import ( + QWidget, QVBoxLayout, QHBoxLayout, QLabel, + QPushButton, QTableWidget, QTableWidgetItem, + QLineEdit, QProgressBar, QFrame +) +from PyQt6.QtCore import Qt + +from plugins.base_plugin import BasePlugin + + +class InventoryManagerPlugin(BasePlugin): + """Track and manage inventory.""" + + name = "Inventory" + version = "1.0.0" + author = "ImpulsiveFPS" + description = "Track items, TT value, and weight" + hotkey = "ctrl+shift+i" + + def initialize(self): + """Setup inventory manager.""" + self.data_file = Path("data/inventory.json") + self.data_file.parent.mkdir(parents=True, exist_ok=True) + + self.items = [] + self.max_slots = 200 + self.max_weight = 355.0 + + self._load_data() + + def _load_data(self): + """Load inventory data.""" + if self.data_file.exists(): + try: + with open(self.data_file, 'r') as f: + data = json.load(f) + self.items = data.get('items', []) + except: + pass + + def _save_data(self): + """Save inventory data.""" + with open(self.data_file, 'w') as f: + json.dump({'items': self.items}, f, indent=2) + + def get_ui(self): + """Create inventory UI.""" + widget = QWidget() + widget.setStyleSheet("background: transparent;") + layout = QVBoxLayout(widget) + layout.setSpacing(15) + layout.setContentsMargins(0, 0, 0, 0) + + # Title + title = QLabel("🎒 Inventory Manager") + title.setStyleSheet("color: white; font-size: 16px; font-weight: bold;") + layout.addWidget(title) + + # Stats + stats_frame = QFrame() + stats_frame.setStyleSheet(""" + QFrame { + background-color: rgba(30, 35, 45, 200); + border: 1px solid rgba(100, 110, 130, 80); + border-radius: 6px; + } + """) + stats_layout = QVBoxLayout(stats_frame) + + # Slots + slots_used = len(self.items) + slots_layout = QHBoxLayout() + slots_layout.addWidget(QLabel("Slots:")) + self.slots_progress = QProgressBar() + self.slots_progress.setMaximum(self.max_slots) + self.slots_progress.setValue(slots_used) + self.slots_progress.setStyleSheet(""" + QProgressBar { + background-color: rgba(60, 70, 90, 150); + border: none; + border-radius: 3px; + text-align: center; + color: white; + } + QProgressBar::chunk { + background-color: #4a9eff; + border-radius: 3px; + } + """) + slots_layout.addWidget(self.slots_progress) + self.slots_label = QLabel(f"{slots_used}/{self.max_slots}") + self.slots_label.setStyleSheet("color: white;") + slots_layout.addWidget(self.slots_label) + stats_layout.addLayout(slots_layout) + + # Weight + total_weight = sum(item.get('weight', 0) for item in self.items) + weight_layout = QHBoxLayout() + weight_layout.addWidget(QLabel("Weight:")) + self.weight_progress = QProgressBar() + self.weight_progress.setMaximum(int(self.max_weight)) + self.weight_progress.setValue(int(total_weight)) + self.weight_progress.setStyleSheet(""" + QProgressBar { + background-color: rgba(60, 70, 90, 150); + border: none; + border-radius: 3px; + text-align: center; + color: white; + } + QProgressBar::chunk { + background-color: #ffc107; + border-radius: 3px; + } + """) + weight_layout.addWidget(self.weight_progress) + self.weight_label = QLabel(f"{total_weight:.1f}/{self.max_weight} kg") + self.weight_label.setStyleSheet("color: white;") + weight_layout.addWidget(self.weight_label) + stats_layout.addLayout(weight_layout) + + # PED value + total_tt = sum(item.get('tt', 0) for item in self.items) + ped_label = QLabel(f"💰 Total TT: {total_tt:.2f} PED") + ped_label.setStyleSheet("color: #ffc107; font-weight: bold;") + stats_layout.addWidget(ped_label) + + layout.addWidget(stats_frame) + + # Items table + self.items_table = QTableWidget() + self.items_table.setColumnCount(5) + self.items_table.setHorizontalHeaderLabels(["Item", "Qty", "TT", "Weight", "Container"]) + self.items_table.setStyleSheet(""" + QTableWidget { + background-color: rgba(30, 35, 45, 200); + color: white; + border: 1px solid rgba(100, 110, 130, 80); + border-radius: 6px; + } + QHeaderView::section { + background-color: rgba(35, 40, 55, 200); + color: rgba(255,255,255,180); + padding: 8px; + font-weight: bold; + font-size: 11px; + } + """) + self.items_table.horizontalHeader().setStretchLastSection(True) + layout.addWidget(self.items_table) + + self._refresh_items() + + # Scan button + scan_btn = QPushButton("📸 Scan Inventory") + scan_btn.setStyleSheet(""" + QPushButton { + background-color: #ff8c42; + color: white; + padding: 12px; + border: none; + border-radius: 4px; + font-weight: bold; + } + """) + scan_btn.clicked.connect(self._scan_inventory) + layout.addWidget(scan_btn) + + layout.addStretch() + return widget + + def _refresh_items(self): + """Refresh items table.""" + self.items_table.setRowCount(len(self.items)) + + for i, item in enumerate(self.items): + self.items_table.setItem(i, 0, QTableWidgetItem(item.get('name', 'Unknown'))) + self.items_table.setItem(i, 1, QTableWidgetItem(str(item.get('quantity', 1)))) + self.items_table.setItem(i, 2, QTableWidgetItem(f"{item.get('tt', 0):.2f}")) + self.items_table.setItem(i, 3, QTableWidgetItem(f"{item.get('weight', 0):.2f}")) + self.items_table.setItem(i, 4, QTableWidgetItem(item.get('container', 'Main'))) + + def _scan_inventory(self): + """Scan inventory with OCR.""" + # TODO: Implement OCR + pass + + def add_item(self, name, quantity=1, tt=0.0, weight=0.0, container='Main'): + """Add item to inventory.""" + # Check if exists + for item in self.items: + if item['name'] == name and item['container'] == container: + item['quantity'] += quantity + item['tt'] += tt + item['weight'] += weight + self._save_data() + self._refresh_items() + return + + # New item + self.items.append({ + 'name': name, + 'quantity': quantity, + 'tt': tt, + 'weight': weight, + 'container': container + }) + self._save_data() + self._refresh_items() diff --git a/projects/EU-Utility/plugins/mission_tracker/__init__.py b/projects/EU-Utility/plugins/mission_tracker/__init__.py new file mode 100644 index 0000000..de1d978 --- /dev/null +++ b/projects/EU-Utility/plugins/mission_tracker/__init__.py @@ -0,0 +1,7 @@ +""" +Mission Tracker Plugin +""" + +from .plugin import MissionTrackerPlugin + +__all__ = ["MissionTrackerPlugin"] diff --git a/projects/EU-Utility/plugins/mission_tracker/plugin.py b/projects/EU-Utility/plugins/mission_tracker/plugin.py new file mode 100644 index 0000000..4052fd7 --- /dev/null +++ b/projects/EU-Utility/plugins/mission_tracker/plugin.py @@ -0,0 +1,310 @@ +""" +EU-Utility - Mission Tracker Plugin + +Track active missions and progress. +""" + +import json +from datetime import datetime +from pathlib import Path + +from PyQt6.QtWidgets import ( + QWidget, QVBoxLayout, QHBoxLayout, QLabel, + QPushButton, QProgressBar, QFrame, QScrollArea +) +from PyQt6.QtCore import Qt + +from plugins.base_plugin import BasePlugin + + +class MissionTrackerPlugin(BasePlugin): + """Track active missions and daily challenges.""" + + name = "Mission Tracker" + version = "1.0.0" + author = "ImpulsiveFPS" + description = "Track missions, challenges, and objectives" + hotkey = "ctrl+shift+m" + + def initialize(self): + """Setup mission tracker.""" + self.data_file = Path("data/missions.json") + self.data_file.parent.mkdir(parents=True, exist_ok=True) + + self.missions = [] + self.daily_challenges = [] + + self._load_data() + + def _load_data(self): + """Load mission data.""" + if self.data_file.exists(): + try: + with open(self.data_file, 'r') as f: + data = json.load(f) + self.missions = data.get('missions', []) + self.daily_challenges = data.get('daily', []) + except: + pass + + def _save_data(self): + """Save mission data.""" + with open(self.data_file, 'w') as f: + json.dump({ + 'missions': self.missions, + 'daily': self.daily_challenges + }, f, indent=2) + + def get_ui(self): + """Create mission tracker UI.""" + widget = QWidget() + widget.setStyleSheet("background: transparent;") + layout = QVBoxLayout(widget) + layout.setSpacing(15) + layout.setContentsMargins(0, 0, 0, 0) + + # Title + title = QLabel("📜 Mission Tracker") + title.setStyleSheet(""" + color: white; + font-size: 16px; + font-weight: bold; + """) + layout.addWidget(title) + + # Active missions section + active_label = QLabel("Active Missions") + active_label.setStyleSheet("color: rgba(255,255,255,200); font-size: 12px;") + layout.addWidget(active_label) + + # Mission cards + self.missions_container = QWidget() + self.missions_layout = QVBoxLayout(self.missions_container) + self.missions_layout.setSpacing(10) + self.missions_layout.setContentsMargins(0, 0, 0, 0) + + self._refresh_missions() + layout.addWidget(self.missions_container) + + # Daily challenges + daily_label = QLabel("Daily Challenges") + daily_label.setStyleSheet("color: rgba(255,255,255,200); font-size: 12px; margin-top: 10px;") + layout.addWidget(daily_label) + + self.daily_container = QWidget() + self.daily_layout = QVBoxLayout(self.daily_container) + self.daily_layout.setSpacing(8) + self.daily_layout.setContentsMargins(0, 0, 0, 0) + + self._refresh_daily() + layout.addWidget(self.daily_container) + + # Add mission button + add_btn = QPushButton("+ Add Mission") + add_btn.setStyleSheet(""" + QPushButton { + background-color: rgba(255, 140, 66, 200); + color: white; + padding: 10px; + border: none; + border-radius: 4px; + font-weight: bold; + } + QPushButton:hover { + background-color: rgba(255, 160, 80, 230); + } + """) + add_btn.clicked.connect(self._add_mission) + layout.addWidget(add_btn) + + layout.addStretch() + return widget + + def _create_mission_card(self, mission): + """Create a mission card widget.""" + card = QFrame() + card.setStyleSheet(""" + QFrame { + background-color: rgba(30, 35, 45, 200); + border: 1px solid rgba(100, 110, 130, 80); + border-radius: 6px; + } + """) + layout = QVBoxLayout(card) + layout.setContentsMargins(12, 10, 12, 10) + layout.setSpacing(8) + + # Header + header = QHBoxLayout() + + name = QLabel(mission.get('name', 'Unknown Mission')) + name.setStyleSheet("color: #ff8c42; font-weight: bold; font-size: 12px;") + header.addWidget(name) + + header.addStretch() + + # Complete button + complete_btn = QPushButton("✓") + complete_btn.setFixedSize(24, 24) + complete_btn.setStyleSheet(""" + QPushButton { + background-color: rgba(76, 175, 80, 150); + color: white; + border: none; + border-radius: 3px; + font-weight: bold; + } + QPushButton:hover { + background-color: rgba(76, 175, 80, 200); + } + """) + complete_btn.clicked.connect(lambda: self._complete_mission(mission)) + header.addWidget(complete_btn) + + layout.addLayout(header) + + # Progress + current = mission.get('current', 0) + total = mission.get('total', 1) + pct = min(100, int(current / total * 100)) + + progress_layout = QHBoxLayout() + + progress = QProgressBar() + progress.setValue(pct) + progress.setTextVisible(False) + progress.setFixedHeight(6) + progress.setStyleSheet(""" + QProgressBar { + background-color: rgba(60, 70, 90, 150); + border: none; + border-radius: 3px; + } + QProgressBar::chunk { + background-color: #ff8c42; + border-radius: 3px; + } + """) + progress_layout.addWidget(progress, 1) + + progress_text = QLabel(f"{current}/{total}") + progress_text.setStyleSheet("color: rgba(255,255,255,150); font-size: 10px;") + progress_layout.addWidget(progress_text) + + layout.addLayout(progress_layout) + + return card + + def _create_challenge_card(self, challenge): + """Create a daily challenge card.""" + card = QFrame() + card.setStyleSheet(""" + QFrame { + background-color: rgba(25, 30, 40, 180); + border: 1px solid rgba(80, 90, 110, 60); + border-radius: 4px; + } + """) + layout = QHBoxLayout(card) + layout.setContentsMargins(10, 8, 10, 8) + layout.setSpacing(10) + + # Icon based on type + icon = QLabel("⚔️") + layout.addWidget(icon) + + # Name + name = QLabel(challenge.get('name', 'Challenge')) + name.setStyleSheet("color: white; font-size: 11px;") + layout.addWidget(name) + + layout.addStretch() + + # Progress + current = challenge.get('current', 0) + total = challenge.get('total', 1) + pct = min(100, int(current / total * 100)) + + progress = QProgressBar() + progress.setValue(pct) + progress.setTextVisible(False) + progress.setFixedSize(60, 4) + progress.setStyleSheet(""" + QProgressBar { + background-color: rgba(60, 70, 90, 150); + border: none; + border-radius: 2px; + } + QProgressBar::chunk { + background-color: #ffc107; + border-radius: 2px; + } + """) + layout.addWidget(progress) + + text = QLabel(f"{current}/{total}") + text.setStyleSheet("color: rgba(255,255,255,120); font-size: 10px;") + layout.addWidget(text) + + return card + + def _refresh_missions(self): + """Refresh mission display.""" + # Clear existing + while self.missions_layout.count(): + item = self.missions_layout.takeAt(0) + if item.widget(): + item.widget().deleteLater() + + # Add missions + for mission in self.missions: + card = self._create_mission_card(mission) + self.missions_layout.addWidget(card) + + def _refresh_daily(self): + """Refresh daily challenges.""" + while self.daily_layout.count(): + item = self.daily_layout.takeAt(0) + if item.widget(): + item.widget().deleteLater() + + for challenge in self.daily_challenges: + card = self._create_challenge_card(challenge) + self.daily_layout.addWidget(card) + + def _add_mission(self): + """Add a new mission.""" + # Add sample mission + self.missions.append({ + 'name': 'Oratan Payback Mission III', + 'current': 0, + 'total': 100, + 'type': 'kill', + 'target': 'Oratan Prospector Bandits', + 'added': datetime.now().isoformat() + }) + self._save_data() + self._refresh_missions() + + def _complete_mission(self, mission): + """Mark mission as complete.""" + if mission in self.missions: + self.missions.remove(mission) + self._save_data() + self._refresh_missions() + + def parse_chat_message(self, message): + """Parse mission progress from chat.""" + # Look for mission progress + # Example: "Mission updated: 12/100 Oratan Prospector Bandits" + for mission in self.missions: + target = mission.get('target', '') + if target and target in message: + # Extract progress + import re + match = re.search(r'(\d+)/(\d+)', message) + if match: + mission['current'] = int(match.group(1)) + mission['total'] = int(match.group(2)) + self._save_data() + self._refresh_missions() diff --git a/projects/EU-Utility/plugins/tp_runner/__init__.py b/projects/EU-Utility/plugins/tp_runner/__init__.py new file mode 100644 index 0000000..25d81e4 --- /dev/null +++ b/projects/EU-Utility/plugins/tp_runner/__init__.py @@ -0,0 +1,7 @@ +""" +TP Runner Plugin +""" + +from .plugin import TPRunnerPlugin + +__all__ = ["TPRunnerPlugin"] diff --git a/projects/EU-Utility/plugins/tp_runner/plugin.py b/projects/EU-Utility/plugins/tp_runner/plugin.py new file mode 100644 index 0000000..dc1d868 --- /dev/null +++ b/projects/EU-Utility/plugins/tp_runner/plugin.py @@ -0,0 +1,219 @@ +""" +EU-Utility - TP Runner Plugin + +Track teleporter locations and plan routes. +""" + +import json +from pathlib import Path + +from PyQt6.QtWidgets import ( + QWidget, QVBoxLayout, QHBoxLayout, QLabel, + QPushButton, QComboBox, QTreeWidget, QTreeWidgetItem, + QLineEdit, QFrame +) +from PyQt6.QtCore import Qt + +from plugins.base_plugin import BasePlugin + + +class TPRunnerPlugin(BasePlugin): + """Track TP locations and plan routes.""" + + name = "TP Runner" + version = "1.0.0" + author = "ImpulsiveFPS" + description = "Teleporter locations and route planner" + hotkey = "ctrl+shift+p" # P for Port + + # Arkadia TPs + ARKADIA_TPS = [ + "Arkadia City", "Arkadia City Outskirts", "8 Coins Creek", + "Celeste Harbour", "Celeste Outpost North", "Celeste Outpost South", + "Celeste Quarry", "Cycadia", "Dauntless Dock", "East Scythe", + "Genesis", "Hadesheim", "Hadesheim Ashland", "Hadesheim Pass", + "Hadesheim Valley", "Hellfire Hills", "Hero's Landing", + "Horror-Filled Hallows", "Jagged Coast", "Jungle Camp", + "Khorum Coast", "Khorum Highway", "Kronus", "Lava Camp", + "Lighthouse", "Living Graveyard", "Mountaintop", "Neo-Shanghai", + "North Scythe", "Nusul Fields", "Oily Business", "Perseus", + "Pilgrim's Landing", "Poseidon West", "Red Sands", + "Releks Hills", "Rest Stop", "Ripper Snapper", "Sacred Cove", + "Sentinel Hill", "Shady Ridge", "Sisyphus", "South Scythe", + "Spiral Mountain", "Stormbird Landing", "Sundari", "Tides, + "Traveller's Landing", "Victorious", "Vikings", "West Scythe", + "Wild Banks", "Wolf's Ridge", + ] + + def initialize(self): + """Setup TP runner.""" + self.data_file = Path("data/tp_runner.json") + self.data_file.parent.mkdir(parents=True, exist_ok=True) + + self.unlocked_tps = set() + self.favorite_routes = [] + + self._load_data() + + def _load_data(self): + """Load TP data.""" + if self.data_file.exists(): + try: + with open(self.data_file, 'r') as f: + data = json.load(f) + self.unlocked_tps = set(data.get('unlocked', [])) + self.favorite_routes = data.get('routes', []) + except: + pass + + def _save_data(self): + """Save TP data.""" + with open(self.data_file, 'w') as f: + json.dump({ + 'unlocked': list(self.unlocked_tps), + 'routes': self.favorite_routes + }, f, indent=2) + + def get_ui(self): + """Create TP runner UI.""" + widget = QWidget() + widget.setStyleSheet("background: transparent;") + layout = QVBoxLayout(widget) + layout.setSpacing(15) + layout.setContentsMargins(0, 0, 0, 0) + + # Title + title = QLabel("🚀 TP Runner") + title.setStyleSheet("color: white; font-size: 16px; font-weight: bold;") + layout.addWidget(title) + + # Progress + unlocked = len(self.unlocked_tps) + total = len(self.ARKADIA_TPS) + progress = QLabel(f"Unlocked: {unlocked}/{total} ({unlocked/total*100:.1f}%)") + progress.setStyleSheet("color: #4caf50;") + layout.addWidget(progress) + + # Route planner + planner = QFrame() + planner.setStyleSheet(""" + QFrame { + background-color: rgba(30, 35, 45, 200); + border: 1px solid rgba(100, 110, 130, 80); + border-radius: 6px; + } + """) + planner_layout = QVBoxLayout(planner) + + # From/To + fromto = QHBoxLayout() + + self.from_combo = QComboBox() + self.from_combo.addItems(self.ARKADIA_TPS) + self.from_combo.setStyleSheet(""" + QComboBox { + background-color: rgba(20, 25, 35, 200); + color: white; + border: 1px solid rgba(100, 110, 130, 80); + padding: 5px; + } + """) + fromto.addWidget(QLabel("From:")) + fromto.addWidget(self.from_combo) + + self.to_combo = QComboBox() + self.to_combo.addItems(self.ARKADIA_TPS) + self.to_combo.setStyleSheet(self.from_combo.styleSheet()) + fromto.addWidget(QLabel("To:")) + fromto.addWidget(self.to_combo) + + planner_layout.addLayout(fromto) + + # Route button + route_btn = QPushButton("Find Route") + route_btn.setStyleSheet(""" + QPushButton { + background-color: #ff8c42; + color: white; + padding: 10px; + border: none; + border-radius: 4px; + font-weight: bold; + } + """) + route_btn.clicked.connect(self._find_route) + planner_layout.addWidget(route_btn) + + layout.addWidget(planner) + + # TP List + self.tp_tree = QTreeWidget() + self.tp_tree.setHeaderLabels(["Teleporter", "Status"]) + self.tp_tree.setStyleSheet(""" + QTreeWidget { + background-color: rgba(30, 35, 45, 200); + color: white; + border: 1px solid rgba(100, 110, 130, 80); + border-radius: 6px; + } + QHeaderView::section { + background-color: rgba(35, 40, 55, 200); + color: rgba(255,255,255,180); + padding: 8px; + font-weight: bold; + font-size: 11px; + } + """) + + # Populate list + for tp in sorted(self.ARKADIA_TPS): + item = QTreeWidgetItem() + item.setText(0, tp) + if tp in self.unlocked_tps: + item.setText(1, "✓ Unlocked") + item.setForeground(1, Qt.GlobalColor.green) + else: + item.setText(1, "Locked") + item.setForeground(1, Qt.GlobalColor.gray) + self.tp_tree.addTopLevelItem(item) + + layout.addWidget(self.tp_tree) + + # Mark as unlocked button + unlock_btn = QPushButton("✓ Mark Selected as Unlocked") + unlock_btn.setStyleSheet(""" + QPushButton { + background-color: #4caf50; + color: white; + padding: 10px; + border: none; + border-radius: 4px; + font-weight: bold; + } + """) + unlock_btn.clicked.connect(self._mark_unlocked) + layout.addWidget(unlock_btn) + + layout.addStretch() + return widget + + def _find_route(self): + """Find route between TPs.""" + from_tp = self.from_combo.currentText() + to_tp = self.to_combo.currentText() + + if from_tp == to_tp: + return + + # Simple distance estimation (would use actual coordinates) + # For now, just show direct route + + def _mark_unlocked(self): + """Mark selected TP as unlocked.""" + item = self.tp_tree.currentItem() + if item: + tp_name = item.text(0) + self.unlocked_tps.add(tp_name) + item.setText(1, "✓ Unlocked") + item.setForeground(1, Qt.GlobalColor.green) + self._save_data()