393 lines
13 KiB
Python
393 lines
13 KiB
Python
"""
|
|
Hunting Session Tracker for Lemontropia Suite
|
|
Complete session tracking with cost, loot, and profit/loss calculations.
|
|
"""
|
|
|
|
from decimal import Decimal
|
|
from datetime import datetime, timedelta
|
|
from dataclasses import dataclass, field
|
|
from typing import Dict, List, Optional, Any
|
|
import json
|
|
import logging
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# Add parent to path for standalone testing
|
|
if __name__ == "__main__":
|
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
|
|
try:
|
|
from core.healing_tools import HealingTool, get_healing_tool
|
|
from core.armor_decay import calculate_armor_decay, get_armor_durability
|
|
except ImportError:
|
|
# Fallback for standalone testing
|
|
def calculate_armor_decay(damage, durability):
|
|
durability_factor = Decimal(1) - Decimal(durability) / Decimal(100000)
|
|
decay_pec = damage * Decimal("0.05") * durability_factor
|
|
return decay_pec / Decimal(100)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@dataclass
|
|
class HuntingSession:
|
|
"""Complete hunting session tracking."""
|
|
|
|
# Session identification
|
|
session_id: str
|
|
project_id: int
|
|
start_time: datetime
|
|
end_time: Optional[datetime] = None
|
|
|
|
# Gear configuration
|
|
weapon_name: str = "Unknown"
|
|
weapon_damage: Decimal = Decimal('0')
|
|
weapon_decay_pec: Decimal = Decimal('0')
|
|
weapon_ammo_burn: Decimal = Decimal('0')
|
|
|
|
armor_name: str = "Unknown"
|
|
armor_durability: int = 2000
|
|
|
|
medical_tool_name: str = "None"
|
|
medical_tool_decay_pec: Decimal = Decimal('0')
|
|
|
|
# Combat statistics
|
|
shots_fired: int = 0
|
|
damage_dealt: Decimal = Decimal('0')
|
|
damage_taken: Decimal = Decimal('0')
|
|
kills: int = 0
|
|
|
|
# Healing statistics
|
|
heals_performed: int = 0
|
|
health_healed: Decimal = Decimal('0')
|
|
|
|
# Loot tracking
|
|
loot_events: List[Dict[str, Any]] = field(default_factory=list)
|
|
total_loot_value: Decimal = Decimal('0')
|
|
shrapnel_value: Decimal = Decimal('0')
|
|
universal_ammo_value: Decimal = Decimal('0')
|
|
other_loot_value: Decimal = Decimal('0')
|
|
|
|
# Special events
|
|
globals_count: int = 0
|
|
hofs_count: int = 0
|
|
personal_globals: List[Dict] = field(default_factory=list)
|
|
|
|
# Cost tracking (calculated)
|
|
_weapon_cost: Decimal = field(default=Decimal('0'), repr=False)
|
|
_armor_cost: Decimal = field(default=Decimal('0'), repr=False)
|
|
_healing_cost: Decimal = field(default=Decimal('0'), repr=False)
|
|
_plate_cost: Decimal = field(default=Decimal('0'), repr=False)
|
|
|
|
def add_shot(self):
|
|
"""Record a weapon shot."""
|
|
self.shots_fired += 1
|
|
self._recalculate_weapon_cost()
|
|
|
|
def add_damage_dealt(self, damage: Decimal):
|
|
"""Record damage dealt to creatures."""
|
|
self.damage_dealt += damage
|
|
|
|
def add_damage_taken(self, damage: Decimal):
|
|
"""Record damage taken from creatures."""
|
|
self.damage_taken += damage
|
|
self._recalculate_armor_cost()
|
|
|
|
def add_heal(self, amount: Decimal):
|
|
"""Record a healing action."""
|
|
self.heals_performed += 1
|
|
self.health_healed += amount
|
|
self._recalculate_healing_cost()
|
|
|
|
def add_kill(self):
|
|
"""Record a creature kill."""
|
|
self.kills += 1
|
|
|
|
def add_loot(self, item_name: str, quantity: int, value_ped: Decimal,
|
|
is_shrapnel: bool = False, is_universal_ammo: bool = False):
|
|
"""Record loot received."""
|
|
loot_event = {
|
|
'timestamp': datetime.now().isoformat(),
|
|
'item': item_name,
|
|
'quantity': quantity,
|
|
'value': str(value_ped),
|
|
'is_shrapnel': is_shrapnel,
|
|
'is_universal_ammo': is_universal_ammo
|
|
}
|
|
self.loot_events.append(loot_event)
|
|
|
|
if is_shrapnel:
|
|
self.shrapnel_value += value_ped
|
|
elif is_universal_ammo:
|
|
self.universal_ammo_value += value_ped
|
|
else:
|
|
self.other_loot_value += value_ped
|
|
self.total_loot_value += value_ped
|
|
|
|
def add_global(self, is_hof: bool = False, value_ped: Decimal = Decimal('0')):
|
|
"""Record a global or HOF."""
|
|
if is_hof:
|
|
self.hofs_count += 1
|
|
else:
|
|
self.globals_count += 1
|
|
|
|
self.personal_globals.append({
|
|
'timestamp': datetime.now().isoformat(),
|
|
'is_hof': is_hof,
|
|
'value': str(value_ped)
|
|
})
|
|
|
|
def _recalculate_weapon_cost(self):
|
|
"""Calculate weapon cost based on shots fired."""
|
|
# Cost per shot = (decay + ammo_cost) / 100 (convert to PED)
|
|
ammo_cost_pec = self.weapon_ammo_burn * Decimal('0.01') # 1 ammo = 0.01 PEC
|
|
cost_per_shot = (self.weapon_decay_pec + ammo_cost_pec) / Decimal('100')
|
|
self._weapon_cost = cost_per_shot * self.shots_fired
|
|
|
|
def _recalculate_armor_cost(self):
|
|
"""Calculate armor cost based on damage taken."""
|
|
# Using official decay formula
|
|
self._armor_cost = calculate_armor_decay(self.damage_taken, self.armor_durability)
|
|
|
|
def _recalculate_healing_cost(self):
|
|
"""Calculate healing cost based on heals performed."""
|
|
self._healing_cost = (self.medical_tool_decay_pec * self.heals_performed) / Decimal('100')
|
|
|
|
@property
|
|
def total_cost(self) -> Decimal:
|
|
"""Get total hunting cost (PED)."""
|
|
return self._weapon_cost + self._armor_cost + self._healing_cost + self._plate_cost
|
|
|
|
@property
|
|
def profit_loss(self) -> Decimal:
|
|
"""Calculate profit/loss (PED) - excludes shrapnel/UA."""
|
|
return self.total_loot_value - self.total_cost
|
|
|
|
@property
|
|
def return_percentage(self) -> Decimal:
|
|
"""Calculate return percentage (loot/cost * 100)."""
|
|
if self.total_cost > 0:
|
|
return (self.total_loot_value / self.total_cost) * Decimal('100')
|
|
return Decimal('0')
|
|
|
|
@property
|
|
def cost_per_kill(self) -> Decimal:
|
|
"""Calculate cost per kill."""
|
|
if self.kills > 0:
|
|
return self.total_cost / self.kills
|
|
return Decimal('0')
|
|
|
|
@property
|
|
def session_duration(self) -> timedelta:
|
|
"""Get session duration."""
|
|
end = self.end_time or datetime.now()
|
|
return end - self.start_time
|
|
|
|
@property
|
|
def cost_per_hour(self) -> Decimal:
|
|
"""Calculate cost per hour."""
|
|
hours = self.session_duration.total_seconds() / 3600
|
|
if hours > 0:
|
|
return self.total_cost / Decimal(str(hours))
|
|
return Decimal('0')
|
|
|
|
@property
|
|
def loot_per_hour(self) -> Decimal:
|
|
"""Calculate loot value per hour."""
|
|
hours = self.session_duration.total_seconds() / 3600
|
|
if hours > 0:
|
|
return self.total_loot_value / Decimal(str(hours))
|
|
return Decimal('0')
|
|
|
|
def get_summary(self) -> Dict[str, Any]:
|
|
"""Get session summary as dictionary."""
|
|
return {
|
|
'session_id': self.session_id,
|
|
'duration_minutes': self.session_duration.total_seconds() / 60,
|
|
'weapon': self.weapon_name,
|
|
'armor': self.armor_name,
|
|
'medical_tool': self.medical_tool_name,
|
|
|
|
'combat': {
|
|
'shots_fired': self.shots_fired,
|
|
'damage_dealt': str(self.damage_dealt),
|
|
'damage_taken': str(self.damage_taken),
|
|
'kills': self.kills,
|
|
},
|
|
|
|
'healing': {
|
|
'heals_performed': self.heals_performed,
|
|
'health_healed': str(self.health_healed),
|
|
},
|
|
|
|
'loot': {
|
|
'total_value': str(self.total_loot_value),
|
|
'shrapnel': str(self.shrapnel_value),
|
|
'universal_ammo': str(self.universal_ammo_value),
|
|
'other': str(self.other_loot_value),
|
|
'events_count': len(self.loot_events),
|
|
},
|
|
|
|
'special_events': {
|
|
'globals': self.globals_count,
|
|
'hofs': self.hofs_count,
|
|
},
|
|
|
|
'costs': {
|
|
'weapon': str(self._weapon_cost),
|
|
'armor': str(self._armor_cost),
|
|
'healing': str(self._healing_cost),
|
|
'plates': str(self._plate_cost),
|
|
'total': str(self.total_cost),
|
|
},
|
|
|
|
'results': {
|
|
'profit_loss': str(self.profit_loss),
|
|
'return_percentage': float(self.return_percentage),
|
|
'cost_per_kill': str(self.cost_per_kill),
|
|
'cost_per_hour': str(self.cost_per_hour),
|
|
'loot_per_hour': str(self.loot_per_hour),
|
|
}
|
|
}
|
|
|
|
def to_json(self) -> str:
|
|
"""Export session to JSON."""
|
|
return json.dumps(self.get_summary(), indent=2)
|
|
|
|
|
|
class SessionManager:
|
|
"""Manages multiple hunting sessions."""
|
|
|
|
def __init__(self):
|
|
self.active_session: Optional[HuntingSession] = None
|
|
self.completed_sessions: List[HuntingSession] = []
|
|
|
|
def start_session(self, session_id: str, project_id: int, **gear_config) -> HuntingSession:
|
|
"""Start a new hunting session."""
|
|
session = HuntingSession(
|
|
session_id=session_id,
|
|
project_id=project_id,
|
|
start_time=datetime.now(),
|
|
**gear_config
|
|
)
|
|
self.active_session = session
|
|
logger.info(f"Started hunting session {session_id}")
|
|
return session
|
|
|
|
def end_session(self) -> Optional[HuntingSession]:
|
|
"""End the current session."""
|
|
if self.active_session:
|
|
self.active_session.end_time = datetime.now()
|
|
self.completed_sessions.append(self.active_session)
|
|
session = self.active_session
|
|
self.active_session = None
|
|
logger.info(f"Ended hunting session {session.session_id}")
|
|
return session
|
|
return None
|
|
|
|
def get_active_session(self) -> Optional[HuntingSession]:
|
|
"""Get currently active session."""
|
|
return self.active_session
|
|
|
|
def get_session_history(self) -> List[HuntingSession]:
|
|
"""Get all completed sessions."""
|
|
return self.completed_sessions.copy()
|
|
|
|
def get_totals(self) -> Dict[str, Decimal]:
|
|
"""Get totals across all sessions."""
|
|
totals = {
|
|
'total_loot': Decimal('0'),
|
|
'total_cost': Decimal('0'),
|
|
'total_profit': Decimal('0'),
|
|
'total_kills': Decimal('0'),
|
|
}
|
|
|
|
for session in self.completed_sessions:
|
|
totals['total_loot'] += session.total_loot_value
|
|
totals['total_cost'] += session.total_cost
|
|
totals['total_profit'] += session.profit_loss
|
|
totals['total_kills'] += Decimal(session.kills)
|
|
|
|
return totals
|
|
|
|
|
|
# Global session manager instance
|
|
_session_manager = SessionManager()
|
|
|
|
|
|
def get_session_manager() -> SessionManager:
|
|
"""Get the global session manager."""
|
|
return _session_manager
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# Test the session tracking
|
|
print("Testing Hunting Session Tracker")
|
|
print("=" * 60)
|
|
|
|
# Create a test session
|
|
session = HuntingSession(
|
|
session_id="test-001",
|
|
project_id=1,
|
|
start_time=datetime.now(),
|
|
weapon_name="ArMatrix BP-25",
|
|
weapon_damage=Decimal('39'),
|
|
weapon_decay_pec=Decimal('0.688'),
|
|
weapon_ammo_burn=Decimal('848'),
|
|
armor_name="Ghost",
|
|
armor_durability=2000,
|
|
medical_tool_name="Vivo S10",
|
|
medical_tool_decay_pec=Decimal('1.705')
|
|
)
|
|
|
|
# Simulate a hunting session
|
|
print("\nSimulating 10-minute hunt...")
|
|
|
|
# Fire 100 shots
|
|
for _ in range(100):
|
|
session.add_shot()
|
|
session.add_damage_dealt(Decimal('39'))
|
|
|
|
# Take some damage
|
|
session.add_damage_taken(Decimal('150'))
|
|
|
|
# Heal twice
|
|
session.add_heal(Decimal('21'))
|
|
session.add_heal(Decimal('21'))
|
|
|
|
# Kill 5 mobs
|
|
for _ in range(5):
|
|
session.add_kill()
|
|
|
|
# Get some loot
|
|
session.add_loot("Animal Hide", 5, Decimal('0.50'))
|
|
session.add_loot("Animal Oil", 3, Decimal('0.30'))
|
|
session.add_loot("Shrapnel", 100, Decimal('1.00'), is_shrapnel=True)
|
|
|
|
# End session
|
|
session.end_time = datetime.now() + timedelta(minutes=10)
|
|
|
|
# Print summary
|
|
print(f"\nSession Summary:")
|
|
print(f" Duration: {session.session_duration}")
|
|
print(f" Shots: {session.shots_fired}")
|
|
print(f" Damage Dealt: {session.damage_dealt}")
|
|
print(f" Damage Taken: {session.damage_taken}")
|
|
print(f" Kills: {session.kills}")
|
|
print(f" Heals: {session.heals_performed}")
|
|
|
|
print(f"\nCosts:")
|
|
print(f" Weapon: {session._weapon_cost:.4f} PED")
|
|
print(f" Armor: {session._armor_cost:.4f} PED")
|
|
print(f" Healing: {session._healing_cost:.4f} PED")
|
|
print(f" Total: {session.total_cost:.4f} PED")
|
|
|
|
print(f"\nLoot:")
|
|
print(f" Total: {session.total_loot_value:.4f} PED")
|
|
print(f" Shrapnel: {session.shrapnel_value:.4f} PED")
|
|
|
|
print(f"\nResults:")
|
|
print(f" Profit/Loss: {session.profit_loss:.4f} PED")
|
|
print(f" Return: {session.return_percentage:.2f}%")
|
|
print(f" Cost/Kill: {session.cost_per_kill:.4f} PED")
|
|
print(f" Cost/Hour: {session.cost_per_hour:.2f} PED") |