852 lines
38 KiB
Python
852 lines
38 KiB
Python
"""
|
|
Complete Entropia Nexus API Integration for Lemontropia Suite
|
|
Fetches all gear types: weapons, armors, plates, attachments, enhancers, healing, rings, clothes, pets
|
|
"""
|
|
|
|
from decimal import Decimal, InvalidOperation
|
|
from dataclasses import dataclass
|
|
from typing import Optional, List, Dict, Any
|
|
import requests
|
|
import json
|
|
import logging
|
|
from functools import lru_cache
|
|
|
|
from core.armor_system import ProtectionProfile
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
NEXUS_API_BASE = "https://api.entropianexus.com"
|
|
|
|
# Cache durations
|
|
CACHE_WEAPONS = 3600 # 1 hour
|
|
CACHE_ARMORS = 3600
|
|
CACHE_HEALING = 3600
|
|
|
|
|
|
def safe_decimal(value: Any, default: str = "0") -> Decimal:
|
|
"""Safely convert a value to Decimal, handling None, empty strings, and invalid values."""
|
|
if value is None:
|
|
return Decimal(default)
|
|
|
|
# Convert to string and clean
|
|
str_val = str(value).strip()
|
|
|
|
# Handle common null/invalid values
|
|
if str_val.lower() in ('none', 'null', '', 'nan', 'inf', '-inf'):
|
|
return Decimal(default)
|
|
|
|
try:
|
|
return Decimal(str_val)
|
|
except (InvalidOperation, ValueError, TypeError):
|
|
return Decimal(default)
|
|
|
|
|
|
def safe_int(value: Any, default: int = 0) -> int:
|
|
"""Safely convert a value to int, handling None and invalid values."""
|
|
if value is None:
|
|
return default
|
|
|
|
try:
|
|
return int(value)
|
|
except (ValueError, TypeError):
|
|
return default
|
|
|
|
|
|
@dataclass
|
|
class NexusItem:
|
|
"""Base class for all Nexus items."""
|
|
id: int
|
|
name: str
|
|
item_id: str
|
|
category: str
|
|
|
|
@classmethod
|
|
def from_api(cls, data: Dict[str, Any]) -> "NexusItem":
|
|
"""Create from API response."""
|
|
raise NotImplementedError
|
|
|
|
|
|
@dataclass
|
|
class NexusWeapon(NexusItem):
|
|
"""Weapon from Entropia Nexus API."""
|
|
damage: Decimal
|
|
decay: Decimal # PEC
|
|
ammo_burn: int
|
|
uses_per_minute: int
|
|
dpp: Decimal
|
|
cost_per_hour: Decimal
|
|
efficiency: Decimal
|
|
range_val: Decimal
|
|
type: str = ""
|
|
|
|
@classmethod
|
|
def from_api(cls, data: Dict[str, Any]) -> "NexusWeapon":
|
|
"""Create from API response."""
|
|
props = data.get('Properties', {})
|
|
economy = props.get('Economy', {})
|
|
damage = props.get('Damage', {})
|
|
|
|
return cls(
|
|
id=data.get('Id', 0),
|
|
name=data.get('Name', 'Unknown'),
|
|
item_id=str(data.get('Id', 0)),
|
|
category='weapon',
|
|
damage=safe_decimal(damage.get('Total')),
|
|
decay=safe_decimal(economy.get('Decay')),
|
|
ammo_burn=safe_int(economy.get('AmmoBurn')),
|
|
uses_per_minute=safe_int(economy.get('UsesPerMinute')),
|
|
dpp=safe_decimal(economy.get('DPP')),
|
|
cost_per_hour=safe_decimal(economy.get('CostPerHour')),
|
|
efficiency=safe_decimal(props.get('Efficiency')),
|
|
range_val=safe_decimal(props.get('Range')),
|
|
type=props.get('Type', ''),
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class NexusArmor(NexusItem):
|
|
"""Armor from Entropia Nexus API."""
|
|
durability: int
|
|
protection_impact: Decimal
|
|
protection_cut: Decimal
|
|
protection_stab: Decimal
|
|
protection_burn: Decimal
|
|
protection_cold: Decimal
|
|
protection_acid: Decimal
|
|
protection_electric: Decimal
|
|
type: str = ""
|
|
|
|
@classmethod
|
|
def from_api(cls, data: Dict[str, Any]) -> "NexusArmor":
|
|
"""Create from API response."""
|
|
props = data.get('Properties', {})
|
|
protection = props.get('Protection', {})
|
|
economy = props.get('Economy', {})
|
|
|
|
return cls(
|
|
id=data.get('Id', 0),
|
|
name=data.get('Name', 'Unknown'),
|
|
item_id=str(data.get('Id', 0)),
|
|
category='armor',
|
|
durability=safe_int(economy.get('Durability'), 2000),
|
|
protection_impact=safe_decimal(protection.get('Impact')),
|
|
protection_cut=safe_decimal(protection.get('Cut')),
|
|
protection_stab=safe_decimal(protection.get('Stab')),
|
|
protection_burn=safe_decimal(protection.get('Burn')),
|
|
protection_cold=safe_decimal(protection.get('Cold')),
|
|
protection_acid=safe_decimal(protection.get('Acid')),
|
|
protection_electric=safe_decimal(protection.get('Electric')),
|
|
type=props.get('Type', ''),
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class NexusPlate(NexusItem):
|
|
"""Armor plate from Entropia Nexus API."""
|
|
protection_impact: Decimal
|
|
protection_cut: Decimal
|
|
protection_stab: Decimal
|
|
protection_burn: Decimal
|
|
protection_cold: Decimal
|
|
protection_acid: Decimal
|
|
protection_electric: Decimal
|
|
decay: Decimal
|
|
|
|
@classmethod
|
|
def from_api(cls, data: Dict[str, Any]) -> "NexusPlate":
|
|
"""Create from API response."""
|
|
props = data.get('Properties', {})
|
|
protection = props.get('Protection', {})
|
|
|
|
return cls(
|
|
id=data.get('Id', 0),
|
|
name=data.get('Name', 'Unknown'),
|
|
item_id=str(data.get('Id', 0)),
|
|
category='plate',
|
|
protection_impact=safe_decimal(protection.get('Impact')),
|
|
protection_cut=safe_decimal(protection.get('Cut')),
|
|
protection_stab=safe_decimal(protection.get('Stab')),
|
|
protection_burn=safe_decimal(protection.get('Burn')),
|
|
protection_cold=safe_decimal(protection.get('Cold')),
|
|
protection_acid=safe_decimal(protection.get('Acid')),
|
|
protection_electric=safe_decimal(protection.get('Electric')),
|
|
decay=safe_decimal(props.get('Decay')),
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class NexusAttachment(NexusItem):
|
|
"""Weapon attachment from Entropia Nexus API."""
|
|
attachment_type: str # 'scope', 'sight', 'amplifier', 'absorber'
|
|
damage_bonus: Decimal
|
|
range_bonus: Decimal
|
|
decay: Decimal
|
|
efficiency_bonus: Decimal
|
|
zoom: int = 0 # For scopes
|
|
absorption: Decimal = Decimal("0") # For absorbers
|
|
|
|
@classmethod
|
|
def from_api(cls, data: Dict[str, Any]) -> "NexusAttachment":
|
|
"""Create from API response."""
|
|
props = data.get('Properties', {})
|
|
|
|
# Get Economy data
|
|
economy = props.get('Economy', {}) or {}
|
|
|
|
# Determine attachment type from API type or name
|
|
api_type = props.get('Type', '').lower()
|
|
name = data.get('Name', '').lower()
|
|
|
|
if 'amplifier' in name or 'amp' in name:
|
|
attachment_type = 'amplifier'
|
|
elif 'scope' in api_type or 'scope' in name:
|
|
attachment_type = 'scope'
|
|
elif 'sight' in api_type or 'sight' in name:
|
|
attachment_type = 'sight'
|
|
elif 'absorber' in name or 'extender' in name:
|
|
attachment_type = 'absorber'
|
|
else:
|
|
attachment_type = api_type or 'unknown'
|
|
|
|
# Get decay - absorbers don't have decay, they have absorption
|
|
if attachment_type == 'absorber':
|
|
decay = Decimal("0")
|
|
absorption = safe_decimal(economy.get('Absorption'))
|
|
else:
|
|
decay = safe_decimal(economy.get('Decay'))
|
|
absorption = Decimal("0")
|
|
|
|
efficiency = safe_decimal(economy.get('Efficiency'))
|
|
|
|
# Parse based on attachment type
|
|
damage_bonus = Decimal("0")
|
|
range_bonus = Decimal("0")
|
|
zoom = 0
|
|
absorption = Decimal("0")
|
|
|
|
if attachment_type == 'amplifier':
|
|
# Amplifiers have damage in Properties.Damage
|
|
damage_data = props.get('Damage', {}) or {}
|
|
# Sum up all non-null damage values
|
|
for dmg_type, value in damage_data.items():
|
|
if value is not None:
|
|
damage_bonus += safe_decimal(value)
|
|
|
|
elif attachment_type == 'scope':
|
|
# Scopes have skill modification and zoom
|
|
skill_mod = safe_decimal(props.get('SkillModification'))
|
|
skill_bonus = safe_decimal(props.get('SkillBonus'))
|
|
range_bonus = skill_mod + skill_bonus
|
|
zoom = int(props.get('Zoom', 0) or 0)
|
|
|
|
elif attachment_type == 'absorber':
|
|
# Absorbers have absorption in Economy
|
|
absorption = safe_decimal(economy.get('Absorption'))
|
|
|
|
return cls(
|
|
id=data.get('Id', 0),
|
|
name=data.get('Name', 'Unknown'),
|
|
item_id=str(data.get('Id', 0)),
|
|
category='attachment',
|
|
attachment_type=attachment_type,
|
|
damage_bonus=damage_bonus,
|
|
range_bonus=range_bonus,
|
|
decay=decay,
|
|
efficiency_bonus=efficiency,
|
|
zoom=zoom,
|
|
absorption=absorption,
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class NexusEnhancer(NexusItem):
|
|
"""Weapon/Armor enhancer from Entropia Nexus API."""
|
|
enhancer_type: str # 'damage', 'economy', 'range', 'accuracy', etc.
|
|
tier: int
|
|
effect_value: Decimal
|
|
break_chance: Decimal
|
|
|
|
@classmethod
|
|
def from_api(cls, data: Dict[str, Any]) -> "NexusEnhancer":
|
|
"""Create from API response."""
|
|
props = data.get('Properties', {})
|
|
|
|
return cls(
|
|
id=data.get('Id', 0),
|
|
name=data.get('Name', 'Unknown'),
|
|
item_id=str(data.get('Id', 0)),
|
|
category='enhancer',
|
|
enhancer_type=props.get('Type', ''),
|
|
tier=safe_int(props.get('Tier'), 1),
|
|
effect_value=safe_decimal(props.get('Effect')),
|
|
break_chance=safe_decimal(props.get('BreakChance'), "0.01"),
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class NexusArmorSet(NexusItem):
|
|
"""Armor set from Entropia Nexus API (e.g., 'Ghost Set', 'Shogun Set')."""
|
|
pieces: List[str] # List of armor piece names in the set
|
|
total_protection: ProtectionProfile
|
|
set_bonus: Optional[str] = None
|
|
|
|
@classmethod
|
|
def from_api(cls, data: Dict[str, Any]) -> "NexusArmorSet":
|
|
"""Create from API response."""
|
|
props = data.get('Properties', {})
|
|
|
|
# Get pieces from 'Armors' field (array of arrays)
|
|
armors_data = data.get('Armors', []) or []
|
|
pieces = []
|
|
for armor_group in armors_data:
|
|
if isinstance(armor_group, list):
|
|
for armor in armor_group:
|
|
pieces.append(armor.get('Name', ''))
|
|
elif isinstance(armor_group, dict):
|
|
pieces.append(armor_group.get('Name', ''))
|
|
|
|
# Get total protection from 'Defense' field
|
|
defense = props.get('Defense', {})
|
|
protection = ProtectionProfile(
|
|
impact=safe_decimal(defense.get('Impact')),
|
|
cut=safe_decimal(defense.get('Cut')),
|
|
stab=safe_decimal(defense.get('Stab')),
|
|
burn=safe_decimal(defense.get('Burn')),
|
|
cold=safe_decimal(defense.get('Cold')),
|
|
acid=safe_decimal(defense.get('Acid')),
|
|
electric=safe_decimal(defense.get('Electric')),
|
|
)
|
|
|
|
# Get set bonus from EffectsOnSetEquip
|
|
set_bonus = None
|
|
effects = data.get('EffectsOnSetEquip', [])
|
|
if effects:
|
|
# Join all effects into a string
|
|
set_bonus = "; ".join([str(e) for e in effects])
|
|
|
|
return cls(
|
|
id=data.get('Id', 0),
|
|
name=data.get('Name', 'Unknown'),
|
|
item_id=str(data.get('Id', 0)),
|
|
category='armorset',
|
|
pieces=pieces,
|
|
total_protection=protection,
|
|
set_bonus=set_bonus,
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class NexusMindforceImplant(NexusItem):
|
|
"""Mindforce implant from Entropia Nexus API."""
|
|
implant_type: str # 'damage', 'healing', 'utility'
|
|
chip_type: str # 'combustive', 'cryogenic', 'electric', etc.
|
|
decay: Decimal
|
|
profession_level: int = 0
|
|
is_limited: bool = False
|
|
|
|
@classmethod
|
|
def from_api(cls, data: Dict[str, Any]) -> "NexusMindforceImplant":
|
|
"""Create from API response."""
|
|
props = data.get('Properties', {})
|
|
economy = props.get('Economy', {})
|
|
|
|
# Determine type from name or properties
|
|
name = data.get('Name', '').lower()
|
|
if 'heal' in name or 'restore' in name:
|
|
implant_type = 'healing'
|
|
elif 'damage' in name or 'attack' in name:
|
|
implant_type = 'damage'
|
|
else:
|
|
implant_type = 'utility'
|
|
|
|
return cls(
|
|
id=data.get('Id', 0),
|
|
name=data.get('Name', 'Unknown'),
|
|
item_id=str(data.get('Id', 0)),
|
|
category='mindforce',
|
|
implant_type=implant_type,
|
|
chip_type=props.get('ChipType', 'unknown'),
|
|
decay=safe_decimal(economy.get('Decay')),
|
|
profession_level=int(props.get('RequiredLevel', 0) or 0),
|
|
is_limited='(L)' in data.get('Name', ''),
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class NexusHealingTool(NexusItem):
|
|
"""Healing tool from Entropia Nexus API."""
|
|
heal_amount: Decimal
|
|
decay: Decimal # PEC per heal
|
|
heal_per_pec: Decimal
|
|
type: str # 'fap', 'chip', 'pack'
|
|
profession_level: int = 0
|
|
is_limited: bool = False
|
|
|
|
@classmethod
|
|
def from_api(cls, data: Dict[str, Any]) -> "NexusHealingTool":
|
|
"""Create from API response."""
|
|
props = data.get('Properties', {})
|
|
economy = props.get('Economy', {}) or {}
|
|
|
|
decay = safe_decimal(economy.get('Decay'))
|
|
# API uses MaxHeal/MinHeal directly in Properties, not nested Heal object
|
|
max_heal = safe_decimal(props.get('MaxHeal'))
|
|
min_heal = safe_decimal(props.get('MinHeal'))
|
|
# Use average of min/max heal, or max if min is 0
|
|
heal_amount = (max_heal + min_heal) / 2 if min_heal > 0 else max_heal
|
|
if heal_amount == 0:
|
|
heal_amount = max_heal # Fallback to max heal
|
|
|
|
heal_per_pec = heal_amount / decay if decay > 0 else Decimal('0')
|
|
|
|
# Determine type based on name or properties
|
|
name = data.get('Name', '').lower()
|
|
if 'chip' in name or 'restoration' in name:
|
|
tool_type = 'chip'
|
|
elif 'pack' in name or 'fap' in name:
|
|
tool_type = 'pack'
|
|
else:
|
|
tool_type = 'fap'
|
|
|
|
return cls(
|
|
id=data.get('Id', 0),
|
|
name=data.get('Name', 'Unknown'),
|
|
item_id=str(data.get('Id', 0)),
|
|
category='healing',
|
|
heal_amount=heal_amount,
|
|
decay=decay,
|
|
heal_per_pec=heal_per_pec,
|
|
type=tool_type,
|
|
profession_level=int(props.get('RequiredLevel', 0) or 0),
|
|
is_limited='(L)' in data.get('Name', ''),
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class NexusRing(NexusItem):
|
|
"""Ring from Entropia Nexus API (found in /clothings endpoint)."""
|
|
slot: str # 'Left Finger', 'Right Finger'
|
|
gender: str # 'Both', 'Male', 'Female'
|
|
effects: Dict[str, str] # e.g., {"Increased Run Speed": "25%"}
|
|
max_tt: Decimal
|
|
min_tt: Decimal
|
|
is_limited: bool
|
|
|
|
@classmethod
|
|
def from_api(cls, data: Dict[str, Any]) -> "NexusRing":
|
|
"""Create from API response."""
|
|
props = data.get('Properties', {})
|
|
economy = props.get('Economy', {})
|
|
effects_data = props.get('Effects', {})
|
|
|
|
# Parse effects (usually under "Effects on Equip")
|
|
effects = {}
|
|
if 'Effects on Equip' in effects_data:
|
|
effects = effects_data['Effects on Equip']
|
|
elif isinstance(effects_data, dict):
|
|
effects = effects_data
|
|
|
|
return cls(
|
|
id=data.get('Id', 0),
|
|
name=data.get('Name', 'Unknown'),
|
|
item_id=str(data.get('Id', 0)),
|
|
category='ring',
|
|
slot=props.get('Slot', 'Left Finger'),
|
|
gender=props.get('Gender', 'Both'),
|
|
effects=effects,
|
|
max_tt=safe_decimal(economy.get('MaxTT')),
|
|
min_tt=safe_decimal(economy.get('MinTT')),
|
|
is_limited='(L)' in data.get('Name', ''),
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class NexusClothing(NexusItem):
|
|
"""Clothing item from Entropia Nexus API."""
|
|
slot: str # 'face', 'body', 'legs', 'feet'
|
|
buffs: Dict[str, Decimal]
|
|
is_cosmetic: bool
|
|
|
|
@classmethod
|
|
def from_api(cls, data: Dict[str, Any]) -> "NexusClothing":
|
|
"""Create from API response."""
|
|
props = data.get('Properties', {})
|
|
buffs_raw = props.get('Buffs', {}) or {}
|
|
buffs = {}
|
|
for k, v in buffs_raw.items():
|
|
try:
|
|
buffs[k] = safe_decimal(v)
|
|
except:
|
|
buffs[k] = Decimal('0')
|
|
|
|
return cls(
|
|
id=data.get('Id', 0),
|
|
name=data.get('Name', 'Unknown'),
|
|
item_id=str(data.get('Id', 0)),
|
|
category='clothing',
|
|
slot=props.get('Slot', 'body'),
|
|
buffs=buffs,
|
|
is_cosmetic=props.get('IsCosmetic', True),
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class NexusPet(NexusItem):
|
|
"""Pet from Entropia Nexus API."""
|
|
effect_type: str
|
|
effect_value: Decimal
|
|
level_required: int
|
|
|
|
@classmethod
|
|
def from_api(cls, data: Dict[str, Any]) -> "NexusPet":
|
|
"""Create from API response."""
|
|
props = data.get('Properties', {})
|
|
|
|
return cls(
|
|
id=data.get('Id', 0),
|
|
name=data.get('Name', 'Unknown'),
|
|
item_id=str(data.get('Id', 0)),
|
|
category='pet',
|
|
effect_type=props.get('EffectType', ''),
|
|
effect_value=Decimal(str(props.get('EffectValue', 0))),
|
|
level_required=int(props.get('LevelRequired', 0)),
|
|
)
|
|
|
|
|
|
class EntropiaNexusFullAPI:
|
|
"""Complete Entropia Nexus API client for all gear types."""
|
|
|
|
def __init__(self):
|
|
self.base_url = NEXUS_API_BASE
|
|
self._weapons_cache: Optional[List[NexusWeapon]] = None
|
|
self._armors_cache: Optional[List[NexusArmor]] = None
|
|
self._armor_sets_cache: Optional[List[NexusArmorSet]] = None
|
|
self._plates_cache: Optional[List[NexusPlate]] = None
|
|
self._absorbers_cache: Optional[List[NexusAttachment]] = None
|
|
self._amplifiers_cache: Optional[List[NexusAttachment]] = None
|
|
self._scopes_cache: Optional[List[NexusAttachment]] = None
|
|
self._attachments_cache: Optional[List[NexusAttachment]] = None
|
|
self._enhancers_cache: Optional[List[NexusEnhancer]] = None
|
|
self._healing_cache: Optional[List[NexusHealingTool]] = None
|
|
self._healing_chips_cache: Optional[List[NexusHealingTool]] = None
|
|
self._mindforce_implants_cache: Optional[List[NexusMindforceImplant]] = None
|
|
self._rings_cache: Optional[List[NexusRing]] = None
|
|
self._clothing_cache: Optional[List[NexusClothing]] = None
|
|
self._pets_cache: Optional[List[NexusPet]] = None
|
|
|
|
def _fetch(self, endpoint: str) -> List[Dict]:
|
|
"""Fetch data from API endpoint."""
|
|
try:
|
|
url = f"{self.base_url}/{endpoint}"
|
|
logger.info(f"Fetching from {url}")
|
|
response = requests.get(url, timeout=30)
|
|
response.raise_for_status()
|
|
return response.json()
|
|
except Exception as e:
|
|
logger.error(f"Failed to fetch {endpoint}: {e}")
|
|
return []
|
|
|
|
def get_all_weapons(self, force_refresh: bool = False) -> List[NexusWeapon]:
|
|
"""Fetch all weapons from Nexus API."""
|
|
if self._weapons_cache is None or force_refresh:
|
|
data = self._fetch("weapons")
|
|
self._weapons_cache = [NexusWeapon.from_api(item) for item in data]
|
|
logger.info(f"Loaded {len(self._weapons_cache)} weapons")
|
|
return self._weapons_cache
|
|
|
|
def get_all_armors(self, force_refresh: bool = False) -> List[NexusArmor]:
|
|
"""Fetch all armors from Nexus API."""
|
|
if self._armors_cache is None or force_refresh:
|
|
data = self._fetch("armors")
|
|
self._armors_cache = [NexusArmor.from_api(item) for item in data]
|
|
logger.info(f"Loaded {len(self._armors_cache)} armors")
|
|
return self._armors_cache
|
|
|
|
def get_all_armor_sets(self, force_refresh: bool = False) -> List[NexusArmorSet]:
|
|
"""Fetch all armor sets from Nexus API."""
|
|
if self._armor_sets_cache is None or force_refresh:
|
|
data = self._fetch("armorsets")
|
|
if data:
|
|
self._armor_sets_cache = [NexusArmorSet.from_api(item) for item in data]
|
|
logger.info(f"Loaded {len(self._armor_sets_cache)} armor sets from API")
|
|
else:
|
|
self._armor_sets_cache = []
|
|
logger.warning("No armor sets found in API")
|
|
return self._armor_sets_cache
|
|
|
|
def get_all_plates(self, force_refresh: bool = False) -> List[NexusPlate]:
|
|
"""Fetch plates from /armorplatings endpoint."""
|
|
if self._plates_cache is None or force_refresh:
|
|
data = self._fetch("armorplatings")
|
|
if data:
|
|
self._plates_cache = [NexusPlate.from_api(item) for item in data]
|
|
logger.info(f"Loaded {len(self._plates_cache)} plates from API")
|
|
else:
|
|
# Fallback to hardcoded data
|
|
self._plates_cache = self._get_hardcoded_plates()
|
|
logger.info(f"Loaded {len(self._plates_cache)} plates from hardcoded data")
|
|
return self._plates_cache
|
|
|
|
def _get_hardcoded_plates(self) -> List[NexusPlate]:
|
|
"""Return hardcoded plate data since API may not have plates endpoint."""
|
|
return [
|
|
# Impact Plates
|
|
NexusPlate(id=1, name="Armor Plating Mk. 5B", item_id="plate_5b", category="plate",
|
|
protection_impact=Decimal("6"), protection_cut=Decimal("0"), protection_stab=Decimal("0"),
|
|
protection_burn=Decimal("0"), protection_cold=Decimal("0"), protection_acid=Decimal("0"), protection_electric=Decimal("0"),
|
|
decay=Decimal("0.05")),
|
|
NexusPlate(id=2, name="Armor Plating Mk. 10A", item_id="plate_10a", category="plate",
|
|
protection_impact=Decimal("12"), protection_cut=Decimal("0"), protection_stab=Decimal("0"),
|
|
protection_burn=Decimal("0"), protection_cold=Decimal("0"), protection_acid=Decimal("0"), protection_electric=Decimal("0"),
|
|
decay=Decimal("0.05")),
|
|
NexusPlate(id=3, name="Armor Plating Mk. 25A", item_id="plate_25a", category="plate",
|
|
protection_impact=Decimal("25"), protection_cut=Decimal("0"), protection_stab=Decimal("0"),
|
|
protection_burn=Decimal("0"), protection_cold=Decimal("0"), protection_acid=Decimal("0"), protection_electric=Decimal("0"),
|
|
decay=Decimal("0.05")),
|
|
NexusPlate(id=4, name="Armor Plating Mk. 50A", item_id="plate_50a", category="plate",
|
|
protection_impact=Decimal("50"), protection_cut=Decimal("0"), protection_stab=Decimal("0"),
|
|
protection_burn=Decimal("0"), protection_cold=Decimal("0"), protection_acid=Decimal("0"), protection_electric=Decimal("0"),
|
|
decay=Decimal("0.05")),
|
|
# Cut/Stab Plates
|
|
NexusPlate(id=5, name="Armor Plating Mk. 5C", item_id="plate_5c", category="plate",
|
|
protection_impact=Decimal("0"), protection_cut=Decimal("6"), protection_stab=Decimal("6"),
|
|
protection_burn=Decimal("0"), protection_cold=Decimal("0"), protection_acid=Decimal("0"), protection_electric=Decimal("0"),
|
|
decay=Decimal("0.05")),
|
|
NexusPlate(id=6, name="Armor Plating Mk. 10C", item_id="plate_10c", category="plate",
|
|
protection_impact=Decimal("0"), protection_cut=Decimal("12"), protection_stab=Decimal("12"),
|
|
protection_burn=Decimal("0"), protection_cold=Decimal("0"), protection_acid=Decimal("0"), protection_electric=Decimal("0"),
|
|
decay=Decimal("0.05")),
|
|
NexusPlate(id=7, name="Armor Plating Mk. 25C", item_id="plate_25c", category="plate",
|
|
protection_impact=Decimal("0"), protection_cut=Decimal("25"), protection_stab=Decimal("25"),
|
|
protection_burn=Decimal("0"), protection_cold=Decimal("0"), protection_acid=Decimal("0"), protection_electric=Decimal("0"),
|
|
decay=Decimal("0.05")),
|
|
NexusPlate(id=8, name="Armor Plating Mk. 50C", item_id="plate_50c", category="plate",
|
|
protection_impact=Decimal("0"), protection_cut=Decimal("50"), protection_stab=Decimal("50"),
|
|
protection_burn=Decimal("0"), protection_cold=Decimal("0"), protection_acid=Decimal("0"), protection_electric=Decimal("0"),
|
|
decay=Decimal("0.05")),
|
|
# Electric Plates
|
|
NexusPlate(id=9, name="Armor Plating Mk. 10E", item_id="plate_10e", category="plate",
|
|
protection_impact=Decimal("0"), protection_cut=Decimal("0"), protection_stab=Decimal("0"),
|
|
protection_burn=Decimal("0"), protection_cold=Decimal("0"), protection_acid=Decimal("0"), protection_electric=Decimal("12"),
|
|
decay=Decimal("0.05")),
|
|
NexusPlate(id=10, name="Armor Plating Mk. 25E", item_id="plate_25e", category="plate",
|
|
protection_impact=Decimal("0"), protection_cut=Decimal("0"), protection_stab=Decimal("0"),
|
|
protection_burn=Decimal("0"), protection_cold=Decimal("0"), protection_acid=Decimal("0"), protection_electric=Decimal("25"),
|
|
decay=Decimal("0.05")),
|
|
# Burn Plates
|
|
NexusPlate(id=11, name="Armor Plating Mk. 10F", item_id="plate_10f", category="plate",
|
|
protection_impact=Decimal("0"), protection_cut=Decimal("0"), protection_stab=Decimal("0"),
|
|
protection_burn=Decimal("12"), protection_cold=Decimal("0"), protection_acid=Decimal("0"), protection_electric=Decimal("0"),
|
|
decay=Decimal("0.05")),
|
|
NexusPlate(id=12, name="Armor Plating Mk. 25F", item_id="plate_25f", category="plate",
|
|
protection_impact=Decimal("0"), protection_cut=Decimal("0"), protection_stab=Decimal("0"),
|
|
protection_burn=Decimal("25"), protection_cold=Decimal("0"), protection_acid=Decimal("0"), protection_electric=Decimal("0"),
|
|
decay=Decimal("0.05")),
|
|
# Acid Plates
|
|
NexusPlate(id=13, name="Armor Plating Mk. 10Acd", item_id="plate_10acd", category="plate",
|
|
protection_impact=Decimal("0"), protection_cut=Decimal("0"), protection_stab=Decimal("0"),
|
|
protection_burn=Decimal("0"), protection_cold=Decimal("0"), protection_acid=Decimal("12"), protection_electric=Decimal("0"),
|
|
decay=Decimal("0.05")),
|
|
NexusPlate(id=14, name="Armor Plating Mk. 25Acd", item_id="plate_25acd", category="plate",
|
|
protection_impact=Decimal("0"), protection_cut=Decimal("0"), protection_stab=Decimal("0"),
|
|
protection_burn=Decimal("0"), protection_cold=Decimal("0"), protection_acid=Decimal("25"), protection_electric=Decimal("0"),
|
|
decay=Decimal("0.05")),
|
|
# Cold Plates
|
|
NexusPlate(id=15, name="Armor Plating Mk. 10Cl", item_id="plate_10cl", category="plate",
|
|
protection_impact=Decimal("0"), protection_cut=Decimal("0"), protection_stab=Decimal("0"),
|
|
protection_burn=Decimal("0"), protection_cold=Decimal("12"), protection_acid=Decimal("0"), protection_electric=Decimal("0"),
|
|
decay=Decimal("0.05")),
|
|
NexusPlate(id=16, name="Armor Plating Mk. 25Cl", item_id="plate_25cl", category="plate",
|
|
protection_impact=Decimal("0"), protection_cut=Decimal("0"), protection_stab=Decimal("0"),
|
|
protection_burn=Decimal("0"), protection_cold=Decimal("25"), protection_acid=Decimal("0"), protection_electric=Decimal("0"),
|
|
decay=Decimal("0.05")),
|
|
# Shrapnel Plates
|
|
NexusPlate(id=17, name="Armor Plating Mk. 10S", item_id="plate_10s", category="plate",
|
|
protection_impact=Decimal("0"), protection_cut=Decimal("0"), protection_stab=Decimal("0"),
|
|
protection_burn=Decimal("0"), protection_cold=Decimal("0"), protection_acid=Decimal("0"), protection_electric=Decimal("0"),
|
|
decay=Decimal("0.05")),
|
|
NexusPlate(id=18, name="Armor Plating Mk. 25S", item_id="plate_25s", category="plate",
|
|
protection_impact=Decimal("0"), protection_cut=Decimal("0"), protection_stab=Decimal("0"),
|
|
protection_burn=Decimal("0"), protection_cold=Decimal("0"), protection_acid=Decimal("0"), protection_electric=Decimal("0"),
|
|
decay=Decimal("0.05")),
|
|
# Penetration Plates
|
|
NexusPlate(id=19, name="Armor Plating Mk. 10P", item_id="plate_10p", category="plate",
|
|
protection_impact=Decimal("0"), protection_cut=Decimal("0"), protection_stab=Decimal("0"),
|
|
protection_burn=Decimal("0"), protection_cold=Decimal("0"), protection_acid=Decimal("0"), protection_electric=Decimal("0"),
|
|
decay=Decimal("0.05")),
|
|
NexusPlate(id=20, name="Armor Plating Mk. 25P", item_id="plate_25p", category="plate",
|
|
protection_impact=Decimal("0"), protection_cut=Decimal("0"), protection_stab=Decimal("0"),
|
|
protection_burn=Decimal("0"), protection_cold=Decimal("0"), protection_acid=Decimal("0"), protection_electric=Decimal("0"),
|
|
decay=Decimal("0.05")),
|
|
]
|
|
|
|
def get_all_absorbers(self, force_refresh: bool = False) -> List[NexusAttachment]:
|
|
"""Fetch absorbers from /absorbers endpoint."""
|
|
if self._absorbers_cache is None or force_refresh:
|
|
data = self._fetch("absorbers")
|
|
if data:
|
|
self._absorbers_cache = [NexusAttachment.from_api(item) for item in data]
|
|
logger.info(f"Loaded {len(self._absorbers_cache)} absorbers from API")
|
|
else:
|
|
self._absorbers_cache = []
|
|
return self._absorbers_cache
|
|
|
|
def get_all_amplifiers(self, force_refresh: bool = False) -> List[NexusAttachment]:
|
|
"""Fetch amplifiers from /weaponamplifiers endpoint."""
|
|
if self._amplifiers_cache is None or force_refresh:
|
|
data = self._fetch("weaponamplifiers")
|
|
if data:
|
|
self._amplifiers_cache = [NexusAttachment.from_api(item) for item in data]
|
|
logger.info(f"Loaded {len(self._amplifiers_cache)} amplifiers from API")
|
|
else:
|
|
self._amplifiers_cache = []
|
|
return self._amplifiers_cache
|
|
|
|
def get_all_scopes(self, force_refresh: bool = False) -> List[NexusAttachment]:
|
|
"""Fetch scopes/sights from /weaponvisionattachments endpoint."""
|
|
if self._scopes_cache is None or force_refresh:
|
|
data = self._fetch("weaponvisionattachments")
|
|
if data:
|
|
self._scopes_cache = [NexusAttachment.from_api(item) for item in data]
|
|
logger.info(f"Loaded {len(self._scopes_cache)} scopes from API")
|
|
else:
|
|
self._scopes_cache = []
|
|
return self._scopes_cache
|
|
|
|
def get_all_healing_chips(self, force_refresh: bool = False) -> List[NexusHealingTool]:
|
|
"""Fetch healing chips from /medicalchips endpoint."""
|
|
if self._healing_chips_cache is None or force_refresh:
|
|
data = self._fetch("medicalchips")
|
|
if data:
|
|
self._healing_chips_cache = [NexusHealingTool.from_api(item) for item in data]
|
|
logger.info(f"Loaded {len(self._healing_chips_cache)} healing chips from API")
|
|
else:
|
|
self._healing_chips_cache = []
|
|
return self._healing_chips_cache
|
|
|
|
def get_all_enhancers(self, force_refresh: bool = False) -> List[NexusEnhancer]:
|
|
"""Fetch all enhancers from Nexus API."""
|
|
if self._enhancers_cache is None or force_refresh:
|
|
data = self._fetch("enhancers")
|
|
self._enhancers_cache = [NexusEnhancer.from_api(item) for item in data]
|
|
logger.info(f"Loaded {len(self._enhancers_cache)} enhancers")
|
|
return self._enhancers_cache
|
|
|
|
def get_all_healing_tools(self, force_refresh: bool = False) -> List[NexusHealingTool]:
|
|
"""Fetch all healing tools from Nexus API (includes medical tools AND chips)."""
|
|
if self._healing_cache is None or force_refresh:
|
|
# Fetch both medical tools and medical chips
|
|
tools_data = self._fetch("medicaltools")
|
|
chips_data = self._fetch("medicalchips")
|
|
|
|
all_healing = []
|
|
if tools_data:
|
|
all_healing.extend([NexusHealingTool.from_api(item) for item in tools_data])
|
|
if chips_data:
|
|
all_healing.extend([NexusHealingTool.from_api(item) for item in chips_data])
|
|
|
|
self._healing_cache = all_healing
|
|
logger.info(f"Loaded {len(self._healing_cache)} healing tools ({len(tools_data) if tools_data else 0} tools + {len(chips_data) if chips_data else 0} chips)")
|
|
return self._healing_cache
|
|
|
|
def get_all_mindforce_implants(self, force_refresh: bool = False) -> List[NexusMindforceImplant]:
|
|
"""Fetch mindforce implants from /mindforceimplants endpoint."""
|
|
if self._mindforce_implants_cache is None or force_refresh:
|
|
data = self._fetch("mindforceimplants")
|
|
if data:
|
|
self._mindforce_implants_cache = [NexusMindforceImplant.from_api(item) for item in data]
|
|
logger.info(f"Loaded {len(self._mindforce_implants_cache)} mindforce implants from API")
|
|
else:
|
|
self._mindforce_implants_cache = []
|
|
logger.warning("No mindforce implants found in API")
|
|
return self._mindforce_implants_cache
|
|
|
|
def get_all_rings(self, force_refresh: bool = False) -> List[NexusRing]:
|
|
"""Fetch rings from /clothings endpoint (filtered by Type=Ring)."""
|
|
if self._rings_cache is None or force_refresh:
|
|
data = self._fetch("clothings")
|
|
# Filter for Type=Ring
|
|
rings_data = [item for item in data if item.get('Properties', {}).get('Type') == 'Ring']
|
|
if rings_data:
|
|
self._rings_cache = [NexusRing.from_api(item) for item in rings_data]
|
|
logger.info(f"Loaded {len(self._rings_cache)} rings from clothings endpoint")
|
|
else:
|
|
# Fallback to hardcoded data
|
|
self._rings_cache = self._get_hardcoded_rings()
|
|
return self._rings_cache
|
|
|
|
def _get_hardcoded_rings(self) -> List[NexusRing]:
|
|
"""Return hardcoded ring data as fallback."""
|
|
return [
|
|
NexusRing(id=1, name="Ares Ring", item_id="ares_ring", category="ring", slot="Left Finger", gender="Both", effects={"Damage": "5%"}, max_tt=Decimal("10"), min_tt=Decimal("0"), is_limited=False),
|
|
NexusRing(id=2, name="Ares Ring (L)", item_id="ares_ring_l", category="ring", slot="Left Finger", gender="Both", effects={"Damage": "5%"}, max_tt=Decimal("10"), min_tt=Decimal("0"), is_limited=True),
|
|
NexusRing(id=3, name="Hermetic Ring", item_id="hermetic_ring", category="ring", slot="Left Finger", gender="Both", effects={"Economy": "2%"}, max_tt=Decimal("10"), min_tt=Decimal("0"), is_limited=False),
|
|
NexusRing(id=4, name="Hermetic Ring (L)", item_id="hermetic_ring_l", category="ring", slot="Left Finger", gender="Both", effects={"Economy": "2%"}, max_tt=Decimal("10"), min_tt=Decimal("0"), is_limited=True),
|
|
NexusRing(id=5, name="Courage Ring", item_id="courage_ring", category="ring", slot="Right Finger", gender="Both", effects={"Critical Hit": "1%"}, max_tt=Decimal("10"), min_tt=Decimal("0"), is_limited=False),
|
|
NexusRing(id=6, name="Courage Ring (L)", item_id="courage_ring_l", category="ring", slot="Right Finger", gender="Both", effects={"Critical Hit": "1%"}, max_tt=Decimal("10"), min_tt=Decimal("0"), is_limited=True),
|
|
]
|
|
|
|
def get_all_clothing(self, force_refresh: bool = False) -> List[NexusClothing]:
|
|
"""Fetch all clothing from Nexus API."""
|
|
if self._clothing_cache is None or force_refresh:
|
|
data = self._fetch("clothings") # Note: endpoint is 'clothings' not 'clothing'
|
|
self._clothing_cache = [NexusClothing.from_api(item) for item in data]
|
|
logger.info(f"Loaded {len(self._clothing_cache)} clothing items")
|
|
return self._clothing_cache
|
|
|
|
def get_all_pets(self, force_refresh: bool = False) -> List[NexusPet]:
|
|
"""Fetch all pets from Nexus API."""
|
|
if self._pets_cache is None or force_refresh:
|
|
data = self._fetch("pets")
|
|
self._pets_cache = [NexusPet.from_api(item) for item in data]
|
|
logger.info(f"Loaded {len(self._pets_cache)} pets")
|
|
return self._pets_cache
|
|
|
|
def search_weapons(self, query: str) -> List[NexusWeapon]:
|
|
"""Search weapons by name."""
|
|
weapons = self.get_all_weapons()
|
|
query_lower = query.lower()
|
|
return [w for w in weapons if query_lower in w.name.lower()]
|
|
|
|
def search_armors(self, query: str) -> List[NexusArmor]:
|
|
"""Search armors by name."""
|
|
armors = self.get_all_armors()
|
|
query_lower = query.lower()
|
|
return [a for a in armors if query_lower in a.name.lower()]
|
|
|
|
def search_healing_tools(self, query: str) -> List[NexusHealingTool]:
|
|
"""Search healing tools by name."""
|
|
tools = self.get_all_healing_tools()
|
|
query_lower = query.lower()
|
|
return [t for t in tools if query_lower in t.name.lower()]
|
|
|
|
def search_plates(self, query: str) -> List[NexusPlate]:
|
|
"""Search plates by name."""
|
|
plates = self.get_all_plates()
|
|
query_lower = query.lower()
|
|
return [p for p in plates if query_lower in p.name.lower()]
|
|
|
|
def search_all(self, query: str) -> Dict[str, List]:
|
|
"""Search across all gear types."""
|
|
return {
|
|
'weapons': self.search_weapons(query),
|
|
'armors': self.search_armors(query),
|
|
'plates': self.search_plates(query),
|
|
'healing': self.search_healing_tools(query),
|
|
'attachments': [a for a in self.get_all_attachments() if query.lower() in a.name.lower()],
|
|
'enhancers': [e for e in self.get_all_enhancers() if query.lower() in e.name.lower()],
|
|
'rings': [r for r in self.get_all_rings() if query.lower() in r.name.lower()],
|
|
'clothing': [c for c in self.get_all_clothing() if query.lower() in c.name.lower()],
|
|
'pets': [p for p in self.get_all_pets() if query.lower() in p.name.lower()],
|
|
}
|
|
|
|
|
|
# Global API instance
|
|
_nexus_api = None
|
|
|
|
def get_nexus_api() -> EntropiaNexusFullAPI:
|
|
"""Get the global Nexus API instance."""
|
|
global _nexus_api
|
|
if _nexus_api is None:
|
|
_nexus_api = EntropiaNexusFullAPI()
|
|
return _nexus_api |