// ================================================================ // 05_main.js — Poll loop, command dispatch, boot // Depends on: everything above // ================================================================ // Shared farm state — prevents auto-farm and explicit farm_loot commands // from running concurrently. Also caches last-known farm settings so the // auto-farm loop doesn't need its own API call. let farmLootRunning = false; let lastKnownFarmSettings = {}; // Loot option → cooldown ms (matches game's farm timer options) const LOOT_TIMINGS = { 1: 300000, 2: 1200000, 3: 5400000, 4: 14400000 }; // ---------------------------------------------------------------- // scheduleNextFarm — fires autoFarmLoop once, then reschedules // Delay = loot_option cooldown + random 30-120s human jitter. // This mirrors ModernBot's pattern: run exactly when farms are ready. // ---------------------------------------------------------------- function scheduleNextFarm() { const option = lastKnownFarmSettings.loot_option || 1; const baseMs = LOOT_TIMINGS[option] || 300000; const jitterMs = randInt(30000, 120000); // +30 to +120 s const totalMs = baseMs + jitterMs; log(`⏰ Next auto-farm in ${(totalMs / 60000).toFixed(1)} min (option ${option} + ${(jitterMs/1000).toFixed(0)}s jitter)`); setTimeout(async () => { await autoFarmLoop(); scheduleNextFarm(); }, totalMs); } // ---------------------------------------------------------------- // pollAndExecute — runs every 8–18 s (main command loop) // Handles builds, recruits, market, research, explicit farm commands. // Auto-farm has its own separate loop below. // ---------------------------------------------------------------- async function pollAndExecute() { if (paused) return; const player_id = uw.Game?.player_id; if (!player_id) return; let cmdData; try { const res = await apiFetch(`${BASE_URL}/api/commands/pending?player_id=${player_id}`); cmdData = await res.json(); } catch (e) { log(`Poll failed: ${e}`); return; } // Cache farm + bot settings so autonomous loops can read them without extra calls lastKnownFarmSettings = cmdData.farm_settings || {}; lastKnownBotSettings = cmdData.bot_settings || {}; // Feature flags — default to all on if server doesn't send them (backward compatible) const features = cmdData.enabled_features || ['farm', 'admin']; const farmOn = features.includes('farm'); const adminOn = features.includes('admin'); // Build: one command per town (server returns an array) const buildCmds = adminOn ? (cmdData.builds || []) : []; const recruitCmd = adminOn ? cmdData.recruit : null; const marketCmd = adminOn ? cmdData.market : null; const researchCmd = adminOn ? cmdData.research : null; const farmCmd = farmOn ? cmdData.farm : null; const farmUpgradeCmd = farmOn ? cmdData.farm_upgrade : null; if (cmdData.sync_requested) { log('Sync requested by server — pushing state immediately'); pushState(); } const execute = async (cmd) => { if (!cmd) return; log(`Executing command #${cmd.id} — type:${cmd.type} town:${cmd.town_id}`); if (paused) { log(`[Paused] Ignoring command #${cmd.id}`); return; } let result; try { 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_upgrade') result = await executeFarmUpgrade(cmd); else if (cmd.type === 'farm_loot') { // Guard: if auto-farm is mid-run, requeue rather than overlap if (farmLootRunning) { result = { ok: false, requeue: true, msg: 'Auto-farm in progress — requeueing' }; } else { farmLootRunning = true; try { result = await executeFarmLoot(cmd); } finally { farmLootRunning = false; } } } else result = { ok: false, msg: `Unknown type: ${cmd.type}` }; } catch (e) { result = { ok: false, msg: `Exception: ${e}` }; } const finalStatus = result.requeue ? 'pending' : (result.ok ? 'done' : 'failed'); log(`Command #${cmd.id}: ${finalStatus === 'done' ? '✅' : finalStatus === 'pending' ? '⏳' : '❌'} ${result.msg}`); reportResult(cmd.id, finalStatus, result.msg); }; // Execute one build command per town (simultaneous queue draining across all villages) for (let i = 0; i < buildCmds.length; i++) { await execute(buildCmds[i]); if (i < buildCmds.length - 1) { // Random inter-town gap — avoids looking like a macro const gap = randInt(1500, 3000); log(`Build: town done. Waiting ${gap}ms before next town...`); await sleep(gap); } } await execute(recruitCmd); await execute(marketCmd); await execute(researchCmd); await execute(farmCmd); await execute(farmUpgradeCmd); } // ---------------------------------------------------------------- // autoFarmLoop — runs every 60–120 s (independent of main poll) // Checks warehouse capacity and loots ready farms automatically. // Completely decoupled from pollAndExecute so builds/recruits // are never blocked by the long inter-island farm delays. // ---------------------------------------------------------------- async function autoFarmLoop() { if (paused) return; const farmSettings = lastKnownFarmSettings; if (!farmSettings.enabled) return; // Don't overlap with an explicit farm_loot command running in main loop if (farmLootRunning) { log('Auto-farm: explicit farm_loot in progress — skipping this cycle'); return; } // Check if any farms are actually ready before doing anything heavy const nowTs = Math.floor(Date.now() / 1000); let readyFarms = []; try { const coll = uw.MM.getOnlyCollectionByName('FarmTownPlayerRelation'); readyFarms = coll?.models?.filter(r => r.attributes.relation_status === 1 && (r.attributes.lootable_at || 0) <= nowTs ) || []; } catch (e) { return; } if (readyFarms.length === 0) return; log(`⚡ Auto-farm: ${readyFarms.length} ready farms found`); // Check if ALL warehouses are already full (>95%) — no point looting const towns = Object.values(uw.ITowns?.towns || {}); let allFull = true; for (const town of towns) { const res = town.resources?.() || {}; let storage = town.getStorageCapacity?.() || 0; if (!storage) { const buildings = town.buildings?.()?.attributes || {}; const storageLevel = buildings.storage ?? 0; const gd = uw.GameData?.buildingData?.storage; storage = gd?.max_storage?.[storageLevel] || gd?.storage?.[storageLevel] || 0; } if (!storage) storage = res.capacity || res.storage_capacity || res.storage || 0; if (!storage) continue; const maxRes = Math.max(res.wood || 0, res.stone || 0, res.iron || 0); if (maxRes / storage < 0.95) { allFull = false; break; } } const player_id = uw.Game?.player_id; if (allFull) { log('⚠️ Auto-farm: ALL warehouses are full (>95%) — skipping loot this cycle'); try { await apiFetch(`${BASE_URL}/api/farm_status?player_id=${player_id}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ warehouse_full: true }) }); } catch (e) {} return; } // All clear — run the loot farmLootRunning = true; try { await executeFarmLoot({ payload: { loot_option: farmSettings.loot_option } }); // Report success so dashboard shows last_farmed_at try { await apiFetch(`${BASE_URL}/api/farm_status?player_id=${player_id}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ warehouse_full: false }) }); } catch (e) {} pushState(); } finally { farmLootRunning = false; } } // ---------------------------------------------------------------- // Boot — works whether page is already loaded or not. // When eval()'d dynamically the 'load' event has already fired, // so we check readyState and boot immediately in that case. // ---------------------------------------------------------------- function boot() { log('Grepolis Remote Control v4.2.0 (remote) loaded'); detectCaptcha(); setTimeout(pushState, 5000); jitterLoop(pushState, 60000, 120000); // state sync every 1–2 min jitterLoop(pollAndExecute, 8000, 18000); // command poll every 8–18 s scheduleNextFarm(); // auto-farm timer-based (loot_option + 30–120s) jitterLoop(autoBootcampLoop, 720000, 1320000); // bootcamp every 12–22 min jitterLoop(autoRuralTradeLoop, 1500000, 2700000); // rural trade every 25–45 min } if (document.readyState === 'complete') { // Page already loaded (normal case when eval()'d dynamically) boot(); } else { // Fallback: wait for load event (shouldn't happen but safe to keep) window.addEventListener('load', boot); }