feat: add gear management + Windows testing guide
- Fix terminology: Projects (activities) vs Sessions (gameplay) - Add EntropiaNexusAPI integration for weapon/armor/tool stats - Implement GearLoadout class for equipment management - Calculate hunting/mining costs per hour - Add DPP (Damage Per PEC) efficiency tracking - Create comprehensive WINDOWS_TESTING_GUIDE.md - Document live game testing scenarios - Explain ROI calculations and data model Ready for Windows PC live testing with game client.
This commit is contained in:
parent
a516ff4d0c
commit
dfe4e8125f
|
|
@ -0,0 +1,292 @@
|
||||||
|
# Lemontropia Suite — Windows PC Testing Guide
|
||||||
|
|
||||||
|
**Version:** v0.1.0-alpha
|
||||||
|
**Platform:** Windows 10/11
|
||||||
|
**Status:** Ready for Live Game Testing
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 What This Tool Does
|
||||||
|
|
||||||
|
**Lemontropia Suite** is a professional analytics platform for **Entropia Universe** players. It tracks your hunting, mining, and crafting activities with precision.
|
||||||
|
|
||||||
|
### Core Capabilities
|
||||||
|
|
||||||
|
| Feature | Description | Benefit |
|
||||||
|
|---------|-------------|---------|
|
||||||
|
| **Real-Time Loot Tracking** | Parses `chat.log` live | Instant ROI calculation |
|
||||||
|
| **Global/HoF Detection** | Auto-screenshot on >50 PED | Never miss big wins |
|
||||||
|
| **Skill Gain Logging** | Tracks all skill advances | Progress monitoring |
|
||||||
|
| **Weapon Decay Calc** | Precise PEC tracking | True cost analysis |
|
||||||
|
| **Project Management** | Hunt/Mine/Craft projects | Organized data |
|
||||||
|
| **Session Analytics** | Per-session ROI & profit | Performance metrics |
|
||||||
|
| **Gear Integration** | Entropia Nexus API | Automatic weapon stats |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 TERMINOLOGY (Important!)
|
||||||
|
|
||||||
|
Understanding the data model:
|
||||||
|
|
||||||
|
```
|
||||||
|
PROJECT (Activity Definition)
|
||||||
|
├── "Daily Argo Grind" (Hunting project)
|
||||||
|
│ ├── SESSION 1: Morning hunt (45 min, +15 PED)
|
||||||
|
│ ├── SESSION 2: Evening hunt (2 hrs, -5 PED)
|
||||||
|
│ └── SESSION 3: Weekend marathon (5 hrs, +200 PED)
|
||||||
|
│
|
||||||
|
└── Combined stats: Total sessions, average ROI, lifetime profit
|
||||||
|
```
|
||||||
|
|
||||||
|
| Term | Definition | Example |
|
||||||
|
|------|------------|---------|
|
||||||
|
| **PROJECT** | Long-term activity container | "Argo Hunting", "Calypso Mining" |
|
||||||
|
| **SESSION** | Single gameplay instance | One hunting trip |
|
||||||
|
| **RUN** | Complete project lifecycle | Multiple sessions over days |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Windows Setup Instructions
|
||||||
|
|
||||||
|
### Step 1: Install Python
|
||||||
|
|
||||||
|
1. Download Python 3.11+ from [python.org](https://www.python.org/downloads/)
|
||||||
|
2. **IMPORTANT:** Check "Add Python to PATH" during installation
|
||||||
|
3. Verify: Open Command Prompt (`cmd`) and run:
|
||||||
|
```cmd
|
||||||
|
python --version
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Clone/Download the Project
|
||||||
|
|
||||||
|
Option A: Git (if you have it installed)
|
||||||
|
```cmd
|
||||||
|
git clone https://git.lemonlink.eu/impulsivefps/Lemontropia-Suite.git
|
||||||
|
cd Lemontropia-Suite
|
||||||
|
```
|
||||||
|
|
||||||
|
Option B: Download ZIP
|
||||||
|
1. Go to: `https://git.lemonlink.eu/impulsivefps/Lemontropia-Suite`
|
||||||
|
2. Click "Download ZIP"
|
||||||
|
3. Extract to `C:\Lemontropia-Suite\`
|
||||||
|
4. Open Command Prompt in that folder
|
||||||
|
|
||||||
|
### Step 3: Configure Environment
|
||||||
|
|
||||||
|
Create `.env` file in the project folder:
|
||||||
|
|
||||||
|
```env
|
||||||
|
# Windows paths use double backslashes
|
||||||
|
EU_CHAT_LOG_PATH=C:\\Users\\ImpulsiveFPS\\Documents\\Entropia Universe\\chat.log
|
||||||
|
|
||||||
|
# For mock testing (no game needed)
|
||||||
|
USE_MOCK_DATA=false
|
||||||
|
|
||||||
|
# Obsidian integration (optional)
|
||||||
|
OBSIDIAN_API_URL=http://192.168.5.30:27123
|
||||||
|
OBSIDIAN_API_KEY=your_key_here
|
||||||
|
|
||||||
|
# Enable debug output
|
||||||
|
LOG_LEVEL=info
|
||||||
|
```
|
||||||
|
|
||||||
|
**Find your actual log path:**
|
||||||
|
```
|
||||||
|
C:\Users\[YourUsername]\Documents\Entropia Universe\chat.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Run the Application
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
python main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎮 Testing Scenarios
|
||||||
|
|
||||||
|
### Test 1: Live Hunt Tracking (With Game Running)
|
||||||
|
|
||||||
|
**Prerequisites:**
|
||||||
|
- Entropia Universe game running
|
||||||
|
- Character in a safe hunting zone
|
||||||
|
- Weapon equipped
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
1. Start Lemontropia Suite: `python main.py`
|
||||||
|
2. Select `1` → Create New Project
|
||||||
|
3. Name: `Test Hunt` → Type: `1` (Hunting)
|
||||||
|
4. Select `3` → Start New Session
|
||||||
|
5. Choose your `Test Hunt` project
|
||||||
|
6. **In game:** Shoot a creature once
|
||||||
|
7. **Expected:** Lemontropia shows decay/ammo cost
|
||||||
|
8. **In game:** Kill creature, loot
|
||||||
|
9. **Expected:** Lemontropia shows loot items + TT value
|
||||||
|
10. Press `Ctrl+C` to end session
|
||||||
|
11. Select `4` → View Statistics → See ROI
|
||||||
|
|
||||||
|
**Success Criteria:**
|
||||||
|
- [ ] Decay recorded correctly
|
||||||
|
- [ ] Loot items captured
|
||||||
|
- [ ] TT values accurate
|
||||||
|
- [ ] ROI calculation reasonable
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Test 2: Global Detection
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
1. Start a new session
|
||||||
|
2. **In game:** Type `/global` in chat (simulated) OR wait for real global
|
||||||
|
3. **Expected:** Lemontropia shows:
|
||||||
|
```
|
||||||
|
🌍 GLOBAL: PlayerName found 150.00 PED!
|
||||||
|
```
|
||||||
|
4. Check `screenshots/` folder for auto-capture
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Test 3: Skill Gain Tracking
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
1. Start a new session
|
||||||
|
2. **In game:** Hunt until skill gain message appears
|
||||||
|
3. **Expected:** Lemontropia shows:
|
||||||
|
```
|
||||||
|
📈 Skill: Rifle +0.45
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Test 4: Multi-Session Project
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
1. Create project: `Argo Grind Week 1`
|
||||||
|
2. Start Session 1 → Hunt 10 minutes → End
|
||||||
|
3. Start Session 2 → Hunt 10 minutes → End
|
||||||
|
4. Start Session 3 → Hunt 10 minutes → End
|
||||||
|
5. View statistics → Should show:
|
||||||
|
- 3 sessions total
|
||||||
|
- Combined profit/loss
|
||||||
|
- Average ROI across all sessions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Test 5: Gear Loadout (Entropia Nexus Integration)
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
1. In Lemontropia menu, select gear management (when implemented)
|
||||||
|
2. Search for your weapon: `Omegaton M2100`
|
||||||
|
3. **Expected:** Weapon stats auto-populate:
|
||||||
|
- Damage
|
||||||
|
- Decay per shot
|
||||||
|
- Ammo consumption
|
||||||
|
- DPP (Damage Per PEC)
|
||||||
|
4. Calculate cost per hour estimate
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 Troubleshooting
|
||||||
|
|
||||||
|
### Issue: "chat.log not found"
|
||||||
|
**Solution:**
|
||||||
|
1. Verify EU is running and you've logged in at least once
|
||||||
|
2. Check path in `.env` matches your Windows username
|
||||||
|
3. Try running as Administrator (permissions issue)
|
||||||
|
|
||||||
|
### Issue: "Database error"
|
||||||
|
**Solution:**
|
||||||
|
```cmd
|
||||||
|
# Delete database and reinitialize
|
||||||
|
del data\lemontropia.db
|
||||||
|
python main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue: "No events detected"
|
||||||
|
**Solution:**
|
||||||
|
1. Verify `USE_MOCK_DATA=false` in `.env`
|
||||||
|
2. Check that EU client is writing to chat.log (open it in Notepad, should show recent entries)
|
||||||
|
3. Ensure no other log-reading tools are locking the file
|
||||||
|
|
||||||
|
### Issue: "Permission denied"
|
||||||
|
**Solution:**
|
||||||
|
Run Command Prompt as Administrator
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Expected Log Format
|
||||||
|
|
||||||
|
Your `chat.log` should contain lines like:
|
||||||
|
|
||||||
|
```
|
||||||
|
2026-02-08 14:23:15 [System] You received Shrapnel x 50 (Value: 0.50 PED)
|
||||||
|
2026-02-08 14:23:45 [System] You gained 0.12 experience in your Rifle skill
|
||||||
|
2026-02-08 14:24:02 [System] Your Omegaton M2100 has decayed 3 PEC
|
||||||
|
2026-02-08 14:25:30 [System] PlayerName globals in Calypso for 150.00 PED
|
||||||
|
2026-02-08 14:26:10 [System] You killed a Argo Young
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ Development Features (In Progress)
|
||||||
|
|
||||||
|
### Coming Soon
|
||||||
|
|
||||||
|
| Feature | Status | Description |
|
||||||
|
|---------|--------|-------------|
|
||||||
|
| Gear Loadout Manager | 🚧 WIP | Select weapons, get stats from Nexus |
|
||||||
|
| Live HUD Overlay | 📋 Planned | Transparent always-on-top window |
|
||||||
|
| Mining Map | 📋 Planned | Coordinate tracking + heat maps |
|
||||||
|
| Crafting Calculator | 📋 Planned | BP success rate + residue calc |
|
||||||
|
| Auto-Screenshot | ✅ Ready | Triggers on >50 PED |
|
||||||
|
| Data Export | 📋 Planned | CSV/Excel export |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📈 ROI Calculation Explained
|
||||||
|
|
||||||
|
**Formula:**
|
||||||
|
```
|
||||||
|
ROI% = (Total Loot TT / Total Cost) × 100
|
||||||
|
```
|
||||||
|
|
||||||
|
**Total Cost includes:**
|
||||||
|
- Weapon decay (PEC)
|
||||||
|
- Ammo consumed (PEC)
|
||||||
|
- Armor decay (PEC)
|
||||||
|
- Healing costs (if tracked)
|
||||||
|
- Enhancer breaks (if tracked)
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```
|
||||||
|
Session: 1 hour hunting
|
||||||
|
- Cost: 50 PED (ammo + decay)
|
||||||
|
- Loot: 45 PED TT value
|
||||||
|
- ROI: (45 / 50) × 100 = 90% (10% loss)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔐 Security Notes
|
||||||
|
|
||||||
|
- Tool only **READS** from `chat.log` (never writes)
|
||||||
|
- Tool does NOT interact with game memory
|
||||||
|
- Tool does NOT automate gameplay
|
||||||
|
- All data stored locally in SQLite database
|
||||||
|
- No internet connection required (except Nexus API lookups)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 Support
|
||||||
|
|
||||||
|
**Repository:** https://git.lemonlink.eu/impulsivefps/Lemontropia-Suite
|
||||||
|
|
||||||
|
**Issue Reporting:**
|
||||||
|
1. What test were you running?
|
||||||
|
2. What did you expect to happen?
|
||||||
|
3. What actually happened?
|
||||||
|
4. Screenshot of error (if any)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Happy Hunting! 🍋*
|
||||||
|
|
@ -0,0 +1,458 @@
|
||||||
|
# Description: Entropia Nexus API integration for gear/item data
|
||||||
|
# Provides weapon, armor, tool statistics for ROI calculations
|
||||||
|
# API: https://api.entropianexus.com/
|
||||||
|
|
||||||
|
import json
|
||||||
|
import urllib.request
|
||||||
|
import urllib.error
|
||||||
|
from typing import Optional, Dict, List, Any
|
||||||
|
from decimal import Decimal
|
||||||
|
from dataclasses import dataclass
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class WeaponStats:
|
||||||
|
"""Weapon statistics from Entropia Nexus."""
|
||||||
|
name: str
|
||||||
|
damage: Decimal
|
||||||
|
range: int
|
||||||
|
attacks_per_min: int
|
||||||
|
decay_per_shot: Decimal # In PEC
|
||||||
|
ammo_per_shot: Decimal # In PEC
|
||||||
|
total_cost_per_shot: Decimal # decay + ammo
|
||||||
|
dpp: Decimal # Damage Per PEC (efficiency metric)
|
||||||
|
markup_percent: Decimal = Decimal("100.0") # Market value %
|
||||||
|
|
||||||
|
def calculate_cost_per_hour(self) -> Decimal:
|
||||||
|
"""Calculate total cost per hour of use."""
|
||||||
|
shots_per_hour = self.attacks_per_min * 60
|
||||||
|
return (self.total_cost_per_shot * shots_per_hour) / 100 # Convert PEC to PED
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MiningToolStats:
|
||||||
|
"""Mining tool (finder/extractor) statistics."""
|
||||||
|
name: str
|
||||||
|
type: str # 'finder' or 'extractor'
|
||||||
|
depth: int # Finder depth in meters
|
||||||
|
radius: int # Search radius
|
||||||
|
decay_per_use: Decimal # In PEC
|
||||||
|
probe_cost: Decimal = Decimal("0.5") # Standard probe cost in PED
|
||||||
|
|
||||||
|
def calculate_cost_per_drop(self) -> Decimal:
|
||||||
|
"""Calculate total cost per mining drop."""
|
||||||
|
return (self.decay_per_use / 100) + self.probe_cost # PEC to PED + probe
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ArmorStats:
|
||||||
|
"""Armor piece statistics."""
|
||||||
|
name: str
|
||||||
|
slot: str # 'head', 'body', 'arms', 'legs', 'feet'
|
||||||
|
protection: Dict[str, int] # {damage_type: protection_value}
|
||||||
|
decay_per_hit: Decimal # In PEC
|
||||||
|
durability: int # Durability points
|
||||||
|
|
||||||
|
|
||||||
|
class EntropiaNexusAPI:
|
||||||
|
"""
|
||||||
|
Client for Entropia Nexus API.
|
||||||
|
|
||||||
|
Entropia Nexus provides comprehensive item database including:
|
||||||
|
- Weapon stats (damage, decay, DPP)
|
||||||
|
- Armor protection values
|
||||||
|
- Mining tool specifications
|
||||||
|
- Blueprint data
|
||||||
|
- Market markup information
|
||||||
|
|
||||||
|
API Base URL: https://api.entropianexus.com/
|
||||||
|
"""
|
||||||
|
|
||||||
|
BASE_URL = "https://api.entropianexus.com"
|
||||||
|
|
||||||
|
def __init__(self, api_key: Optional[str] = None):
|
||||||
|
"""
|
||||||
|
Initialize API client.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
api_key: Optional API key for higher rate limits
|
||||||
|
"""
|
||||||
|
self.api_key = api_key
|
||||||
|
self._cache: Dict[str, Any] = {} # Simple in-memory cache
|
||||||
|
|
||||||
|
logger.info("EntropiaNexusAPI initialized")
|
||||||
|
|
||||||
|
def _make_request(self, endpoint: str, params: Optional[Dict] = None) -> Optional[Dict]:
|
||||||
|
"""
|
||||||
|
Make API request to Entropia Nexus.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
endpoint: API endpoint path
|
||||||
|
params: Query parameters
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON response as dict, or None on error
|
||||||
|
"""
|
||||||
|
url = f"{self.BASE_URL}/{endpoint}"
|
||||||
|
|
||||||
|
if params:
|
||||||
|
query_string = '&'.join(f"{k}={v}" for k, v in params.items())
|
||||||
|
url = f"{url}?{query_string}"
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'User-Agent': 'Lemontropia-Suite/0.1.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.api_key:
|
||||||
|
headers['Authorization'] = f'Bearer {self.api_key}'
|
||||||
|
|
||||||
|
try:
|
||||||
|
req = urllib.request.Request(url, headers=headers)
|
||||||
|
with urllib.request.urlopen(req, timeout=10) as response:
|
||||||
|
data = json.loads(response.read().decode('utf-8'))
|
||||||
|
return data
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
logger.error(f"HTTP Error {e.code}: {e.reason}")
|
||||||
|
return None
|
||||||
|
except urllib.error.URLError as e:
|
||||||
|
logger.error(f"URL Error: {e.reason}")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"API request failed: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# ========================================================================
|
||||||
|
# WEAPON ENDPOINTS
|
||||||
|
# ========================================================================
|
||||||
|
|
||||||
|
def search_weapons(self, query: str, weapon_type: Optional[str] = None) -> List[Dict]:
|
||||||
|
"""
|
||||||
|
Search for weapons by name.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
query: Weapon name search term
|
||||||
|
weapon_type: Optional filter (e.g., 'rifle', 'pistol', 'sword')
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of weapon data dicts
|
||||||
|
"""
|
||||||
|
params = {'q': query}
|
||||||
|
if weapon_type:
|
||||||
|
params['type'] = weapon_type
|
||||||
|
|
||||||
|
data = self._make_request('v1/weapons/search', params)
|
||||||
|
return data.get('results', []) if data else []
|
||||||
|
|
||||||
|
def get_weapon_details(self, weapon_name: str) -> Optional[WeaponStats]:
|
||||||
|
"""
|
||||||
|
Get detailed weapon statistics.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
weapon_name: Exact weapon name
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
WeaponStats object or None
|
||||||
|
"""
|
||||||
|
# Check cache first
|
||||||
|
cache_key = f"weapon:{weapon_name}"
|
||||||
|
if cache_key in self._cache:
|
||||||
|
return self._cache[cache_key]
|
||||||
|
|
||||||
|
data = self._make_request(f'v1/weapons/{urllib.parse.quote(weapon_name)}')
|
||||||
|
|
||||||
|
if not data:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
weapon = WeaponStats(
|
||||||
|
name=data['name'],
|
||||||
|
damage=Decimal(str(data.get('damage', 0))),
|
||||||
|
range=data.get('range', 0),
|
||||||
|
attacks_per_min=data.get('attacks_per_min', 0),
|
||||||
|
decay_per_shot=Decimal(str(data.get('decay_pec', 0))),
|
||||||
|
ammo_per_shot=Decimal(str(data.get('ammo_pec', 0))),
|
||||||
|
total_cost_per_shot=Decimal(str(data.get('total_cost_pec', 0))),
|
||||||
|
dpp=Decimal(str(data.get('dpp', 0))),
|
||||||
|
markup_percent=Decimal(str(data.get('markup', 100)))
|
||||||
|
)
|
||||||
|
|
||||||
|
# Cache result
|
||||||
|
self._cache[cache_key] = weapon
|
||||||
|
return weapon
|
||||||
|
|
||||||
|
except (KeyError, ValueError) as e:
|
||||||
|
logger.error(f"Failed to parse weapon data: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_weapon_dpp_tiers(self) -> List[Dict]:
|
||||||
|
"""
|
||||||
|
Get weapon DPP (Damage Per PEC) tier list.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of weapons ranked by efficiency
|
||||||
|
"""
|
||||||
|
data = self._make_request('v1/weapons/dpp-tiers')
|
||||||
|
return data.get('tiers', []) if data else []
|
||||||
|
|
||||||
|
# ========================================================================
|
||||||
|
# MINING TOOL ENDPOINTS
|
||||||
|
# ========================================================================
|
||||||
|
|
||||||
|
def search_mining_tools(self, query: str, tool_type: Optional[str] = None) -> List[Dict]:
|
||||||
|
"""
|
||||||
|
Search for mining tools (finders/extractors).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
query: Tool name search term
|
||||||
|
tool_type: 'finder' or 'extractor'
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of tool data dicts
|
||||||
|
"""
|
||||||
|
params = {'q': query}
|
||||||
|
if tool_type:
|
||||||
|
params['type'] = tool_type
|
||||||
|
|
||||||
|
data = self._make_request('v1/mining/tools', params)
|
||||||
|
return data.get('results', []) if data else []
|
||||||
|
|
||||||
|
def get_mining_tool_details(self, tool_name: str) -> Optional[MiningToolStats]:
|
||||||
|
"""
|
||||||
|
Get detailed mining tool statistics.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tool_name: Exact tool name
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
MiningToolStats object or None
|
||||||
|
"""
|
||||||
|
cache_key = f"mining:{tool_name}"
|
||||||
|
if cache_key in self._cache:
|
||||||
|
return self._cache[cache_key]
|
||||||
|
|
||||||
|
data = self._make_request(f'v1/mining/tools/{urllib.parse.quote(tool_name)}')
|
||||||
|
|
||||||
|
if not data:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
tool = MiningToolStats(
|
||||||
|
name=data['name'],
|
||||||
|
type=data.get('type', 'finder'),
|
||||||
|
depth=data.get('depth', 0),
|
||||||
|
radius=data.get('radius', 0),
|
||||||
|
decay_per_use=Decimal(str(data.get('decay_pec', 0))),
|
||||||
|
probe_cost=Decimal(str(data.get('probe_cost', 0.5)))
|
||||||
|
)
|
||||||
|
|
||||||
|
self._cache[cache_key] = tool
|
||||||
|
return tool
|
||||||
|
|
||||||
|
except (KeyError, ValueError) as e:
|
||||||
|
logger.error(f"Failed to parse mining tool data: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# ========================================================================
|
||||||
|
# ARMOR ENDPOINTS
|
||||||
|
# ========================================================================
|
||||||
|
|
||||||
|
def search_armor(self, query: str) -> List[Dict]:
|
||||||
|
"""Search for armor pieces."""
|
||||||
|
data = self._make_request('v1/armor/search', {'q': query})
|
||||||
|
return data.get('results', []) if data else []
|
||||||
|
|
||||||
|
def get_armor_details(self, armor_name: str) -> Optional[ArmorStats]:
|
||||||
|
"""
|
||||||
|
Get detailed armor statistics.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
armor_name: Exact armor name
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ArmorStats object or None
|
||||||
|
"""
|
||||||
|
cache_key = f"armor:{armor_name}"
|
||||||
|
if cache_key in self._cache:
|
||||||
|
return self._cache[cache_key]
|
||||||
|
|
||||||
|
data = self._make_request(f'v1/armor/{urllib.parse.quote(armor_name)}')
|
||||||
|
|
||||||
|
if not data:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
armor = ArmorStats(
|
||||||
|
name=data['name'],
|
||||||
|
slot=data.get('slot', 'body'),
|
||||||
|
protection=data.get('protection', {}),
|
||||||
|
decay_per_hit=Decimal(str(data.get('decay_pec', 0))),
|
||||||
|
durability=data.get('durability', 1000)
|
||||||
|
)
|
||||||
|
|
||||||
|
self._cache[cache_key] = armor
|
||||||
|
return armor
|
||||||
|
|
||||||
|
except (KeyError, ValueError) as e:
|
||||||
|
logger.error(f"Failed to parse armor data: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# ========================================================================
|
||||||
|
# MARKET DATA
|
||||||
|
# ========================================================================
|
||||||
|
|
||||||
|
def get_markup(self, item_name: str) -> Optional[Decimal]:
|
||||||
|
"""
|
||||||
|
Get current market markup percentage for an item.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
item_name: Item name
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Markup percentage (e.g., 105.5 for 105.5%) or None
|
||||||
|
"""
|
||||||
|
data = self._make_request(f'v1/market/markup/{urllib.parse.quote(item_name)}')
|
||||||
|
|
||||||
|
if data and 'markup' in data:
|
||||||
|
return Decimal(str(data['markup']))
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_market_trends(self, item_name: str, days: int = 7) -> List[Dict]:
|
||||||
|
"""
|
||||||
|
Get market price trends over time.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
item_name: Item name
|
||||||
|
days: Number of days of history
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of daily price data
|
||||||
|
"""
|
||||||
|
data = self._make_request(
|
||||||
|
f'v1/market/trends/{urllib.parse.quote(item_name)}',
|
||||||
|
{'days': days}
|
||||||
|
)
|
||||||
|
return data.get('trends', []) if data else []
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# GEAR LOADOUT MANAGER
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
class GearLoadout:
|
||||||
|
"""
|
||||||
|
Manages a player's gear setup for hunting/mining/crafting.
|
||||||
|
|
||||||
|
Tracks equipped items and calculates total operational costs.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, name: str, activity_type: str):
|
||||||
|
"""
|
||||||
|
Initialize gear loadout.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Loadout name (e.g., "Daily Argo Setup")
|
||||||
|
activity_type: 'hunt', 'mine', or 'craft'
|
||||||
|
"""
|
||||||
|
self.name = name
|
||||||
|
self.activity_type = activity_type
|
||||||
|
|
||||||
|
# Hunting gear
|
||||||
|
self.weapon: Optional[WeaponStats] = None
|
||||||
|
self.amplifier: Optional[WeaponStats] = None # Weapon amp
|
||||||
|
self.scope: Optional[Any] = None
|
||||||
|
self.sight: Optional[Any] = None
|
||||||
|
|
||||||
|
# Armor set
|
||||||
|
self.armor: Dict[str, ArmorStats] = {} # slot -> ArmorStats
|
||||||
|
|
||||||
|
# Mining gear
|
||||||
|
self.finder: Optional[MiningToolStats] = None
|
||||||
|
self.extractor: Optional[MiningToolStats] = None
|
||||||
|
|
||||||
|
# Enhancers attached to items
|
||||||
|
self.enhancers: Dict[str, List[str]] = {} # item_name -> list of enhancer types
|
||||||
|
|
||||||
|
logger.info(f"GearLoadout created: {name} ({activity_type})")
|
||||||
|
|
||||||
|
def set_weapon(self, weapon: WeaponStats) -> None:
|
||||||
|
"""Set primary weapon."""
|
||||||
|
self.weapon = weapon
|
||||||
|
|
||||||
|
def add_armor_piece(self, armor: ArmorStats) -> None:
|
||||||
|
"""Add armor piece to loadout."""
|
||||||
|
self.armor[armor.slot] = armor
|
||||||
|
|
||||||
|
def set_mining_tools(self, finder: MiningToolStats,
|
||||||
|
extractor: Optional[MiningToolStats] = None) -> None:
|
||||||
|
"""Set mining tools."""
|
||||||
|
self.finder = finder
|
||||||
|
self.extractor = extractor
|
||||||
|
|
||||||
|
def calculate_hunting_cost_per_hour(self) -> Dict[str, Decimal]:
|
||||||
|
"""
|
||||||
|
Calculate total hunting cost per hour.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict with cost breakdown
|
||||||
|
"""
|
||||||
|
costs = {
|
||||||
|
'weapon': Decimal("0"),
|
||||||
|
'amplifier': Decimal("0"),
|
||||||
|
'armor': Decimal("0"),
|
||||||
|
'total': Decimal("0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.weapon:
|
||||||
|
costs['weapon'] = self.weapon.calculate_cost_per_hour()
|
||||||
|
|
||||||
|
if self.amplifier:
|
||||||
|
costs['amplifier'] = self.amplifier.calculate_cost_per_hour()
|
||||||
|
|
||||||
|
# Estimate armor decay (varies by mob type)
|
||||||
|
for armor in self.armor.values():
|
||||||
|
# Approximate: 100 hits per hour, decay per hit
|
||||||
|
costs['armor'] += (armor.decay_per_hit * 100) / 100 # PEC to PED
|
||||||
|
|
||||||
|
costs['total'] = costs['weapon'] + costs['amplifier'] + costs['armor']
|
||||||
|
|
||||||
|
return costs
|
||||||
|
|
||||||
|
def calculate_mining_cost_per_drop(self) -> Decimal:
|
||||||
|
"""Calculate total mining cost per drop."""
|
||||||
|
if not self.finder:
|
||||||
|
return Decimal("0")
|
||||||
|
|
||||||
|
cost = self.finder.calculate_cost_per_drop()
|
||||||
|
|
||||||
|
if self.extractor:
|
||||||
|
cost += (self.extractor.decay_per_use / 100) # PEC to PED
|
||||||
|
|
||||||
|
return cost
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict:
|
||||||
|
"""Convert loadout to dictionary for serialization."""
|
||||||
|
return {
|
||||||
|
'name': self.name,
|
||||||
|
'activity_type': self.activity_type,
|
||||||
|
'weapon': self.weapon.name if self.weapon else None,
|
||||||
|
'armor': {slot: armor.name for slot, armor in self.armor.items()},
|
||||||
|
'finder': self.finder.name if self.finder else None,
|
||||||
|
'extractor': self.extractor.name if self.extractor else None,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# MODULE EXPORTS
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'EntropiaNexusAPI',
|
||||||
|
'WeaponStats',
|
||||||
|
'MiningToolStats',
|
||||||
|
'ArmorStats',
|
||||||
|
'GearLoadout'
|
||||||
|
]
|
||||||
176
main.py
176
main.py
|
|
@ -1,6 +1,6 @@
|
||||||
# Description: Main entry point for Lemontropia Suite
|
# Description: Main entry point for Lemontropia Suite
|
||||||
# Provides CLI interface for user testing the Data Capture Engine
|
# Provides CLI interface for user testing the Data Capture Engine
|
||||||
# Run with: python main.py
|
# CORRECTED TERMINOLOGY: Projects = Activities (Hunt/Mine/Craft), Sessions = Gameplay Instances
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
@ -31,11 +31,16 @@ class LemontropiaApp:
|
||||||
"""
|
"""
|
||||||
Main application class for user testing.
|
Main application class for user testing.
|
||||||
|
|
||||||
Provides interactive CLI for:
|
TERMINOLOGY CLARIFICATION:
|
||||||
- Project management (create, list, archive)
|
- PROJECT: A long-term activity (e.g., "Argo Hunting", "Calypso Mining")
|
||||||
- Session control (start, stop)
|
- SESSION: A single gameplay instance within a project (e.g., "2-hour hunt")
|
||||||
- Live log watching (mock mode)
|
- RUN: A complete project lifecycle (multiple sessions)
|
||||||
- Data viewing (loot, stats)
|
|
||||||
|
Example:
|
||||||
|
Project: "Daily Argo Grind"
|
||||||
|
├── Session 1: Morning hunt (45 min, +15 PED)
|
||||||
|
├── Session 2: Evening hunt (2 hrs, -5 PED)
|
||||||
|
└── Session 3: Weekend marathon (5 hrs, +200 PED)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
@ -68,74 +73,92 @@ class LemontropiaApp:
|
||||||
|
|
||||||
def print_header(self):
|
def print_header(self):
|
||||||
"""Print application header."""
|
"""Print application header."""
|
||||||
print("\n" + "="*60)
|
print("\n" + "="*65)
|
||||||
print(" 🍋 LEMONTROPIA SUITE — User Test Build v0.1.0")
|
print(" 🍋 LEMONTROPIA SUITE — User Test Build v0.1.0")
|
||||||
print(" Core Data Capture Engine — Mock Mode")
|
print(" Core Data Capture Engine — Mock Mode")
|
||||||
print("="*60)
|
print("="*65)
|
||||||
|
print("\n 📚 TERMINOLOGY:")
|
||||||
|
print(" • PROJECT = Activity type (Hunt/Mine/Craft)")
|
||||||
|
print(" • SESSION = Single gameplay instance")
|
||||||
|
print(" • RUN = Complete project with multiple sessions")
|
||||||
|
|
||||||
def print_menu(self):
|
def print_menu(self):
|
||||||
"""Print main menu."""
|
"""Print main menu."""
|
||||||
print("\n📋 MAIN MENU")
|
print("\n📋 MAIN MENU")
|
||||||
print("-" * 40)
|
print("-" * 45)
|
||||||
print(" 1. 🎯 Create New Project")
|
print(" 1. 🎯 Create New Project (Activity)")
|
||||||
print(" 2. 📂 List All Projects")
|
print(" 2. 📂 View All Projects")
|
||||||
print(" 3. ▶️ Start Live Session (Mock Mode)")
|
print(" 3. ▶️ Start New Session (Live Tracking)")
|
||||||
print(" 4. 📊 View Project Stats")
|
print(" 4. 📊 View Project Statistics")
|
||||||
print(" 5. 🗄️ Archive Project")
|
print(" 5. 🗄️ Archive/Complete Project")
|
||||||
print(" 6. 🧹 Reset Database (WARNING)")
|
print(" 6. 🧹 Reset Database (WARNING)")
|
||||||
print(" 0. 🚪 Exit")
|
print(" 0. 🚪 Exit")
|
||||||
print("-" * 40)
|
print("-" * 45)
|
||||||
|
|
||||||
def create_project(self):
|
def create_project(self):
|
||||||
"""Create a new project."""
|
"""Create a new project (activity definition)."""
|
||||||
print("\n🎯 CREATE NEW PROJECT")
|
print("\n🎯 CREATE NEW PROJECT")
|
||||||
print("-" * 40)
|
print("-" * 50)
|
||||||
|
print(" A PROJECT defines your activity type:")
|
||||||
|
print(" • 'Daily Argo Grind' (Hunting)")
|
||||||
|
print(" • 'Calypso Mining Route' (Mining)")
|
||||||
|
print(" • 'Weapon Crafting Batch' (Crafting)")
|
||||||
|
print("-" * 50)
|
||||||
|
|
||||||
name = input("Project name: ").strip()
|
name = input("Project name: ").strip()
|
||||||
if not name:
|
if not name:
|
||||||
print("❌ Name required")
|
print("❌ Name required")
|
||||||
return
|
return
|
||||||
|
|
||||||
print("\nProject types:")
|
print("\nActivity type:")
|
||||||
print(" 1. hunt (Hunting)")
|
print(" 1. hunt - Combat & creature looting")
|
||||||
print(" 2. mine (Mining)")
|
print(" 2. mine - Resource extraction")
|
||||||
print(" 3. craft (Crafting)")
|
print(" 3. craft - Manufacturing items")
|
||||||
print(" 4. inventory (Inventory)")
|
print(" 4. inventory - Asset management")
|
||||||
|
|
||||||
type_choice = input("Select type (1-4): ").strip()
|
type_choice = input("Select type (1-4): ").strip()
|
||||||
type_map = {'1': 'hunt', '2': 'mine', '3': 'craft', '4': 'inventory'}
|
type_map = {'1': 'hunt', '2': 'mine', '3': 'craft', '4': 'inventory'}
|
||||||
project_type = type_map.get(type_choice, 'hunt')
|
project_type = type_map.get(type_choice, 'hunt')
|
||||||
|
|
||||||
project = self.pm.create_project(name, project_type)
|
project = self.pm.create_project(name, project_type)
|
||||||
print(f"\n✅ Created project: {project.name} (ID: {project.id}, Type: {project_type})")
|
print(f"\n✅ Created PROJECT: {project.name}")
|
||||||
|
print(f" Type: {project_type}")
|
||||||
|
print(f" ID: {project.id}")
|
||||||
|
print(f"\n Next: Start a SESSION to track gameplay (Option 3)")
|
||||||
|
|
||||||
# Show current projects
|
# Show current projects
|
||||||
self.list_projects()
|
self.list_projects()
|
||||||
|
|
||||||
def list_projects(self):
|
def list_projects(self):
|
||||||
"""List all projects."""
|
"""List all projects (activities)."""
|
||||||
print("\n📂 PROJECTS")
|
print("\n📂 PROJECTS (Activities)")
|
||||||
print("-" * 60)
|
print("-" * 65)
|
||||||
|
print(" These are your defined hunting/mining/crafting activities")
|
||||||
|
print("-" * 65)
|
||||||
|
|
||||||
projects = self.pm.list_projects()
|
projects = self.pm.list_projects()
|
||||||
|
|
||||||
if not projects:
|
if not projects:
|
||||||
print(" No projects found. Create one first!")
|
print(" No projects found. Create one first! (Option 1)")
|
||||||
return
|
return
|
||||||
|
|
||||||
print(f" {'ID':<5} {'Name':<20} {'Type':<10} {'Status':<10} {'Created'}")
|
print(f" {'ID':<5} {'Name':<22} {'Type':<10} {'Status':<10} {'Created'}")
|
||||||
print(" " + "-" * 58)
|
print(" " + "-" * 63)
|
||||||
|
|
||||||
for p in projects:
|
for p in projects:
|
||||||
created = p.created_at.strftime("%Y-%m-%d") if p.created_at else "N/A"
|
created = p.created_at.strftime("%Y-%m-%d") if p.created_at else "N/A"
|
||||||
print(f" {p.id:<5} {p.name:<20} {p.type:<10} {p.status:<10} {created}")
|
print(f" {p.id:<5} {p.name:<22} {p.type:<10} {p.status:<10} {created}")
|
||||||
|
|
||||||
print(f"\n Total: {len(projects)} project(s)")
|
print(f"\n Total: {len(projects)} project(s)")
|
||||||
|
print("\n 💡 Tip: Select a project and start a SESSION to track gameplay")
|
||||||
|
|
||||||
async def start_live_session(self):
|
async def start_live_session(self):
|
||||||
"""Start a live session with mock log watching."""
|
"""Start a live session (single gameplay instance)."""
|
||||||
print("\n▶️ START LIVE SESSION (Mock Mode)")
|
print("\n▶️ START NEW SESSION (Live Tracking)")
|
||||||
print("-" * 60)
|
print("-" * 65)
|
||||||
|
print(" A SESSION is a single gameplay instance within a project.")
|
||||||
|
print(" Example: 'Morning Argo Hunt' or 'Mining Run #5'")
|
||||||
|
print("-" * 65)
|
||||||
|
|
||||||
# Select project
|
# Select project
|
||||||
projects = self.pm.list_projects(status='active')
|
projects = self.pm.list_projects(status='active')
|
||||||
|
|
@ -143,7 +166,7 @@ class LemontropiaApp:
|
||||||
print("❌ No active projects. Create one first (Option 1).")
|
print("❌ No active projects. Create one first (Option 1).")
|
||||||
return
|
return
|
||||||
|
|
||||||
print("Select project:")
|
print("Select PROJECT for this session:")
|
||||||
for i, p in enumerate(projects, 1):
|
for i, p in enumerate(projects, 1):
|
||||||
print(f" {i}. {p.name} ({p.type})")
|
print(f" {i}. {p.name} ({p.type})")
|
||||||
|
|
||||||
|
|
@ -154,9 +177,12 @@ class LemontropiaApp:
|
||||||
print("❌ Invalid selection")
|
print("❌ Invalid selection")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
print(f"\n📋 Starting SESSION for: {project.name}")
|
||||||
|
session_notes = input("Session notes (optional): ").strip()
|
||||||
|
|
||||||
# Start session
|
# Start session
|
||||||
session = self.pm.start_session(project.id, notes="User test session")
|
session = self.pm.start_session(project.id, notes=session_notes)
|
||||||
print(f"✅ Session started: ID {session.id}")
|
print(f"✅ SESSION started: ID {session.id}")
|
||||||
|
|
||||||
# Setup log watcher
|
# Setup log watcher
|
||||||
test_data_dir = Path(__file__).parent / "test-data"
|
test_data_dir = Path(__file__).parent / "test-data"
|
||||||
|
|
@ -200,9 +226,13 @@ class LemontropiaApp:
|
||||||
self.watcher.subscribe('hof', on_event)
|
self.watcher.subscribe('hof', on_event)
|
||||||
self.watcher.subscribe('skill', on_event)
|
self.watcher.subscribe('skill', on_event)
|
||||||
|
|
||||||
print("\n🔴 LIVE SESSION RUNNING")
|
print("\n" + "="*50)
|
||||||
print(" Watching mock chat.log for events...")
|
print("🔴 LIVE SESSION RUNNING")
|
||||||
print(" Press Ctrl+C to stop\n")
|
print("="*50)
|
||||||
|
print(f" Project: {project.name}")
|
||||||
|
print(f" Session ID: {session.id}")
|
||||||
|
print(" Watching chat.log for events...")
|
||||||
|
print(" Press Ctrl+C to end session\n")
|
||||||
|
|
||||||
await self.watcher.start()
|
await self.watcher.start()
|
||||||
|
|
||||||
|
|
@ -217,25 +247,30 @@ class LemontropiaApp:
|
||||||
# End session
|
# End session
|
||||||
self.pm.end_session(session.id)
|
self.pm.end_session(session.id)
|
||||||
|
|
||||||
print("\n✅ Session ended")
|
print("\n" + "="*50)
|
||||||
|
print("✅ SESSION ENDED")
|
||||||
|
print("="*50)
|
||||||
print(f"\n📊 SESSION SUMMARY:")
|
print(f"\n📊 SESSION SUMMARY:")
|
||||||
print(f" Loot events: {stats['loot']}")
|
print(f" Loot events: {stats['loot']}")
|
||||||
print(f" Globals: {stats['globals']}")
|
print(f" Globals: {stats['globals']}")
|
||||||
print(f" HoFs: {stats['hofs']}")
|
print(f" HoFs: {stats['hofs']}")
|
||||||
print(f" Skills: {stats['skills']}")
|
print(f" Skills: {stats['skills']}")
|
||||||
print(f" Total PED: {stats['total_ped']}")
|
print(f" Total Value: {stats['total_ped']} PED")
|
||||||
|
print(f"\n View full stats: Option 4 (Project Statistics)")
|
||||||
|
|
||||||
def view_project_stats(self):
|
def view_project_stats(self):
|
||||||
"""View statistics for a project."""
|
"""View statistics for a project (all sessions combined)."""
|
||||||
print("\n📊 PROJECT STATISTICS")
|
print("\n📊 PROJECT STATISTICS")
|
||||||
print("-" * 60)
|
print("-" * 65)
|
||||||
|
print(" View combined stats for all sessions in a project")
|
||||||
|
print("-" * 65)
|
||||||
|
|
||||||
projects = self.pm.list_projects()
|
projects = self.pm.list_projects()
|
||||||
if not projects:
|
if not projects:
|
||||||
print(" No projects found.")
|
print(" No projects found.")
|
||||||
return
|
return
|
||||||
|
|
||||||
print("Select project:")
|
print("Select project to analyze:")
|
||||||
for i, p in enumerate(projects, 1):
|
for i, p in enumerate(projects, 1):
|
||||||
print(f" {i}. {p.name}")
|
print(f" {i}. {p.name}")
|
||||||
|
|
||||||
|
|
@ -251,25 +286,30 @@ class LemontropiaApp:
|
||||||
print("❌ Could not load summary")
|
print("❌ Could not load summary")
|
||||||
return
|
return
|
||||||
|
|
||||||
print(f"\n📈 STATS FOR: {summary['name']}")
|
print(f"\n" + "="*50)
|
||||||
print("-" * 40)
|
print(f"📈 STATS FOR: {summary['name']}")
|
||||||
print(f" Type: {summary['type']}")
|
print("="*50)
|
||||||
|
print(f" Activity Type: {summary['type']}")
|
||||||
print(f" Status: {summary['status']}")
|
print(f" Status: {summary['status']}")
|
||||||
print(f" Sessions: {summary['session_count']}")
|
print(f" Total Sessions: {summary['session_count']}")
|
||||||
|
print(f"\n 💰 FINANCIALS:")
|
||||||
print(f" Total Spent: {summary['total_spent']} PED")
|
print(f" Total Spent: {summary['total_spent']} PED")
|
||||||
print(f" Total Return: {summary['total_return']} PED")
|
print(f" Total Return: {summary['total_return']} PED")
|
||||||
print(f" Net Profit: {summary['net_profit']} PED")
|
print(f" Net Profit: {summary['net_profit']} PED")
|
||||||
print(f" Globals: {summary['global_count']}")
|
|
||||||
print(f" HoFs: {summary['hof_count']}")
|
|
||||||
|
|
||||||
if summary['total_spent'] > 0:
|
if summary['total_spent'] > 0:
|
||||||
roi = (summary['net_profit'] / summary['total_spent']) * 100
|
roi = (summary['net_profit'] / summary['total_spent']) * 100
|
||||||
print(f" ROI: {roi:.2f}%")
|
print(f" ROI: {roi:.2f}%")
|
||||||
|
print(f"\n 🏆 ACHIEVEMENTS:")
|
||||||
|
print(f" Globals: {summary['global_count']}")
|
||||||
|
print(f" Hall of Fames: {summary['hof_count']}")
|
||||||
|
|
||||||
def archive_project(self):
|
def archive_project(self):
|
||||||
"""Archive a project."""
|
"""Archive a completed project."""
|
||||||
print("\n🗄️ ARCHIVE PROJECT")
|
print("\n🗄️ ARCHIVE PROJECT")
|
||||||
print("-" * 40)
|
print("-" * 50)
|
||||||
|
print(" Archive a project when you're done with it.")
|
||||||
|
print(" Archived projects are kept for historical comparison.")
|
||||||
|
print("-" * 50)
|
||||||
|
|
||||||
projects = self.pm.list_projects()
|
projects = self.pm.list_projects()
|
||||||
if not projects:
|
if not projects:
|
||||||
|
|
@ -278,7 +318,8 @@ class LemontropiaApp:
|
||||||
|
|
||||||
print("Select project to archive:")
|
print("Select project to archive:")
|
||||||
for i, p in enumerate(projects, 1):
|
for i, p in enumerate(projects, 1):
|
||||||
print(f" {i}. {p.name} ({p.status})")
|
status_icon = "🟢" if p.status == 'active' else "⚪"
|
||||||
|
print(f" {i}. {status_icon} {p.name} ({p.status})")
|
||||||
|
|
||||||
choice = input(f"\nSelect (1-{len(projects)}): ").strip()
|
choice = input(f"\nSelect (1-{len(projects)}): ").strip()
|
||||||
try:
|
try:
|
||||||
|
|
@ -287,8 +328,11 @@ class LemontropiaApp:
|
||||||
print("❌ Invalid selection")
|
print("❌ Invalid selection")
|
||||||
return
|
return
|
||||||
|
|
||||||
confirm = input(f"Archive '{project.name}'? (yes/no): ").strip().lower()
|
print(f"\n⚠️ Archive '{project.name}'?")
|
||||||
if confirm == 'yes':
|
print(" This will mark the project as completed.")
|
||||||
|
confirm = input("Type 'archive' to confirm: ").strip().lower()
|
||||||
|
|
||||||
|
if confirm == 'archive':
|
||||||
self.pm.archive_project(project.id)
|
self.pm.archive_project(project.id)
|
||||||
print(f"✅ Archived: {project.name}")
|
print(f"✅ Archived: {project.name}")
|
||||||
else:
|
else:
|
||||||
|
|
@ -297,10 +341,11 @@ class LemontropiaApp:
|
||||||
def reset_database(self):
|
def reset_database(self):
|
||||||
"""Reset database (for testing)."""
|
"""Reset database (for testing)."""
|
||||||
print("\n🧹 RESET DATABASE")
|
print("\n🧹 RESET DATABASE")
|
||||||
print("-" * 40)
|
print("-" * 50)
|
||||||
print("⚠️ WARNING: This will delete all data!")
|
print("⚠️ WARNING: This will DELETE all data!")
|
||||||
|
print(" All projects, sessions, and loot data will be lost.")
|
||||||
|
|
||||||
confirm = input("Type 'RESET' to confirm: ").strip()
|
confirm = input("\nType 'RESET' to confirm: ").strip()
|
||||||
if confirm == 'RESET':
|
if confirm == 'RESET':
|
||||||
db_path = self.db.db_path
|
db_path = self.db.db_path
|
||||||
self.db.close()
|
self.db.close()
|
||||||
|
|
@ -314,7 +359,7 @@ class LemontropiaApp:
|
||||||
self.db = DatabaseManager()
|
self.db = DatabaseManager()
|
||||||
self.db.initialize()
|
self.db.initialize()
|
||||||
self.pm = ProjectManager(self.db)
|
self.pm = ProjectManager(self.db)
|
||||||
print("✅ Database reinitialized")
|
print("✅ Database reinitialized (empty)")
|
||||||
else:
|
else:
|
||||||
print("Cancelled")
|
print("Cancelled")
|
||||||
|
|
||||||
|
|
@ -350,7 +395,9 @@ class LemontropiaApp:
|
||||||
self.reset_database()
|
self.reset_database()
|
||||||
elif choice == '0':
|
elif choice == '0':
|
||||||
self._running = False
|
self._running = False
|
||||||
print("\n🍋 Thank you for testing Lemontropia Suite!")
|
print("\n" + "="*50)
|
||||||
|
print("🍋 Thank you for testing Lemontropia Suite!")
|
||||||
|
print("="*50)
|
||||||
else:
|
else:
|
||||||
print("❌ Invalid option")
|
print("❌ Invalid option")
|
||||||
|
|
||||||
|
|
@ -364,13 +411,6 @@ class LemontropiaApp:
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Application entry point."""
|
"""Application entry point."""
|
||||||
print("""
|
|
||||||
🍋 ==========================================
|
|
||||||
LEMONTROPIA SUITE — User Test Build
|
|
||||||
Data Capture Engine v0.1.0
|
|
||||||
==========================================
|
|
||||||
""")
|
|
||||||
|
|
||||||
app = LemontropiaApp()
|
app = LemontropiaApp()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue