Minecraft Bedrock сервер на Go. Часть #1

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.
Источник: https://github.com/Sandertv/gophertunnel
Источник: https://github.com/Sandertv/gophertunnel

Для реализации нашего сервера, мы будем использовать библиотеку Sandertv/gophertunnel. В этой части туториала, мы напишем свой тунель(прокси?), основываясь на примере из официального репозитория библиотеки, немного упростив его.

Предисловие

Bedrock версия использует надстройку над UDP - Raknet. Подробная документация и варианты реализации протокола на других языках, доступны здесь. Написание Minecraft сервера, подразумевает под собой написание прокси между оригинальным сервером и клиетами. Вы получаете возможность манипулировать данными, которыми обмениваются клиент и сервер. Статья подразумевает, что вы обладаете базовыми познаниями в Go и умеете разворачивать оригинальный сервер Minecraft.

Базовый прокси

package main

import (
	"github.com/sandertv/gophertunnel/minecraft"
	"sync"
)

func main() {
	listener, _ := minecraft.ListenConfig{
		AuthenticationDisabled: true, // Отключаем авторизацию, чтобы не мучаться с входом в xbox, не забудьте отключить ее и в оригинальном сервере
	}.Listen("raknet", ":19130") // Стартуем наш прокси сервер

	for {
		c, _ := listener.Accept()

		go handleConnection(c.(*minecraft.Conn))
	}
}

func handleConnection(conn *minecraft.Conn) {
	dialer, _ := minecraft.Dialer{
		ClientData: conn.ClientData(),
	}.Dial("raknet", ":19131") // Клиент к оригинальному серверу

	g := sync.WaitGroup{}

	g.Add(2)

	/* Начинаем игру и спавним игрока */
	go func() {
		conn.StartGame(dialer.GameData())

		g.Done()
	}()

	go func() {
		dialer.DoSpawn()

		g.Done()
	}()

	g.Wait()

	// Передаем данные полученные от игрока на оригинальный сервер
	go func() {
		for {
			pk, _ := conn.ReadPacket()

			dialer.WritePacket(pk)
		}
	}()

	// Передаем данные полученые от оригинального сервера, подключенному клиенту
	go func() {
		for {
			pk, _ := dialer.ReadPacket()
			conn.WritePacket(pk)
		}
	}()
}

Теперь у нас есть прокси сервер, который пока что просто пропускает через себя запросы. Пока что оно не умеет закрывать соединение, не обрабатывает ошибки, падает если какой либо клиент отключается.

два клиента, один из которых подключен к оригинальному серверу
два клиента, один из которых подключен к оригинальному серверу

Немного модифицируем "слушающие" горутины, чтобы наш сервер научился закрывать соединение и не падать когда какой либо клиент отключается:

// Передаем данные полученные от игрока на оригинальный сервер
	go func() {
		defer listener.Disconnect(conn, "connection lost")
		defer dialer.Close()

		for {
			pk, err := conn.ReadPacket()
			if err != nil {
				return
			}

			err = dialer.WritePacket(pk)
			if err != nil {
				return
			}
		}
	}()

	// Передаем данные полученые от оригинального сервера, подключенному клиенту
	go func() {
		defer dialer.Close()
		defer listener.Disconnect(conn, "connection lost")

		for {
			pk, err := dialer.ReadPacket()
			if err != nil {
				return
			}

			err = conn.WritePacket(pk)
			if err != nil {
				return
			}
		}
	}()

Чтобы не дублировать в наш прокси сервер, статусные данные оригинального сервера, такие как название, кол-во игроков и прочее(подробее тут), настроим провайдер данных в виде оригинального сервера:

func main() {
	p, _ := minecraft.NewForeignStatusProvider(":19131") // В качестве провайдера данных, используем оригинальный сервер

	listener, _ := minecraft.ListenConfig{
		StatusProvider:         p,
		AuthenticationDisabled: true, // Отключаем авторизацию, чтобы не мучаться с входом в xbox, не забудьте отключить ее и в оригинальном сервере
	}.Listen("raknet", ":19130") // Стартуем наш прокси сервер

	for {
		c, _ := listener.Accept()

		go handleConnection(c.(*minecraft.Conn), listener)
	}
}

Это всё-таки прокси, а не сервер

В Minecraft Education Edition, с версии 1.0.1, появился "Agent". Этакий управляемый вашим кодом NPC. Принцип добавления функциональности для серверов Minecraft, чем то схож с принципом программирования "агентов". Все что вы можете делать, это пропускать данные от клиента к серверу и наоборот. Таким образом вы можете действовать от лица конкретных пользователей(не обязательно), фильтровать сообщения, запрещать или разрешать соединение с оригинальным сервером, логировать действия клиентов. Все ограничивается, только вашей фантазией.

Эмуляция клиента

Мы можем создать диалер, не привязываясь к реальному клиенту. Это может быть полезно для выполнения действий от именни "Сервера". По сути мы программируем своего "Агента", который не обязательно должен присуствовать в качестве физического объекта:

package main

import (
	"github.com/sandertv/gophertunnel/minecraft"
	"github.com/sandertv/gophertunnel/minecraft/protocol/login"
	"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
	"time"
)

func main() {
	dialer, _ := minecraft.Dialer{
		IdentityData: login.IdentityData{DisplayName: "Server"},
	}.Dial("raknet", ":19131") // Клиент к оригинальному серверу

	for {
		time.Sleep(10 * time.Second)

		dialer.WritePacket(&packet.Text{
			TextType:       packet.TextTypeChat,
			SourceName:     "MainDialer",
			Message:        "Сообщение от сервера",
			XUID:           "",
			PlatformChatID: "",
		})
	}
}

Такой подход, может первое время сбивать с толку и казаться очень урезанным, но на самом деле он открывает огромные возможности по модификации игрового процесса. В последующих частях, мы будем постепенно наращивать возможности и надежность нашего сервера.

Источник: https://habr.com/ru/post/645715/


Интересные статьи

Интересные статьи

Меня зовут Дмитрий, я специалист по тестированию в студии IT Territory. За 17 лет мы выпустили более 15 успешных игровых проектов с общей аудиторией около 100 млн игроков по всему миру. Вы можете бы...
Когда мы говорим о бессерверной архитектуре, мы обычно выходим далеко за рамки модели «функция как услуга» (FaaS), одной из реализаций которой являются функции AWS Lambda...
Посвящается моему коллеге и наставнику по Informatica Максиму Генцелю, который умер от COVID-19 21.01.2021 Привет! Меня зовут Баранов Владимир, и я уже несколько лет администрирую In...
На прошлую статью, где мы рассмотрели три оператора PostgreSQL для Kubernetes (Stolon, Crunchy Data и Zalando), поделились своим выбором и опытом эксплуатации, — поступила отличная об...
Практически все коммерческие интернет-ресурсы создаются на уникальных платформах соответствующего типа. Среди них наибольшее распространение получил Битрикс24.