fix: add comma to loot regex patterns for items like 'Dominax Original Garter, Adjusted (L)'

- Add comma (,) to character class in PATTERN_LOOT_EN and PATTERN_LOOT_SV
- Add comma (,) to PATTERN_LOOT_NO_VALUE_EN and PATTERN_LOOT_NO_VALUE_SV
- Fixes items with commas in their names not being tracked
This commit is contained in:
LemonNexus 2026-02-10 15:13:21 +00:00
parent e4fd75e2a7
commit 6d8f710244
1 changed files with 83 additions and 83 deletions

View File

@ -43,53 +43,53 @@ class HuntingSessionStats:
total_shrapnel_ped: Decimal = Decimal('0.0') total_shrapnel_ped: Decimal = Decimal('0.0')
total_universal_ammo_ped: Decimal = Decimal('0.0') total_universal_ammo_ped: Decimal = Decimal('0.0')
total_other_loot_ped: Decimal = Decimal('0.0') # Non-shrapnel, non-UA loot total_other_loot_ped: Decimal = Decimal('0.0') # Non-shrapnel, non-UA loot
# Cost tracking # Cost tracking
weapon_cost_ped: Decimal = Decimal('0.0') weapon_cost_ped: Decimal = Decimal('0.0')
armor_cost_ped: Decimal = Decimal('0.0') armor_cost_ped: Decimal = Decimal('0.0')
healing_cost_ped: Decimal = Decimal('0.0') healing_cost_ped: Decimal = Decimal('0.0')
plates_cost_ped: Decimal = Decimal('0.0') plates_cost_ped: Decimal = Decimal('0.0')
total_cost_ped: Decimal = Decimal('0.0') total_cost_ped: Decimal = Decimal('0.0')
# Combat tracking # Combat tracking
damage_dealt: Decimal = Decimal('0.0') damage_dealt: Decimal = Decimal('0.0')
damage_taken: Decimal = Decimal('0.0') damage_taken: Decimal = Decimal('0.0')
healing_done: Decimal = Decimal('0.0') healing_done: Decimal = Decimal('0.0')
shots_fired: int = 0 shots_fired: int = 0
kills: int = 0 kills: int = 0
# Special events # Special events
globals_count: int = 0 globals_count: int = 0
hofs_count: int = 0 hofs_count: int = 0
personal_globals: List[Dict[str, Any]] = field(default_factory=list) personal_globals: List[Dict[str, Any]] = field(default_factory=list)
# Calculated metrics # Calculated metrics
@property @property
def net_profit_ped(self) -> Decimal: def net_profit_ped(self) -> Decimal:
"""Calculate net profit (excluding shrapnel from loot).""" """Calculate net profit (excluding shrapnel from loot)."""
return self.total_other_loot_ped - self.total_cost_ped return self.total_other_loot_ped - self.total_cost_ped
@property @property
def return_percentage(self) -> Decimal: def return_percentage(self) -> Decimal:
"""Calculate return percentage (loot/cost * 100).""" """Calculate return percentage (loot/cost * 100)."""
if self.total_cost_ped > 0: if self.total_cost_ped > 0:
return (self.total_other_loot_ped / self.total_cost_ped) * Decimal('100') return (self.total_other_loot_ped / self.total_cost_ped) * Decimal('100')
return Decimal('0.0') return Decimal('0.0')
@property @property
def cost_per_kill(self) -> Decimal: def cost_per_kill(self) -> Decimal:
"""Calculate cost per kill.""" """Calculate cost per kill."""
if self.kills > 0: if self.kills > 0:
return self.total_cost_ped / self.kills return self.total_cost_ped / self.kills
return Decimal('0.0') return Decimal('0.0')
@property @property
def dpp(self) -> Decimal: def dpp(self) -> Decimal:
"""Calculate Damage Per PED (efficiency metric).""" """Calculate Damage Per PED (efficiency metric)."""
if self.total_cost_ped > 0: if self.total_cost_ped > 0:
return self.damage_dealt / self.total_cost_ped return self.damage_dealt / self.total_cost_ped
return Decimal('0.0') return Decimal('0.0')
@property @property
def damage_per_kill(self) -> Decimal: def damage_per_kill(self) -> Decimal:
"""Calculate average damage per kill.""" """Calculate average damage per kill."""
@ -117,29 +117,29 @@ class LogWatcher:
# Swedish: "Du fick Shrapnel x (4627) Värde: 0.4627 PED" # Swedish: "Du fick Shrapnel x (4627) Värde: 0.4627 PED"
PATTERN_LOOT_EN = re.compile( PATTERN_LOOT_EN = re.compile(
r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[System\]:?\s*\[?\]?\s*' r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[System\]:?\s*\[?\]?\s*'
r'You\s+received\s+\[?([\w\s\-()]+?)\]?\s+x\s*\((\d+)\)\s*' r'You\s+received\s+\[?([\w\s\-(),]+?)\]?\s+x\s*\((\d+)\)\s*'
r'Value:\s+(\d+(?:\.\d+)?)\s+PED', r'Value:\s+(\d+(?:\.\d+)?)\s+PED',
re.IGNORECASE re.IGNORECASE
) )
PATTERN_LOOT_SV = re.compile( PATTERN_LOOT_SV = re.compile(
r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[System\]\s+\[?\]?\s*' 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'Du\s+fick\s+([\w\s\-(),]+?)\s+x\s*\((\d+)\)\s*'
r'Värde:\s+(\d+(?:\.\d+)?)\s+PED', r'Värde:\s+(\d+(?:\.\d+)?)\s+PED',
re.IGNORECASE re.IGNORECASE
) )
# LOOT PATTERN WITHOUT VALUE (some items don't show value) # LOOT PATTERN WITHOUT VALUE (some items don't show value)
# English: "You received Animal Thyroid Oil x 5" # English: "You received Animal Thyroid Oil x 5"
PATTERN_LOOT_NO_VALUE_EN = re.compile( PATTERN_LOOT_NO_VALUE_EN = re.compile(
r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[System\]:?\s*\[?\]?\s*' r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[System\]:?\s*\[?\]?\s*'
r'You\s+received\s+\[?([\w\s\-()]+?)\]?\s+x\s*(\d+)', r'You\s+received\s+\[?([\w\s\-(),]+?)\]?\s+x\s*(\d+)',
re.IGNORECASE re.IGNORECASE
) )
PATTERN_LOOT_NO_VALUE_SV = re.compile( PATTERN_LOOT_NO_VALUE_SV = re.compile(
r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[System\]\s+\[?\]?\s*' 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+)', r'Du\s+fick\s+([\w\s\-(),]+?)\s+x\s*(\d+)',
re.IGNORECASE re.IGNORECASE
) )
@ -151,7 +151,7 @@ class LogWatcher:
r'You\s+killed\s+\[?([\w\s\-()]+?)\]?', r'You\s+killed\s+\[?([\w\s\-()]+?)\]?',
re.IGNORECASE re.IGNORECASE
) )
PATTERN_KILL_SV = re.compile( PATTERN_KILL_SV = re.compile(
r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[System\]\s+\[?\]?\s*' r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[System\]\s+\[?\]?\s*'
r'Du\s+dödade\s+\[?([\w\s\-()]+?)\]?', r'Du\s+dödade\s+\[?([\w\s\-()]+?)\]?',
@ -201,7 +201,7 @@ class LogWatcher:
r'(?:for|with\s+a\s+value\s+of)\s+(\d+(?:\.\d+)?)\s+PED', r'(?:for|with\s+a\s+value\s+of)\s+(\d+(?:\.\d+)?)\s+PED',
re.IGNORECASE re.IGNORECASE
) )
PATTERN_HOF_MARKER = re.compile( PATTERN_HOF_MARKER = re.compile(
r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[\w+\]\s+\[?\]?\s*' r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[\w+\]\s+\[?\]?\s*'
r'.*?Hall\s+of\s+Fame', r'.*?Hall\s+of\s+Fame',
@ -232,7 +232,7 @@ class LogWatcher:
r'You\s+have\s+advanced\s+to\s+level\s+(\d+)\s+in\s+([\w\s]+)', r'You\s+have\s+advanced\s+to\s+level\s+(\d+)\s+in\s+([\w\s]+)',
re.IGNORECASE re.IGNORECASE
) )
PATTERN_LEVEL_UP_SV = re.compile( PATTERN_LEVEL_UP_SV = re.compile(
r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[System\]\s+\[?\]?\s*' r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[System\]\s+\[?\]?\s*'
r'Du\s+har\s+avancerat\s+till\s+nivå\s+(\d+)\s+i\s+([\w\s]+)', r'Du\s+har\s+avancerat\s+till\s+nivå\s+(\d+)\s+i\s+([\w\s]+)',
@ -322,7 +322,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)', 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 re.IGNORECASE
) )
PATTERN_EVADE_SV = re.compile( PATTERN_EVADE_SV = re.compile(
r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[System\]\s+\[?\]?\s*' r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[System\]\s+\[?\]?\s*'
r'(Du\s+undvek|Målet\s+undvek\s+din\s+attack|Attacken\s+missade\s+dig)', r'(Du\s+undvek|Målet\s+undvek\s+din\s+attack|Attacken\s+missade\s+dig)',
@ -337,13 +337,13 @@ class LogWatcher:
r'Your\s+([\w\s\-()]+?)\s+has\s+decayed\s+(\d+(?:\.\d+)?)\s+PEC', r'Your\s+([\w\s\-()]+?)\s+has\s+decayed\s+(\d+(?:\.\d+)?)\s+PEC',
re.IGNORECASE re.IGNORECASE
) )
PATTERN_DECAY_SV = re.compile( PATTERN_DECAY_SV = re.compile(
r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[System\]\s+\[?\]?\s*' r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[System\]\s+\[?\]?\s*'
r'Din\s+([\w\s\-()]+?)\s+har\s+decayed\s+(\d+(?:\.\d+)?)\s+PEC', r'Din\s+([\w\s\-()]+?)\s+har\s+decayed\s+(\d+(?:\.\d+)?)\s+PEC',
re.IGNORECASE re.IGNORECASE
) )
PATTERN_WEAPON_BROKEN_SV = re.compile( PATTERN_WEAPON_BROKEN_SV = re.compile(
r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[System\]\s+\[?\]?\s*' r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[System\]\s+\[?\]?\s*'
r'Din\s+([\w\s\-()]+?)\s+har\s+nått\s+minimalt\s+skick', r'Din\s+([\w\s\-()]+?)\s+har\s+nått\s+minimalt\s+skick',
@ -365,7 +365,7 @@ class LogWatcher:
r'Överföring\s+slutförd.*?((\d+(?:\.\d+)?))\s+PED', r'Överföring\s+slutförd.*?((\d+(?:\.\d+)?))\s+PED',
re.IGNORECASE re.IGNORECASE
) )
PATTERN_PED_TRANSFER_EN = re.compile( PATTERN_PED_TRANSFER_EN = re.compile(
r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[System\]\s+' r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[System\]\s+'
r'Transfer\s+complete.*?((\d+(?:\.\d+)?))\s+PED', r'Transfer\s+complete.*?((\d+(?:\.\d+)?))\s+PED',
@ -394,7 +394,7 @@ class LogWatcher:
r'You\s+received\s+(\d+(?:\.\d+)?)\s+PED\s+from\s+your\s+teammates', r'You\s+received\s+(\d+(?:\.\d+)?)\s+PED\s+from\s+your\s+teammates',
re.IGNORECASE re.IGNORECASE
) )
PATTERN_TEAM_SHARE_SV = re.compile( PATTERN_TEAM_SHARE_SV = re.compile(
r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[System\]\s+\[?\]?\s*' r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[System\]\s+\[?\]?\s*'
r'Du\s+fick\s+(\d+(?:\.\d+)?)\s+PED\s+från\s+dina\s+lagkamrater', r'Du\s+fick\s+(\d+(?:\.\d+)?)\s+PED\s+från\s+dina\s+lagkamrater',
@ -529,7 +529,7 @@ class LogWatcher:
def _is_shrapnel(self, item_name: str) -> bool: def _is_shrapnel(self, item_name: str) -> bool:
"""Check if item is Shrapnel.""" """Check if item is Shrapnel."""
return item_name.strip().lower() == 'shrapnel' return item_name.strip().lower() == 'shrapnel'
def _is_universal_ammo(self, item_name: str) -> bool: def _is_universal_ammo(self, item_name: str) -> bool:
"""Check if item is Universal Ammo.""" """Check if item is Universal Ammo."""
name = item_name.strip().lower() name = item_name.strip().lower()
@ -539,7 +539,7 @@ class LogWatcher:
"""Categorize loot item and return LootItem.""" """Categorize loot item and return LootItem."""
is_shrapnel = self._is_shrapnel(item_name) is_shrapnel = self._is_shrapnel(item_name)
is_ua = self._is_universal_ammo(item_name) is_ua = self._is_universal_ammo(item_name)
return LootItem( return LootItem(
name=item_name.strip(), name=item_name.strip(),
quantity=1, # Will be set by caller quantity=1, # Will be set by caller
@ -558,12 +558,12 @@ class LogWatcher:
return None return None
# Try each pattern in priority order # Try each pattern in priority order
# KILL - Swedish # KILL - Swedish
match = self.PATTERN_KILL_SV.match(line) match = self.PATTERN_KILL_SV.match(line)
if match: if match:
return self._create_kill_event(match, line, 'swedish') return self._create_kill_event(match, line, 'swedish')
# KILL - English # KILL - English
match = self.PATTERN_KILL_EN.match(line) match = self.PATTERN_KILL_EN.match(line)
if match: if match:
@ -578,12 +578,12 @@ class LogWatcher:
match = self.PATTERN_LOOT_EN.match(line) match = self.PATTERN_LOOT_EN.match(line)
if match: if match:
return self._create_loot_event_en(match, line) return self._create_loot_event_en(match, line)
# LOOT WITHOUT VALUE - Swedish # LOOT WITHOUT VALUE - Swedish
match = self.PATTERN_LOOT_NO_VALUE_SV.match(line) match = self.PATTERN_LOOT_NO_VALUE_SV.match(line)
if match: if match:
return self._create_loot_event_no_value(match, line, 'swedish') return self._create_loot_event_no_value(match, line, 'swedish')
# LOOT WITHOUT VALUE - English # LOOT WITHOUT VALUE - English
match = self.PATTERN_LOOT_NO_VALUE_EN.match(line) match = self.PATTERN_LOOT_NO_VALUE_EN.match(line)
if match: if match:
@ -608,7 +608,7 @@ class LogWatcher:
match = self.PATTERN_PERSONAL_GLOBAL_EN.match(line) match = self.PATTERN_PERSONAL_GLOBAL_EN.match(line)
if match: if match:
return self._create_personal_global_event(match, line, 'english') return self._create_personal_global_event(match, line, 'english')
# HOF - English # HOF - English
match = self.PATTERN_HOF_EN.match(line) match = self.PATTERN_HOF_EN.match(line)
if match: if match:
@ -665,7 +665,7 @@ class LogWatcher:
'language': 'english' 'language': 'english'
} }
) )
# LEVEL UP - Swedish # LEVEL UP - Swedish
match = self.PATTERN_LEVEL_UP_SV.match(line) match = self.PATTERN_LEVEL_UP_SV.match(line)
if match: if match:
@ -797,7 +797,7 @@ class LogWatcher:
raw_line=line, raw_line=line,
data={'type': match.group(2), 'language': 'english'} data={'type': match.group(2), 'language': 'english'}
) )
# EVADE/DODGE/MISS - Swedish # EVADE/DODGE/MISS - Swedish
match = self.PATTERN_EVADE_SV.match(line) match = self.PATTERN_EVADE_SV.match(line)
if match: if match:
@ -821,7 +821,7 @@ class LogWatcher:
'language': 'english' 'language': 'english'
} }
) )
# DECAY - Swedish # DECAY - Swedish
match = self.PATTERN_DECAY_SV.match(line) match = self.PATTERN_DECAY_SV.match(line)
if match: if match:
@ -877,7 +877,7 @@ class LogWatcher:
'language': 'english' 'language': 'english'
} }
) )
# TEAM SHARE - English # TEAM SHARE - English
match = self.PATTERN_TEAM_SHARE_EN.match(line) match = self.PATTERN_TEAM_SHARE_EN.match(line)
if match: if match:
@ -890,7 +890,7 @@ class LogWatcher:
'language': 'english' 'language': 'english'
} }
) )
# TEAM SHARE - Swedish # TEAM SHARE - Swedish
match = self.PATTERN_TEAM_SHARE_SV.match(line) match = self.PATTERN_TEAM_SHARE_SV.match(line)
if match: if match:
@ -911,10 +911,10 @@ class LogWatcher:
item_name = match.group(2).strip() item_name = match.group(2).strip()
quantity = int(match.group(3)) quantity = int(match.group(3))
value_ped = Decimal(match.group(4)) value_ped = Decimal(match.group(4))
loot_item = self._categorize_loot(item_name, value_ped) loot_item = self._categorize_loot(item_name, value_ped)
loot_item.quantity = quantity loot_item.quantity = quantity
return LogEvent( return LogEvent(
timestamp=self._parse_timestamp(match.group(1)), timestamp=self._parse_timestamp(match.group(1)),
event_type='loot', event_type='loot',
@ -934,10 +934,10 @@ class LogWatcher:
item_name = match.group(2).strip() item_name = match.group(2).strip()
quantity = int(match.group(3)) quantity = int(match.group(3))
value_ped = Decimal(match.group(4)) if match.group(4) else Decimal('0') value_ped = Decimal(match.group(4)) if match.group(4) else Decimal('0')
loot_item = self._categorize_loot(item_name, value_ped) loot_item = self._categorize_loot(item_name, value_ped)
loot_item.quantity = quantity loot_item.quantity = quantity
return LogEvent( return LogEvent(
timestamp=self._parse_timestamp(match.group(1)), timestamp=self._parse_timestamp(match.group(1)),
event_type='loot', event_type='loot',
@ -951,15 +951,15 @@ class LogWatcher:
'language': 'english' 'language': 'english'
} }
) )
def _create_loot_event_no_value(self, match: re.Match, line: str, language: str) -> LogEvent: def _create_loot_event_no_value(self, match: re.Match, line: str, language: str) -> LogEvent:
"""Create loot event without value.""" """Create loot event without value."""
item_name = match.group(2).strip() item_name = match.group(2).strip()
quantity = int(match.group(3)) quantity = int(match.group(3))
loot_item = self._categorize_loot(item_name, Decimal('0')) loot_item = self._categorize_loot(item_name, Decimal('0'))
loot_item.quantity = quantity loot_item.quantity = quantity
return LogEvent( return LogEvent(
timestamp=self._parse_timestamp(match.group(1)), timestamp=self._parse_timestamp(match.group(1)),
event_type='loot', event_type='loot',
@ -973,7 +973,7 @@ class LogWatcher:
'language': language 'language': language
} }
) )
def _create_kill_event(self, match: re.Match, line: str, language: str) -> LogEvent: def _create_kill_event(self, match: re.Match, line: str, language: str) -> LogEvent:
"""Create kill event.""" """Create kill event."""
return LogEvent( return LogEvent(
@ -1027,7 +1027,7 @@ class LogWatcher:
'language': language 'language': language
} }
) )
def _create_hof_event(self, match: re.Match, line: str, language: str) -> LogEvent: def _create_hof_event(self, match: re.Match, line: str, language: str) -> LogEvent:
"""Create Hall of Fame event.""" """Create Hall of Fame event."""
return LogEvent( return LogEvent(
@ -1118,40 +1118,40 @@ class LogWatcher:
class HuntingSessionTracker: class HuntingSessionTracker:
""" """
Tracks hunting session statistics from LogWatcher events. Tracks hunting session statistics from LogWatcher events.
This class accumulates all hunting-related data and provides This class accumulates all hunting-related data and provides
real-time metrics like profit/loss, return percentage, etc. real-time metrics like profit/loss, return percentage, etc.
""" """
def __init__(self): def __init__(self):
self.stats = HuntingSessionStats() self.stats = HuntingSessionStats()
self._session_start: Optional[datetime] = None self._session_start: Optional[datetime] = None
self._session_active = False self._session_active = False
# Callbacks for real-time updates # Callbacks for real-time updates
self._on_stats_update: Optional[Callable] = None self._on_stats_update: Optional[Callable] = None
def start_session(self): def start_session(self):
"""Start a new hunting session.""" """Start a new hunting session."""
self._session_start = datetime.now() self._session_start = datetime.now()
self._session_active = True self._session_active = True
self.stats = HuntingSessionStats() self.stats = HuntingSessionStats()
logger.info("Hunting session started") logger.info("Hunting session started")
def end_session(self) -> HuntingSessionStats: def end_session(self) -> HuntingSessionStats:
"""End the current hunting session and return final stats.""" """End the current hunting session and return final stats."""
self._session_active = False self._session_active = False
logger.info("Hunting session ended") logger.info("Hunting session ended")
return self.stats return self.stats
def is_active(self) -> bool: def is_active(self) -> bool:
"""Check if session is active.""" """Check if session is active."""
return self._session_active return self._session_active
def set_stats_callback(self, callback: Callable[[HuntingSessionStats], None]): def set_stats_callback(self, callback: Callable[[HuntingSessionStats], None]):
"""Set callback for real-time stats updates.""" """Set callback for real-time stats updates."""
self._on_stats_update = callback self._on_stats_update = callback
def _notify_update(self): def _notify_update(self):
"""Notify listeners of stats update.""" """Notify listeners of stats update."""
if self._on_stats_update: if self._on_stats_update:
@ -1159,71 +1159,71 @@ class HuntingSessionTracker:
self._on_stats_update(self.stats) self._on_stats_update(self.stats)
except Exception as e: except Exception as e:
logger.error(f"Stats callback error: {e}") logger.error(f"Stats callback error: {e}")
def on_loot(self, event: LogEvent): def on_loot(self, event: LogEvent):
"""Process loot event.""" """Process loot event."""
if not self._session_active: if not self._session_active:
return return
data = event.data data = event.data
value_ped = data.get('value_ped', Decimal('0.0')) value_ped = data.get('value_ped', Decimal('0.0'))
is_shrapnel = data.get('is_shrapnel', False) is_shrapnel = data.get('is_shrapnel', False)
is_ua = data.get('is_universal_ammo', False) is_ua = data.get('is_universal_ammo', False)
self.stats.total_loot_ped += value_ped self.stats.total_loot_ped += value_ped
if is_shrapnel: if is_shrapnel:
self.stats.total_shrapnel_ped += value_ped self.stats.total_shrapnel_ped += value_ped
elif is_ua: elif is_ua:
self.stats.total_universal_ammo_ped += value_ped self.stats.total_universal_ammo_ped += value_ped
else: else:
self.stats.total_other_loot_ped += value_ped self.stats.total_other_loot_ped += value_ped
self._notify_update() self._notify_update()
def on_kill(self, event: LogEvent): def on_kill(self, event: LogEvent):
"""Process kill event.""" """Process kill event."""
if not self._session_active: if not self._session_active:
return return
self.stats.kills += 1 self.stats.kills += 1
self._notify_update() self._notify_update()
def on_damage_dealt(self, event: LogEvent): def on_damage_dealt(self, event: LogEvent):
"""Process damage dealt event.""" """Process damage dealt event."""
if not self._session_active: if not self._session_active:
return return
damage = event.data.get('damage', Decimal('0.0')) damage = event.data.get('damage', Decimal('0.0'))
self.stats.damage_dealt += damage self.stats.damage_dealt += damage
self.stats.shots_fired += 1 # Each damage event = 1 shot self.stats.shots_fired += 1 # Each damage event = 1 shot
self._notify_update() self._notify_update()
def on_damage_taken(self, event: LogEvent): def on_damage_taken(self, event: LogEvent):
"""Process damage taken event.""" """Process damage taken event."""
if not self._session_active: if not self._session_active:
return return
damage = event.data.get('damage', Decimal('0.0')) damage = event.data.get('damage', Decimal('0.0'))
self.stats.damage_taken += damage self.stats.damage_taken += damage
self._notify_update() self._notify_update()
def on_heal(self, event: LogEvent): def on_heal(self, event: LogEvent):
"""Process heal event.""" """Process heal event."""
if not self._session_active: if not self._session_active:
return return
heal_amount = event.data.get('heal_amount', Decimal('0.0')) heal_amount = event.data.get('heal_amount', Decimal('0.0'))
self.stats.healing_done += heal_amount self.stats.healing_done += heal_amount
self._notify_update() self._notify_update()
def on_global(self, event: LogEvent): def on_global(self, event: LogEvent):
"""Process global event.""" """Process global event."""
if not self._session_active: if not self._session_active:
return return
self.stats.globals_count += 1 self.stats.globals_count += 1
# Store personal global details # Store personal global details
if event.event_type == 'personal_global': if event.event_type == 'personal_global':
self.stats.personal_globals.append({ self.stats.personal_globals.append({
@ -1231,16 +1231,16 @@ class HuntingSessionTracker:
'creature': event.data.get('creature', 'Unknown'), 'creature': event.data.get('creature', 'Unknown'),
'value_ped': event.data.get('value_ped', Decimal('0.0')) 'value_ped': event.data.get('value_ped', Decimal('0.0'))
}) })
self._notify_update() self._notify_update()
def on_hof(self, event: LogEvent): def on_hof(self, event: LogEvent):
"""Process Hall of Fame event.""" """Process Hall of Fame event."""
if not self._session_active: if not self._session_active:
return return
self.stats.hofs_count += 1 self.stats.hofs_count += 1
# Store HoF details # Store HoF details
if 'creature' in event.data: if 'creature' in event.data:
self.stats.personal_globals.append({ self.stats.personal_globals.append({
@ -1249,53 +1249,53 @@ class HuntingSessionTracker:
'value_ped': event.data.get('value_ped', Decimal('0.0')), 'value_ped': event.data.get('value_ped', Decimal('0.0')),
'is_hof': True 'is_hof': True
}) })
self._notify_update() self._notify_update()
def on_decay(self, event: LogEvent): def on_decay(self, event: LogEvent):
"""Process decay event.""" """Process decay event."""
if not self._session_active: if not self._session_active:
return return
# Convert PEC to PED # Convert PEC to PED
amount_pec = event.data.get('amount_pec', Decimal('0.0')) amount_pec = event.data.get('amount_pec', Decimal('0.0'))
amount_ped = amount_pec / Decimal('100') amount_ped = amount_pec / Decimal('100')
self.stats.weapon_cost_ped += amount_ped self.stats.weapon_cost_ped += amount_ped
self.stats.total_cost_ped += amount_ped self.stats.total_cost_ped += amount_ped
self._notify_update() self._notify_update()
def add_weapon_cost(self, cost_ped: Decimal): def add_weapon_cost(self, cost_ped: Decimal):
"""Manually add weapon cost (for calculated decay).""" """Manually add weapon cost (for calculated decay)."""
if not self._session_active: if not self._session_active:
return return
self.stats.weapon_cost_ped += cost_ped self.stats.weapon_cost_ped += cost_ped
self.stats.total_cost_ped += cost_ped self.stats.total_cost_ped += cost_ped
self._notify_update() self._notify_update()
def add_armor_cost(self, cost_ped: Decimal): def add_armor_cost(self, cost_ped: Decimal):
"""Manually add armor cost.""" """Manually add armor cost."""
if not self._session_active: if not self._session_active:
return return
self.stats.armor_cost_ped += cost_ped self.stats.armor_cost_ped += cost_ped
self.stats.total_cost_ped += cost_ped self.stats.total_cost_ped += cost_ped
self._notify_update() self._notify_update()
def add_healing_cost(self, cost_ped: Decimal): def add_healing_cost(self, cost_ped: Decimal):
"""Manually add healing cost.""" """Manually add healing cost."""
if not self._session_active: if not self._session_active:
return return
self.stats.healing_cost_ped += cost_ped self.stats.healing_cost_ped += cost_ped
self.stats.total_cost_ped += cost_ped self.stats.total_cost_ped += cost_ped
self._notify_update() self._notify_update()
def get_stats(self) -> HuntingSessionStats: def get_stats(self) -> HuntingSessionStats:
"""Get current stats.""" """Get current stats."""
return self.stats return self.stats
def get_summary(self) -> Dict[str, Any]: def get_summary(self) -> Dict[str, Any]:
"""Get session summary as dictionary.""" """Get session summary as dictionary."""
return { return {