Lemontropia-Suite/core/armor_system.py

1397 lines
52 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.
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
block_chance: Decimal = Decimal("0") # Chance to nullify hit completely (no decay)
# Constants
BASE_DECAY_FACTOR: Decimal = Decimal("0.05")
MAX_DURABILITY: 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
# Base decay factor: 0.05 PEC per HP (20 hp/pec standard)
BASE_DECAY_FACTOR: Decimal = Decimal("0.05")
MAX_DURABILITY: int = 100000
# 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_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."""
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,
'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.CHEST: ArmorPiece(
name="Ghost Harness",
item_id="ghost_harness",
slot=ArmorSlot.CHEST,
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.LEFT_ARM: ArmorPiece(
name="Ghost Arm Guards (L)",
item_id="ghost_arm_l",
slot=ArmorSlot.LEFT_ARM,
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.RIGHT_ARM: ArmorPiece(
name="Ghost Arm Guards (R)",
item_id="ghost_arm_r",
slot=ArmorSlot.RIGHT_ARM,
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.LEFT_HAND: ArmorPiece(
name="Ghost Gloves (L)",
item_id="ghost_gloves_l",
slot=ArmorSlot.LEFT_HAND,
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.RIGHT_HAND: ArmorPiece(
name="Ghost Gloves (R)",
item_id="ghost_gloves_r",
slot=ArmorSlot.RIGHT_HAND,
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 uses standard 20 hp/pec economy
shogun_economy = Decimal("0.05")
pieces = {
ArmorSlot.HEAD: ArmorPiece(
name="Shogun Helmet",
item_id="shogun_helmet",
slot=ArmorSlot.HEAD,
set_name="Shogun",
decay_per_hp=shogun_economy,
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_hp=shogun_economy,
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_hp=shogun_economy,
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_hp=shogun_economy,
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_hp=shogun_economy,
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_hp=shogun_economy,
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_hp=shogun_economy,
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_economy = Decimal("0.05")
pieces = {
ArmorSlot.HEAD: ArmorPiece(
name="Vigilante Helmet",
item_id="vigilante_helmet",
slot=ArmorSlot.HEAD,
set_name="Vigilante",
decay_per_hp=vigilante_economy,
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_hp=vigilante_economy,
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_hp=vigilante_economy,
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_hp=vigilante_economy,
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_hp=vigilante_economy,
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_hp=vigilante_economy,
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_hp=vigilante_economy,
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.CHEST: ArmorPiece(
name="Hermes Harness",
item_id="hermes_harness",
slot=ArmorSlot.CHEST,
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.LEFT_ARM: ArmorPiece(
name="Hermes Arm Guards (L)",
item_id="hermes_arm_l",
slot=ArmorSlot.LEFT_ARM,
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.RIGHT_ARM: ArmorPiece(
name="Hermes Arm Guards (R)",
item_id="hermes_arm_r",
slot=ArmorSlot.RIGHT_ARM,
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.LEFT_HAND: ArmorPiece(
name="Hermes Gloves (L)",
item_id="hermes_gloves_l",
slot=ArmorSlot.LEFT_HAND,
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.RIGHT_HAND: ArmorPiece(
name="Hermes Gloves (R)",
item_id="hermes_gloves_r",
slot=ArmorSlot.RIGHT_HAND,
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.CHEST: ArmorPiece(
name="Pixie Harness",
item_id="pixie_harness",
slot=ArmorSlot.CHEST,
set_name="Pixie",
decay_per_hp=pixie_economy,
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_hp=pixie_economy,
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_hp=pixie_economy,
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_hp=pixie_economy,
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_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.CHEST: ArmorPiece(
name="Frontier Harness, Adjusted (M)",
item_id="frontier_harness_adj_m",
slot=ArmorSlot.CHEST,
set_name="Frontier",
decay_per_hp=frontier_economy,
protection=ProtectionProfile(impact=Decimal("8"), cut=Decimal("6"), stab=Decimal("6")),
weight=Decimal("1.0"),
),
ArmorSlot.LEFT_ARM: ArmorPiece(
name="Frontier Arm Guards, Adjusted (M)",
item_id="frontier_arm_adj_m",
slot=ArmorSlot.LEFT_ARM,
set_name="Frontier",
decay_per_hp=frontier_economy,
protection=ProtectionProfile(impact=Decimal("3"), cut=Decimal("2"), stab=Decimal("2")),
weight=Decimal("0.4"),
),
ArmorSlot.RIGHT_ARM: ArmorPiece(
name="Frontier Arm Guards, Adjusted (M)", # Same name - both arms
item_id="frontier_arm_adj_m_r",
slot=ArmorSlot.RIGHT_ARM,
set_name="Frontier",
decay_per_hp=frontier_economy,
protection=ProtectionProfile(impact=Decimal("3"), cut=Decimal("2"), stab=Decimal("2")),
weight=Decimal("0.4"),
),
ArmorSlot.LEFT_HAND: ArmorPiece(
name="Frontier Gloves, Adjusted (M)",
item_id="frontier_gloves_adj_m",
slot=ArmorSlot.LEFT_HAND,
set_name="Frontier",
decay_per_hp=frontier_economy,
protection=ProtectionProfile(impact=Decimal("2"), cut=Decimal("1"), stab=Decimal("1")),
weight=Decimal("0.3"),
),
ArmorSlot.RIGHT_HAND: ArmorPiece(
name="Frontier Gloves, Adjusted (M)", # Same name - both hands
item_id="frontier_gloves_adj_m_r",
slot=ArmorSlot.RIGHT_HAND,
set_name="Frontier",
decay_per_hp=frontier_economy,
protection=ProtectionProfile(impact=Decimal("2"), cut=Decimal("1"), stab=Decimal("1")),
weight=Decimal("0.3"),
),
# Note: Full Frontier set has separate Thigh, Shin, and Foot pieces
# Currently modeled as combined leg piece for simplicity
ArmorSlot.LEGS: ArmorPiece(
name="Frontier Thigh+Shin+Foot Guards, Adjusted (M)", # All leg pieces combined
item_id="frontier_legs_adj_m",
slot=ArmorSlot.LEGS,
set_name="Frontier",
decay_per_hp=frontier_economy,
protection=ProtectionProfile(impact=Decimal("8"), cut=Decimal("6"), stab=Decimal("6")),
weight=Decimal("1.2"),
),
}
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_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"