Lemontropia-Suite/core/entropia_nexus.py

459 lines
15 KiB
Python

# Description: Entropia Nexus API integration for gear/item data
# Provides weapon, armor, tool statistics for ROI calculations
# API: https://api.entropianexus.com/
import json
import urllib.request
import urllib.error
from typing import Optional, Dict, List, Any
from decimal import Decimal
from dataclasses import dataclass
import logging
logger = logging.getLogger(__name__)
@dataclass
class WeaponStats:
"""Weapon statistics from Entropia Nexus."""
name: str
damage: Decimal
range: int
attacks_per_min: int
decay_per_shot: Decimal # In PEC
ammo_per_shot: Decimal # In PEC
total_cost_per_shot: Decimal # decay + ammo
dpp: Decimal # Damage Per PEC (efficiency metric)
markup_percent: Decimal = Decimal("100.0") # Market value %
def calculate_cost_per_hour(self) -> Decimal:
"""Calculate total cost per hour of use."""
shots_per_hour = self.attacks_per_min * 60
return (self.total_cost_per_shot * shots_per_hour) / 100 # Convert PEC to PED
@dataclass
class MiningToolStats:
"""Mining tool (finder/extractor) statistics."""
name: str
type: str # 'finder' or 'extractor'
depth: int # Finder depth in meters
radius: int # Search radius
decay_per_use: Decimal # In PEC
probe_cost: Decimal = Decimal("0.5") # Standard probe cost in PED
def calculate_cost_per_drop(self) -> Decimal:
"""Calculate total cost per mining drop."""
return (self.decay_per_use / 100) + self.probe_cost # PEC to PED + probe
@dataclass
class ArmorStats:
"""Armor piece statistics."""
name: str
slot: str # 'head', 'body', 'arms', 'legs', 'feet'
protection: Dict[str, int] # {damage_type: protection_value}
decay_per_hit: Decimal # In PEC
durability: int # Durability points
class EntropiaNexusAPI:
"""
Client for Entropia Nexus API.
Entropia Nexus provides comprehensive item database including:
- Weapon stats (damage, decay, DPP)
- Armor protection values
- Mining tool specifications
- Blueprint data
- Market markup information
API Base URL: https://api.entropianexus.com/
"""
BASE_URL = "https://api.entropianexus.com"
def __init__(self, api_key: Optional[str] = None):
"""
Initialize API client.
Args:
api_key: Optional API key for higher rate limits
"""
self.api_key = api_key
self._cache: Dict[str, Any] = {} # Simple in-memory cache
logger.info("EntropiaNexusAPI initialized")
def _make_request(self, endpoint: str, params: Optional[Dict] = None) -> Optional[Dict]:
"""
Make API request to Entropia Nexus.
Args:
endpoint: API endpoint path
params: Query parameters
Returns:
JSON response as dict, or None on error
"""
url = f"{self.BASE_URL}/{endpoint}"
if params:
query_string = '&'.join(f"{k}={v}" for k, v in params.items())
url = f"{url}?{query_string}"
headers = {
'Accept': 'application/json',
'User-Agent': 'Lemontropia-Suite/0.1.0'
}
if self.api_key:
headers['Authorization'] = f'Bearer {self.api_key}'
try:
req = urllib.request.Request(url, headers=headers)
with urllib.request.urlopen(req, timeout=10) as response:
data = json.loads(response.read().decode('utf-8'))
return data
except urllib.error.HTTPError as e:
logger.error(f"HTTP Error {e.code}: {e.reason}")
return None
except urllib.error.URLError as e:
logger.error(f"URL Error: {e.reason}")
return None
except Exception as e:
logger.error(f"API request failed: {e}")
return None
# ========================================================================
# WEAPON ENDPOINTS
# ========================================================================
def search_weapons(self, query: str, weapon_type: Optional[str] = None) -> List[Dict]:
"""
Search for weapons by name.
Args:
query: Weapon name search term
weapon_type: Optional filter (e.g., 'rifle', 'pistol', 'sword')
Returns:
List of weapon data dicts
"""
params = {'q': query}
if weapon_type:
params['type'] = weapon_type
data = self._make_request('v1/weapons/search', params)
return data.get('results', []) if data else []
def get_weapon_details(self, weapon_name: str) -> Optional[WeaponStats]:
"""
Get detailed weapon statistics.
Args:
weapon_name: Exact weapon name
Returns:
WeaponStats object or None
"""
# Check cache first
cache_key = f"weapon:{weapon_name}"
if cache_key in self._cache:
return self._cache[cache_key]
data = self._make_request(f'v1/weapons/{urllib.parse.quote(weapon_name)}')
if not data:
return None
try:
weapon = WeaponStats(
name=data['name'],
damage=Decimal(str(data.get('damage', 0))),
range=data.get('range', 0),
attacks_per_min=data.get('attacks_per_min', 0),
decay_per_shot=Decimal(str(data.get('decay_pec', 0))),
ammo_per_shot=Decimal(str(data.get('ammo_pec', 0))),
total_cost_per_shot=Decimal(str(data.get('total_cost_pec', 0))),
dpp=Decimal(str(data.get('dpp', 0))),
markup_percent=Decimal(str(data.get('markup', 100)))
)
# Cache result
self._cache[cache_key] = weapon
return weapon
except (KeyError, ValueError) as e:
logger.error(f"Failed to parse weapon data: {e}")
return None
def get_weapon_dpp_tiers(self) -> List[Dict]:
"""
Get weapon DPP (Damage Per PEC) tier list.
Returns:
List of weapons ranked by efficiency
"""
data = self._make_request('v1/weapons/dpp-tiers')
return data.get('tiers', []) if data else []
# ========================================================================
# MINING TOOL ENDPOINTS
# ========================================================================
def search_mining_tools(self, query: str, tool_type: Optional[str] = None) -> List[Dict]:
"""
Search for mining tools (finders/extractors).
Args:
query: Tool name search term
tool_type: 'finder' or 'extractor'
Returns:
List of tool data dicts
"""
params = {'q': query}
if tool_type:
params['type'] = tool_type
data = self._make_request('v1/mining/tools', params)
return data.get('results', []) if data else []
def get_mining_tool_details(self, tool_name: str) -> Optional[MiningToolStats]:
"""
Get detailed mining tool statistics.
Args:
tool_name: Exact tool name
Returns:
MiningToolStats object or None
"""
cache_key = f"mining:{tool_name}"
if cache_key in self._cache:
return self._cache[cache_key]
data = self._make_request(f'v1/mining/tools/{urllib.parse.quote(tool_name)}')
if not data:
return None
try:
tool = MiningToolStats(
name=data['name'],
type=data.get('type', 'finder'),
depth=data.get('depth', 0),
radius=data.get('radius', 0),
decay_per_use=Decimal(str(data.get('decay_pec', 0))),
probe_cost=Decimal(str(data.get('probe_cost', 0.5)))
)
self._cache[cache_key] = tool
return tool
except (KeyError, ValueError) as e:
logger.error(f"Failed to parse mining tool data: {e}")
return None
# ========================================================================
# ARMOR ENDPOINTS
# ========================================================================
def search_armor(self, query: str) -> List[Dict]:
"""Search for armor pieces."""
data = self._make_request('v1/armor/search', {'q': query})
return data.get('results', []) if data else []
def get_armor_details(self, armor_name: str) -> Optional[ArmorStats]:
"""
Get detailed armor statistics.
Args:
armor_name: Exact armor name
Returns:
ArmorStats object or None
"""
cache_key = f"armor:{armor_name}"
if cache_key in self._cache:
return self._cache[cache_key]
data = self._make_request(f'v1/armor/{urllib.parse.quote(armor_name)}')
if not data:
return None
try:
armor = ArmorStats(
name=data['name'],
slot=data.get('slot', 'body'),
protection=data.get('protection', {}),
decay_per_hit=Decimal(str(data.get('decay_pec', 0))),
durability=data.get('durability', 1000)
)
self._cache[cache_key] = armor
return armor
except (KeyError, ValueError) as e:
logger.error(f"Failed to parse armor data: {e}")
return None
# ========================================================================
# MARKET DATA
# ========================================================================
def get_markup(self, item_name: str) -> Optional[Decimal]:
"""
Get current market markup percentage for an item.
Args:
item_name: Item name
Returns:
Markup percentage (e.g., 105.5 for 105.5%) or None
"""
data = self._make_request(f'v1/market/markup/{urllib.parse.quote(item_name)}')
if data and 'markup' in data:
return Decimal(str(data['markup']))
return None
def get_market_trends(self, item_name: str, days: int = 7) -> List[Dict]:
"""
Get market price trends over time.
Args:
item_name: Item name
days: Number of days of history
Returns:
List of daily price data
"""
data = self._make_request(
f'v1/market/trends/{urllib.parse.quote(item_name)}',
{'days': days}
)
return data.get('trends', []) if data else []
# ============================================================================
# GEAR LOADOUT MANAGER
# ============================================================================
class GearLoadout:
"""
Manages a player's gear setup for hunting/mining/crafting.
Tracks equipped items and calculates total operational costs.
"""
def __init__(self, name: str, activity_type: str):
"""
Initialize gear loadout.
Args:
name: Loadout name (e.g., "Daily Argo Setup")
activity_type: 'hunt', 'mine', or 'craft'
"""
self.name = name
self.activity_type = activity_type
# Hunting gear
self.weapon: Optional[WeaponStats] = None
self.amplifier: Optional[WeaponStats] = None # Weapon amp
self.scope: Optional[Any] = None
self.sight: Optional[Any] = None
# Armor set
self.armor: Dict[str, ArmorStats] = {} # slot -> ArmorStats
# Mining gear
self.finder: Optional[MiningToolStats] = None
self.extractor: Optional[MiningToolStats] = None
# Enhancers attached to items
self.enhancers: Dict[str, List[str]] = {} # item_name -> list of enhancer types
logger.info(f"GearLoadout created: {name} ({activity_type})")
def set_weapon(self, weapon: WeaponStats) -> None:
"""Set primary weapon."""
self.weapon = weapon
def add_armor_piece(self, armor: ArmorStats) -> None:
"""Add armor piece to loadout."""
self.armor[armor.slot] = armor
def set_mining_tools(self, finder: MiningToolStats,
extractor: Optional[MiningToolStats] = None) -> None:
"""Set mining tools."""
self.finder = finder
self.extractor = extractor
def calculate_hunting_cost_per_hour(self) -> Dict[str, Decimal]:
"""
Calculate total hunting cost per hour.
Returns:
Dict with cost breakdown
"""
costs = {
'weapon': Decimal("0"),
'amplifier': Decimal("0"),
'armor': Decimal("0"),
'total': Decimal("0")
}
if self.weapon:
costs['weapon'] = self.weapon.calculate_cost_per_hour()
if self.amplifier:
costs['amplifier'] = self.amplifier.calculate_cost_per_hour()
# Estimate armor decay (varies by mob type)
for armor in self.armor.values():
# Approximate: 100 hits per hour, decay per hit
costs['armor'] += (armor.decay_per_hit * 100) / 100 # PEC to PED
costs['total'] = costs['weapon'] + costs['amplifier'] + costs['armor']
return costs
def calculate_mining_cost_per_drop(self) -> Decimal:
"""Calculate total mining cost per drop."""
if not self.finder:
return Decimal("0")
cost = self.finder.calculate_cost_per_drop()
if self.extractor:
cost += (self.extractor.decay_per_use / 100) # PEC to PED
return cost
def to_dict(self) -> Dict:
"""Convert loadout to dictionary for serialization."""
return {
'name': self.name,
'activity_type': self.activity_type,
'weapon': self.weapon.name if self.weapon else None,
'armor': {slot: armor.name for slot, armor in self.armor.items()},
'finder': self.finder.name if self.finder else None,
'extractor': self.extractor.name if self.extractor else None,
}
# ============================================================================
# MODULE EXPORTS
# ============================================================================
__all__ = [
'EntropiaNexusAPI',
'WeaponStats',
'MiningToolStats',
'ArmorStats',
'GearLoadout'
]