diff --git a/config.py b/config.py index e69de29..d29155f 100644 --- a/config.py +++ b/config.py @@ -0,0 +1,44 @@ +import os +from dotenv import load_dotenv + +load_dotenv() + +BOT_TOKEN = os.getenv("BOT_TOKEN") +ADMIN_ID = os.getenv("ADMIN_ID") + +# Стартовые значения +INITIAL_BALANCE = 10_000 +INITIAL_SERVERS = 1 +INITIAL_COVERAGE_CUSTOMERS = 500 +INITIAL_MAX_CUSTOMERS = 500 +INITIAL_SPEED = 10 +INITIAL_CHANNEL_LEVEL = 1 +INITIAL_POWER_CAPACITY = 5.0 +INITIAL_NETWORK_CHANNEL_CAPACITY = 10 # Мбит/с (база) + +# Параметры инфраструктуры +SERVER_POWER = 2.0 # кВт/сервер +SERVER_COST = 3_000 +SERVER_CAPACITY_ADD = 500 +CABLE_COST = 800 +CABLE_COVERAGE_ADD = 500 +BACKUP_CHANNEL_COST = 2_500 +BACKUP_CHANNEL_POWER_ADD = 100 + +# Тарифы (в ₡/день/клиент) +TARIFFS = { + "home": 10, + "office": 30, + "gamer": 50, + "botfarm": 15 +} + +# Уровни канала +CHANNEL_LEVELS = { + 1: 10, + 2: 50, + 3: 100, + 4: 250, + 5: 500, + 6: 1000 +} \ No newline at end of file diff --git a/database.py b/database.py index bf7e439..00b95e6 100644 --- a/database.py +++ b/database.py @@ -1,8 +1,10 @@ +# database.py — работа с SQLite import sqlite3 from typing import Optional, Dict, Any DB_PATH = "data/db.sqlite3" + def init_db(): conn = sqlite3.connect(DB_PATH) cur = conn.cursor() @@ -32,14 +34,17 @@ def init_db(): conn.commit() conn.close() + def get_player(user_id: int) -> Optional[Dict[str, Any]]: conn = sqlite3.connect(DB_PATH) cur = conn.cursor() cur.execute("SELECT * FROM players WHERE user_id = ?", (user_id,)) row = cur.fetchone() conn.close() + if not row: return None + keys = [ "user_id", "balance", "servers", "cables", "coverage_customers", "max_customers", "speed", "rating", "rating_threshold", @@ -49,6 +54,7 @@ def get_player(user_id: int) -> Optional[Dict[str, Any]]: ] return dict(zip(keys, row)) + def update_player(user_id: int, updates: Dict[str, Any]): conn = sqlite3.connect(DB_PATH) cur = conn.cursor() @@ -58,15 +64,30 @@ def update_player(user_id: int, updates: Dict[str, Any]): conn.commit() conn.close() + def create_player_if_not_exists(user_id: int): player = get_player(user_id) - if not player: - conn = sqlite3.connect(DB_PATH) - cur = conn.cursor() - cur.execute(""" - INSERT INTO players (user_id, balance, servers, cables, coverage_customers, max_customers, speed, power_capacity, channel_capacity) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) - """, (user_id, INITIAL_BALANCE, INITIAL_SERVERS, 0, INITIAL_COVERAGE_CUSTOMERS, - INITIAL_MAX_CUSTOMERS, INITIAL_SPEED, INITIAL_POWER_CAPACITY, INITIAL_NETWORK_CHANNEL_CAPACITY)) - conn.commit() - conn.close() \ No newline at end of file + if player: + return + + conn = sqlite3.connect(DB_PATH) + cur = conn.cursor() + cur.execute(""" + INSERT INTO players + (user_id, balance, servers, cables, coverage_customers, max_customers, speed, + power_capacity, network_channel_level, channel_capacity) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, ( + user_id, + 10_000, # balance + 1, # servers + 0, # cables + 500, # coverage_customers + 500, # max_customers + 10, # speed + 5.0, # power_capacity + 1, # network_channel_level + 10 # channel_capacity + )) + conn.commit() + conn.close() \ No newline at end of file diff --git a/handlers/build.py b/handlers/build.py index e69de29..908bbb6 100644 --- a/handlers/build.py +++ b/handlers/build.py @@ -0,0 +1,74 @@ +from aiogram import types, Router +from database import get_player, update_player +from economy import sync_power +from config import CABLE_COST, SERVER_COST, BACKUP_CHANNEL_COST, BACKUP_CHANNEL_POWER_ADD + +router = Router() + +@router.callback_query(lambda c: c.data == "build_cable") +async def build_cable(callback: types.CallbackQuery): + user_id = callback.from_user.id + player = get_player(user_id) + if player["balance"] < CABLE_COST: + await callback.answer("❌ Не хватает средств!", show_alert=True) + return + + new_power = player["power_capacity"] + 2 + update_player(user_id, { + "balance": player["balance"] - CABLE_COST, + "power_capacity": new_power, + "cables": player["cables"] + 1 + }) + await callback.message.edit_text( + f"✅ Кабель проложен!\n" + f"⚡ Лимит питания: {new_power} кВт\n" + f"💰 Остаток: {player['balance'] - CABLE_COST} ₡" + ) + await callback.answer("Кабель установлен 🛠️") + +@router.callback_query(lambda c: c.data == "buy_server") +async def buy_server(callback: types.CallbackQuery): + user_id = callback.from_user.id + player = get_player(user_id) + if player["balance"] < SERVER_COST: + await callback.answer("❌ Не хватает средств!", show_alert=True) + return + + new_servers = player["servers"] + 1 + new_power = new_servers * 2.0 # SERVER_POWER = 2.0 из config.py + + update_player(user_id, { + "balance": player["balance"] - SERVER_COST, + "servers": new_servers, + "max_customers": player["max_customers"] + 500, + "power_capacity": new_power, # обновляем лимит + "power_usage": player["servers"] * 2.0 # обновляем нагрузку (была на старом количестве) + }) + + await callback.message.edit_text( + f"✅ Сервер установлен!\n" + f"🖥 Теперь: {new_servers} шт.\n" + f"⚡ Потребление: {new_power:.1f}/{new_power:.1f} кВт" + ) + await callback.answer("Сервер включён!") + +@router.callback_query(lambda c: c.data == "buy_backup_channel") +async def buy_backup_channel(callback: types.CallbackQuery): + user_id = callback.from_user.id + player = get_player(user_id) + if player["balance"] < BACKUP_CHANNEL_COST: + await callback.answer("❌ Не хватает средств!", show_alert=True) + return + + new_power = player["power_capacity"] + BACKUP_CHANNEL_POWER_ADD + update_player(user_id, { + "balance": player["balance"] - BACKUP_CHANNEL_COST, + "power_capacity": new_power, + "backup_channel_active": 0 + }) + await callback.message.edit_text( + f"⚡ Резервный канал построен! ✅\n" + f"Лимит питания: {new_power} кВт\n" + f"💰 Остаток: {player['balance'] - BACKUP_CHANNEL_COST} ₡" + ) + await callback.answer("Резерв готов!") \ No newline at end of file diff --git a/handlers/channel.py b/handlers/channel.py index 8ce500a..15620bb 100644 --- a/handlers/channel.py +++ b/handlers/channel.py @@ -1,7 +1,10 @@ -from aiogram import types +from aiogram import types, Router from database import get_player, update_player from economy import calculate_channel_cost +router = Router() # ← ВАЖНО! + +@router.callback_query(lambda c: c.data == "upgrade_channel") async def upgrade_channel(callback: types.CallbackQuery): user_id = callback.from_user.id player = get_player(user_id) @@ -17,7 +20,7 @@ async def upgrade_channel(callback: types.CallbackQuery): return next_level = current_level + 1 - next_capacity = int(player["channel_capacity"] * 2.5) # +150% каждый уровень + next_capacity = int(player["channel_capacity"] * 2.5) upgrade_hours = 4 * current_level update_player(user_id, { @@ -50,7 +53,11 @@ async def speedup_channel(callback: types.CallbackQuery): await callback.answer("❌ Нет средств на ускорение", show_alert=True) return - new_end = max(0, int(__import__("time").time()) + (player["next_build_end_ts"] - int(__import__("time").time())) // 2) + new_end = max( + 0, + int(__import__("time").time()) + + (player["next_build_end_ts"] - int(__import__("time").time())) // 2 + ) update_player(user_id, { "balance": player["balance"] - 500, diff --git a/handlers/map.py b/handlers/map.py index a5109a0..35604b2 100644 --- a/handlers/map.py +++ b/handlers/map.py @@ -1,12 +1,15 @@ -from aiogram import types +# handlers/map.py +from aiogram import types, Router from database import get_player +router = Router() # ← ВАЖНО! Регистрируем роутер + @router.callback_query(lambda c: c.data == "map") async def show_map(callback: types.CallbackQuery): player = get_player(callback.from_user.id) districts = [ ("🏠 Гараж (ваша база)", "✅ Подключён"), - ("🏢 Центральный район", "🟡 В процессе (15 мин)" if player["next_build_end_ts"] > 0 else "🔒 Недоступен"), + ("🏢 Центральный район", "🟡 В процессе" if player.get("next_build_end_ts", 0) > 0 else "🔒 Недоступен"), ("🎮 Западный квартал", "🔒 Недоступен"), ] diff --git a/handlers/start.py b/handlers/start.py index 031652e..75f463b 100644 --- a/handlers/start.py +++ b/handlers/start.py @@ -1,13 +1,10 @@ from aiogram import types, Router from aiogram.filters import CommandStart -from aiogram.types import WebAppInfo - from database import create_player_if_not_exists, get_player from economy import ( - calculate_daily_offline_earnings, calculate_effective_bandwidth, - calculate_rating, calculate_daily_income + calculate_effective_bandwidth, calculate_rating, calculate_client_retention, + calculate_daily_income, calculate_daily_offline_earnings ) -from keyboards import get_main_menu router = Router() @@ -23,7 +20,6 @@ async def cmd_start(message: types.Message): player["balance"] += offline_earn player["total_income"] += offline_earn player["last_login_ts"] = int(__import__("time").time()) - get_player.cache_clear() update_player(user_id, { "balance": player["balance"], "total_income": player["total_income"], @@ -31,6 +27,8 @@ async def cmd_start(message: types.Message): }) effective_bw = calculate_effective_bandwidth(player) + lost, churn_msg = calculate_client_retention(player) + daily_income = calculate_daily_income(player) status = "" if player["outage_end_ts"] > 0: @@ -44,13 +42,14 @@ async def cmd_start(message: types.Message): f"⚡ *Канал:* {player['channel_capacity']} → {effective_bw} Мбит/с\n" f"🔋 *Сеть:* {player['power_usage']:.1f}/{player['power_capacity']} кВт\n" f"🖥 *Серверы:* {player['servers']} (ёмкость: {player['max_customers']} клиентов)\n" - f"📡 *Клиенты:* {player['coverage_customers']} (отток: {calculate_client_retention(player)[0]}/сутки)\n" + f"📡 *Клиенты:* {player['coverage_customers']} (отток: {lost}/сутки)\n" f"💰 *Бюджет:* {player['balance']:,.0f} ₡\n" - f"📊 *Доход/день:* {calculate_daily_income(player):,.0f} ₡\n" + f"📊 *Доход/день:* {daily_income:,.0f} ₡\n" f"🌟 *Рейтинг:* {calculate_rating(player)}\n" - f"{status}" + f"{churn_msg}\n{status}" ) + from keyboards import get_main_menu await message.answer( text, reply_markup=get_main_menu(player), diff --git a/handlers/stats.py b/handlers/stats.py index df2d5aa..ee8b8b0 100644 --- a/handlers/stats.py +++ b/handlers/stats.py @@ -1,18 +1,26 @@ -from aiogram import types +# handlers/stats.py +from aiogram import types, Router from database import get_player from economy import calculate_rating, calculate_effective_bandwidth +router = Router() + @router.callback_query(lambda c: c.data == "stats") async def stats(callback: types.CallbackQuery): player = get_player(callback.from_user.id) + if not player: + await callback.answer("❌ Игрок не найден", show_alert=True) + return + bw = calculate_effective_bandwidth(player) + daily = calculate_daily_income(player) text = ( f"📊 *Ваша статистика*\n\n" f"🔹 Канал: {player['network_channel_level']} → {bw} Мбит/с\n" f"🔹 Серверы: {player['servers']} шт. (ёмкость: {player['max_customers']})\n" f"🔹 Клиенты: {player['coverage_customers']} / {player['max_customers']}\n" - f"🔹 Доход/день: {calculate_daily_income(player):,.0f} ₡\n" + f"🔹 Доход/день: {daily:,.0f} ₡\n" f"🔹 Общий доход: {player['total_income']:,.0f} ₡\n" f"🔹 Рейтинг: {calculate_rating(player)} 🌟" ) diff --git a/main.py b/main.py index e1dc239..1e0d38a 100644 --- a/main.py +++ b/main.py @@ -1,16 +1,12 @@ -from aiogram import Bot, Dispatcher, types +from aiogram import Bot, Dispatcher from aiogram.enums import ParseMode -from aiogram.filters import Command from aiogram import Router import asyncio import logging -import time from config import BOT_TOKEN -from database import init_db, get_player, update_player -from economy import calculate_effective_bandwidth, handle_outage, sync_power +from database import init_db from handlers.start import router as start_router -from handlers.economy import router as economy_router from handlers.build import router as build_router from handlers.channel import router as channel_router from handlers.stats import router as stats_router @@ -19,53 +15,37 @@ from handlers.events import router as events_router dp = Dispatcher() dp.include_router(start_router) -dp.include_router(economy_router) dp.include_router(build_router) dp.include_router(channel_router) dp.include_router(stats_router) dp.include_router(map_router) dp.include_router(events_router) -# Периодическая проверка состояний (каждые 5 минут) -@dp.message(Command("check")) -async def check_state(message: types.Message): - user_id = message.from_user.id - player = get_player(user_id) - if not player: - await message.answer("❌ Игрок не найден") - return +@dp.message() +async def echo(message: types.Message): + if message.text == "/check": + from database import get_player + from economy import calculate_effective_bandwidth, handle_outage, sync_power + player = get_player(message.from_user.id) + if not player: + await message.answer("❌ Игрок не найден") + return - # Оффлайн доход + апгрейды - player = handle_outage(player) - player = sync_power(player) - effective_bw = calculate_effective_bandwidth(player) + player = handle_outage(player) + player = sync_power(player) - # Обновление - update_player(user_id, { - "power_usage": player["power_usage"], - "coverage_customers": player["coverage_customers"], - "max_customers": player["max_customers"] - }) - - status = "" - if player["outage_end_ts"] > 0: - status = f"\n⚠️ АВАРИЯ: Отключение до {player['outage_end_ts'] - int(__import__('time').time()) // 3600}ч" - - await message.answer( - f"⚡ Состояние проверено!\n" - f"Канал: {player['channel_capacity']} → {effective_bw} Мбит/с\n" - f"Лимит: {player['power_usage']:.1f}/{player['power_capacity']} кВт\n" - f"Клиенты: {player['coverage_customers']} / {player['max_customers']}\n" - f"{status}", - parse_mode="Markdown" - ) + await message.answer( + f"⚡ Состояние проверено!\n" + f"Канал: {player['channel_capacity']} → {calculate_effective_bandwidth(player)} Мбит/с\n" + f"Лимит: {player['power_usage']:.1f}/{player['power_capacity']} кВт" + ) async def main(): init_db() + bot = Bot(token=BOT_TOKEN, parse_mode=ParseMode.HTML) print("🤖 ISP Tycoon bot started...") await dp.start_polling(bot) if __name__ == "__main__": logging.basicConfig(level=logging.INFO) - bot = Bot(token=BOT_TOKEN, parse_mode=ParseMode.HTML) asyncio.run(main()) \ No newline at end of file