DEV Community

idk
idk

Posted on

Гайд на PySupercell Core: Создаём свой сервер для игр Supercell

Итак, вы приняли решение создать свой собственный сервер для какой-нибудь Supercell'овской игры, и ваш взгляд пал на то, чтобы использовать Python для разработки. Среди множества проектов сомнительного качества вы наткнулись на PySupercell Core. А что дальше?

Почему PySupercell Core?

Игры Supercell (Clash of Clans, Brawl Stars и т. д.) имеют схожую архитектуру - это объясняется тем, что они сделаны на основе ядра (некой основы для сервера). PySupercell Core (далее PSC) — это новое Python-ядро, которое:

  • Реализует базовую архитектуру сервера как у Supercell
  • Легко адаптируется под любую игру SC
  • Но не является готовым сервером — вам предстоит дописать логику самостоятельно

Большинство других питонических серверов / ядер медленные и устаревшие. PSC же быстрый и удобный в использовании

Ставим PSC

  1. Клонируем репозиторий:

    git clone https://github.com/REtard-1337/pysupercell-core
    cd pysupercell-core
    
  2. Ставим зависимости:

    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"
Enter fullscreen mode Exit fullscreen mode

И вот теперь, когда сервер настроен и вроде бы как готов к работе, вы запускаете его, но...

— Но падажи, почему-то на клиенте происходит бесконечное "подключение к серверу" — это значит, что PSC не работает!!!

— Нет, всё работает, просто PSC — это ядро, а не полноценный сервер. Все пакеты необходимо реализовать самостоятельно


Пишем месседжи

В качестве примера я возьму LoginMessage.

  1. Создаём файл logic/messages/auth/login_message.py.

  2. Пишем пакет:

    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
Enter fullscreen mode Exit fullscreen mode

Далее создаём класс LoginMessage — он обязательно должен быть наследником от PiranhaMessage:

class LoginMessage(PiranhaMessage):
    ...
Enter fullscreen mode Exit fullscreen mode

PiranhaMessage — это базовый класс для всех пакетов, который предоставляет доступ к stream — аналогу Reader/Writer из Classic-Brawl

Затем мы создаём конструктор класса и определяем в нём поля:

    def __init__(self):
        super().__init__()
        self.account_id = LogicLong()
        self.pass_token = ""
        self.major_version = 0
Enter fullscreen mode Exit fullscreen mode

Теперь создаём декод:

    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()
Enter fullscreen mode Exit fullscreen mode

И теперь пишем get_message_type — он вернёт ID месседжа:

    def get_message_type(self) -> int:
        return 10101
Enter fullscreen mode Exit fullscreen mode

И это всё, конечно, невероятно увлекательно, но как насчёт серверного пакета?
Рядом с 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
Enter fullscreen mode Exit fullscreen mode

Здесь нам разбирать особо нечего — скажу только, что раз это серверный пакет, то здесь должна быть реализована функция 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)
Enter fullscreen mode Exit fullscreen mode

Что здесь происходит?
Начну с того, что изначально message_manager.py — это не пустой файлик. В нём уже есть базовая структура (см. скриншот ниже).
Image description

Мы просто добавили нужный кейс (назовём его просто условием), чтобы вызвать обработчик login message’а, если придёт пакет с нужным ID:

    async def receive_message(self, message):
        ...
            case 10101:
                await self.on_login(message) # Поскольку on_login - асинхронная функция, мы вызываем её с ключевым словом await.
Enter fullscreen mode Exit fullscreen mode

А теперь посмотрим на сам on_login:

    async def on_login(self, message: LoginMessage):
        response = LoginOkMessage()
        response.account_id = ...
        response.pass_token = ...
        # ...
        await self.connection.send_message(response)
Enter fullscreen mode Exit fullscreen mode

В аргументы функции передаём пакет, который обрабатываем:

    async def on_login(self, message: LoginMessage):
Enter fullscreen mode Exit fullscreen mode

Потом создаём инстанс пакета, который хотим отправить клиенту:

        response = LoginOkMessage()
Enter fullscreen mode Exit fullscreen mode

И заполняем поля — чтобы при енкоде сообщения туда вошли данные, нужные нам:

        response.account_id = ...
        response.pass_token = ...
Enter fullscreen mode Exit fullscreen mode

И потом шлём назад нужный месседж клиенту:

await self.connection.send_message(response)
Enter fullscreen mode Exit fullscreen mode

Остались вопросы? Пиши в лс — t.me/TheBladewise1337, или второму разрабу — t.me/user_with_username.

Top comments (0)