// ================================================================ // API & Networking // ================================================================ window.fetchTowns = async function() { try { const res = await fetch('/dashboard/towns?player_id=' + window.PLAYER_ID); window.towns = await res.json(); window.renderTowns(); window.updateServerStatus(true); // Automatically select the first town if none is selected if (!window.selectedTownId && window.towns && window.towns.length > 0) { window.selectTown(window.towns[0].town_id); } if (window.selectedTownId) { window.renderBuildQueuePreview(); window.renderBuildingDropdown(); window.renderUnitDropdown(); window.renderTownDetails(); } } catch (e) { window.updateServerStatus(false); } }; window.fetchClientStatus = async function() { try { const res = await fetch('/dashboard/client-status?player_id=' + window.PLAYER_ID); const data = await res.json(); const isOnline = data.online === true; // Detect transition: online → offline if (window.wasClientOnline === true && !isOnline) { // Fail all queued commands immediately await fetch('/dashboard/commands/fail-stale?player_id=' + window.PLAYER_ID, { method: 'POST' }); window.fetchLog(); } window.clientOnline = isOnline; window.wasClientOnline = isOnline; window.updateClientStatus(isOnline); } catch (e) { window.updateClientStatus(false); } }; window.fetchLog = async function() { try { const res = await fetch('/dashboard/commands?player_id=' + window.PLAYER_ID); const cmds = await res.json(); window.cmds = cmds; // Save globally so viewer can see reserved resources window.renderLog(cmds); if (window.selectedTownId) window.renderTownDetails(); } catch (e) {} }; window.updateServerStatus = function(online) { const el = document.getElementById('server-status'); if (online) { el.textContent = '● Server'; el.className = 'conn-badge online'; } else { el.textContent = '● Server offline'; el.className = 'conn-badge offline'; } }; window.updateClientStatus = function(online) { const el = document.getElementById('client-status'); if (online) { el.textContent = '● Script'; el.className = 'conn-badge online'; } else { el.textContent = '● Script offline'; el.className = 'conn-badge offline'; } // Enable/disable the Send button const btn = document.querySelector('#command-form-wrap .btn-gold'); if (btn) { btn.disabled = !online; btn.title = online ? '' : 'Script is offline — cannot send commands'; btn.style.opacity = online ? '' : '0.4'; btn.style.cursor = online ? '' : 'not-allowed'; } }; window.sendCommand = async function() { if (!window.clientOnline) return alert('Το script είναι offline — δεν μπορείτε να στείλετε εντολές.'); const town = window.getSelectedTown(); if (!town) return alert('Select a town first.'); const type = document.getElementById('cmd-type').value; if (!type) return alert('Παρακαλώ επιλέξτε Ενέργεια (Command Type) πρώτα.'); let payload = {}; if (type === 'build') { const building_id = window.selectedBuildingId; if (!building_id) return alert('Παρακαλώ επιλέξτε Κατασκευή από τον πίνακα.'); const bData = town.build_data?.[building_id]; // UI Validation logic if (bData) { const missingDeps = bData.missing_dependencies || {}; const missingKeys = Object.keys(missingDeps); // 1. Popup if dependencies are lacking if (missingKeys.length > 0) { let msg = '⚠ ΑΔΥΝΑΤΗ Η ΚΑΤΑΣΚΕΥΗ: Λείπουν προϋποθέσεις!\n\nΑπαιτείται:\n'; for (const k of missingKeys) { msg += `- ${missingDeps[k].name} (Επίπεδο ${missingDeps[k].needed_level})\n`; } return alert(msg); } // 2. Confirmation if resources are lacking if (bData.enough_resources === false) { if (!confirm('❌ Δεν έχετε αρκετούς πόρους!\n\nΘέλετε να προστεθεί στην ουρά αναμονής; Το σύστημα θα το χτίσει αυτόματα μόλις συγκεντρωθούν οι πόροι.')) { return; } } } payload = { building_id }; } else if (type === 'recruit') { const unit_id = document.getElementById('unit-select').value; if (!unit_id) return alert('Παρακαλώ επιλέξτε Μονάδα προς εκπαίδευση.'); const amount = parseInt(document.getElementById('recruit-amount').value) || 1; const uData = town.unit_data?.[unit_id]; // UI Validation logic if (uData) { const missingDeps = uData.missing_dependencies || {}; const missingKeys = Object.keys(missingDeps); // 1. Popup if dependencies are lacking if (missingKeys.length > 0) { let msg = '⚠ ΑΔΥΝΑΤΗ Η ΕΚΠΑΙΔΕΥΣΗ: Λείπουν προϋποθέσεις!\n\nΑπαιτείται:\n'; for (const k of missingKeys) { msg += `- ${missingDeps[k].name || k} (Επίπεδο ${missingDeps[k].needed_level || '1'})\n`; } return alert(msg); } // 2. Confirmation if resources are lacking for 1x if (uData.enough_resources === false) { if (!confirm(`❌ Δεν έχετε αρκετούς πόρους για 1x ${unit_id}!\n\nΘέλετε να προστεθεί στην ουρά αναμονής; Το σύστημα θα εκπαιδεύσει τον στρατό μόλις συγκεντρωθούν οι πόροι.`)) { return; } } } payload = { unit_id, amount }; } else if (type === 'market_offer') { const offer = parseInt(document.getElementById('market-offer-amount').value) || 1000; const offer_type = document.getElementById('market-offer-type').value; const demand = parseInt(document.getElementById('market-demand-amount').value) || 1000; const demand_type = document.getElementById('market-demand-type').value; const max_delivery_time = parseInt(document.getElementById('market-max-time').value) || 1800; const visibility = document.getElementById('market-visibility').value; payload = { offer, offer_type, demand, demand_type, max_delivery_time, visibility }; } try { const res = await fetch('/dashboard/commands', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ town_id: town.town_id, town_name: town.town_name, type, payload, player_id: window.PLAYER_ID }) }); const data = await res.json(); if (data.ok) { window.fetchLog(); } else if (data.error === 'client_offline') { alert(data.message || 'Το script είναι offline.'); } else { alert('Error: ' + JSON.stringify(data)); } } catch (e) { alert('Failed to send command: ' + e); } }; window.cancelCommand = async function(id) { await fetch(`/dashboard/commands/${id}`, { method: 'DELETE' }); window.fetchLog(); }; window.fetchCaptchaStatus = async function() { try { const res = await fetch('/dashboard/captcha-status?player_id=' + window.PLAYER_ID); const data = await res.json(); const banner = document.getElementById('captcha-banner'); if (!banner) return; if (data.captcha_active) { // Only show it if the user hasn't explicitly clicked 'close' for this specific alert if (banner.dataset.dismissed !== '1') { banner.style.display = 'flex'; } } else { // Captcha cleared from the game - hide banner and reset dismiss state for next time banner.style.display = 'none'; banner.dataset.dismissed = '0'; } } catch (e) {} }; window.dismissCaptchaBanner = function() { const banner = document.getElementById('captcha-banner'); if (banner) { banner.style.display = 'none'; banner.dataset.dismissed = '1'; } }; window.requestLiveSync = async function() { const btn = document.getElementById('live-btn'); const originalText = btn.textContent; btn.textContent = '⏳ Requesting...'; btn.disabled = true; try { const res = await fetch('/api/sync-request?player_id=' + window.PLAYER_ID, { method: 'POST' }); const data = await res.json(); if (data.ok) { btn.textContent = '✅ Requested!'; setTimeout(() => { btn.textContent = originalText; btn.disabled = false; }, 5000); } } catch (e) { btn.textContent = '❌ Failed'; setTimeout(() => { btn.textContent = originalText; btn.disabled = false; }, 3000); } };