Итак, вы приняли решение создать свой собственный сервер для какой-нибудь Supercell'овской игры, и ваш взгляд пал на то, чтобы использовать Python для разработки. Среди множества проектов сомнительного качества вы наткнулись на PySupercell Core. А что дальше?
Почему PySupercell Core?
Игры Supercell (Clash of Clans, Brawl Stars и т. д.) имеют схожую архитектуру - это объясняется тем, что они сделаны на основе ядра (некой основы для сервера). PySupercell Core (далее PSC) — это новое Python-ядро, которое:
- Реализует базовую архитектуру сервера как у Supercell
- Легко адаптируется под любую игру SC
- Но не является готовым сервером — вам предстоит дописать логику самостоятельно
Большинство других питонических серверов / ядер медленные и устаревшие. PSC же быстрый и удобный в использовании
Ставим PSC
-
Клонируем репозиторий:
git clone https://github.com/REtard-1337/pysupercell-core cd pysupercell-core
-
Ставим зависимости:
pip install -r requirements.txt
Выбираем игру
В папке logic/messages/
ищем файл logic_magic_message_factory.py
. Здесь magic
— кодовое имя Clash of Clans. Меняем его на нужное нам:
Игра | Код-нейм |
---|---|
Clash of Clans | magic |
Brawl Stars | laser |
Clash Royale | scroll |
Hay Day | soil |
Boom Beach | reef |
Squad Busters | squad |
Конфигурация сервера
Открываем config.toml
и задаем параметры игры. Например, для Brawl Stars версии 52.13.77
:
[game]
major_version = 52
build = 13
content_version = 77
environment = "int"
И вот теперь, когда сервер настроен и вроде бы как готов к работе, вы запускаете его, но...
— Но падажи, почему-то на клиенте происходит бесконечное "подключение к серверу" — это значит, что PSC не работает!!!
— Нет, всё работает, просто PSC — это ядро, а не полноценный сервер. Все пакеты необходимо реализовать самостоятельно
Пишем месседжи
В качестве примера я возьму LoginMessage
.
Создаём файл
logic/messages/auth/login_message.py
.-
Пишем пакет:
from titan.message.piranha_message import PiranhaMessage from titan.math.logic_long import LogicLong class LoginMessage(PiranhaMessage): def __init__(self): super().__init__() self.account_id = LogicLong() self.pass_token = "" self.major_version = 0 def decode(self): super().decode() self.account_id = self.stream.read_long() self.pass_token = self.stream.read_string() self.major_version = self.stream.read_int() # ... def get_message_type(self) -> int: return 10101
Но всё это какт сложно, потому давай разберём.
Сначала мы импортируем зависимости:
from titan.message.piranha_message import PiranhaMessage
from titan.math.logic_long import LogicLong
Далее создаём класс LoginMessage — он обязательно должен быть наследником от PiranhaMessage:
class LoginMessage(PiranhaMessage):
...
PiranhaMessage — это базовый класс для всех пакетов, который предоставляет доступ к stream — аналогу Reader/Writer из Classic-Brawl
Затем мы создаём конструктор класса и определяем в нём поля:
def __init__(self):
super().__init__()
self.account_id = LogicLong()
self.pass_token = ""
self.major_version = 0
Теперь создаём декод:
def decode(self):
super().decode()
self.account_id = self.stream.read_long()
self.pass_token = self.stream.read_string()
self.major_version = self.stream.read_int()
И теперь пишем get_message_type
— он вернёт ID месседжа:
def get_message_type(self) -> int:
return 10101
И это всё, конечно, невероятно увлекательно, но как насчёт серверного пакета?
Рядом с login_message.py
создаём новый файлик — login_ok_message.py
:
from titan.math.logic_long import LogicLong
from titan.message.piranha_message import PiranhaMessage
class LoginOkMessage(PiranhaMessage):
def __init__(self) -> None:
super().__init__()
self.account_id = LogicLong()
self.home_id = LogicLong()
self.pass_token = ""
...
def encode(self):
super().encode()
self.stream.write_long(self.account_id)
self.stream.write_long(self.home_id)
self.stream.write_string(self.pass_token)
# и чтот ещё
def get_message_type(self) -> int:
return 20104
Здесь нам разбирать особо нечего — скажу только, что раз это серверный пакет, то здесь должна быть реализована функция encode()
, в которой мы используем созданные нами в конструкторе поля
Обработка пакетов
Уже заметили, что в классах месседжей нет ни process()
, ни execute()
, ни handle()
? Как же так? Дело в том, что Supercell использует немного другой метод обработки пакетов — все пакеты обрабатываются через MessageManager.receive_message
Вот так, например, будет выглядеть обработка LoginMessage
:
# server/protocol/message_manager.py
from logic.messages.auth import LoginMessage, LoginOkMessage
from titan.math import LogicLong
class MessageManager:
def __init__(self, connection):
self.connection = connection
async def receive_message(self, message):
match message.get_message_type():
case 10101:
await self.on_login(message)
case _:
... # чтот другое
async def on_login(self, message: LoginMessage):
response = LoginOkMessage()
response.account_id = ...
response.pass_token = ...
# ...
await self.connection.send_message(response)
Что здесь происходит?
Начну с того, что изначально message_manager.py
— это не пустой файлик. В нём уже есть базовая структура (см. скриншот ниже).
Мы просто добавили нужный кейс (назовём его просто условием), чтобы вызвать обработчик login message
’а, если придёт пакет с нужным ID:
async def receive_message(self, message):
...
case 10101:
await self.on_login(message) # Поскольку on_login - асинхронная функция, мы вызываем её с ключевым словом await.
А теперь посмотрим на сам on_login
:
async def on_login(self, message: LoginMessage):
response = LoginOkMessage()
response.account_id = ...
response.pass_token = ...
# ...
await self.connection.send_message(response)
В аргументы функции передаём пакет, который обрабатываем:
async def on_login(self, message: LoginMessage):
Потом создаём инстанс пакета, который хотим отправить клиенту:
response = LoginOkMessage()
И заполняем поля — чтобы при енкоде сообщения туда вошли данные, нужные нам:
response.account_id = ...
response.pass_token = ...
И потом шлём назад нужный месседж клиенту:
await self.connection.send_message(response)
Top comments (0)