enchance farming/fix
This commit is contained in:
@@ -3,6 +3,17 @@
|
||||
// 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 = {};
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// 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;
|
||||
@@ -17,13 +28,16 @@ async function pollAndExecute() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Cache farm settings so autoFarmLoop can read them without an extra call
|
||||
lastKnownFarmSettings = cmdData.farm_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 buildCmds = adminOn ? (cmdData.builds || []) : [];
|
||||
const recruitCmd = adminOn ? cmdData.recruit : null;
|
||||
const marketCmd = adminOn ? cmdData.market : null;
|
||||
const researchCmd = adminOn ? cmdData.research : null;
|
||||
@@ -49,8 +63,17 @@ async function pollAndExecute() {
|
||||
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 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}` };
|
||||
@@ -76,88 +99,103 @@ async function pollAndExecute() {
|
||||
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;
|
||||
// ----------------------------------------------------------------
|
||||
// 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 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 farmSettings = lastKnownFarmSettings;
|
||||
if (!farmSettings.enabled) return;
|
||||
|
||||
const wood = res.wood || 0;
|
||||
const stone = res.stone || 0;
|
||||
const iron = res.iron || 0;
|
||||
if (!storage) continue;
|
||||
// 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;
|
||||
}
|
||||
|
||||
const maxRes = Math.max(wood, stone, iron);
|
||||
const pct = maxRes / storage;
|
||||
// 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 (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 (readyFarms.length === 0) return;
|
||||
log(`⚡ Auto-farm: ${readyFarms.length} ready farms found`);
|
||||
|
||||
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) {}
|
||||
}
|
||||
// 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.0.0 (remote) loaded');
|
||||
log('Grepolis Remote Control v4.1.0 (remote) loaded');
|
||||
detectCaptcha();
|
||||
setTimeout(pushState, 5000);
|
||||
jitterLoop(pushState, 60000, 120000);
|
||||
jitterLoop(pollAndExecute, 8000, 18000);
|
||||
jitterLoop(pushState, 60000, 120000); // state sync every 1-2 min
|
||||
jitterLoop(pollAndExecute, 8000, 18000); // command poll every 8-18 s
|
||||
jitterLoop(autoFarmLoop, 60000, 120000); // auto-farm every 1-2 min (independent)
|
||||
}
|
||||
|
||||
if (document.readyState === 'complete') {
|
||||
|
||||
Reference in New Issue
Block a user