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