Новости

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

Источник: 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: "",
		})
	}
}

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

Добавить комментарий

Кнопка «Наверх»