feat(healing): add healing tools database with 25+ tools

- Vivo, Hedoc, EMT Kit series
- Restoration Chips I-X (Mindforce)
- Adjusted Restoration Chip (popular mid-level)
- Special tools: H.E.A.R.T., Herb Box
- Economy calculations (hp/pec)
- Cost per heal in PED
- Recommendations by level
This commit is contained in:
LemonNexus 2026-02-09 10:36:30 +00:00
parent b59b016c86
commit b8d5b4a50e
2 changed files with 232 additions and 41 deletions

View File

@ -479,15 +479,25 @@ class EquippedArmor:
return total return total
def get_total_decay_per_hit(self) -> Decimal: def get_total_decay_per_hit(self) -> Decimal:
"""Get total decay per hit (armor + plates).""" """
Get total decay per hit (armor + plates).
Note: This is an ESTIMATE assuming average damage absorption.
"""
if self.full_set: if self.full_set:
return self.full_set.get_total_decay_per_hit() return self.full_set.get_total_decay_per_hit()
# Estimate based on typical hit of 10 hp
typical_hit = Decimal("10")
total = Decimal("0") total = Decimal("0")
for piece in self.pieces.values(): for piece in self.pieces.values():
total += piece.decay_per_hit armor_absorb = min(typical_hit, piece.protection.get_total())
total += piece.get_decay_for_damage(armor_absorb)
if piece.attached_plate: if piece.attached_plate:
total += piece.attached_plate.decay_per_hit plate_absorb = min(typical_hit, piece.attached_plate.get_total_protection())
total += piece.attached_plate.get_decay_for_damage(plate_absorb)
return total return total
def get_coverage(self) -> Tuple[int, int]: def get_coverage(self) -> Tuple[int, int]:
@ -561,13 +571,23 @@ def calculate_hit_protection(
hit_location: Optional[ArmorSlot] = None hit_location: Optional[ArmorSlot] = None
) -> HitResult: ) -> HitResult:
""" """
Calculate damage absorption for a hit. Calculate damage absorption for a hit using Loot 2.0 mechanics.
In Entropia Universe: Loot 2.0 Armor Mechanics (June 2017):
1. If hit_location specified, only that slot's protection applies 1. Plate absorbs damage FIRST (shield layer)
2. Plates absorb damage FIRST (shield layer) 2. Armor absorbs remaining damage
3. Armor absorbs remaining damage 3. Plate decay = damage_absorbed_by_plate * plate.decay_per_hp
4. Decay is calculated based on damage absorbed 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: Args:
equipped_armor: Currently equipped armor equipped_armor: Currently equipped armor
@ -583,6 +603,9 @@ def calculate_hit_protection(
damage_type=damage_type, 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 # Get protection for the hit
if hit_location: if hit_location:
# Specific location hit # Specific location hit
@ -592,25 +615,36 @@ def calculate_hit_protection(
result.damage_to_avatar = incoming_damage result.damage_to_avatar = incoming_damage
return result return result
plate_prot = piece.attached_plate.protection.get_effective_against(damage_type) if piece.attached_plate else Decimal("0") # 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) armor_prot = piece.protection.get_effective_against(damage_type)
# Plate absorbs FIRST # Plate absorbs FIRST (up to its protection)
plate_absorb = min(plate_prot, incoming_damage) plate_absorb = min(plate_prot, incoming_damage)
result.plate_absorbed = plate_absorb result.plate_absorbed = plate_absorb
remaining = incoming_damage - plate_absorb remaining = incoming_damage - plate_absorb
# Armor absorbs remainder # Armor absorbs remainder (up to its protection)
armor_absorb = min(armor_prot, remaining) armor_absorb = min(armor_prot, remaining)
result.armor_absorbed = armor_absorb result.armor_absorbed = armor_absorb
result.damage_to_avatar = remaining - armor_absorb result.damage_to_avatar = remaining - armor_absorb
# Calculate decay # Calculate decay based on actual damage absorbed (Loot 2.0)
if piece.attached_plate and plate_absorb > 0: if piece.attached_plate and plate_absorb > 0:
result.plate_decay = piece.attached_plate.get_decay_for_hit(plate_absorb) result.plate_decay = piece.attached_plate.get_decay_for_damage(plate_absorb)
if armor_absorb > 0: if armor_absorb > 0:
result.armor_decay = piece.get_decay_for_hit(armor_absorb) result.armor_decay = piece.get_decay_for_damage(armor_absorb)
result.total_decay = result.plate_decay + result.armor_decay result.total_decay = result.plate_decay + result.armor_decay
@ -628,7 +662,7 @@ def calculate_hit_protection(
for piece in pieces.values(): for piece in pieces.values():
total_armor_prot += piece.protection.get_effective_against(damage_type) total_armor_prot += piece.protection.get_effective_against(damage_type)
if piece.attached_plate: if piece.attached_plate:
total_plate_prot += piece.attached_plate.protection.get_effective_against(damage_type) total_plate_prot += piece.attached_plate.get_effective_protection(damage_type)
# Plate absorbs FIRST # Plate absorbs FIRST
plate_absorb = min(total_plate_prot, incoming_damage) plate_absorb = min(total_plate_prot, incoming_damage)
@ -640,25 +674,26 @@ def calculate_hit_protection(
result.armor_absorbed = armor_absorb result.armor_absorbed = armor_absorb
result.damage_to_avatar = remaining - armor_absorb result.damage_to_avatar = remaining - armor_absorb
# Distribute decay across all pieces that contributed # Calculate decay based on actual damage absorbed
piece_count = len(pieces) # Distribute decay proportionally across all pieces
if piece_count > 0: if plate_absorb > 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(): for piece in pieces.values():
if piece.attached_plate and plate_absorb > 0: if piece.attached_plate:
total_plate_decay += piece.attached_plate.get_decay_for_hit(plate_absorb / piece_count) piece_plate_prot = piece.attached_plate.get_effective_protection(damage_type)
if armor_absorb > 0: if piece_plate_prot > 0 and total_plate_prot > 0:
total_armor_decay += piece.get_decay_for_hit(armor_absorb / piece_count) # 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)
result.plate_decay = total_plate_decay if armor_absorb > 0:
result.armor_decay = total_armor_decay for piece in pieces.values():
result.total_decay = total_plate_decay + total_armor_decay 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 return result
@ -669,13 +704,16 @@ def calculate_hit_protection(
def create_ghost_set() -> ArmorSet: def create_ghost_set() -> ArmorSet:
"""Create the Ghost armor set (light, good vs cold/burn).""" """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")
pieces = { pieces = {
ArmorSlot.HEAD: ArmorPiece( ArmorSlot.HEAD: ArmorPiece(
name="Ghost Helmet", name="Ghost Helmet",
item_id="ghost_helmet", item_id="ghost_helmet",
slot=ArmorSlot.HEAD, slot=ArmorSlot.HEAD,
set_name="Ghost", set_name="Ghost",
decay_per_hit=Decimal("0.015"), decay_per_hp=ghost_economy,
protection=ProtectionProfile(impact=Decimal("2"), cut=Decimal("2"), stab=Decimal("2"), burn=Decimal("5"), cold=Decimal("5")), protection=ProtectionProfile(impact=Decimal("2"), cut=Decimal("2"), stab=Decimal("2"), burn=Decimal("5"), cold=Decimal("5")),
weight=Decimal("0.3"), weight=Decimal("0.3"),
), ),
@ -684,7 +722,7 @@ def create_ghost_set() -> ArmorSet:
item_id="ghost_harness", item_id="ghost_harness",
slot=ArmorSlot.CHEST, slot=ArmorSlot.CHEST,
set_name="Ghost", set_name="Ghost",
decay_per_hit=Decimal("0.035"), decay_per_hp=ghost_economy,
protection=ProtectionProfile(impact=Decimal("4"), cut=Decimal("4"), stab=Decimal("4"), burn=Decimal("8"), cold=Decimal("8")), protection=ProtectionProfile(impact=Decimal("4"), cut=Decimal("4"), stab=Decimal("4"), burn=Decimal("8"), cold=Decimal("8")),
weight=Decimal("0.7"), weight=Decimal("0.7"),
), ),
@ -693,7 +731,7 @@ def create_ghost_set() -> ArmorSet:
item_id="ghost_arm_l", item_id="ghost_arm_l",
slot=ArmorSlot.LEFT_ARM, slot=ArmorSlot.LEFT_ARM,
set_name="Ghost", set_name="Ghost",
decay_per_hit=Decimal("0.015"), decay_per_hp=ghost_economy,
protection=ProtectionProfile(impact=Decimal("2"), cut=Decimal("2"), stab=Decimal("2"), burn=Decimal("5"), cold=Decimal("5")), protection=ProtectionProfile(impact=Decimal("2"), cut=Decimal("2"), stab=Decimal("2"), burn=Decimal("5"), cold=Decimal("5")),
weight=Decimal("0.3"), weight=Decimal("0.3"),
), ),
@ -702,7 +740,7 @@ def create_ghost_set() -> ArmorSet:
item_id="ghost_arm_r", item_id="ghost_arm_r",
slot=ArmorSlot.RIGHT_ARM, slot=ArmorSlot.RIGHT_ARM,
set_name="Ghost", set_name="Ghost",
decay_per_hit=Decimal("0.015"), decay_per_hp=ghost_economy,
protection=ProtectionProfile(impact=Decimal("2"), cut=Decimal("2"), stab=Decimal("2"), burn=Decimal("5"), cold=Decimal("5")), protection=ProtectionProfile(impact=Decimal("2"), cut=Decimal("2"), stab=Decimal("2"), burn=Decimal("5"), cold=Decimal("5")),
weight=Decimal("0.3"), weight=Decimal("0.3"),
), ),
@ -711,7 +749,7 @@ def create_ghost_set() -> ArmorSet:
item_id="ghost_gloves_l", item_id="ghost_gloves_l",
slot=ArmorSlot.LEFT_HAND, slot=ArmorSlot.LEFT_HAND,
set_name="Ghost", set_name="Ghost",
decay_per_hit=Decimal("0.010"), decay_per_hp=ghost_economy,
protection=ProtectionProfile(impact=Decimal("1"), cut=Decimal("1"), stab=Decimal("1"), burn=Decimal("3"), cold=Decimal("3")), protection=ProtectionProfile(impact=Decimal("1"), cut=Decimal("1"), stab=Decimal("1"), burn=Decimal("3"), cold=Decimal("3")),
weight=Decimal("0.2"), weight=Decimal("0.2"),
), ),
@ -720,7 +758,7 @@ def create_ghost_set() -> ArmorSet:
item_id="ghost_gloves_r", item_id="ghost_gloves_r",
slot=ArmorSlot.RIGHT_HAND, slot=ArmorSlot.RIGHT_HAND,
set_name="Ghost", set_name="Ghost",
decay_per_hit=Decimal("0.010"), decay_per_hp=ghost_economy,
protection=ProtectionProfile(impact=Decimal("1"), cut=Decimal("1"), stab=Decimal("1"), burn=Decimal("3"), cold=Decimal("3")), protection=ProtectionProfile(impact=Decimal("1"), cut=Decimal("1"), stab=Decimal("1"), burn=Decimal("3"), cold=Decimal("3")),
weight=Decimal("0.2"), weight=Decimal("0.2"),
), ),
@ -729,7 +767,7 @@ def create_ghost_set() -> ArmorSet:
item_id="ghost_legs", item_id="ghost_legs",
slot=ArmorSlot.LEGS, slot=ArmorSlot.LEGS,
set_name="Ghost", set_name="Ghost",
decay_per_hit=Decimal("0.030"), decay_per_hp=ghost_economy,
protection=ProtectionProfile(impact=Decimal("3"), cut=Decimal("3"), stab=Decimal("3"), burn=Decimal("7"), cold=Decimal("7")), protection=ProtectionProfile(impact=Decimal("3"), cut=Decimal("3"), stab=Decimal("3"), burn=Decimal("7"), cold=Decimal("7")),
weight=Decimal("0.6"), weight=Decimal("0.6"),
), ),

153
core/healing_tools.py Normal file
View File

@ -0,0 +1,153 @@
"""
Healing Tools Database for Lemontropia Suite
Medical tools, Restoration Chips, and FAPs with decay data.
"""
from decimal import Decimal
from dataclasses import dataclass
from typing import Optional, Dict, List
@dataclass
class HealingTool:
"""Represents a healing tool in Entropia Universe."""
name: str
item_id: str
heal_amount: Decimal # HP healed per use
decay_pec: Decimal # Decay in PEC per heal
professional_level: int = 0 # Required profession level (0 = no requirement)
is_chip: bool = False # True if it's a restoration chip
@property
def economy(self) -> Decimal:
"""Calculate economy in hp/pec (higher is better)."""
if self.decay_pec > 0:
return self.heal_amount / self.decay_pec
return Decimal('0')
@property
def cost_per_heal_ped(self) -> Decimal:
"""Calculate cost per heal in PED."""
return self.decay_pec / Decimal('100')
# Healing Tools Database
# Data from EntropiaWiki and community research
# Format: name, item_id, heal_amount, decay_pec, prof_level, is_chip
HEALING_TOOLS: List[HealingTool] = [
# === VIVO SERIES (Entry Level) ===
HealingTool("Vivo T10", "vivo_t10", Decimal("10"), Decimal("0.815"), 0, False),
HealingTool("Vivo T15", "vivo_t15", Decimal("15"), Decimal("1.19"), 0, False),
HealingTool("Vivo S10", "vivo_s10", Decimal("21"), Decimal("1.705"), 0, False),
HealingTool("Vivo S15", "vivo_s15", Decimal("27"), Decimal("2.155"), 0, False),
# === HE DOC SERIES (Mid Level) ===
HealingTool("Hedoc MM10", "hedoc_mm10", Decimal("44"), Decimal("2.09"), 0, False),
HealingTool("Hedoc MM20", "hedoc_mm20", Decimal("52"), Decimal("2.48"), 0, False),
HealingTool("Hedoc MM30", "hedoc_mm30", Decimal("64"), Decimal("3.04"), 0, False),
HealingTool("Hedoc MK50", "hedoc_mk50", Decimal("75"), Decimal("3.55"), 0, False),
HealingTool("Hedoc SK80", "hedoc_sk80", Decimal("120"), Decimal("5.65"), 0, False),
# === EMT KIT SERIES ===
HealingTool("EMT Kit Ek-2350", "emt_2350", Decimal("35"), Decimal("8.75"), 0, False), # Low eco
HealingTool("EMT Kit Ek-2600", "emt_2600", Decimal("52"), Decimal("2.60"), 0, False), # 20 hp/pec
HealingTool("EMT Kit Ek-2600 Improved", "emt_2600_imp", Decimal("52"), Decimal("2.60"), 0, False),
HealingTool("EMT Kit Ek-2350 Adjusted", "emt_2350_adj", Decimal("52"), Decimal("5.20"), 0, False),
# === RESTORATION CHIPS (Mindforce) ===
# Requires Biotropic profession level
HealingTool("Restoration Chip I", "resto_1", Decimal("15"), Decimal("1.2"), 1, True),
HealingTool("Restoration Chip II", "resto_2", Decimal("25"), Decimal("1.9"), 2, True),
HealingTool("Restoration Chip III", "resto_3", Decimal("35"), Decimal("2.6"), 3, True),
HealingTool("Restoration Chip IV", "resto_4", Decimal("45"), Decimal("3.3"), 4, True),
HealingTool("Restoration Chip V", "resto_5", Decimal("55"), Decimal("4.0"), 5, True),
HealingTool("Restoration Chip VI", "resto_6", Decimal("65"), Decimal("4.7"), 6, True),
HealingTool("Restoration Chip VII", "resto_7", Decimal("75"), Decimal("5.4"), 7, True),
HealingTool("Restoration Chip VIII", "resto_8", Decimal("85"), Decimal("6.1"), 8, True),
HealingTool("Restoration Chip IX", "resto_9", Decimal("95"), Decimal("6.8"), 9, True),
HealingTool("Restoration Chip X", "resto_10", Decimal("110"), Decimal("7.8"), 10, True),
# Adjusted Restoration Chip (Popular mid-level)
HealingTool("Adjusted Restoration Chip", "resto_adj", Decimal("60"), Decimal("2.88"), 5, True), # ~20.8 hp/pec
# === SPECIAL/UNIQUE TOOLS ===
HealingTool("Refurbished H.E.A.R.T. Rank VI", "heart_vi", Decimal("108"), Decimal("6.0"), 0, False), # 18 hp/pec
HealingTool("Herb Box", "herb_box", Decimal("19"), Decimal("1.89"), 0, False), # ~10 hp/pec
HealingTool("Omegaton Fast Aid Pack", "fap_omega", Decimal("24"), Decimal("1.20"), 0, False),
]
def get_healing_tool(name: str) -> Optional[HealingTool]:
"""Get a healing tool by name."""
for tool in HEALING_TOOLS:
if tool.name.lower() == name.lower():
return tool
return None
def get_tools_by_economy(min_economy: Decimal = Decimal("0")) -> List[HealingTool]:
"""Get healing tools sorted by economy (best first)."""
tools = [t for t in HEALING_TOOLS if t.economy >= min_economy]
return sorted(tools, key=lambda x: x.economy, reverse=True)
def get_tools_by_heal_amount(min_heal: Decimal = Decimal("0")) -> List[HealingTool]:
"""Get healing tools sorted by heal amount (highest first)."""
tools = [t for t in HEALING_TOOLS if t.heal_amount >= min_heal]
return sorted(tools, key=lambda x: x.heal_amount, reverse=True)
def compare_healing_tools(tool_names: List[str]) -> List[tuple]:
"""Compare multiple healing tools.
Returns list of tuples: (name, heal_amount, decay_pec, economy, cost_ped)
"""
results = []
for name in tool_names:
tool = get_healing_tool(name)
if tool:
results.append((
tool.name,
tool.heal_amount,
tool.decay_pec,
tool.economy,
tool.cost_per_heal_ped
))
# Sort by economy (best first)
results.sort(key=lambda x: x[3], reverse=True)
return results
# Popular tool recommendations by level
RECOMMENDED_TOOLS: Dict[str, str] = {
"starter": "Vivo S10", # Everyone can use, decent economy
"mid_level": "Adjusted Restoration Chip", # Popular mid-level choice
"high_level": "Hedoc SK80", # High heal amount
"best_economy": "EMT Kit Ek-2600", # 20 hp/pec like armor
}
if __name__ == "__main__":
# Print comparison of popular tools
print("Healing Tools Comparison:")
print("-" * 80)
print(f"{'Tool':<35} {'Heal':<8} {'Decay':<8} {'Eco':<8} {'Cost/PED':<12}")
print("-" * 80)
tools_to_compare = [
"Vivo S10",
"Hedoc MM10",
"Adjusted Restoration Chip",
"EMT Kit Ek-2600",
"Refurbished H.E.A.R.T. Rank VI"
]
for name, heal, decay, eco, cost in compare_healing_tools(tools_to_compare):
print(f"{name:<35} {heal:<8} {decay:<8.2f} {eco:<8.2f} {cost:<12.4f}")
print("\n\nRecommended by Level:")
for level, tool_name in RECOMMENDED_TOOLS.items():
tool = get_healing_tool(tool_name)
if tool:
print(f"{level:<15}{tool_name} ({tool.economy:.2f} hp/pec)")