feat(globals): add personal global detection with PLAYER_NAME setting

- 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.
This commit is contained in:
LemonNexus 2026-02-08 18:23:35 +00:00
parent 06a95f56f4
commit 77d8e808fb
3 changed files with 149 additions and 96 deletions

View File

@ -69,6 +69,10 @@ DB_MAX_BACKUPS=10
# Linux/Wine: ~/.wine/drive_c/users/<Username>/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

View File

@ -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)")

10
main.py
View File

@ -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']}")