Mj2 : modular and prepare for client options

This commit is contained in:
2026-04-26 21:45:32 +03:00
parent adb42c1649
commit 8b42c7c2f9
7 changed files with 246 additions and 170 deletions

View File

@@ -1,6 +1,6 @@
// ================================================================
// 04_execute.js — All command executors
// Depends on: uw, BASE_URL, log, sleep, randInt, paused, pushState
// 04a_execute_farm.js — Farm command executors
// Depends on: uw, log, sleep, randInt, paused, pushState
// ================================================================
// ----------------------------------------------------------------
@@ -129,8 +129,8 @@ async function executeFarmLoot(cmd) {
const island_id = islandList[i];
const townIds = islandTownsMap[island_id];
let selected_town_id = null;
let lowest_total_res = Infinity;
let selected_town_id = null;
let lowest_total_res = Infinity;
for (const t_id of townIds) {
const t = uw.ITowns?.towns?.[t_id];
@@ -152,8 +152,8 @@ async function executeFarmLoot(cmd) {
const total_res = w + s + ir;
if (total_res < lowest_total_res) {
lowest_total_res = total_res;
selected_town_id = t_id;
lowest_total_res = total_res;
selected_town_id = t_id;
}
}
@@ -219,138 +219,3 @@ async function executeFarmLoot(cmd) {
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` };
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})` };
}
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}`); }
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` };
const navalUnits = [
'big_transporter', 'small_transporter', 'bireme',
'attack_ship', 'trireme', 'colonize_ship', 'sea_monster'
];
const endpoint = navalUnits.includes(unit_id) ? 'building_docks' : 'building_barracks';
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 Offer
// ----------------------------------------------------------------
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
});
await sleep(500);
return { ok: true, msg: `Research ${research_id} queued` };
}

View File

@@ -0,0 +1,139 @@
// ================================================================
// 04b_execute_admin.js — Admin command executors
// Depends on: uw, log, sleep, randInt, paused
// ================================================================
// ----------------------------------------------------------------
// 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` };
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})` };
}
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}`); }
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` };
const navalUnits = [
'big_transporter', 'small_transporter', 'bireme',
'attack_ship', 'trireme', 'colonize_ship', 'sea_monster'
];
const endpoint = navalUnits.includes(unit_id) ? 'building_docks' : 'building_barracks';
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 Offer
// ----------------------------------------------------------------
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
});
await sleep(500);
return { ok: true, msg: `Research ${research_id} queued` };
}

View File

@@ -17,12 +17,17 @@ async function pollAndExecute() {
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;
// 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');
@@ -63,9 +68,9 @@ async function pollAndExecute() {
await execute(farmCmd);
await execute(farmUpgradeCmd);
// Auto-farm: if enabled, claim all ready farms (no explicit command needed)
// Auto-farm: only if farm feature is enabled
const farmSettings = cmdData.farm_settings || {};
if (farmSettings.enabled && !farmCmd) {
if (farmOn && farmSettings.enabled && !farmCmd) {
const nowTs = Math.floor(Date.now() / 1000);
let readyFarms = [];
try {