diff --git a/blueprint_engine.py b/blueprint_engine.py new file mode 100644 index 0000000..cc0d99e --- /dev/null +++ b/blueprint_engine.py @@ -0,0 +1,114 @@ +import json + +STANDARD_BLUEPRINT = [ + {"barracks": 1, "farm": 3, "lumber": 2, "stoner": 2, "ironer": 2, "storage": 2, "main": 2, "temple": 1}, + {"barracks": 1, "farm": 3, "lumber": 3, "stoner": 3, "ironer": 3, "storage": 6, "main": 8}, + {"farm": 8, "lumber": 8, "ironer": 8, "stoner": 8, "market": 5, "temple": 5, "barracks": 5}, + {"academy": 13}, + {"storage": 12, "farm": 12}, + {"main": 25}, + {"storage": 21, "farm": 15}, + {"lumber": 15, "stoner": 10, "ironer": 12}, + {"docks": 10}, + {"academy": 30}, + {"farm": 20, "storage": 25}, + {"market": 15, "trade_office": 1, "hide": 10}, + {"market": 30, "farm": 35, "thermal": 1, "academy": 36}, + {"farm": 45, "storage": 35, "lumber": 40, "ironer": 40, "stoner": 40}, + {"temple": 30} +] + +RESEARCH_LIST = [ + "booty", "pottery", "architecture", "building_crane", + "shipwright", "plow", "mathematics", "combat_experience", + "strong_wine", "take_over", "colonize_ship" +] + +RESEARCH_LEVELS = { + "booty": 7, + "pottery": 7, + "architecture": 10, + "building_crane": 13, + "shipwright": 13, + "plow": 22, + "mathematics": 25, + "combat_experience": 34, + "strong_wine": 34, + "take_over": 28, + "colonize_ship": 13 +} + +def evaluate_blueprints(conn): + blueprints = conn.execute('SELECT town_id, blueprint_name FROM town_blueprints WHERE is_active = 1').fetchall() + if not blueprints: + return + + for row in blueprints: + town_id = str(row['town_id']) + town_row = conn.execute('SELECT data FROM town_state WHERE town_id = ?', (town_id,)).fetchone() + if not town_row: + continue + + try: + town = json.loads(town_row['data']) + except Exception: + continue + + build_queue = town.get('build_queue', []) + buildings = town.get('buildings', {}) + build_data = town.get('build_data', {}) + + # Don't queue anything if there's already a pending/executing command in DB + db_pending = conn.execute("SELECT id FROM commands WHERE town_id = ? AND type IN ('build', 'research') AND status IN ('pending', 'executing')", (town_id,)).fetchall() + if db_pending: + continue + + # Calculate Future Levels based on current + game queue + future_levels = {k: v for k, v in buildings.items()} + for q_item in build_queue: + b_type = q_item.get('building_type') or q_item.get('name') + if b_type: + future_levels[b_type] = future_levels.get(b_type, 0) + 1 + + # Find next required building + target_building = None + for phase in STANDARD_BLUEPRINT: + incomplete = False + for b_name, req_level in phase.items(): + if future_levels.get(b_name, 0) < req_level: + incomplete = True + b_info = build_data.get(b_name) + if b_info and not b_info.get('has_max_level'): + if b_info.get('enough_resources') != False: + target_building = b_name + break + if incomplete: + break + + # Handle Academy Tech Research if no building was prioritized + target_research = None + if not target_building: + academy_level = future_levels.get('academy', 0) + researched = town.get('researches', {}) + research_queue = town.get('research_queue', []) + + for r_name in RESEARCH_LIST: + if not researched.get(r_name) and r_name not in [q.get('type') for q in research_queue]: + # Check if academy level is high enough + req_level = RESEARCH_LEVELS.get(r_name, 99) + if academy_level >= req_level: + target_research = r_name + break + + if target_building: + payload_str = json.dumps({"building_id": target_building}) + conn.execute(''' + INSERT INTO commands (town_id, town_name, type, payload, status, player_id) + VALUES (?, ?, ?, ?, ?, ?) + ''', (town_id, town.get('town_name'), 'build', payload_str, 'pending', town.get('player_id'))) + elif target_research: + payload_str = json.dumps({"research_id": target_research}) + conn.execute(''' + INSERT INTO commands (town_id, town_name, type, payload, status, player_id) + VALUES (?, ?, ?, ?, ?, ?) + ''', (town_id, town.get('town_name'), 'research', payload_str, 'pending', town.get('player_id'))) diff --git a/db.py b/db.py index 07588d2..69bf513 100644 --- a/db.py +++ b/db.py @@ -92,6 +92,16 @@ def init_db(): ''') c.execute('CREATE INDEX IF NOT EXISTS idx_bot_logs_player_feature ON bot_logs(player_id, feature)') + # Blueprints - assigns a blueprint to a specific town + c.execute(''' + CREATE TABLE IF NOT EXISTS town_blueprints ( + town_id TEXT PRIMARY KEY, + blueprint_name TEXT NOT NULL, + is_active INTEGER NOT NULL DEFAULT 1, + updated_at TEXT NOT NULL DEFAULT (datetime('now')) + ) + ''') + # Migration: add new columns if upgrading an existing database for _col in [ 'ALTER TABLE town_state ADD COLUMN player_id TEXT', diff --git a/routes/api.py b/routes/api.py index 004ce6d..f1837e7 100644 --- a/routes/api.py +++ b/routes/api.py @@ -4,6 +4,7 @@ import json from datetime import datetime, timedelta import os from flask import make_response +from blueprint_engine import evaluate_blueprints api = Blueprint('api', __name__) @@ -94,6 +95,13 @@ def receive_state(): datetime.utcnow().isoformat() )) conn.commit() + + try: + evaluate_blueprints(conn) + conn.commit() + except Exception as e: + print("Error evaluating blueprints:", e) + conn.close() return jsonify({'ok': True, 'towns_updated': len(towns)}) diff --git a/routes/dashboard.py b/routes/dashboard.py index 036f8c9..78026a2 100644 --- a/routes/dashboard.py +++ b/routes/dashboard.py @@ -178,11 +178,13 @@ def get_towns(): player_id = request.args.get('player_id') conn = get_db() rows = conn.execute(''' - SELECT town_id, town_name, player, player_id, alliance_id, - world_id, x, y, sea, data, updated_at - FROM town_state - WHERE player_id = ? - ORDER BY town_name ASC + SELECT ts.town_id, ts.town_name, ts.player, ts.player_id, ts.alliance_id, + ts.world_id, ts.x, ts.y, ts.sea, ts.data, ts.updated_at, + tb.blueprint_name, tb.is_active as blueprint_active + FROM town_state ts + LEFT JOIN town_blueprints tb ON ts.town_id = tb.town_id AND tb.is_active = 1 + WHERE ts.player_id = ? + ORDER BY ts.town_name ASC ''', (player_id, )).fetchall() conn.close() @@ -220,11 +222,51 @@ def get_towns(): 'bonuses': d.get('bonuses', {}), 'wonder_points': d.get('wonder_points', 0), 'total_points': d.get('total_points', 0), - 'alliance_name': d.get('alliance_name', None) + 'alliance_name': d.get('alliance_name', None), + 'blueprint_name': row['blueprint_name'], + 'blueprint_active': bool(row['blueprint_active']) }) return jsonify(towns) +# ------------------------------------------------------------------ +# POST /dashboard/blueprints +# Toggle a blueprint for a specific town +# ------------------------------------------------------------------ +@dashboard.route('/dashboard/blueprints', methods=['POST']) +@login_required +def toggle_blueprint(): + data = request.get_json(silent=True) or {} + town_id = data.get('town_id') + blueprint_name = data.get('blueprint_name', 'Standard Growth') + + if not town_id: + return jsonify({'error': 'missing town_id'}), 400 + + conn = get_db() + + # Check if currently active + row = conn.execute('SELECT is_active FROM town_blueprints WHERE town_id = ?', (town_id,)).fetchone() + + new_state = 1 + if row and row['is_active'] == 1: + new_state = 0 # Toggle off + + conn.execute(''' + INSERT INTO town_blueprints (town_id, blueprint_name, is_active, updated_at) + VALUES (?, ?, ?, ?) + ON CONFLICT(town_id) DO UPDATE SET + blueprint_name = excluded.blueprint_name, + is_active = excluded.is_active, + updated_at = excluded.updated_at + ''', (town_id, blueprint_name, new_state, datetime.utcnow().isoformat())) + + conn.commit() + conn.close() + + return jsonify({'ok': True, 'is_active': bool(new_state), 'blueprint_name': blueprint_name}) + + # ------------------------------------------------------------------ # GET /dashboard/client-status # Returns whether the Tampermonkey client is considered online. diff --git a/static/css/styles.css b/static/css/styles.css index 920f606..6a6a893 100644 --- a/static/css/styles.css +++ b/static/css/styles.css @@ -318,9 +318,9 @@ tr:hover td { background: #1e1e40; } } /* ========================================================================== - Building, Academy, Unit & Market Modals + Building, Academy, Unit, Market & Blueprint Modals ========================================================================== */ -#building-modal-overlay, #academy-modal-overlay, #unit-modal-overlay, #market-modal-overlay { +#building-modal-overlay, #academy-modal-overlay, #unit-modal-overlay, #market-modal-overlay, #blueprints-modal-overlay { display: none; position: fixed; inset: 0; @@ -329,9 +329,9 @@ tr:hover td { background: #1e1e40; } align-items: center; justify-content: center; } -#building-modal-overlay.open, #academy-modal-overlay.open, #unit-modal-overlay.open, #market-modal-overlay.open { display: flex; } +#building-modal-overlay.open, #academy-modal-overlay.open, #unit-modal-overlay.open, #market-modal-overlay.open, #blueprints-modal-overlay.open { display: flex; } -#building-modal, #academy-modal, #unit-modal, #market-modal { +#building-modal, #academy-modal, #unit-modal, #market-modal, #blueprints-modal { background: #16213e; border: 2px solid #c8a44a; border-radius: 10px; diff --git a/static/js/api.js b/static/js/api.js index cd3b8e2..b3b17e5 100644 --- a/static/js/api.js +++ b/static/js/api.js @@ -182,6 +182,30 @@ window.sendCommand = async function() { } payload = { research_id }; + } else if (type === 'blueprints') { + const blueprint_name = window.selectedBlueprintName; + if (!blueprint_name) return alert('Παρακαλώ επιλέξτε Blueprint.'); + + try { + const res = await fetch('/dashboard/blueprints', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + town_id: town.town_id, + blueprint_name: blueprint_name + }) + }); + const data = await res.json(); + if (data.ok) { + alert(data.is_active ? 'Blueprint ενεργοποιήθηκε!' : 'Blueprint απενεργοποιήθηκε!'); + window.fetchTowns(); // refresh towns to update UI state + } else { + alert('Σφάλμα: ' + data.error); + } + } catch(e) { + alert('Failed to toggle blueprint: ' + e); + } + return; // Exit here as we don't send this to /dashboard/commands } try { diff --git a/static/js/components/commandForm.js b/static/js/components/commandForm.js index b030299..efbbc62 100644 --- a/static/js/components/commandForm.js +++ b/static/js/components/commandForm.js @@ -49,6 +49,12 @@ window.updateSelectionDisplay = function() { } } else if (type === 'market_offer') { labelEl.innerHTML = `🛒 Ρυθμίσεις Παζαριού`; + } else if (type === 'blueprints') { + if (window.selectedBlueprintName) { + labelEl.innerHTML = `📜 ${window.selectedBlueprintName}`; + } else { + labelEl.innerHTML = `-- Επιλέξτε Blueprint --`; + } } }; @@ -58,6 +64,22 @@ window.reopenActiveModal = function() { else if (type === 'recruit') window.openUnitModal(); else if (type === 'research') window.openAcademyModal(); else if (type === 'market_offer') window.openMarketModal(); + else if (type === 'blueprints') window.openBlueprintsModal(); +}; + +window.selectedBlueprintName = null; +window.selectBlueprint = function(name) { + window.selectedBlueprintName = name; + window.updateSelectionDisplay(); + window.closeBlueprintsModal(); +}; + +window.openBlueprintsModal = function() { + document.getElementById('blueprints-modal-overlay').classList.add('open'); +}; +window.closeBlueprintsModal = function(e) { + if (e && e.target.id !== 'blueprints-modal-overlay' && !e.target.classList.contains('modal-close')) return; + document.getElementById('blueprints-modal-overlay').classList.remove('open'); }; window.openMarketModal = function() { diff --git a/static/js/components/townViewer.js b/static/js/components/townViewer.js index c928a84..217f996 100644 --- a/static/js/components/townViewer.js +++ b/static/js/components/townViewer.js @@ -212,4 +212,43 @@ window.renderTownDetails = function() { if(unitsHtml === '') unitsHtml = '
Κανένα στράτευμα
'; document.getElementById('td-units').innerHTML = unitsHtml; + + // ---- Blueprint Lock & Indicator ---- + const isBlueprintActive = !!t.blueprint_active; + const bpName = t.blueprint_name || 'Standard Growth'; + + if (isBlueprintActive) { + document.getElementById('td-general').innerHTML += ` +
+ 🤖 Blueprint: ${bpName} (ΕΝΕΡΓΟ) +
+ `; + } + + const btnBuild = document.getElementById('seg-build'); + const btnResearch = document.getElementById('seg-research'); + + if (btnBuild && btnResearch) { + if (isBlueprintActive) { + btnBuild.style.opacity = '0.3'; + btnBuild.style.pointerEvents = 'none'; + btnBuild.title = 'Απενεργοποιημένο λόγω Blueprint'; + + btnResearch.style.opacity = '0.3'; + btnResearch.style.pointerEvents = 'none'; + btnResearch.title = 'Απενεργοποιημένο λόγω Blueprint'; + + if (window.currentCmdType === 'build' || window.currentCmdType === 'research') { + window.setCmdType('recruit'); + } + } else { + btnBuild.style.opacity = '1'; + btnBuild.style.pointerEvents = 'auto'; + btnBuild.title = ''; + + btnResearch.style.opacity = '1'; + btnResearch.style.pointerEvents = 'auto'; + btnResearch.title = ''; + } + } }; diff --git a/templates/dashboard.html b/templates/dashboard.html index 292117f..e6cf56c 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -91,6 +91,7 @@ + @@ -127,6 +128,31 @@ + + +