fix(armor): match Entropia Nexus slot naming exactly
- TORSO (was CHEST) - Harness/Chest piece - LEGS (was THIGHS) - Thigh Guards - Updated Frontier set with correct slots - Display names match Nexus: Head, Torso, Arms, Hands, Legs, Shins, Feet Now matches Nexus JSON export structure exactly
This commit is contained in:
parent
fd2b34e395
commit
9e55c751b9
|
|
@ -19,21 +19,21 @@ from enum import Enum, auto
|
|||
|
||||
class ArmorSlot(Enum):
|
||||
"""Armor slot types in Entropia Universe.
|
||||
|
||||
Standard 7-piece armor structure:
|
||||
|
||||
Standard 7-piece armor structure (matches Entropia Nexus):
|
||||
- Head (Helmet)
|
||||
- Chest (Harness)
|
||||
- Arms (Arm Guards - both arms)
|
||||
- Hands (Gloves - both hands)
|
||||
- Thighs (Thigh Guards)
|
||||
- Torso (Harness/Chest)
|
||||
- Arms (Arm Guards)
|
||||
- Hands (Gloves)
|
||||
- Legs (Thigh Guards)
|
||||
- Shins (Shin Guards)
|
||||
- Feet (Foot Guards)
|
||||
"""
|
||||
HEAD = "head"
|
||||
CHEST = "chest" # Harness
|
||||
ARMS = "arms" # Arm Guards (both arms)
|
||||
HANDS = "hands" # Gloves (both hands)
|
||||
THIGHS = "thighs" # Thigh Guards
|
||||
TORSO = "torso" # Harness/Chest
|
||||
ARMS = "arms" # Arm Guards
|
||||
HANDS = "hands" # Gloves
|
||||
LEGS = "legs" # Thigh Guards
|
||||
SHINS = "shins" # Shin Guards
|
||||
FEET = "feet" # Foot Guards
|
||||
|
||||
|
|
@ -41,10 +41,10 @@ class ArmorSlot(Enum):
|
|||
# Full set of 7 slots
|
||||
ALL_ARMOR_SLOTS = [
|
||||
ArmorSlot.HEAD,
|
||||
ArmorSlot.CHEST,
|
||||
ArmorSlot.TORSO,
|
||||
ArmorSlot.ARMS,
|
||||
ArmorSlot.HANDS,
|
||||
ArmorSlot.THIGHS,
|
||||
ArmorSlot.LEGS,
|
||||
ArmorSlot.SHINS,
|
||||
ArmorSlot.FEET,
|
||||
]
|
||||
|
|
@ -62,18 +62,18 @@ class ProtectionProfile:
|
|||
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(
|
||||
|
|
@ -87,7 +87,7 @@ class ProtectionProfile:
|
|||
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(
|
||||
|
|
@ -101,7 +101,7 @@ class ProtectionProfile:
|
|||
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 {
|
||||
|
|
@ -115,7 +115,7 @@ class ProtectionProfile:
|
|||
'acid': str(self.acid),
|
||||
'electric': str(self.electric),
|
||||
}
|
||||
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, str]) -> "ProtectionProfile":
|
||||
"""Create from dictionary."""
|
||||
|
|
@ -137,7 +137,7 @@ 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)
|
||||
"""
|
||||
|
|
@ -146,35 +146,35 @@ class ArmorPlate:
|
|||
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).
|
||||
|
|
@ -184,11 +184,11 @@ class ArmorPlate:
|
|||
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 {
|
||||
|
|
@ -198,7 +198,7 @@ class ArmorPlate:
|
|||
'durability': self.durability,
|
||||
'block_chance': str(self.block_chance),
|
||||
}
|
||||
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict) -> "ArmorPlate":
|
||||
"""Create from dictionary."""
|
||||
|
|
@ -216,10 +216,10 @@ 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
|
||||
|
|
@ -232,51 +232,51 @@ class ArmorPiece:
|
|||
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).
|
||||
|
|
@ -286,31 +286,31 @@ class ArmorPiece:
|
|||
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."""
|
||||
"""Get human-readable slot name (matches Entropia Nexus)."""
|
||||
slot_names = {
|
||||
ArmorSlot.HEAD: "Helmet",
|
||||
ArmorSlot.CHEST: "Harness",
|
||||
ArmorSlot.ARMS: "Arm Guards",
|
||||
ArmorSlot.HANDS: "Gloves",
|
||||
ArmorSlot.THIGHS: "Thigh Guards",
|
||||
ArmorSlot.SHINS: "Shin Guards",
|
||||
ArmorSlot.FEET: "Foot Guards",
|
||||
ArmorSlot.HEAD: "Head",
|
||||
ArmorSlot.TORSO: "Torso",
|
||||
ArmorSlot.ARMS: "Arms",
|
||||
ArmorSlot.HANDS: "Hands",
|
||||
ArmorSlot.LEGS: "Legs",
|
||||
ArmorSlot.SHINS: "Shins",
|
||||
ArmorSlot.FEET: "Feet",
|
||||
}
|
||||
return slot_names.get(self.slot, self.slot.value)
|
||||
|
||||
|
||||
def to_dict(self) -> Dict:
|
||||
"""Convert to dictionary."""
|
||||
return {
|
||||
|
|
@ -323,7 +323,7 @@ class ArmorPiece:
|
|||
'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."""
|
||||
|
|
@ -351,33 +351,33 @@ class ArmorSet:
|
|||
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).
|
||||
|
|
@ -388,23 +388,23 @@ class ArmorSet:
|
|||
# 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 {
|
||||
|
|
@ -413,7 +413,7 @@ class ArmorSet:
|
|||
'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."""
|
||||
|
|
@ -440,45 +440,45 @@ class EquippedArmor:
|
|||
"""
|
||||
# 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)
|
||||
|
|
@ -486,29 +486,29 @@ class EquippedArmor:
|
|||
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).
|
||||
|
|
@ -516,48 +516,48 @@ class EquippedArmor:
|
|||
"""
|
||||
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:
|
||||
|
|
@ -566,7 +566,7 @@ class EquippedArmor:
|
|||
for slot, piece_data in data.get('pieces', {}).items()
|
||||
}
|
||||
equipped.pieces = pieces
|
||||
|
||||
|
||||
return equipped
|
||||
|
||||
|
||||
|
|
@ -579,17 +579,17 @@ 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
|
||||
|
|
@ -603,7 +603,7 @@ def calculate_hit_protection(
|
|||
) -> 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
|
||||
|
|
@ -611,21 +611,21 @@ def calculate_hit_protection(
|
|||
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
|
||||
"""
|
||||
|
|
@ -633,10 +633,10 @@ def calculate_hit_protection(
|
|||
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
|
||||
|
|
@ -645,66 +645,66 @@ def calculate_hit_protection(
|
|||
# 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:
|
||||
|
|
@ -715,7 +715,7 @@ def calculate_hit_protection(
|
|||
# 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)
|
||||
|
|
@ -723,9 +723,9 @@ def calculate_hit_protection(
|
|||
# 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
|
||||
|
||||
|
||||
|
|
@ -737,7 +737,7 @@ 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",
|
||||
|
|
@ -814,7 +814,7 @@ def create_shogun_set() -> ArmorSet:
|
|||
"""Create the Shogun armor set (medium, good vs impact/cut)."""
|
||||
# Shogun: 2500 durability = better economy than Ghost
|
||||
shogun_durability = 2500
|
||||
|
||||
|
||||
pieces = {
|
||||
ArmorSlot.HEAD: ArmorPiece(
|
||||
name="Shogun Helmet",
|
||||
|
|
@ -890,7 +890,7 @@ def create_shogun_set() -> ArmorSet:
|
|||
def create_vigilante_set() -> ArmorSet:
|
||||
"""Create the Vigilante armor set (light, good all-around)."""
|
||||
vigilante_durability = 2000 # Same as Ghost
|
||||
|
||||
|
||||
pieces = {
|
||||
ArmorSlot.HEAD: ArmorPiece(
|
||||
name="Vigilante Helmet",
|
||||
|
|
@ -966,7 +966,7 @@ def create_vigilante_set() -> ArmorSet:
|
|||
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",
|
||||
|
|
@ -1043,7 +1043,7 @@ 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",
|
||||
|
|
@ -1126,7 +1126,7 @@ def get_mock_plates() -> List[ArmorPlate]:
|
|||
# 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(
|
||||
|
|
@ -1226,7 +1226,7 @@ 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)",
|
||||
|
|
@ -1237,17 +1237,17 @@ def create_frontier_set() -> ArmorSet:
|
|||
protection=ProtectionProfile(impact=Decimal("4"), cut=Decimal("3"), stab=Decimal("3")),
|
||||
weight=Decimal("0.5"),
|
||||
),
|
||||
ArmorSlot.CHEST: ArmorPiece(
|
||||
ArmorSlot.TORSO: ArmorPiece(
|
||||
name="Frontier Harness, Adjusted (M)",
|
||||
item_id="frontier_harness_adj_m",
|
||||
slot=ArmorSlot.CHEST,
|
||||
slot=ArmorSlot.TORSO,
|
||||
set_name="Frontier",
|
||||
decay_per_hp=frontier_economy,
|
||||
protection=ProtectionProfile(impact=Decimal("8"), cut=Decimal("6"), stab=Decimal("6")),
|
||||
weight=Decimal("1.0"),
|
||||
),
|
||||
ArmorSlot.ARMS: ArmorPiece(
|
||||
name="Frontier Arm Guards, Adjusted (M)", # Both arms
|
||||
name="Frontier Arm Guards, Adjusted (M)",
|
||||
item_id="frontier_arm_adj_m",
|
||||
slot=ArmorSlot.ARMS,
|
||||
set_name="Frontier",
|
||||
|
|
@ -1256,7 +1256,7 @@ def create_frontier_set() -> ArmorSet:
|
|||
weight=Decimal("0.8"),
|
||||
),
|
||||
ArmorSlot.HANDS: ArmorPiece(
|
||||
name="Frontier Gloves, Adjusted (M)", # Both hands
|
||||
name="Frontier Gloves, Adjusted (M)",
|
||||
item_id="frontier_gloves_adj_m",
|
||||
slot=ArmorSlot.HANDS,
|
||||
set_name="Frontier",
|
||||
|
|
@ -1264,10 +1264,10 @@ def create_frontier_set() -> ArmorSet:
|
|||
protection=ProtectionProfile(impact=Decimal("3"), cut=Decimal("2"), stab=Decimal("2")),
|
||||
weight=Decimal("0.6"),
|
||||
),
|
||||
ArmorSlot.THIGHS: ArmorPiece(
|
||||
ArmorSlot.LEGS: ArmorPiece(
|
||||
name="Frontier Thigh Guards, Adjusted (M)",
|
||||
item_id="frontier_thigh_adj_m",
|
||||
slot=ArmorSlot.THIGHS,
|
||||
slot=ArmorSlot.LEGS,
|
||||
set_name="Frontier",
|
||||
decay_per_hp=frontier_economy,
|
||||
protection=ProtectionProfile(impact=Decimal("4"), cut=Decimal("3"), stab=Decimal("3")),
|
||||
|
|
@ -1340,16 +1340,16 @@ def get_pieces_by_set(set_name: str) -> List[ArmorPiece]:
|
|||
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:
|
||||
|
|
@ -1376,7 +1376,7 @@ def create_mixed_armor(selections: Dict[ArmorSlot, str]) -> EquippedArmor:
|
|||
weight=original.weight,
|
||||
)
|
||||
equipped.equip_piece(piece_copy)
|
||||
|
||||
|
||||
return equipped
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue