// ================================================================ // 05_main.js — Poll loop, command dispatch, boot // Depends on: everything above // ================================================================ 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; } // 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'); const buildCmd = adminOn ? cmdData.build : null; 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_loot') result = await executeFarmLoot(cmd); else if (cmd.type === 'farm_upgrade') result = await executeFarmUpgrade(cmd); 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); }; // Run sequentially — humans cannot perform 3 actions simultaneously! await execute(buildCmd); await execute(recruitCmd); await execute(marketCmd); await execute(researchCmd); await execute(farmCmd); await execute(farmUpgradeCmd); // Auto-farm: only if farm feature is enabled const farmSettings = cmdData.farm_settings || {}; if (farmOn && farmSettings.enabled && !farmCmd) { 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) { /* silent */ } if (readyFarms.length > 0) { let allFull = true; let claimedAny = false; const towns = Object.values(uw.ITowns?.towns || {}); for (const town of towns) { // Use same multi-strategy lookup as gatherState() — res.storage is often 0 in Grepolis 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; const wood = res.wood || 0; const stone = res.stone || 0; const iron = res.iron || 0; if (!storage) continue; const maxRes = Math.max(wood, stone, iron); const pct = maxRes / storage; if (pct < 0.95) { allFull = false; log(`⚡ Auto-farm: looting into town ${town.get?.('name')} (${Math.round(pct * 100)}% full)`); await executeFarmLoot({ payload: { loot_option: farmSettings.loot_option } }); claimedAny = true; pushState(); break; } } if (allFull) { log('⚠️ Auto-farm: ALL warehouses are full (>95%) — skipping loot this cycle'); try { await apiFetch(`${BASE_URL}/api/farm_status?player_id=${uw.Game.player_id}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ warehouse_full: true }) }); } catch (e) {} } else if (claimedAny) { try { await apiFetch(`${BASE_URL}/api/farm_status?player_id=${uw.Game.player_id}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ warehouse_full: false }) }); } catch (e) {} } } } } // ---------------------------------------------------------------- // 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.0.0 (remote) loaded'); detectCaptcha(); setTimeout(pushState, 5000); jitterLoop(pushState, 60000, 120000); jitterLoop(pollAndExecute, 8000, 18000); } 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); }