""" Armor System for Lemontropia Suite Implements Entropia Universe armor mechanics: - 7 armor slots: Head, Chest/Harness, Left Arm, Right Arm, Left Hand, Right Hand, Legs/Feet - Full armor sets (e.g., "Ghost Set", "Shogun Set") with matching pieces - Individual armor pieces (mix & match) - 7 plate slots (one per armor piece) - Plates take damage FIRST (shield layer) - Plate protection + Armor protection = Total protection - Plate decay tracked separately from armor decay """ from dataclasses import dataclass, field from decimal import Decimal from typing import Optional, Dict, List, Tuple, ClassVar from enum import Enum, auto class ArmorSlot(Enum): """Armor slot types in Entropia Universe. Standard 7-piece armor structure (matches Entropia Nexus): - Head (Helmet) - Torso (Harness/Chest) - Arms (Arm Guards) - Hands (Gloves) - Legs (Thigh Guards) - Shins (Shin Guards) - Feet (Foot Guards) """ HEAD = "head" TORSO = "torso" # Harness/Chest ARMS = "arms" # Arm Guards HANDS = "hands" # Gloves LEGS = "legs" # Thigh Guards SHINS = "shins" # Shin Guards FEET = "feet" # Foot Guards # Full set of 7 slots ALL_ARMOR_SLOTS = [ ArmorSlot.HEAD, ArmorSlot.TORSO, ArmorSlot.ARMS, ArmorSlot.HANDS, ArmorSlot.LEGS, ArmorSlot.SHINS, ArmorSlot.FEET, ] @dataclass class ProtectionProfile: """Protection values for all damage types.""" stab: Decimal = Decimal("0") cut: Decimal = Decimal("0") impact: Decimal = Decimal("0") penetration: Decimal = Decimal("0") shrapnel: Decimal = Decimal("0") burn: Decimal = Decimal("0") cold: Decimal = Decimal("0") acid: Decimal = Decimal("0") electric: Decimal = Decimal("0") def get_total(self) -> Decimal: """Get total protection across all types.""" return ( self.stab + self.cut + self.impact + self.penetration + self.shrapnel + self.burn + self.cold + self.acid + self.electric ) def get_effective_against(self, damage_type: str) -> Decimal: """Get protection value for a specific damage type.""" return getattr(self, damage_type.lower(), Decimal("0")) def add(self, other: "ProtectionProfile") -> "ProtectionProfile": """Add another protection profile to this one.""" return ProtectionProfile( stab=self.stab + other.stab, cut=self.cut + other.cut, impact=self.impact + other.impact, penetration=self.penetration + other.penetration, shrapnel=self.shrapnel + other.shrapnel, burn=self.burn + other.burn, cold=self.cold + other.cold, acid=self.acid + other.acid, electric=self.electric + other.electric, ) def subtract(self, other: "ProtectionProfile") -> "ProtectionProfile": """Subtract another protection profile from this one.""" return ProtectionProfile( stab=max(Decimal("0"), self.stab - other.stab), cut=max(Decimal("0"), self.cut - other.cut), impact=max(Decimal("0"), self.impact - other.impact), penetration=max(Decimal("0"), self.penetration - other.penetration), shrapnel=max(Decimal("0"), self.shrapnel - other.shrapnel), burn=max(Decimal("0"), self.burn - other.burn), cold=max(Decimal("0"), self.cold - other.cold), acid=max(Decimal("0"), self.acid - other.acid), electric=max(Decimal("0"), self.electric - other.electric), ) def to_dict(self) -> Dict[str, str]: """Convert to dictionary with string values.""" return { 'stab': str(self.stab), 'cut': str(self.cut), 'impact': str(self.impact), 'penetration': str(self.penetration), 'shrapnel': str(self.shrapnel), 'burn': str(self.burn), 'cold': str(self.cold), 'acid': str(self.acid), 'electric': str(self.electric), } @classmethod def from_dict(cls, data: Dict[str, str]) -> "ProtectionProfile": """Create from dictionary.""" return cls( stab=Decimal(data.get('stab', '0')), cut=Decimal(data.get('cut', '0')), impact=Decimal(data.get('impact', '0')), penetration=Decimal(data.get('penetration', '0')), shrapnel=Decimal(data.get('shrapnel', '0')), burn=Decimal(data.get('burn', '0')), cold=Decimal(data.get('cold', '0')), acid=Decimal(data.get('acid', '0')), electric=Decimal(data.get('electric', '0')), ) @dataclass class ArmorPlate: """ Armor plating that attaches to armor pieces. Plates act as a shield layer - they take damage FIRST. Official Decay Formula (VU 15.15): Decay (PEC) = damage_absorbed * 0.05 * (1 - durability/100000) """ name: str item_id: str protection: ProtectionProfile = field(default_factory=ProtectionProfile) durability: int = 2000 # Plate durability affects economy decay_per_hp: Decimal = Decimal("0.05") # PEC per HP absorbed block_chance: Decimal = Decimal("0") # Chance to nullify hit completely (no decay) # Constants BASE_DECAY_FACTOR: ClassVar[Decimal] = Decimal("0.05") MAX_DURABILITY: ClassVar[int] = 100000 def get_total_protection(self) -> Decimal: """Get total protection value.""" return self.protection.get_total() def get_decay_for_damage(self, damage_absorbed: Decimal) -> Decimal: """ Calculate decay for absorbing damage using official formula. Formula: Decay (PEC) = damage_absorbed * 0.05 * (1 - durability/100000) Args: damage_absorbed: Amount of damage the plate actually absorbed Returns: Decay in PEC """ if damage_absorbed <= 0: return Decimal("0") durability_factor = Decimal("1") - Decimal(self.durability) / Decimal(self.MAX_DURABILITY) decay_pec = damage_absorbed * self.BASE_DECAY_FACTOR * durability_factor return decay_pec def get_hp_per_pec(self) -> Decimal: """ Calculate economy rating (hp per pec). Higher is better. """ durability_factor = Decimal("1") - Decimal(self.durability) / Decimal(self.MAX_DURABILITY) if durability_factor <= 0: return Decimal("0") return Decimal("1") / (self.BASE_DECAY_FACTOR * durability_factor) def get_effective_protection(self, damage_type: str) -> Decimal: """Get protection value for a specific damage type.""" return self.protection.get_effective_against(damage_type) def to_dict(self) -> Dict: """Convert to dictionary.""" return { 'name': self.name, 'item_id': self.item_id, 'protection': self.protection.to_dict(), 'durability': self.durability, 'block_chance': str(self.block_chance), } @classmethod def from_dict(cls, data: Dict) -> "ArmorPlate": """Create from dictionary.""" return cls( name=data['name'], item_id=data['item_id'], protection=ProtectionProfile.from_dict(data.get('protection', {})), durability=data.get('durability', 2000), block_chance=Decimal(data.get('block_chance', '0')), ) @dataclass class ArmorPiece: """ Individual armor piece (e.g., Ghost Helmet, Shogun Harness). Each piece protects one slot and can have one plate attached. Official Decay Formula (VU 15.15): Decay (PEC) = damage_absorbed * 0.05 * (1 - durability/100000) Economy varies by durability: - Ghost (2000 dur): 20.41 hp/pec - Gremlin (2950 dur): 20.61 hp/pec - Angel (4000 dur): 20.83 hp/pec """ name: str item_id: str slot: ArmorSlot set_name: Optional[str] = None # e.g., "Ghost", "Shogun" protection: ProtectionProfile = field(default_factory=ProtectionProfile) durability: int = 2000 # Durability affects economy weight: Decimal = Decimal("1.0") # Weight in kg decay_per_hp: Decimal = Decimal("0.05") # PEC per HP absorbed (0.05 = 20 hp/pec) # Optional plate attachment attached_plate: Optional[ArmorPlate] = None # Class constants (not instance fields) BASE_DECAY_FACTOR: ClassVar[Decimal] = Decimal("0.05") MAX_DURABILITY: ClassVar[int] = 100000 def get_base_protection(self) -> ProtectionProfile: """Get base protection without plate.""" return self.protection def get_total_protection(self) -> ProtectionProfile: """Get total protection including plate.""" if self.attached_plate: return self.protection.add(self.attached_plate.protection) return self.protection def get_total_protection_value(self) -> Decimal: """Get total protection value including plate.""" return self.get_total_protection().get_total() def get_decay_for_damage(self, damage_absorbed: Decimal) -> Decimal: """ Calculate armor decay for absorbing damage. Official Formula (VU 15.15): Decay (PEC) = damage_absorbed * 0.05 * (1 - durability/100000) Args: damage_absorbed: Amount of damage the armor actually absorbed Returns: Decay in PEC """ if damage_absorbed <= 0: return Decimal("0") # Calculate economy factor based on durability # Higher durability = better economy (less decay) durability_factor = Decimal("1") - Decimal(self.durability) / Decimal(self.MAX_DURABILITY) decay_pec = damage_absorbed * self.BASE_DECAY_FACTOR * durability_factor return decay_pec def get_hp_per_pec(self) -> Decimal: """ Calculate economy rating (hp per pec). Higher is better (more damage absorbed per pec spent). """ durability_factor = Decimal("1") - Decimal(self.durability) / Decimal(self.MAX_DURABILITY) if durability_factor <= 0: return Decimal("0") return Decimal("1") / (self.BASE_DECAY_FACTOR * durability_factor) def attach_plate(self, plate: ArmorPlate) -> bool: """Attach a plate to this armor piece.""" self.attached_plate = plate return True def remove_plate(self) -> Optional[ArmorPlate]: """Remove and return the attached plate.""" plate = self.attached_plate self.attached_plate = None return plate def get_slot_display_name(self) -> str: """Get human-readable slot name (matches Entropia Nexus).""" slot_names = { ArmorSlot.HEAD: "Head", ArmorSlot.TORSO: "Torso", ArmorSlot.ARMS: "Arms", ArmorSlot.HANDS: "Hands", ArmorSlot.LEGS: "Legs", ArmorSlot.SHINS: "Shins", ArmorSlot.FEET: "Feet", } return slot_names.get(self.slot, self.slot.value) def to_dict(self) -> Dict: """Convert to dictionary.""" return { 'name': self.name, 'item_id': self.item_id, 'slot': self.slot.value, 'set_name': self.set_name, 'protection': self.protection.to_dict(), 'durability': self.durability, 'weight': str(self.weight), 'attached_plate': self.attached_plate.to_dict() if self.attached_plate else None, } @classmethod def from_dict(cls, data: Dict) -> "ArmorPiece": """Create from dictionary.""" piece = cls( name=data['name'], item_id=data['item_id'], slot=ArmorSlot(data['slot']), set_name=data.get('set_name'), protection=ProtectionProfile.from_dict(data.get('protection', {})), durability=data.get('durability', 2000), weight=Decimal(data.get('weight', '1.0')), ) if data.get('attached_plate'): piece.attached_plate = ArmorPlate.from_dict(data['attached_plate']) return piece @dataclass class ArmorSet: """ Complete armor set (7 pieces covering all slots). Examples: Ghost Set, Shogun Set, Vigilante Set """ name: str # e.g., "Ghost Set" set_id: str pieces: Dict[ArmorSlot, ArmorPiece] = field(default_factory=dict) set_bonus: Optional[ProtectionProfile] = None # Some sets have bonuses def __post_init__(self): """Ensure all pieces reference this set.""" set_name = self.name.replace(" Set", "") for slot, piece in self.pieces.items(): piece.set_name = set_name def get_piece(self, slot: ArmorSlot) -> Optional[ArmorPiece]: """Get armor piece for a specific slot.""" return self.pieces.get(slot) def is_complete(self) -> bool: """Check if set has all 7 pieces.""" return len(self.pieces) == 7 and all(slot in self.pieces for slot in ALL_ARMOR_SLOTS) def get_total_protection(self) -> ProtectionProfile: """Get total protection from all pieces including plates.""" total = ProtectionProfile() for piece in self.pieces.values(): total = total.add(piece.get_total_protection()) # Add set bonus if complete if self.set_bonus and self.is_complete(): total = total.add(self.set_bonus) return total def get_total_decay_per_hit(self) -> Decimal: """ Get total decay per hit across all pieces (including plates). Note: This is an ESTIMATE assuming average damage absorption. Actual decay depends on how much damage each piece absorbs. """ # This is a rough estimate - actual decay depends on damage absorbed # Using a typical hit value of 10 hp for estimation typical_hit = Decimal("10") total = Decimal("0") for piece in self.pieces.values(): # Estimate armor decay (assuming it absorbs typical hit up to its protection) armor_absorb = min(typical_hit, piece.protection.get_total()) total += piece.get_decay_for_damage(armor_absorb) if piece.attached_plate: # Estimate plate decay plate_absorb = min(typical_hit, piece.attached_plate.get_total_protection()) total += piece.attached_plate.get_decay_for_damage(plate_absorb) return total def get_pieces_list(self) -> List[ArmorPiece]: """Get list of all pieces in slot order.""" return [self.pieces.get(slot) for slot in ALL_ARMOR_SLOTS if slot in self.pieces] def to_dict(self) -> Dict: """Convert to dictionary.""" return { 'name': self.name, 'set_id': self.set_id, 'pieces': {slot.value: piece.to_dict() for slot, piece in self.pieces.items()}, 'set_bonus': self.set_bonus.to_dict() if self.set_bonus else None, } @classmethod def from_dict(cls, data: Dict) -> "ArmorSet": """Create from dictionary.""" pieces = { ArmorSlot(slot): ArmorPiece.from_dict(piece_data) for slot, piece_data in data.get('pieces', {}).items() } set_bonus = None if data.get('set_bonus'): set_bonus = ProtectionProfile.from_dict(data['set_bonus']) return cls( name=data['name'], set_id=data['set_id'], pieces=pieces, set_bonus=set_bonus, ) @dataclass class EquippedArmor: """ Currently equipped armor configuration. Can mix pieces from different sets or wear a full set. """ # Individual pieces (mix & match) pieces: Dict[ArmorSlot, ArmorPiece] = field(default_factory=dict) # Or reference a full set full_set: Optional[ArmorSet] = None def get_piece(self, slot: ArmorSlot) -> Optional[ArmorPiece]: """Get equipped piece for a slot.""" if self.full_set: return self.full_set.get_piece(slot) return self.pieces.get(slot) def get_all_pieces(self) -> Dict[ArmorSlot, ArmorPiece]: """Get all equipped pieces as a dict.""" if self.full_set: return self.full_set.pieces return self.pieces def equip_piece(self, piece: ArmorPiece) -> None: """Equip an individual armor piece.""" # Unequip full set if equipping individual pieces self.full_set = None self.pieces[piece.slot] = piece def unequip_piece(self, slot: ArmorSlot) -> Optional[ArmorPiece]: """Unequip a piece from a slot.""" if self.full_set: return None # Can't unequip individual pieces from full set return self.pieces.pop(slot, None) def equip_full_set(self, armor_set: ArmorSet) -> None: """Equip a full armor set.""" self.full_set = armor_set self.pieces = {} def unequip_full_set(self) -> Optional[ArmorSet]: """Unequip full set.""" set_ref = self.full_set self.full_set = None return set_ref def attach_plate_to_slot(self, slot: ArmorSlot, plate: ArmorPlate) -> bool: """Attach a plate to the armor piece in a slot.""" piece = self.get_piece(slot) if piece: piece.attach_plate(plate) return True return False def remove_plate_from_slot(self, slot: ArmorSlot) -> Optional[ArmorPlate]: """Remove plate from armor piece in a slot.""" piece = self.get_piece(slot) if piece: return piece.remove_plate() return None def get_plate(self, slot: ArmorSlot) -> Optional[ArmorPlate]: """Get attached plate for a slot.""" piece = self.get_piece(slot) return piece.attached_plate if piece else None def get_total_protection(self) -> ProtectionProfile: """Get total protection from all equipped pieces and plates.""" if self.full_set: return self.full_set.get_total_protection() total = ProtectionProfile() for piece in self.pieces.values(): total = total.add(piece.get_total_protection()) return total def get_total_decay_per_hit(self) -> Decimal: """ Get total decay per hit (armor + plates). Note: This is an ESTIMATE assuming average damage absorption. """ if self.full_set: return self.full_set.get_total_decay_per_hit() # Estimate based on typical hit of 10 hp typical_hit = Decimal("10") total = Decimal("0") for piece in self.pieces.values(): armor_absorb = min(typical_hit, piece.protection.get_total()) total += piece.get_decay_for_damage(armor_absorb) if piece.attached_plate: plate_absorb = min(typical_hit, piece.attached_plate.get_total_protection()) total += piece.attached_plate.get_decay_for_damage(plate_absorb) return total def get_coverage(self) -> Tuple[int, int]: """Get armor coverage as (equipped_slots, total_slots).""" pieces = self.get_all_pieces() return (len(pieces), 7) def get_coverage_percentage(self) -> float: """Get armor coverage as percentage.""" equipped, total = self.get_coverage() return (equipped / total) * 100 if total > 0 else 0 def get_slot_status(self) -> Dict[ArmorSlot, bool]: """Get status of each slot (True if equipped).""" pieces = self.get_all_pieces() return {slot: slot in pieces for slot in ALL_ARMOR_SLOTS} def to_dict(self) -> Dict: """Convert to dictionary.""" return { 'pieces': {slot.value: piece.to_dict() for slot, piece in self.pieces.items()}, 'full_set': self.full_set.to_dict() if self.full_set else None, } @classmethod def from_dict(cls, data: Dict) -> "EquippedArmor": """Create from dictionary.""" equipped = cls() if data.get('full_set'): equipped.full_set = ArmorSet.from_dict(data['full_set']) else: pieces = { ArmorSlot(slot): ArmorPiece.from_dict(piece_data) for slot, piece_data in data.get('pieces', {}).items() } equipped.pieces = pieces return equipped # ============================================================================ # Damage Absorption Logic # ============================================================================ @dataclass class HitResult: """Result of a hit against armored target.""" raw_damage: Decimal damage_type: str # Damage absorbed plate_absorbed: Decimal = Decimal("0") armor_absorbed: Decimal = Decimal("0") damage_to_avatar: Decimal = Decimal("0") # Decay incurred plate_decay: Decimal = Decimal("0") armor_decay: Decimal = Decimal("0") total_decay: Decimal = Decimal("0") # Status plate_broken: bool = False armor_broken: bool = False def calculate_hit_protection( equipped_armor: EquippedArmor, incoming_damage: Decimal, damage_type: str, hit_location: Optional[ArmorSlot] = None ) -> HitResult: """ Calculate damage absorption for a hit using Loot 2.0 mechanics. Loot 2.0 Armor Mechanics (June 2017): 1. Plate absorbs damage FIRST (shield layer) 2. Armor absorbs remaining damage 3. Plate decay = damage_absorbed_by_plate * plate.decay_per_hp 4. Armor decay = damage_absorbed_by_armor * armor.decay_per_hp 5. Decay is LINEAR per damage point (20 hp/pec standard = 0.05 pec/hp) 6. Block chance on upgraded plates can nullify hit (no decay) Damage Flow: Incoming Damage → Plate absorbs first → Armor absorbs remainder → Player takes overflow Example: 20 Impact hit vs Ghost Harness (4 Impact) + Impact Plate (3 Impact): - Plate absorbs 3, decays for 3 * 0.05 = 0.15 PEC - Armor absorbs 4 (remaining after plate), decays for 4 * 0.05 = 0.20 PEC - Player takes 20 - 3 - 4 = 13 damage Args: equipped_armor: Currently equipped armor incoming_damage: Raw damage from attack damage_type: Type of damage (impact, burn, etc.) hit_location: Specific slot hit (None for full body/average) Returns: HitResult with absorption details """ result = HitResult( raw_damage=incoming_damage, damage_type=damage_type, ) # Check for block chance on plates (upgraded plates only) # This would nullify the hit completely with no decay # Get protection for the hit if hit_location: # Specific location hit piece = equipped_armor.get_piece(hit_location) if not piece: # No armor on that slot - full damage result.damage_to_avatar = incoming_damage return result # Check for block on plate if piece.attached_plate and piece.attached_plate.block_chance > 0: # Note: In real implementation, use random() to check block # For calculation purposes, we don't factor block chance pass # Plate protection for this damage type plate_prot = Decimal("0") if piece.attached_plate: plate_prot = piece.attached_plate.get_effective_protection(damage_type) # Armor protection for this damage type armor_prot = piece.protection.get_effective_against(damage_type) # Plate absorbs FIRST (up to its protection) plate_absorb = min(plate_prot, incoming_damage) result.plate_absorbed = plate_absorb remaining = incoming_damage - plate_absorb # Armor absorbs remainder (up to its protection) armor_absorb = min(armor_prot, remaining) result.armor_absorbed = armor_absorb result.damage_to_avatar = remaining - armor_absorb # Calculate decay based on actual damage absorbed (Loot 2.0) if piece.attached_plate and plate_absorb > 0: result.plate_decay = piece.attached_plate.get_decay_for_damage(plate_absorb) if armor_absorb > 0: result.armor_decay = piece.get_decay_for_damage(armor_absorb) result.total_decay = result.plate_decay + result.armor_decay else: # Full body hit - use average protection from all equipped pieces pieces = equipped_armor.get_all_pieces() if not pieces: result.damage_to_avatar = incoming_damage return result # Calculate total protection across all slots total_plate_prot = Decimal("0") total_armor_prot = Decimal("0") for piece in pieces.values(): total_armor_prot += piece.protection.get_effective_against(damage_type) if piece.attached_plate: total_plate_prot += piece.attached_plate.get_effective_protection(damage_type) # Plate absorbs FIRST plate_absorb = min(total_plate_prot, incoming_damage) result.plate_absorbed = plate_absorb remaining = incoming_damage - plate_absorb # Armor absorbs remainder armor_absorb = min(total_armor_prot, remaining) result.armor_absorbed = armor_absorb result.damage_to_avatar = remaining - armor_absorb # Calculate decay based on actual damage absorbed # Distribute decay proportionally across all pieces if plate_absorb > 0: for piece in pieces.values(): if piece.attached_plate: piece_plate_prot = piece.attached_plate.get_effective_protection(damage_type) if piece_plate_prot > 0 and total_plate_prot > 0: # This plate's share of absorption piece_plate_share = plate_absorb * (piece_plate_prot / total_plate_prot) result.plate_decay += piece.attached_plate.get_decay_for_damage(piece_plate_share) if armor_absorb > 0: for piece in pieces.values(): piece_armor_prot = piece.protection.get_effective_against(damage_type) if piece_armor_prot > 0 and total_armor_prot > 0: # This armor's share of absorption piece_armor_share = armor_absorb * (piece_armor_prot / total_armor_prot) result.armor_decay += piece.get_decay_for_damage(piece_armor_share) result.total_decay = result.plate_decay + result.armor_decay return result # ============================================================================ # Mock Data - Real Entropia Universe Armor Sets # ============================================================================ def create_ghost_set() -> ArmorSet: """Create the Ghost armor set (light, good vs cold/burn).""" # Ghost: 2000 durability = 20.41 hp/pec ghost_durability = 2000 pieces = { ArmorSlot.HEAD: ArmorPiece( name="Ghost Helmet", item_id="ghost_helmet", slot=ArmorSlot.HEAD, set_name="Ghost", durability=ghost_durability, protection=ProtectionProfile(impact=Decimal("2"), cut=Decimal("2"), stab=Decimal("2"), burn=Decimal("5"), cold=Decimal("5")), weight=Decimal("0.3"), ), ArmorSlot.TORSO: ArmorPiece( name="Ghost Harness", item_id="ghost_harness", slot=ArmorSlot.TORSO, set_name="Ghost", durability=ghost_durability, protection=ProtectionProfile(impact=Decimal("4"), cut=Decimal("4"), stab=Decimal("4"), burn=Decimal("8"), cold=Decimal("8")), weight=Decimal("0.7"), ), ArmorSlot.ARMS: ArmorPiece( name="Ghost Arm Guards (L)", item_id="ghost_arm_l", slot=ArmorSlot.ARMS, set_name="Ghost", durability=ghost_durability, protection=ProtectionProfile(impact=Decimal("2"), cut=Decimal("2"), stab=Decimal("2"), burn=Decimal("5"), cold=Decimal("5")), weight=Decimal("0.3"), ), ArmorSlot.ARMS: ArmorPiece( name="Ghost Arm Guards (R)", item_id="ghost_arm_r", slot=ArmorSlot.ARMS, set_name="Ghost", durability=ghost_durability, protection=ProtectionProfile(impact=Decimal("2"), cut=Decimal("2"), stab=Decimal("2"), burn=Decimal("5"), cold=Decimal("5")), weight=Decimal("0.3"), ), ArmorSlot.HANDS: ArmorPiece( name="Ghost Gloves (L)", item_id="ghost_gloves_l", slot=ArmorSlot.HANDS, set_name="Ghost", durability=ghost_durability, protection=ProtectionProfile(impact=Decimal("1"), cut=Decimal("1"), stab=Decimal("1"), burn=Decimal("3"), cold=Decimal("3")), weight=Decimal("0.2"), ), ArmorSlot.HANDS: ArmorPiece( name="Ghost Gloves (R)", item_id="ghost_gloves_r", slot=ArmorSlot.HANDS, set_name="Ghost", durability=ghost_durability, protection=ProtectionProfile(impact=Decimal("1"), cut=Decimal("1"), stab=Decimal("1"), burn=Decimal("3"), cold=Decimal("3")), weight=Decimal("0.2"), ), ArmorSlot.LEGS: ArmorPiece( name="Ghost Thigh+Shin Guards", item_id="ghost_legs", slot=ArmorSlot.LEGS, set_name="Ghost", durability=ghost_durability, protection=ProtectionProfile(impact=Decimal("3"), cut=Decimal("3"), stab=Decimal("3"), burn=Decimal("7"), cold=Decimal("7")), weight=Decimal("0.6"), ), } return ArmorSet( name="Ghost Set", set_id="ghost_set", pieces=pieces, ) def create_shogun_set() -> ArmorSet: """Create the Shogun armor set (medium, good vs impact/cut).""" # Shogun: 2500 durability = better economy than Ghost shogun_durability = 2500 pieces = { ArmorSlot.HEAD: ArmorPiece( name="Shogun Helmet", item_id="shogun_helmet", slot=ArmorSlot.HEAD, set_name="Shogun", durability=shogun_durability, protection=ProtectionProfile(impact=Decimal("5"), cut=Decimal("4"), stab=Decimal("3"), burn=Decimal("2"), cold=Decimal("2")), weight=Decimal("0.8"), ), ArmorSlot.TORSO: ArmorPiece( name="Shogun Harness", item_id="shogun_harness", slot=ArmorSlot.TORSO, set_name="Shogun", durability=shogun_durability, protection=ProtectionProfile(impact=Decimal("8"), cut=Decimal("6"), stab=Decimal("5"), burn=Decimal("4"), cold=Decimal("4")), weight=Decimal("1.5"), ), ArmorSlot.ARMS: ArmorPiece( name="Shogun Arm Guards (L)", item_id="shogun_arm_l", slot=ArmorSlot.ARMS, set_name="Shogun", durability=shogun_durability, protection=ProtectionProfile(impact=Decimal("5"), cut=Decimal("4"), stab=Decimal("3"), burn=Decimal("2"), cold=Decimal("2")), weight=Decimal("0.8"), ), ArmorSlot.ARMS: ArmorPiece( name="Shogun Arm Guards (R)", item_id="shogun_arm_r", slot=ArmorSlot.ARMS, set_name="Shogun", durability=shogun_durability, protection=ProtectionProfile(impact=Decimal("5"), cut=Decimal("4"), stab=Decimal("3"), burn=Decimal("2"), cold=Decimal("2")), weight=Decimal("0.8"), ), ArmorSlot.HANDS: ArmorPiece( name="Shogun Gloves (L)", item_id="shogun_gloves_l", slot=ArmorSlot.HANDS, set_name="Shogun", durability=shogun_durability, protection=ProtectionProfile(impact=Decimal("3"), cut=Decimal("2"), stab=Decimal("2"), burn=Decimal("1"), cold=Decimal("1")), weight=Decimal("0.4"), ), ArmorSlot.HANDS: ArmorPiece( name="Shogun Gloves (R)", item_id="shogun_gloves_r", slot=ArmorSlot.HANDS, set_name="Shogun", durability=shogun_durability, protection=ProtectionProfile(impact=Decimal("3"), cut=Decimal("2"), stab=Decimal("2"), burn=Decimal("1"), cold=Decimal("1")), weight=Decimal("0.4"), ), ArmorSlot.LEGS: ArmorPiece( name="Shogun Thigh+Shin Guards", item_id="shogun_legs", slot=ArmorSlot.LEGS, set_name="Shogun", durability=shogun_durability, protection=ProtectionProfile(impact=Decimal("7"), cut=Decimal("5"), stab=Decimal("4"), burn=Decimal("3"), cold=Decimal("3")), weight=Decimal("1.2"), ), } return ArmorSet( name="Shogun Set", set_id="shogun_set", pieces=pieces, ) def create_vigilante_set() -> ArmorSet: """Create the Vigilante armor set (light, good all-around).""" vigilante_durability = 2000 # Same as Ghost pieces = { ArmorSlot.HEAD: ArmorPiece( name="Vigilante Helmet", item_id="vigilante_helmet", slot=ArmorSlot.HEAD, set_name="Vigilante", durability=vigilante_durability, protection=ProtectionProfile(impact=Decimal("3"), cut=Decimal("2"), stab=Decimal("2")), weight=Decimal("0.4"), ), ArmorSlot.TORSO: ArmorPiece( name="Vigilante Harness", item_id="vigilante_harness", slot=ArmorSlot.TORSO, set_name="Vigilante", durability=vigilante_durability, protection=ProtectionProfile(impact=Decimal("6"), cut=Decimal("5"), stab=Decimal("4")), weight=Decimal("1.0"), ), ArmorSlot.ARMS: ArmorPiece( name="Vigilante Arm Guards (L)", item_id="vigilante_arm_l", slot=ArmorSlot.ARMS, set_name="Vigilante", durability=vigilante_durability, protection=ProtectionProfile(impact=Decimal("3"), cut=Decimal("2"), stab=Decimal("2")), weight=Decimal("0.4"), ), ArmorSlot.ARMS: ArmorPiece( name="Vigilante Arm Guards (R)", item_id="vigilante_arm_r", slot=ArmorSlot.ARMS, set_name="Vigilante", durability=vigilante_durability, protection=ProtectionProfile(impact=Decimal("3"), cut=Decimal("2"), stab=Decimal("2")), weight=Decimal("0.4"), ), ArmorSlot.HANDS: ArmorPiece( name="Vigilante Gloves (L)", item_id="vigilante_gloves_l", slot=ArmorSlot.HANDS, set_name="Vigilante", durability=vigilante_durability, protection=ProtectionProfile(impact=Decimal("2"), cut=Decimal("1"), stab=Decimal("1")), weight=Decimal("0.2"), ), ArmorSlot.HANDS: ArmorPiece( name="Vigilante Gloves (R)", item_id="vigilante_gloves_r", slot=ArmorSlot.HANDS, set_name="Vigilante", durability=vigilante_durability, protection=ProtectionProfile(impact=Decimal("2"), cut=Decimal("1"), stab=Decimal("1")), weight=Decimal("0.2"), ), ArmorSlot.LEGS: ArmorPiece( name="Vigilante Thigh+Shin Guards", item_id="vigilante_legs", slot=ArmorSlot.LEGS, set_name="Vigilante", durability=vigilante_durability, protection=ProtectionProfile(impact=Decimal("5"), cut=Decimal("4"), stab=Decimal("3")), weight=Decimal("0.8"), ), } return ArmorSet( name="Vigilante Set", set_id="vigilante_set", pieces=pieces, ) def create_hermes_set() -> ArmorSet: """Create the Hermes armor set (medium, good vs penetration).""" hermes_economy = Decimal("0.05") pieces = { ArmorSlot.HEAD: ArmorPiece( name="Hermes Helmet", item_id="hermes_helmet", slot=ArmorSlot.HEAD, set_name="Hermes", decay_per_hp=hermes_economy, protection=ProtectionProfile(impact=Decimal("3"), cut=Decimal("3"), stab=Decimal("2"), penetration=Decimal("3")), weight=Decimal("0.5"), ), ArmorSlot.TORSO: ArmorPiece( name="Hermes Harness", item_id="hermes_harness", slot=ArmorSlot.TORSO, set_name="Hermes", decay_per_hp=hermes_economy, protection=ProtectionProfile(impact=Decimal("10"), cut=Decimal("8"), stab=Decimal("7"), penetration=Decimal("5")), weight=Decimal("1.2"), ), ArmorSlot.ARMS: ArmorPiece( name="Hermes Arm Guards (L)", item_id="hermes_arm_l", slot=ArmorSlot.ARMS, set_name="Hermes", decay_per_hp=hermes_economy, protection=ProtectionProfile(impact=Decimal("4"), cut=Decimal("4"), stab=Decimal("3"), penetration=Decimal("2")), weight=Decimal("0.5"), ), ArmorSlot.ARMS: ArmorPiece( name="Hermes Arm Guards (R)", item_id="hermes_arm_r", slot=ArmorSlot.ARMS, set_name="Hermes", decay_per_hp=hermes_economy, protection=ProtectionProfile(impact=Decimal("4"), cut=Decimal("4"), stab=Decimal("3"), penetration=Decimal("2")), weight=Decimal("0.5"), ), ArmorSlot.HANDS: ArmorPiece( name="Hermes Gloves (L)", item_id="hermes_gloves_l", slot=ArmorSlot.HANDS, set_name="Hermes", decay_per_hp=hermes_economy, protection=ProtectionProfile(impact=Decimal("2"), cut=Decimal("2"), stab=Decimal("1"), penetration=Decimal("1")), weight=Decimal("0.25"), ), ArmorSlot.HANDS: ArmorPiece( name="Hermes Gloves (R)", item_id="hermes_gloves_r", slot=ArmorSlot.HANDS, set_name="Hermes", decay_per_hp=hermes_economy, protection=ProtectionProfile(impact=Decimal("2"), cut=Decimal("2"), stab=Decimal("1"), penetration=Decimal("1")), weight=Decimal("0.25"), ), ArmorSlot.LEGS: ArmorPiece( name="Hermes Thigh+Shin Guards", item_id="hermes_legs", slot=ArmorSlot.LEGS, set_name="Hermes", decay_per_hp=hermes_economy, protection=ProtectionProfile(impact=Decimal("7"), cut=Decimal("6"), stab=Decimal("5"), penetration=Decimal("4")), weight=Decimal("1.0"), ), } return ArmorSet( name="Hermes Set", set_id="hermes_set", pieces=pieces, ) def create_pixie_set() -> ArmorSet: """Create the Pixie armor set (light starter armor).""" # Pixie is starter armor, slightly worse economy (18 hp/pec) pixie_economy = Decimal("0.055") pieces = { ArmorSlot.HEAD: ArmorPiece( name="Pixie Helmet", item_id="pixie_helmet", slot=ArmorSlot.HEAD, set_name="Pixie", decay_per_hp=pixie_economy, protection=ProtectionProfile(impact=Decimal("1"), cut=Decimal("1"), stab=Decimal("1")), weight=Decimal("0.2"), ), ArmorSlot.TORSO: ArmorPiece( name="Pixie Harness", item_id="pixie_harness", slot=ArmorSlot.TORSO, set_name="Pixie", decay_per_hp=pixie_economy, protection=ProtectionProfile(impact=Decimal("4"), cut=Decimal("3"), stab=Decimal("2")), weight=Decimal("0.5"), ), ArmorSlot.ARMS: ArmorPiece( name="Pixie Arm Guards (L)", item_id="pixie_arm_l", slot=ArmorSlot.ARMS, set_name="Pixie", decay_per_hp=pixie_economy, protection=ProtectionProfile(impact=Decimal("1"), cut=Decimal("1"), stab=Decimal("1")), weight=Decimal("0.2"), ), ArmorSlot.ARMS: ArmorPiece( name="Pixie Arm Guards (R)", item_id="pixie_arm_r", slot=ArmorSlot.ARMS, set_name="Pixie", decay_per_hp=pixie_economy, protection=ProtectionProfile(impact=Decimal("1"), cut=Decimal("1"), stab=Decimal("1")), weight=Decimal("0.2"), ), ArmorSlot.HANDS: ArmorPiece( name="Pixie Gloves (L)", item_id="pixie_gloves_l", slot=ArmorSlot.HANDS, set_name="Pixie", decay_per_hp=pixie_economy, protection=ProtectionProfile(impact=Decimal("1"), cut=Decimal("1")), weight=Decimal("0.1"), ), ArmorSlot.HANDS: ArmorPiece( name="Pixie Gloves (R)", item_id="pixie_gloves_r", slot=ArmorSlot.HANDS, set_name="Pixie", decay_per_hp=pixie_economy, protection=ProtectionProfile(impact=Decimal("1"), cut=Decimal("1")), weight=Decimal("0.1"), ), ArmorSlot.LEGS: ArmorPiece( name="Pixie Thigh+Shin Guards", item_id="pixie_legs", slot=ArmorSlot.LEGS, set_name="Pixie", decay_per_hp=pixie_economy, protection=ProtectionProfile(impact=Decimal("2"), cut=Decimal("2"), stab=Decimal("1")), weight=Decimal("0.4"), ), } return ArmorSet( name="Pixie Set", set_id="pixie_set", pieces=pieces, ) # ============================================================================ # Mock Plates # ============================================================================ def get_mock_plates() -> List[ArmorPlate]: """Get list of available armor plates.""" # Standard plate economy: 20 hp/pec = 0.05 pec per hp # Upgraded plates might have better economy (25 hp/pec = 0.04 pec per hp) standard_economy = Decimal("0.05") improved_economy = Decimal("0.04") # 25 hp/pec return [ # General purpose plates ArmorPlate( name="Impact Plating I", item_id="plate_impact_1", decay_per_hp=standard_economy, protection=ProtectionProfile(impact=Decimal("3")), ), ArmorPlate( name="Impact Plating II", item_id="plate_impact_2", decay_per_hp=improved_economy, protection=ProtectionProfile(impact=Decimal("6")), ), # 5B Plates (the standard mid-game plate - good all-around) ArmorPlate( name="5B Plating Set", item_id="plate_5b", decay_per_hp=standard_economy, protection=ProtectionProfile( impact=Decimal("5"), cut=Decimal("5"), stab=Decimal("5"), burn=Decimal("3"), cold=Decimal("3") ), block_chance=Decimal("0.01"), # 1% block chance ), ArmorPlate( name="Cut Plating I", item_id="plate_cut_1", decay_per_hp=standard_economy, protection=ProtectionProfile(cut=Decimal("3")), ), ArmorPlate( name="Stab Plating I", item_id="plate_stab_1", decay_per_hp=standard_economy, protection=ProtectionProfile(stab=Decimal("3")), ), ArmorPlate( name="Burn Plating I", item_id="plate_burn_1", decay_per_hp=standard_economy, protection=ProtectionProfile(burn=Decimal("3")), ), ArmorPlate( name="Cold Plating I", item_id="plate_cold_1", decay_per_hp=standard_economy, protection=ProtectionProfile(cold=Decimal("3")), ), ArmorPlate( name="Penetration Plating I", item_id="plate_pen_1", decay_per_hp=standard_economy, protection=ProtectionProfile(penetration=Decimal("3")), ), # Composite plates (multi-type) ArmorPlate( name="Composite Plating I", item_id="plate_composite_1", decay_per_hp=standard_economy, protection=ProtectionProfile( impact=Decimal("2"), cut=Decimal("2"), stab=Decimal("2") ), ), ArmorPlate( name="Elemental Plating I", item_id="plate_elemental_1", decay_per_hp=standard_economy, protection=ProtectionProfile( burn=Decimal("2"), cold=Decimal("2"), acid=Decimal("2"), electric=Decimal("2") ), ), # Heavy plates ArmorPlate( name="Heavy Impact Plating", item_id="plate_heavy_impact", decay_per_hp=improved_economy, protection=ProtectionProfile(impact=Decimal("10")), ), ArmorPlate( name="Heavy Universal Plating", item_id="plate_heavy_uni", decay_per_hp=improved_economy, protection=ProtectionProfile( impact=Decimal("3"), cut=Decimal("3"), stab=Decimal("3"), burn=Decimal("3"), cold=Decimal("3"), penetration=Decimal("3") ), ), ] # ============================================================================ # Helper Functions # ============================================================================ def create_frontier_set() -> ArmorSet: """Create the Frontier armor set (Arkadia mid-level, good mobility).""" # Frontier Adjusted has 3800 durability = 20.83 hp/pec economy frontier_economy = Decimal("0.048") # ~20.8 hp/pec pieces = { ArmorSlot.HEAD: ArmorPiece( name="Frontier Helmet, Adjusted (M)", item_id="frontier_helmet_adj_m", slot=ArmorSlot.HEAD, set_name="Frontier", decay_per_hp=frontier_economy, protection=ProtectionProfile(impact=Decimal("4"), cut=Decimal("3"), stab=Decimal("3")), weight=Decimal("0.5"), ), ArmorSlot.TORSO: ArmorPiece( name="Frontier Harness, Adjusted (M)", item_id="frontier_harness_adj_m", slot=ArmorSlot.TORSO, set_name="Frontier", decay_per_hp=frontier_economy, protection=ProtectionProfile(impact=Decimal("8"), cut=Decimal("6"), stab=Decimal("6")), weight=Decimal("1.0"), ), ArmorSlot.ARMS: ArmorPiece( name="Frontier Arm Guards, Adjusted (M)", item_id="frontier_arm_adj_m", slot=ArmorSlot.ARMS, set_name="Frontier", decay_per_hp=frontier_economy, protection=ProtectionProfile(impact=Decimal("5"), cut=Decimal("4"), stab=Decimal("4")), weight=Decimal("0.8"), ), ArmorSlot.HANDS: ArmorPiece( name="Frontier Gloves, Adjusted (M)", item_id="frontier_gloves_adj_m", slot=ArmorSlot.HANDS, set_name="Frontier", decay_per_hp=frontier_economy, protection=ProtectionProfile(impact=Decimal("3"), cut=Decimal("2"), stab=Decimal("2")), weight=Decimal("0.6"), ), ArmorSlot.LEGS: ArmorPiece( name="Frontier Thigh Guards, Adjusted (M)", item_id="frontier_thigh_adj_m", slot=ArmorSlot.LEGS, set_name="Frontier", decay_per_hp=frontier_economy, protection=ProtectionProfile(impact=Decimal("4"), cut=Decimal("3"), stab=Decimal("3")), weight=Decimal("0.6"), ), ArmorSlot.SHINS: ArmorPiece( name="Frontier Shin Guards, Adjusted (M)", item_id="frontier_shin_adj_m", slot=ArmorSlot.SHINS, set_name="Frontier", decay_per_hp=frontier_economy, protection=ProtectionProfile(impact=Decimal("3"), cut=Decimal("2"), stab=Decimal("2")), weight=Decimal("0.4"), ), ArmorSlot.FEET: ArmorPiece( name="Frontier Foot Guards, Adjusted (M)", item_id="frontier_foot_adj_m", slot=ArmorSlot.FEET, set_name="Frontier", decay_per_hp=frontier_economy, protection=ProtectionProfile(impact=Decimal("2"), cut=Decimal("1"), stab=Decimal("1")), weight=Decimal("0.3"), ), } return ArmorSet( name="Frontier Set (Adjusted)", set_id="frontier_set_adj", pieces=pieces, ) def get_all_armor_sets() -> List[ArmorSet]: """Get all available armor sets.""" return [ create_pixie_set(), create_vigilante_set(), create_ghost_set(), create_shogun_set(), create_frontier_set(), create_hermes_set(), ] def get_armor_set_by_name(name: str) -> Optional[ArmorSet]: """Get an armor set by name.""" for armor_set in get_all_armor_sets(): if armor_set.name.lower() == name.lower() or armor_set.set_id.lower() == name.lower(): return armor_set return None def get_all_armor_pieces() -> List[ArmorPiece]: """Get all individual armor pieces from all sets.""" pieces = [] for armor_set in get_all_armor_sets(): pieces.extend(armor_set.pieces.values()) return pieces def get_pieces_by_slot(slot: ArmorSlot) -> List[ArmorPiece]: """Get all armor pieces for a specific slot.""" return [p for p in get_all_armor_pieces() if p.slot == slot] def get_pieces_by_set(set_name: str) -> List[ArmorPiece]: """Get all armor pieces from a specific set.""" return [p for p in get_all_armor_pieces() if p.set_name and p.set_name.lower() == set_name.lower()] def create_mixed_armor(selections: Dict[ArmorSlot, str]) -> EquippedArmor: """ Create mixed armor from piece names. Args: selections: Dict mapping slots to piece names Returns: EquippedArmor with mixed pieces """ equipped = EquippedArmor() all_pieces = {p.name.lower(): p for p in get_all_armor_pieces()} for slot, piece_name in selections.items(): piece_key = piece_name.lower() if piece_key in all_pieces: # Create a copy to avoid modifying original original = all_pieces[piece_key] piece_copy = ArmorPiece( name=original.name, item_id=original.item_id, slot=original.slot, set_name=original.set_name, decay_per_hp=original.decay_per_hp, protection=ProtectionProfile( stab=original.protection.stab, cut=original.protection.cut, impact=original.protection.impact, penetration=original.protection.penetration, shrapnel=original.protection.shrapnel, burn=original.protection.burn, cold=original.protection.cold, acid=original.protection.acid, electric=original.protection.electric, ), durability=original.durability, weight=original.weight, ) equipped.equip_piece(piece_copy) return equipped def format_protection(profile: ProtectionProfile) -> str: """Format protection profile for display.""" parts = [] if profile.impact > 0: parts.append(f"Imp:{profile.impact}") if profile.cut > 0: parts.append(f"Cut:{profile.cut}") if profile.stab > 0: parts.append(f"Stab:{profile.stab}") if profile.penetration > 0: parts.append(f"Pen:{profile.penetration}") if profile.shrapnel > 0: parts.append(f"Shr:{profile.shrapnel}") if profile.burn > 0: parts.append(f"Burn:{profile.burn}") if profile.cold > 0: parts.append(f"Cold:{profile.cold}") if profile.acid > 0: parts.append(f"Acid:{profile.acid}") if profile.electric > 0: parts.append(f"Elec:{profile.electric}") return ", ".join(parts) if parts else "None"