488 lines
15 KiB
Python
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'
|
|
]
|