""" 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 from enum import Enum, auto class ArmorSlot(Enum): """Armor slot types in Entropia Universe.""" HEAD = "head" CHEST = "chest" # Also called Harness LEFT_ARM = "left_arm" RIGHT_ARM = "right_arm" LEFT_HAND = "left_hand" RIGHT_HAND = "right_hand" LEGS = "legs" # Also called Thighs + Shins + Feet # Full set of 7 slots ALL_ARMOR_SLOTS = [ ArmorSlot.HEAD, ArmorSlot.CHEST, ArmorSlot.LEFT_ARM, ArmorSlot.RIGHT_ARM, ArmorSlot.LEFT_HAND, ArmorSlot.RIGHT_HAND, ArmorSlot.LEGS, ] @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. """ name: str item_id: str decay_per_hit: Decimal # Decay in PEC per hit absorbed protection: ProtectionProfile = field(default_factory=ProtectionProfile) durability: int = 10000 # Plate durability (hits it can take) def get_total_protection(self) -> Decimal: """Get total protection value.""" return self.protection.get_total() def get_decay_for_hit(self, damage_absorbed: Decimal) -> Decimal: """ Calculate decay for absorbing damage. In Entropia: Plate decay = base_decay + (damage_absorbed * decay_factor) Simplified: Fixed decay per hit when plate absorbs damage. """ # Base decay plus a small amount per damage point return self.decay_per_hit + (damage_absorbed * Decimal("0.001")) def to_dict(self) -> Dict: """Convert to dictionary.""" return { 'name': self.name, 'item_id': self.item_id, 'decay_per_hit': str(self.decay_per_hit), 'protection': self.protection.to_dict(), 'durability': self.durability, } @classmethod def from_dict(cls, data: Dict) -> "ArmorPlate": """Create from dictionary.""" return cls( name=data['name'], item_id=data['item_id'], decay_per_hit=Decimal(data['decay_per_hit']), protection=ProtectionProfile.from_dict(data.get('protection', {})), durability=data.get('durability', 10000), ) @dataclass class ArmorPiece: """ Individual armor piece (e.g., Ghost Helmet, Shogun Harness). Each piece protects one slot and can have one plate attached. """ name: str item_id: str slot: ArmorSlot set_name: Optional[str] = None # e.g., "Ghost", "Shogun" decay_per_hit: Decimal = Decimal("0.05") # Decay in PEC when hit protection: ProtectionProfile = field(default_factory=ProtectionProfile) durability: int = 10000 weight: Decimal = Decimal("1.0") # Weight in kg # Optional plate attachment attached_plate: Optional[ArmorPlate] = None 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_hit(self, damage_after_plate: Decimal) -> Decimal: """ Calculate armor piece decay for taking damage. Plate absorbs first, so damage_after_plate is what gets through. """ # Base decay plus small amount per damage point that hits armor return self.decay_per_hit + (damage_after_plate * Decimal("0.001")) 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.""" slot_names = { ArmorSlot.HEAD: "Head", ArmorSlot.CHEST: "Chest/Harness", ArmorSlot.LEFT_ARM: "Left Arm", ArmorSlot.RIGHT_ARM: "Right Arm", ArmorSlot.LEFT_HAND: "Left Hand", ArmorSlot.RIGHT_HAND: "Right Hand", ArmorSlot.LEGS: "Legs/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, 'decay_per_hit': str(self.decay_per_hit), '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'), decay_per_hit=Decimal(data['decay_per_hit']), protection=ProtectionProfile.from_dict(data.get('protection', {})), durability=data.get('durability', 10000), 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).""" total = Decimal("0") for piece in self.pieces.values(): total += piece.decay_per_hit if piece.attached_plate: total += piece.attached_plate.decay_per_hit 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).""" if self.full_set: return self.full_set.get_total_decay_per_hit() total = Decimal("0") for piece in self.pieces.values(): total += piece.decay_per_hit if piece.attached_plate: total += piece.attached_plate.decay_per_hit 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. In Entropia Universe: 1. If hit_location specified, only that slot's protection applies 2. Plates absorb damage FIRST (shield layer) 3. Armor absorbs remaining damage 4. Decay is calculated based on damage absorbed 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, ) # 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 plate_prot = piece.attached_plate.protection.get_effective_against(damage_type) if piece.attached_plate else Decimal("0") armor_prot = piece.protection.get_effective_against(damage_type) # Plate absorbs FIRST plate_absorb = min(plate_prot, incoming_damage) result.plate_absorbed = plate_absorb remaining = incoming_damage - plate_absorb # Armor absorbs remainder armor_absorb = min(armor_prot, remaining) result.armor_absorbed = armor_absorb result.damage_to_avatar = remaining - armor_absorb # Calculate decay if piece.attached_plate and plate_absorb > 0: result.plate_decay = piece.attached_plate.get_decay_for_hit(plate_absorb) if armor_absorb > 0: result.armor_decay = piece.get_decay_for_hit(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.protection.get_effective_against(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 # Distribute decay across all pieces that contributed piece_count = len(pieces) if piece_count > 0: plate_decay_per = result.plate_decay / Decimal(piece_count) if plate_absorb > 0 else Decimal("0") armor_decay_per = result.armor_decay / Decimal(piece_count) if armor_absorb > 0 else Decimal("0") # Sum actual decay from all pieces with plates/armor total_plate_decay = Decimal("0") total_armor_decay = Decimal("0") for piece in pieces.values(): if piece.attached_plate and plate_absorb > 0: total_plate_decay += piece.attached_plate.get_decay_for_hit(plate_absorb / piece_count) if armor_absorb > 0: total_armor_decay += piece.get_decay_for_hit(armor_absorb / piece_count) result.plate_decay = total_plate_decay result.armor_decay = total_armor_decay result.total_decay = total_plate_decay + total_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).""" pieces = { ArmorSlot.HEAD: ArmorPiece( name="Ghost Helmet", item_id="ghost_helmet", slot=ArmorSlot.HEAD, set_name="Ghost", decay_per_hit=Decimal("0.015"), protection=ProtectionProfile(impact=Decimal("2"), cut=Decimal("2"), stab=Decimal("2"), burn=Decimal("5"), cold=Decimal("5")), weight=Decimal("0.3"), ), ArmorSlot.CHEST: ArmorPiece( name="Ghost Harness", item_id="ghost_harness", slot=ArmorSlot.CHEST, set_name="Ghost", decay_per_hit=Decimal("0.035"), protection=ProtectionProfile(impact=Decimal("4"), cut=Decimal("4"), stab=Decimal("4"), burn=Decimal("8"), cold=Decimal("8")), weight=Decimal("0.7"), ), ArmorSlot.LEFT_ARM: ArmorPiece( name="Ghost Arm Guards (L)", item_id="ghost_arm_l", slot=ArmorSlot.LEFT_ARM, set_name="Ghost", decay_per_hit=Decimal("0.015"), protection=ProtectionProfile(impact=Decimal("2"), cut=Decimal("2"), stab=Decimal("2"), burn=Decimal("5"), cold=Decimal("5")), weight=Decimal("0.3"), ), ArmorSlot.RIGHT_ARM: ArmorPiece( name="Ghost Arm Guards (R)", item_id="ghost_arm_r", slot=ArmorSlot.RIGHT_ARM, set_name="Ghost", decay_per_hit=Decimal("0.015"), protection=ProtectionProfile(impact=Decimal("2"), cut=Decimal("2"), stab=Decimal("2"), burn=Decimal("5"), cold=Decimal("5")), weight=Decimal("0.3"), ), ArmorSlot.LEFT_HAND: ArmorPiece( name="Ghost Gloves (L)", item_id="ghost_gloves_l", slot=ArmorSlot.LEFT_HAND, set_name="Ghost", decay_per_hit=Decimal("0.010"), protection=ProtectionProfile(impact=Decimal("1"), cut=Decimal("1"), stab=Decimal("1"), burn=Decimal("3"), cold=Decimal("3")), weight=Decimal("0.2"), ), ArmorSlot.RIGHT_HAND: ArmorPiece( name="Ghost Gloves (R)", item_id="ghost_gloves_r", slot=ArmorSlot.RIGHT_HAND, set_name="Ghost", decay_per_hit=Decimal("0.010"), 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", decay_per_hit=Decimal("0.030"), 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).""" pieces = { ArmorSlot.HEAD: ArmorPiece( name="Shogun Helmet", item_id="shogun_helmet", slot=ArmorSlot.HEAD, set_name="Shogun", decay_per_hit=Decimal("0.025"), protection=ProtectionProfile(impact=Decimal("5"), cut=Decimal("4"), stab=Decimal("3"), burn=Decimal("2"), cold=Decimal("2")), weight=Decimal("0.8"), ), ArmorSlot.CHEST: ArmorPiece( name="Shogun Harness", item_id="shogun_harness", slot=ArmorSlot.CHEST, set_name="Shogun", decay_per_hit=Decimal("0.060"), protection=ProtectionProfile(impact=Decimal("8"), cut=Decimal("6"), stab=Decimal("5"), burn=Decimal("4"), cold=Decimal("4")), weight=Decimal("1.5"), ), ArmorSlot.LEFT_ARM: ArmorPiece( name="Shogun Arm Guards (L)", item_id="shogun_arm_l", slot=ArmorSlot.LEFT_ARM, set_name="Shogun", decay_per_hit=Decimal("0.025"), protection=ProtectionProfile(impact=Decimal("5"), cut=Decimal("4"), stab=Decimal("3"), burn=Decimal("2"), cold=Decimal("2")), weight=Decimal("0.8"), ), ArmorSlot.RIGHT_ARM: ArmorPiece( name="Shogun Arm Guards (R)", item_id="shogun_arm_r", slot=ArmorSlot.RIGHT_ARM, set_name="Shogun", decay_per_hit=Decimal("0.025"), protection=ProtectionProfile(impact=Decimal("5"), cut=Decimal("4"), stab=Decimal("3"), burn=Decimal("2"), cold=Decimal("2")), weight=Decimal("0.8"), ), ArmorSlot.LEFT_HAND: ArmorPiece( name="Shogun Gloves (L)", item_id="shogun_gloves_l", slot=ArmorSlot.LEFT_HAND, set_name="Shogun", decay_per_hit=Decimal("0.015"), protection=ProtectionProfile(impact=Decimal("3"), cut=Decimal("2"), stab=Decimal("2"), burn=Decimal("1"), cold=Decimal("1")), weight=Decimal("0.4"), ), ArmorSlot.RIGHT_HAND: ArmorPiece( name="Shogun Gloves (R)", item_id="shogun_gloves_r", slot=ArmorSlot.RIGHT_HAND, set_name="Shogun", decay_per_hit=Decimal("0.015"), 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", decay_per_hit=Decimal("0.050"), 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).""" pieces = { ArmorSlot.HEAD: ArmorPiece( name="Vigilante Helmet", item_id="vigilante_helmet", slot=ArmorSlot.HEAD, set_name="Vigilante", decay_per_hit=Decimal("0.015"), protection=ProtectionProfile(impact=Decimal("3"), cut=Decimal("2"), stab=Decimal("2")), weight=Decimal("0.4"), ), ArmorSlot.CHEST: ArmorPiece( name="Vigilante Harness", item_id="vigilante_harness", slot=ArmorSlot.CHEST, set_name="Vigilante", decay_per_hit=Decimal("0.040"), protection=ProtectionProfile(impact=Decimal("6"), cut=Decimal("5"), stab=Decimal("4")), weight=Decimal("1.0"), ), ArmorSlot.LEFT_ARM: ArmorPiece( name="Vigilante Arm Guards (L)", item_id="vigilante_arm_l", slot=ArmorSlot.LEFT_ARM, set_name="Vigilante", decay_per_hit=Decimal("0.015"), protection=ProtectionProfile(impact=Decimal("3"), cut=Decimal("2"), stab=Decimal("2")), weight=Decimal("0.4"), ), ArmorSlot.RIGHT_ARM: ArmorPiece( name="Vigilante Arm Guards (R)", item_id="vigilante_arm_r", slot=ArmorSlot.RIGHT_ARM, set_name="Vigilante", decay_per_hit=Decimal("0.015"), protection=ProtectionProfile(impact=Decimal("3"), cut=Decimal("2"), stab=Decimal("2")), weight=Decimal("0.4"), ), ArmorSlot.LEFT_HAND: ArmorPiece( name="Vigilante Gloves (L)", item_id="vigilante_gloves_l", slot=ArmorSlot.LEFT_HAND, set_name="Vigilante", decay_per_hit=Decimal("0.010"), protection=ProtectionProfile(impact=Decimal("2"), cut=Decimal("1"), stab=Decimal("1")), weight=Decimal("0.2"), ), ArmorSlot.RIGHT_HAND: ArmorPiece( name="Vigilante Gloves (R)", item_id="vigilante_gloves_r", slot=ArmorSlot.RIGHT_HAND, set_name="Vigilante", decay_per_hit=Decimal("0.010"), 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", decay_per_hit=Decimal("0.030"), 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).""" pieces = { ArmorSlot.HEAD: ArmorPiece( name="Hermes Helmet", item_id="hermes_helmet", slot=ArmorSlot.HEAD, set_name="Hermes", decay_per_hit=Decimal("0.020"), protection=ProtectionProfile(impact=Decimal("3"), cut=Decimal("3"), stab=Decimal("2"), penetration=Decimal("3")), weight=Decimal("0.5"), ), ArmorSlot.CHEST: ArmorPiece( name="Hermes Harness", item_id="hermes_harness", slot=ArmorSlot.CHEST, set_name="Hermes", decay_per_hit=Decimal("0.050"), protection=ProtectionProfile(impact=Decimal("10"), cut=Decimal("8"), stab=Decimal("7"), penetration=Decimal("5")), weight=Decimal("1.2"), ), ArmorSlot.LEFT_ARM: ArmorPiece( name="Hermes Arm Guards (L)", item_id="hermes_arm_l", slot=ArmorSlot.LEFT_ARM, set_name="Hermes", decay_per_hit=Decimal("0.020"), protection=ProtectionProfile(impact=Decimal("4"), cut=Decimal("4"), stab=Decimal("3"), penetration=Decimal("2")), weight=Decimal("0.5"), ), ArmorSlot.RIGHT_ARM: ArmorPiece( name="Hermes Arm Guards (R)", item_id="hermes_arm_r", slot=ArmorSlot.RIGHT_ARM, set_name="Hermes", decay_per_hit=Decimal("0.020"), protection=ProtectionProfile(impact=Decimal("4"), cut=Decimal("4"), stab=Decimal("3"), penetration=Decimal("2")), weight=Decimal("0.5"), ), ArmorSlot.LEFT_HAND: ArmorPiece( name="Hermes Gloves (L)", item_id="hermes_gloves_l", slot=ArmorSlot.LEFT_HAND, set_name="Hermes", decay_per_hit=Decimal("0.012"), protection=ProtectionProfile(impact=Decimal("2"), cut=Decimal("2"), stab=Decimal("1"), penetration=Decimal("1")), weight=Decimal("0.25"), ), ArmorSlot.RIGHT_HAND: ArmorPiece( name="Hermes Gloves (R)", item_id="hermes_gloves_r", slot=ArmorSlot.RIGHT_HAND, set_name="Hermes", decay_per_hit=Decimal("0.012"), 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_hit=Decimal("0.040"), 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).""" pieces = { ArmorSlot.HEAD: ArmorPiece( name="Pixie Helmet", item_id="pixie_helmet", slot=ArmorSlot.HEAD, set_name="Pixie", decay_per_hit=Decimal("0.008"), protection=ProtectionProfile(impact=Decimal("1"), cut=Decimal("1"), stab=Decimal("1")), weight=Decimal("0.2"), ), ArmorSlot.CHEST: ArmorPiece( name="Pixie Harness", item_id="pixie_harness", slot=ArmorSlot.CHEST, set_name="Pixie", decay_per_hit=Decimal("0.020"), protection=ProtectionProfile(impact=Decimal("4"), cut=Decimal("3"), stab=Decimal("2")), weight=Decimal("0.5"), ), ArmorSlot.LEFT_ARM: ArmorPiece( name="Pixie Arm Guards (L)", item_id="pixie_arm_l", slot=ArmorSlot.LEFT_ARM, set_name="Pixie", decay_per_hit=Decimal("0.008"), protection=ProtectionProfile(impact=Decimal("1"), cut=Decimal("1"), stab=Decimal("1")), weight=Decimal("0.2"), ), ArmorSlot.RIGHT_ARM: ArmorPiece( name="Pixie Arm Guards (R)", item_id="pixie_arm_r", slot=ArmorSlot.RIGHT_ARM, set_name="Pixie", decay_per_hit=Decimal("0.008"), protection=ProtectionProfile(impact=Decimal("1"), cut=Decimal("1"), stab=Decimal("1")), weight=Decimal("0.2"), ), ArmorSlot.LEFT_HAND: ArmorPiece( name="Pixie Gloves (L)", item_id="pixie_gloves_l", slot=ArmorSlot.LEFT_HAND, set_name="Pixie", decay_per_hit=Decimal("0.005"), protection=ProtectionProfile(impact=Decimal("1"), cut=Decimal("1")), weight=Decimal("0.1"), ), ArmorSlot.RIGHT_HAND: ArmorPiece( name="Pixie Gloves (R)", item_id="pixie_gloves_r", slot=ArmorSlot.RIGHT_HAND, set_name="Pixie", decay_per_hit=Decimal("0.005"), 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_hit=Decimal("0.015"), 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.""" return [ # General purpose plates ArmorPlate( name="Impact Plating I", item_id="plate_impact_1", decay_per_hit=Decimal("0.015"), protection=ProtectionProfile(impact=Decimal("3")), ), ArmorPlate( name="Impact Plating II", item_id="plate_impact_2", decay_per_hit=Decimal("0.030"), protection=ProtectionProfile(impact=Decimal("6")), ), ArmorPlate( name="Cut Plating I", item_id="plate_cut_1", decay_per_hit=Decimal("0.015"), protection=ProtectionProfile(cut=Decimal("3")), ), ArmorPlate( name="Stab Plating I", item_id="plate_stab_1", decay_per_hit=Decimal("0.015"), protection=ProtectionProfile(stab=Decimal("3")), ), ArmorPlate( name="Burn Plating I", item_id="plate_burn_1", decay_per_hit=Decimal("0.015"), protection=ProtectionProfile(burn=Decimal("3")), ), ArmorPlate( name="Cold Plating I", item_id="plate_cold_1", decay_per_hit=Decimal("0.015"), protection=ProtectionProfile(cold=Decimal("3")), ), ArmorPlate( name="Penetration Plating I", item_id="plate_pen_1", decay_per_hit=Decimal("0.020"), protection=ProtectionProfile(penetration=Decimal("3")), ), # Composite plates (multi-type) ArmorPlate( name="Composite Plating I", item_id="plate_composite_1", decay_per_hit=Decimal("0.025"), protection=ProtectionProfile( impact=Decimal("2"), cut=Decimal("2"), stab=Decimal("2") ), ), ArmorPlate( name="Elemental Plating I", item_id="plate_elemental_1", decay_per_hit=Decimal("0.025"), 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_hit=Decimal("0.050"), protection=ProtectionProfile(impact=Decimal("10")), ), ArmorPlate( name="Heavy Universal Plating", item_id="plate_heavy_uni", decay_per_hit=Decimal("0.060"), protection=ProtectionProfile( impact=Decimal("3"), cut=Decimal("3"), stab=Decimal("3"), burn=Decimal("3"), cold=Decimal("3"), penetration=Decimal("3") ), ), ] # ============================================================================ # Helper Functions # ============================================================================ 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_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_hit=original.decay_per_hit, 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"