diff --git a/town.js b/town.js new file mode 100644 index 0000000..fb81c5a --- /dev/null +++ b/town.js @@ -0,0 +1,175 @@ +// ==UserScript== +// @name Grepolis Town Stats Extended Clean (with Sea, Buildings, Researches) +// @namespace http://tampermonkey.net/ +// @version 1.4 +// @description Sends town stats with coords, sea, full building orders, and researches +// @author Dimitrios +// @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) loaded"); + + // ----------------------------- + // Toolbar pause/resume button + // ----------------------------- + const toolbarButtonHtml = ` +
+
+

TownScan

+
`; + + 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) { + const seaY = Math.floor(y / 100); + const seaX = Math.floor(x / 100); + return seaY * 10 + seaX; + } + + // ----------------------------- + // 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 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(); + } + + 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: town.points || 0, + 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, 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(); + }); + +})();