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:
LemonNexus 2026-02-08 17:17:37 +00:00
parent a516ff4d0c
commit dfe4e8125f
3 changed files with 862 additions and 72 deletions

292
WINDOWS_TESTING_GUIDE.md Normal file
View File

@ -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! 🍋*

458
core/entropia_nexus.py Normal file
View File

@ -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'
]

184
main.py
View File

@ -1,6 +1,6 @@
# Description: Main entry point for Lemontropia Suite
# 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 asyncio
@ -31,11 +31,16 @@ class LemontropiaApp:
"""
Main application class for user testing.
Provides interactive CLI for:
- Project management (create, list, archive)
- Session control (start, stop)
- Live log watching (mock mode)
- Data viewing (loot, stats)
TERMINOLOGY CLARIFICATION:
- PROJECT: A long-term activity (e.g., "Argo Hunting", "Calypso Mining")
- SESSION: A single gameplay instance within a project (e.g., "2-hour hunt")
- RUN: A complete project lifecycle (multiple sessions)
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):
@ -68,74 +73,92 @@ class LemontropiaApp:
def print_header(self):
"""Print application header."""
print("\n" + "="*60)
print("\n" + "="*65)
print(" 🍋 LEMONTROPIA SUITE — User Test Build v0.1.0")
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):
"""Print main menu."""
print("\n📋 MAIN MENU")
print("-" * 40)
print(" 1. 🎯 Create New Project")
print(" 2. 📂 List All Projects")
print(" 3. ▶️ Start Live Session (Mock Mode)")
print(" 4. 📊 View Project Stats")
print(" 5. 🗄️ Archive Project")
print("-" * 45)
print(" 1. 🎯 Create New Project (Activity)")
print(" 2. 📂 View All Projects")
print(" 3. ▶️ Start New Session (Live Tracking)")
print(" 4. 📊 View Project Statistics")
print(" 5. 🗄️ Archive/Complete Project")
print(" 6. 🧹 Reset Database (WARNING)")
print(" 0. 🚪 Exit")
print("-" * 40)
print("-" * 45)
def create_project(self):
"""Create a new project."""
"""Create a new project (activity definition)."""
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()
if not name:
print("❌ Name required")
return
print("\nProject types:")
print(" 1. hunt (Hunting)")
print(" 2. mine (Mining)")
print(" 3. craft (Crafting)")
print(" 4. inventory (Inventory)")
print("\nActivity type:")
print(" 1. hunt - Combat & creature looting")
print(" 2. mine - Resource extraction")
print(" 3. craft - Manufacturing items")
print(" 4. inventory - Asset management")
type_choice = input("Select type (1-4): ").strip()
type_map = {'1': 'hunt', '2': 'mine', '3': 'craft', '4': 'inventory'}
project_type = type_map.get(type_choice, 'hunt')
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
self.list_projects()
def list_projects(self):
"""List all projects."""
print("\n📂 PROJECTS")
print("-" * 60)
"""List all projects (activities)."""
print("\n📂 PROJECTS (Activities)")
print("-" * 65)
print(" These are your defined hunting/mining/crafting activities")
print("-" * 65)
projects = self.pm.list_projects()
if not projects:
print(" No projects found. Create one first!")
print(" No projects found. Create one first! (Option 1)")
return
print(f" {'ID':<5} {'Name':<20} {'Type':<10} {'Status':<10} {'Created'}")
print(" " + "-" * 58)
print(f" {'ID':<5} {'Name':<22} {'Type':<10} {'Status':<10} {'Created'}")
print(" " + "-" * 63)
for p in projects:
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("\n 💡 Tip: Select a project and start a SESSION to track gameplay")
async def start_live_session(self):
"""Start a live session with mock log watching."""
print("\n▶️ START LIVE SESSION (Mock Mode)")
print("-" * 60)
"""Start a live session (single gameplay instance)."""
print("\n▶️ START NEW SESSION (Live Tracking)")
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
projects = self.pm.list_projects(status='active')
@ -143,7 +166,7 @@ class LemontropiaApp:
print("❌ No active projects. Create one first (Option 1).")
return
print("Select project:")
print("Select PROJECT for this session:")
for i, p in enumerate(projects, 1):
print(f" {i}. {p.name} ({p.type})")
@ -154,9 +177,12 @@ class LemontropiaApp:
print("❌ Invalid selection")
return
print(f"\n📋 Starting SESSION for: {project.name}")
session_notes = input("Session notes (optional): ").strip()
# Start session
session = self.pm.start_session(project.id, notes="User test session")
print(f"✅ Session started: ID {session.id}")
session = self.pm.start_session(project.id, notes=session_notes)
print(f"✅ SESSION started: ID {session.id}")
# Setup log watcher
test_data_dir = Path(__file__).parent / "test-data"
@ -200,9 +226,13 @@ class LemontropiaApp:
self.watcher.subscribe('hof', on_event)
self.watcher.subscribe('skill', on_event)
print("\n🔴 LIVE SESSION RUNNING")
print(" Watching mock chat.log for events...")
print(" Press Ctrl+C to stop\n")
print("\n" + "="*50)
print("🔴 LIVE SESSION RUNNING")
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()
@ -217,25 +247,30 @@ class LemontropiaApp:
# End session
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" Loot events: {stats['loot']}")
print(f" Globals: {stats['globals']}")
print(f" HoFs: {stats['hofs']}")
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):
"""View statistics for a project."""
"""View statistics for a project (all sessions combined)."""
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()
if not projects:
print(" No projects found.")
return
print("Select project:")
print("Select project to analyze:")
for i, p in enumerate(projects, 1):
print(f" {i}. {p.name}")
@ -251,25 +286,30 @@ class LemontropiaApp:
print("❌ Could not load summary")
return
print(f"\n📈 STATS FOR: {summary['name']}")
print("-" * 40)
print(f" Type: {summary['type']}")
print(f"\n" + "="*50)
print(f"📈 STATS FOR: {summary['name']}")
print("="*50)
print(f" Activity Type: {summary['type']}")
print(f" Status: {summary['status']}")
print(f" Sessions: {summary['session_count']}")
print(f" Total Spent: {summary['total_spent']} PED")
print(f" Total Return: {summary['total_return']} PED")
print(f" Net Profit: {summary['net_profit']} PED")
print(f" Globals: {summary['global_count']}")
print(f" HoFs: {summary['hof_count']}")
print(f" Total Sessions: {summary['session_count']}")
print(f"\n 💰 FINANCIALS:")
print(f" Total Spent: {summary['total_spent']} PED")
print(f" Total Return: {summary['total_return']} PED")
print(f" Net Profit: {summary['net_profit']} PED")
if summary['total_spent'] > 0:
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):
"""Archive a project."""
"""Archive a completed 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()
if not projects:
@ -278,7 +318,8 @@ class LemontropiaApp:
print("Select project to archive:")
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()
try:
@ -287,8 +328,11 @@ class LemontropiaApp:
print("❌ Invalid selection")
return
confirm = input(f"Archive '{project.name}'? (yes/no): ").strip().lower()
if confirm == 'yes':
print(f"\n⚠️ Archive '{project.name}'?")
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)
print(f"✅ Archived: {project.name}")
else:
@ -297,10 +341,11 @@ class LemontropiaApp:
def reset_database(self):
"""Reset database (for testing)."""
print("\n🧹 RESET DATABASE")
print("-" * 40)
print("⚠️ WARNING: This will delete all data!")
print("-" * 50)
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':
db_path = self.db.db_path
self.db.close()
@ -314,7 +359,7 @@ class LemontropiaApp:
self.db = DatabaseManager()
self.db.initialize()
self.pm = ProjectManager(self.db)
print("✅ Database reinitialized")
print("✅ Database reinitialized (empty)")
else:
print("Cancelled")
@ -350,7 +395,9 @@ class LemontropiaApp:
self.reset_database()
elif choice == '0':
self._running = False
print("\n🍋 Thank you for testing Lemontropia Suite!")
print("\n" + "="*50)
print("🍋 Thank you for testing Lemontropia Suite!")
print("="*50)
else:
print("❌ Invalid option")
@ -364,13 +411,6 @@ class LemontropiaApp:
def main():
"""Application entry point."""
print("""
🍋 ==========================================
LEMONTROPIA SUITE User Test Build
Data Capture Engine v0.1.0
==========================================
""")
app = LemontropiaApp()
try: