191 lines
6.5 KiB
JavaScript
191 lines
6.5 KiB
JavaScript
// ==UserScript==
|
|
// @name Grepolis Town Stats Extended Clean (with Sea, Buildings, Researches, Points, Gods)
|
|
// @namespace http://tampermonkey.net/
|
|
// @version 1.8
|
|
// @description Sends town stats with coords, sea, full building orders, researches, points, and gods
|
|
// @author Dimitrios + GPT
|
|
// @match https://*.grepolis.com/game/*
|
|
// @grant unsafeWindow
|
|
// ==/UserScript==
|
|
|
|
(function () {
|
|
'use strict';
|
|
|
|
const uw = unsafeWindow || window;
|
|
let paused = false;
|
|
|
|
console.log("🚀 Town Stats Extended Clean (with Sea, Buildings, Researches, Points, Gods) loaded");
|
|
|
|
// -----------------------------
|
|
// Toolbar pause/resume button
|
|
// -----------------------------
|
|
const toolbarButtonHtml = `
|
|
<div class="divider"></div>
|
|
<div class="activity" id="tsbutton"
|
|
style="filter: brightness(70%) sepia(100%) hue-rotate(-50deg) saturate(1000%) contrast(0.8);">
|
|
<p id="tslabel" style="position: relative; top: -8px; font-weight: bold; z-index:6;">TownScan</p>
|
|
</div>`;
|
|
|
|
function togglePause() {
|
|
paused = !paused;
|
|
const label = document.getElementById('tslabel');
|
|
const btn = document.getElementById('tsbutton');
|
|
|
|
if (paused) {
|
|
label.textContent = 'Paused';
|
|
btn.style.filter = 'brightness(70%) sepia(100%) hue-rotate(-50deg) saturate(1000%) contrast(0.8)';
|
|
} else {
|
|
label.textContent = 'TownScan';
|
|
btn.style.filter = 'brightness(294%) sepia(100%) hue-rotate(15deg) saturate(1000%) contrast(0.8)';
|
|
}
|
|
|
|
console.log(`🔘 TownScan is now ${paused ? 'paused' : 'running'}`);
|
|
}
|
|
|
|
setTimeout(() => {
|
|
if (!document.getElementById('tsbutton')) {
|
|
uw.$('.tb_activities, .toolbar_activities').find('.middle').append(toolbarButtonHtml);
|
|
}
|
|
}, 4000);
|
|
|
|
uw.$(document).on('click', '#tsbutton', togglePause);
|
|
|
|
// -----------------------------
|
|
// Compute Sea from x,y
|
|
// -----------------------------
|
|
function computeSea(x, y) {
|
|
if (typeof x !== 'number' || typeof y !== 'number') return null;
|
|
const sx = Math.floor(x / 100);
|
|
const sy = Math.floor(y / 100);
|
|
return sx * 10 + sy;
|
|
}
|
|
|
|
// -----------------------------
|
|
// Gather Town Stats
|
|
// -----------------------------
|
|
function gatherTownStats() {
|
|
const towns = uw.ITowns?.towns || {};
|
|
const player = uw.Game?.player_name || "unknown";
|
|
const player_id = uw.Game?.player_id || "unknown";
|
|
const total_points = uw.Game?.player_points || 0;
|
|
|
|
const townStats = Object.values(towns).map(town => {
|
|
const res = town.resources();
|
|
const buildings = town.buildings()?.attributes ?? {};
|
|
|
|
// Units fix
|
|
const unitsObj = {};
|
|
const units = town.units();
|
|
if (units) {
|
|
Object.keys(units).forEach(type => {
|
|
if (typeof units[type] === 'number') {
|
|
unitsObj[type] = units[type];
|
|
} else if (typeof units[type]?.getAmount === 'function') {
|
|
unitsObj[type] = units[type].getAmount();
|
|
} else {
|
|
unitsObj[type] = 0;
|
|
}
|
|
});
|
|
}
|
|
|
|
// Coordinates & sea
|
|
const x = town.getIslandCoordinateX ? town.getIslandCoordinateX() : null;
|
|
const y = town.getIslandCoordinateY ? town.getIslandCoordinateY() : null;
|
|
const sea = (x !== null && y !== null) ? computeSea(x, y) : null;
|
|
|
|
// Full building orders
|
|
let buildingOrder = [];
|
|
if (typeof town.buildingOrders === "function") {
|
|
const bo = town.buildingOrders();
|
|
if (bo && bo.models) {
|
|
buildingOrder = bo.models.map(m => m.attributes);
|
|
}
|
|
}
|
|
|
|
// Researches
|
|
let researches = {};
|
|
if (typeof town.researches === "function") {
|
|
researches = town.researches();
|
|
}
|
|
|
|
// Village points — use fixed getPoints
|
|
let points = 0;
|
|
if (typeof town.getPoints === 'function') {
|
|
points = town.getPoints();
|
|
}
|
|
|
|
// God — keep original logic
|
|
let god = null;
|
|
if (typeof town.god === 'function') {
|
|
god = town.god();
|
|
}
|
|
|
|
return {
|
|
town_id: town.id,
|
|
town_name: town.name,
|
|
x, y, sea,
|
|
wood: res.wood,
|
|
stone: res.stone,
|
|
iron: res.iron,
|
|
population: res.population,
|
|
points,
|
|
god,
|
|
buildings,
|
|
units: unitsObj,
|
|
buildingOrder,
|
|
researches,
|
|
has_premium: town.hasPremium?.() || false,
|
|
bonuses: town.getBonus ? town.getBonus() : {},
|
|
wonder_points: town.wonder_points || 0
|
|
};
|
|
});
|
|
|
|
return { player, player_id, total_points, towns: townStats };
|
|
}
|
|
|
|
// -----------------------------
|
|
// Send Data
|
|
// -----------------------------
|
|
function sendData() {
|
|
if (paused) return console.log("⏸️ TownScan is paused — skipping data send");
|
|
|
|
try {
|
|
const payload = {
|
|
type: "extended",
|
|
timestamp: new Date().toISOString(),
|
|
...gatherTownStats(),
|
|
server_received_at: new Date().toISOString()
|
|
};
|
|
|
|
console.log("📤 Sending payload:", payload);
|
|
|
|
fetch("https://grepo.haunter-pets.top/api/grepolis-data", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(payload)
|
|
})
|
|
.then(res => res.text())
|
|
.then(txt => console.log("✅ Data sent:", txt))
|
|
.catch(err => console.error("❌ Failed to send:", err));
|
|
} catch (e) {
|
|
console.error("💥 Error sending stats:", e);
|
|
}
|
|
}
|
|
|
|
// -----------------------------
|
|
// Main Loop
|
|
// -----------------------------
|
|
function startCycle() {
|
|
setInterval(() => {
|
|
console.log("🕒 TownScan cycle tick");
|
|
sendData();
|
|
}, 30000);
|
|
}
|
|
|
|
window.addEventListener("load", () => {
|
|
console.log("🚀 TownScan booting...");
|
|
startCycle();
|
|
});
|
|
|
|
})();
|