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
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:
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():
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:
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
def get_coverage(self) -> Tuple[int, int]:
@ -561,13 +571,23 @@ def calculate_hit_protection(
hit_location: Optional[ArmorSlot] = None
) -> HitResult:
"""
Calculate damage absorption for a hit.
Calculate damage absorption for a hit using Loot 2.0 mechanics.
In Entropia Universe:
1. If hit_location specified, only that slot's protection applies
2. Plates absorb damage FIRST (shield layer)
3. Armor absorbs remaining damage
4. Decay is calculated based on damage absorbed
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
@ -583,6 +603,9 @@ def calculate_hit_protection(
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
@ -592,25 +615,36 @@ def calculate_hit_protection(
result.damage_to_avatar = incoming_damage
return result
plate_prot = piece.attached_plate.protection.get_effective_against(damage_type) if piece.attached_plate else Decimal("0")
# 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
# 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
# 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
# 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_hit(plate_absorb)
result.plate_decay = piece.attached_plate.get_decay_for_damage(plate_absorb)
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
@ -628,7 +662,7 @@ def calculate_hit_protection(
for piece in pieces.values():
total_armor_prot += piece.protection.get_effective_against(damage_type)
if piece.attached_plate:
total_plate_prot += piece.attached_plate.protection.get_effective_against(damage_type)
total_plate_prot += piece.attached_plate.get_effective_protection(damage_type)
# Plate absorbs FIRST
plate_absorb = min(total_plate_prot, incoming_damage)
@ -640,25 +674,26 @@ def calculate_hit_protection(
result.armor_absorbed = armor_absorb
result.damage_to_avatar = remaining - armor_absorb
# Distribute decay across all pieces that contributed
piece_count = len(pieces)
if piece_count > 0:
plate_decay_per = result.plate_decay / Decimal(piece_count) if plate_absorb > 0 else Decimal("0")
armor_decay_per = result.armor_decay / Decimal(piece_count) if armor_absorb > 0 else Decimal("0")
# Sum actual decay from all pieces with plates/armor
total_plate_decay = Decimal("0")
total_armor_decay = Decimal("0")
# 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 and plate_absorb > 0:
total_plate_decay += piece.attached_plate.get_decay_for_hit(plate_absorb / piece_count)
if armor_absorb > 0:
total_armor_decay += piece.get_decay_for_hit(armor_absorb / piece_count)
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)
result.plate_decay = total_plate_decay
result.armor_decay = total_armor_decay
result.total_decay = total_plate_decay + total_armor_decay
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
@ -669,13 +704,16 @@ 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")
pieces = {
ArmorSlot.HEAD: ArmorPiece(
name="Ghost Helmet",
item_id="ghost_helmet",
slot=ArmorSlot.HEAD,
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")),
weight=Decimal("0.3"),
),
@ -684,7 +722,7 @@ def create_ghost_set() -> ArmorSet:
item_id="ghost_harness",
slot=ArmorSlot.CHEST,
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")),
weight=Decimal("0.7"),
),
@ -693,7 +731,7 @@ def create_ghost_set() -> ArmorSet:
item_id="ghost_arm_l",
slot=ArmorSlot.LEFT_ARM,
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")),
weight=Decimal("0.3"),
),
@ -702,7 +740,7 @@ def create_ghost_set() -> ArmorSet:
item_id="ghost_arm_r",
slot=ArmorSlot.RIGHT_ARM,
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")),
weight=Decimal("0.3"),
),
@ -711,7 +749,7 @@ def create_ghost_set() -> ArmorSet:
item_id="ghost_gloves_l",
slot=ArmorSlot.LEFT_HAND,
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")),
weight=Decimal("0.2"),
),
@ -720,7 +758,7 @@ def create_ghost_set() -> ArmorSet:
item_id="ghost_gloves_r",
slot=ArmorSlot.RIGHT_HAND,
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")),
weight=Decimal("0.2"),
),
@ -729,7 +767,7 @@ def create_ghost_set() -> ArmorSet:
item_id="ghost_legs",
slot=ArmorSlot.LEGS,
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")),
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)")