Lemontropia-Suite/core/loadout_db.py

344 lines
11 KiB
Python

# Description: Loadout database operations for Lemontropia Suite
# Manages saving, loading, and retrieving complete gear configurations
# Standards: Python 3.11+, type hints, Decimal precision for PED/PEC
import json
import logging
from decimal import Decimal
from typing import Optional, List, Dict, Any
from dataclasses import asdict
from core.database import DatabaseManager
logger = logging.getLogger(__name__)
class LoadoutDatabase:
"""Database operations for loadout management."""
def __init__(self, db_manager: Optional[DatabaseManager] = None):
"""Initialize with database manager."""
self.db = db_manager or DatabaseManager()
def save_loadout(self, name: str, config: 'LoadoutConfig', description: str = "") -> int:
"""
Save a loadout configuration to database.
Args:
name: Unique loadout name
config: LoadoutConfig object
description: Optional description
Returns:
Loadout ID
"""
conn = self.db.get_connection()
# Convert config to JSON-serializable dict
plates_json = json.dumps(config.armor_plates) if config.armor_plates else "{}"
enhancers_json = json.dumps(
{str(k): {"name": v.name, "decay": float(v.decay)}
for k, v in config.enhancers.items()}
) if config.enhancers else "{}"
accessories_json = json.dumps({
"left_ring": config.left_ring,
"right_ring": config.right_ring,
"clothing": config.clothing_items,
"pet": config.pet
})
cursor = conn.execute("""
INSERT OR REPLACE INTO loadouts (
name, description, updated_at,
weapon_name, weapon_damage, weapon_decay_pec, weapon_ammo_pec,
weapon_dpp, weapon_efficiency,
amplifier_name, amplifier_decay_pec,
scope_name, scope_decay_pec,
absorber_name, absorber_decay_pec,
armor_name, armor_decay_per_hp,
plates_json,
healing_tool_name, healing_decay_pec, healing_amount,
mindforce_implant_name, mindforce_decay_pec,
left_ring, right_ring, pet_name, accessories_json,
enhancers_json,
cost_per_shot_ped, cost_per_hit_ped, cost_per_heal_ped
) VALUES (
?, ?, CURRENT_TIMESTAMP,
?, ?, ?, ?, ?, ?,
?, ?,
?, ?,
?, ?,
?, ?,
?,
?, ?, ?,
?, ?,
?, ?, ?, ?,
?,
?, ?, ?
)
""", (
name, description,
config.weapon_name,
float(config.weapon_damage),
float(config.weapon_decay_pec),
float(config.weapon_ammo_pec),
float(config.weapon_dpp),
float(config.weapon_efficiency),
config.weapon_amplifier.name if config.weapon_amplifier else None,
float(config.weapon_amplifier.decay_pec) if config.weapon_amplifier else 0.0,
config.weapon_scope.name if config.weapon_scope else None,
float(config.weapon_scope.decay_pec) if config.weapon_scope else 0.0,
config.weapon_absorber.name if config.weapon_absorber else None,
float(config.weapon_absorber.decay_pec) if config.weapon_absorber else 0.0,
config.armor_name,
float(config.armor_decay_per_hp),
plates_json,
config.heal_name,
float(config.heal_cost_pec),
float(config.heal_amount),
config.mindforce_implant,
float(config.mindforce_decay_pec),
config.left_ring,
config.right_ring,
config.pet,
accessories_json,
enhancers_json,
float(config.get_cost_per_shot()),
float(config.get_cost_per_hit()),
float(config.get_cost_per_heal())
))
conn.commit()
loadout_id = cursor.lastrowid
logger.info(f"Saved loadout '{name}' (ID: {loadout_id})")
return loadout_id
def get_loadout(self, name: str) -> Optional[Dict[str, Any]]:
"""
Retrieve a loadout by name.
Args:
name: Loadout name
Returns:
Loadout data dict or None
"""
conn = self.db.get_connection()
cursor = conn.execute(
"SELECT * FROM loadouts WHERE name = ?",
(name,)
)
row = cursor.fetchone()
if row:
return dict(row)
return None
def list_loadouts(self) -> List[Dict[str, Any]]:
"""
List all saved loadouts.
Returns:
List of loadout dicts
"""
conn = self.db.get_connection()
cursor = conn.execute(
"""SELECT id, name, description, weapon_name, armor_name,
cost_per_shot_ped, cost_per_hit_ped, cost_per_heal_ped,
is_active, created_at, updated_at
FROM loadouts ORDER BY updated_at DESC"""
)
return [dict(row) for row in cursor.fetchall()]
def set_active_loadout(self, loadout_id: int) -> bool:
"""
Set a loadout as the active one.
Args:
loadout_id: Loadout ID to activate
Returns:
True if successful
"""
conn = self.db.get_connection()
# Clear all active flags first
conn.execute("UPDATE loadouts SET is_active = 0")
# Set new active
cursor = conn.execute(
"UPDATE loadouts SET is_active = 1 WHERE id = ?",
(loadout_id,)
)
conn.commit()
if cursor.rowcount > 0:
logger.info(f"Set loadout {loadout_id} as active")
return True
return False
def get_active_loadout(self) -> Optional[Dict[str, Any]]:
"""
Get the currently active loadout.
Returns:
Active loadout dict or None
"""
conn = self.db.get_connection()
cursor = conn.execute(
"SELECT * FROM loadouts WHERE is_active = 1 LIMIT 1"
)
row = cursor.fetchone()
if row:
return dict(row)
return None
def delete_loadout(self, name: str) -> bool:
"""
Delete a loadout by name.
Args:
name: Loadout name to delete
Returns:
True if deleted
"""
conn = self.db.get_connection()
cursor = conn.execute(
"DELETE FROM loadouts WHERE name = ?",
(name,)
)
conn.commit()
if cursor.rowcount > 0:
logger.info(f"Deleted loadout '{name}'")
return True
return False
def link_loadout_to_session(self, session_id: int, loadout_id: int) -> bool:
"""
Link a loadout to a hunting session.
Args:
session_id: Hunting session ID
loadout_id: Loadout ID
Returns:
True if successful
"""
conn = self.db.get_connection()
# Get loadout data
loadout = conn.execute(
"SELECT * FROM loadouts WHERE id = ?",
(loadout_id,)
).fetchone()
if not loadout:
logger.error(f"Loadout {loadout_id} not found")
return False
# Update hunting session with loadout reference and snapshot
conn.execute("""
UPDATE hunting_sessions
SET loadout_id = ?,
weapon_name = ?,
armor_name = ?,
fap_name = ?
WHERE session_id = ?
""", (
loadout_id,
loadout['weapon_name'],
loadout['armor_name'],
loadout['healing_tool_name'],
session_id
))
conn.commit()
logger.info(f"Linked loadout {loadout_id} to session {session_id}")
return True
def update_session_costs(self, session_id: int,
shots: int = 0,
hits: int = 0,
heals: int = 0) -> Dict[str, Decimal]:
"""
Calculate and update costs for a hunting session based on loadout.
Args:
session_id: Hunting session ID
shots: Number of shots fired
hits: Number of hits taken
heals: Number of heals used
Returns:
Dict with cost breakdown
"""
conn = self.db.get_connection()
# Get session with linked loadout
session = conn.execute("""
SELECT hs.*, l.*
FROM hunting_sessions hs
LEFT JOIN loadouts l ON hs.loadout_id = l.id
WHERE hs.session_id = ?
""", (session_id,)).fetchone()
if not session:
logger.error(f"Session {session_id} not found")
return {}
# Calculate costs
weapon_cost = Decimal(str(session['cost_per_shot_ped'] or 0)) * shots
armor_cost = Decimal(str(session['cost_per_hit_ped'] or 0)) * hits
healing_cost = Decimal(str(session['cost_per_heal_ped'] or 0)) * heals
# Add enhancer costs (if applicable)
enhancer_cost = Decimal("0")
if session['enhancers_json']:
try:
enhancers = json.loads(session['enhancers_json'])
for tier, enh in enhancers.items():
enhancer_cost += Decimal(str(enh.get('decay', 0))) * shots
except json.JSONDecodeError:
pass
# Add mindforce costs
mindforce_cost = Decimal(str(session['mindforce_decay_pec'] or 0)) * heals * Decimal("0.01")
total_cost = weapon_cost + armor_cost + healing_cost + enhancer_cost + mindforce_cost
# Update session
conn.execute("""
UPDATE hunting_sessions
SET weapon_cost_ped = ?,
armor_cost_ped = ?,
healing_cost_ped = ?,
enhancer_cost_ped = ?,
mindforce_cost_ped = ?,
total_cost_ped = ?,
shots_fired = shots_fired + ?,
hits_taken = hits_taken + ?,
heals_used = heals_used + ?
WHERE session_id = ?
""", (
float(weapon_cost), float(armor_cost), float(healing_cost),
float(enhancer_cost), float(mindforce_cost), float(total_cost),
shots, hits, heals,
session_id
))
conn.commit()
costs = {
'weapon': weapon_cost,
'armor': armor_cost,
'healing': healing_cost,
'enhancer': enhancer_cost,
'mindforce': mindforce_cost,
'total': total_cost
}
logger.debug(f"Updated session {session_id} costs: {costs}")
return costs