Lemontropia-Suite/core/armor_decay.py

488 lines
15 KiB
Python

# 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'
]