agoara update

This commit is contained in:
2026-05-06 23:41:52 +03:00
parent 7e98f1292e
commit b824144a6a
9 changed files with 1283 additions and 6 deletions

View File

@@ -97,6 +97,11 @@ def player_farm(player_id, world_id):
def player_tracker(player_id, world_id):
return render_template('tracker.html', player_id=player_id, world_id=world_id)
@dashboard.route('/player/<player_id>/<world_id>/agora')
@login_required
def player_agora(player_id, world_id):
return render_template('agora.html', player_id=player_id, world_id=world_id)
# ------------------------------------------------------------------
# GET /dashboard/farm-settings — returns current farm config
# POST /dashboard/farm-settings — updates farm config
@@ -464,8 +469,9 @@ def create_command():
return jsonify({'error': f'missing field: {field}'}), 400
cmd_type = data['type']
if cmd_type not in ('build', 'recruit', 'market_offer', 'farm_loot', 'farm_upgrade', 'research'):
return jsonify({'error': 'type must be build, recruit, market_offer, farm_loot, farm_upgrade, or research'}), 400
if cmd_type not in ('build', 'recruit', 'market_offer', 'farm_loot', 'farm_upgrade', 'research', 'culture'):
return jsonify({'error': 'type must be build, recruit, market_offer, farm_loot, farm_upgrade, research, or culture'}), 400
# Reject if the Tampermonkey client is offline (no state push in last 150 s)
conn = get_db()
@@ -682,3 +688,269 @@ def bootcamp_attack_now():
conn.commit()
conn.close()
return jsonify({'ok': True})
# ------------------------------------------------------------------
# GET /dashboard/agora
# Returns per-town celebration eligibility for the Αγορά tab.
# For each town owned by player_id + world_id we calculate:
# • party — has 15k wood / 18k stone / 15k iron? on cooldown?
# • triumph — has 300+ battle points? on cooldown?
# ------------------------------------------------------------------
PARTY_COST = {'wood': 15000, 'stone': 18000, 'iron': 15000}
TRIUMPH_COST = 300 # battle points
@dashboard.route('/dashboard/agora', methods=['GET'])
@login_required
def get_agora():
player_id = request.args.get('player_id')
world_id = request.args.get('world_id', '')
if not player_id:
return jsonify({'error': 'missing player_id'}), 400
conn = get_db()
now_ts = int(datetime.utcnow().timestamp())
# ── Town snapshots ───────────────────────────────────────────────
town_rows = conn.execute('''
SELECT town_id, town_name, data
FROM town_state
WHERE player_id = ? AND world_id = ?
ORDER BY town_name ASC
''', (player_id, world_id)).fetchall()
# ── Active celebrations (cooldowns) ──────────────────────────────
cel_rows = conn.execute('''
SELECT town_id, celebration_type, finished_at
FROM celebrations
WHERE player_id = ? AND world_id = ? AND finished_at > ?
''', (player_id, world_id, now_ts)).fetchall()
# Build a lookup: (town_id, cel_type) → finished_at
cooldowns = {}
for r in cel_rows:
cooldowns[(r['town_id'], r['celebration_type'])] = r['finished_at']
conn.close()
# ── Per-town eligibility ─────────────────────────────────────────
towns_out = []
for row in town_rows:
d = json.loads(row['data'])
wood = d.get('wood', 0)
stone = d.get('stone', 0)
iron = d.get('iron', 0)
bp = d.get('battle_points', {}).get('available', 0)
academy = d.get('buildings', {}).get('academy', 0)
tid = row['town_id']
# ── Party (Γιορτή πόλης) ─────────────────────────────────────
party_cd = cooldowns.get((tid, 'party'), 0)
if party_cd:
party_status = 'cooldown'
party_reason = f'Ενεργή — λήγει σε {_fmt_seconds(party_cd - now_ts)}'
party_ok = False
elif academy < 30:
party_status = 'unavailable'
party_reason = f'Ακαδημία {academy}/30'
party_ok = False
elif wood < PARTY_COST['wood'] or stone < PARTY_COST['stone'] or iron < PARTY_COST['iron']:
missing = []
if wood < PARTY_COST['wood']: missing.append(f'ξύλο {wood:,}/{PARTY_COST["wood"]:,}')
if stone < PARTY_COST['stone']: missing.append(f'πέτρα {stone:,}/{PARTY_COST["stone"]:,}')
if iron < PARTY_COST['iron']: missing.append(f'σίδερο {iron:,}/{PARTY_COST["iron"]:,}')
party_status = 'unavailable'
party_reason = 'Ανεπαρκείς πόροι: ' + ', '.join(missing)
party_ok = False
else:
party_status = 'available'
party_reason = ''
party_ok = True
# ── Triumph (Παρέλαση θριάμβου) ──────────────────────────────
triumph_cd = cooldowns.get((tid, 'triumph'), 0)
if triumph_cd:
triumph_status = 'cooldown'
triumph_reason = f'Ενεργή — λήγει σε {_fmt_seconds(triumph_cd - now_ts)}'
triumph_ok = False
elif bp < TRIUMPH_COST:
triumph_status = 'unavailable'
triumph_reason = f'Ανεπαρκείς πόντοι μάχης: {bp}/{TRIUMPH_COST}'
triumph_ok = False
else:
triumph_status = 'available'
triumph_reason = ''
triumph_ok = True
towns_out.append({
'town_id': tid,
'town_name': row['town_name'],
'resources': {'wood': wood, 'stone': stone, 'iron': iron},
'battle_points': bp,
'academy': academy,
'party': {
'status': party_status,
'reason': party_reason,
'available': party_ok,
'cooldown_until': party_cd or None,
},
'triumph': {
'status': triumph_status,
'reason': triumph_reason,
'available': triumph_ok,
'cooldown_until': triumph_cd or None,
},
})
return jsonify({'towns': towns_out, 'costs': {'party': PARTY_COST, 'triumph': TRIUMPH_COST}})
def _fmt_seconds(secs):
"""Format a duration in seconds to a human-readable Greek string."""
secs = max(0, int(secs))
h, rem = divmod(secs, 3600)
m, s = divmod(rem, 60)
if h:
return f'{h}ω {m}λ'
if m:
return f'{m}λ {s}δ'
return f'{s}δ'
# ------------------------------------------------------------------
# POST /dashboard/culture-command
# Dashboard fires when the user confirms a celebration.
# Body: { player_id, world_id, town_id, town_name, celebration_type }
# • Validates client is online.
# • Validates eligibility (resources / battle_points / cooldown).
# • Inserts a 'culture' command into the commands queue.
# • Inserts a 'pending' row into culture_log.
# ------------------------------------------------------------------
@dashboard.route('/dashboard/culture-command', methods=['POST'])
@login_required
def culture_command():
data = request.get_json(silent=True) or {}
player_id = data.get('player_id')
world_id = data.get('world_id', '')
town_id = str(data.get('town_id', ''))
town_name = data.get('town_name', '')
cel_type = data.get('celebration_type', '') # 'party' | 'triumph'
if not all([player_id, town_id, cel_type]):
return jsonify({'error': 'missing player_id, town_id or celebration_type'}), 400
if cel_type not in ('party', 'triumph'):
return jsonify({'error': 'celebration_type must be party or triumph'}), 400
conn = get_db()
# ── Client online check ──────────────────────────────────────────
row = conn.execute(
'SELECT MAX(updated_at) AS last_seen FROM town_state WHERE player_id = ?', (player_id,)
).fetchone()
last_seen = row['last_seen'] if row else None
client_online = False
if last_seen:
try:
dt = datetime.fromisoformat(last_seen)
client_online = (datetime.utcnow() - dt).total_seconds() <= 150
except Exception:
pass
if not client_online:
conn.close()
return jsonify({'error': 'client_offline',
'message': 'Το script είναι offline — δεν μπορείτε να στείλετε εντολές.'}), 503
# ── Re-validate eligibility server-side ─────────────────────────
town_row = conn.execute(
'SELECT data FROM town_state WHERE town_id = ? AND player_id = ?', (town_id, player_id)
).fetchone()
if not town_row:
conn.close()
return jsonify({'error': 'town not found'}), 404
d = json.loads(town_row['data'])
now_t = int(datetime.utcnow().timestamp())
# Cooldown check
cel_row = conn.execute('''
SELECT finished_at FROM celebrations
WHERE player_id = ? AND world_id = ? AND town_id = ? AND celebration_type = ? AND finished_at > ?
''', (player_id, world_id, town_id, cel_type, now_t)).fetchone()
if cel_row:
conn.close()
return jsonify({'error': 'on_cooldown',
'message': f'Η εορτή τρέχει ήδη — λήγει σε {_fmt_seconds(cel_row["finished_at"] - now_t)}'}), 409
# Cost determination & resource check
if cel_type == 'party':
cost_wood = PARTY_COST['wood']
cost_stone = PARTY_COST['stone']
cost_iron = PARTY_COST['iron']
cost_bp = 0
if (d.get('wood', 0) < cost_wood or d.get('stone', 0) < cost_stone
or d.get('iron', 0) < cost_iron):
conn.close()
return jsonify({'error': 'insufficient_resources',
'message': 'Ανεπαρκείς πόροι για τη Γιορτή πόλης.'}), 409
else: # triumph
cost_wood = cost_stone = cost_iron = 0
cost_bp = TRIUMPH_COST
available_bp = d.get('battle_points', {}).get('available', 0)
if available_bp < TRIUMPH_COST:
conn.close()
return jsonify({'error': 'insufficient_battle_points',
'message': f'Ανεπαρκείς πόντοι μάχης ({available_bp}/{TRIUMPH_COST}).'}), 409
# ── Insert command into bot queue ────────────────────────────────
now_iso = datetime.utcnow().isoformat()
c = conn.cursor()
c.execute('''
INSERT INTO commands (town_id, town_name, type, payload, status, created_at, updated_at, player_id)
VALUES (?, ?, 'culture', ?, 'pending', ?, ?, ?)
''', (
town_id, town_name,
json.dumps({'celebration_type': cel_type, 'town_id': int(town_id)}),
now_iso, now_iso, str(player_id)
))
cmd_id = c.lastrowid
# ── Append culture_log row (status=pending until bot confirms) ───
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, fired_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', ?)
''', (str(player_id), world_id, town_id, town_name, cel_type,
cost_wood, cost_stone, cost_iron, cost_bp, now_iso))
log_id = c.lastrowid
conn.commit()
conn.close()
return jsonify({'ok': True, 'cmd_id': cmd_id, 'log_id': log_id})
# ------------------------------------------------------------------
# GET /dashboard/culture-log
# Returns the last 50 Αγορά log entries for a player + world.
# ------------------------------------------------------------------
@dashboard.route('/dashboard/culture-log', methods=['GET'])
@login_required
def get_culture_log():
player_id = request.args.get('player_id')
world_id = request.args.get('world_id', '')
if not player_id:
return jsonify({'error': 'missing player_id'}), 400
conn = get_db()
rows = conn.execute('''
SELECT id, town_id, town_name, celebration_type,
cost_wood, cost_stone, cost_iron, cost_battle_pts,
status, result_msg, fired_at, confirmed_at
FROM culture_log
WHERE player_id = ? AND world_id = ?
ORDER BY id DESC
LIMIT 50
''', (player_id, world_id)).fetchall()
conn.close()
return jsonify([dict(r) for r in rows])