Lemontropia-Suite/core/armor_system.py

1173 lines
43 KiB
Python

"""
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"