407 lines
16 KiB
JavaScript
407 lines
16 KiB
JavaScript
// Execute: Farm Upgrade / Unlock
|
||
// Iterates all farm relations. Locked villages (status 0) get
|
||
// unlocked; unlocked villages below max level get upgraded.
|
||
// Uses random 800ms–2000ms delay between each action.
|
||
// payload.threshold = minimum kill points to keep (default 0)
|
||
// ----------------------------------------------------------------
|
||
async function executeFarmUpgrade(cmd) {
|
||
const threshold = parseInt(cmd.payload?.threshold ?? 0);
|
||
const now = Math.floor(Date.now() / 1000);
|
||
|
||
let farmModels, relModels;
|
||
try {
|
||
farmModels = uw.MM.getOnlyCollectionByName('FarmTown')?.models;
|
||
relModels = uw.MM.getOnlyCollectionByName('FarmTownPlayerRelation')?.models;
|
||
} catch (e) {
|
||
return { ok: false, msg: `Cannot access farm collections: ${e.message}` };
|
||
}
|
||
if (!farmModels || !relModels) {
|
||
return { ok: false, msg: 'Farm collections not loaded yet' };
|
||
}
|
||
|
||
// Build polis list (one town per island)
|
||
const islandsSeen = new Set();
|
||
const polisList = [];
|
||
try {
|
||
for (const town of uw.MM.getCollections().Town[0].models) {
|
||
const { on_small_island, island_id, id } = town.attributes;
|
||
if (on_small_island) continue;
|
||
if (!islandsSeen.has(island_id)) {
|
||
islandsSeen.add(island_id);
|
||
polisList.push(id);
|
||
}
|
||
}
|
||
} catch (e) {
|
||
return { ok: false, msg: `Cannot build town list: ${e.message}` };
|
||
}
|
||
|
||
let upgraded = 0;
|
||
let unlocked = 0;
|
||
let skipped = 0;
|
||
let errors = 0;
|
||
|
||
for (const town_id of polisList) {
|
||
const town = uw.ITowns?.towns?.[town_id];
|
||
if (!town) continue;
|
||
const ix = town.getIslandCoordinateX();
|
||
const iy = town.getIslandCoordinateY();
|
||
if (ix == null || iy == null) continue;
|
||
|
||
for (const farm of farmModels) {
|
||
if (farm.attributes.island_x !== ix || farm.attributes.island_y !== iy) continue;
|
||
|
||
for (const rel of relModels) {
|
||
if (rel.attributes.farm_town_id !== farm.attributes.id) continue;
|
||
|
||
const status = rel.attributes.relation_status;
|
||
const level = rel.attributes.expansion_stage || 0;
|
||
const expAt = rel.attributes.expansion_at || 0;
|
||
|
||
// Skip if upgrade already in progress
|
||
if (expAt > now) { skipped++; continue; }
|
||
// Skip if already max level
|
||
if (status === 1 && level >= 5) { skipped++; continue; }
|
||
// Skip if locked and we can't unlock (status -1 means enemy)
|
||
if (status < 0) { skipped++; continue; }
|
||
|
||
const isLocked = status === 0;
|
||
const action = isLocked ? 'unlock' : 'upgrade';
|
||
|
||
const requestedAction = cmd.payload?.action_type;
|
||
if (requestedAction && requestedAction !== action) {
|
||
skipped++; continue;
|
||
}
|
||
|
||
if (paused) {
|
||
return { ok: false, msg: 'Aborted due to pause/captcha' };
|
||
}
|
||
|
||
log(`Farm ${action}: farm_id=${farm.attributes.id} level=${level} town=${town_id}`);
|
||
try {
|
||
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', {
|
||
model_url: `FarmTownPlayerRelation/${rel.id}`,
|
||
action_name: action,
|
||
arguments: { farm_town_id: farm.attributes.id },
|
||
town_id
|
||
});
|
||
isLocked ? unlocked++ : upgraded++;
|
||
} catch (e) { errors++; }
|
||
|
||
// Random delay between actions: 1200ms – 2500ms
|
||
await sleep(randInt(1200, 2500));
|
||
}
|
||
}
|
||
}
|
||
|
||
pushState(); // refresh farm data after upgrades
|
||
return { ok: true, msg: `Farm upgrade done: ${unlocked} unlocked, ${upgraded} upgraded, ${skipped} skipped, ${errors} errors` };
|
||
}
|
||
|
||
// ----------------------------------------------------------------
|
||
// Execute: Farm Loot
|
||
// Claims all ready farm towns across all towns that match
|
||
// the cmd payload (town_ids list + loot_option).
|
||
// Between-claim delay: random 500ms–1500ms (never below 500ms)
|
||
// Between-town-group delay: random 30s–90s
|
||
// ----------------------------------------------------------------
|
||
async function executeFarmLoot(cmd) {
|
||
const { loot_option } = cmd.payload || {};
|
||
const option = parseInt(loot_option) || 1;
|
||
const now = Math.floor(Date.now() / 1000);
|
||
|
||
let farmModels, relModels;
|
||
try {
|
||
farmModels = uw.MM.getOnlyCollectionByName('FarmTown')?.models;
|
||
relModels = uw.MM.getOnlyCollectionByName('FarmTownPlayerRelation')?.models;
|
||
} catch (e) {
|
||
return { ok: false, msg: `Cannot access farm collections: ${e.message}` };
|
||
}
|
||
if (!farmModels || !relModels) {
|
||
return { ok: false, msg: 'Farm collections not loaded yet — open island view first' };
|
||
}
|
||
|
||
// Build island groups using MM (all towns, not just visible)
|
||
const islandTownsMap = {}; // island_id -> [town_id1, town_id2]
|
||
try {
|
||
const allTowns = uw.MM.getCollections().Town[0].models;
|
||
for (const town of allTowns) {
|
||
const { on_small_island, island_id, id } = town.attributes;
|
||
if (on_small_island) continue;
|
||
if (!islandTownsMap[island_id]) islandTownsMap[island_id] = [];
|
||
islandTownsMap[island_id].push(id);
|
||
}
|
||
} catch (e) {
|
||
return { ok: false, msg: `Cannot build town list: ${e.message}` };
|
||
}
|
||
const islandList = Object.keys(islandTownsMap);
|
||
|
||
log(`Farm: processing ${islandList.length} islands with option=${option}`);
|
||
|
||
let claimed = 0;
|
||
let skipped = 0;
|
||
let errors = 0;
|
||
|
||
for (let i = 0; i < islandList.length; i++) {
|
||
const island_id = islandList[i];
|
||
const townIds = islandTownsMap[island_id];
|
||
|
||
let selected_town_id = null;
|
||
let lowest_total_res = Infinity;
|
||
|
||
for (const t_id of townIds) {
|
||
const t = uw.ITowns?.towns?.[t_id];
|
||
if (!t) continue;
|
||
|
||
let storageCapacity = t.getStorageCapacity?.() || 0;
|
||
if (!storageCapacity) {
|
||
const buildings = t.buildings?.()?.attributes || {};
|
||
const storageLevel = buildings.storage ?? 0;
|
||
const gd = uw.GameData?.buildingData?.storage;
|
||
storageCapacity = gd?.max_storage?.[storageLevel] || gd?.storage?.[storageLevel] || 0;
|
||
}
|
||
|
||
const res = t.resources?.() || {};
|
||
const w = res.wood || 0;
|
||
const s = res.stone || 0;
|
||
const ir = res.iron || 0;
|
||
|
||
// If completely full (all 3 resources >= max storage), skip this town
|
||
if (storageCapacity > 0 && w >= storageCapacity && s >= storageCapacity && ir >= storageCapacity) {
|
||
continue;
|
||
}
|
||
|
||
// Pick town with most space (lowest total resources)
|
||
const total_res = w + s + ir;
|
||
if (total_res < lowest_total_res) {
|
||
lowest_total_res = total_res;
|
||
selected_town_id = t_id;
|
||
}
|
||
}
|
||
|
||
if (!selected_town_id) {
|
||
log(`Farm: Skipping island ${island_id} (All towns are 100% full)`);
|
||
skipped++;
|
||
continue;
|
||
}
|
||
|
||
const town_id = selected_town_id;
|
||
const town = uw.ITowns?.towns?.[town_id];
|
||
if (!town) { skipped++; continue; }
|
||
|
||
// Use the same method as the original script
|
||
const ix = town.getIslandCoordinateX();
|
||
const iy = town.getIslandCoordinateY();
|
||
|
||
if (ix == null || iy == null) { skipped++; continue; }
|
||
|
||
// Find ready farms on this island (mirrors original getLootableFarms exactly)
|
||
const readyFarms = [];
|
||
for (const farm of farmModels) {
|
||
if (farm.attributes.island_x !== ix || farm.attributes.island_y !== iy) continue;
|
||
for (const rel of relModels) {
|
||
if (
|
||
rel.attributes.farm_town_id === farm.attributes.id &&
|
||
rel.attributes.relation_status === 1 &&
|
||
(!rel.attributes.lootable_at || now >= rel.attributes.lootable_at)
|
||
) {
|
||
readyFarms.push({
|
||
town_id,
|
||
farm_town_id: rel.attributes.farm_town_id,
|
||
relation_id: rel.id
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
if (readyFarms.length === 0) { skipped++; continue; }
|
||
log(`Farm: ${readyFarms.length} ready on island of town ${town_id}`);
|
||
|
||
for (const farm of readyFarms) {
|
||
if (paused) {
|
||
return { ok: false, msg: 'Aborted due to pause/captcha' };
|
||
}
|
||
|
||
try {
|
||
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', {
|
||
model_url: `FarmTownPlayerRelation/${farm.relation_id}`,
|
||
action_name: 'claim',
|
||
arguments: { farm_town_id: farm.farm_town_id, type: 'resources', option },
|
||
town_id: farm.town_id
|
||
});
|
||
claimed++;
|
||
} catch (e) { errors++; }
|
||
|
||
// Random per-claim delay: 1000ms – 2200ms
|
||
await sleep(randInt(1000, 2200));
|
||
}
|
||
|
||
// Refresh map icons after claiming (same as original)
|
||
try { uw.WMap.removeFarmTownLootCooldownIconAndRefreshLootTimers(); } catch (e) { }
|
||
|
||
// Random between-island delay: 30s – 90s (only if more islands remain)
|
||
if (i < islandList.length - 1) {
|
||
if (paused) return { ok: false, msg: 'Aborted due to pause/captcha' };
|
||
const gap = randInt(30000, 90000);
|
||
log(`Farm: island done. Waiting ${(gap / 1000).toFixed(0)}s before next island...`);
|
||
await sleep(gap);
|
||
}
|
||
}
|
||
|
||
return { ok: true, msg: `Farm done: ${claimed} claimed, ${skipped} islands skipped, ${errors} errors` };
|
||
}
|
||
|
||
|
||
// ----------------------------------------------------------------
|
||
// Execute: Build
|
||
// ----------------------------------------------------------------
|
||
async function executeBuild(cmd) {
|
||
const { town_id, payload } = cmd;
|
||
const { building_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 in ITowns` };
|
||
}
|
||
|
||
// Check build queue
|
||
const queueLen = town.buildingOrders?.()?.length ?? 0;
|
||
const hasCurator = uw.GameDataPremium?.isAdvisorActivated?.('curator');
|
||
const maxQueue = hasCurator ? 7 : 2;
|
||
if (queueLen >= maxQueue) {
|
||
return { ok: false, requeue: true, msg: `Build queue full (${queueLen}/${maxQueue})` };
|
||
}
|
||
|
||
// Check resources
|
||
try {
|
||
const buildData = uw.MM.getModels?.()?.BuildingBuildData?.[town_id]
|
||
?.attributes?.building_data?.[building_id];
|
||
if (buildData) {
|
||
const res = town.resources();
|
||
const { resources_for, population_for } = buildData;
|
||
if (town.getAvailablePopulation?.() < population_for) {
|
||
return { ok: false, requeue: true, msg: `Not enough population for ${building_id}` };
|
||
}
|
||
if (res.wood < resources_for.wood || res.stone < resources_for.stone || res.iron < resources_for.iron) {
|
||
return { ok: false, requeue: true, msg: `Not enough resources for ${building_id}` };
|
||
}
|
||
}
|
||
} catch (e) {
|
||
log(`Resource check skipped: ${e}`);
|
||
}
|
||
|
||
// Fire the build request — with a human-like reaction delay
|
||
const reactionMs = randInt(800, 2500);
|
||
log(`Waiting ${reactionMs}ms before firing buildUp (reaction time)...`);
|
||
await sleep(reactionMs);
|
||
|
||
if (paused) return { ok: false, msg: 'Aborted due to pause/captcha' };
|
||
|
||
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', {
|
||
model_url: 'BuildingOrder',
|
||
action_name: 'buildUp',
|
||
arguments: { building_id },
|
||
town_id
|
||
});
|
||
|
||
await sleep(500);
|
||
return { ok: true, msg: `buildUp ${building_id} queued` };
|
||
}
|
||
|
||
// ----------------------------------------------------------------
|
||
// Execute: Recruit
|
||
// ----------------------------------------------------------------
|
||
async function executeRecruit(cmd) {
|
||
const { town_id, payload } = cmd;
|
||
const { unit_id, amount } = payload;
|
||
|
||
const town = uw.ITowns?.getTown?.(town_id) || uw.ITowns?.towns?.[town_id];
|
||
if (!town) {
|
||
return { ok: false, msg: `Town ${town_id} not found` };
|
||
}
|
||
|
||
// Determine endpoint based on unit type
|
||
const navalUnits = [
|
||
'big_transporter', 'small_transporter', 'bireme',
|
||
'attack_ship', 'trireme', 'colonize_ship', 'sea_monster'
|
||
];
|
||
const endpoint = navalUnits.includes(unit_id) ? 'building_docks' : 'building_barracks';
|
||
|
||
// Fire the recruit request — with a human-like reaction delay
|
||
const reactionMs = randInt(800, 2500);
|
||
log(`Waiting ${reactionMs}ms before firing recruit (reaction time)...`);
|
||
await sleep(reactionMs);
|
||
|
||
if (paused) return { ok: false, msg: 'Aborted due to pause/captcha' };
|
||
|
||
uw.gpAjax.ajaxPost(endpoint, 'build', {
|
||
unit_id,
|
||
amount: parseInt(amount) || 1,
|
||
town_id
|
||
});
|
||
|
||
await sleep(500);
|
||
return { ok: true, msg: `Recruit ${amount}x ${unit_id} submitted` };
|
||
}
|
||
|
||
// ----------------------------------------------------------------
|
||
// Execute: Market
|
||
// ----------------------------------------------------------------
|
||
async function executeMarketOffer(cmd) {
|
||
const { town_id, payload } = cmd;
|
||
const { offer, offer_type, demand, demand_type, max_delivery_time, visibility } = 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 market offer (reaction time)...`);
|
||
await sleep(reactionMs);
|
||
|
||
if (paused) return { ok: false, msg: 'Aborted due to pause/captcha' };
|
||
|
||
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', {
|
||
model_url: 'CreateOffers/' + town_id,
|
||
action_name: 'createOffer',
|
||
captcha: null,
|
||
arguments: {
|
||
offer, offer_type, demand, demand_type, max_delivery_time, visibility
|
||
}
|
||
});
|
||
|
||
await sleep(500);
|
||
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` };
|
||
}
|
||
|
||
// ----------------------------------------------------------------
|