Начало разработки интеграций
Это руководство поможет вам создать индивидуальные интеграции между Pano и плагинами серверов Minecraft, используя Pano MC Plugin API.
🎯 Что такое разработка интеграций?
Разработка интеграций позволяет создавать бесшовные соединения между сторонними плагинами Minecraft и веб-платформой Pano. Используя API Pano MC Plugin, вы можете:
- Синхронизировать данные между игрой и вебом в режиме реального времени.
- Отправлять запросы из вашего плагина Minecraft на веб-платформу Pano.
- Получать и обрабатывать сообщения от Pano.
- Запускать веб-действия на основе игровых событий.
- Создавать единый пользовательский опыт на обеих платформах.
🔧 Предварительные требования
Перед началом разработки интеграций убедитесь, что у вас есть:
- Java Development Kit (JDK 11+) — необходим для разработки плагинов.
- Знание Java или Kotlin — вы можете использовать любой из этих языков с API Pano MC Plugin.
- Опыт разработки плагинов Minecraft — понимание Spigot/Paper/Bukkit API.
- Pano MC Plugin API — репозиторий GitHub.
- Запущенный экземпляр Pano — для тестирования вашей интеграции.
- Тестовый сервер Minecraft — сервер Spigot, Paper или Folia для разработки.
💡 Примечание: Все примеры в этом руководстве приведены как на Kotlin, так и на Java для вашего удобства.
🏗️ Обзор архитектуры
Система интеграции Pano состоит из трех основных компонентов:
┌─────────────────────┐ ┌──────────────────────┐ ┌─────────────────┐
│ Ваш MC плагин │ ◄─────► │ Pano MC Plugin │ ◄─────► │ Бэкенд Pano │
│ (Интеграция) │ │ (API связи) │ │ (Веб-платформа)│
└─────────────────────┘ └──────────────────────┘ └─────────────────┘
(Хуки плагина) (Безопасный WebSocket API) (API платформы)Поток данных
- Ваш плагин → API Pano MC Plugin: Вы используете API для отправки запросов или сообщений.
- Pano MC Plugin → Бэкенд Pano: Безопасная зашифрованная связь через WebSocket (RSA + AES-256).
- Бэкенд Pano → Ваш плагин: Pano автоматически обрабатывает соединения и направляет сообщения обратно.
- Бэкенд Pano → Веб: Данные синхронизируются и отображаются на веб-сайте.
Важно: НЕ делайте форк Pano MC Plugin. Вместо этого создайте свой отдельный плагин и используйте API Pano MC Plugin.
📚 Основные концепции API
1. PlatformRequest
Чтобы отправить запрос на веб-платформу Pano, наследуйте абстрактный класс PlatformRequest:
abstract class PlatformRequest {
abstract fun getRequestType(): String
abstract fun getData(): Map<String, Any>
}import com.panomc.plugin.api.PlatformRequest
class MyCustomRequest(
private val playerName: String,
private val data: String
) : PlatformRequest() {
override fun getRequestType(): String = "my_custom_request"
override fun getData(): Map<String, Any> {
return mapOf(
"player" to playerName,
"data" to data,
"timestamp" to System.currentTimeMillis()
)
}
}import com.panomc.plugin.api.PlatformRequest;
import java.util.HashMap;
import java.util.Map;
public class MyCustomRequest extends PlatformRequest {
private final String playerName;
private final String data;
public MyCustomRequest(String playerName, String data) {
this.playerName = playerName;
this.data = data;
}
@Override
public String getRequestType() {
return "my_custom_request";
}
@Override
public Map<String, Object> getData() {
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("player", playerName);
dataMap.put("data", data);
dataMap.put("timestamp", System.currentTimeMillis());
return dataMap;
}
}2. PlatformMessageResponse
Если вы ожидаете ответ от Pano, реализуйте интерфейс PlatformMessageResponse:
interface PlatformMessageResponse {
fun onResponse(response: Map<String, Any>)
fun onError(error: String)
}import com.panomc.plugin.api.PlatformMessageResponse
class MyRequestWithResponse(
private val playerName: String
) : PlatformRequest(), PlatformMessageResponse {
override fun getRequestType(): String = "player_data_request"
override fun getData(): Map<String, Any> {
return mapOf("player" to playerName)
}
override fun onResponse(response: Map<String, Any>) {
// Обработка успешного ответа
val points = response["points"] as? Int ?: 0
println("Игрок $playerName имеет $points очков")
}
override fun onError(error: String) {
// Обработка ошибки
println("Ошибка: $error")
}
}import com.panomc.plugin.api.PlatformRequest;
import com.panomc.plugin.api.PlatformMessageResponse;
import java.util.HashMap;
import java.util.Map;
public class MyRequestWithResponse extends PlatformRequest implements PlatformMessageResponse {
private final String playerName;
public MyRequestWithResponse(String playerName) {
this.playerName = playerName;
}
@Override
public String getRequestType() {
return "player_data_request";
}
@Override
public Map<String, Object> getData() {
Map<String, Object> data = new HashMap<>();
data.put("player", playerName);
return data;
}
@Override
public void onResponse(Map<String, Object> response) {
// Обработка успешного ответа
Integer points = (Integer) response.getOrDefault("points", 0);
System.out.println("Игрок " + playerName + " имеет " + points + " очков");
}
@Override
public void onError(String error) {
// Обработка ошибки
System.out.println("Ошибка: " + error);
}
}3. PlatformMessageHandler
Чтобы получать и обрабатывать сообщения от веб-платформы Pano, наследуйте PlatformMessageHandler<R : PlatformMessage>:
abstract class PlatformMessageHandler<R : PlatformMessage> {
abstract fun handle(message: R)
abstract fun getMessageType(): String
}import com.panomc.plugin.api.PlatformMessageHandler
import com.panomc.plugin.api.PlatformMessage
import com.panomc.plugin.api.PlatformManager
// Определение структуры вашего сообщения
data class PlayerRewardMessage(
val playerName: String,
val reward: String,
val amount: Int
) : PlatformMessage
// Создание обработчика
class PlayerRewardHandler : PlatformMessageHandler<PlayerRewardMessage>() {
override fun getMessageType(): String = "player_reward"
override fun handle(message: PlayerRewardMessage) {
val player = Bukkit.getPlayer(message.playerName)
if (player != null) {
// Выдача награды игроку
when (message.reward) {
"coins" -> giveCoins(player, message.amount)
"items" -> giveItems(player, message.amount)
}
player.sendMessage("Вы получили ${message.amount} ${message.reward}!")
}
}
}
// Регистрация обработчика
fun registerHandlers(platformManager: PlatformManager) {
platformManager.registerMessageHandler(PlayerRewardHandler())
}import com.panomc.plugin.api.PlatformMessageHandler;
import com.panomc.plugin.api.PlatformMessage;
import com.panomc.plugin.api.PlatformManager;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
// Определение структуры вашего сообщения
public class PlayerRewardMessage implements PlatformMessage {
private final String playerName;
private final String reward;
private final int amount;
public PlayerRewardMessage(String playerName, String reward, int amount) {
this.playerName = playerName;
this.reward = reward;
this.amount = amount;
}
public String getPlayerName() { return playerName; }
public String getReward() { return reward; }
public int getAmount() { return amount; }
}
// Создание обработчика
public class PlayerRewardHandler extends PlatformMessageHandler<PlayerRewardMessage> {
@Override
public String getMessageType() {
return "player_reward";
}
@Override
public void handle(PlayerRewardMessage message) {
Player player = Bukkit.getPlayer(message.getPlayerName());
if (player != null) {
// Выдача награды игроку
switch (message.getReward()) {
case "coins":
giveCoins(player, message.getAmount());
break;
case "items":
giveItems(player, message.getAmount());
break;
}
player.sendMessage("Вы получили " + message.getAmount() + " " + message.getReward() + "!");
}
}
}
// Регистрация обработчика
public void registerHandlers(PlatformManager platformManager) {
platformManager.registerMessageHandler(new PlayerRewardHandler());
}🚀 Создание вашего первого плагина интеграции
Шаг 1: Создание нового проекта плагина
Создайте стандартный плагин Spigot/Paper с файлом plugin.yml:
name: MyPanoIntegration
version: 1.0.0
main: com.example.integration.MyIntegrationPlugin
api-version: 1.19
depend: [Pano, YourTargetPlugin] # Зависимость от Pano MC PluginШаг 2: Добавление Pano MC Plugin в зависимости
Добавьте Pano MC Plugin в конфигурацию сборки:
Maven:
<dependency>
<groupId>com.panomc</groupId>
<artifactId>pano-mc-plugin</artifactId>
<version>1.0.0</version>
<scope>provided</scope>
</dependency>Gradle (Kotlin DSL):
dependencies {
compileOnly("com.panomc:pano-mc-plugin:1.0.0")
}Шаг 3: Инициализация вашей интеграции
package com.example.integration
import com.panomc.plugin.api.PlatformManager
import org.bukkit.plugin.java.JavaPlugin
class MyIntegrationPlugin : JavaPlugin() {
private lateinit var platformManager: PlatformManager
override fun onEnable() {
// Получение PlatformManager из Pano MC Plugin
val panoPlugin = server.pluginManager.getPlugin("Pano")
if (panoPlugin == null) {
logger.severe("Pano MC Plugin не найден! Отключение...")
server.pluginManager.disablePlugin(this)
return
}
platformManager = panoPlugin.getPlatformManager()
// Регистрация обработчиков сообщений
registerHandlers()
// Подключение к целевому плагину
setupIntegration()
logger.info("Интеграция успешно включена!")
}
private fun registerHandlers() {
// Регистрация обработчиков для получения сообщений от Pano
platformManager.registerMessageHandler(PlayerRewardHandler())
platformManager.registerMessageHandler(ConfigUpdateHandler())
}
private fun setupIntegration() {
// Подключение к API вашего целевого плагина
// Слушайте события, регистрируйте команды и т.д.
}
fun sendRequestToPano(request: PlatformRequest) {
platformManager.sendRequest(request)
}
}package com.example.integration;
import com.panomc.plugin.api.PlatformManager;
import com.panomc.plugin.api.PlatformRequest;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
public class MyIntegrationPlugin extends JavaPlugin {
private PlatformManager platformManager;
@Override
public void onEnable() {
// Получение PlatformManager из Pano MC Plugin
Plugin panoPlugin = getServer().getPluginManager().getPlugin("Pano");
if (panoPlugin == null) {
getLogger().severe("Pano MC Plugin не найден! Отключение...");
getServer().getPluginManager().disablePlugin(this);
return;
}
platformManager = panoPlugin.getPlatformManager();
// Регистрация обработчиков сообщений
registerHandlers();
// Подключение к целевому плагину
setupIntegration();
getLogger().info("Интеграция успешно включена!");
}
private void registerHandlers() {
// Регистрация обработчиков для получения сообщений от Pano
platformManager.registerMessageHandler(new PlayerRewardHandler());
platformManager.registerMessageHandler(new ConfigUpdateHandler());
}
private void setupIntegration() {
// Подключение к API вашего целевого плагина
// Слушайте события, регистрируйте команды и т.д.
}
public void sendRequestToPano(PlatformRequest request) {
platformManager.sendRequest(request);
}
public PlatformManager getPlatformManager() {
return platformManager;
}
}Шаг 4: Подключение к событиям целевого плагина
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.player.PlayerJoinEvent
class PlayerJoinListener(
private val plugin: MyIntegrationPlugin
) : Listener {
@EventHandler
fun onPlayerJoin(event: PlayerJoinEvent) {
val player = event.player
// Отправка события входа игрока в Pano
val request = object : PlatformRequest() {
override fun getRequestType() = "player_join"
override fun getData() = mapOf(
"player" to player.name,
"uuid" to player.uniqueId.toString(),
"ip" to player.address?.address?.hostAddress
)
}
plugin.sendRequestToPano(request)
}
}import com.panomc.plugin.api.PlatformRequest;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import java.util.HashMap;
import java.util.Map;
public class PlayerJoinListener implements Listener {
private final MyIntegrationPlugin plugin;
public PlayerJoinListener(MyIntegrationPlugin plugin) {
this.plugin = plugin;
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
// Отправка события входа игрока в Pano
PlatformRequest request = new PlatformRequest() {
@Override
public String getRequestType() {
return "player_join";
}
@Override
public Map<String, Object> getData() {
Map<String, Object> data = new HashMap<>();
data.put("player", player.getName());
data.put("uuid", player.getUniqueId().toString());
data.put("ip", player.getAddress().getAddress().getHostAddress());
return data;
}
};
plugin.sendRequestToPano(request);
}
}Шаг 5: Отправка запросов с ожиданием ответа
class PlayerStatsRequest(
private val playerUUID: String,
private val callback: (Map<String, Any>) -> Unit
) : PlatformRequest(), PlatformMessageResponse {
override fun getRequestType() = "player_stats"
override fun getData() = mapOf("uuid" to playerUUID)
override fun onResponse(response: Map<String, Any>) {
callback(response)
}
override fun onError(error: String) {
println("Не удалось получить статистику игрока: $error")
}
}
// Использование в команде
fun onCommand(player: Player) {
val request = PlayerStatsRequest(player.uniqueId.toString()) { stats ->
player.sendMessage("Ваша статистика:")
player.sendMessage("Убийств: ${stats["kills"]}")
player.sendMessage("Смертей: ${stats["deaths"]}")
}
platformManager.sendRequest(request)
}import com.panomc.plugin.api.PlatformRequest;
import com.panomc.plugin.api.PlatformMessageResponse;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
public class PlayerStatsRequest extends PlatformRequest implements PlatformMessageResponse {
private final String playerUUID;
private final Consumer<Map<String, Object>> callback;
public PlayerStatsRequest(String playerUUID, Consumer<Map<String, Object>> callback) {
this.playerUUID = playerUUID;
this.callback = callback;
}
@Override
public String getRequestType() {
return "player_stats";
}
@Override
public Map<String, Object> getData() {
Map<String, Object> data = new HashMap<>();
data.put("uuid", playerUUID);
return data;
}
@Override
public void onResponse(Map<String, Object> response) {
callback.accept(response);
}
@Override
public void onError(String error) {
System.out.println("Не удалось получить статистику игрока: " + error);
}
}
// Использование в команде
public void onCommand(Player player) {
PlayerStatsRequest request = new PlayerStatsRequest(
player.getUniqueId().toString(),
stats -> {
player.sendMessage("Ваша статистика:");
player.sendMessage("Убийств: " + stats.get("kills"));
player.sendMessage("Смертей: " + stats.get("deaths"));
}
);
platformManager.sendRequest(request);
}🔗 Расширение бэкенда Pano (рекомендуется)
Для полноценной интеграции настоятельно рекомендуется также создать плагин Pano (на стороне бэкенда), который будет обрабатывать ваши пользовательские запросы и сообщения.
Структура плагина Pano
Создайте плагин в бэкенде Pano для обработки вашей логики интеграции:
// В вашем плагине Pano (бэкенд)
class MyIntegrationPanoPlugin : PanoPlugin() {
override fun onEnable() {
// Регистрация обработчиков запросов
registerRequestHandler("player_join") { data, connection ->
handlePlayerJoin(data, connection)
}
registerRequestHandler("player_stats") { data, connection ->
handlePlayerStatsRequest(data, connection)
}
}
private fun handlePlayerJoin(data: Map<String, Any>, connection: Connection) {
val playerName = data["player"] as String
val uuid = data["uuid"] as String
// Сохранение в БД, вызов событий и т.д.
database.updatePlayerLastJoin(uuid)
// Опциональная отправка ответа
connection.sendResponse("success", mapOf("message" to "Вход зафиксирован"))
}
private fun handlePlayerStatsRequest(data: Map<String, Any>, connection: Connection) {
val uuid = data["uuid"] as String
val stats = database.getPlayerStats(uuid)
// Отправка статистики обратно в MC плагин
connection.sendResponse("player_stats_response", stats)
}
}Отправка сообщений из Pano в Minecraft
Из вашего плагина Pano вы можете отправлять сообщения на подключенные серверы Minecraft:
// Отправка награды игроку
platformManager.sendMessage("player_reward", mapOf(
"playerName" to "Steve",
"reward" to "coins",
"amount" to 100
))Это сообщение будет получено вашим PlayerRewardHandler на сервере Minecraft.
🔒 Лучшие практики безопасности
- Проверяйте все данные — никогда не доверяйте входящим данным без валидации.
- Используйте шифрование Pano — вся связь автоматически шифруется через WebSocket.
- Проверяйте права доступа — убедитесь в наличии прав у пользователя перед выполнением действий.
- Очищайте ввод — предотвращайте инъекционные атаки.
- Ограничение частоты (Rate Limiting) — внедряйте ограничения для частых операций.
- Обработка ошибок — всегда корректно обрабатывайте ошибки.
📦 Пример проекта
Ознакомьтесь с нашим репозиторием примера плагина интеграции:
- Пример плагина интеграции (Эталонная реализация)
Этот репозиторий демонстрирует:
- Настройку зависимости Pano MC Plugin API.
- Создание пользовательских запросов и обработчиков.
- Подключение к сторонним плагинам.
- Лучшие практики и паттерны.
🧪 Тестирование вашей интеграции
Локальное тестирование
- Соберите ваш плагин:
./gradlew build- Скопируйте JAR-файл в папку
plugins/вашего тестового сервера. - Убедитесь, что Pano MC Plugin установлен и подключен.
- Установите ваш целевой плагин.
- Запустите сервер и протестируйте функциональность.
Отладка
Включите логирование отладки в вашем плагине:
if (config.getBoolean("debug", false)) {
logger.info("[Debug] Запрос отправлен: ${request.getRequestType()}")
}📚 Справочник API
Методы PlatformManager
interface PlatformManager {
// Отправить запрос в Pano
fun sendRequest(request: PlatformRequest)
// Зарегистрировать обработчик сообщений
fun registerMessageHandler(handler: PlatformMessageHandler<*>)
// Проверить наличие соединения с Pano
fun isConnected(): Boolean
// Получить статус соединения
fun getConnectionStatus(): ConnectionStatus
}PlatformRequest
abstract class PlatformRequest {
abstract fun getRequestType(): String
abstract fun getData(): Map<String, Any>
}PlatformMessageResponse
interface PlatformMessageResponse {
fun onResponse(response: Map<String, Any>)
fun onError(error: String)
}PlatformMessageHandler
abstract class PlatformMessageHandler<R : PlatformMessage> {
abstract fun handle(message: R)
abstract fun getMessageType(): String
}💬 Нужна помощь?
- Discord: Присоединяйтесь к нашему каналу разработки.
- GitHub Discussions: Обсуждения Pano MC Plugin.
- Трекер ошибок: Сообщайте об ошибках или предлагайте функции.
- Пример проекта: Эталонная реализация.
📚 Связанная документация
Приятного кодинга! 🚀