Implement complete team swap functionality between web interface and IRC battles

🔄 **New Active Team System**
- Replace Team 1 hardcoded active system with flexible active_teams table
- Players can now edit ALL teams (1, 2, 3) equally via web interface
- Support for swapping any saved team as the active battle team

🌐 **Web Interface Enhancements**
- Add "Make Active" buttons to team management hub
- Real-time team swapping with loading states and success notifications
- Visual indicators for currently active team with green highlighting
- Updated team builder to treat all team slots consistently

🎮 **IRC Battle Integration**
- Update get_active_pets() and get_player_pets() methods to use new active_teams table
- IRC battles (\!battle, \!attack, \!gym) now use web-selected active team
- Real-time sync: team swaps via web immediately affect IRC battles
- Maintain backward compatibility with existing IRC commands

🛠️ **Database Architecture**
- Add active_teams table with player_id -> active_slot mapping
- Migrate existing active teams to team_configurations format
- Update team save logic to store all teams as configurations
- Add set_active_team_slot() and get_active_team_slot() methods

 **Key Features**
- Seamless web-to-IRC active team synchronization
- All teams editable with proper PIN verification
- Enhanced UX with animations and proper error handling
- Maintains data consistency across all interfaces

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
megaproxy 2025-07-17 16:39:42 +00:00
parent 285a7c4a7e
commit 5293da2921
2 changed files with 578 additions and 140 deletions

View file

@ -5082,8 +5082,10 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
# Get player and team configurations
player = loop.run_until_complete(database.get_player(nickname))
team_configs = []
current_active_slot = 1 # Default
if player:
team_configs = loop.run_until_complete(database.get_player_team_configurations(player['id']))
current_active_slot = loop.run_until_complete(database.get_active_team_slot(player['id']))
# Debug logging
print(f"Team Builder Debug for {nickname}:")
@ -5268,6 +5270,11 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
border-color: var(--text-accent);
background: var(--bg-primary);
}
.team-card.active {
border-color: var(--accent-green);
box-shadow: 0 0 15px rgba(83, 255, 169, 0.3);
}
.team-card-header {
display: flex;
@ -5329,6 +5336,62 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
background: var(--text-accent);
color: var(--bg-primary);
}
.team-actions {
display: flex;
flex-direction: column;
gap: 10px;
margin-top: 15px;
}
.swap-team-btn {
background: var(--accent-blue);
color: white;
border: none;
padding: 10px 20px;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
transition: all 0.3s ease;
}
.swap-team-btn:hover {
background: #339af0;
transform: translateY(-1px);
}
.active-badge {
background: var(--accent-green);
color: var(--bg-primary);
padding: 8px 16px;
border-radius: 8px;
font-weight: bold;
text-align: center;
display: block;
}
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOut {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(100%);
opacity: 0;
}
}
.team-sections {
margin-top: 30px;
@ -5944,6 +6007,7 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
// Initialize when DOM is ready
// Global variables for team management
let currentEditingTeam = 1; // Default to team 1
const playerNickname = '""" + nickname + """'; // Player nickname for API calls
document.addEventListener('DOMContentLoaded', function() {
console.log('Team Builder: DOM loaded, initializing...');
@ -5976,6 +6040,107 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
// Load team data for this slot (to be implemented)
loadTeamConfiguration(teamSlot);
}
function showMessage(message, type = 'info') {
// Check if message area exists, if not create it
let messageArea = document.getElementById('message-area');
if (!messageArea) {
messageArea = document.createElement('div');
messageArea.id = 'message-area';
messageArea.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
`;
document.body.appendChild(messageArea);
}
const messageDiv = document.createElement('div');
messageDiv.className = `message ${type}`;
messageDiv.style.cssText = `
background: ${type === 'success' ? '#4CAF50' : type === 'error' ? '#f44336' : '#2196F3'};
color: white;
padding: 15px 20px;
border-radius: 8px;
margin-bottom: 10px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
animation: slideIn 0.3s ease-out;
`;
messageDiv.textContent = message;
messageArea.appendChild(messageDiv);
// Remove message after 5 seconds
setTimeout(() => {
messageDiv.style.animation = 'slideOut 0.3s ease-out';
setTimeout(() => messageDiv.remove(), 300);
}, 5000);
}
async function swapToTeam(teamSlot) {
console.log('Swapping to team slot:', teamSlot);
// Show loading state
const swapBtn = document.querySelector(`[data-slot="${teamSlot}"] .swap-team-btn`);
if (swapBtn) {
swapBtn.disabled = true;
swapBtn.textContent = '⏳ Switching...';
}
try {
const response = await fetch(`/teambuilder/${playerNickname}/swap/${teamSlot}`, {
method: 'POST'
});
const result = await response.json();
if (result.success) {
// Update UI to reflect new active team
document.querySelectorAll('.team-card').forEach(card => {
card.classList.remove('active');
const actions = card.querySelector('.team-actions');
const slot = card.dataset.slot;
// Update button/badge
if (slot == teamSlot) {
actions.innerHTML = `
<button class="edit-team-btn" onclick="selectTeam(${slot})">
📝 Edit ${card.querySelector('h3').textContent}
</button>
<span class="active-badge">🟢 Active Team</span>
`;
card.classList.add('active');
} else {
const teamName = card.querySelector('h3').textContent;
actions.innerHTML = `
<button class="edit-team-btn" onclick="selectTeam(${slot})">
📝 Edit ${teamName}
</button>
<button class="swap-team-btn" onclick="swapToTeam(${slot})">🔄 Make Active</button>
`;
}
});
showMessage(`${result.message}`, 'success');
} else {
showMessage(`Failed to switch team: ${result.error}`, 'error');
// Reset button
if (swapBtn) {
swapBtn.disabled = false;
swapBtn.textContent = '🔄 Make Active';
}
}
} catch (error) {
console.error('Error swapping team:', error);
showMessage('Failed to switch team. Please try again.', 'error');
// Reset button
if (swapBtn) {
swapBtn.disabled = false;
swapBtn.textContent = '🔄 Make Active';
}
}
}
function loadTeamConfiguration(teamSlot) {
console.log('Loading team configuration for slot:', teamSlot);
@ -6608,8 +6773,11 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
pet_previews = '<div class="mini-pet empty">Empty</div>' * 6
status_text = "Empty team"
active_class = "active" if slot == current_active_slot else ""
is_active = slot == current_active_slot
team_cards_html += f'''
<div class="team-card {'active' if slot == 1 else ''}" data-slot="{slot}">
<div class="team-card {active_class}" data-slot="{slot}">
<div class="team-card-header">
<h3>Team {slot}</h3>
<span class="team-status">{status_text}</span>
@ -6617,9 +6785,12 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
<div class="team-preview">
{pet_previews}
</div>
<button class="edit-team-btn {'active' if slot == 1 else ''}" onclick="selectTeam({slot})">
{'🟢 Currently Editing' if slot == 1 else f'📝 Edit Team {slot}'}
</button>
<div class="team-actions">
<button class="edit-team-btn" onclick="selectTeam({slot})">
📝 Edit Team {slot}
</button>
{'<span class="active-badge">🟢 Active Team</span>' if is_active else f'<button class="swap-team-btn" onclick="swapToTeam({slot})">🔄 Make Active</button>'}
</div>
</div>
'''
else:
@ -6655,7 +6826,8 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
pet_previews = '<div class="mini-pet empty">Empty</div>' * 6
status_text = f"{config['pet_count']}/6 pets" if config['pet_count'] > 0 else "Empty team"
active_class = "active" if config['slot'] == 1 else "" # Default to team 1 as active
active_class = "active" if config['slot'] == current_active_slot else ""
is_active = config['slot'] == current_active_slot
team_cards_html += f'''
<div class="team-card {active_class}" data-slot="{config['slot']}">
@ -6666,9 +6838,12 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
<div class="team-preview">
{pet_previews}
</div>
<button class="edit-team-btn {active_class}" onclick="selectTeam({config['slot']})">
{'🟢 Currently Editing' if config['slot'] == 1 else f'📝 Edit {config["name"]}'}
</button>
<div class="team-actions">
<button class="edit-team-btn" onclick="selectTeam({config['slot']})">
📝 Edit {config["name"]}
</button>
{'<span class="active-badge">🟢 Active Team</span>' if is_active else f'<button class="swap-team-btn" onclick="swapToTeam({config["slot"]})">🔄 Make Active</button>'}
</div>
</div>
'''
@ -7842,6 +8017,48 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
print(f"Error in _handle_team_config_apply_async: {e}")
return {"success": False, "error": str(e)}
def handle_team_swap_request(self, nickname, slot):
"""Handle team swap request to change active team"""
try:
# Validate slot number
try:
slot_num = int(slot)
if slot_num < 1 or slot_num > 3:
self.send_json_response({"success": False, "error": "Slot must be 1, 2, or 3"}, 400)
return
except ValueError:
self.send_json_response({"success": False, "error": "Invalid slot number"}, 400)
return
# Run async operations
import asyncio
result = asyncio.run(self._handle_team_swap_async(nickname, slot_num))
if result["success"]:
self.send_json_response(result, 200)
else:
self.send_json_response(result, 404 if "not found" in result.get("error", "") else 400)
except Exception as e:
print(f"Error in handle_team_swap_request: {e}")
self.send_json_response({"success": False, "error": "Internal server error"}, 500)
async def _handle_team_swap_async(self, nickname, slot_num):
"""Async handler for team swapping"""
try:
# Get player
player = await self.database.get_player(nickname)
if not player:
return {"success": False, "error": "Player not found"}
# Set the new active team slot
result = await self.database.set_active_team_slot(player["id"], slot_num)
return result
except Exception as e:
print(f"Error in _handle_team_swap_async: {e}")
return {"success": False, "error": str(e)}
def handle_test_team_save(self, nickname):
"""Handle test team builder save request and generate PIN"""
try:
@ -10372,7 +10589,7 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
<a href="/teambuilder/{self.path.split('/')[2]}/team/{team_identifier}" class="btn btn-primary">
Edit Team {team_identifier}
</a>
<button class="btn btn-success" onclick="alert('Team swap coming soon!')">
<button class="btn btn-success" onclick="swapToTeam({team_identifier})">
🔄 Make Active
</button>
'''
@ -10660,6 +10877,89 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
margin-top: 8px;
}}
</style>
<script>
const playerNickname = '{nickname}';
function showMessage(message, type = 'info') {{
// Check if message area exists, if not create it
let messageArea = document.getElementById('message-area');
if (!messageArea) {{
messageArea = document.createElement('div');
messageArea.id = 'message-area';
messageArea.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
`;
document.body.appendChild(messageArea);
}}
const messageDiv = document.createElement('div');
messageDiv.className = `message ${{type}}`;
messageDiv.style.cssText = `
background: ${{type === 'success' ? '#4CAF50' : type === 'error' ? '#f44336' : '#2196F3'}};
color: white;
padding: 15px 20px;
border-radius: 8px;
margin-bottom: 10px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
animation: slideIn 0.3s ease-out;
`;
messageDiv.textContent = message;
messageArea.appendChild(messageDiv);
// Remove message after 5 seconds
setTimeout(() => {{
messageDiv.style.animation = 'slideOut 0.3s ease-out';
setTimeout(() => messageDiv.remove(), 300);
}}, 5000);
}}
async function swapToTeam(teamSlot) {{
console.log('Swapping to team slot:', teamSlot);
// Show loading state
const swapBtn = document.querySelector(`button[onclick="swapToTeam(${{teamSlot}})"]`);
if (swapBtn) {{
swapBtn.disabled = true;
swapBtn.textContent = '⏳ Switching...';
}}
try {{
const response = await fetch(`/teambuilder/${{playerNickname}}/swap/${{teamSlot}}`, {{
method: 'POST'
}});
const result = await response.json();
if (result.success) {{
showMessage(`${{result.message}}`, 'success');
// Reload page to show updated active team
setTimeout(() => {{
window.location.reload();
}}, 1500);
}} else {{
showMessage(`Failed to switch team: ${{result.error}}`, 'error');
// Reset button
if (swapBtn) {{
swapBtn.disabled = false;
swapBtn.textContent = '🔄 Make Active';
}}
}}
}} catch (error) {{
console.error('Error swapping team:', error);
showMessage('Failed to switch team. Please try again.', 'error');
// Reset button
if (swapBtn) {{
swapBtn.disabled = false;
swapBtn.textContent = '🔄 Make Active';
}}
}}
}}
</script>
'''
def generate_individual_team_editor_content(self, nickname, team_identifier, team_data, player_pets):