156 lines
6.3 KiB
JavaScript
156 lines
6.3 KiB
JavaScript
// ================================================================
|
|
// 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;
|
|
}
|
|
|
|
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;
|
|
|
|
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: if enabled, claim all ready farms (no explicit command needed)
|
|
const farmSettings = cmdData.farm_settings || {};
|
|
if (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);
|
|
}
|