Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
2e919e8
Implement initial bot structure with database integration and command…
Hell4uk Oct 28, 2025
ade9c5b
Add item and inventory models with enums for item type and rarity
Hell4uk Oct 29, 2025
02eba6d
Rename ItemService file and related item handling logic from the bot …
Hell4uk Oct 29, 2025
3c83468
Add default command handling and greeting responses to the bot. Intro…
Hell4uk Oct 29, 2025
acbdee0
Refactor inventory handling by renaming Inventory to InventoryItem an…
Hell4uk Oct 31, 2025
67dda7f
Refactor bot structure by updating import paths, enhancing router con…
Hell4uk Oct 31, 2025
34e29de
Rename User, Item, and InventoryItem models to Users, Items, and Inve…
Hell4uk Oct 31, 2025
3c032f9
Refactor item and user models by enhancing enum definitions to inheri…
Hell4uk Oct 31, 2025
757bff3
Enhance bot configuration by validating `BOT_API_KEY` in the environm…
Hell4uk Oct 31, 2025
83fb96a
Refactor inventory system to enforce single item equipping per type, …
Hell4uk Nov 5, 2025
4ef88f4
Update default command handling to include main menu keyboard in resp…
Hell4uk Nov 5, 2025
ca56871
Add enemy model and calculation logic to enhance gameplay dynamics. I…
Hell4uk Nov 5, 2025
d3b0acb
Refactor EnemyService to improve clarity and consistency in enemy rew…
Hell4uk Nov 6, 2025
5117261
Refactor enemy reward system to replace `gold_reward_multiplier` with…
Hell4uk Nov 6, 2025
59d49d9
Refactor user handling by introducing UserService for user creation a…
Hell4uk Nov 6, 2025
148e478
Integrate SafeEditMiddleware into bot message handling for improved m…
Hell4uk Nov 6, 2025
708bd21
[MEGA-UPDATE]
Hell4uk Nov 24, 2025
8e67df4
Refactor bot middleware and handlers for improved functionality. Repl…
Hell4uk Nov 28, 2025
70785c1
Enhance user and enemy models by adding new fields for tracking wins,…
Hell4uk Nov 28, 2025
4c207b7
Enhance user statistics display in callback handler by adding detaile…
Hell4uk Nov 28, 2025
350166c
Implement Windows compatibility for asyncio event loop in main.py. Re…
Hell4uk Nov 30, 2025
3f65b92
Enhance main.py to handle InterruptedError gracefully, providing user…
Hell4uk Dec 6, 2025
916b552
Refactor enemy spawning logic in EnemyService to include location fil…
Hell4uk Dec 6, 2025
3c12066
Refactor battle callback handlers to improve structure and clarity. U…
Hell4uk Dec 7, 2025
2140452
Enchance fighting function and created attributes for Enchants and Ca…
Hell4uk Dec 11, 2025
288f9ba
Created base case system enchance fight system, itemservice
Hell4uk Dec 13, 2025
6e88719
enchanced items validator, battle system, enemy system
Hell4uk Dec 14, 2025
9f5a02b
feat: Implement case management system with opening and purchasing fu…
Hell4uk Jan 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import logging
from src.bot.bot import bootstrap
from src.bot.db.database import init_db, close_db
import asyncio
import sys

if sys.platform == 'win32':
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(),
logging.FileHandler('bot_logs.log')
]
)

logging.getLogger('tortoise').setLevel(logging.DEBUG)
logging.getLogger('db_client').setLevel(logging.DEBUG)

async def main() -> None:
await init_db()
try:
await bootstrap()

except KeyboardInterrupt:
print("Telegram bot was closed!")

finally:
await close_db()

if __name__ == '__main__':
asyncio.run(main())
47 changes: 47 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# CometFall — текстовая MMORPG в Telegram

![Python](https://img.shields.io/badge/python-3.11-blue)
![Aiogram](https://img.shields.io/badge/aiogram-3.22-green)
![Tortoise ORM](https://img.shields.io/badge/tortoise--orm-0.25-orange)
![PostgreSQL](https://img.shields.io/badge/postgres-15-blue)
![License](https://img.shields.io/badge/license-MIT-brightgreen)

---

**CometFall - это современный проект**, который включает в себя **совершенные подходы программирования** и **структуризации**.

Бей врагов, прокачивай меч, собирай дроп, покупай оружия, соревнуйся с другими игроками — и всё это в **Telegram** без **никаких лишних действий**.

[ИГРАТЬ СЕЙЧАС](https://t.me/CometFall_bot)\
тг: @CometFall_bot

---

## Что умеет бот:

---

## Структура проекта:

```
src/
└─ bot/
├─ handlers/ - команды и колбэки
├─ keyboards/ - кнопки
├─ services/ - сервисы инвентаря, врагов, дропа
├─ middlewares/ - проверки, логирование
├─ game/
│ ├─ logic/ - расчёт урона, брони
│ ├─ views/ - создание читабельного сообщения
│ └─ config.py - конфигурация
├─ db/
│ ├─ models.py - Users, Items, Enemies
│ └─ schemas/ - Pydantic-валидация атрибутов
└─ config.py - Получения констант из .env, mapping текста

```

---

Наша команда: **TeraCodeFrame**\
тг: @TeraCodeFrame
Binary file modified requirements.txt
Binary file not shown.
2 changes: 2 additions & 0 deletions scripts/basedb/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# basedb package

57 changes: 57 additions & 0 deletions scripts/basedb/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
TOTAL_WEAPONS = 100
TOTAL_ARMORS = 100

RARITY_ROTATION = ("common", "rare", "epic", "legendary")

WEAPON_PREFIXES = [
"Кометный",
"Звёздный",
"Астральный",
"Грозовой",
"Неоновый",
"Ледяной",
"Пылающий",
"Теневой",
"Хрустальный",
"Гравитационный",
]

WEAPON_SUFFIXES = [
"Клинок",
"Меч",
"Коса",
"Сабля",
"Копьё",
"Молот",
"Тесак",
"Катана",
"Глефа",
"Шип",
]

ARMOR_PREFIXES = [
"Кометный",
"Лунный",
"Солнечный",
"Грозовой",
"Неоновый",
"Ледяной",
"Пылающий",
"Теневой",
"Хрустальный",
"Гравитационный",
]

ARMOR_SUFFIXES = [
"Доспех",
"Кираса",
"Латы",
"Панцирь",
"Нагрудник",
"Бронежилет",
"Мантия",
"Плащ",
"Жилет",
"Скафандр",
]

178 changes: 178 additions & 0 deletions scripts/basedb/generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import sys
from pathlib import Path
from typing import Iterable, Tuple

sys.path.append(str(Path(__file__).resolve().parents[2]))

from tortoise import Tortoise

from src.bot.db.database import close_db, init_db
from src.bot.db.models import ItemRarityEnum, ItemTypeEnum, Items

from .constants import (
ARMOR_PREFIXES,
ARMOR_SUFFIXES,
RARITY_ROTATION,
TOTAL_ARMORS,
TOTAL_WEAPONS,
WEAPON_PREFIXES,
WEAPON_SUFFIXES,
)

RARITY_MAP = {
"common": ItemRarityEnum.COMMON,
"rare": ItemRarityEnum.RARE,
"epic": ItemRarityEnum.EPIC,
"legendary": ItemRarityEnum.LEGENDARY,
}


def _cycled_value(values: Iterable[str], index: int) -> str:
sequence = tuple(values)
return sequence[index % len(sequence)]


def _rarity_for_index(index: int) -> ItemRarityEnum:
slug = RARITY_ROTATION[index % len(RARITY_ROTATION)]
return RARITY_MAP[slug]


async def _reset_sequence():
"""Reset PostgreSQL sequence to match the maximum ID in the items table."""
# Get the maximum ID using Tortoise ORM
max_item = await Items.all().order_by("-id").first()
max_id = max_item.id if max_item else 0
# Reset the sequence to the maximum ID (or 1 if table is empty)
next_id = max(max_id, 1)

# Execute raw SQL to reset the sequence using the underlying connection
conn = Tortoise.get_connection("default")
# Use execute_query for SELECT statements
await conn.execute_query(f"SELECT setval('items_id_seq', {next_id}, true);")


async def _ensure_default_items():
sword = await Items.get_or_none(id=1)
if sword:
print("Уже есть", sword.name, "(id=1)")
else:
sword = await Items.create(
id=1,
name="Деревянный меч",
type=ItemTypeEnum.WEAPON,
rarity=ItemRarityEnum.COMMON,
attributes={
"item_level": 1,
"min_damage": 5,
"max_damage": 12,
"attack_speed": 1.0,
"critical_chance": 0.05,
"critical_multiplier": 1.5,
},
description="Простой деревянный меч. Лучше, чем кулаки.",
)
print("Создан", sword.name, "(id=1)")

armor = await Items.get_or_none(id=2)
if armor:
print("Уже есть", armor.name, "(id=2)")
else:
armor = await Items.create(
id=2,
name="Тряпичная броня",
type=ItemTypeEnum.ARMOR,
rarity=ItemRarityEnum.COMMON,
attributes={"item_level": 1, "defense": 8, "health_bonus": 20},
description="Старая одежда. Немного защищает.",
)
print("Создана", armor.name, "(id=2)")

# Reset the sequence to prevent ID conflicts
await _reset_sequence()


async def _bulk_create_weapons(start_index: int, count: int):
for offset in range(count):
sequence_index = start_index + offset
rarity = _rarity_for_index(sequence_index)
level = sequence_index + 1
prefix = _cycled_value(WEAPON_PREFIXES, sequence_index)
suffix = _cycled_value(WEAPON_SUFFIXES, sequence_index // len(WEAPON_PREFIXES))
name = f"{prefix} {suffix} {level}"

min_damage = 6 + sequence_index * 2
max_damage = min_damage + 6 + (sequence_index % 5)
attack_speed = round(1.0 + (sequence_index % 7) * 0.05, 2)
crit_chance = round(0.05 + (sequence_index % 4) * 0.03, 2)
crit_mult = round(1.4 + (sequence_index % 3) * 0.2, 2)

defaults = {
"type": ItemTypeEnum.WEAPON,
"rarity": rarity,
"attributes": {
"item_level": level,
"min_damage": min_damage,
"max_damage": max_damage,
"attack_speed": attack_speed,
"critical_chance": min(crit_chance, 0.65),
"critical_multiplier": min(crit_mult, 3.5),
},
"description": (
f"Оружие {rarity.name.lower()} класса, выкованное мастерами Кометопада. "
f"Номер серии {level}."
),
}

_, created = await Items.get_or_create(name=name, defaults=defaults)
if created:
print("Добавлено оружие:", name)


async def _bulk_create_armors(start_index: int, count: int):
for offset in range(count):
sequence_index = start_index + offset
rarity = _rarity_for_index(sequence_index)
level = sequence_index + 1
prefix = _cycled_value(ARMOR_PREFIXES, sequence_index)
suffix = _cycled_value(ARMOR_SUFFIXES, sequence_index // len(ARMOR_PREFIXES))
name = f"{prefix} {suffix} {level}"

defense = 10 + sequence_index * 3
health_bonus = 25 + sequence_index * 5
defaults = {
"type": ItemTypeEnum.ARMOR,
"rarity": rarity,
"attributes": {
"item_level": level,
"defense": defense,
"health_bonus": health_bonus,
},
"description": (
f"Броня {rarity.name.lower()} класса, созданная для защитников Кометопада. "
f"Серия {level}."
),
}

_, created = await Items.get_or_create(name=name, defaults=defaults)
if created:
print("Добавлена броня:", name)


async def create_base_items():
await init_db()
try:
await _ensure_default_items()

extra_weapons = TOTAL_WEAPONS - 1
extra_armors = TOTAL_ARMORS - 1

await _bulk_create_weapons(start_index=1, count=extra_weapons)
await _bulk_create_armors(start_index=1, count=extra_armors)

print("Генерация предметов завершена.")
finally:
await close_db()


__all__ = ("create_base_items",)

2 changes: 2 additions & 0 deletions scripts/gamedb/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# gamedb package

Loading