live scan fix
This commit is contained in:
@@ -126,8 +126,9 @@
|
|||||||
|
|
||||||
// --- Bind to GameEvents (passive, zero server cost) ---
|
// --- Bind to GameEvents (passive, zero server cost) ---
|
||||||
try {
|
try {
|
||||||
// New incoming attack detected
|
// New incoming attack detected (This event works according to logs)
|
||||||
uw.$.Observer(uw.GameEvents.attack.incoming).subscribe(
|
const attackEvent = uw.GameEvents?.attack?.incoming || 'attack:incoming';
|
||||||
|
uw.$.Observer(attackEvent).subscribe(
|
||||||
'GRC_TRACKER_ATTACK',
|
'GRC_TRACKER_ATTACK',
|
||||||
function(e, data) {
|
function(e, data) {
|
||||||
// Small delay so game model updates before we read it
|
// Small delay so game model updates before we read it
|
||||||
@@ -141,17 +142,25 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Any command state changed (sent, landed, recalled, etc.)
|
// Any command state changed (sent, landed, recalled, etc.)
|
||||||
uw.$.Observer(uw.GameEvents.command.change).subscribe(
|
// Fallback to string if the constant doesn't exist in this version
|
||||||
|
const cmdEvent = (uw.GameEvents?.command && uw.GameEvents.command.change)
|
||||||
|
? uw.GameEvents.command.change
|
||||||
|
: 'CommandsMenuBubble:change';
|
||||||
|
|
||||||
|
uw.$.Observer(cmdEvent).subscribe(
|
||||||
'GRC_TRACKER_CMD',
|
'GRC_TRACKER_CMD',
|
||||||
function(e, data) {
|
function(e, data) {
|
||||||
setTimeout(_pushMovements, 500);
|
setTimeout(_pushMovements, 500);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
log('[tracker] ✅ Subscribed to command.change event');
|
log('[tracker] ✅ Subscribed to command changes');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(`[tracker] Could not subscribe to command.change: ${e}`);
|
log(`[tracker] Could not subscribe to command changes: ${e}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Failsafe: push every 15 seconds regardless of events ---
|
||||||
|
setInterval(_pushMovements, 15000);
|
||||||
|
|
||||||
}, 6000); // 6s after boot — ensures CommandsMenuBubble model is loaded
|
}, 6000); // 6s after boot — ensures CommandsMenuBubble model is loaded
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
111
future_ideas.md
Normal file
111
future_ideas.md
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
# Future Ideas & Optimizations
|
||||||
|
|
||||||
|
This document tracks potential architectural improvements and features inspired by other Grepolis alliance coordination scripts (like GrepoData and Noct).
|
||||||
|
|
||||||
|
## 1. Timestamp-Based Polling Optimization (`since` parameter)
|
||||||
|
**Inspired by:** `noct-api.grepo-soft.workers.dev`
|
||||||
|
|
||||||
|
**Current State:**
|
||||||
|
The Tampermonkey client polls the server every 8-18 seconds and receives the full state/command payload every time, even if nothing has changed.
|
||||||
|
|
||||||
|
**Proposed Implementation:**
|
||||||
|
- Add a `since` timestamp parameter to the client's poll requests.
|
||||||
|
- The server checks if any commands or state updates have occurred *after* the `since` timestamp.
|
||||||
|
- **If no new data:** The server returns an empty `HTTP 204 No Content` response.
|
||||||
|
- **If new data:** The server returns `HTTP 200 OK` with only the data that changed.
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- Drastically reduces server bandwidth and CPU load.
|
||||||
|
- Minimizes the size of network requests on the client side, making the script stealthier and less resource-intensive in the browser.
|
||||||
|
|
||||||
|
**Concrete Code Example (How Noct does it):**
|
||||||
|
```javascript
|
||||||
|
// 1. Client-side polling logic
|
||||||
|
let lastFetchTime = Date.now();
|
||||||
|
|
||||||
|
async function pollCommands() {
|
||||||
|
const url = `https://noct-api.grepo-soft.workers.dev/api/alliance/commands` +
|
||||||
|
`?alliance=p0PmzsZMo4xZ2o29uvqggy5d` +
|
||||||
|
`&world=gr118` +
|
||||||
|
`&clientId=848938473` +
|
||||||
|
`&since=${lastFetchTime}`; // Ask only for things after this timestamp
|
||||||
|
|
||||||
|
const response = await fetch(url);
|
||||||
|
|
||||||
|
// 2. Server returns HTTP 204 (No Content) if nothing new happened
|
||||||
|
if (response.status === 204) {
|
||||||
|
return; // Empty payload, exit early
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Server returns HTTP 200 (OK) only if there are NEW commands
|
||||||
|
if (response.status === 200) {
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
// Process new commands...
|
||||||
|
executeCommands(data.commands);
|
||||||
|
|
||||||
|
// Update the timestamp so the next poll only asks for things after this moment
|
||||||
|
lastFetchTime = Date.now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Backend (Python/Flask Equivalent)
|
||||||
|
@app.route('/api/alliance/commands')
|
||||||
|
def get_commands():
|
||||||
|
# Get the timestamp from the URL query
|
||||||
|
since_ts = int(request.args.get('since', 0))
|
||||||
|
|
||||||
|
# Query DB for commands created AFTER the 'since' timestamp
|
||||||
|
new_commands = db.execute(
|
||||||
|
"SELECT * FROM commands WHERE created_at_ts > ?", (since_ts,)
|
||||||
|
).fetchall()
|
||||||
|
|
||||||
|
if not new_commands:
|
||||||
|
# Return empty body with 204 No Content
|
||||||
|
return '', 204
|
||||||
|
|
||||||
|
return jsonify({"commands": new_commands}), 200
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. WebSocket Architecture for Real-Time Synchronization
|
||||||
|
**Inspired by:** `grepodata.com` (ReactPHP WebSocket server)
|
||||||
|
|
||||||
|
**Current State:**
|
||||||
|
Command delivery relies on HTTP polling. If an attack plan requires a launch in 30 seconds, but the client is on an 18-second polling interval, there is a high risk of missing the execution window or executing late.
|
||||||
|
|
||||||
|
**Proposed Implementation:**
|
||||||
|
- Integrate `Flask-SocketIO` (or a standalone async WebSocket server) into the backend.
|
||||||
|
- The client script establishes a persistent `wss://` connection upon loading the game.
|
||||||
|
- The client authenticates using its `clan_key` and subscribes to its alliance "topic/room".
|
||||||
|
- When an admin arms an attack plan or a player updates their town state, the server instantly *pushes* the payload to all connected alliance members.
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- **Zero Polling Latency:** Commands arrive in ~100ms instead of 8-18 seconds.
|
||||||
|
- **Perfect Attack Timing:** Ensures clients receive armed plans immediately, maximizing the margin for precise execution.
|
||||||
|
- **Instant UI Updates:** The dashboard and attack planner can update in real-time as members come online or troop counts change.
|
||||||
|
|
||||||
|
## 3. Server-Sent Events (SSE) Lag/Refresh Bug Fix
|
||||||
|
**Issue:**
|
||||||
|
When refreshing or hitting the "back" button on the Live Tracker (`tracker.html`), the page occasionally hangs, lags heavily, or completely fails to load.
|
||||||
|
|
||||||
|
**Root Cause:**
|
||||||
|
The Live Tracker uses SSE (`EventSource`) to receive real-time movement updates. Modern browsers strictly limit simultaneous HTTP/1.1 connections to the same server (usually 6 maximum). When the user navigates away or refreshes, the browser drops the frontend page, but the Python/Flask backend (`tracker.py`) does not immediately detect the broken pipe and keeps the socket open, waiting to send data.
|
||||||
|
If the user hits refresh multiple times, these "ghost" connections stack up. Upon reaching 6 ghost connections, the browser refuses to load any further requests until the old connections naturally time out (which can take 30+ seconds).
|
||||||
|
|
||||||
|
**Proposed Fix:**
|
||||||
|
1. **Client-side (`tracker.html`)**: Ensure the browser explicitly tells the server the connection is closing exactly as the page unloads.
|
||||||
|
```javascript
|
||||||
|
window.addEventListener('beforeunload', () => {
|
||||||
|
if (typeof es !== 'undefined' && es !== null) {
|
||||||
|
es.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Server-side (`tracker.py`)**: Ensure the generator handles client disconnects gracefully and immediately cleans up the subscriber queue without waiting for a timeout.
|
||||||
|
```python
|
||||||
|
# Make sure the generator yields spaces/heartbeats actively so the OS
|
||||||
|
# throws an IOError/GeneratorExit the moment the client drops.
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user