Mj2 : modular and prepare for client options
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
// ================================================================
|
// ================================================================
|
||||||
// 04_execute.js — All command executors
|
// 04a_execute_farm.js — Farm command executors
|
||||||
// Depends on: uw, BASE_URL, log, sleep, randInt, paused, pushState
|
// Depends on: uw, log, sleep, randInt, paused, pushState
|
||||||
// ================================================================
|
// ================================================================
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
@@ -219,138 +219,3 @@ async function executeFarmLoot(cmd) {
|
|||||||
|
|
||||||
return { ok: true, msg: `Farm done: ${claimed} claimed, ${skipped} islands skipped, ${errors} errors` };
|
return { ok: true, msg: `Farm done: ${claimed} claimed, ${skipped} islands skipped, ${errors} errors` };
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
// Execute: Build
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
async function executeBuild(cmd) {
|
|
||||||
const { town_id, payload } = cmd;
|
|
||||||
const { building_id } = payload;
|
|
||||||
|
|
||||||
const town = uw.ITowns?.getTown?.(town_id) || uw.ITowns?.towns?.[town_id];
|
|
||||||
if (!town) return { ok: false, msg: `Town ${town_id} not found in ITowns` };
|
|
||||||
|
|
||||||
const queueLen = town.buildingOrders?.()?.length ?? 0;
|
|
||||||
const hasCurator = uw.GameDataPremium?.isAdvisorActivated?.('curator');
|
|
||||||
const maxQueue = hasCurator ? 7 : 2;
|
|
||||||
if (queueLen >= maxQueue) {
|
|
||||||
return { ok: false, requeue: true, msg: `Build queue full (${queueLen}/${maxQueue})` };
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const buildData = uw.MM.getModels?.()?.BuildingBuildData?.[town_id]
|
|
||||||
?.attributes?.building_data?.[building_id];
|
|
||||||
if (buildData) {
|
|
||||||
const res = town.resources();
|
|
||||||
const { resources_for, population_for } = buildData;
|
|
||||||
if (town.getAvailablePopulation?.() < population_for) {
|
|
||||||
return { ok: false, requeue: true, msg: `Not enough population for ${building_id}` };
|
|
||||||
}
|
|
||||||
if (res.wood < resources_for.wood || res.stone < resources_for.stone || res.iron < resources_for.iron) {
|
|
||||||
return { ok: false, requeue: true, msg: `Not enough resources for ${building_id}` };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) { log(`Resource check skipped: ${e}`); }
|
|
||||||
|
|
||||||
const reactionMs = randInt(800, 2500);
|
|
||||||
log(`Waiting ${reactionMs}ms before firing buildUp (reaction time)...`);
|
|
||||||
await sleep(reactionMs);
|
|
||||||
|
|
||||||
if (paused) return { ok: false, msg: 'Aborted due to pause/captcha' };
|
|
||||||
|
|
||||||
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', {
|
|
||||||
model_url: 'BuildingOrder',
|
|
||||||
action_name: 'buildUp',
|
|
||||||
arguments: { building_id },
|
|
||||||
town_id
|
|
||||||
});
|
|
||||||
|
|
||||||
await sleep(500);
|
|
||||||
return { ok: true, msg: `buildUp ${building_id} queued` };
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
// Execute: Recruit
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
async function executeRecruit(cmd) {
|
|
||||||
const { town_id, payload } = cmd;
|
|
||||||
const { unit_id, amount } = payload;
|
|
||||||
|
|
||||||
const town = uw.ITowns?.getTown?.(town_id) || uw.ITowns?.towns?.[town_id];
|
|
||||||
if (!town) return { ok: false, msg: `Town ${town_id} not found` };
|
|
||||||
|
|
||||||
const navalUnits = [
|
|
||||||
'big_transporter', 'small_transporter', 'bireme',
|
|
||||||
'attack_ship', 'trireme', 'colonize_ship', 'sea_monster'
|
|
||||||
];
|
|
||||||
const endpoint = navalUnits.includes(unit_id) ? 'building_docks' : 'building_barracks';
|
|
||||||
|
|
||||||
const reactionMs = randInt(800, 2500);
|
|
||||||
log(`Waiting ${reactionMs}ms before firing recruit (reaction time)...`);
|
|
||||||
await sleep(reactionMs);
|
|
||||||
|
|
||||||
if (paused) return { ok: false, msg: 'Aborted due to pause/captcha' };
|
|
||||||
|
|
||||||
uw.gpAjax.ajaxPost(endpoint, 'build', {
|
|
||||||
unit_id,
|
|
||||||
amount: parseInt(amount) || 1,
|
|
||||||
town_id
|
|
||||||
});
|
|
||||||
|
|
||||||
await sleep(500);
|
|
||||||
return { ok: true, msg: `Recruit ${amount}x ${unit_id} submitted` };
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
// Execute: Market Offer
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
async function executeMarketOffer(cmd) {
|
|
||||||
const { town_id, payload } = cmd;
|
|
||||||
const { offer, offer_type, demand, demand_type, max_delivery_time, visibility } = payload;
|
|
||||||
|
|
||||||
const town = uw.ITowns?.getTown?.(town_id) || uw.ITowns?.towns?.[town_id];
|
|
||||||
if (!town) return { ok: false, msg: `Town ${town_id} not found` };
|
|
||||||
|
|
||||||
const reactionMs = randInt(800, 2500);
|
|
||||||
log(`Waiting ${reactionMs}ms before firing market offer (reaction time)...`);
|
|
||||||
await sleep(reactionMs);
|
|
||||||
|
|
||||||
if (paused) return { ok: false, msg: 'Aborted due to pause/captcha' };
|
|
||||||
|
|
||||||
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', {
|
|
||||||
model_url: 'CreateOffers/' + town_id,
|
|
||||||
action_name: 'createOffer',
|
|
||||||
captcha: null,
|
|
||||||
arguments: { offer, offer_type, demand, demand_type, max_delivery_time, visibility }
|
|
||||||
});
|
|
||||||
|
|
||||||
await sleep(500);
|
|
||||||
return { ok: true, msg: `Market offer posted: ${offer} ${offer_type} => ${demand} ${demand_type}` };
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
// Execute: Research (Academy)
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
async function executeResearch(cmd) {
|
|
||||||
const { town_id, payload } = cmd;
|
|
||||||
const { research_id } = payload;
|
|
||||||
|
|
||||||
const town = uw.ITowns?.getTown?.(town_id) || uw.ITowns?.towns?.[town_id];
|
|
||||||
if (!town) return { ok: false, msg: `Town ${town_id} not found` };
|
|
||||||
|
|
||||||
const reactionMs = randInt(800, 2500);
|
|
||||||
log(`Waiting ${reactionMs}ms before firing research (reaction time)...`);
|
|
||||||
await sleep(reactionMs);
|
|
||||||
|
|
||||||
if (paused) return { ok: false, msg: 'Aborted due to pause/captcha' };
|
|
||||||
|
|
||||||
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', {
|
|
||||||
model_url: 'ResearchOrder',
|
|
||||||
action_name: 'research',
|
|
||||||
arguments: { id: research_id },
|
|
||||||
town_id
|
|
||||||
});
|
|
||||||
|
|
||||||
await sleep(500);
|
|
||||||
return { ok: true, msg: `Research ${research_id} queued` };
|
|
||||||
}
|
|
||||||
139
bot_modules/04b_execute_admin.js
Normal file
139
bot_modules/04b_execute_admin.js
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
// ================================================================
|
||||||
|
// 04b_execute_admin.js — Admin command executors
|
||||||
|
// Depends on: uw, log, sleep, randInt, paused
|
||||||
|
// ================================================================
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------
|
||||||
|
// Execute: Build
|
||||||
|
// ----------------------------------------------------------------
|
||||||
|
async function executeBuild(cmd) {
|
||||||
|
const { town_id, payload } = cmd;
|
||||||
|
const { building_id } = payload;
|
||||||
|
|
||||||
|
const town = uw.ITowns?.getTown?.(town_id) || uw.ITowns?.towns?.[town_id];
|
||||||
|
if (!town) return { ok: false, msg: `Town ${town_id} not found in ITowns` };
|
||||||
|
|
||||||
|
const queueLen = town.buildingOrders?.()?.length ?? 0;
|
||||||
|
const hasCurator = uw.GameDataPremium?.isAdvisorActivated?.('curator');
|
||||||
|
const maxQueue = hasCurator ? 7 : 2;
|
||||||
|
if (queueLen >= maxQueue) {
|
||||||
|
return { ok: false, requeue: true, msg: `Build queue full (${queueLen}/${maxQueue})` };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const buildData = uw.MM.getModels?.()?.BuildingBuildData?.[town_id]
|
||||||
|
?.attributes?.building_data?.[building_id];
|
||||||
|
if (buildData) {
|
||||||
|
const res = town.resources();
|
||||||
|
const { resources_for, population_for } = buildData;
|
||||||
|
if (town.getAvailablePopulation?.() < population_for) {
|
||||||
|
return { ok: false, requeue: true, msg: `Not enough population for ${building_id}` };
|
||||||
|
}
|
||||||
|
if (res.wood < resources_for.wood || res.stone < resources_for.stone || res.iron < resources_for.iron) {
|
||||||
|
return { ok: false, requeue: true, msg: `Not enough resources for ${building_id}` };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) { log(`Resource check skipped: ${e}`); }
|
||||||
|
|
||||||
|
const reactionMs = randInt(800, 2500);
|
||||||
|
log(`Waiting ${reactionMs}ms before firing buildUp (reaction time)...`);
|
||||||
|
await sleep(reactionMs);
|
||||||
|
|
||||||
|
if (paused) return { ok: false, msg: 'Aborted due to pause/captcha' };
|
||||||
|
|
||||||
|
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', {
|
||||||
|
model_url: 'BuildingOrder',
|
||||||
|
action_name: 'buildUp',
|
||||||
|
arguments: { building_id },
|
||||||
|
town_id
|
||||||
|
});
|
||||||
|
|
||||||
|
await sleep(500);
|
||||||
|
return { ok: true, msg: `buildUp ${building_id} queued` };
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------
|
||||||
|
// Execute: Recruit
|
||||||
|
// ----------------------------------------------------------------
|
||||||
|
async function executeRecruit(cmd) {
|
||||||
|
const { town_id, payload } = cmd;
|
||||||
|
const { unit_id, amount } = payload;
|
||||||
|
|
||||||
|
const town = uw.ITowns?.getTown?.(town_id) || uw.ITowns?.towns?.[town_id];
|
||||||
|
if (!town) return { ok: false, msg: `Town ${town_id} not found` };
|
||||||
|
|
||||||
|
const navalUnits = [
|
||||||
|
'big_transporter', 'small_transporter', 'bireme',
|
||||||
|
'attack_ship', 'trireme', 'colonize_ship', 'sea_monster'
|
||||||
|
];
|
||||||
|
const endpoint = navalUnits.includes(unit_id) ? 'building_docks' : 'building_barracks';
|
||||||
|
|
||||||
|
const reactionMs = randInt(800, 2500);
|
||||||
|
log(`Waiting ${reactionMs}ms before firing recruit (reaction time)...`);
|
||||||
|
await sleep(reactionMs);
|
||||||
|
|
||||||
|
if (paused) return { ok: false, msg: 'Aborted due to pause/captcha' };
|
||||||
|
|
||||||
|
uw.gpAjax.ajaxPost(endpoint, 'build', {
|
||||||
|
unit_id,
|
||||||
|
amount: parseInt(amount) || 1,
|
||||||
|
town_id
|
||||||
|
});
|
||||||
|
|
||||||
|
await sleep(500);
|
||||||
|
return { ok: true, msg: `Recruit ${amount}x ${unit_id} submitted` };
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------
|
||||||
|
// Execute: Market Offer
|
||||||
|
// ----------------------------------------------------------------
|
||||||
|
async function executeMarketOffer(cmd) {
|
||||||
|
const { town_id, payload } = cmd;
|
||||||
|
const { offer, offer_type, demand, demand_type, max_delivery_time, visibility } = payload;
|
||||||
|
|
||||||
|
const town = uw.ITowns?.getTown?.(town_id) || uw.ITowns?.towns?.[town_id];
|
||||||
|
if (!town) return { ok: false, msg: `Town ${town_id} not found` };
|
||||||
|
|
||||||
|
const reactionMs = randInt(800, 2500);
|
||||||
|
log(`Waiting ${reactionMs}ms before firing market offer (reaction time)...`);
|
||||||
|
await sleep(reactionMs);
|
||||||
|
|
||||||
|
if (paused) return { ok: false, msg: 'Aborted due to pause/captcha' };
|
||||||
|
|
||||||
|
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', {
|
||||||
|
model_url: 'CreateOffers/' + town_id,
|
||||||
|
action_name: 'createOffer',
|
||||||
|
captcha: null,
|
||||||
|
arguments: { offer, offer_type, demand, demand_type, max_delivery_time, visibility }
|
||||||
|
});
|
||||||
|
|
||||||
|
await sleep(500);
|
||||||
|
return { ok: true, msg: `Market offer posted: ${offer} ${offer_type} => ${demand} ${demand_type}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------
|
||||||
|
// Execute: Research (Academy)
|
||||||
|
// ----------------------------------------------------------------
|
||||||
|
async function executeResearch(cmd) {
|
||||||
|
const { town_id, payload } = cmd;
|
||||||
|
const { research_id } = payload;
|
||||||
|
|
||||||
|
const town = uw.ITowns?.getTown?.(town_id) || uw.ITowns?.towns?.[town_id];
|
||||||
|
if (!town) return { ok: false, msg: `Town ${town_id} not found` };
|
||||||
|
|
||||||
|
const reactionMs = randInt(800, 2500);
|
||||||
|
log(`Waiting ${reactionMs}ms before firing research (reaction time)...`);
|
||||||
|
await sleep(reactionMs);
|
||||||
|
|
||||||
|
if (paused) return { ok: false, msg: 'Aborted due to pause/captcha' };
|
||||||
|
|
||||||
|
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', {
|
||||||
|
model_url: 'ResearchOrder',
|
||||||
|
action_name: 'research',
|
||||||
|
arguments: { id: research_id },
|
||||||
|
town_id
|
||||||
|
});
|
||||||
|
|
||||||
|
await sleep(500);
|
||||||
|
return { ok: true, msg: `Research ${research_id} queued` };
|
||||||
|
}
|
||||||
@@ -17,12 +17,17 @@ async function pollAndExecute() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildCmd = cmdData.build;
|
// Feature flags — default to all on if server doesn't send them (backward compatible)
|
||||||
const recruitCmd = cmdData.recruit;
|
const features = cmdData.enabled_features || ['farm', 'admin'];
|
||||||
const marketCmd = cmdData.market;
|
const farmOn = features.includes('farm');
|
||||||
const researchCmd = cmdData.research;
|
const adminOn = features.includes('admin');
|
||||||
const farmCmd = cmdData.farm;
|
|
||||||
const farmUpgradeCmd = cmdData.farm_upgrade;
|
const buildCmd = adminOn ? cmdData.build : null;
|
||||||
|
const recruitCmd = adminOn ? cmdData.recruit : null;
|
||||||
|
const marketCmd = adminOn ? cmdData.market : null;
|
||||||
|
const researchCmd = adminOn ? cmdData.research : null;
|
||||||
|
const farmCmd = farmOn ? cmdData.farm : null;
|
||||||
|
const farmUpgradeCmd = farmOn ? cmdData.farm_upgrade : null;
|
||||||
|
|
||||||
if (cmdData.sync_requested) {
|
if (cmdData.sync_requested) {
|
||||||
log('Sync requested by server — pushing state immediately');
|
log('Sync requested by server — pushing state immediately');
|
||||||
@@ -63,9 +68,9 @@ async function pollAndExecute() {
|
|||||||
await execute(farmCmd);
|
await execute(farmCmd);
|
||||||
await execute(farmUpgradeCmd);
|
await execute(farmUpgradeCmd);
|
||||||
|
|
||||||
// Auto-farm: if enabled, claim all ready farms (no explicit command needed)
|
// Auto-farm: only if farm feature is enabled
|
||||||
const farmSettings = cmdData.farm_settings || {};
|
const farmSettings = cmdData.farm_settings || {};
|
||||||
if (farmSettings.enabled && !farmCmd) {
|
if (farmOn && farmSettings.enabled && !farmCmd) {
|
||||||
const nowTs = Math.floor(Date.now() / 1000);
|
const nowTs = Math.floor(Date.now() / 1000);
|
||||||
let readyFarms = [];
|
let readyFarms = [];
|
||||||
try {
|
try {
|
||||||
|
|||||||
1
db.py
1
db.py
@@ -76,6 +76,7 @@ def init_db():
|
|||||||
'ALTER TABLE town_state ADD COLUMN sea INTEGER',
|
'ALTER TABLE town_state ADD COLUMN sea INTEGER',
|
||||||
'ALTER TABLE commands ADD COLUMN player_id TEXT',
|
'ALTER TABLE commands ADD COLUMN player_id TEXT',
|
||||||
'ALTER TABLE farm_settings ADD COLUMN bandit_camp_enabled INTEGER NOT NULL DEFAULT 0',
|
'ALTER TABLE farm_settings ADD COLUMN bandit_camp_enabled INTEGER NOT NULL DEFAULT 0',
|
||||||
|
"ALTER TABLE clan_members ADD COLUMN features TEXT NOT NULL DEFAULT 'farm,admin'",
|
||||||
]:
|
]:
|
||||||
try:
|
try:
|
||||||
c.execute(_col)
|
c.execute(_col)
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ def get_pending_command():
|
|||||||
research_cmd = _fetch_pending_of_type(c, 'research', player_id)
|
research_cmd = _fetch_pending_of_type(c, 'research', player_id)
|
||||||
sync_req = _check_and_reset_sync(c, player_id)
|
sync_req = _check_and_reset_sync(c, player_id)
|
||||||
|
|
||||||
# Also return current farm settings so TM knows loot_option
|
# Farm settings
|
||||||
farm_row = c.execute(
|
farm_row = c.execute(
|
||||||
'SELECT enabled, loot_option FROM farm_settings WHERE player_id = ?', (player_id,)
|
'SELECT enabled, loot_option FROM farm_settings WHERE player_id = ?', (player_id,)
|
||||||
).fetchone()
|
).fetchone()
|
||||||
@@ -151,6 +151,15 @@ def get_pending_command():
|
|||||||
'loot_option': farm_row['loot_option'] if farm_row else 1
|
'loot_option': farm_row['loot_option'] if farm_row else 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Feature flags — look up this player's authorized features from their clan
|
||||||
|
member_row = c.execute(
|
||||||
|
'SELECT features FROM clan_members WHERE player_id = ?', (str(player_id),)
|
||||||
|
).fetchone()
|
||||||
|
if member_row and member_row['features']:
|
||||||
|
enabled_features = [f.strip() for f in member_row['features'].split(',') if f.strip()]
|
||||||
|
else:
|
||||||
|
enabled_features = ['farm', 'admin'] # default: all on (backward-compatible)
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
@@ -162,9 +171,11 @@ def get_pending_command():
|
|||||||
'farm': farm_cmd,
|
'farm': farm_cmd,
|
||||||
'farm_upgrade': farm_upgrade_cmd,
|
'farm_upgrade': farm_upgrade_cmd,
|
||||||
'farm_settings': farm_settings,
|
'farm_settings': farm_settings,
|
||||||
|
'enabled_features': enabled_features,
|
||||||
'sync_requested': sync_req
|
'sync_requested': sync_req
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
def _check_and_reset_sync(c, player_id):
|
def _check_and_reset_sync(c, player_id):
|
||||||
key = f'sync_request_{player_id}'
|
key = f'sync_request_{player_id}'
|
||||||
row = c.execute("SELECT value FROM kv_store WHERE key = ?", (key,)).fetchone()
|
row = c.execute("SELECT value FROM kv_store WHERE key = ?", (key,)).fetchone()
|
||||||
|
|||||||
@@ -140,7 +140,9 @@ def options():
|
|||||||
'player_id': row['player_id'],
|
'player_id': row['player_id'],
|
||||||
'player_name': row['player_name'] or 'Άγνωστος',
|
'player_name': row['player_name'] or 'Άγνωστος',
|
||||||
'joined_at': row['joined_at'][:10] if row['joined_at'] else '',
|
'joined_at': row['joined_at'][:10] if row['joined_at'] else '',
|
||||||
'is_online': is_online
|
'is_online': is_online,
|
||||||
|
'feat_farm': 'farm' in (row['features'] or 'farm,admin'),
|
||||||
|
'feat_admin': 'admin' in (row['features'] or 'farm,admin'),
|
||||||
})
|
})
|
||||||
|
|
||||||
conn.close()
|
conn.close()
|
||||||
@@ -191,9 +193,6 @@ def regenerate_key():
|
|||||||
return redirect(url_for('auth.options'))
|
return redirect(url_for('auth.options'))
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
# POST /auth/clan/remove-member/<player_id>
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
@auth.route('/auth/clan/remove-member/<player_id>', methods=['POST'])
|
@auth.route('/auth/clan/remove-member/<player_id>', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def remove_member(player_id):
|
def remove_member(player_id):
|
||||||
@@ -209,3 +208,28 @@ def remove_member(player_id):
|
|||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
return redirect(url_for('auth.options'))
|
return redirect(url_for('auth.options'))
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# POST /auth/clan/update-features/<player_id>
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
@auth.route('/auth/clan/update-features/<player_id>', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def update_member_features(player_id):
|
||||||
|
farm = 'farm' if request.form.get('farm') else None
|
||||||
|
admin = 'admin' if request.form.get('admin') else None
|
||||||
|
features = ','.join(f for f in [farm, admin] if f) or ''
|
||||||
|
|
||||||
|
conn = get_db()
|
||||||
|
clan = conn.execute(
|
||||||
|
'SELECT id FROM clans WHERE owner_id = ?', (current_user.id,)
|
||||||
|
).fetchone()
|
||||||
|
if clan:
|
||||||
|
conn.execute(
|
||||||
|
'UPDATE clan_members SET features = ? WHERE clan_id = ? AND player_id = ?',
|
||||||
|
(features, clan['id'], player_id)
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
return redirect(url_for('auth.options'))
|
||||||
|
|
||||||
|
|||||||
@@ -134,6 +134,24 @@
|
|||||||
color: #8b949e; font-size: 0.9rem;
|
color: #8b949e; font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toggle-group { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
|
||||||
|
.toggle-label {
|
||||||
|
display: flex; align-items: center; gap: 6px;
|
||||||
|
font-size: 0.8rem; color: #8b949e; cursor: pointer;
|
||||||
|
background: #0d1117; border: 1px solid #30363d;
|
||||||
|
padding: 4px 10px; border-radius: 20px;
|
||||||
|
transition: border-color 0.2s, color 0.2s;
|
||||||
|
}
|
||||||
|
.toggle-label:has(input:checked) { border-color: #3fb950; color: #3fb950; }
|
||||||
|
.toggle-label input[type=checkbox] { display: none; }
|
||||||
|
.btn-apply {
|
||||||
|
background: #21262d; color: #8b949e;
|
||||||
|
border: 1px solid #30363d; border-radius: 6px;
|
||||||
|
padding: 4px 10px; font-size: 0.78rem; font-family: inherit;
|
||||||
|
cursor: pointer; transition: background 0.2s, color 0.2s;
|
||||||
|
}
|
||||||
|
.btn-apply:hover { background: #30363d; color: #e6edf3; }
|
||||||
|
|
||||||
.warn-box {
|
.warn-box {
|
||||||
background: rgba(210,153,34,0.1);
|
background: rgba(210,153,34,0.1);
|
||||||
border: 1px solid rgba(210,153,34,0.3);
|
border: 1px solid rgba(210,153,34,0.3);
|
||||||
@@ -186,6 +204,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Παίκτης</th>
|
<th>Παίκτης</th>
|
||||||
<th>Κατάσταση</th>
|
<th>Κατάσταση</th>
|
||||||
|
<th>Δυνατότητες</th>
|
||||||
<th>Προστέθηκε</th>
|
<th>Προστέθηκε</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -204,6 +223,18 @@
|
|||||||
<span class="status-offline">● Offline</span>
|
<span class="status-offline">● Offline</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<form method="POST" action="/auth/clan/update-features/{{ m.player_id }}" style="display:inline;">
|
||||||
|
<div class="toggle-group">
|
||||||
|
<label class="toggle-label">
|
||||||
|
<input type="checkbox" name="farm" onchange="this.form.submit()" {{ 'checked' if m.feat_farm }}> 🌾 Farm
|
||||||
|
</label>
|
||||||
|
<label class="toggle-label">
|
||||||
|
<input type="checkbox" name="admin" onchange="this.form.submit()" {{ 'checked' if m.feat_admin }}> 🏛 Admin
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
<td style="color:#8b949e; font-size:0.8rem;">{{ m.joined_at }}</td>
|
<td style="color:#8b949e; font-size:0.8rem;">{{ m.joined_at }}</td>
|
||||||
<td style="text-align:right;">
|
<td style="text-align:right;">
|
||||||
<form method="POST" action="/auth/clan/remove-member/{{ m.player_id }}"
|
<form method="POST" action="/auth/clan/remove-member/{{ m.player_id }}"
|
||||||
|
|||||||
Reference in New Issue
Block a user