academy mode

This commit is contained in:
2026-04-25 15:18:56 +03:00
parent 22a379c2a1
commit 853525d8ad
6 changed files with 213 additions and 10 deletions

View File

@@ -787,6 +787,35 @@
return { ok: true, msg: `Market offer posted: ${offer} ${offer_type} => ${demand} ${demand_type}` }; return { ok: true, msg: `Market offer posted: ${offer} ${offer_type} => ${demand} ${demand_type}` };
} }
// ----------------------------------------------------------------
// Execute: Research (Academy)
// ----------------------------------------------------------------
async function executeResearch(cmd) {
const { town_id, payload } = cmd;
const { research_id } = payload;
const town = uw.ITowns?.getTown?.(town_id) || uw.ITowns?.towns?.[town_id];
if (!town) {
return { ok: false, msg: `Town ${town_id} not found` };
}
const reactionMs = randInt(800, 2500);
log(`Waiting ${reactionMs}ms before firing research (reaction time)...`);
await sleep(reactionMs);
if (paused) return { ok: false, msg: 'Aborted due to pause/captcha' };
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', {
model_url: 'ResearchOrder',
action_name: 'research',
arguments: { id: research_id },
town_id: town_id
});
await sleep(500);
return { ok: true, msg: `Research ${research_id} queued` };
}
// ---------------------------------------------------------------- // ----------------------------------------------------------------
// Poll for and execute pending commands (build + recruit + market) // Poll for and execute pending commands (build + recruit + market)
// ---------------------------------------------------------------- // ----------------------------------------------------------------
@@ -808,6 +837,7 @@
const buildCmd = cmdData.build; const buildCmd = cmdData.build;
const recruitCmd = cmdData.recruit; const recruitCmd = cmdData.recruit;
const marketCmd = cmdData.market; const marketCmd = cmdData.market;
const researchCmd = cmdData.research;
const farmCmd = cmdData.farm; const farmCmd = cmdData.farm;
const farmUpgradeCmd = cmdData.farm_upgrade; const farmUpgradeCmd = cmdData.farm_upgrade;
@@ -826,6 +856,7 @@
if (cmd.type === 'build') result = await executeBuild(cmd); if (cmd.type === 'build') result = await executeBuild(cmd);
else if (cmd.type === 'recruit') result = await executeRecruit(cmd); else if (cmd.type === 'recruit') result = await executeRecruit(cmd);
else if (cmd.type === 'market_offer') result = await executeMarketOffer(cmd); else if (cmd.type === 'market_offer') result = await executeMarketOffer(cmd);
else if (cmd.type === 'research') result = await executeResearch(cmd);
else if (cmd.type === 'farm_loot') result = await executeFarmLoot(cmd); else if (cmd.type === 'farm_loot') result = await executeFarmLoot(cmd);
else if (cmd.type === 'farm_upgrade') result = await executeFarmUpgrade(cmd); else if (cmd.type === 'farm_upgrade') result = await executeFarmUpgrade(cmd);
else result = { ok: false, msg: `Unknown type: ${cmd.type}` }; else result = { ok: false, msg: `Unknown type: ${cmd.type}` };
@@ -841,6 +872,7 @@
await execute(buildCmd); await execute(buildCmd);
await execute(recruitCmd); await execute(recruitCmd);
await execute(marketCmd); await execute(marketCmd);
await execute(researchCmd);
await execute(farmCmd); await execute(farmCmd);
await execute(farmUpgradeCmd); await execute(farmUpgradeCmd);
@@ -867,7 +899,7 @@
// Boot // Boot
// ---------------------------------------------------------------- // ----------------------------------------------------------------
window.addEventListener('load', () => { window.addEventListener('load', () => {
log('Grepolis Remote Control v3.5 loaded'); log('Grepolis Remote Control v3.5.9 loaded');
// Start captcha watcher immediately // Start captcha watcher immediately
detectCaptcha(); detectCaptcha();

View File

@@ -100,6 +100,7 @@ def get_pending_command():
market_cmd = _fetch_pending_of_type(c, 'market_offer', player_id) market_cmd = _fetch_pending_of_type(c, 'market_offer', player_id)
farm_cmd = _fetch_pending_of_type(c, 'farm_loot', player_id) farm_cmd = _fetch_pending_of_type(c, 'farm_loot', player_id)
farm_upgrade_cmd = _fetch_pending_of_type(c, 'farm_upgrade', player_id) farm_upgrade_cmd = _fetch_pending_of_type(c, 'farm_upgrade', player_id)
research_cmd = _fetch_pending_of_type(c, 'research', player_id)
sync_req = _check_and_reset_sync(c, player_id) sync_req = _check_and_reset_sync(c, player_id)
# Also return current farm settings so TM knows loot_option # Also return current farm settings so TM knows loot_option
@@ -118,6 +119,7 @@ def get_pending_command():
'build': build_cmd, 'build': build_cmd,
'recruit': recruit_cmd, 'recruit': recruit_cmd,
'market': market_cmd, 'market': market_cmd,
'research': research_cmd,
'farm': farm_cmd, 'farm': farm_cmd,
'farm_upgrade': farm_upgrade_cmd, 'farm_upgrade': farm_upgrade_cmd,
'farm_settings': farm_settings, 'farm_settings': farm_settings,

View File

@@ -314,9 +314,9 @@ tr:hover td { background: #1e1e40; }
} }
/* ========================================================================== /* ==========================================================================
Building Picker Modal Building & Academy Picker Modal
========================================================================== */ ========================================================================== */
#building-modal-overlay { #building-modal-overlay, #academy-modal-overlay {
display: none; display: none;
position: fixed; position: fixed;
inset: 0; inset: 0;
@@ -325,9 +325,9 @@ tr:hover td { background: #1e1e40; }
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
#building-modal-overlay.open { display: flex; } #building-modal-overlay.open, #academy-modal-overlay.open { display: flex; }
#building-modal { #building-modal, #academy-modal {
background: #16213e; background: #16213e;
border: 2px solid #c8a44a; border: 2px solid #c8a44a;
border-radius: 10px; border-radius: 10px;
@@ -342,7 +342,7 @@ tr:hover td { background: #1e1e40; }
from { transform: scale(0.92); opacity: 0; } from { transform: scale(0.92); opacity: 0; }
to { transform: scale(1); opacity: 1; } to { transform: scale(1); opacity: 1; }
} }
#building-modal-header { #building-modal-header, #academy-modal-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
@@ -350,12 +350,12 @@ tr:hover td { background: #1e1e40; }
padding-bottom: 10px; padding-bottom: 10px;
border-bottom: 1px solid #2a4a6a; border-bottom: 1px solid #2a4a6a;
} }
#building-modal-header h3 { #building-modal-header h3, #academy-modal-header h3 {
color: #c8a44a; color: #c8a44a;
font-size: 1rem; font-size: 1rem;
letter-spacing: 0.5px; letter-spacing: 0.5px;
} }
#building-modal-close { #building-modal-close, #academy-modal-close {
background: none; background: none;
border: none; border: none;
color: #888; color: #888;
@@ -364,7 +364,7 @@ tr:hover td { background: #1e1e40; }
line-height: 1; line-height: 1;
padding: 0 4px; padding: 0 4px;
} }
#building-modal-close:hover { color: #fff; } #building-modal-close:hover, #academy-modal-close:hover { color: #fff; }
#building-grid { #building-grid {
display: grid; display: grid;
@@ -436,3 +436,26 @@ tr:hover td { background: #1e1e40; }
@media (max-width: 600px) { @media (max-width: 600px) {
#building-grid { grid-template-columns: repeat(2, 1fr); } #building-grid { grid-template-columns: repeat(2, 1fr); }
} }
/* Academy Grid specific styling */
#academy-grid {
display: flex;
gap: 12px;
overflow-x: auto;
padding-bottom: 12px;
}
.academy-col {
display: flex;
flex-direction: column;
gap: 8px;
min-width: 140px;
}
.academy-col-header {
text-align: center;
font-size: 0.8rem;
color: #c8a44a;
border-bottom: 1px solid #2a4a6a;
padding-bottom: 4px;
margin-bottom: 4px;
}

View File

@@ -164,6 +164,18 @@ window.sendCommand = async function() {
const visibility = document.getElementById('market-visibility').value; const visibility = document.getElementById('market-visibility').value;
payload = { offer, offer_type, demand, demand_type, max_delivery_time, visibility }; 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 { try {

View File

@@ -8,6 +8,7 @@ window.onCmdTypeChange = function() {
document.getElementById('recruit-options').style.display = type === 'recruit' ? '' : 'none'; document.getElementById('recruit-options').style.display = type === 'recruit' ? '' : 'none';
document.getElementById('amount-group').style.display = type === 'recruit' ? '' : 'none'; document.getElementById('amount-group').style.display = type === 'recruit' ? '' : 'none';
document.getElementById('market-options').style.display = type === 'market_offer' ? '' : 'none'; document.getElementById('market-options').style.display = type === 'market_offer' ? '' : 'none';
document.getElementById('research-options').style.display = type === 'research' ? '' : 'none';
}; };
// Building emoji icons for the visual grid // Building emoji icons for the visual grid
@@ -125,6 +126,120 @@ window.selectBuilding = function(key, nameGr) {
setTimeout(() => document.getElementById('building-modal-overlay').classList.remove('open'), 180); setTimeout(() => document.getElementById('building-modal-overlay').classList.remove('open'), 180);
}; };
// ================================================================
// Academy Research Logic
// ================================================================
window.selectedResearchId = null;
window.RESEARCH_DATA = {
"slinger": { "name": "Εκσφενδονιστές", "academy_level": 1, "wood": 300, "stone": 500, "iron": 200, "points": 4 },
"archer": { "name": "Τοξότες", "academy_level": 1, "wood": 550, "stone": 100, "iron": 400, "points": 8 },
"town_guard": { "name": "Φρουρά πόλης", "academy_level": 1, "wood": 400, "stone": 300, "iron": 300, "points": 3 },
"hoplite": { "name": "Οπλίτες", "academy_level": 4, "wood": 600, "stone": 200, "iron": 850, "points": 8 },
"meteorology": { "name": "Μετεωρολογία", "academy_level": 4, "wood": 2500, "stone": 1700, "iron": 6500, "points": 4 },
"espionage": { "name": "Κατασκοπεία", "academy_level": 7, "wood": 900, "stone": 900, "iron": 1100, "points": 3 },
"booty": { "name": "Αφοσίωση χωρικών", "academy_level": 7, "wood": 1300, "stone": 1300, "iron": 1300, "points": 6 },
"pottery": { "name": "Κεραμικά", "academy_level": 7, "wood": 700, "stone": 1500, "iron": 900, "points": 4 },
"rider": { "name": "Ιππείς", "academy_level": 10, "wood": 1400, "stone": 700, "iron": 1800, "points": 8 },
"architecture": { "name": "Αρχιτεκτονική", "academy_level": 10, "wood": 1900, "stone": 2100, "iron": 1300, "points": 6 },
"instructor": { "name": "Εκπαιδευτής", "academy_level": 10, "wood": 800, "stone": 1300, "iron": 1600, "points": 4 },
"bireme": { "name": "Διήρεις", "academy_level": 13, "wood": 2800, "stone": 1300, "iron": 2200, "points": 8 },
"building_crane": { "name": "Γερανός", "academy_level": 13, "wood": 3000, "stone": 1800, "iron": 1400, "points": 4 },
"shipwright": { "name": "Ναυπηγός", "academy_level": 13, "wood": 5000, "stone": 2000, "iron": 3900, "points": 6 },
"colonize_ship": { "name": "Αποικιακά πλοία", "academy_level": 13, "wood": 7500, "stone": 7500, "iron": 9500, "points": 0 },
"chariot": { "name": "Άρματα", "academy_level": 16, "wood": 3700, "stone": 1900, "iron": 2800, "points": 8 },
"attack_ship": { "name": "Πλοία-φάροι", "academy_level": 16, "wood": 4400, "stone": 2000, "iron": 2400, "points": 8 },
"conscription": { "name": "Στρατολόγηση", "academy_level": 16, "wood": 3800, "stone": 4200, "iron": 6000, "points": 4 },
"demolition_ship": { "name": "Πυρπολικά", "academy_level": 19, "wood": 5300, "stone": 2600, "iron": 2700, "points": 8 },
"catapult": { "name": "Καταπέλτες", "academy_level": 19, "wood": 5500, "stone": 2900, "iron": 3600, "points": 8 },
"cryptography": { "name": "Κρυπτογραφία", "academy_level": 19, "wood": 2500, "stone": 3000, "iron": 5100, "points": 6 },
"small_transporter": { "name": "Γρήγορα μεταφορικά πλοία", "academy_level": 22, "wood": 6500, "stone": 2800, "iron": 3200, "points": 8 },
"plow": { "name": "Άροτρο", "academy_level": 22, "wood": 3000, "stone": 3300, "iron": 2100, "points": 4 },
"berth": { "name": "Κουκέτες", "academy_level": 22, "wood": 8900, "stone": 5200, "iron": 7800, "points": 6 },
"trireme": { "name": "Τριήρεις", "academy_level": 25, "wood": 6500, "stone": 3800, "iron": 4700, "points": 8 },
"phalanx": { "name": "Φάλαγγα", "academy_level": 25, "wood": 4000, "stone": 4000, "iron": 15000, "points": 9 },
"breach": { "name": "Διάσπαση εχθρικού μετώπου", "academy_level": 25, "wood": 8000, "stone": 8000, "iron": 9000, "points": 6 },
"mathematics": { "name": "Μαθηματικά", "academy_level": 25, "wood": 7100, "stone": 4400, "iron": 8600, "points": 6 },
"ram": { "name": "Πολιορκητικός κριός", "academy_level": 28, "wood": 7900, "stone": 9200, "iron": 14000, "points": 10 },
"cartography": { "name": "Χαρτογραφία", "academy_level": 28, "wood": 10000, "stone": 6700, "iron": 12500, "points": 8 },
"take_over": { "name": "Κατάκτηση", "academy_level": 28, "wood": 12000, "stone": 12000, "iron": 16000, "points": 0 },
"stone_storm": { "name": "Καταιγισμός από πέτρες", "academy_level": 31, "wood": 8500, "stone": 5900, "iron": 6600, "points": 4 },
"temple_looting": { "name": "Λεηλασία ναού", "academy_level": 31, "wood": 9200, "stone": 5300, "iron": 10000, "points": 6 },
"divine_selection": { "name": "Θεϊκή επιλογή", "academy_level": 31, "wood": 10000, "stone": 8000, "iron": 12000, "points": 10 },
"combat_experience": { "name": "Εμπειρία μάχης", "academy_level": 34, "wood": 9800, "stone": 11400, "iron": 14200, "points": 6 },
"strong_wine": { "name": "Δυνατό κρασί", "academy_level": 34, "wood": 8000, "stone": 6500, "iron": 11000, "points": 4 },
"set_sail": { "name": "Σαλπάρισμα!", "academy_level": 34, "wood": 13000, "stone": 9700, "iron": 15500, "points": 8 }
};
window.openAcademyModal = function() {
const town = window.getSelectedTown();
if (!town) return;
const grid = document.getElementById('academy-grid');
// Group researches by academy level
const levels = [1, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34];
const townResearches = town.researches || {};
const townBuildings = town.buildings || {};
const townResources = town.resources || { wood: 0, stone: 0, iron: 0 };
const academyLvl = townBuildings.academy || 0;
let html = '';
for (const lvl of levels) {
const researchesInLvl = Object.entries(window.RESEARCH_DATA).filter(([k, v]) => v.academy_level === lvl);
if (researchesInLvl.length === 0) continue;
html += `<div class="academy-col">
<div class="academy-col-header">Επίπεδο ${lvl}</div>`;
for (const [key, data] of researchesInLvl) {
const isResearched = !!townResearches[key];
const isSelected = key === window.selectedResearchId;
const isLocked = academyLvl < lvl;
const noResources = townResources.wood < data.wood || townResources.stone < data.stone || townResources.iron < data.iron;
let statusClass, statusLabel, cardClass = '';
if (isResearched) {
statusClass = 'maxed'; statusLabel = '✓ Ερευνήθηκε'; cardClass = 'bld-maxed';
} else if (isLocked) {
statusClass = 'locked'; statusLabel = '🔒 Κλειδωμένο'; cardClass = 'bld-locked';
} else if (noResources) {
statusClass = 'no-resources'; statusLabel = '❌ Λείπουν Πόροι';
} else {
statusClass = 'can-build'; statusLabel = '✅ Έρευνα';
}
const clickable = !isResearched && !isLocked;
const onclick = clickable ? `onclick="window.selectResearch('${key}', '${data.name}')"` : '';
const costStr = `Ξ:${window.fmt(data.wood)} Π:${window.fmt(data.stone)} Α:${window.fmt(data.iron)}`;
html += `<div class="bld-card ${cardClass}${isSelected ? ' bld-selected' : ''}" ${onclick}>
<span class="bld-name" style="margin-top:6px; font-size:0.8rem;">${data.name}</span>
<span class="bld-status ${statusClass}">${statusLabel}</span>
<span class="bld-cost" style="color:#a88; margin-top:4px;">Πόντοι: ${data.points}</span>
<span class="bld-cost">${costStr}</span>
</div>`;
}
html += `</div>`;
}
grid.innerHTML = html;
document.getElementById('academy-modal-overlay').classList.add('open');
};
window.closeAcademyModal = function(e) {
if (e && e.target !== document.getElementById('academy-modal-overlay') && e.target !== document.getElementById('building-modal-close')) return;
document.getElementById('academy-modal-overlay').classList.remove('open');
};
window.selectResearch = function(key, name) {
window.selectedResearchId = key;
document.getElementById('selected-research-label').textContent = `🧪 ${name}`;
window.openAcademyModal();
setTimeout(() => document.getElementById('academy-modal-overlay').classList.remove('open'), 180);
};
window.renderUnitDropdown = function() { window.renderUnitDropdown = function() {

View File

@@ -96,17 +96,26 @@
<option value="build">Build / Upgrade</option> <option value="build">Build / Upgrade</option>
<option value="recruit">Recruit Troops</option> <option value="recruit">Recruit Troops</option>
<option value="market_offer">Παζάρι - Προσφορά</option> <option value="market_offer">Παζάρι - Προσφορά</option>
<option value="research">Ακαδημία - Έρευνες</option>
</select> </select>
</div> </div>
<!-- Build options - now a button that opens the visual picker --> <!-- Build options - now a button that opens the visual picker -->
<div class="form-group" id="build-options"> <div class="form-group" id="build-options" style="display:none">
<label>Building</label> <label>Building</label>
<button class="btn btn-gold" id="open-building-modal" onclick="window.openBuildingModal()" style="text-align:left; min-width:200px;"> <button class="btn btn-gold" id="open-building-modal" onclick="window.openBuildingModal()" style="text-align:left; min-width:200px;">
<span id="selected-building-label">-- Επιλέξτε Κατασκευή --</span> <span id="selected-building-label">-- Επιλέξτε Κατασκευή --</span>
</button> </button>
</div> </div>
<!-- Research options -->
<div class="form-group" id="research-options" style="display:none">
<label>Έρευνα</label>
<button class="btn btn-gold" id="open-academy-modal" onclick="window.openAcademyModal()" style="text-align:left; min-width:200px;">
<span id="selected-research-label">-- Επιλέξτε Έρευνα --</span>
</button>
</div>
<!-- Recruit options --> <!-- Recruit options -->
<div class="form-group" id="recruit-options" style="display:none"> <div class="form-group" id="recruit-options" style="display:none">
<label>Unit</label> <label>Unit</label>
@@ -241,5 +250,15 @@
</div> </div>
</div> </div>
</div> </div>
<!-- ====== Academy Picker Modal ====== -->
<div id="academy-modal-overlay" onclick="window.closeAcademyModal(event)">
<div id="academy-modal">
<div id="building-modal-header">
<h3>🦉 Ακαδημία</h3>
<button id="building-modal-close" onclick="window.closeAcademyModal()"></button>
</div>
<div id="academy-grid"></div>
</div>
</div>
</body> </body>
</html> </html>