From 77d8e808fb56990e88af40d18ce506f0d22ee7db Mon Sep 17 00:00:00 2001 From: LemonNexus Date: Sun, 8 Feb 2026 18:23:35 +0000 Subject: [PATCH] feat(globals): add personal global detection with PLAYER_NAME setting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add PLAYER_NAME setting to .env.example for avatar name configuration - Add personal_global patterns for Swedish and English: - EN: '[Globals] Player killed a creature (Creature) for X PED' - SV: '[Globala] Player dödade ett kreatur (Creature) med X PED' - Distinguish personal globals from other players' globals - Show 🎉🎉🎉 YOUR GLOBAL notification with creature name - Track personal_globals separately in session summary Fixes #42 - Users can now see when THEY global vs others. --- .env.example | 4 + core/log_watcher.py | 231 ++++++++++++++++++++++++++------------------ main.py | 10 +- 3 files changed, 149 insertions(+), 96 deletions(-) diff --git a/.env.example b/.env.example index d6f0c1d..db9be00 100644 --- a/.env.example +++ b/.env.example @@ -69,6 +69,10 @@ DB_MAX_BACKUPS=10 # Linux/Wine: ~/.wine/drive_c/users//Documents/Entropia Universe/chat.log EU_CHAT_LOG_PATH=./test-data/chat.log +# Your Entropia Universe avatar name (for detecting personal globals/HoFs) +# Example: PLAYER_NAME=John Doe +PLAYER_NAME= + # Log polling interval (milliseconds) LOG_POLL_INTERVAL=1000 diff --git a/core/log_watcher.py b/core/log_watcher.py index 045a699..ec4ae9e 100644 --- a/core/log_watcher.py +++ b/core/log_watcher.py @@ -28,16 +28,16 @@ class LogEvent: class LogWatcher: """ Watches Entropia Universe chat.log and notifies observers of events. - + Supports multiple languages (English, Swedish) based on real game logs. Implements Observer Pattern: Multiple modules can subscribe to specific event types without tight coupling. """ - + # ======================================================================== # REGEX PATTERNS - ENGLISH & SWEDISH (from real game logs) # ======================================================================== - + # LOOT PATTERNS # English: "You received Shrapnel x 123 (Value: 1.23 PED)" # Swedish: "Du fick Shrapnel x (4627) Värde: 0.4627 PED" @@ -48,14 +48,14 @@ class LogWatcher: r'Value:\s+(\d+(?:\.\d+)?)\s+PED', re.IGNORECASE ) - + PATTERN_LOOT_SV = re.compile( r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[System\]\s+\[?\]?\s*' r'Du\s+fick\s+([\w\s\-()]+?)\s+x\s*\((\d+)\)\s*' r'Värde:\s+(\d+(?:\.\d+)?)\s+PED', re.IGNORECASE ) - + # GLOBAL PATTERNS (Other players) # English: "PlayerName globals in Zone for 150.00 PED" # Swedish: "PlayerName hittade en avsättning (Item) med ett värde av X PED" @@ -65,14 +65,31 @@ class LogWatcher: r'(?:for|with\s+a\s+value\s+of)\s+(\d+(?:\.\d+)?)\s+PED', re.IGNORECASE ) - + PATTERN_GLOBAL_SV = re.compile( r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[Globala?\]\s+\[?\]?\s*' r'([\w\s]+?)\s+hittade\s+en\s+avsättning\s+\(([^)]+)\)\s+' r'med\s+ett\s+värde\s+av\s+(\d+(?:\.\d+)?)\s+PED', re.IGNORECASE ) - + + # PERSONAL GLOBAL (when YOU get a global - different from seeing others) + # English: "[Globals] [Player] killed a creature (Creature) with a value of X PED" + # Swedish: "[Globala] [Player] dödade ett kreatur (Creature) med ett värde av X PED" + PATTERN_PERSONAL_GLOBAL_EN = re.compile( + r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[Globals\]\s+\[?\]?\s*' + r'([\w\s]+?)\s+killed\s+a\s+creature\s+\(([^)]+)\)\s+' + r'(?:with\s+a\s+value\s+of|for)\s+(\d+(?:\.\d+)?)\s+PED', + re.IGNORECASE + ) + + PATTERN_PERSONAL_GLOBAL_SV = re.compile( + r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[Globala\]\s+\[?\]?\s*' + r'([\w\s]+?)\s+dödade\s+ett\s+kreatur\s+\(([^)]+)\)\s+' + r'med\s+ett\s+värde\s+av\s+(\d+(?:\.\d+)?)\s+PED', + re.IGNORECASE + ) + # HALL OF FAME PATTERNS # Swedish: "...En post har lagts till i Hall of Fame!" PATTERN_HOF_MARKER = re.compile( @@ -80,7 +97,7 @@ class LogWatcher: r'.*?Hall\s+of\s+Fame', re.IGNORECASE ) - + # SKILL GAIN PATTERNS # English: "You have gained 1.1466 experience in your Whip skill" # English (alt): "You gained 0.45 experience in your Rifle skill" @@ -90,13 +107,13 @@ class LogWatcher: r'You\s+(?:have\s+)?gained\s+(\d+(?:\.\d+)?)\s+experience\s+in\s+your\s+([\w\s]+?)\s+skill', re.IGNORECASE ) - + PATTERN_SKILL_SV = re.compile( r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[System\]\s+\[?\]?\s*' r'Du\s+har\s+fått\s+(\d+(?:\.\d+)?)\s+erfarenhet\s+i\s+din\s+([\w\s]+?)\s+färdighet', re.IGNORECASE ) - + # SKILL LEVEL UP # English: "You have advanced to level 45 in Rifle" # Swedish: "Du har avancerat till nivå 45 i Rifle" @@ -105,7 +122,7 @@ class LogWatcher: r'You\s+have\s+advanced\s+to\s+level\s+(\d+)\s+in\s+([\w\s]+)', re.IGNORECASE ) - + # DAMAGE DEALT - Swedish & English # Swedish: "Du orsakade 13.5 poäng skada" # English: "You inflicted 4.4 points of damage" @@ -114,13 +131,13 @@ class LogWatcher: r'Du\s+orsakade\s+(\d+(?:\.\d+)?)\s+poäng\s+skada', re.IGNORECASE ) - + PATTERN_DAMAGE_DEALT_EN = re.compile( r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[System\]\s+\[?\]?\s*' r'You\s+inflicted\s+(\d+(?:\.\d+)?)\s+points?\s+of\s+damage', re.IGNORECASE ) - + # CRITICAL HIT # Swedish: "Kritisk träff - Extra skada! Du orsakade 44.4 poäng skada" # English: "Critical hit - Additional damage! You inflicted 49.6 points of damage" @@ -129,13 +146,13 @@ class LogWatcher: r'Kritisk\s+träff.*?Du\s+orsakade\s+(\d+(?:\.\d+)?)\s+poäng\s+skada', re.IGNORECASE ) - + PATTERN_CRITICAL_EN = re.compile( r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[System\]\s+\[?\]?\s*' r'Critical\s+hit.*?You\s+inflicted\s+(\d+(?:\.\d+)?)\s+points?\s+of\s+damage', re.IGNORECASE ) - + # DAMAGE TAKEN # Swedish: "Du tog 31.5 poäng skada" # English: "You took 7.4 points of damage" @@ -144,13 +161,13 @@ class LogWatcher: r'Du\s+tog\s+(\d+(?:\.\d+)?)\s+poäng\s+skada', re.IGNORECASE ) - + PATTERN_DAMAGE_TAKEN_EN = re.compile( r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[System\]\s+\[?\]?\s*' r'You\s+took\s+(\d+(?:\.\d+)?)\s+points?\s+of\s+damage', re.IGNORECASE ) - + # HEALING # Swedish: "Du läkte dig själv 4.0 poäng" # English: "You healed yourself 25.5 points" @@ -159,13 +176,13 @@ class LogWatcher: r'Du\s+läkte\s+dig\s+jälv\s+(\d+(?:\.\d+)?)\s+poäng', re.IGNORECASE ) - + PATTERN_HEAL_EN = re.compile( r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[System\]\s+\[?\]?\s*' r'You\s+healed\s+yourself\s+(\d+(?:\.\d+)?)\s+points?', re.IGNORECASE ) - + # WEAPON TIER/LEVEL UP # Swedish: "Din Piron PBP-17 (L) har nått nivå 0.38" # English: "Your Piron PBP-17 (L) has reached tier 2.68" @@ -174,13 +191,13 @@ class LogWatcher: r'Din\s+([\w\s\-()]+?)\s+har\s+nått\s+nivå\s+(\d+(?:\.\d+)?)', re.IGNORECASE ) - + PATTERN_WEAPON_TIER_EN = re.compile( r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[System\]\s+\[?\]?\s*' r'Your\s+([\w\s\-()]+?)\s+has\s+reached\s+tier\s+(\d+(?:\.\d+)?)', re.IGNORECASE ) - + # COMBAT EVADE/DODGE/MISS # English: "You Evaded", "The target Evaded your attack", "The attack missed you" PATTERN_EVADE = re.compile( @@ -188,7 +205,7 @@ class LogWatcher: r'(You\s+Evaded|You\s+dodged|The\s+target\s+Evaded\s+your\s+attack|The\s+target\s+Dodged|The\s+attack\s+missed\s+you)', re.IGNORECASE ) - + # DECAY (when weapon durability decreases) # English: "Your Omegaton M2100 has decayed 15 PEC" # Swedish: "Din Piron PBP-17 (L) har nått minimalt skick" @@ -197,7 +214,7 @@ class LogWatcher: r'(?:Your|Din)\s+([\w\s\-()]+?)\s+(?:has\s+decayed|har\s+nått\s+minimalt\s+skick)', re.IGNORECASE ) - + # BROKEN ENHANCERS # English: "Your enhancer Weapon Damage Enhancer 1 on your Piron PBP-17 (L) broke" PATTERN_ENHANCER_BROKEN = re.compile( @@ -205,7 +222,7 @@ class LogWatcher: r'Your\s+enhancer\s+([\w\s]+?)\s+on\s+your\s+([\w\s\-()]+?)\s+broke', re.IGNORECASE ) - + # PED TRANSFER # Swedish: "Överföring slutförd! 3.38000 PED har överförts till ditt PED-kort." PATTERN_PED_TRANSFER = re.compile( @@ -213,7 +230,7 @@ class LogWatcher: r'Överföring\s+slutförd.*?((\d+(?:\.\d+)?))\s+PED', re.IGNORECASE ) - + # ATTRIBUTE GAIN (Agility, etc) # Swedish: "Din Agility har förbättrats med 0.0001" # English: "Your Agility has improved by 0.0001" OR "You gained 0.0001 Agility" @@ -222,18 +239,20 @@ class LogWatcher: r'Din\s+(\w+)\s+har\s+förbättrats\s+med\s+(\d+(?:\.\d+)?)', re.IGNORECASE ) - + PATTERN_ATTRIBUTE_EN = re.compile( r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[System\]\s+\[?\]?\s*' r'(?:Your\s+(\w+)\s+has\s+improved\s+by|You\s+gained)\s+(\d+(?:\.\d+)?)\s+(\w+)', re.IGNORECASE ) - + EVENT_PATTERNS = { 'loot_en': PATTERN_LOOT_EN, 'loot_sv': PATTERN_LOOT_SV, 'global_en': PATTERN_GLOBAL_EN, 'global_sv': PATTERN_GLOBAL_SV, + 'personal_global_en': PATTERN_PERSONAL_GLOBAL_EN, + 'personal_global_sv': PATTERN_PERSONAL_GLOBAL_SV, 'hof': PATTERN_HOF_MARKER, 'skill_en': PATTERN_SKILL_EN, 'skill_sv': PATTERN_SKILL_SV, @@ -254,23 +273,23 @@ class LogWatcher: 'attribute_sv': PATTERN_ATTRIBUTE_SV, 'attribute_en': PATTERN_ATTRIBUTE_EN, } - - def __init__(self, log_path: Optional[str] = None, + + def __init__(self, log_path: Optional[str] = None, poll_interval: float = 1.0, mock_mode: bool = False): """Initialize LogWatcher.""" self.mock_mode = mock_mode - + if log_path is None: if mock_mode: core_dir = Path(__file__).parent log_path = core_dir.parent / "test-data" / "mock-chat.log" else: log_path = self._find_eu_log_path() - + self.log_path = Path(log_path) self.poll_interval = poll_interval - + self.observers: Dict[str, List[Callable]] = { 'loot': [], 'global': [], 'hof': [], 'skill': [], 'damage_dealt': [], 'damage_taken': [], 'heal': [], @@ -278,50 +297,50 @@ class LogWatcher: 'critical_hit': [], 'ped_transfer': [], 'attribute': [], 'any': [], } - + self._running = False self._file_position = 0 self._last_file_size = 0 self._task: Optional[asyncio.Task] = None - + logger.info(f"LogWatcher initialized: {self.log_path} (mock={mock_mode})") - + def _find_eu_log_path(self) -> Path: """Attempt to find Entropia Universe chat.log.""" possible_paths = [ Path.home() / "Documents" / "Entropia Universe" / "chat.log", Path("C:") / "Users" / os.getenv("USERNAME", "User") / "Documents" / "Entropia Universe" / "chat.log", ] - + wine_prefix = Path.home() / ".wine" / "drive_c" possible_paths.extend([ wine_prefix / "users" / os.getenv("USER", "user") / "Documents" / "Entropia Universe" / "chat.log", ]) - + for path in possible_paths: if path.exists(): logger.info(f"Found EU log: {path}") return path - + fallback = Path(__file__).parent.parent / "test-data" / "chat.log" logger.warning(f"EU log not found, using fallback: {fallback}") return fallback - + def subscribe(self, event_type: str, callback: Callable[[LogEvent], None]) -> None: """Subscribe to an event type.""" if event_type not in self.observers: self.observers[event_type] = [] - + self.observers[event_type].append(callback) logger.debug(f"Subscribed to {event_type}: {callback.__name__}") - + def unsubscribe(self, event_type: str, callback: Callable[[LogEvent], None]) -> None: """Unsubscribe from an event type.""" if event_type in self.observers: if callback in self.observers[event_type]: self.observers[event_type].remove(callback) logger.debug(f"Unsubscribed from {event_type}: {callback.__name__}") - + def _notify(self, event: LogEvent) -> None: """Notify all observers of an event.""" if event.event_type in self.observers: @@ -330,17 +349,17 @@ class LogWatcher: callback(event) except Exception as e: logger.error(f"Observer error for {event.event_type}: {e}") - + for callback in self.observers['any']: try: callback(event) except Exception as e: logger.error(f"Observer error for 'any': {e}") - + def _parse_timestamp(self, ts_str: str) -> datetime: """Parse EU timestamp format.""" return datetime.strptime(ts_str, "%Y-%m-%d %H:%M:%S") - + def _parse_line(self, line: str) -> Optional[LogEvent]: """ Parse a single log line. @@ -349,28 +368,38 @@ class LogWatcher: line = line.strip() if not line: return None - + # Try each pattern # LOOT - Swedish (prioritize based on your game client) match = self.PATTERN_LOOT_SV.match(line) if match: return self._create_loot_event_sv(match, line) - + # LOOT - English match = self.PATTERN_LOOT_EN.match(line) if match: return self._create_loot_event_en(match, line) - + # GLOBAL - Swedish match = self.PATTERN_GLOBAL_SV.match(line) if match: return self._create_global_event_sv(match, line) - + # GLOBAL - English match = self.PATTERN_GLOBAL_EN.match(line) if match: return self._create_global_event_en(match, line) - + + # PERSONAL GLOBAL - Swedish (when YOU get a global) + match = self.PATTERN_PERSONAL_GLOBAL_SV.match(line) + if match: + return self._create_personal_global_event(match, line, 'swedish') + + # PERSONAL GLOBAL - English (when YOU get a global) + match = self.PATTERN_PERSONAL_GLOBAL_EN.match(line) + if match: + return self._create_personal_global_event(match, line, 'english') + # HOF match = self.PATTERN_HOF_MARKER.match(line) if match: @@ -380,7 +409,7 @@ class LogWatcher: raw_line=line, data={'message': 'Hall of Fame entry'} ) - + # SKILL - Swedish match = self.PATTERN_SKILL_SV.match(line) if match: @@ -394,7 +423,7 @@ class LogWatcher: 'language': 'swedish' } ) - + # SKILL - English match = self.PATTERN_SKILL_EN.match(line) if match: @@ -408,7 +437,7 @@ class LogWatcher: 'language': 'english' } ) - + # LEVEL UP - English match = self.PATTERN_LEVEL_UP_EN.match(line) if match: @@ -422,7 +451,7 @@ class LogWatcher: 'language': 'english' } ) - + # DAMAGE DEALT - Swedish match = self.PATTERN_DAMAGE_DEALT_SV.match(line) if match: @@ -432,7 +461,7 @@ class LogWatcher: raw_line=line, data={'damage': Decimal(match.group(2)), 'language': 'swedish'} ) - + # DAMAGE DEALT - English match = self.PATTERN_DAMAGE_DEALT_EN.match(line) if match: @@ -442,7 +471,7 @@ class LogWatcher: raw_line=line, data={'damage': Decimal(match.group(2)), 'language': 'english'} ) - + # CRITICAL HIT - Swedish match = self.PATTERN_CRITICAL_SV.match(line) if match: @@ -452,7 +481,7 @@ class LogWatcher: raw_line=line, data={'damage': Decimal(match.group(2)), 'language': 'swedish'} ) - + # CRITICAL HIT - English match = self.PATTERN_CRITICAL_EN.match(line) if match: @@ -462,7 +491,7 @@ class LogWatcher: raw_line=line, data={'damage': Decimal(match.group(2)), 'language': 'english'} ) - + # DAMAGE TAKEN - Swedish match = self.PATTERN_DAMAGE_TAKEN_SV.match(line) if match: @@ -472,7 +501,7 @@ class LogWatcher: raw_line=line, data={'damage': Decimal(match.group(2)), 'language': 'swedish'} ) - + # DAMAGE TAKEN - English match = self.PATTERN_DAMAGE_TAKEN_EN.match(line) if match: @@ -482,7 +511,7 @@ class LogWatcher: raw_line=line, data={'damage': Decimal(match.group(2)), 'language': 'english'} ) - + # HEALING - Swedish match = self.PATTERN_HEAL_SV.match(line) if match: @@ -492,7 +521,7 @@ class LogWatcher: raw_line=line, data={'heal_amount': Decimal(match.group(2)), 'language': 'swedish'} ) - + # HEALING - English match = self.PATTERN_HEAL_EN.match(line) if match: @@ -502,7 +531,7 @@ class LogWatcher: raw_line=line, data={'heal_amount': Decimal(match.group(2)), 'language': 'english'} ) - + # WEAPON TIER/LEVEL - Swedish match = self.PATTERN_WEAPON_TIER_SV.match(line) if match: @@ -516,7 +545,7 @@ class LogWatcher: 'language': 'swedish' } ) - + # WEAPON TIER/LEVEL - English match = self.PATTERN_WEAPON_TIER_EN.match(line) if match: @@ -530,7 +559,7 @@ class LogWatcher: 'language': 'english' } ) - + # EVADE/DODGE/MISS match = self.PATTERN_EVADE.match(line) if match: @@ -540,7 +569,7 @@ class LogWatcher: raw_line=line, data={'type': match.group(2)} ) - + # DECAY match = self.PATTERN_DECAY.match(line) if match: @@ -550,7 +579,7 @@ class LogWatcher: raw_line=line, data={'item': match.group(2).strip()} ) - + # BROKEN ENHANCER match = self.PATTERN_ENHANCER_BROKEN.match(line) if match: @@ -563,7 +592,7 @@ class LogWatcher: 'weapon': match.group(3).strip() } ) - + # ATTRIBUTE GAIN - Swedish match = self.PATTERN_ATTRIBUTE_SV.match(line) if match: @@ -577,7 +606,7 @@ class LogWatcher: 'language': 'swedish' } ) - + # ATTRIBUTE GAIN - English match = self.PATTERN_ATTRIBUTE_EN.match(line) if match: @@ -591,9 +620,9 @@ class LogWatcher: 'language': 'english' } ) - + return None - + def _create_loot_event_sv(self, match: re.Match, line: str) -> LogEvent: """Create loot event from Swedish pattern.""" return LogEvent( @@ -607,7 +636,7 @@ class LogWatcher: 'language': 'swedish' } ) - + def _create_loot_event_en(self, match: re.Match, line: str) -> LogEvent: """Create loot event from English pattern.""" value = match.group(4) @@ -622,7 +651,7 @@ class LogWatcher: 'language': 'english' } ) - + def _create_global_event_sv(self, match: re.Match, line: str) -> LogEvent: """Create global event from Swedish pattern.""" return LogEvent( @@ -636,7 +665,7 @@ class LogWatcher: 'language': 'swedish' } ) - + def _create_global_event_en(self, match: re.Match, line: str) -> LogEvent: """Create global event from English pattern.""" return LogEvent( @@ -650,39 +679,53 @@ class LogWatcher: 'language': 'english' } ) - + + def _create_personal_global_event(self, match: re.Match, line: str, language: str) -> LogEvent: + """Create personal global event (when YOU get a global).""" + return LogEvent( + timestamp=self._parse_timestamp(match.group(1)), + event_type='personal_global', + raw_line=line, + data={ + 'player_name': match.group(2).strip(), + 'creature': match.group(3).strip(), + 'value_ped': Decimal(match.group(4)), + 'language': language + } + ) + # ======================================================================== # ASYNC POLLING LOOP # ======================================================================== - + async def start(self) -> None: """Start watching log file asynchronously.""" if self._running: logger.warning("LogWatcher already running") return - + self._running = True - + if self.log_path.exists(): self._last_file_size = self.log_path.stat().st_size self._file_position = self._last_file_size - + self._task = asyncio.create_task(self._watch_loop()) logger.info("LogWatcher started") - + async def stop(self) -> None: """Stop watching log file.""" self._running = False - + if self._task: self._task.cancel() try: await self._task except asyncio.CancelledError: pass - + logger.info("LogWatcher stopped") - + async def _watch_loop(self) -> None: """Main watching loop.""" while self._running: @@ -690,33 +733,33 @@ class LogWatcher: await self._poll_once() except Exception as e: logger.error(f"Poll error: {e}") - + await asyncio.sleep(self.poll_interval) - + async def _poll_once(self) -> None: """Single poll iteration.""" if not self.log_path.exists(): return - + current_size = self.log_path.stat().st_size - + if current_size < self._file_position: logger.info("Log file truncated, resetting position") self._file_position = 0 - + if current_size == self._file_position: return - + with open(self.log_path, 'r', encoding='utf-8', errors='ignore') as f: f.seek(self._file_position) new_lines = f.readlines() self._file_position = f.tell() - + for line in new_lines: event = self._parse_line(line) if event: self._notify(event) - + self._last_file_size = current_size @@ -726,7 +769,7 @@ class LogWatcher: class MockLogGenerator: """Generates mock log entries for testing.""" - + MOCK_LINES = [ "2026-02-08 14:23:15 [System] You received Shrapnel x 123 (Value: 1.23 PED)", "2026-02-08 14:23:45 [System] You gained 0.45 experience in your Rifle skill", @@ -740,17 +783,17 @@ class MockLogGenerator: "2025-09-23 19:36:08 [System] Du har fått 0.3238 erfarenhet i din Translocation färdighet", "2025-09-23 19:36:18 [System] Du orsakade 13.5 poäng skada", ] - + @classmethod def create_mock_file(cls, path: Path, lines: int = 100) -> None: """Create a mock chat.log file.""" path.parent.mkdir(parents=True, exist_ok=True) - + with open(path, 'w') as f: for i in range(lines): line = cls.MOCK_LINES[i % len(cls.MOCK_LINES)] f.write(f"{line}\n") - + logger.info(f"Created mock log: {path} ({lines} lines)") diff --git a/main.py b/main.py index 893d08c..ee6cf43 100644 --- a/main.py +++ b/main.py @@ -207,7 +207,7 @@ class LemontropiaApp: logger.info(f"Using REAL log: {log_path}") # Stats tracking - stats = {'loot': 0, 'globals': 0, 'hofs': 0, 'skills': 0, 'level_ups': 0, 'weapon_tiers': 0, 'enhancers_broken': 0, 'damage_dealt': 0, 'damage_taken': 0, 'evades': 0, 'total_ped': Decimal('0.0')} + stats = {'loot': 0, 'globals': 0, 'personal_globals': 0, 'hofs': 0, 'skills': 0, 'level_ups': 0, 'weapon_tiers': 0, 'enhancers_broken': 0, 'damage_dealt': 0, 'damage_taken': 0, 'evades': 0, 'total_ped': Decimal('0.0')} def on_event(event): """Handle log events.""" @@ -232,6 +232,10 @@ class LemontropiaApp: stats['globals'] += 1 print(f" 🌍 GLOBAL: {event.data.get('player_name')} found {event.data.get('value_ped')} PED!") + elif event.event_type == 'personal_global': + stats['personal_globals'] += 1 + print(f" 🎉🎉🎉 YOUR GLOBAL: {event.data.get('player_name')} killed {event.data.get('creature')} for {event.data.get('value_ped')} PED!!! 🎉🎉🎉") + elif event.event_type == 'hof': stats['hofs'] += 1 print(f" 🏆 HALL OF FAME: {event.data.get('value_ped')} PED!") @@ -271,6 +275,7 @@ class LemontropiaApp: # Subscribe to events self.watcher.subscribe('loot', on_event) self.watcher.subscribe('global', on_event) + self.watcher.subscribe('personal_global', on_event) self.watcher.subscribe('hof', on_event) self.watcher.subscribe('skill', on_event) self.watcher.subscribe('level_up', on_event) @@ -307,7 +312,8 @@ class LemontropiaApp: print("="*50) print(f"\n📊 SESSION SUMMARY:") print(f" Loot events: {stats['loot']}") - print(f" Globals: {stats['globals']}") + print(f" Your Globals: {stats['personal_globals']}") + print(f" Other Globals: {stats['globals']}") print(f" HoFs: {stats['hofs']}") print(f" Skills: {stats['skills']}") print(f" Level Ups: {stats['level_ups']}")