✨ MVP ISP Tycoon: от гаража до мегаполиса
This commit is contained in:
89
.gitignore
vendored
Normal file
89
.gitignore
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Database
|
||||
data/
|
||||
*.db
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
|
||||
# IDE & editors
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.DS_Store
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
logs/
|
||||
|
||||
# Temporary files
|
||||
tmp/
|
||||
temp/
|
||||
*.tmp
|
||||
|
||||
# Build artifacts
|
||||
*.egg-info/
|
||||
.eggs/
|
||||
|
||||
# OS-specific
|
||||
Thumbs.db
|
||||
.DS_Store
|
||||
|
||||
# Sensitive config
|
||||
config.json
|
||||
config.yml
|
||||
credentials.json
|
||||
keys.json
|
||||
bot_secrets.json
|
||||
|
||||
# Mini App (если будете хостить)
|
||||
client-side/
|
||||
frontend/
|
||||
build/
|
||||
dist/
|
||||
|
||||
# Docker (если добавите позже)
|
||||
Dockerfile
|
||||
docker-compose.yml
|
||||
|
||||
# Testing
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
|
||||
# Documentation (опционально)
|
||||
docs/_build/
|
||||
docs/_static/
|
||||
docs/_templates/
|
||||
72
database.py
Normal file
72
database.py
Normal file
@@ -0,0 +1,72 @@
|
||||
import sqlite3
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
DB_PATH = "data/db.sqlite3"
|
||||
|
||||
def init_db():
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
cur = conn.cursor()
|
||||
cur.execute("""
|
||||
CREATE TABLE IF NOT EXISTS players (
|
||||
user_id INTEGER PRIMARY KEY,
|
||||
balance REAL DEFAULT 10000,
|
||||
servers INTEGER DEFAULT 1,
|
||||
cables INTEGER DEFAULT 0,
|
||||
coverage_customers INTEGER DEFAULT 500,
|
||||
max_customers INTEGER DEFAULT 500,
|
||||
speed INTEGER DEFAULT 10,
|
||||
rating INTEGER DEFAULT 0,
|
||||
rating_threshold INTEGER DEFAULT 100,
|
||||
next_build_end_ts INTEGER DEFAULT 0,
|
||||
last_login_ts INTEGER DEFAULT 0,
|
||||
total_income REAL DEFAULT 0,
|
||||
infrastructure_level INTEGER DEFAULT 1,
|
||||
power_capacity REAL DEFAULT 5.0,
|
||||
power_usage REAL DEFAULT 0.0,
|
||||
outage_end_ts INTEGER DEFAULT 0,
|
||||
backup_channel_active INTEGER DEFAULT 0,
|
||||
network_channel_level INTEGER DEFAULT 1,
|
||||
channel_capacity INTEGER DEFAULT 10
|
||||
)
|
||||
""")
|
||||
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",
|
||||
"next_build_end_ts", "last_login_ts", "total_income", "infrastructure_level",
|
||||
"power_capacity", "power_usage", "outage_end_ts", "backup_channel_active",
|
||||
"network_channel_level", "channel_capacity"
|
||||
]
|
||||
return dict(zip(keys, row))
|
||||
|
||||
def update_player(user_id: int, updates: Dict[str, Any]):
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
cur = conn.cursor()
|
||||
fields = ", ".join(f"{k} = ?" for k in updates.keys())
|
||||
values = list(updates.values()) + [user_id]
|
||||
cur.execute(f"UPDATE players SET {fields} WHERE user_id = ?", values)
|
||||
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()
|
||||
101
economy.py
Normal file
101
economy.py
Normal file
@@ -0,0 +1,101 @@
|
||||
import time
|
||||
from config import *
|
||||
|
||||
def calculate_effective_bandwidth(player: dict) -> int:
|
||||
base = player["channel_capacity"]
|
||||
server_boost = min(0.5, player["servers"] * 0.1)
|
||||
cable_boost = player["cables"] * 0.2
|
||||
return int(base * (1 + server_boost + min(1.0, cable_boost)))
|
||||
|
||||
def calculate_rating(player: dict) -> float:
|
||||
monthly_income = player["coverage_customers"] * 15 * 30
|
||||
income_score = 10 * (monthly_income / 100_000) ** 0.5
|
||||
churn_ratio = (player["max_customers"] - player["coverage_customers"]) / max(player["max_customers"], 1)
|
||||
stability_score = max(0, 20 - (churn_ratio * 100))
|
||||
coverage_score = 20 * min(1, player["max_customers"] / 100_000)
|
||||
return round(income_score * 0.4 + stability_score * 0.3 + coverage_score * 0.3, 2)
|
||||
|
||||
def calculate_client_retention(player: dict) -> tuple[int, str]:
|
||||
effective_bw = calculate_effective_bandwidth(player)
|
||||
required_bw_per_client = 2.0
|
||||
max_supported = effective_bw / required_bw_per_client
|
||||
|
||||
if player["coverage_customers"] > max_supported:
|
||||
churn_ratio = min(0.9, (player["coverage_customers"] - max_supported) / max_supported)
|
||||
lost = int(player["coverage_customers"] * churn_ratio)
|
||||
return lost, f"⚠️ Канала не хватает! Отток: {lost} клиентов/сутки"
|
||||
|
||||
if player["network_channel_level"] >= 3 and effective_bw >= 100:
|
||||
return 0, "✅ Высокий канал: отток снижен на 15%"
|
||||
return 0, "🌐 Связь стабильна."
|
||||
|
||||
def calculate_daily_income(player: dict) -> float:
|
||||
supported_tiers = ["home"]
|
||||
if player["network_channel_level"] >= 2:
|
||||
supported_tiers.append("office")
|
||||
if player["network_channel_level"] >= 3:
|
||||
supported_tiers.append("gamer")
|
||||
if player["network_channel_level"] >= 4:
|
||||
supported_tiers.append("botfarm")
|
||||
|
||||
base_rate = sum(TARIFFS[t] for t in supported_tiers) / len(supported_tiers)
|
||||
effective_bw = calculate_effective_bandwidth(player)
|
||||
ratio = effective_bw / max(player["coverage_customers"], 1)
|
||||
multiplier = 1.0 + 0.2 * min(1, ratio / 5)
|
||||
|
||||
return round(player["coverage_customers"] * base_rate * multiplier, 2)
|
||||
|
||||
def calculate_channel_cost(level: int) -> int:
|
||||
base = 2000
|
||||
return int(base * (1.8 ** (level - 1)))
|
||||
|
||||
def calculate_daily_offline_earnings(player: dict) -> float:
|
||||
now = int(time.time())
|
||||
if player["last_login_ts"] >= now:
|
||||
return 0.0
|
||||
offline_hours = max((now - player["last_login_ts"]) / 3600, 0)
|
||||
if offline_hours > 48:
|
||||
offline_hours = 48
|
||||
|
||||
effective_bw = calculate_effective_bandwidth(player)
|
||||
server_capacity = player["servers"] * 500
|
||||
stability = min(1.0, server_capacity / max(player["coverage_customers"], 1))
|
||||
tariff = 15 # ₡/час/клиент (усреднённый)
|
||||
|
||||
income = player["coverage_customers"] * tariff * stability * offline_hours
|
||||
return round(income, 2)
|
||||
|
||||
def handle_outage(player: dict) -> dict:
|
||||
if player["outage_end_ts"] > 0:
|
||||
now = int(time.time())
|
||||
if now > player["outage_end_ts"]:
|
||||
# Восстановление
|
||||
return {**player, "outage_end_ts": 0, "backup_channel_active": False}
|
||||
else:
|
||||
if player["backup_channel_active"]:
|
||||
return {**player, "speed": max(5, player["speed"] * 0.75)}
|
||||
else:
|
||||
churn = int(player["coverage_customers"] * 0.35)
|
||||
return {
|
||||
**player,
|
||||
"coverage_customers": max(0, player["coverage_customers"] - churn),
|
||||
"max_customers": max(0, player["max_customers"] - churn)
|
||||
}
|
||||
return player
|
||||
|
||||
def check_power_overload(player: dict) -> tuple[bool, str]:
|
||||
power_usage = player["servers"] * SERVER_POWER
|
||||
if power_usage > player["power_capacity"] * 1.2:
|
||||
return True, "⚠️ КРИТИЧЕСКАЯ ПЕРЕНАГРУЗКА! Серверы отключаются..."
|
||||
elif power_usage > player["power_capacity"]:
|
||||
return True, "⚠️ Перегрузка! Некоторые серверы работают с лагами."
|
||||
return False, ""
|
||||
|
||||
def sync_power(player: dict) -> dict:
|
||||
power_usage = player["servers"] * SERVER_POWER
|
||||
if power_usage > player["power_capacity"] * 1.2:
|
||||
disabled = max(1, int(player["servers"] * 0.1))
|
||||
new_servers = max(0, player["servers"] - disabled)
|
||||
new_max = max(0, player["max_customers"] - disabled * 100)
|
||||
return {**player, "servers": new_servers, "max_customers": new_max}
|
||||
return {**player, "power_usage": power_usage}
|
||||
0
handlers/__init__.py
Normal file
0
handlers/__init__.py
Normal file
0
handlers/build.py
Normal file
0
handlers/build.py
Normal file
63
handlers/channel.py
Normal file
63
handlers/channel.py
Normal file
@@ -0,0 +1,63 @@
|
||||
from aiogram import types
|
||||
from database import get_player, update_player
|
||||
from economy import calculate_channel_cost
|
||||
|
||||
async def upgrade_channel(callback: types.CallbackQuery):
|
||||
user_id = callback.from_user.id
|
||||
player = get_player(user_id)
|
||||
|
||||
current_level = player["network_channel_level"]
|
||||
if current_level >= 6:
|
||||
await callback.answer("⚠️ Максимальный уровень канала достигнут! 🌍", show_alert=True)
|
||||
return
|
||||
|
||||
cost = calculate_channel_cost(current_level)
|
||||
if player["balance"] < cost:
|
||||
await callback.answer(f"❌ Не хватает {cost - player['balance']} ₡", show_alert=True)
|
||||
return
|
||||
|
||||
next_level = current_level + 1
|
||||
next_capacity = int(player["channel_capacity"] * 2.5) # +150% каждый уровень
|
||||
upgrade_hours = 4 * current_level
|
||||
|
||||
update_player(user_id, {
|
||||
"balance": player["balance"] - cost,
|
||||
"next_build_end_ts": int(__import__("time").time()) + upgrade_hours * 3600,
|
||||
"infrastructure_level": player["infrastructure_level"] + 0.4
|
||||
})
|
||||
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
|
||||
[types.InlineKeyboardButton(
|
||||
text="🚀 Ускорить (x2 за 500 ₡)",
|
||||
callback_data=f"speedup_channel_{current_level}"
|
||||
)]
|
||||
])
|
||||
|
||||
await callback.message.edit_text(
|
||||
f"🚀 Прокачка канала до уровня {next_level} (до {next_capacity} Мбит/с)...\n"
|
||||
f"⏳ Ожидание: {upgrade_hours} ч\n"
|
||||
f"💰 Остаток: {player['balance'] - cost} ₡",
|
||||
reply_markup=keyboard
|
||||
)
|
||||
await callback.answer("Процесс запущен 🛠️")
|
||||
|
||||
@router.callback_query(lambda c: c.data.startswith("speedup_channel_"))
|
||||
async def speedup_channel(callback: types.CallbackQuery):
|
||||
user_id = callback.from_user.id
|
||||
player = get_player(user_id)
|
||||
|
||||
if player["balance"] < 500:
|
||||
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)
|
||||
|
||||
update_player(user_id, {
|
||||
"balance": player["balance"] - 500,
|
||||
"next_build_end_ts": new_end
|
||||
})
|
||||
|
||||
await callback.message.edit_text(
|
||||
f"⚡ Ускорено! Канал будет готов через ~{(new_end - __import__('time').time()) / 3600:.1f} ч"
|
||||
)
|
||||
await callback.answer("🚀 Апгрейд ускорен!")
|
||||
43
handlers/events.py
Normal file
43
handlers/events.py
Normal file
@@ -0,0 +1,43 @@
|
||||
from aiogram import types
|
||||
import random
|
||||
import time
|
||||
from database import get_player, update_player
|
||||
|
||||
async def activate_backup(callback: types.CallbackQuery):
|
||||
user_id = callback.from_user.id
|
||||
player = get_player(user_id)
|
||||
|
||||
if player["backup_channel_active"]:
|
||||
await callback.answer("✅ Резерв уже активен!", show_alert=True)
|
||||
return
|
||||
|
||||
# Проверка: мощности хватит?
|
||||
power_used = player["servers"] * 2.0
|
||||
if power_used > player["power_capacity"]:
|
||||
await callback.answer(
|
||||
f"⚡ Недостаточно мощности: {power_used:.1f}/{player['power_capacity']} кВт",
|
||||
show_alert=True
|
||||
)
|
||||
return
|
||||
|
||||
update_player(user_id, {
|
||||
"backup_channel_active": True,
|
||||
"speed": max(5, player["speed"] * 0.75)
|
||||
})
|
||||
await callback.message.edit_text(
|
||||
"✅ Резервный канал активирован! Сеть работает на 75% скорости."
|
||||
)
|
||||
await callback.answer("⚡ Резерв включён!")
|
||||
|
||||
def trigger_outage_event() -> tuple[str, dict]:
|
||||
if random.random() > 0.1: # 10% шанс за сессию (можно регулировать)
|
||||
return "", {}
|
||||
|
||||
district = random.choice(["Северный", "Западный", "Индустриальный"])
|
||||
duration_hours = 1 + random.randint(0, 3)
|
||||
return (
|
||||
f"⚠️ АВАРИЯ В РАЙОНЕ '{district}'!\n"
|
||||
f"Электроснабжение отключено. Ожидание: {duration_hours} ч.\n"
|
||||
f"💡 Используйте резервный канал, если есть.",
|
||||
{"outage_end_ts": int(__import__("time").time()) + duration_hours * 3600}
|
||||
)
|
||||
24
handlers/map.py
Normal file
24
handlers/map.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from aiogram import types
|
||||
from database import get_player
|
||||
|
||||
@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 "🔒 Недоступен"),
|
||||
("🎮 Западный квартал", "🔒 Недоступен"),
|
||||
]
|
||||
|
||||
text = "🗺 *Карта города*\n\n"
|
||||
for dist, status in districts:
|
||||
text += f"{status} — {dist}\n"
|
||||
text += "\n💡 *Подсказка:* Прокачайте канал, чтобы подключить офисы и геймеров."
|
||||
|
||||
await callback.message.edit_text(
|
||||
text,
|
||||
parse_mode="Markdown",
|
||||
reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[
|
||||
[types.InlineKeyboardButton(text="⬅️ Назад", callback_data="back_menu")]
|
||||
])
|
||||
)
|
||||
58
handlers/start.py
Normal file
58
handlers/start.py
Normal file
@@ -0,0 +1,58 @@
|
||||
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
|
||||
)
|
||||
from keyboards import get_main_menu
|
||||
|
||||
router = Router()
|
||||
|
||||
@router.message(CommandStart())
|
||||
async def cmd_start(message: types.Message):
|
||||
user_id = message.from_user.id
|
||||
create_player_if_not_exists(user_id)
|
||||
player = get_player(user_id)
|
||||
|
||||
# Оффлайн доход
|
||||
offline_earn = calculate_daily_offline_earnings(player)
|
||||
if offline_earn > 0:
|
||||
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"],
|
||||
"last_login_ts": player["last_login_ts"]
|
||||
})
|
||||
|
||||
effective_bw = calculate_effective_bandwidth(player)
|
||||
|
||||
status = ""
|
||||
if player["outage_end_ts"] > 0:
|
||||
remaining = player["outage_end_ts"] - int(__import__("time").time())
|
||||
status = f"\n⚠️ АВАРИЯ: Отключение до {remaining // 3600}ч {remaining % 3600 // 60}м"
|
||||
if player["backup_channel_active"]:
|
||||
status += " (резерв активен)"
|
||||
|
||||
text = (
|
||||
f"🌐 *ISP Tycoon* | 📡 Канал: {player['network_channel_level']} ✨\n\n"
|
||||
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['balance']:,.0f} ₡\n"
|
||||
f"📊 *Доход/день:* {calculate_daily_income(player):,.0f} ₡\n"
|
||||
f"🌟 *Рейтинг:* {calculate_rating(player)}\n"
|
||||
f"{status}"
|
||||
)
|
||||
|
||||
await message.answer(
|
||||
text,
|
||||
reply_markup=get_main_menu(player),
|
||||
parse_mode="Markdown"
|
||||
)
|
||||
32
handlers/stats.py
Normal file
32
handlers/stats.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from aiogram import types
|
||||
from database import get_player
|
||||
from economy import calculate_rating, calculate_effective_bandwidth
|
||||
|
||||
@router.callback_query(lambda c: c.data == "stats")
|
||||
async def stats(callback: types.CallbackQuery):
|
||||
player = get_player(callback.from_user.id)
|
||||
bw = calculate_effective_bandwidth(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"🔹 Общий доход: {player['total_income']:,.0f} ₡\n"
|
||||
f"🔹 Рейтинг: {calculate_rating(player)} 🌟"
|
||||
)
|
||||
|
||||
await callback.message.edit_text(
|
||||
text,
|
||||
reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[
|
||||
[types.InlineKeyboardButton(text="⬅️ Назад", callback_data="back_menu")]
|
||||
]),
|
||||
parse_mode="Markdown"
|
||||
)
|
||||
|
||||
@router.callback_query(lambda c: c.data == "back_menu")
|
||||
async def back_to_menu(callback: types.CallbackQuery):
|
||||
await callback.message.delete()
|
||||
from handlers.start import cmd_start
|
||||
await cmd_start(callback.message)
|
||||
18
keyboards.py
Normal file
18
keyboards.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
|
||||
def get_main_menu(player: dict) -> InlineKeyboardMarkup:
|
||||
return InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="🚀 Прокачать канал", callback_data="upgrade_channel")],
|
||||
[InlineKeyboardButton(text="🛠 Построить кабель", callback_data="build_cable")],
|
||||
[InlineKeyboardButton(text="🖥 Купить сервер", callback_data="buy_server")],
|
||||
[InlineKeyboardButton(text="💡 Ускорить постройку (x2 за 500 ₡)", callback_data="speedup_construction")],
|
||||
[InlineKeyboardButton(text="📊 Статистика", callback_data="stats")],
|
||||
[InlineKeyboardButton(text="🗺 Карта города", callback_data="map")]
|
||||
])
|
||||
|
||||
def get_backup_activation(player: dict) -> InlineKeyboardMarkup:
|
||||
if player["backup_channel_active"] or player["outage_end_ts"] == 0:
|
||||
return None
|
||||
return InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="⚡ Активировать резервный канал", callback_data="activate_backup")]
|
||||
])
|
||||
71
main.py
Normal file
71
main.py
Normal file
@@ -0,0 +1,71 @@
|
||||
from aiogram import Bot, Dispatcher, types
|
||||
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 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
|
||||
from handlers.map import router as map_router
|
||||
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
|
||||
|
||||
# Оффлайн доход + апгрейды
|
||||
player = handle_outage(player)
|
||||
player = sync_power(player)
|
||||
effective_bw = calculate_effective_bandwidth(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"
|
||||
)
|
||||
|
||||
async def main():
|
||||
init_db()
|
||||
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())
|
||||
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
aiogram>=3.1.0
|
||||
python-dotenv>=1.0.0
|
||||
Reference in New Issue
Block a user