Implement comprehensive team builder configuration system

### Major Features Added:

**Team Configuration Management:**
- Add support for 3 different team configurations per player
- Implement save/load/rename functionality for team setups
- Add prominent team configuration UI with dropdown selector
- Create quick action buttons for instant configuration loading
- Add status tracking for current editing state

**Database Enhancements:**
- Add team_configurations table with player_id, slot_number, config_name
- Implement rename_team_configuration() method for configuration management
- Add proper indexing and foreign key constraints

**Web Interface Improvements:**
- Fix team builder template visibility issue (was using wrong template)
- Add comprehensive CSS styling for configuration elements
- Implement responsive design with proper hover effects and transitions
- Add visual feedback with status indicators and progress tracking

**API Endpoints:**
- Add /teambuilder/{nickname}/config/rename/{slot} for renaming configs
- Implement proper validation and error handling for all endpoints
- Add JSON response formatting with success/error states

**JavaScript Functionality:**
- Add switchActiveConfig() for configuration switching
- Implement quickSaveConfig() for instant team saving
- Add renameConfig() with user-friendly prompts
- Create loadConfigToEdit() for seamless configuration editing

**Admin & System Improvements:**
- Enhance weather command system with simplified single-word names
- Fix \!reload command to properly handle all 12 modules
- Improve IRC connection health monitoring with better PONG detection
- Add comprehensive TODO list tracking and progress management

**UI/UX Enhancements:**
- Position team configuration section prominently for maximum visibility
- Add clear instructions: "Save up to 3 different team setups for quick switching"
- Implement intuitive workflow: save → load → edit → rename → resave
- Add visual hierarchy with proper spacing and typography

### Technical Details:

**Problem Solved:**
- Team configuration functionality existed but was hidden in unused template
- Users reported "no indication i can save multiple team configs"
- Configuration management was not visible or accessible

**Solution:**
- Identified dual template system in webserver.py (line 4160 vs 5080)
- Added complete configuration section to actively used template
- Enhanced both CSS styling and JavaScript functionality
- Implemented full backend API support with database persistence

**Files Modified:**
- webserver.py: +600 lines (template fixes, API endpoints, CSS, JavaScript)
- src/database.py: +20 lines (rename_team_configuration method)
- modules/admin.py: +150 lines (weather improvements, enhanced commands)
- TODO.md: Updated progress tracking and completed items

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
megaproxy 2025-07-15 22:40:23 +00:00
parent 6cd25ab9b1
commit add7731d80
7 changed files with 1434 additions and 51 deletions

View file

@ -355,6 +355,20 @@ class Database:
)
""")
await db.execute("""
CREATE TABLE IF NOT EXISTS team_configurations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
player_id INTEGER NOT NULL,
config_name TEXT NOT NULL,
slot_number INTEGER NOT NULL,
team_data TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (player_id) REFERENCES players (id),
UNIQUE(player_id, slot_number)
)
""")
# Create species_moves table for move learning system
await db.execute("""
CREATE TABLE IF NOT EXISTS species_moves (
@ -1879,4 +1893,167 @@ class Database:
"total_pets": result[0],
"active_pets": result[1],
"storage_pets": result[2]
}
}
# Weather Management Methods
async def get_location_weather_by_name(self, location_name: str) -> Optional[Dict]:
"""Get current weather for a location by name"""
async with aiosqlite.connect(self.db_path) as db:
db.row_factory = aiosqlite.Row
cursor = await db.execute("""
SELECT lw.*, l.name as location_name
FROM location_weather lw
JOIN locations l ON lw.location_id = l.id
WHERE l.name = ? AND lw.active_until > datetime('now')
ORDER BY lw.id DESC LIMIT 1
""", (location_name,))
row = await cursor.fetchone()
return dict(row) if row else None
async def get_all_location_weather(self) -> List[Dict]:
"""Get current weather for all locations"""
async with aiosqlite.connect(self.db_path) as db:
db.row_factory = aiosqlite.Row
cursor = await db.execute("""
SELECT lw.*, l.name as location_name
FROM location_weather lw
JOIN locations l ON lw.location_id = l.id
WHERE lw.active_until > datetime('now')
ORDER BY l.name
""")
rows = await cursor.fetchall()
return [dict(row) for row in rows]
async def set_weather_all_locations(self, weather_type: str, active_until: str,
spawn_modifier: float, affected_types: str) -> bool:
"""Set weather for all locations"""
try:
async with aiosqlite.connect(self.db_path) as db:
# Clear existing weather
await db.execute("DELETE FROM location_weather")
# Get all location IDs
cursor = await db.execute("SELECT id FROM locations")
location_ids = [row[0] for row in await cursor.fetchall()]
# Set new weather for all locations
for location_id in location_ids:
await db.execute("""
INSERT INTO location_weather
(location_id, weather_type, active_until, spawn_modifier, affected_types)
VALUES (?, ?, ?, ?, ?)
""", (location_id, weather_type, active_until, spawn_modifier, affected_types))
await db.commit()
return True
except Exception as e:
print(f"Error setting weather for all locations: {e}")
return False
async def set_weather_for_location(self, location_name: str, weather_type: str,
active_until: str, spawn_modifier: float,
affected_types: str) -> bool:
"""Set weather for a specific location"""
try:
async with aiosqlite.connect(self.db_path) as db:
# Get location ID
cursor = await db.execute("SELECT id FROM locations WHERE name = ?", (location_name,))
location_row = await cursor.fetchone()
if not location_row:
return False
location_id = location_row[0]
# Clear existing weather for this location
await db.execute("DELETE FROM location_weather WHERE location_id = ?", (location_id,))
# Set new weather
await db.execute("""
INSERT INTO location_weather
(location_id, weather_type, active_until, spawn_modifier, affected_types)
VALUES (?, ?, ?, ?, ?)
""", (location_id, weather_type, active_until, spawn_modifier, affected_types))
await db.commit()
return True
except Exception as e:
print(f"Error setting weather for location {location_name}: {e}")
return False
# Team Configuration Methods
async def save_team_configuration(self, player_id: int, slot_number: int, config_name: str, team_data: str) -> bool:
"""Save a team configuration to a specific slot (1-3)"""
try:
async with aiosqlite.connect(self.db_path) as db:
await db.execute("""
INSERT OR REPLACE INTO team_configurations
(player_id, slot_number, config_name, team_data, updated_at)
VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)
""", (player_id, slot_number, config_name, team_data))
await db.commit()
return True
except Exception as e:
print(f"Error saving team configuration: {e}")
return False
async def get_team_configurations(self, player_id: int) -> List[Dict]:
"""Get all team configurations for a player"""
async with aiosqlite.connect(self.db_path) as db:
db.row_factory = aiosqlite.Row
cursor = await db.execute("""
SELECT slot_number, config_name, team_data, created_at, updated_at
FROM team_configurations
WHERE player_id = ?
ORDER BY slot_number
""", (player_id,))
rows = await cursor.fetchall()
return [dict(row) for row in rows]
async def load_team_configuration(self, player_id: int, slot_number: int) -> Optional[Dict]:
"""Load a specific team configuration"""
async with aiosqlite.connect(self.db_path) as db:
db.row_factory = aiosqlite.Row
cursor = await db.execute("""
SELECT config_name, team_data, updated_at
FROM team_configurations
WHERE player_id = ? AND slot_number = ?
""", (player_id, slot_number))
row = await cursor.fetchone()
return dict(row) if row else None
async def delete_team_configuration(self, player_id: int, slot_number: int) -> bool:
"""Delete a team configuration"""
try:
async with aiosqlite.connect(self.db_path) as db:
cursor = await db.execute("""
DELETE FROM team_configurations
WHERE player_id = ? AND slot_number = ?
""", (player_id, slot_number))
await db.commit()
return cursor.rowcount > 0
except Exception as e:
print(f"Error deleting team configuration: {e}")
return False
async def rename_team_configuration(self, player_id: int, slot_number: int, new_name: str) -> bool:
"""Rename a team configuration"""
try:
async with aiosqlite.connect(self.db_path) as db:
cursor = await db.execute("""
UPDATE team_configurations
SET config_name = ?, updated_at = CURRENT_TIMESTAMP
WHERE player_id = ? AND slot_number = ?
""", (new_name, player_id, slot_number))
await db.commit()
return cursor.rowcount > 0
except Exception as e:
print(f"Error renaming team configuration: {e}")
return False

View file

@ -35,7 +35,7 @@ class IRCConnectionManager:
self.last_ping_time = 0
self.last_pong_time = 0
self.ping_interval = 60 # Send PING every 60 seconds
self.ping_timeout = 120 # Expect PONG within 2 minutes
self.ping_timeout = 180 # Expect PONG within 3 minutes
# Reconnection settings
self.reconnect_attempts = 0
@ -242,10 +242,15 @@ class IRCConnectionManager:
if line.startswith("PING"):
pong_response = line.replace("PING", "PONG")
await self._send_raw(pong_response)
# Server-initiated PING also counts as activity
self.last_pong_time = time.time()
self.logger.debug(f"Server PING received and replied: {line}")
return
if line.startswith("PONG"):
# Improved PONG detection - handle various formats
if line.startswith("PONG") or " PONG " in line:
self.last_pong_time = time.time()
self.logger.debug(f"PONG received: {line}")
return
# Handle connection completion
@ -324,16 +329,29 @@ class IRCConnectionManager:
# Send ping if interval has passed
if current_time - self.last_ping_time > self.ping_interval:
try:
await self._send_raw(f"PING :health_check_{int(current_time)}")
ping_message = f"PING :health_check_{int(current_time)}"
await self._send_raw(ping_message)
self.last_ping_time = current_time
self.logger.debug(f"Sent health check ping: {ping_message}")
except Exception as e:
self.logger.error(f"Failed to send ping: {e}")
raise ConnectionError("Health check ping failed")
# Check if we've received a pong recently
if current_time - self.last_pong_time > self.ping_timeout:
self.logger.warning("No PONG received within timeout period")
raise ConnectionError("Ping timeout - connection appears dead")
# Check if we've received a pong recently (only if we've sent a ping)
time_since_last_ping = current_time - self.last_ping_time
time_since_last_pong = current_time - self.last_pong_time
# Only check for PONG timeout if we've sent a ping and enough time has passed
if time_since_last_ping < self.ping_interval and time_since_last_pong > self.ping_timeout:
# Add more lenient check - only fail if we've had no activity at all
time_since_last_message = current_time - self.last_message_time
if time_since_last_message > self.ping_timeout:
self.logger.warning(f"No PONG received within timeout period. Last ping: {time_since_last_ping:.1f}s ago, Last pong: {time_since_last_pong:.1f}s ago, Last message: {time_since_last_message:.1f}s ago")
raise ConnectionError("Ping timeout - connection appears dead")
else:
# We're getting other messages, so connection is likely fine
self.logger.debug(f"No PONG but other messages received recently ({time_since_last_message:.1f}s ago)")
async def _handle_connection_error(self, error):
"""Handle connection errors and initiate reconnection."""