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