diff --git a/core/armor_system.py b/core/armor_system.py index aed6fdb..8b815cc 100644 --- a/core/armor_system.py +++ b/core/armor_system.py @@ -781,13 +781,16 @@ def create_ghost_set() -> ArmorSet: def create_shogun_set() -> ArmorSet: """Create the Shogun armor set (medium, good vs impact/cut).""" + # Shogun uses standard 20 hp/pec economy + shogun_economy = Decimal("0.05") + pieces = { ArmorSlot.HEAD: ArmorPiece( name="Shogun Helmet", item_id="shogun_helmet", slot=ArmorSlot.HEAD, set_name="Shogun", - decay_per_hit=Decimal("0.025"), + decay_per_hp=shogun_economy, protection=ProtectionProfile(impact=Decimal("5"), cut=Decimal("4"), stab=Decimal("3"), burn=Decimal("2"), cold=Decimal("2")), weight=Decimal("0.8"), ), @@ -796,7 +799,7 @@ def create_shogun_set() -> ArmorSet: item_id="shogun_harness", slot=ArmorSlot.CHEST, set_name="Shogun", - decay_per_hit=Decimal("0.060"), + decay_per_hp=shogun_economy, protection=ProtectionProfile(impact=Decimal("8"), cut=Decimal("6"), stab=Decimal("5"), burn=Decimal("4"), cold=Decimal("4")), weight=Decimal("1.5"), ), @@ -805,7 +808,7 @@ def create_shogun_set() -> ArmorSet: item_id="shogun_arm_l", slot=ArmorSlot.LEFT_ARM, set_name="Shogun", - decay_per_hit=Decimal("0.025"), + decay_per_hp=shogun_economy, protection=ProtectionProfile(impact=Decimal("5"), cut=Decimal("4"), stab=Decimal("3"), burn=Decimal("2"), cold=Decimal("2")), weight=Decimal("0.8"), ), @@ -814,7 +817,7 @@ def create_shogun_set() -> ArmorSet: item_id="shogun_arm_r", slot=ArmorSlot.RIGHT_ARM, set_name="Shogun", - decay_per_hit=Decimal("0.025"), + decay_per_hp=shogun_economy, protection=ProtectionProfile(impact=Decimal("5"), cut=Decimal("4"), stab=Decimal("3"), burn=Decimal("2"), cold=Decimal("2")), weight=Decimal("0.8"), ), @@ -823,7 +826,7 @@ def create_shogun_set() -> ArmorSet: item_id="shogun_gloves_l", slot=ArmorSlot.LEFT_HAND, set_name="Shogun", - decay_per_hit=Decimal("0.015"), + decay_per_hp=shogun_economy, protection=ProtectionProfile(impact=Decimal("3"), cut=Decimal("2"), stab=Decimal("2"), burn=Decimal("1"), cold=Decimal("1")), weight=Decimal("0.4"), ), @@ -832,7 +835,7 @@ def create_shogun_set() -> ArmorSet: item_id="shogun_gloves_r", slot=ArmorSlot.RIGHT_HAND, set_name="Shogun", - decay_per_hit=Decimal("0.015"), + decay_per_hp=shogun_economy, protection=ProtectionProfile(impact=Decimal("3"), cut=Decimal("2"), stab=Decimal("2"), burn=Decimal("1"), cold=Decimal("1")), weight=Decimal("0.4"), ), @@ -841,7 +844,7 @@ def create_shogun_set() -> ArmorSet: item_id="shogun_legs", slot=ArmorSlot.LEGS, set_name="Shogun", - decay_per_hit=Decimal("0.050"), + decay_per_hp=shogun_economy, protection=ProtectionProfile(impact=Decimal("7"), cut=Decimal("5"), stab=Decimal("4"), burn=Decimal("3"), cold=Decimal("3")), weight=Decimal("1.2"), ), diff --git a/core/hunting_session.py b/core/hunting_session.py new file mode 100644 index 0000000..da4b98d --- /dev/null +++ b/core/hunting_session.py @@ -0,0 +1,393 @@ +""" +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") \ No newline at end of file