diff --git a/GrepolisRemoteControl.user.js b/GrepolisRemoteControl.user.js index d5b7838..6401da7 100644 --- a/GrepolisRemoteControl.user.js +++ b/GrepolisRemoteControl.user.js @@ -787,6 +787,35 @@ 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) // ---------------------------------------------------------------- @@ -808,6 +837,7 @@ const buildCmd = cmdData.build; const recruitCmd = cmdData.recruit; const marketCmd = cmdData.market; + const researchCmd = cmdData.research; const farmCmd = cmdData.farm; const farmUpgradeCmd = cmdData.farm_upgrade; @@ -826,6 +856,7 @@ if (cmd.type === 'build') result = await executeBuild(cmd); else if (cmd.type === 'recruit') result = await executeRecruit(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_upgrade') result = await executeFarmUpgrade(cmd); else result = { ok: false, msg: `Unknown type: ${cmd.type}` }; @@ -841,6 +872,7 @@ await execute(buildCmd); await execute(recruitCmd); await execute(marketCmd); + await execute(researchCmd); await execute(farmCmd); await execute(farmUpgradeCmd); @@ -867,7 +899,7 @@ // Boot // ---------------------------------------------------------------- window.addEventListener('load', () => { - log('Grepolis Remote Control v3.5 loaded'); + log('Grepolis Remote Control v3.5.9 loaded'); // Start captcha watcher immediately detectCaptcha(); diff --git a/routes/api.py b/routes/api.py index ac504de..e67ac0b 100644 --- a/routes/api.py +++ b/routes/api.py @@ -100,6 +100,7 @@ def get_pending_command(): market_cmd = _fetch_pending_of_type(c, 'market_offer', 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) + research_cmd = _fetch_pending_of_type(c, 'research', player_id) sync_req = _check_and_reset_sync(c, player_id) # Also return current farm settings so TM knows loot_option @@ -118,6 +119,7 @@ def get_pending_command(): 'build': build_cmd, 'recruit': recruit_cmd, 'market': market_cmd, + 'research': research_cmd, 'farm': farm_cmd, 'farm_upgrade': farm_upgrade_cmd, 'farm_settings': farm_settings, diff --git a/static/css/styles.css b/static/css/styles.css index f3b67a5..03a792d 100644 --- a/static/css/styles.css +++ b/static/css/styles.css @@ -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; position: fixed; inset: 0; @@ -325,9 +325,9 @@ tr:hover td { background: #1e1e40; } align-items: 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; border: 2px solid #c8a44a; border-radius: 10px; @@ -342,7 +342,7 @@ tr:hover td { background: #1e1e40; } from { transform: scale(0.92); opacity: 0; } to { transform: scale(1); opacity: 1; } } -#building-modal-header { +#building-modal-header, #academy-modal-header { display: flex; justify-content: space-between; align-items: center; @@ -350,12 +350,12 @@ tr:hover td { background: #1e1e40; } padding-bottom: 10px; border-bottom: 1px solid #2a4a6a; } -#building-modal-header h3 { +#building-modal-header h3, #academy-modal-header h3 { color: #c8a44a; font-size: 1rem; letter-spacing: 0.5px; } -#building-modal-close { +#building-modal-close, #academy-modal-close { background: none; border: none; color: #888; @@ -364,7 +364,7 @@ tr:hover td { background: #1e1e40; } line-height: 1; padding: 0 4px; } -#building-modal-close:hover { color: #fff; } +#building-modal-close:hover, #academy-modal-close:hover { color: #fff; } #building-grid { display: grid; @@ -436,3 +436,26 @@ tr:hover td { background: #1e1e40; } @media (max-width: 600px) { #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; +} + diff --git a/static/js/api.js b/static/js/api.js index 4e14fb9..796fb1f 100644 --- a/static/js/api.js +++ b/static/js/api.js @@ -164,6 +164,18 @@ window.sendCommand = async function() { 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 { diff --git a/static/js/components/commandForm.js b/static/js/components/commandForm.js index 3519db4..d76c9d2 100644 --- a/static/js/components/commandForm.js +++ b/static/js/components/commandForm.js @@ -8,6 +8,7 @@ window.onCmdTypeChange = function() { document.getElementById('recruit-options').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('research-options').style.display = type === 'research' ? '' : 'none'; }; // 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); }; +// ================================================================ +// 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 += `