diff --git a/core/armor_system.py b/core/armor_system.py index 0576121..c15efd1 100644 --- a/core/armor_system.py +++ b/core/armor_system.py @@ -128,20 +128,18 @@ class ArmorPlate: Armor plating that attaches to armor pieces. Plates act as a shield layer - they take damage FIRST. - Loot 2.0 Mechanics (June 2017): - - Plate decay is LINEAR per damage point absorbed - - Base economy: 20 hp/pec (0.05 pec per hp) - - Plate decays ONLY for damage it actually absorbs + Official Decay Formula (VU 15.15): + Decay (PEC) = damage_absorbed * 0.05 * (1 - durability/100000) """ name: str item_id: str - decay_per_hp: Decimal # Decay in PEC per HP absorbed (typically ~0.05 for 20 hp/pec) protection: ProtectionProfile = field(default_factory=ProtectionProfile) - durability: int = 10000 # Plate durability (hits it can take) + durability: int = 2000 # Plate durability affects economy block_chance: Decimal = Decimal("0") # Chance to nullify hit completely (no decay) - # Loot 2.0: Economy standard is 20 hp/pec = 0.05 pec/hp - DEFAULT_ECONOMY: Decimal = Decimal("0.05") # 20 hp per pec + # Constants + BASE_DECAY_FACTOR: Decimal = Decimal("0.05") + MAX_DURABILITY: int = 100000 def get_total_protection(self) -> Decimal: """Get total protection value.""" @@ -149,12 +147,9 @@ class ArmorPlate: def get_decay_for_damage(self, damage_absorbed: Decimal) -> Decimal: """ - Calculate decay for absorbing damage. + Calculate decay for absorbing damage using official formula. - Loot 2.0 Formula: - - Decay is LINEAR per damage point - - Plate only decays for damage it ACTUALLY absorbs - - Base: 20 hp/pec = 0.05 pec per hp absorbed + Formula: Decay (PEC) = damage_absorbed * 0.05 * (1 - durability/100000) Args: damage_absorbed: Amount of damage the plate actually absorbed @@ -164,7 +159,21 @@ class ArmorPlate: """ if damage_absorbed <= 0: return Decimal("0") - return damage_absorbed * self.decay_per_hp + + 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.""" @@ -175,7 +184,6 @@ class ArmorPlate: return { 'name': self.name, 'item_id': self.item_id, - 'decay_per_hp': str(self.decay_per_hp), 'protection': self.protection.to_dict(), 'durability': self.durability, 'block_chance': str(self.block_chance), @@ -187,9 +195,8 @@ class ArmorPlate: return cls( name=data['name'], item_id=data['item_id'], - decay_per_hp=Decimal(data['decay_per_hp']), protection=ProtectionProfile.from_dict(data.get('protection', {})), - durability=data.get('durability', 10000), + durability=data.get('durability', 2000), block_chance=Decimal(data.get('block_chance', '0')), ) @@ -200,27 +207,29 @@ class ArmorPiece: Individual armor piece (e.g., Ghost Helmet, Shogun Harness). Each piece protects one slot and can have one plate attached. - Loot 2.0 Mechanics (June 2017): - - Armor decay is LINEAR per damage point absorbed - - Base economy: 20 hp/pec (0.05 pec per hp) - - Armor only decays for damage it ACTUALLY absorbs (after plate) - - Plate absorbs FIRST, then armor takes remainder + 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" - decay_per_hp: Decimal = Decimal("0.05") # Decay in PEC per HP absorbed (20 hp/pec standard) protection: ProtectionProfile = field(default_factory=ProtectionProfile) - durability: int = 10000 + 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 - # Loot 2.0: Economy standard is 20 hp/pec = 0.05 pec/hp - DEFAULT_ECONOMY: Decimal = Decimal("0.05") # 20 hp per pec - def get_base_protection(self) -> ProtectionProfile: """Get base protection without plate.""" return self.protection @@ -239,10 +248,8 @@ class ArmorPiece: """ Calculate armor decay for absorbing damage. - Loot 2.0 Formula: - - Decay is LINEAR per damage point - - Armor only decays for damage it ACTUALLY absorbs - - Base: 20 hp/pec = 0.05 pec per hp absorbed + Official Formula (VU 15.15): + Decay (PEC) = damage_absorbed * 0.05 * (1 - durability/100000) Args: damage_absorbed: Amount of damage the armor actually absorbed @@ -252,7 +259,23 @@ class ArmorPiece: """ if damage_absorbed <= 0: return Decimal("0") - return damage_absorbed * self.decay_per_hp + + # 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.""" @@ -285,7 +308,6 @@ class ArmorPiece: 'item_id': self.item_id, 'slot': self.slot.value, 'set_name': self.set_name, - 'decay_per_hp': str(self.decay_per_hp), 'protection': self.protection.to_dict(), 'durability': self.durability, 'weight': str(self.weight), @@ -300,9 +322,8 @@ class ArmorPiece: item_id=data['item_id'], slot=ArmorSlot(data['slot']), set_name=data.get('set_name'), - decay_per_hp=Decimal(data.get('decay_per_hp', '0.05')), protection=ProtectionProfile.from_dict(data.get('protection', {})), - durability=data.get('durability', 10000), + durability=data.get('durability', 2000), weight=Decimal(data.get('weight', '1.0')), ) if data.get('attached_plate'): @@ -704,8 +725,8 @@ def calculate_hit_protection( def create_ghost_set() -> ArmorSet: """Create the Ghost armor set (light, good vs cold/burn).""" - # Ghost uses standard 20 hp/pec economy (0.05 pec per hp) - ghost_economy = Decimal("0.05") + # Ghost: 2000 durability = 20.41 hp/pec + ghost_durability = 2000 pieces = { ArmorSlot.HEAD: ArmorPiece( @@ -713,7 +734,7 @@ def create_ghost_set() -> ArmorSet: item_id="ghost_helmet", slot=ArmorSlot.HEAD, set_name="Ghost", - decay_per_hp=ghost_economy, + durability=ghost_durability, protection=ProtectionProfile(impact=Decimal("2"), cut=Decimal("2"), stab=Decimal("2"), burn=Decimal("5"), cold=Decimal("5")), weight=Decimal("0.3"), ), @@ -722,7 +743,7 @@ def create_ghost_set() -> ArmorSet: item_id="ghost_harness", slot=ArmorSlot.CHEST, set_name="Ghost", - decay_per_hp=ghost_economy, + durability=ghost_durability, protection=ProtectionProfile(impact=Decimal("4"), cut=Decimal("4"), stab=Decimal("4"), burn=Decimal("8"), cold=Decimal("8")), weight=Decimal("0.7"), ), @@ -731,7 +752,7 @@ def create_ghost_set() -> ArmorSet: item_id="ghost_arm_l", slot=ArmorSlot.LEFT_ARM, set_name="Ghost", - decay_per_hp=ghost_economy, + durability=ghost_durability, protection=ProtectionProfile(impact=Decimal("2"), cut=Decimal("2"), stab=Decimal("2"), burn=Decimal("5"), cold=Decimal("5")), weight=Decimal("0.3"), ), @@ -740,7 +761,7 @@ def create_ghost_set() -> ArmorSet: item_id="ghost_arm_r", slot=ArmorSlot.RIGHT_ARM, set_name="Ghost", - decay_per_hp=ghost_economy, + durability=ghost_durability, protection=ProtectionProfile(impact=Decimal("2"), cut=Decimal("2"), stab=Decimal("2"), burn=Decimal("5"), cold=Decimal("5")), weight=Decimal("0.3"), ), @@ -749,7 +770,7 @@ def create_ghost_set() -> ArmorSet: item_id="ghost_gloves_l", slot=ArmorSlot.LEFT_HAND, set_name="Ghost", - decay_per_hp=ghost_economy, + durability=ghost_durability, protection=ProtectionProfile(impact=Decimal("1"), cut=Decimal("1"), stab=Decimal("1"), burn=Decimal("3"), cold=Decimal("3")), weight=Decimal("0.2"), ), @@ -758,7 +779,7 @@ def create_ghost_set() -> ArmorSet: item_id="ghost_gloves_r", slot=ArmorSlot.RIGHT_HAND, set_name="Ghost", - decay_per_hp=ghost_economy, + durability=ghost_durability, protection=ProtectionProfile(impact=Decimal("1"), cut=Decimal("1"), stab=Decimal("1"), burn=Decimal("3"), cold=Decimal("3")), weight=Decimal("0.2"), ), @@ -767,7 +788,7 @@ def create_ghost_set() -> ArmorSet: item_id="ghost_legs", slot=ArmorSlot.LEGS, set_name="Ghost", - decay_per_hp=ghost_economy, + durability=ghost_durability, protection=ProtectionProfile(impact=Decimal("3"), cut=Decimal("3"), stab=Decimal("3"), burn=Decimal("7"), cold=Decimal("7")), weight=Decimal("0.6"), ), @@ -1198,8 +1219,8 @@ def create_frontier_set() -> ArmorSet: pieces = { ArmorSlot.HEAD: ArmorPiece( - name="Frontier Helmet, Adjusted", - item_id="frontier_helmet_adj", + name="Frontier Helmet, Adjusted (M)", + item_id="frontier_helmet_adj_m", slot=ArmorSlot.HEAD, set_name="Frontier", decay_per_hp=frontier_economy, @@ -1207,8 +1228,8 @@ def create_frontier_set() -> ArmorSet: weight=Decimal("0.5"), ), ArmorSlot.CHEST: ArmorPiece( - name="Frontier Harness, Adjusted", - item_id="frontier_harness_adj", + name="Frontier Harness, Adjusted (M)", + item_id="frontier_harness_adj_m", slot=ArmorSlot.CHEST, set_name="Frontier", decay_per_hp=frontier_economy, @@ -1216,8 +1237,8 @@ def create_frontier_set() -> ArmorSet: weight=Decimal("1.0"), ), ArmorSlot.LEFT_ARM: ArmorPiece( - name="Frontier Arm Guards, Adjusted (L)", - item_id="frontier_arm_l_adj", + name="Frontier Arm Guards, Adjusted (M)", + item_id="frontier_arm_adj_m", slot=ArmorSlot.LEFT_ARM, set_name="Frontier", decay_per_hp=frontier_economy, @@ -1225,8 +1246,8 @@ def create_frontier_set() -> ArmorSet: weight=Decimal("0.4"), ), ArmorSlot.RIGHT_ARM: ArmorPiece( - name="Frontier Arm Guards, Adjusted (R)", - item_id="frontier_arm_r_adj", + 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, @@ -1234,8 +1255,8 @@ def create_frontier_set() -> ArmorSet: weight=Decimal("0.4"), ), ArmorSlot.LEFT_HAND: ArmorPiece( - name="Frontier Gloves, Adjusted (L)", - item_id="frontier_gloves_l_adj", + name="Frontier Gloves, Adjusted (M)", + item_id="frontier_gloves_adj_m", slot=ArmorSlot.LEFT_HAND, set_name="Frontier", decay_per_hp=frontier_economy, @@ -1243,22 +1264,24 @@ def create_frontier_set() -> ArmorSet: weight=Decimal("0.3"), ), ArmorSlot.RIGHT_HAND: ArmorPiece( - name="Frontier Gloves, Adjusted (R)", - item_id="frontier_gloves_r_adj", + 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 Guards, Adjusted", - item_id="frontier_legs_adj", + 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("7"), cut=Decimal("5"), stab=Decimal("5")), - weight=Decimal("1.1"), + protection=ProtectionProfile(impact=Decimal("8"), cut=Decimal("6"), stab=Decimal("6")), + weight=Decimal("1.2"), ), } return ArmorSet(