# Description: Official Armor Decay Calculator for Entropia Universe # Formula Source: Hijacker27's Official Guide (VU 15.15) # Standards: Python 3.11+, Decimal precision for PED/PEC calculations from decimal import Decimal, ROUND_HALF_UP from dataclasses import dataclass from typing import Dict, Optional, List import logging logger = logging.getLogger(__name__) @dataclass class ArmorSet: """Represents an armor set with its properties.""" name: str durability: Decimal total_protection: Decimal # Total protection value notes: str = "" @property def hp_per_pec(self) -> Decimal: """Calculate HP absorbed per PEC of decay.""" # Formula: hp/pec = 1 / (0.05 * (1 - durability/100000)) decay_factor = Decimal('0.05') * (Decimal('1') - self.durability / Decimal('100000')) if decay_factor > 0: return Decimal('1') / decay_factor return Decimal('0') # Official armor database with verified durability values ARMOR_DATABASE: Dict[str, ArmorSet] = { # Common beginner armors 'pixie': ArmorSet( name='Pixie', durability=Decimal('1000'), total_protection=Decimal('13'), notes='Beginner armor set' ), 'goblin': ArmorSet( name='Goblin', durability=Decimal('1400'), total_protection=Decimal('18'), notes='Basic protective armor' ), 'ghost': ArmorSet( name='Ghost', durability=Decimal('2000'), total_protection=Decimal('21'), notes='Popular mid-tier armor - 20.41 hp/pec' ), 'gremlin': ArmorSet( name='Gremlin', durability=Decimal('2950'), total_protection=Decimal('27'), notes='Well-balanced armor - 20.61 hp/pec' ), 'dragon': ArmorSet( name='Dragon', durability=Decimal('3200'), total_protection=Decimal('30'), notes='Good impact protection' ), 'hermit': ArmorSet( name='Hermit', durability=Decimal('3200'), total_protection=Decimal('32'), notes='Balanced protection' ), 'knight': ArmorSet( name='Knight', durability=Decimal('3500'), total_protection=Decimal('35'), notes='Heavy combat armor' ), 'brave': ArmorSet( name='Brave', durability=Decimal('3700'), total_protection=Decimal('36'), notes='Tough armor for tough hunters' ), 'angel': ArmorSet( name='Angel', durability=Decimal('4000'), total_protection=Decimal('38'), notes='High durability armor - 20.83 hp/pec' ), 'spartacus': ArmorSet( name='Spartacus', durability=Decimal('4300'), total_protection=Decimal('40'), notes='Gladiator armor' ), 'liakon': ArmorSet( name='Liakon', durability=Decimal('4400'), total_protection=Decimal('41'), notes='Advanced protection' ), 'jaguar': ArmorSet( name='Jaguar', durability=Decimal('4800'), total_protection=Decimal('43'), notes='Speed and protection' ), 'tiger': ArmorSet( name='Tiger', durability=Decimal('5200'), total_protection=Decimal('45'), notes='Fierce protection' ), 'bear': ArmorSet( name='Bear', durability=Decimal('5600'), total_protection=Decimal('47'), notes='Heavy duty armor' ), 'boar': ArmorSet( name='Boar', durability=Decimal('6000'), total_protection=Decimal('49'), notes='Solid all-around protection' ), 'lion': ArmorSet( name='Lion', durability=Decimal('6400'), total_protection=Decimal('51'), notes='Pride of the hunter' ), 'eudoracell': ArmorSet( name='Eudoracell', durability=Decimal('5000'), total_protection=Decimal('44'), notes='Event armor' ), 'hunting': ArmorSet( name='Hunting', durability=Decimal('4500'), total_protection=Decimal('42'), notes='Purpose-built hunting armor' ), 'frontier': ArmorSet( name='Frontier', durability=Decimal('3200'), total_protection=Decimal('35'), notes='Popular mid-level armor with good mobility' ), 'frontier_adjusted': ArmorSet( name='Frontier Adjusted', durability=Decimal('3800'), total_protection=Decimal('38'), notes='Upgraded Frontier with enhanced protection' ), } def calculate_armor_decay(damage_absorbed: Decimal, durability: Decimal) -> Decimal: """ Calculate armor decay in PEC using the official formula. Official Formula (VU 15.15): Decay (PEC) = damage_absorbed * 0.05 * (1 - durability/100000) Args: damage_absorbed: Amount of damage absorbed by armor durability: Armor durability rating (e.g., 2000 for Ghost) Returns: Decay in PEC (will be converted to PED by dividing by 100) Example: >>> calculate_armor_decay(Decimal('15'), Decimal('2000')) # Ghost Decimal('0.735') # PEC """ if damage_absorbed <= 0 or durability <= 0: return Decimal('0') # Formula: Decay (PEC) = damage_absorbed * 0.05 * (1 - durability/100000) durability_factor = Decimal('1') - (durability / Decimal('100000')) decay_pec = damage_absorbed * Decimal('0.05') * durability_factor # Round to 6 decimal places for precision return decay_pec.quantize(Decimal('0.000001'), rounding=ROUND_HALF_UP) def calculate_armor_decay_ped(damage_absorbed: Decimal, durability: Decimal) -> Decimal: """ Calculate armor decay in PED using the official formula. Args: damage_absorbed: Amount of damage absorbed by armor durability: Armor durability rating Returns: Decay in PED Example: >>> calculate_armor_decay_ped(Decimal('15'), Decimal('2000')) # Ghost Decimal('0.00735') # PED """ decay_pec = calculate_armor_decay(damage_absorbed, durability) decay_ped = decay_pec / Decimal('100') # Round to 8 decimal places for precision return decay_ped.quantize(Decimal('0.00000001'), rounding=ROUND_HALF_UP) def calculate_decay_from_hits(damage_per_hit: Decimal, num_hits: int, durability: Decimal) -> tuple[Decimal, Decimal]: """ Calculate total decay from multiple hits. Args: damage_per_hit: Average damage absorbed per hit num_hits: Number of hits taken durability: Armor durability rating Returns: Tuple of (total_decay_pec, total_decay_ped) """ if num_hits <= 0 or damage_per_hit <= 0: return Decimal('0'), Decimal('0') total_damage = damage_per_hit * num_hits decay_pec = calculate_armor_decay(total_damage, durability) decay_ped = decay_pec / Decimal('100') return decay_pec, decay_ped def get_hp_per_pec(durability: Decimal) -> Decimal: """ Calculate HP absorbed per PEC of decay. This is the inverse of the decay formula and helps hunters understand armor efficiency. Args: durability: Armor durability rating Returns: HP per PEC Example: >>> get_hp_per_pec(Decimal('2000')) # Ghost Decimal('20.4082') # hp/pec """ if durability <= 0: return Decimal('0') decay_factor = Decimal('0.05') * (Decimal('1') - durability / Decimal('100000')) if decay_factor <= 0: return Decimal('0') hp_per_pec = Decimal('1') / decay_factor return hp_per_pec.quantize(Decimal('0.0001'), rounding=ROUND_HALF_UP) def get_armor_by_name(name: str) -> Optional[ArmorSet]: """ Get armor set data by name (case-insensitive). Args: name: Armor set name (e.g., 'Ghost', 'gremlin') Returns: ArmorSet if found, None otherwise """ lookup_name = name.lower().strip() return ARMOR_DATABASE.get(lookup_name) def get_armor_decay_ped_for_damage(damage_taken: Decimal, armor_name: str) -> Decimal: """ Convenience function to get armor decay for a specific armor set. Args: damage_taken: Amount of damage absorbed armor_name: Name of the armor set Returns: Decay in PED, or 0 if armor not found """ armor = get_armor_by_name(armor_name) if not armor: logger.warning(f"Armor '{armor_name}' not found in database, using default") # Use Ghost (2000 durability) as reasonable default return calculate_armor_decay_ped(damage_taken, Decimal('2000')) return calculate_armor_decay_ped(damage_taken, armor.durability) def estimate_armor_protection(armor_name: str, plate_name: Optional[str] = None) -> Dict[str, Decimal]: """ Estimate armor protection values. Args: armor_name: Name of the armor set plate_name: Optional name of plating used Returns: Dictionary with protection estimates """ armor = get_armor_by_name(armor_name) if not armor: return {'stab': Decimal('0'), 'impact': Decimal('0'), 'cut': Decimal('0'), 'total': Decimal('0')} # Approximate distribution (actual varies by armor) # Most armors have balanced protection base_protection = armor.total_protection / Decimal('3') result = { 'stab': base_protection.quantize(Decimal('0.1')), 'impact': base_protection.quantize(Decimal('0.1')), 'cut': base_protection.quantize(Decimal('0.1')), 'total': armor.total_protection, 'hp_per_pec': get_hp_per_pec(armor.durability), 'durability': armor.durability } return result class ArmorDecayTracker: """ Tracks armor decay during a hunting session. This class accumulates damage taken and calculates armor decay in real-time. """ def __init__(self, armor_name: str = "Ghost"): """ Initialize the armor decay tracker. Args: armor_name: Name of the equipped armor set """ self.armor_name = armor_name self.armor = get_armor_by_name(armor_name) if not self.armor: logger.warning(f"Armor '{armor_name}' not found, using Ghost (2000 dur)") self.armor = ARMOR_DATABASE['ghost'] self.total_damage_absorbed: Decimal = Decimal('0') self.total_decay_pec: Decimal = Decimal('0') self.total_decay_ped: Decimal = Decimal('0') self.hits_taken: int = 0 self._session_active: bool = False def start_session(self): """Start tracking for a new session.""" self._session_active = True self.total_damage_absorbed = Decimal('0') self.total_decay_pec = Decimal('0') self.total_decay_ped = Decimal('0') self.hits_taken = 0 logger.info(f"Armor decay tracking started for {self.armor.name}") def end_session(self) -> Dict[str, any]: """ End tracking and return summary. Returns: Dictionary with session summary """ self._session_active = False return { 'armor_name': self.armor.name, 'armor_durability': float(self.armor.durability), 'total_damage_absorbed': float(self.total_damage_absorbed), 'hits_taken': self.hits_taken, 'total_decay_pec': float(self.total_decay_pec), 'total_decay_ped': float(self.total_decay_ped), 'hp_per_pec': float(get_hp_per_pec(self.armor.durability)) } def record_damage_taken(self, damage: Decimal) -> Decimal: """ Record damage taken and calculate decay. Args: damage: Amount of damage absorbed by armor Returns: Decay in PED for this hit """ if not self._session_active or damage <= 0: return Decimal('0') self.total_damage_absorbed += damage self.hits_taken += 1 # Calculate decay for this hit decay_pec = calculate_armor_decay(damage, self.armor.durability) decay_ped = decay_pec / Decimal('100') self.total_decay_pec += decay_pec self.total_decay_ped += decay_ped return decay_ped def get_current_decay(self) -> tuple[Decimal, Decimal]: """ Get current total decay. Returns: Tuple of (total_decay_pec, total_decay_ped) """ return self.total_decay_pec, self.total_decay_ped def get_efficiency_stats(self) -> Dict[str, Decimal]: """ Get armor efficiency statistics. Returns: Dictionary with efficiency metrics """ hp_per_pec = get_hp_per_pec(self.armor.durability) return { 'armor_durability': self.armor.durability, 'hp_per_pec': hp_per_pec, 'pec_per_hp': Decimal('1') / hp_per_pec if hp_per_pec > 0 else Decimal('0'), 'total_damage_absorbed': self.total_damage_absorbed, 'total_decay_ped': self.total_decay_ped, 'hits_taken': Decimal(str(self.hits_taken)) } # Convenience functions for common use cases def get_ghost_decay(damage: Decimal) -> Decimal: """Quick calculation for Ghost armor (2000 durability).""" return calculate_armor_decay_ped(damage, Decimal('2000')) def get_gremlin_decay(damage: Decimal) -> Decimal: """Quick calculation for Gremlin armor (2950 durability).""" return calculate_armor_decay_ped(damage, Decimal('2950')) def get_angel_decay(damage: Decimal) -> Decimal: """Quick calculation for Angel armor (4000 durability).""" return calculate_armor_decay_ped(damage, Decimal('4000')) def format_decay(decay_ped: Decimal) -> str: """ Format decay value for display. Args: decay_ped: Decay in PED Returns: Formatted string (e.g., "0.00735 PED" or "0.735 PEC") """ if decay_ped < Decimal('0.01'): pec = decay_ped * Decimal('100') return f"{pec:.4f} PEC" else: return f"{decay_ped:.4f} PED" # ============================================================================ # MODULE EXPORTS # ============================================================================ __all__ = [ 'ArmorSet', 'ARMOR_DATABASE', 'calculate_armor_decay', 'calculate_armor_decay_ped', 'calculate_decay_from_hits', 'get_hp_per_pec', 'get_armor_by_name', 'get_armor_decay_ped_for_damage', 'estimate_armor_protection', 'ArmorDecayTracker', 'get_ghost_decay', 'get_gremlin_decay', 'get_angel_decay', 'format_decay' ]