MJ: claude fix
This commit is contained in:
@@ -1,21 +1,31 @@
|
|||||||
|
// ================================================================
|
||||||
|
// 00_config.js — Shared constants and utility helpers
|
||||||
|
// Runs first; everything here is available to all other modules.
|
||||||
|
// ================================================================
|
||||||
|
|
||||||
const uw = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
|
const uw = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
|
||||||
const BASE_URL = 'https://grepo.haunter-pets.top';
|
const BASE_URL = 'https://grepo.haunter-pets.top';
|
||||||
|
|
||||||
// ---- Jitter helpers -----------------------------------------------
|
// Returns a random integer between min and max (inclusive)
|
||||||
// Returns a random integer between min and max (inclusive)
|
function randInt(min, max) {
|
||||||
function randInt(min, max) {
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
}
|
||||||
}
|
|
||||||
// Schedules fn to run after a random ms delay, then reschedules itself
|
// Schedules fn to run after a random ms delay, then reschedules itself
|
||||||
function jitterLoop(fn, minMs, maxMs) {
|
function jitterLoop(fn, minMs, maxMs) {
|
||||||
function schedule() {
|
function schedule() {
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
await fn();
|
await fn();
|
||||||
schedule(); // reschedule with a NEW random delay every time
|
schedule();
|
||||||
}, randInt(minMs, maxMs));
|
}, randInt(minMs, maxMs));
|
||||||
}
|
|
||||||
schedule();
|
|
||||||
}
|
}
|
||||||
|
schedule();
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
function log(msg) {
|
||||||
|
console.log(`[GRC] ${msg}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sleep(ms) {
|
||||||
|
return new Promise(r => setTimeout(r, ms));
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,45 +1,35 @@
|
|||||||
// Toolbar indicator button
|
// ================================================================
|
||||||
// ----------------------------------------------------------------
|
// 01_ui.js — Toolbar button + pause toggle
|
||||||
const btnHtml = `
|
// Depends on: uw, log (00_config.js)
|
||||||
<div class="divider"></div>
|
// ================================================================
|
||||||
<div class="activity" id="grc_btn"
|
|
||||||
style="filter: brightness(70%) sepia(100%) hue-rotate(200deg) saturate(1000%) contrast(0.8);">
|
|
||||||
<p id="grc_label" style="position:relative;top:-8px;font-weight:bold;z-index:6;">Remote</p>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
let paused = false;
|
const btnHtml = `
|
||||||
|
<div class="divider"></div>
|
||||||
|
<div class="activity" id="grc_btn"
|
||||||
|
style="filter: brightness(70%) sepia(100%) hue-rotate(200deg) saturate(1000%) contrast(0.8);">
|
||||||
|
<p id="grc_label" style="position:relative;top:-8px;font-weight:bold;z-index:6;">Remote</p>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
function togglePause() {
|
let paused = false;
|
||||||
paused = !paused;
|
|
||||||
const label = document.getElementById('grc_label');
|
function togglePause() {
|
||||||
const btn = document.getElementById('grc_btn');
|
paused = !paused;
|
||||||
if (paused) {
|
const label = document.getElementById('grc_label');
|
||||||
label.textContent = 'Paused';
|
const btn = document.getElementById('grc_btn');
|
||||||
btn.style.filter = 'brightness(70%) sepia(100%) hue-rotate(-50deg) saturate(1000%) contrast(0.8)';
|
if (paused) {
|
||||||
} else {
|
label.textContent = 'Paused';
|
||||||
label.textContent = 'Remote';
|
btn.style.filter = 'brightness(70%) sepia(100%) hue-rotate(-50deg) saturate(1000%) contrast(0.8)';
|
||||||
btn.style.filter = 'brightness(294%) sepia(100%) hue-rotate(200deg) saturate(1000%) contrast(0.8)';
|
} else {
|
||||||
}
|
label.textContent = 'Remote';
|
||||||
log(`Remote is now ${paused ? 'PAUSED' : 'ACTIVE'}`);
|
btn.style.filter = 'brightness(294%) sepia(100%) hue-rotate(200deg) saturate(1000%) contrast(0.8)';
|
||||||
}
|
}
|
||||||
|
log(`Remote is now ${paused ? 'PAUSED' : 'ACTIVE'}`);
|
||||||
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!document.getElementById('grc_btn')) {
|
if (!document.getElementById('grc_btn')) {
|
||||||
uw.$('.tb_activities, .toolbar_activities').find('.middle').append(btnHtml);
|
uw.$('.tb_activities, .toolbar_activities').find('.middle').append(btnHtml);
|
||||||
}
|
|
||||||
}, 4000);
|
|
||||||
|
|
||||||
uw.$(document).on('click', '#grc_btn', togglePause);
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
// Helpers
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
function log(msg) {
|
|
||||||
console.log(`[GRC] ${msg}`);
|
|
||||||
}
|
}
|
||||||
|
}, 4000);
|
||||||
|
|
||||||
function sleep(ms) {
|
uw.$(document).on('click', '#grc_btn', togglePause);
|
||||||
return new Promise(r => setTimeout(r, ms));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
|
|||||||
@@ -1,285 +1,244 @@
|
|||||||
// Push town state to relay
|
// ================================================================
|
||||||
// ----------------------------------------------------------------
|
// 02_state.js — Gather & push town state to the relay server
|
||||||
function gatherState() {
|
// Depends on: uw, BASE_URL, log, paused (00_config.js / 01_ui.js)
|
||||||
const towns = uw.ITowns?.towns || {};
|
// ================================================================
|
||||||
const player = uw.Game?.player_name || '';
|
|
||||||
const player_id = uw.Game?.player_id ?? null;
|
function gatherState() {
|
||||||
const alliance_id = uw.Game?.alliance_id ?? null;
|
const towns = uw.ITowns?.towns || {};
|
||||||
let alliance_name = null;
|
const player = uw.Game?.player_name || '';
|
||||||
|
const player_id = uw.Game?.player_id ?? null;
|
||||||
|
const alliance_id = uw.Game?.alliance_id ?? null;
|
||||||
|
let alliance_name = null;
|
||||||
|
try {
|
||||||
|
const pm = uw.MM.getModels().Player[player_id];
|
||||||
|
if (pm && pm.attributes) alliance_name = pm.attributes.alliance_name;
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
const total_points = uw.Game?.player_points ?? 0;
|
||||||
|
const world = uw.Game?.world_id || '';
|
||||||
|
|
||||||
|
const townList = Object.values(towns).map(town => {
|
||||||
|
const res = town.resources();
|
||||||
|
const buildings = town.buildings()?.attributes ?? {};
|
||||||
|
|
||||||
|
const unitsObj = {};
|
||||||
try {
|
try {
|
||||||
const pm = uw.MM.getModels().Player[player_id];
|
const units = town.units();
|
||||||
if (pm && pm.attributes) alliance_name = pm.attributes.alliance_name;
|
if (units) {
|
||||||
console.log("GrepoRemote: Extracted alliance_name =", alliance_name);
|
Object.keys(units).forEach(k => {
|
||||||
} catch (e) {
|
unitsObj[k] = typeof units[k] === 'number'
|
||||||
console.log("GrepoRemote: Failed to extract alliance_name", e);
|
? units[k]
|
||||||
}
|
: (units[k]?.getAmount?.() ?? 0);
|
||||||
|
});
|
||||||
const total_points = uw.Game?.player_points ?? 0;
|
|
||||||
const world = uw.Game?.world_id || '';
|
|
||||||
|
|
||||||
const townList = Object.values(towns).map(town => {
|
|
||||||
const res = town.resources();
|
|
||||||
const buildings = town.buildings()?.attributes ?? {};
|
|
||||||
|
|
||||||
const unitsObj = {};
|
|
||||||
try {
|
|
||||||
const units = town.units();
|
|
||||||
if (units) {
|
|
||||||
Object.keys(units).forEach(k => {
|
|
||||||
unitsObj[k] = typeof units[k] === 'number'
|
|
||||||
? units[k]
|
|
||||||
: (units[k]?.getAmount?.() ?? 0);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) { }
|
|
||||||
|
|
||||||
let buildQueue = [];
|
|
||||||
try {
|
|
||||||
const bo = town.buildingOrders?.();
|
|
||||||
if (bo?.models) buildQueue = bo.models.map(m => m.attributes);
|
|
||||||
} catch (e) { }
|
|
||||||
|
|
||||||
let buildDataMap = {};
|
|
||||||
try {
|
|
||||||
const buildDataRaw = uw.MM?.getModels?.()?.BuildingBuildData?.[town.id]?.attributes?.building_data || {};
|
|
||||||
for (const k in buildDataRaw) {
|
|
||||||
buildDataMap[k] = {
|
|
||||||
buildable: buildDataRaw[k].buildable,
|
|
||||||
dependencies: buildDataRaw[k].dependencies_fulfilled !== false,
|
|
||||||
wood: buildDataRaw[k].resources_for?.wood || 0,
|
|
||||||
stone: buildDataRaw[k].resources_for?.stone || 0,
|
|
||||||
iron: buildDataRaw[k].resources_for?.iron || 0,
|
|
||||||
pop: buildDataRaw[k].population_for || 0,
|
|
||||||
build_time: buildDataRaw[k].building_time || '',
|
|
||||||
can_upgrade: !!buildDataRaw[k].can_upgrade,
|
|
||||||
enough_resources: !!buildDataRaw[k].enough_resources,
|
|
||||||
missing_dependencies: buildDataRaw[k].missing_dependencies || [],
|
|
||||||
has_max_level: !!buildDataRaw[k].has_max_level
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
log(`Failed to gather build data: ${e}`);
|
|
||||||
}
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
// ---- Storage capacity -----------------------------------------------
|
let buildQueue = [];
|
||||||
// res.storage / res.storage_capacity are always 0 in Grepolis.
|
|
||||||
// Real capacity lives in GameData keyed by the storage building level.
|
|
||||||
let storageCapacity = 0;
|
|
||||||
try {
|
|
||||||
// Strategy 1: dedicated getter (exists on some server versions)
|
|
||||||
storageCapacity = town.getStorageCapacity?.() || 0;
|
|
||||||
|
|
||||||
// Strategy 2: GameData.buildingData.storage.max_storage[level]
|
|
||||||
if (!storageCapacity) {
|
|
||||||
const storageLevel = buildings.storage ?? 0;
|
|
||||||
const gd = uw.GameData?.buildingData?.storage;
|
|
||||||
storageCapacity = gd?.max_storage?.[storageLevel]
|
|
||||||
|| gd?.storage?.[storageLevel]
|
|
||||||
|| 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strategy 3: resource object fallback keys
|
|
||||||
if (!storageCapacity) {
|
|
||||||
storageCapacity = res.capacity || res.storage_capacity || res.storage || 0;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
log(`storage capacity lookup failed: ${e}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Market / Trade capacity -----------------------------------------
|
|
||||||
let marketCapacity = 0;
|
|
||||||
try {
|
|
||||||
const marketLevel = buildings.market ?? 0;
|
|
||||||
const gd = uw.GameData?.buildingData?.market;
|
|
||||||
marketCapacity = gd?.capacity_per_level?.[marketLevel] || 0;
|
|
||||||
|
|
||||||
// Add Trade Office bonus if present
|
|
||||||
if (buildings.trade_office && buildings.trade_office > 0) {
|
|
||||||
marketCapacity += (uw.GameData?.buildingData?.trade_office?.capacity_extra_per_level || 500) * marketLevel;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
log(`market capacity lookup failed: ${e}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Coordinates & sea zone -----------------------------------------
|
|
||||||
let x = null, y = null, sea = null;
|
|
||||||
try {
|
|
||||||
x = town.getIslandCoordinateX?.() ?? null;
|
|
||||||
y = town.getIslandCoordinateY?.() ?? null;
|
|
||||||
if (typeof x === 'number' && typeof y === 'number') {
|
|
||||||
sea = Math.floor(x / 100) * 10 + Math.floor(y / 100);
|
|
||||||
}
|
|
||||||
} catch (e) { }
|
|
||||||
|
|
||||||
// ---- Researches -----------------------------------------------------
|
|
||||||
let researches = {};
|
|
||||||
try {
|
|
||||||
const r = town.researches?.();
|
|
||||||
if (r) researches = r.attributes ?? (typeof r === 'object' ? r : {});
|
|
||||||
} catch (e) { log(`[Debug] town.researches() failed: ${e}`); }
|
|
||||||
|
|
||||||
// ---- Unit Data (Costs & Dependencies) -------------------------------
|
|
||||||
let unitDataMap = {};
|
|
||||||
try {
|
|
||||||
const gdUnits = uw.GameData?.units || {};
|
|
||||||
|
|
||||||
for (const u in gdUnits) {
|
|
||||||
if (u === 'militia') continue;
|
|
||||||
|
|
||||||
const reqBuildings = gdUnits[u].building_dependencies || {};
|
|
||||||
const reqResearch = gdUnits[u].research_dependencies || [];
|
|
||||||
|
|
||||||
let missing_deps = {};
|
|
||||||
for (const reqB in reqBuildings) {
|
|
||||||
if ((buildings[reqB] || 0) < reqBuildings[reqB]) {
|
|
||||||
missing_deps[reqB] = { name: reqB, needed_level: reqBuildings[reqB] };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const reqR of reqResearch) {
|
|
||||||
if (!researches[reqR]) {
|
|
||||||
missing_deps[reqR] = { name: reqR, needed_level: 'Έρευνα' };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const cost = gdUnits[u].resources || {};
|
|
||||||
const w = cost.wood || 0;
|
|
||||||
const s = cost.stone || 0;
|
|
||||||
const i = cost.iron || 0;
|
|
||||||
|
|
||||||
let enough = true;
|
|
||||||
if (res.wood < w || res.stone < s || res.iron < i) enough = false;
|
|
||||||
|
|
||||||
unitDataMap[u] = {
|
|
||||||
wood: w,
|
|
||||||
stone: s,
|
|
||||||
iron: i,
|
|
||||||
pop: gdUnits[u].population || 0,
|
|
||||||
build_time: gdUnits[u].build_time || 0,
|
|
||||||
enough_resources: enough,
|
|
||||||
missing_dependencies: missing_deps
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} catch (e) { log(`Failed to gather unit data: ${e}`); }
|
|
||||||
|
|
||||||
// ---- Farm town data -----------------------------------------------
|
|
||||||
let farms = [];
|
|
||||||
try {
|
|
||||||
const farmCollection = uw.MM.getOnlyCollectionByName('FarmTown');
|
|
||||||
const relCollection = uw.MM.getOnlyCollectionByName('FarmTownPlayerRelation');
|
|
||||||
if (farmCollection && relCollection) {
|
|
||||||
const ix = town.getIslandCoordinateX?.();
|
|
||||||
const iy = town.getIslandCoordinateY?.();
|
|
||||||
farmCollection.models.forEach(farm => {
|
|
||||||
if (farm.attributes.island_x !== ix || farm.attributes.island_y !== iy) return;
|
|
||||||
relCollection.models.forEach(rel => {
|
|
||||||
if (rel.attributes.farm_town_id === farm.attributes.id &&
|
|
||||||
rel.attributes.relation_status >= 1) {
|
|
||||||
farms.push({
|
|
||||||
farm_town_id: farm.attributes.id,
|
|
||||||
farm_name: farm.attributes.name || '',
|
|
||||||
relation_id: rel.id,
|
|
||||||
relation_status: rel.attributes.relation_status,
|
|
||||||
expansion_stage: rel.attributes.expansion_stage || 0,
|
|
||||||
expansion_at: rel.attributes.expansion_at || 0,
|
|
||||||
lootable_at: rel.attributes.lootable_at || 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) { }
|
|
||||||
|
|
||||||
// ---- Extra town flags -----------------------------------------------
|
|
||||||
let has_premium = false;
|
|
||||||
let bonuses = {};
|
|
||||||
let wonder_points = 0;
|
|
||||||
try {
|
|
||||||
has_premium = uw.GameDataPremium?.isAdvisorActivated?.('curator') || false;
|
|
||||||
} catch (e) { }
|
|
||||||
|
|
||||||
return {
|
|
||||||
town_id: town.id,
|
|
||||||
town_name: town.name,
|
|
||||||
x, y, sea,
|
|
||||||
wood: res.wood,
|
|
||||||
stone: res.stone,
|
|
||||||
iron: res.iron,
|
|
||||||
storage: storageCapacity,
|
|
||||||
market_capacity: marketCapacity,
|
|
||||||
population: res.population,
|
|
||||||
points: town.getPoints?.() ?? 0,
|
|
||||||
god: town.god?.() ?? null,
|
|
||||||
buildings,
|
|
||||||
units: unitsObj,
|
|
||||||
buildingOrder: buildQueue,
|
|
||||||
buildData: buildDataMap,
|
|
||||||
unitData: unitDataMap,
|
|
||||||
researches,
|
|
||||||
has_premium,
|
|
||||||
bonuses,
|
|
||||||
wonder_points,
|
|
||||||
alliance_name,
|
|
||||||
farms,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return { player, player_id, alliance_id, total_points, world_id: world, towns: townList };
|
|
||||||
}
|
|
||||||
|
|
||||||
function pushState() {
|
|
||||||
if (paused) return;
|
|
||||||
try {
|
try {
|
||||||
const payload = gatherState();
|
const bo = town.buildingOrders?.();
|
||||||
fetch(`${BASE_URL}/api/state`, {
|
if (bo?.models) buildQueue = bo.models.map(m => m.attributes);
|
||||||
method: 'POST',
|
} catch (e) {}
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(payload)
|
|
||||||
})
|
|
||||||
.then(() => log(`State pushed — ${payload.towns.length} towns`))
|
|
||||||
.catch(e => log(`State push failed: ${e}`));
|
|
||||||
} catch (e) {
|
|
||||||
log(`gatherState error: ${e}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
let buildDataMap = {};
|
||||||
// AJAX Interceptor for Zero-Delay Push
|
try {
|
||||||
// ----------------------------------------------------------------
|
const buildDataRaw = uw.MM?.getModels?.()?.BuildingBuildData?.[town.id]?.attributes?.building_data || {};
|
||||||
let pushTimeout = null;
|
for (const k in buildDataRaw) {
|
||||||
function debouncedPushState() {
|
buildDataMap[k] = {
|
||||||
if (paused) return;
|
buildable: buildDataRaw[k].buildable,
|
||||||
if (pushTimeout) clearTimeout(pushTimeout);
|
dependencies: buildDataRaw[k].dependencies_fulfilled !== false,
|
||||||
pushTimeout = setTimeout(() => {
|
wood: buildDataRaw[k].resources_for?.wood || 0,
|
||||||
log('⚡ State change detected (AJAX). Syncing to Remote...');
|
stone: buildDataRaw[k].resources_for?.stone || 0,
|
||||||
pushState();
|
iron: buildDataRaw[k].resources_for?.iron || 0,
|
||||||
}, 1200); // Wait 1.2s for memory models to update after AJAX finishes
|
pop: buildDataRaw[k].population_for || 0,
|
||||||
}
|
build_time: buildDataRaw[k].building_time || '',
|
||||||
|
can_upgrade: !!buildDataRaw[k].can_upgrade,
|
||||||
if (uw.$) {
|
enough_resources: !!buildDataRaw[k].enough_resources,
|
||||||
uw.$(document).ajaxComplete(function (e, xhr, opt) {
|
missing_dependencies: buildDataRaw[k].missing_dependencies || [],
|
||||||
if (!opt || !opt.url) return;
|
has_max_level: !!buildDataRaw[k].has_max_level
|
||||||
|
};
|
||||||
// Ignore requests to our own bot server or map data
|
|
||||||
if (opt.url.includes(BASE_URL)) return;
|
|
||||||
if (opt.url.includes('map_tiles')) return;
|
|
||||||
|
|
||||||
// Most game actions use "action=" parameter
|
|
||||||
// Switching towns uses "switch_town"
|
|
||||||
if (opt.url.includes('action=') || opt.url.includes('switch_town')) {
|
|
||||||
debouncedPushState();
|
|
||||||
}
|
}
|
||||||
});
|
} catch (e) { log(`Failed to gather build data: ${e}`); }
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
// ---- Storage capacity -----------------------------------------------
|
||||||
// Report command result back to relay
|
let storageCapacity = 0;
|
||||||
// ----------------------------------------------------------------
|
try {
|
||||||
function reportResult(cmdId, status, message) {
|
storageCapacity = town.getStorageCapacity?.() || 0;
|
||||||
fetch(`${BASE_URL}/api/commands/${cmdId}/result`, {
|
if (!storageCapacity) {
|
||||||
|
const storageLevel = buildings.storage ?? 0;
|
||||||
|
const gd = uw.GameData?.buildingData?.storage;
|
||||||
|
storageCapacity = gd?.max_storage?.[storageLevel] || gd?.storage?.[storageLevel] || 0;
|
||||||
|
}
|
||||||
|
if (!storageCapacity) {
|
||||||
|
storageCapacity = res.capacity || res.storage_capacity || res.storage || 0;
|
||||||
|
}
|
||||||
|
} catch (e) { log(`storage capacity lookup failed: ${e}`); }
|
||||||
|
|
||||||
|
// ---- Market / Trade capacity -----------------------------------------
|
||||||
|
let marketCapacity = 0;
|
||||||
|
try {
|
||||||
|
const marketLevel = buildings.market ?? 0;
|
||||||
|
const gd = uw.GameData?.buildingData?.market;
|
||||||
|
marketCapacity = gd?.capacity_per_level?.[marketLevel] || 0;
|
||||||
|
if (buildings.trade_office && buildings.trade_office > 0) {
|
||||||
|
marketCapacity += (uw.GameData?.buildingData?.trade_office?.capacity_extra_per_level || 500) * marketLevel;
|
||||||
|
}
|
||||||
|
} catch (e) { log(`market capacity lookup failed: ${e}`); }
|
||||||
|
|
||||||
|
// ---- Coordinates & sea zone -----------------------------------------
|
||||||
|
let x = null, y = null, sea = null;
|
||||||
|
try {
|
||||||
|
x = town.getIslandCoordinateX?.() ?? null;
|
||||||
|
y = town.getIslandCoordinateY?.() ?? null;
|
||||||
|
if (typeof x === 'number' && typeof y === 'number') {
|
||||||
|
sea = Math.floor(x / 100) * 10 + Math.floor(y / 100);
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
// ---- Researches -----------------------------------------------------
|
||||||
|
let researches = {};
|
||||||
|
try {
|
||||||
|
const r = town.researches?.();
|
||||||
|
if (r) researches = r.attributes ?? (typeof r === 'object' ? r : {});
|
||||||
|
} catch (e) { log(`[Debug] town.researches() failed: ${e}`); }
|
||||||
|
|
||||||
|
// ---- Unit Data (Costs & Dependencies) -------------------------------
|
||||||
|
let unitDataMap = {};
|
||||||
|
try {
|
||||||
|
const gdUnits = uw.GameData?.units || {};
|
||||||
|
for (const u in gdUnits) {
|
||||||
|
if (u === 'militia') continue;
|
||||||
|
const reqBuildings = gdUnits[u].building_dependencies || {};
|
||||||
|
const reqResearch = gdUnits[u].research_dependencies || [];
|
||||||
|
let missing_deps = {};
|
||||||
|
for (const reqB in reqBuildings) {
|
||||||
|
if ((buildings[reqB] || 0) < reqBuildings[reqB]) {
|
||||||
|
missing_deps[reqB] = { name: reqB, needed_level: reqBuildings[reqB] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const reqR of reqResearch) {
|
||||||
|
if (!researches[reqR]) {
|
||||||
|
missing_deps[reqR] = { name: reqR, needed_level: 'Έρευνα' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const cost = gdUnits[u].resources || {};
|
||||||
|
const w = cost.wood || 0, s = cost.stone || 0, i = cost.iron || 0;
|
||||||
|
let enough = !(res.wood < w || res.stone < s || res.iron < i);
|
||||||
|
unitDataMap[u] = {
|
||||||
|
wood: w, stone: s, iron: i,
|
||||||
|
pop: gdUnits[u].population || 0,
|
||||||
|
build_time: gdUnits[u].build_time || 0,
|
||||||
|
enough_resources: enough,
|
||||||
|
missing_dependencies: missing_deps
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (e) { log(`Failed to gather unit data: ${e}`); }
|
||||||
|
|
||||||
|
// ---- Farm town data -----------------------------------------------
|
||||||
|
let farms = [];
|
||||||
|
try {
|
||||||
|
const farmCollection = uw.MM.getOnlyCollectionByName('FarmTown');
|
||||||
|
const relCollection = uw.MM.getOnlyCollectionByName('FarmTownPlayerRelation');
|
||||||
|
if (farmCollection && relCollection) {
|
||||||
|
const ix = town.getIslandCoordinateX?.();
|
||||||
|
const iy = town.getIslandCoordinateY?.();
|
||||||
|
farmCollection.models.forEach(farm => {
|
||||||
|
if (farm.attributes.island_x !== ix || farm.attributes.island_y !== iy) return;
|
||||||
|
relCollection.models.forEach(rel => {
|
||||||
|
if (rel.attributes.farm_town_id === farm.attributes.id &&
|
||||||
|
rel.attributes.relation_status >= 1) {
|
||||||
|
farms.push({
|
||||||
|
farm_town_id: farm.attributes.id,
|
||||||
|
farm_name: farm.attributes.name || '',
|
||||||
|
relation_id: rel.id,
|
||||||
|
relation_status: rel.attributes.relation_status,
|
||||||
|
expansion_stage: rel.attributes.expansion_stage || 0,
|
||||||
|
expansion_at: rel.attributes.expansion_at || 0,
|
||||||
|
lootable_at: rel.attributes.lootable_at || 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
// ---- Extra town flags -----------------------------------------------
|
||||||
|
let has_premium = false;
|
||||||
|
try {
|
||||||
|
has_premium = uw.GameDataPremium?.isAdvisorActivated?.('curator') || false;
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
town_id: town.id,
|
||||||
|
town_name: town.name,
|
||||||
|
x, y, sea,
|
||||||
|
wood: res.wood,
|
||||||
|
stone: res.stone,
|
||||||
|
iron: res.iron,
|
||||||
|
storage: storageCapacity,
|
||||||
|
market_capacity: marketCapacity,
|
||||||
|
population: res.population,
|
||||||
|
points: town.getPoints?.() ?? 0,
|
||||||
|
god: town.god?.() ?? null,
|
||||||
|
buildings,
|
||||||
|
units: unitsObj,
|
||||||
|
buildingOrder: buildQueue,
|
||||||
|
buildData: buildDataMap,
|
||||||
|
unitData: unitDataMap,
|
||||||
|
researches,
|
||||||
|
has_premium,
|
||||||
|
bonuses: {},
|
||||||
|
wonder_points: 0,
|
||||||
|
alliance_name,
|
||||||
|
farms,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return { player, player_id, alliance_id, total_points, world_id: world, towns: townList };
|
||||||
|
}
|
||||||
|
|
||||||
|
function pushState() {
|
||||||
|
if (paused) return;
|
||||||
|
try {
|
||||||
|
const payload = gatherState();
|
||||||
|
fetch(`${BASE_URL}/api/state`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ status, message })
|
body: JSON.stringify(payload)
|
||||||
}).catch(e => log(`reportResult failed: ${e}`));
|
})
|
||||||
|
.then(() => log(`State pushed — ${payload.towns.length} towns`))
|
||||||
|
.catch(e => log(`State push failed: ${e}`));
|
||||||
|
} catch (e) {
|
||||||
|
log(`gatherState error: ${e}`);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
// ---- AJAX Interceptor for Zero-Delay Push ----------------------------
|
||||||
|
let pushTimeout = null;
|
||||||
|
function debouncedPushState() {
|
||||||
|
if (paused) return;
|
||||||
|
if (pushTimeout) clearTimeout(pushTimeout);
|
||||||
|
pushTimeout = setTimeout(() => {
|
||||||
|
log('⚡ State change detected (AJAX). Syncing to Remote...');
|
||||||
|
pushState();
|
||||||
|
}, 1200);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uw.$) {
|
||||||
|
uw.$(document).ajaxComplete(function (e, xhr, opt) {
|
||||||
|
if (!opt || !opt.url) return;
|
||||||
|
if (opt.url.includes(BASE_URL)) return;
|
||||||
|
if (opt.url.includes('map_tiles')) return;
|
||||||
|
if (opt.url.includes('action=') || opt.url.includes('switch_town')) {
|
||||||
|
debouncedPushState();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Report command result back to relay -----------------------------
|
||||||
|
function reportResult(cmdId, status, message) {
|
||||||
|
fetch(`${BASE_URL}/api/commands/${cmdId}/result`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ status, message })
|
||||||
|
}).catch(e => log(`reportResult failed: ${e}`));
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,48 +1,45 @@
|
|||||||
// Captcha detection via MutationObserver
|
// ================================================================
|
||||||
// Watches document.body for #hcaptcha_window being added/removed.
|
// 03_captcha.js — hCaptcha detection & server alert
|
||||||
// Confirmed selector from live DOM: DIV#hcaptcha_window > DIV.h-captcha
|
// Depends on: uw, BASE_URL, log, paused (00_config.js / 01_ui.js)
|
||||||
// ----------------------------------------------------------------
|
// ================================================================
|
||||||
let captchaActive = false;
|
|
||||||
|
|
||||||
function reportCaptcha(detected) {
|
let captchaActive = false;
|
||||||
const player_id = uw.Game?.player_id;
|
|
||||||
if (!player_id) return;
|
|
||||||
fetch(`${BASE_URL}/api/captcha/alert?player_id=${player_id}`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ detected })
|
|
||||||
}).catch(e => log(`captcha report failed: ${e}`));
|
|
||||||
}
|
|
||||||
|
|
||||||
function detectCaptcha() {
|
function reportCaptcha(detected) {
|
||||||
setInterval(() => {
|
const player_id = uw.Game?.player_id;
|
||||||
const win = document.getElementById('hcaptcha_window');
|
if (!player_id) return;
|
||||||
let isVisible = false;
|
fetch(`${BASE_URL}/api/captcha/alert?player_id=${player_id}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ detected })
|
||||||
|
}).catch(e => log(`captcha report failed: ${e}`));
|
||||||
|
}
|
||||||
|
|
||||||
if (win) {
|
function detectCaptcha() {
|
||||||
// Check if it's actually visible on screen (not display: none by the game)
|
setInterval(() => {
|
||||||
const style = window.getComputedStyle(win);
|
const win = document.getElementById('hcaptcha_window');
|
||||||
if (style.display !== 'none' && style.visibility !== 'hidden') {
|
let isVisible = false;
|
||||||
isVisible = true;
|
|
||||||
}
|
if (win) {
|
||||||
|
const style = window.getComputedStyle(win);
|
||||||
|
if (style.display !== 'none' && style.visibility !== 'hidden') {
|
||||||
|
isVisible = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isVisible && !captchaActive) {
|
if (isVisible && !captchaActive) {
|
||||||
captchaActive = true;
|
captchaActive = true;
|
||||||
paused = true;
|
paused = true;
|
||||||
const label = document.getElementById('grc_label');
|
const label = document.getElementById('grc_label');
|
||||||
const btn = document.getElementById('grc_btn');
|
const btn = document.getElementById('grc_btn');
|
||||||
if (label) label.textContent = '⚠ CAPTCHA';
|
if (label) label.textContent = '⚠ CAPTCHA';
|
||||||
if (btn) btn.style.filter = 'brightness(70%) sepia(100%) hue-rotate(300deg) saturate(1000%) contrast(0.8)';
|
if (btn) btn.style.filter = 'brightness(70%) sepia(100%) hue-rotate(300deg) saturate(1000%) contrast(0.8)';
|
||||||
log('⚠ CAPTCHA detected — bot paused, alerting server');
|
log('⚠ CAPTCHA detected — bot paused, alerting server');
|
||||||
reportCaptcha(true);
|
reportCaptcha(true);
|
||||||
} else if (!isVisible && captchaActive) {
|
} else if (!isVisible && captchaActive) {
|
||||||
captchaActive = false;
|
captchaActive = false;
|
||||||
// Don't auto-resume — let the user click the button manually
|
log('✅ Captcha resolved — alert cleared (bot remains paused)');
|
||||||
log('✅ Captcha resolved — alert cleared (bot remains paused)');
|
reportCaptcha(false);
|
||||||
reportCaptcha(false);
|
}
|
||||||
}
|
}, 1000);
|
||||||
}, 1000);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
|
|||||||
@@ -1,406 +1,356 @@
|
|||||||
// Execute: Farm Upgrade / Unlock
|
// ================================================================
|
||||||
// Iterates all farm relations. Locked villages (status 0) get
|
// 04_execute.js — All command executors
|
||||||
// unlocked; unlocked villages below max level get upgraded.
|
// Depends on: uw, BASE_URL, log, sleep, randInt, paused, pushState
|
||||||
// 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 {
|
// Execute: Farm Upgrade / Unlock
|
||||||
farmModels = uw.MM.getOnlyCollectionByName('FarmTown')?.models;
|
// ----------------------------------------------------------------
|
||||||
relModels = uw.MM.getOnlyCollectionByName('FarmTownPlayerRelation')?.models;
|
async function executeFarmUpgrade(cmd) {
|
||||||
} catch (e) {
|
const now = Math.floor(Date.now() / 1000);
|
||||||
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)
|
let farmModels, relModels;
|
||||||
const islandsSeen = new Set();
|
try {
|
||||||
const polisList = [];
|
farmModels = uw.MM.getOnlyCollectionByName('FarmTown')?.models;
|
||||||
try {
|
relModels = uw.MM.getOnlyCollectionByName('FarmTownPlayerRelation')?.models;
|
||||||
for (const town of uw.MM.getCollections().Town[0].models) {
|
} catch (e) {
|
||||||
const { on_small_island, island_id, id } = town.attributes;
|
return { ok: false, msg: `Cannot access farm collections: ${e.message}` };
|
||||||
if (on_small_island) continue;
|
}
|
||||||
if (!islandsSeen.has(island_id)) {
|
if (!farmModels || !relModels) {
|
||||||
islandsSeen.add(island_id);
|
return { ok: false, msg: 'Farm collections not loaded yet' };
|
||||||
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` };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
// Build polis list (one town per island)
|
||||||
// Execute: Farm Loot
|
const islandsSeen = new Set();
|
||||||
// Claims all ready farm towns across all towns that match
|
const polisList = [];
|
||||||
// the cmd payload (town_ids list + loot_option).
|
try {
|
||||||
// Between-claim delay: random 500ms–1500ms (never below 500ms)
|
for (const town of uw.MM.getCollections().Town[0].models) {
|
||||||
// Between-town-group delay: random 30s–90s
|
const { on_small_island, island_id, id } = town.attributes;
|
||||||
// ----------------------------------------------------------------
|
if (on_small_island) continue;
|
||||||
async function executeFarmLoot(cmd) {
|
if (!islandsSeen.has(island_id)) {
|
||||||
const { loot_option } = cmd.payload || {};
|
islandsSeen.add(island_id);
|
||||||
const option = parseInt(loot_option) || 1;
|
polisList.push(id);
|
||||||
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' };
|
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return { ok: false, msg: `Cannot build town list: ${e.message}` };
|
||||||
|
}
|
||||||
|
|
||||||
// Build island groups using MM (all towns, not just visible)
|
let upgraded = 0, unlocked = 0, skipped = 0, errors = 0;
|
||||||
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}`);
|
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;
|
||||||
|
|
||||||
let claimed = 0;
|
for (const farm of farmModels) {
|
||||||
let skipped = 0;
|
if (farm.attributes.island_x !== ix || farm.attributes.island_y !== iy) continue;
|
||||||
let errors = 0;
|
|
||||||
|
|
||||||
for (let i = 0; i < islandList.length; i++) {
|
for (const rel of relModels) {
|
||||||
const island_id = islandList[i];
|
if (rel.attributes.farm_town_id !== farm.attributes.id) continue;
|
||||||
const townIds = islandTownsMap[island_id];
|
|
||||||
|
|
||||||
let selected_town_id = null;
|
const status = rel.attributes.relation_status;
|
||||||
let lowest_total_res = Infinity;
|
const level = rel.attributes.expansion_stage || 0;
|
||||||
|
const expAt = rel.attributes.expansion_at || 0;
|
||||||
|
|
||||||
for (const t_id of townIds) {
|
if (expAt > now) { skipped++; continue; }
|
||||||
const t = uw.ITowns?.towns?.[t_id];
|
if (status === 1 && level >= 5) { skipped++; continue; }
|
||||||
if (!t) continue;
|
if (status < 0) { skipped++; continue; }
|
||||||
|
|
||||||
let storageCapacity = t.getStorageCapacity?.() || 0;
|
const isLocked = status === 0;
|
||||||
if (!storageCapacity) {
|
const action = isLocked ? 'unlock' : 'upgrade';
|
||||||
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 requestedAction = cmd.payload?.action_type;
|
||||||
const w = res.wood || 0;
|
if (requestedAction && requestedAction !== action) { skipped++; continue; }
|
||||||
const s = res.stone || 0;
|
|
||||||
const ir = res.iron || 0;
|
|
||||||
|
|
||||||
// If completely full (all 3 resources >= max storage), skip this town
|
if (paused) return { ok: false, msg: 'Aborted due to pause/captcha' };
|
||||||
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' };
|
|
||||||
}
|
|
||||||
|
|
||||||
|
log(`Farm ${action}: farm_id=${farm.attributes.id} level=${level} town=${town_id}`);
|
||||||
try {
|
try {
|
||||||
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', {
|
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', {
|
||||||
model_url: `FarmTownPlayerRelation/${farm.relation_id}`,
|
model_url: `FarmTownPlayerRelation/${rel.id}`,
|
||||||
action_name: 'claim',
|
action_name: action,
|
||||||
arguments: { farm_town_id: farm.farm_town_id, type: 'resources', option },
|
arguments: { farm_town_id: farm.attributes.id },
|
||||||
town_id: farm.town_id
|
town_id
|
||||||
});
|
});
|
||||||
claimed++;
|
isLocked ? unlocked++ : upgraded++;
|
||||||
} catch (e) { errors++; }
|
} catch (e) { errors++; }
|
||||||
|
|
||||||
// Random per-claim delay: 1000ms – 2200ms
|
await sleep(randInt(1200, 2500));
|
||||||
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` };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pushState();
|
||||||
|
return { ok: true, msg: `Farm upgrade done: ${unlocked} unlocked, ${upgraded} upgraded, ${skipped} skipped, ${errors} errors` };
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
// Execute: Build
|
// Execute: Farm Loot
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
async function executeBuild(cmd) {
|
async function executeFarmLoot(cmd) {
|
||||||
const { town_id, payload } = cmd;
|
const { loot_option } = cmd.payload || {};
|
||||||
const { building_id } = payload;
|
const option = parseInt(loot_option) || 1;
|
||||||
|
const now = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
const town = uw.ITowns?.getTown?.(town_id) || uw.ITowns?.towns?.[town_id];
|
let farmModels, relModels;
|
||||||
if (!town) {
|
try {
|
||||||
return { ok: false, msg: `Town ${town_id} not found in ITowns` };
|
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 = {};
|
||||||
|
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, skipped = 0, 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, s = res.stone || 0, ir = res.iron || 0;
|
||||||
|
|
||||||
|
// Skip completely full towns
|
||||||
|
if (storageCapacity > 0 && w >= storageCapacity && s >= storageCapacity && ir >= storageCapacity) continue;
|
||||||
|
|
||||||
|
const total_res = w + s + ir;
|
||||||
|
if (total_res < lowest_total_res) {
|
||||||
|
lowest_total_res = total_res;
|
||||||
|
selected_town_id = t_id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check build queue
|
if (!selected_town_id) {
|
||||||
const queueLen = town.buildingOrders?.()?.length ?? 0;
|
log(`Farm: Skipping island ${island_id} (All towns are 100% full)`);
|
||||||
const hasCurator = uw.GameDataPremium?.isAdvisorActivated?.('curator');
|
skipped++;
|
||||||
const maxQueue = hasCurator ? 7 : 2;
|
continue;
|
||||||
if (queueLen >= maxQueue) {
|
|
||||||
return { ok: false, requeue: true, msg: `Build queue full (${queueLen}/${maxQueue})` };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check resources
|
const town_id = selected_town_id;
|
||||||
try {
|
const town = uw.ITowns?.towns?.[town_id];
|
||||||
const buildData = uw.MM.getModels?.()?.BuildingBuildData?.[town_id]
|
if (!town) { skipped++; continue; }
|
||||||
?.attributes?.building_data?.[building_id];
|
|
||||||
if (buildData) {
|
const ix = town.getIslandCoordinateX();
|
||||||
const res = town.resources();
|
const iy = town.getIslandCoordinateY();
|
||||||
const { resources_for, population_for } = buildData;
|
if (ix == null || iy == null) { skipped++; continue; }
|
||||||
if (town.getAvailablePopulation?.() < population_for) {
|
|
||||||
return { ok: false, requeue: true, msg: `Not enough population for ${building_id}` };
|
const readyFarms = [];
|
||||||
}
|
for (const farm of farmModels) {
|
||||||
if (res.wood < resources_for.wood || res.stone < resources_for.stone || res.iron < resources_for.iron) {
|
if (farm.attributes.island_x !== ix || farm.attributes.island_y !== iy) continue;
|
||||||
return { ok: false, requeue: true, msg: `Not enough resources for ${building_id}` };
|
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
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
log(`Resource check skipped: ${e}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fire the build request — with a human-like reaction delay
|
if (readyFarms.length === 0) { skipped++; continue; }
|
||||||
const reactionMs = randInt(800, 2500);
|
log(`Farm: ${readyFarms.length} ready on island of town ${town_id}`);
|
||||||
log(`Waiting ${reactionMs}ms before firing buildUp (reaction time)...`);
|
|
||||||
await sleep(reactionMs);
|
|
||||||
|
|
||||||
if (paused) return { ok: false, msg: 'Aborted due to pause/captcha' };
|
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++; }
|
||||||
|
|
||||||
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', {
|
await sleep(randInt(1000, 2200));
|
||||||
model_url: 'BuildingOrder',
|
}
|
||||||
action_name: 'buildUp',
|
|
||||||
arguments: { building_id },
|
|
||||||
town_id
|
|
||||||
});
|
|
||||||
|
|
||||||
await sleep(500);
|
try { uw.WMap.removeFarmTownLootCooldownIconAndRefreshLootTimers(); } catch (e) {}
|
||||||
return { ok: true, msg: `buildUp ${building_id} queued` };
|
|
||||||
|
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: 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) {
|
// Execute: Build
|
||||||
return { ok: false, msg: `Town ${town_id} not found` };
|
// ----------------------------------------------------------------
|
||||||
}
|
async function executeBuild(cmd) {
|
||||||
|
const { town_id, payload } = cmd;
|
||||||
|
const { building_id } = payload;
|
||||||
|
|
||||||
// Determine endpoint based on unit type
|
const town = uw.ITowns?.getTown?.(town_id) || uw.ITowns?.towns?.[town_id];
|
||||||
const navalUnits = [
|
if (!town) return { ok: false, msg: `Town ${town_id} not found in ITowns` };
|
||||||
'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 queueLen = town.buildingOrders?.()?.length ?? 0;
|
||||||
const reactionMs = randInt(800, 2500);
|
const hasCurator = uw.GameDataPremium?.isAdvisorActivated?.('curator');
|
||||||
log(`Waiting ${reactionMs}ms before firing recruit (reaction time)...`);
|
const maxQueue = hasCurator ? 7 : 2;
|
||||||
await sleep(reactionMs);
|
if (queueLen >= maxQueue) {
|
||||||
|
return { ok: false, requeue: true, msg: `Build queue full (${queueLen}/${maxQueue})` };
|
||||||
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` };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
try {
|
||||||
// Execute: Market
|
const buildData = uw.MM.getModels?.()?.BuildingBuildData?.[town_id]
|
||||||
// ----------------------------------------------------------------
|
?.attributes?.building_data?.[building_id];
|
||||||
async function executeMarketOffer(cmd) {
|
if (buildData) {
|
||||||
const { town_id, payload } = cmd;
|
const res = town.resources();
|
||||||
const { offer, offer_type, demand, demand_type, max_delivery_time, visibility } = payload;
|
const { resources_for, population_for } = buildData;
|
||||||
|
if (town.getAvailablePopulation?.() < population_for) {
|
||||||
const town = uw.ITowns?.getTown?.(town_id) || uw.ITowns?.towns?.[town_id];
|
return { ok: false, requeue: true, msg: `Not enough population for ${building_id}` };
|
||||||
if (!town) {
|
}
|
||||||
return { ok: false, msg: `Town ${town_id} not found` };
|
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}` };
|
||||||
|
|
||||||
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` };
|
|
||||||
}
|
}
|
||||||
|
} catch (e) { log(`Resource check skipped: ${e}`); }
|
||||||
|
|
||||||
const reactionMs = randInt(800, 2500);
|
const reactionMs = randInt(800, 2500);
|
||||||
log(`Waiting ${reactionMs}ms before firing research (reaction time)...`);
|
log(`Waiting ${reactionMs}ms before firing buildUp (reaction time)...`);
|
||||||
await sleep(reactionMs);
|
await sleep(reactionMs);
|
||||||
|
|
||||||
if (paused) return { ok: false, msg: 'Aborted due to pause/captcha' };
|
if (paused) return { ok: false, msg: 'Aborted due to pause/captcha' };
|
||||||
|
|
||||||
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', {
|
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', {
|
||||||
model_url: 'ResearchOrder',
|
model_url: 'BuildingOrder',
|
||||||
action_name: 'research',
|
action_name: 'buildUp',
|
||||||
arguments: { id: research_id },
|
arguments: { building_id },
|
||||||
town_id: town_id
|
town_id
|
||||||
});
|
});
|
||||||
|
|
||||||
await sleep(500);
|
await sleep(500);
|
||||||
return { ok: true, msg: `Research ${research_id} queued` };
|
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` };
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,148 +1,137 @@
|
|||||||
// Poll for and execute pending commands (build + recruit + market)
|
// ================================================================
|
||||||
// ----------------------------------------------------------------
|
// 05_main.js — Poll loop, command dispatch, boot
|
||||||
async function pollAndExecute() {
|
// Depends on: everything above
|
||||||
if (paused) return;
|
// ================================================================
|
||||||
const player_id = uw.Game?.player_id;
|
|
||||||
if (!player_id) return;
|
|
||||||
|
|
||||||
let cmdData;
|
async function pollAndExecute() {
|
||||||
try {
|
if (paused) return;
|
||||||
const res = await fetch(`${BASE_URL}/api/commands/pending?player_id=${player_id}`);
|
const player_id = uw.Game?.player_id;
|
||||||
cmdData = await res.json();
|
if (!player_id) return;
|
||||||
} catch (e) {
|
|
||||||
log(`Poll failed: ${e}`);
|
let cmdData;
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build queue, Recruit queue and Market queue are independent
|
let result;
|
||||||
const buildCmd = cmdData.build;
|
try {
|
||||||
const recruitCmd = cmdData.recruit;
|
if (cmd.type === 'build') result = await executeBuild(cmd);
|
||||||
const marketCmd = cmdData.market;
|
else if (cmd.type === 'recruit') result = await executeRecruit(cmd);
|
||||||
const researchCmd = cmdData.research;
|
else if (cmd.type === 'market_offer') result = await executeMarketOffer(cmd);
|
||||||
const farmCmd = cmdData.farm;
|
else if (cmd.type === 'research') result = await executeResearch(cmd);
|
||||||
const farmUpgradeCmd = cmdData.farm_upgrade;
|
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) {
|
||||||
if (cmdData.sync_requested) {
|
result = { ok: false, msg: `Exception: ${e}` };
|
||||||
log('Sync requested by server — pushing state immediately');
|
|
||||||
pushState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const execute = async (cmd) => {
|
const finalStatus = result.requeue ? 'pending' : (result.ok ? 'done' : 'failed');
|
||||||
if (!cmd) return;
|
log(`Command #${cmd.id}: ${finalStatus === 'done' ? '✅' : finalStatus === 'pending' ? '⏳' : '❌'} ${result.msg}`);
|
||||||
log(`Executing command #${cmd.id} — type:${cmd.type} town:${cmd.town_id}`);
|
reportResult(cmd.id, finalStatus, result.msg);
|
||||||
if (paused) {
|
};
|
||||||
log(`[Paused] Ignoring command #${cmd.id}`);
|
|
||||||
return;
|
// 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) {
|
||||||
|
const storage = town.resources?.()?.storage || town.get?.('storage') || 0;
|
||||||
|
const wood = town.resources?.()?.wood || town.get?.('wood') || 0;
|
||||||
|
const stone = town.resources?.()?.stone || town.get?.('stone') || 0;
|
||||||
|
const iron = town.resources?.()?.iron || town.get?.('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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let result;
|
if (allFull) {
|
||||||
try {
|
log('⚠️ Auto-farm: ALL warehouses are full (>95%) — skipping loot this cycle');
|
||||||
if (cmd.type === 'build') result = await executeBuild(cmd);
|
try {
|
||||||
else if (cmd.type === 'recruit') result = await executeRecruit(cmd);
|
await fetch(`${BASE_URL}/api/farm_status?player_id=${uw.Game.player_id}`, {
|
||||||
else if (cmd.type === 'market_offer') result = await executeMarketOffer(cmd);
|
method: 'POST',
|
||||||
else if (cmd.type === 'research') result = await executeResearch(cmd);
|
headers: { 'Content-Type': 'application/json' },
|
||||||
else if (cmd.type === 'farm_loot') result = await executeFarmLoot(cmd);
|
body: JSON.stringify({ warehouse_full: true })
|
||||||
else if (cmd.type === 'farm_upgrade') result = await executeFarmUpgrade(cmd);
|
});
|
||||||
else result = { ok: false, msg: `Unknown type: ${cmd.type}` };
|
} catch (e) {}
|
||||||
} catch (e) {
|
} else if (claimedAny) {
|
||||||
result = { ok: false, msg: `Exception: ${e}` };
|
try {
|
||||||
}
|
await fetch(`${BASE_URL}/api/farm_status?player_id=${uw.Game.player_id}`, {
|
||||||
const finalStatus = result.requeue ? 'pending' : (result.ok ? 'done' : 'failed');
|
method: 'POST',
|
||||||
log(`Command #${cmd.id}: ${finalStatus === 'done' ? '✅' : finalStatus === 'pending' ? '⏳' : '❌'} ${result.msg}`);
|
headers: { 'Content-Type': 'application/json' },
|
||||||
reportResult(cmd.id, finalStatus, result.msg);
|
body: JSON.stringify({ warehouse_full: false })
|
||||||
};
|
});
|
||||||
|
} catch (e) {}
|
||||||
// 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);
|
|
||||||
// Check if ANY farm relation is ready
|
|
||||||
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) {
|
|
||||||
// Check if the CURRENT town's warehouse is full (>95%)
|
|
||||||
let allFull = true;
|
|
||||||
let claimedAny = false;
|
|
||||||
|
|
||||||
// Iterate over all towns that have ready farms
|
|
||||||
const towns = Object.values(uw.ITowns?.towns || {});
|
|
||||||
for (const town of towns) {
|
|
||||||
const storage = town.resources?.()?.storage || town.get?.('storage') || 0;
|
|
||||||
const wood = town.resources?.()?.wood || town.get?.('wood') || 0;
|
|
||||||
const stone = town.resources?.()?.stone || town.get?.('stone') || 0;
|
|
||||||
const iron = town.resources?.()?.iron || town.get?.('iron') || 0;
|
|
||||||
if (!storage) continue;
|
|
||||||
|
|
||||||
const maxRes = Math.max(wood, stone, iron);
|
|
||||||
const pct = maxRes / storage;
|
|
||||||
|
|
||||||
if (pct < 0.95) {
|
|
||||||
// This town has room — loot using its town_id context
|
|
||||||
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; // one loot pass is enough per poll cycle
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allFull) {
|
|
||||||
log('⚠️ Auto-farm: ALL warehouses are full (>95%) — skipping loot this cycle');
|
|
||||||
// Report full status to backend so farm.html can show notice
|
|
||||||
try {
|
|
||||||
await fetch(`${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) {
|
|
||||||
// Clear the full flag
|
|
||||||
try {
|
|
||||||
await fetch(`${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
|
// Boot
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
log('Grepolis Remote Control v3.5.9 loaded');
|
log('Grepolis Remote Control v4.0.0 (remote) loaded');
|
||||||
|
|
||||||
// Start captcha watcher immediately
|
detectCaptcha();
|
||||||
detectCaptcha();
|
|
||||||
|
|
||||||
// Push state once after load, then heartbeat every 1-2 minutes
|
|
||||||
// The AJAX interceptor handles the real-time syncing!
|
|
||||||
setTimeout(pushState, 5000);
|
|
||||||
jitterLoop(pushState, 60000, 120000);
|
|
||||||
|
|
||||||
// Poll for commands every 8–18 seconds (randomized jitter)
|
|
||||||
jitterLoop(pollAndExecute, 8000, 18000);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
setTimeout(pushState, 5000);
|
||||||
|
jitterLoop(pushState, 60000, 120000);
|
||||||
|
jitterLoop(pollAndExecute, 8000, 18000);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user