fix que / add auto

This commit is contained in:
2026-05-07 20:22:28 +03:00
parent 51d15118ed
commit 74b51e74ca
6 changed files with 467 additions and 54 deletions

View File

@@ -146,10 +146,103 @@ def receive_state():
''', (str(player_id), world_id, town_id_cel, t_name, cel_type, finished_at, now_iso))
conn.commit()
# ── Auto-culture: check per-town settings and queue if eligible ──
# Runs on every state push — no extra game polling needed.
_auto_culture_check(c, str(player_id), world_id, towns, battle_points)
conn.commit()
conn.close()
return jsonify({'ok': True, 'towns_updated': len(towns)})
# Cost constants (must match dashboard.py PARTY_COST / TRIUMPH_COST)
_PARTY_COST = {'wood': 15000, 'stone': 18000, 'iron': 15000}
_TRIUMPH_BP = 300
def _auto_culture_check(c, player_id, world_id, towns, battle_points):
"""Called after every state push.
For each town that has auto_party or auto_triumph enabled:
- skip if a pending/executing entry already exists in culture_queue
- skip if celebration cooldown hasn't expired yet
- skip if resources/battle-points are insufficient
- otherwise insert a new 'auto' entry into culture_queue
"""
if not player_id or not world_id:
return
now_ts = int(datetime.utcnow().timestamp())
now_iso = datetime.utcnow().isoformat()
bp_available = battle_points.get('available', 0) if isinstance(battle_points, dict) else 0
# Load auto settings for this player/world
auto_rows = c.execute(
'SELECT town_id, auto_party, auto_triumph FROM culture_settings WHERE player_id = ? AND world_id = ?',
(player_id, world_id)
).fetchall()
if not auto_rows:
return
# Build a quick lookup of town data from the state payload
town_map = {str(t.get('town_id', '')): t for t in towns}
for auto_row in auto_rows:
tid = str(auto_row['town_id'])
td = town_map.get(tid)
if not td:
continue # town not in this state push (shouldn't happen but guard anyway)
town_name = td.get('town_name', '')
for cel_type, enabled in [('party', auto_row['auto_party']), ('triumph', auto_row['auto_triumph'])]:
if not enabled:
continue
# 1. Already queued?
existing = c.execute(
"SELECT id FROM culture_queue WHERE player_id=? AND world_id=? AND celebration_type=? AND status IN ('pending','executing')",
(player_id, world_id, cel_type)
).fetchone()
if existing:
continue
# 2. Cooldown still active?
cel_cd = c.execute(
'SELECT finished_at FROM celebrations WHERE player_id=? AND world_id=? AND town_id=? AND celebration_type=?',
(player_id, world_id, tid, cel_type)
).fetchone()
if cel_cd and int(cel_cd['finished_at'] or 0) > now_ts:
continue # still cooling down
# 3. Resources check
if cel_type == 'party':
if (td.get('wood', 0) < _PARTY_COST['wood'] or
td.get('stone', 0) < _PARTY_COST['stone'] or
td.get('iron', 0) < _PARTY_COST['iron']):
continue # not enough yet — will retry on next state push
else: # triumph
if bp_available < _TRIUMPH_BP:
continue
# All clear — queue it
c.execute('''
INSERT INTO culture_queue
(player_id, world_id, town_id, town_name, celebration_type, status, source, created_at)
VALUES (?, ?, ?, ?, ?, 'pending', 'auto', ?)
''', (player_id, world_id, tid, town_name, cel_type, now_iso))
# Also log it
cost_w = _PARTY_COST['wood'] if cel_type == 'party' else 0
cost_s = _PARTY_COST['stone'] if cel_type == 'party' else 0
cost_i = _PARTY_COST['iron'] if cel_type == 'party' else 0
cost_b = _TRIUMPH_BP if cel_type == 'triumph' else 0
c.execute('''
INSERT INTO culture_log
(player_id, world_id, town_id, town_name, celebration_type,
cost_wood, cost_stone, cost_iron, cost_battle_pts, status, source, fired_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', 'auto', ?)
''', (player_id, world_id, tid, town_name, cel_type,
cost_w, cost_s, cost_i, cost_b, now_iso))
# ------------------------------------------------------------------
@@ -184,7 +277,7 @@ def _fetch_pending_of_type(c, cmd_type, player_id, world_id):
if not row:
return None
c.execute('''
c.execute('''\
UPDATE commands
SET status = 'executing', updated_at = ?
WHERE id = ?
@@ -197,7 +290,58 @@ def _fetch_pending_of_type(c, cmd_type, player_id, world_id):
}
def _fetch_pending_culture(c, player_id, world_id):
"""Fetch one pending culture command from the dedicated culture_queue table.
Completely separate from the commands table — no interference with builds/recruits.
Also times out stuck 'executing' rows to 'failed' after 5 minutes.
"""
now = datetime.utcnow().isoformat()
five_min_ago = (datetime.utcnow() - timedelta(minutes=5)).isoformat()
# Expire stuck executing entries (fail, don't requeue — auto will re-fire on next state push)
c.execute('''
UPDATE culture_queue
SET status = 'failed', result_msg = 'Timeout (5 min)'
WHERE status = 'executing' AND executed_at < ? AND player_id = ?
''', (five_min_ago, player_id))
# Fetch one pending row
if world_id:
row = c.execute('''
SELECT * FROM culture_queue
WHERE status = 'pending' AND player_id = ? AND world_id = ?
ORDER BY created_at ASC
LIMIT 1
''', (player_id, world_id)).fetchone()
else:
row = c.execute('''
SELECT * FROM culture_queue
WHERE status = 'pending' AND player_id = ?
ORDER BY created_at ASC
LIMIT 1
''', (player_id,)).fetchone()
if not row:
return None
c.execute('''
UPDATE culture_queue SET status = 'executing', executed_at = ? WHERE id = ?
''', (now, row['id']))
return {
'id': row['id'],
'town_id': row['town_id'],
'type': 'culture',
'payload': {
'town_id': row['town_id'],
'celebration_type': row['celebration_type'],
'source': row['source']
}
}
def _fetch_pending_builds_all_towns(c, player_id, world_id):
"""
Fetch ONE pending 'build' command per distinct town_id.
This allows all towns to build in parallel within a single poll cycle.
@@ -298,7 +442,7 @@ def get_pending_command():
farm_cmd = _fetch_pending_of_type(c, 'farm_loot', player_id, world_id)
farm_upgrade_cmd = _fetch_pending_of_type(c, 'farm_upgrade', player_id, world_id)
research_cmd = _fetch_pending_of_type(c, 'research', player_id, world_id)
culture_cmd = _fetch_pending_of_type(c, 'culture', player_id, world_id)
culture_cmd = _fetch_pending_culture(c, player_id, world_id) # reads culture_queue
sync_req = _check_and_reset_sync(c, player_id)
# Determine player_key for world-specific settings if world_id is provided
@@ -573,9 +717,53 @@ def api_bot_logs():
return jsonify({'ok': True})
# ------------------------------------------------------------------
# GET /api/bot
# POST /api/culture/result/<id>
# Bot reports success/failure of a culture command.
# Completely separate from /api/commands/<id>/result — zero
# interference with builds, recruits, or any other command type.
# ------------------------------------------------------------------
@api.route('/api/culture/result/<int:queue_id>', methods=['POST'])
def culture_result(queue_id):
data = request.get_json(silent=True) or {}
status_in = data.get('status', 'done') # 'done' | 'failed'
msg = data.get('message', '')
now = datetime.utcnow().isoformat()
final_status = 'done' if status_in == 'done' else 'failed'
conn = get_db()
row = conn.execute(
'SELECT * FROM culture_queue WHERE id = ?', (queue_id,)
).fetchone()
if row:
conn.execute('''
UPDATE culture_queue
SET status = ?, result_msg = ?, executed_at = ?
WHERE id = ?
''', (final_status, msg, now, queue_id))
# Sync the most recent matching pending culture_log entry
log_row = conn.execute('''
SELECT id FROM culture_log
WHERE player_id = ? AND world_id = ? AND town_id = ?
AND celebration_type = ? AND status = 'pending'
ORDER BY id DESC LIMIT 1
''', (row['player_id'], row['world_id'], row['town_id'], row['celebration_type'])).fetchone()
if log_row:
log_status = 'success' if final_status == 'done' else 'failed'
conn.execute('''
UPDATE culture_log
SET status = ?, result_msg = ?, confirmed_at = ?
WHERE id = ?
''', (log_status, msg, now, log_row['id']))
conn.commit()
conn.close()
return jsonify({'ok': True})
# Serves the modular bot code concatenated into a single response
# ------------------------------------------------------------------
@api.route('/api/bot', methods=['GET'])