Files
grepo-remote/static/js/api.js
2026-04-25 16:01:15 +03:00

390 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ================================================================
// 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 };
} else if (type === 'research') {
const research_id = window.selectedResearchId;
if (!research_id) return alert('Παρακαλώ επιλέξτε Έρευνα από την Ακαδημία.');
const academyLevel = town.buildings?.academy || 0;
const rData = window.RESEARCH_DATA[research_id];
if (rData && academyLevel < rData.academy_level) {
return alert(`❌ ΑΔΥΝΑΤΗ Η ΕΡΕΥΝΑ: Απαιτείται Ακαδημία Επίπεδο ${rData.academy_level} (Έχετε ${academyLevel})`);
}
payload = { research_id };
}
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!';
}, 5000);
}
} catch (e) {
btn.textContent = '❌ Failed';
setTimeout(() => {
btn.textContent = originalText;
btn.disabled = false;
}, 3000);
}
};
window.scanMarket = async function() {
if (!window.clientOnline) return alert('Το script είναι offline.');
const town = window.getSelectedTown();
if (!town) return alert('Select a town first.');
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: 'scan_market',
payload: {},
player_id: window.PLAYER_ID
})
});
const data = await res.json();
if (data.ok) {
document.getElementById('market-offers-content').innerHTML = '<span style="color:#c8a44a;">⏳ Αναζήτηση σε εξέλιξη... Περιμένετε!</span>';
window.fetchLog();
// Auto fetch market data every 2 seconds for the next 15 seconds
let attempts = 0;
const interval = setInterval(() => {
window.fetchMarketData();
attempts++;
if (attempts > 7) clearInterval(interval);
}, 2000);
} else {
alert('Error: ' + JSON.stringify(data));
}
} catch (e) {
alert('Failed to send scan_market command: ' + e);
}
};
window.acceptMarketOffer = async function(offer_id, amount) {
if (!window.clientOnline) return alert('Το script είναι offline.');
const town = window.getSelectedTown();
if (!town) return alert('Select a town first.');
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: 'accept_market_offer',
payload: { offer_id: parseInt(offer_id), amount: parseInt(amount) },
player_id: window.PLAYER_ID
})
});
const data = await res.json();
if (data.ok) {
window.fetchLog();
alert(`Εστάλη εντολή αποδοχής για την προσφορά #${offer_id}!`);
// Update local UI immediately so user doesn't double click
document.getElementById(`offer-btn-${offer_id}`).disabled = true;
document.getElementById(`offer-btn-${offer_id}`).innerText = '⏳';
} else {
alert('Error: ' + JSON.stringify(data));
}
} catch (e) {
alert('Failed to send accept_market_offer command: ' + e);
}
};
window.lastMarketUpdate = null;
window.fetchMarketData = async function() {
try {
const res = await fetch('/dashboard/market-data?player_id=' + window.PLAYER_ID);
const data = await res.json();
if (!data || !data.data || !data.data.offers) return;
// Only re-render if the timestamp has changed
if (window.lastMarketUpdate === data.updated_at) return;
window.lastMarketUpdate = data.updated_at;
const offers = data.data.offers;
if (offers.length === 0) {
document.getElementById('market-offers-content').innerHTML = '<span style="color:#aaa;">Καμία διαθέσιμη προσφορά.</span>';
return;
}
let html = `<table style="width:100%; border-collapse:collapse; font-size:0.75rem;">
<tr style="border-bottom:1px solid #2a4a6a; color:#c8a44a;">
<th style="padding:4px; text-align:left;">Παίκτης</th>
<th style="padding:4px; text-align:right;">Προσφέρει</th>
<th style="padding:4px; text-align:right;">Ζητάει</th>
<th style="padding:4px; text-align:center;">Λόγος</th>
<th style="padding:4px; text-align:center;">Διάρκεια</th>
<th style="padding:4px;"></th>
</tr>`;
for (const off of offers) {
const ratio = (off.demand / off.offer).toFixed(2);
// Map resources to emoji
const offerEmoji = off.offer_type === 'wood' ? '🪵' : off.offer_type === 'stone' ? '🪨' : '🪙';
const demandEmoji = off.demand_type === 'wood' ? '🪵' : off.demand_type === 'stone' ? '🪨' : '🪙';
let seconds = off.delivery_time;
let h = Math.floor(seconds / 3600);
let m = Math.floor((seconds % 3600) / 60);
let timeStr = `${h}h ${m}m`;
html += `<tr style="border-bottom:1px solid #1a3a5a;">
<td style="padding:6px 4px; color:#ddd;">${off.player_name || 'NPC'}</td>
<td style="padding:6px 4px; text-align:right; color:#88aa55;">${off.offer} ${offerEmoji}</td>
<td style="padding:6px 4px; text-align:right; color:#ee4444;">${off.demand} ${demandEmoji}</td>
<td style="padding:6px 4px; text-align:center; color:#aaa;">1 : ${ratio}</td>
<td style="padding:6px 4px; text-align:center; color:#aaa;">${timeStr}</td>
<td style="padding:6px 4px; text-align:right;">
<button id="offer-btn-${off.id}" class="btn btn-gold btn-sm" onclick="window.acceptMarketOffer(${off.id}, ${off.offer})">✓</button>
</td>
</tr>`;
}
html += '</table>';
// Add timestamp note
let d = new Date(data.updated_at + "Z"); // SQLite UTC
html += `<div style="text-align:right; font-size:0.65rem; color:#666; margin-top:5px; padding-right:5px;">Τελευταία ανανέωση: ${d.toLocaleTimeString()}</div>`;
document.getElementById('market-offers-content').innerHTML = html;
} catch (e) {
console.error('Failed to fetch market data:', e);
}
};