Как пишутся плагины для майнкрафт

Время на прочтение
6 мин

Количество просмотров 12K

Еще с детства я начал покорять бесконечные просторы Minecraft. Естественно о разработке в то время никакой речи не шло. Но с недавних пор загорелся идеей создать о свой проект серверов.

На Java до этого никогда не писал, но есть бекграунд на других языках, поэтому осталось только приспособиться. Соотвественно разработка плагинов, Bukkit и другие библиотеки вижу впервые, но посмотрев несколько туторов, стала понятна примерная концепция.

Ранее писал на таких языках как PHP, JS. В данный момент веду разработку на языке Go. Сильно привык к «гошке» и его синтаксису и в процессе написания плагина часто использовал синтаксис Go для написания логических конструкций.

Мне не сильно хотелось использовать какие-то готовые решения, ведь тогда не будет углубленных знаний, которые я получу в процессе написания кода. Хочется одновременно и поучить Java и написать что-то свое (самое главное).

В этой статье я не буду затрагивать процесс настройки окружения, установки IDE и стороннего софта.

Идея плагина

На серверах часто используются постройки, находящиеся в пустоте, например летающие лобби, острова. Такую модель постройки мы выбрали вместе с моим другом: летающие острова. Одной из проблем таких построек — Игрок может провалиться в пустоту и не выбраться.

Прошерстив Google мне удалось найти парочку подходящих плагинов, которые уже решают это проблему. Но один из них, который оказался поддерживаем разработчиком и самими ядром сервера, предоставлял ограниченный функционал, расширенный можно было приобрести на X евро. Фича, которая мне понравилась в платном плагине — создание анимаций из частиц после телепортации из пустоты.

Мне захотелось самому понять, как это сделать, разработать собственный плагин, а потом с удовольствием им пользоваться, поддерживать, находить баги — мое мелкое детище, как никак.

Создаем сам плагин

Назвал я плагин просто — VoidTeleport.

Первым делом создал класс для управления конфигурацией плагина.

public class Config {
    private static File file;
    private static FileConfiguration config;

    private static final String fileNameConfig = "config.yml";

    /**
     * Initializes the static Config class.
     */
    public static void init() {
        // Получаем инстанс нашего плагина.
        Plugin plugin = Bukkit.getServer().getPluginManager().getPlugin(VoidTeleport.PluginName);
        if (plugin == null) {
            // На этом моменте что-то пошло не так, 
          	// нужно обработать и залогировать.
            Bukkit.getLogger().log(
                    Level.WARNING,
                    MessageFormat.format("Cannot get plugin {0}", VoidTeleport.PluginName)
            );
            return;
        }

        file = new File(plugin.getDataFolder(), fileNameConfig);

        // Мы не знаем существует ли файл, поэтому пытаемся создать его.
      	// Если файл уже есть, то выражение file.createNewFile() вернет false. 
        try {
            if (file.createNewFile()) {
                plugin.getLogger().log(
                        Level.INFO,
                        MessageFormat.format("New config file with name {0} was created", fileNameConfig)
                );
            }
        } catch (IOException e) {
            plugin.getLogger().log(Level.SEVERE, e.toString());
            return;
        }

        // На данно моменте наш конфиг пустой, 
      	// поэтому подгружаем его из файла.
        reload();
    }

    /**
     * Getter
     * @return FileConfiguration
     */
    public static FileConfiguration get() {
        return config;
    }

    public static void reload() {
      	// Самый простой анмаршаллер YAML из файла.
        config = YamlConfiguration.loadConfiguration(file);
    }

Отлично! Класс для работы с конфигом уже есть, теперь нужно определиться со структурой файла config.yml. Нужно реализовать поддержку для разных миров, поэтому не придумал ничего проще, как просто указать список нужных миров.

worlds:
    # Наименование мира, например spawn, world, world_the_end
  - name: spawn
  	# Координаты для респавна игрока при падении в пустоту
    spawnLocation:
      x: 0
      y: 0
      z: 0

Конфиг есть, теперь можно приступить к созданию обработчика событий. Мой выбор пал на событие EntityDamageByBlockEvent. Можно было бы и слушать событие PlayerMoveEvent, но оно случается гораздо чаще, чем триггер на получение урона. Лишняя нагрузка на сервер не нужна, поэтому стал слушать урон.

public class PlayerDamageListener implements Listener {
  	// Хеш мапа в которой хранится наименования мира и точка телепортации.
    private HashMap<String, Location> worlds = new HashMap<>();

    @EventHandler
    public void onPlayerDamage(EntityDamageByBlockEvent e) {
        if (!(e.getEntity() instanceof Player)) {
            // Это не игрок.
            return;
        }

        if (e.getCause() != EntityDamageEvent.DamageCause.VOID) {
            // Урон не от пустоты.
            return;
        }

        Player player = (Player) e.getEntity();

      	// Получаем мир, в котором находится Игрок.
        World world = player.getWorld();
      	
      	// Пытаемся найти в хеш мапе значение по наименованию мира.
        Location spawnLocation = this.worlds.get(world.getName());
        if (spawnLocation == null) {
            // К этому миру не действует правило телепорта.
            return;
        }

        // Данный код является костылем, который я быстро сообразил.
      	// Проблема в том, что мир может быть = null.
      	// В таком случае устанавливаем мир на тот, в котором находится игрок.
        if (spawnLocation.getWorld() == null) {
            spawnLocation.setWorld(world);
        }

        // Добрались до самого главного.
      	// Отменяем событие, которое наносит урон игроку.
        e.setCancelled(true);

        // Отменяем сам урон от падения, 
				// чтобы при телепортации игрок не разбился.
        player.setFallDistance(0);

        // Телепортируем игрока.
        player.teleport(spawnLocation);

        // Доабвляем анимацию из частиц при попадании на точку телепортации.
        Spiral.spawn(player);
    }

    @SuppressWarnings("unchecked")
    public void updateWorlds(@Nullable ArrayList<HashMap<String, Object>> listWorlds) {
        if (listWorlds == null) {
          	// Ну если null, так null - ничего не делаем.
            return;
        }

        // Очищаем мапу.
        this.worlds = new HashMap<>();

        for (HashMap<String, Object> world: listWorlds) {
            String worldName = (String) world.get("name");
            if (Objects.equals(worldName, "")) {
                // Тут хорошо бы залогировать, но просто скипаем.
                continue;
            }

            Location spawnLocation = Location.deserialize((Map<String, Object>) world.get("spawnLocation"));
            
          	// Т.к. мир у нас не указан, поэтому получаем его.
          	spawnLocation.setWorld(Bukkit.getWorld(worldName));

            // Сохраняем в хеш мапу.
            this.worlds.put(worldName, spawnLocation);
        }
    }
}

Тепер разберем вызов эффекта анимации при телепортации Spiral.spawn(player). Назвал класс Spiral, потому что эффект будет в виде спирали.

Т.к. это мой первый плагин, то не стал заморачиваться с Пакетами и ProtocolLib.

Описываем анимацию в отдельном классе Spiral. Я предпочел реализовать спираль под названием Helix — достаточно простая в реализации модель. Пришлось немного вспомнить тригонометрию, но у меня получилось!

public class Spiral {
    public static void spawn(@NotNull Player player) {
        Location location = player.getLocation();
				
      	// Задаем радиут спирали.
        double radius = 0.5;
				
        for (double y = 0; y <= 23; y += 0.1) {
            double x = radius * Math.cos(y);
            double z = radius * Math.sin(y);

            Location particleLocation = new Location(location.getWorld(), location.getX(), location.getY(), location.getZ());
            player.spawnParticle(Particle.REDSTONE, particleLocation.add(x, y / 10, z), 2, new Particle.DustOptions(Color.AQUA, 1.0F));

            try {
              	// Думаю, что это плохо, но для первого раза сойдет.
                TimeUnit.NANOSECONDS.sleep(1);
            } catch (InterruptedException e) {
                Bukkit.getLogger().log(Level.SEVERE, e.toString());
            }
        }
    }
}

Почему в коде 23? Это число является ограничением для координаты y. Т.е. по сути спираль будет подниматься вверх на y = 2.3. Как можно заметить, при указании смещения particleLocation.add(x, y / 10, z) y делится на 10. Еще одной причиной стало то, что спираль не успевает несколько раз «обернуть» игрока.

Собираем все вместе

Наконец можем собрать наш код в единой точке и протестировать, что получилось.

public final class VoidTeleport extends JavaPlugin {
    public static final String PluginName = "VoidTeleport";

    @Override
    public void onEnable() {
        getLogger().log(Level.INFO, "Plugin enabled!");

        // Инициализируем конфиг
        Config.init();

      	// Регистрируем обработчик событий для входщего урона
        this.registerDamageEvent();
    }

    @Override
    public void onDisable() {
        getLogger().log(Level.INFO, "Plugin disabled!");
    }

    @SuppressWarnings("unchecked")
    private void registerDamageEvent() {
      	// Инициализируем обработчик
        PlayerDamageListener damageListener = new PlayerDamageListener();
      
      	// Достаем из конфига нужные значения и обновляем хеш мапу в обработчике
        damageListener.updateWorlds((ArrayList<HashMap<String, Object>>) Config.get().get("worlds"));
				
      	// Регистрируем новое событие на сервере
        getServer().getPluginManager().registerEvents(damageListener, this);
    }
}

Результат

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

И без указания мира в конфиге.

TODOIcon.png

Working on it…

This page is a work in progess, check back regularly for more content!

Вступление

Статья базируется на уроке от Adamki11s, в нём содержится достаточно большое количество примеров.

После изучения данной статьи мы рекомендуем Вам ознакомиться с дополнительными материалами автора.

Перевод на русский делают ZZZubec и fromgate. Дополняет перевод:D_ART, Xipxop, а также snaypak. Контроль за орфографией, пунктуацией, грамотностью изложения и викифицированием — pashak.

Учим Java

Для создания модов Вам потребуются хотя бы базовые знания языка программирования Java. Для новичков советуем Вам пройтись по следующим ресурсам:

Видеоуроки

  • JavaVideoTutes.com — основы Java.
  • devcolibri.com — Основы Java
  • GTOTechnology — создание плагинов для Bukkit.
  • Dinnerbone — канал одного из основателей Bukkit.
  • Thenewboston — много полезного.

Текстовые учебники

  • Oracle Documentation — официальная документация Java.
  • Java2s.com — удобный справочник Java.
  • Java 101 — краткий учебник Java.

Изучение Java дастся Вам гораздо легче, если вы знаете другие языки програмирования. Также в изучении этого языка могут помочь следующие издания:

  • Герберт Шилдт — «Полный справочник по Java SE6»
  • Брюс Эккель — «Философия Java»

Java IDE

Минимумом для разработки на Java вам являются лишь текстовый редактор и компилятор. Однако гораздо удобнее использовать интегрированную среду разработки — IDE (Integrated Development Environment). Это целый комплекс программных средств, позволяющий писать, компилировать и отлаживать написанные программы.

Наиболее популярными средами для разработки на Java являются:

  • Eclipse
  • Netbeans
  • IntelliJ IDEA.

Eclipse — самая популярная среда, ей пользуется большинство пользователей Bukkit. Если вы новичок в Java, то Eclipse предпочтительнее, так как данная статья написана на примерах именно этой программы. Рекомендуемую версию Eclipse можно взять отсюда.

По мнению автора лучшим руководством для Eclipse является эта статья (англ.). Руководство написано для версии 3.3, но, скорее всего, подойдёт и к более новым версиям.

Также есть руководство (англ.) и для IntelliJ, используемой преимущественно для разработки игр на Java.

Начинаем проект плагина

Создание проекта

Перед тем как мы начнем создавать новый плагин не забудьте настроить «переменные окружения» в Eclipse. Запустите Eclipse и нажмите  File > New > Java Project:

NewJavaProject.png

Имя проекта может быть каким хотите (но я рекомендую в любые названия вкладывать смысл). Далее следуйте инструкциям. В Package Explorer в левой панели будет отображаться весь проект целиком. ЛКМ на плюсе возле папки с названием проекта, раскроет весь проект и Вы сможете посмотреть все файлы которые относятся к нему.

Добавление Bukkit API к нашему плагину

Перед тем как мы начнем разработку нового плагина, нам необходимо добавить внешнюю библиотеку баккита, в виде JAR. Вы также можете добавить и другие плагины или библиотеки таким же способом.

Последнюю текущую версию баккит сервера Вы можете найти здесь: Bukkit API — Development Snapshot

Кликните «Правой кнопкой мыши» (ПКМ) по нашему проекту и нажмите на Свойства. Далее нажмите на Java Build Path в левой части и в правой части перейдите на закладку Libraries. Нажмите на кнопку Add External JARs, чтобы выбрать jar баккит сервера который Вы скачали.

BuildPathPic.png

Подключение справки Bukkit для Eclipse

Если Вы обладаете уже опытом работы с Eclipse и Java, то вы наверняка знаете что при наведении мыши на какую либо команду или класс, всплывает подсказка, которая очень помогает при написании кода. Для подключения справки нам понадобиться интернет соединение. Баккит также содержит справку для разработчиков, которая интегрируется в Eclipse. Для того чтобы её подключить необходимо нажать ПКМ по Bukkit jar файлу (который был добавлен в проект), затем выберем в панели слева Javadocs Location, затем справа в адресе укажем «http://jd.bukkit.org/apidocs/»(без кавычек):

Bukkitjavadocs.png

Нажмите validate (для проверки), а затем OK. Вот и всё! Теперь справка поможет нам в дальнейшем.

Начинаем создавать ваш плагин

Сейчас вы должны создать ‘package’ (или ‘пакет’ как я буду называть его далее) в котором будут храниться все файлы классов Java. Правой кнопкой по папке «src» и выберите New > Package:

MakePackage.png

Имя вашего ‘пакета’ должно быть таковым: «me.yourname.pluginname» — там где ‘yourname’ это название пакета и оно не должно содержать Русских букв и слова bukkit.  Например: Если ваш плагин называется «TestPlugin», то ‘пакет’ должен называться так: «me.adamki11s.TestPlugin».

Теперь наш проект создан, мы можем добавлять файлы классов и начать делать наш плагин. Правой кнопкой по папке «src» и выберите  New > Class. Главный класс должен иметь то же имя, как и плагин! Например: Плагин с именем «TestPlugin», то первый класс должен называться так «TestPlugin».

Сейчас вы создали основной файл и ваш проект. Чтобы bukkit видел ваш плагин, надо добавить plugin.yml файл. Этот файл содержит важную информацию, без него плагин работать не будет! Нажмите правой кнопкой по папке проекта(Не ‘src’ , а та что самая первая). Выберете New > File. Назовите файл так: «plugin.yml«. По умолчанию Eclipse откроет файл plugin.yml в блокноте (Подсказка:  Если вы хотите держать ваше рабочее пространство упорядоченным закройте текстовый редактор и перетащите plugin.yml в главное рабочее пространство (Справа) и вы сможете редактировать файл внутри eclips.) Вот две вещи которые надо добавить; ссылку на ваш главный класс и команды.          

Простой пример plugin.yml  выглядит вот так :

name: <PluginName>
main: <packagename>.<PluginName>
version: <Version Number>

Внимание: Имя пакета часто включает в себя имя плагина, так что не удивляйтесь, если вы видите <pluginname>.<pluginname> в конце второй строки!

onEnable() и onDisable()

Эти функции вызываются когда плагин enabled/disabled (Включен/выключен). По умолчанию плагин будет включаться при запуске сервера и вы можете настроить сообщение которое пишет в консоле при запуске плагина. onEnable() это первое, что выполняется в плагине.

Введение: onEnable() и onDisable()

Создайте методы onEnable () и onDisable () внутри основного класса, созданного в предыдущем разделе:

public void onEnable(){ 

}

public void onDisable(){ 

}

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

 На следующее:

Class <classname> extends JavaPlugin {}

После добавления этого кода вы увидите красные линии под ним, говорящие что, что-то не так. Чтобы исправить это, просто наведите курсор мыши на выделенный код и нажмите правую кнопку мыши Import 'JavaPlugin' (org.bukkit.plugin.java).

Import JavaPlugin.png

 Вывод сообщений при помощи Logger  

Сейчас мы напишем код, который будет выводить сообщения в консоле о статусе плагина (1 раз) Включен/выключен. Сначала мы должны получить объект регистратора, который позволит нам послать выходные данные на консоль. Для этого классе JavaPlugin имеется метод getLogger(). Для удобства можно объявить специальную переменную:

Logger log = getLogger();

Затем внутри onEnable() мы будет отображаться сообщение о том, плагин был включен:

log.info("Your plugin has been enabled.");

Вы можете сделать тоже самое с onDisable(), убедившись, что изменили сообщение. Ваш главный класс должен выглядеть как-то так::

package me.<yourname>.<pluginname>;

import java.util.logging.Logger;
import org.bukkit.plugin.java.JavaPlugin;

public class <classname> extends JavaPlugin {

	Logger log = getLogger();

	public void onEnable(){
		log.info("Your plugin has been enabled!");
	}

	public void onDisable(){
		log.info("Your plugin has been disabled.");
	}
}

Слушатели событий (хуки)

Слушатели событий или хуки, фактически будут самой большой часть в ваших плагинах. Всё что бы не происходило с игроками на сервере можно отловить через них. Игрок поставил блок или игрок убил какого-то монстра, или что-то другое — это всё отлавливается через хук (событий). Перед тем как вы создадите такой хук, Вам надо зарегистрировать его на стороне плагина. Хорошим местом для регистрации хуков, будет то же место где Ваш плагин подключается к серверу (OnEnable). Когда вы зарегистрируете событие(хук), сервер уже будет знать что когда произойдет событие, он оповестит Ваш плагин о нём. Довольно стандартная модель регистрации событий.

Добавление слушателей(хуков)


Информация неактуальна! Сейчас используется упрощенная система слушателей событий!


Чтобы зарегистрировать само событие, нам понадобится Plugin Manager, его можно получить через «окружение».

PluginManager pm = this.getServer().getPluginManager();

Возможно после того как вы запишите этот код, появится красное подчеркивание на слове PluginManager, этого из-за того что мы ещё не импортировали этот класс. Для устранения данной ошибки достаточно поставить курсор на подчеркнутое слово и нажать правой кнопкой мыши. И выбрать импорт(import) класса.

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

pm.registerEvent(Event.Type.PLAYER_MOVE, playerListener, Event.Priority.Normal, this);

Важно! Достаточно одного файла слушателя на все события одного типа. Например чтобы отлавливать все события игрока, мы должны их зарегистрировать. А затем создать всего один файл слушателя, и уже в нём написать реакцию (функции) на каждое зарегистрированное событие игрока. И по скольку игра не разделяет одного игрока от другого, получается, что мы получим слушатель одновременно на всех игроков и на каждое их событие.Кроме того, нам не обязательно выносить хуки в отдельный файл, при их малом налиичи можно их просто описать в этом же файле (плагина) просто добавив именные функции внутри класса и добавив implements PlayerListener нашему класса плагина.Также я решил отойти(в некоторых местах) от стандартного перевода в хуках, чтобы русскоязычному населению было более понятно о возможностях, которые дают хуки.

Посмотрите на шаблон

private final <YourPluginName>PlayerListener playerListener = new <YourPluginName>PlayerListener(this);

Допустим мы возьмем имя Basic для файла слушателя, тогда запишем вот так:

private final BasicPlayerListener playerListener = new BasicPlayerListener(this);

Эту часть кода необходимо внести в класс плагина(основного)

В таком случае, создаем новый класс с именем(имя на самом деле произвольное, главное чтобы не было пересечений с другими именами) BasicPlayerListener, и за основной класс возьмем PlayerListener. Не забывайте добавлять необходимые импорты классов, чтобы не было красных подчеркиваний. Должно получится вот так:

public class BasicPlayerListener extends PlayerListener{ //Code here }

Теперь Вам необходимо создать функцию которая и будет обработчиком события (хука). Но перед этим создадим переменную которая будет хранить ссылку(индентификатор) на основной класс плагина.

MyPlugin необходимо заменить на имя нашего класса плагина (основного). И запишем в основную функцию класса слушателя передачу(присвоение, получение) этого индентификатора.

public My plugin;

public BasicPlayerListener(MyPlugin instance) {
    plugin = instance;
}

Чтобы правильно обработать событие, Вы должны создать функцию

public void onPlayerMove(PlayerMoveEvent event){
//do whatever you want to happen when a player moves here
}

Баккит автоматически будет вызывать её после того как событие произошло. Поэтому старайтесь не ошибиться в имени функции Для примера (пример не удачный, поправил), событие EntityDamage, будет иметь название функции onEntityDamage( EntityDamageEvent event) также внутри функции можно получить доступ к тому кто «ударил», через

Event e = event.getDamager();

Отсюда да же, можно получить координаты как самого Entity (вплоть до отдельных координат X,Y,Z), так и того кто ударил, через

И помните, что каждому типу событий, необходим свой слушатель. На данный момент эта информация не актуальна, используется общий слушатель Listener

для блоков — BlockListener

дла сущностей — EntityListener

для игрока — PlayerListener

Спасибо за то что читаете, мы все стараемся передать вместе с переводом частичку своих знаний

Команды

Метод onCommand()

Теперь Вы знаете как использовать события, но что если Вам нужно обрабатывать команды, посланные игроком? Для этого используется onCommand. Этот код вызывается когда игрок пишет в чат команду, начиная с символа «/». Например ввод «/do something» вызовет код onCommand. В данном случае при вызове ничего не произойдёт т.к. нужно запрограммировать окружение.

Избегайте использования названий команд, используемых стандартым сервером. Так же необходимо следить за уникальностью названия команды. Например: команда «give» уже используется некоторыми плагиными и добавление ещё одной команды с таким же именем сделает ваш плагин несовместимым с этими плагинами.

Метод

всегда должен возвращать булево значение — true или false. Если возвращаемое значение true, то это условно означает о том, что команда выполнена успешно и вызвавшему само по себе ничего не отобразится. Если же возвращаемое значение фелсе, то плагин обратится к параметру команды ‘usage:’ и выведет содержимое этого параметра пользователю команды, взятое из файла plugin.yml.
При использовании метода

необходимо зарегистрировать 4 параметра:

  • '''CommandSender sender'''
    

    — отправитель команды

  • — команда, которая была использована
  • '''String commandLabel'''
    

    — псевдоним команды, который был использован

  • — массив аргументов команды, например: /hello abc def вернёт массив с abc находящимся в args[0] и def находящимся в args[1], но если значение массива будет не верным — не вернет массив с abc

Установка команд

public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){
	if(cmd.getName().equalsIgnoreCase("basic")){ // Если игрок ввёл /basic тогда делаем следующее...
		делаемЧто-то
		return true;
	} //Если это произошло, то возвращаем true, иначе функция вернёт false
	return false; 
}

При написании метода

хорошим тоном считается возвращать false в самом конце метода. Возврат false отобразит пользователю команды параметр использования из plugin.yml (смотри выше). В этом случае, если что-то пошло не так, пользователю выведет сообщение-подсказку. Когда метод возвращает значение, любой код, находящийся ниже него, не будет выполнен, за исключением случаев, когда команда return расположена в закрытом условии и не будет достигнута про исполнении кода.
Код

'''.equalsIgnoreCase("basic")'''

означает, что при сравнении строк не будет различий между верхним и нижним регистром. Например строки «BAsIc» и «BasiC» обе эквивалентны basic и код в условии будет воспроизведён.

Добавляем команду в plugin.yml

Вы также должны добавить команду в plugin.yml файл. Добавьте следующее в конец plugin.yml:

commands:
   basic:
      description: Пример команды
      permission: <plugin name>.basic
      usage: /<command> [player]

  • basic — Название команды
  • description — Описание команды.
  • permission — This is used by some help plugins to work out which commands to show to the user.
  • usage — Сообщение которое будет отправлено игроку если метод onCommand вернёт false. Пишите так чтобы игрок смог понять как эту команду использовать

Консольные команды против команд Игроков

Вы, возможно, заметили выше параметр CommandSender sender. CommandSender является интерфейсом Bukkit, у которого есть две полезные (для писателей плагинов) реализации Player и ConsoleCommandSender (чтобы быть точным, Player тоже является интерфейсом).

When you’re writing your plugin, it’s a very good idea to ensure that commands that can be run from the console actually work, and that commands that should only be run as a logged-in player really are only run as a logged-in player. Some plugins simply return if the sender is not a player (i.e. someone tried to use the plugin’s commands from the console), even when those commands make perfect sense from the console (e.g. changing the weather on the server).

One way to do this is:

public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
	Player player = null;
	if (sender instanceof Player) {
		player = (Player) sender;
	}

	if (cmd.getName().equalsIgnoreCase("basic")){ // If the player typed /basic then do the following...
		// do something...
		return true;
	} else if (cmd.getName().equalsIgnoreCase("basic2") {
		if (player == null) {
			sender.sendMessage("this command can only be run by a player");
		} else {
			// do something else...
		}
		return true;
	}
	return false;
}

In this example, the command basic can be run by anyone — a logged-in player, or the server operator on the console. But the command basic2 can only be run by logged-in players.

In general, you should allow as many commands as possible to work on both the console and for players. Commands that need a logged-in player can use the mechanism in the example above to check that the CommandSender is actually a player before continuing. Such commands would generally depend on some attribute of the player, e.g. a teleportation command needs a player to teleport, an item giving command needs a player to give the item to…

If you want to get more advanced, you could do some extra checks on your command arguments so that e.g. a teleportation command could be used from the console if and only if a player’s name is also supplied.

Использование отдельного класса CommandExecutor

В приведённом выше примере мы просто вставили метод onCommand() в главный класс плагина. Для небольших плагинов, это прекрасно, но если вы пишете что то большое, возможно, имеет смысл разместить метод onCommand() в своём отдельном классе. К счастью, это не слишком сложно:

  • Создайте новый класс в пакете вашего плагина. Назовите его MyPluginCommandExecutor (хотя, конечно, можете заменить MyPlugin на реальное имя вашего плагина). Этот класс должен реализовать интерфейс Bukkit CommandExecutor.
  • В методе onEnable () вашего плагина, вы должны создать экземпляр нового класса для исполнителя команды, а потом вызвать его при помощи getCommand («basic») setExecutor (myExecutor)., Где «basic» команда, которую мы хотим выполнить и myExecutor экземпляр который мы создали. 

Лучше объяснить на примере:  

MyPlugin.java (главный класс плагина):  

private MyPluginCommandExecutor myExecutor;
@Override
public void onEnable() {
	// ....

	myExecutor = new MyPluginCommandExecutor(this);
	getCommand("basic").setExecutor(myExecutor);

	// ...
}

MyPluginCommandExecutor.java:

public class MyPluginCommandExecutor implements CommandExecutor {

	private MyPlugin plugin;

	public MyPluginCommandExecutor(MyPlugin plugin) {
		this.plugin = plugin;
	}

	@Override
	public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
		// ... implementation exactly as before ...
	}
}

Обратите внимание, как мы передаем экземпляр из основного класса в конструктор MyPluginCommandExecutor. Это позволяет легко добраться до основных методов плагина.

Используя эту возможность, мы можем лучше организовывать свой код — если основной метод OnCommand() у вас стал большим и сложным, вы можете разделить его на под методы которые не загромождают основной класс плагина. Обратите внимание, что если ваш плагин имеет несколько команд, то вам нужно создавать исполнителя для каждой команды отдельно.  

Конфигурация/Настройки плагина  

Часто бывает так, что вашему плагину требуется переменная для сохранения, когда сервер выключен или выключается, как правило, какие-то настройки или имущество. Это достигается при помощи конфигурации, для этого Bukkit предоставляет свою встроенную функцию.   

Получаем конфигурацию для плагина

Bukkit automatically creates a Configuration object which is associated with your plugin. In order to work with it you will need to import the Configuration class and retrieve a reference to the object by calling the getConfiguration() method in your main plugin class, as shown below:  

package com.test.testplugin;

import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.util.config.Configuration;

public class TestPlugin extends JavaPlugin {
	protected FileConfiguration config;

	public void onEnable() {
		config = getConfig();
	}

	public void onDisable() {

	}
}

Here the variable config is the object which you will work with to set, get, save, and load settings and properties related to your plugin.

Настройка переменных конфигурации  

Now that we have our Configuration object, we can begin to save our properties and settings to it. Bukkit Configuration objects can handle many different types of variables, such as ints, booleans, and Strings (as well of lists of all of these). In order to set a property, you will need to use the following code:

public void onEnable() {
	this.getConfig().set("your boolean property", true);
	this.getConfig().set("your string property", "yes");
	this.getConfig().set("your int property", 22);
}

This code will create a config.yml which looks like this:

your boolean property: true
your string property: 'yes'
your int property: 22

You will notice that there is only one method which is used to set a property. The kind of property that will be saved is determined by the actual object you pass to the setProperty() method, so make sure it is of a type that Configuration objects can handle.

Bukkit Configurations also provide support for categories. In order to create categories for properties, simply put a decimal point as a seperator between the category and the property itself. An example use of this could be a situation where there is a different category for each player, where variables are set to different values for each different player. For example, you could use the following code to set the value of a few variables under the category of a particular player:

public void onEnable() {
	this.getConfig().set("player.damaged", true);
	this.getConfig().set("player.name", "LOL!");
	this.getConfig().set("player.damage", 22);
}

This will create a config.yml for your plugin that will look like this:

player:
    damaged: true
    name: LOL!
    damage: 22

Получение переменных конфигурации  

Получение переменных конфигурации достигается за счет использования различных методов получения, как показано здесь:

public void onEnable() {
	this.getConfig().set("your boolean property", true);
	this.getConfig().set("your string property", "yes");
	this.getConfig().set("your int property", 22);
	
	boolean bProp = this.getConfig().getBoolean("your boolean property", false);
	String sProp = this.getConfig().getString("your string property", "no");
	int iProp = this.getConfig().getInt("your int property", 0);
}

The first argument passed is the particular property that is being looked for, while the second argument is the default value to return if that property is not found in the configuration.

To get a property from a category, simply prepend the category name to the property you wish to retrieve and seperate the two with a decimal point, as such:

public void onEnable() {
	this.getConfig().set("player.damaged", true);
	this.getConfig().set("player.name", "LOL!");
	this.getConfig().set("player.damage", 22);

	boolean isDamaged = this.getConfig().getBoolean("player.damaged", false);
	String playerName = this.getConfig().getString("player.name", "no");
	int damage = this.getConfig().getInt("player.damage", 0);
}

Сохранение и загрузка с диска

Есть только две команды, которые вы должны знать, чтобы сохранять и загружать настройки с диска:

getConfig(); // получение конфигурации, если она не была загружена, загружается с жесткого диска
reloadConfig();; // перезагрузка конфигурации на диске 
saveConfig(); // сохранение конфигурации на диск

Usually, you would load the configuration on the plugin enable, and save at least once on plugin disable if anything was worth saving.

It is important to remember to call the save() method somewhat more frequently, in order to make sure that any properties that have been set are not forgotten on the termination of the server. You might think you only need to call this in onDisable(), but remember, if the server crashes before your plugin can properly close, any settings that haven’t been saved will be lost. By default, configuration files will be saved in the file plugins/YourPlugin/config.yml, but you can use the Configuration class to read/write any file. Example:

File confFile = new File(plugin.getDataFolder(), "anotherfile.yml");
FileConfiguration conf = YamlConfiguration.loadConfiguration(configFIle);
conf.setProperty("player.name", "Herobrine");
// etc.
conf.save(confFile);

That will create a new file called plugins/YourPluginName/anotherfile.yml. You should avoid creating/modifying any files outside your own plugin’s data folder (which you can get via the getDataFolder() call above) unless you have a very good reason.

 Разрешения (Permissions)  

With the new Bukkit API for permissions, they couldn’t be easier. To find out if a player has a particular permission use the following:  

if(player.hasPermission("some.pointless.permission")) {
   //Do something
}else{
   //Do something else
}

You can also find if a permission has been set or not (equivalent to Java’s null) with the following function:

boolean isPermissionSet(String name)

You may be wondering why there aren’t any groups. The answer to that is because they aren’t really needed. Previously one of the main uses for groups was to format chat messages. That however can be done just as easily with permissions. Inside your chat plugin’s config you would define associations between permissions and prefixes. For example the permission «someChat.prefix.admin» would correspond to the prefix [Admin]. Whenever a player speaks with that permission their name will be prefixed with [Admin].

Another common usage might be to send a message to all users within a group. Again however this can be done with permissions with the following:

for(Player player: getServer().getOnlinePlayers()) {

    if(player.hasPermission("send.me.message")) {
        player.sendMessage("You were sent a message");
    }

}

Finally you may be asking, well how do I set and organise player’s permissions if there are no groups? Although the bukkit API doesn’t provide groups itself, you must install a permission provider plugin such as permissionsBukkit to manage the groups for you. This API provides the interface, not the implementation.

Настройка разрешений  

If you want more control over your permissions, for example default values or children then you should consider adding them to your plugin.yml. This is completely optional, however it is advised. Below is an example permissions config that would be appended to the end of your existing plugin.yml:

permissions:
    doorman.*:
        description: Gives access to all doorman commands
        children:
            doorman.kick: true
            doorman.ban: true
            doorman.knock: true
            doorman.denied: false
    doorman.kick:
        description: Allows you to kick a user
        default: op
    doorman.ban:
        description: Allows you to ban a user
        default: op
    doorman.knock:
        description: Knocks on the door!
        default: true
    doorman.denied:
        description: Prevents this user from entering the door

Firstly, each permission your plugin uses is defined as a child node of the permissions node. Each permission can then optionally have a description, a default value, and children.

Defaults

By default when a permission isn’t defined for a player hasPermission will return false. Inside your plugin.yml you can change this by setting the default node to be one of four values:

  • true — The permission will be true by default.
  • false — The permission will by false by default.
  • op — If the player is an op then this will be true.
  • not op — If the player is not an op then this will be true.

Children

Before now you will probably be used to the * permission to automatically assign all sub permissions. This has changed with the bukkit API and you can now define the child permissions. This allows for a lot more flexibility. Below is an example of how you do this:

permissions:
    doorman.*:
        description: Gives access to all doorman commands
        children:
            doorman.kick: true
            doorman.ban: true
            doorman.knock: true
            doorman.denied: false

Here the doorman.* permission has several child permissions assigned to it. The way child permissions work is when doorman.* is set to true, the child permissions are set to their values defined in the plugin.yml. If however doorman.* was set to false then all child permissions would be inverted.

Создание собственных разрешений  

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

Scheduling Tasks and Background Tasks

Currently, Minecraft servers operate nearly all of the game logic in one thread, so each individual task that happens in the game needs to be kept very short. A complicated piece of code in your plugin has the potential to cause huge delays and lag spikes to the game logic, if not handled properly.

Luckily, Bukkit has support for scheduling code in your plugin. You can submit a Runnable task to occur once in the future, or on a recurring basis, or you can spin off a whole new independent thread that can perform lengthy tasks in parallel with the game logic.

There is a separate Scheduler Programming tutorial which introduces the Scheduler, and gives more information on using it to schedule synchronous tasks, and on kicking off asynchronous tasks in Bukkit.

Операции над блоками

Самый простой способ для создания или измения блоков. Для начала мы изменим блок находящийся над игроком (вверху) в 5ом блоке от позиции игрока. Для этого мы будем использовать событие PlayerMove (оно срабатывает каждый раз когда позиция игрока изменяется).

public void onPlayerMove(PlayerMoveEvent evt) {
    	Location loc = evt.getPlayer().getLocation();
    	World w = loc.getWorld();
    	loc.setY(loc.getY() + 5);
    	Block b = w.getBlockAt(loc);
    	b.setTypeId(1);
    }

Давайте разберем код. В начале мы берем позицию игрока, затем получаем «мир», который нам потребуеться для последующих операций над блоками. Далее мы берем текущую позицию игрока по высоте и увеличиваем её на 5ть. Затем мы получаем информацию о текущем блоке через команду w.getBlockAt(loc); В конце мы меняем ID блока на 1 (камень). Также мы можем изменить и его дополнительные свойства, вы можете добавить новую строку b.setData((byte)3); сразу после строки b.setTypeId(1); Как видите дополнительные свойства должны быть указанны числом с типом byte (8 bit).

Также мы рассмотрим довольно простой алгоритм для конструирования.

public void generateCube(Location point, int length){  // public visible method generateCube() with 2 parameters point and location
	World world = point.getWorld();

	int x_start = point.getBlockX();     // Set the startpoints to the coordinates of the given location
	int y_start = point.getBlockY();     // I use getBlockX() instead of getX() because it gives you a int value and so you dont have
                                                to cast it with (int)point.getX()
	int z_start = point.getBlockZ();

	int x_lenght = x_start + length;    // now i set the lenghts for each dimension... should be clear.
	int y_lenght = y_start + length;
	int z_lenght = z_start + length;

	for(int x_operate = x_start; x_operate <= x_lenght; x_operate++){ 
		// Loop 1 for the X-Dimension "for x_operate (which is set to x_start) 
		//do whats inside the loop while x_operate is 
		//<= x_length and after each loop increase 
		//x_operate by 1 (x_operate++ is the same as x_operate=x_operate+1;)
		for(int y_operate = y_start; y_operate <= y_lenght; y_operate++){// Loop 2 for the Y-Dimension
			for(int z_operate = z_start; z_operate <= z_lenght; z_operate++){// Loop 3 for the Z-Dimension

				Block blockToChange = world.getBlockAt(x_operate,y_operate,z_operate); // get the block with the current coordinates
				blockToChange.setTypeId(34);    // set the block to Type 34
			}
		}
	}
}

Этот код построит 3д куб или кубоид с необходимым размером (его необходимо вызвать принудительно в удобном для вас месте). Если же вам необходимо очистить блоки, то используйте значение ID равным 0 (он же блок воздуха)

Управляем инвентарем

Inventory manipulation might seem a little hard at first, but it’s really easy once you get the hang of it. This section mostly covers player inventory manipulation, but the same applies to chest inventory manipulation as well if you find out how to get a chest’s inventory :P. Here is a simple example of inventory manipulation:

public void onPlayerJoin(PlayerJoinEvent event) {
    Player player = event.getPlayer(); // Игрок, вошедший на сервер
    PlayerInventory inventory = player.getInventory(); // Инвентарь игрока "player"
    ItemStack diamondstack = new ItemStack(Material.DIAMOND, 64); // Стак алмазов
        
    if (inventory.contains(diamondstack)) { // А-ля условие на владение алмазами, если в инвенторе есть стак алмазов, то...
        inventory.addItem(diamondstack); // + 64 алмаза в инвентарь игроку "player"
        player.sendMessage(ChatColor.GOLD + "Welcome! You seem to be reeeally rich, so we gave you some more diamonds!");
        // оповещение игрока о добавлении в инвентарь 64 алмазов (текст золотого цвета).
    }
}

So inside onPlayerJoin we first make a few variables to make our job easier: player, inventory and diamondstack. Inventory is the player’s inventory and diamondstack is a ItemStack that has 64 diamonds. After that we check if the player’s inventory contains a stack of diamonds. If the player has a stack of diamonds, we give him/her another stack with inventory.addItem(diamondstack) and send a golden message. So inventory manipulation isn’t actually that hard, if we wanted we could remove the stack of diamonds by simply replacing inventory.addItem(diamondstack) with inventory.remove(diamondstack) and change the message a little bit. Hopefully this helped!
inventory.remove(diamondstack) — это тоже учти.

HashMaps and How to Use Them

When making a plugin you will get to a point where just using single variables to state an event has happened or a condition has been met will be insufficient, due to more than one player performing that action/event.

This was the problem I had with one of my old plugins, Zones, now improved and re-named to Regions. I was getting most of these errors because I didn’t consider how the plugin would behave on an actual server with more than one on at any given time. I was using a single boolean variable to check whether players were in the region or not and obviously this wouldn’t work as the values for each individual player need to be separate. So if one player was in a region and one was out the variable would constantly be changing which could/would/did cause numerous errors.

A hashmap is an excellent way of doing this. A hashmap is a way of mapping/assigning a value to a key. You could setup the hashmap so that the key is a player and the value could be anything you want, however the useful things with hashmaps is that one key can only contain one value and there can be no duplicate keys. So say for example I put «adam» as the key and assigned a value of «a» to it. That would work as intended, but then say afterwards I wanted to assign the value of «b» to key «adam» I would be able to and would get no errors but the value of «a» assigned to key «adam» in the hashmap would be overwritten because Hashmaps cannot contain duplicate values.

Defining a HashMap

public Map<Key, DataType> HashMapName = new HashMap<Key, Datatype>(); //Example syntax

//Example Declaration

public Map<Player, Boolean> pluginEnabled = new HashMap<Player, Boolean>();
public Map<Player, Boolean> isGodMode = new HashMap<Player, Boolean>();

Keep that code in mind because we will be using it for the rest of the tutorial on HashMaps. So, for example lets create a simple function which will toggle whether the plugin has been enabled or not. Firstly, inside your on command function which I explained earlier you will need to create a function to send the player name to the function and adjust the players state accordingly.

So inside on command you’ll need this, the function name can be different but for the sake of simplicity it’s best if you keep it the same.

Player player = (Player) sender;
togglePluginState(player);

This code above will cast the value of sender to player and pass that arguement to the function togglePluginState(). But now we need to create our togglePluginState() function.

public void togglePluginState(Player player){
    
    if(pluginEnabled.containsKey(player)){
        if(pluginEnabled.get(player)){
            pluginEnabled.put(player, false);
            player.sendMessage("Plugin disabled");
        } else {
            pluginEnabled.put(player, true);
            player.sendMessage("Plugin enabled");
        }
    } else {
        pluginEnabled.put(player, true); //If you want plugin enabled by default change this value to false.
        player.sendMessage("Plugin enabled");
    }

}

Now, what this code is doing is checking if the HashMap first contains the key player, so if it has been put into the hashap, if it is then we check the value of the HashMap key by get(player); if this is true then set value to false and send the player a message, else if the value is false then do the opposite, set the value to true and send a message again. But if the HashMap does not contain the key player then we can assume that this is their first run/use so we change the default value and add the player to the hashmap.

More Ideas for HashMaps

A HashMap (or really any kind of Map in Java) is an association. It allows quick and efficient lookup of some sort of value, given a unique key. Anywhere this happens in your code, a Map may be your solution.

Here are a few other ideas which are ideally suited to using Maps. As you will see, it doesn’t have to be data that you store per player, but can be any kind of data that needs to be «translated» from one form to another.

Data Value Lookups

public Map<String, Integer> wool_colors = new HashMap<String, Integer>();

// Run this on plugin startup (ideally reading from a file instead of copied out row by row):
wool_colors("orange", 1);
wool_colors("magenta", 2);
wool_colors("light blue", 3);
   ...
wool_colors("black", 15);

// Run this in response to user commands - turn "green" into 13
int datavalue = 0;
if (wool_colors.containsKey(argument))
    datavalue = wool_colors.get(argument);
else {
    try { datavalue = Integer.parseInt(argument); }
    catch (Exception e) { ; }
}

Saving/Loading a HashMap

Once you know how to work with HashMaps, you probably want to know how to save and load the HashMap data. Saving and loading HashMap data is appropriate if

  • you don’t want an administrator to edit the data manually
  • you need to save data in binary format (too complex to organize for YAML)
  • you want to avoid parsing block names and/or other objects from freeform text

This is very simple way how to save any HashMap. You can replace HashMap<Player,Boolean> with any type of HashMap you want. Let’s continue using the «pluginEnabled» HashMap defined from the previous tutorial. This code saves the given HashMap to the file with given path.

public void save(HashMap<Player,Boolean> pluginEnabled, String path)
{
	try{
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
		oos.writeObject(pluginEnabled);
		oos.flush();
		oos.close();
		//Handle I/O exceptions
	}catch(Exception e){
		e.printStackTrace();
	}
}

You can see it’s really easy. Loading works very very similar but we use ObjectInputStream instead of ObjectOutputStream ,FileInputStream instead of FileOutputStream,readObject() instead of writeObject() and we return the HashMap.

public HashMap<Player,Boolean> load(String path) {
	try{
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path));
		Object result = ois.readObject();
		//you can feel free to cast result to HashMap<Player,Boolean> if you know there's that HashMap in the file
		return (HashMap<Player,Boolean>)result;
	}catch(Exception e){
		e.printStackTrace();
	}
}

You can use this «API» for saving/loading HashMaps, ArrayLists, Blocks, Players… and all Objects you know  ;) . Please credit me (Tomsik68) if you use this in your plugin.

/** SLAPI = Saving/Loading API
 * API for Saving and Loading Objects.
 * @author Tomsik68
 */
public class SLAPI
{
	public static void save(Object obj,String path) throws Exception
	{
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
		oos.writeObject(obj);
		oos.flush();
		oos.close();
	}
	public static Object load(String path) throws Exception
	{
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path));
		Object result = ois.readObject();
		ois.close();
		return result;
	}
}

Example implementation of this API: I’m skipping some part of code in this source

public class Example extends JavaPlugin {
	private ArrayList<Object> list = new ArrayList<Object>();
	public void onEnable()
	{
		list = (ArrayList<Object>)SLAPI.load("example.bin");
	}
	public void onDisable()
	{
		SLAPI.save(list,"example.bin");
	}
}

A minor note about this SLAPI and Java’s ObjectOutputStream class. This will work un-modified if you are saving almost all well-known Java types like Integer, String, HashMap. This will work un-modified for some Bukkit types as well. If you’re writing your own data object classes, and you may want to save their state using this technique, you should read up on Java’s Serializable interface. It’s easy to add to your code, and it will make your data persistent with very little work on your part. No more parsing!

Базы Данных

База данных представляет собой структурированную совокупность данных. Эти данные могут быть любыми — от простого списка игроков до перечня экспонатов картинной галереи. Для записи, выборки и обработки данных, хранящихся в компьютерной базе данных, необходима система управления базой данных. Поскольку компьютеры замечательно справляются с обработкой больших объемов данных, управление базами данных играет центральную роль в вычислениях. Реализовано такое управление может быть по-разному — как в виде отдельных утилит, так и в виде кода, входящего в состав других приложений.

Alta189 и PatPeter написали удобную библиотеку под названием SQLibrary. Данная библиотека позволяет упростить работу с БД. Так же для работы с БД вам понадобиться освоить синтаксис SQL.

SQLite

SQLite – это встраиваемая библиотека в которой реализовано многое из стандарта SQL. Её притязанием на известность является как собственно сам движок базы, так и её интерфейс (точнее его движок) в пределах одной библиотеки, а также возможность хранить все данные в одном файле. Я отношу позицию функциональности SQLite где-то между MySQL и PostgreSQL. Однако, на практике, SQLite не редко оказывается в 2-3 раза (и даже больше) быстрее. Такое возможно благодаря высокоупорядоченной внутренней архитектуре и устранению необходимости в соединениях типа «сервер-клиент» и «клиент-сервер».

MySQL

Другая популярная база данных SQL двигатель называется MySQL. Это ближе к серверу-класса, чем SQLite, где многие известные компании или веб-сайтов зависят от него миллионы веб-страницы хитов каждый день. При том, что безопасность приходит немного крутой кривой обучения, потому что MySQL имеет больше настраиваемых параметров и возможностей.

Получение скомпилированного плагина

После того как плагин написан, время задать вопрос: как получить рабочий jar-файл, который можно устанавливать на сервер, из набора файлов-исходников? В первую очередь установить сервер CraftBukkit на Вашем компьютере. Что бы сделать это ознакомьте с инструкцией Setting up a server (Настройка сервера). Затем Вам нужно экспортировать плагин в файл .jar, чтобы в дальнейшем запустить его на Вашем новом сервере. Чтобы сделать это в Eclipse, Выберите пункт меню File > Export. В появившемся окне под строкой «Java», выберите «JAR file», и затем выберите «next». Появится окно:

Экспорт в Eclipse

Проверьте что для экспорта выбрана Ваша директория с исходниками (в левой части окна). Справа, два файла с именем начинающимся на точку, не должны экспортироваться — они не нужны для работы плагина (снимите с них «галочки»). Очень важно, чтобы plugin.yml был выбран для экспорта; без него плагин не будет работать.
Экспортируйте Ваш jar-файл, указав нужный путь.

Полученный jar-файл должен быть работоспособным плагином!nbsp;Естественно, если не были допущены ошибки в коде или в файле plugin.yml. Теперь можно скопировать плагин в директорию «plugins» Вашего сервера, перезагрузить или перезапустить сервер и приступить к тестированию! In order to connect to a server running locally on your computer, simply put «localhost» as the IP address of the server in Minecraft multiplayer. If you run into errors that you can’t solve for yourself, try visiting the plugin development forum, asking in the bukkitdev IRC channel, or re-reading this wiki. Once you have a useful working plugin, consider submitting your project to dev.bukkit for consumption by the Bukkit community.

Советы и трюки

Поджигание Игрока

API Bukkit позволяет делать очень много интересных и нестандартных вещей. Вот несколько примеров:

Как поджечь кого-то всего лишь командой:

public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args){
    if(cmd.getName().equalsIgnoreCase("ignite")){
        Player s = (Player)sender;
        Player target = s.getWorld().getPlayer(args[0]); // target - игрок, чьё имя указано в команде в качестве параметр
        // Например, если команда была такова: "/ignite notch", то этим игроком будет "notch". 
        // Обратите внимание: Первый аргумент начинается с [0], а не [1]. Соответственно arg[0] возвращает заданное имя игрока. 
        target.setFireTicks(10000);
        return true;
    }
    return false;
}

Теперь, если написать команду типа /ignite Notch и если игрок «Notch» онлайн, Notch загорится!

Убиваем игрока

Продолжая тему, представляем способ убить игрока.
Используем метод OnCommand для этого:  

public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args){
    if(cmd.getName().equalsIgnoreCase("KillPlayer")){
        Player player = (Player)sender;
        Player target = player.getWorld().getPlayer(args[0]);
        target.setHealth(0); 
    }
    return false;
}

Очередной способ убийства игрока, но уже со взрывом:

float explosionPower = 4F; // Настройка мощности взрыва
Player target = sender.getWorld().getPlayer(args[0]);
target.getWorld().createExplosion(target.getLocation(), explosionPower);
target.setHealth(0);

Request Section

Как создать плагин с использованием Maven

Using git, clone the BukkitPluginArchetype repo and build the archetype:

git clone git://github.com/keyz182/BukkitPluginArchetype.git
cd BukkitPluginArchetype
mvn clean install

Now, in the folder you want to create the plugin in, run the following commands:

mvn archetype:generate -DarchetypeCatalog=local

then select the following from the list when prompted:

uk.co.dbyz:bukkitplugin (bukkitplugin)

For groupid, enter what you’d use as the first part of the Java Package.For Artifactid, enter the last part of the package. Accept Version and package as is, then type Y <enter>

E.G.:

Define value for property 'groupId': : uk.co.dbyz.mc
Define value for property 'artifactId': : plugin
Define value for property 'version': 1.0-SNAPSHOT: 
Define value for property 'package': uk.co.dbyz.mc:

You’ll now have a folder named as whatever you used for archetypeid. In that folder is a folder, src, and a file, pom.xml.

Open the <archetypeid>CommandExecuter.java file in src/main/java/<package>, and add the following code where it says //Do Something

Player player = (Player) sender;
player.setHealth(1000);

Now, go back to the base folder and run

It may take a while downloading things, but let it do it’s thing. When done, there’ll be a folder called target, and a file called <archetypeid>-1.0-SNAPSHOT.jar inside it. Copy this file to the bukkit server plugin folder and reload the server.

Now, if you type the command /<archetypeid in minecraft while logged into the server, it’ll give you full health!

 Добавляйте ещё примеры с HashMap пожалуйста

   

Примеры файлов и шаблоны

  • Example.Java
  • ExamplePlayerListener.Java
  • ExampleBlockListener.Java
  • ExampleEntityListener.Java

My Open Source Plugins

  • TameGrass — What you can learn from this plugin: Creating, deleting and checking blocks in a world. Loops and HashMaps, Permissions integration, Number formatting and Exceptions and more…
  • Zombies(Unfinished) — What you can learn from this plugin: HashMaps, Manipulating blocks, Random number generation, Entity spawning, ArrayLists and more…

If you have any more questions on this matter, don’t hesitate to contact Adamki11s or anyone on the BukkitDev IRC channel

Language   English • беларуская • Deutsch • españolsuomi • français • italiano한국어Nederlandsnorskpolskiportuguês • русский • lietuviųčeština
Страница 1 из 3

  1. Это статья является некоей базой для написания плагинов. По ней Вы сможете написать свой первый и последующие плагины.
    Для начала Вам понадобится jar архив с сервером bukkit/craftbukkit,среда для программирования(мне более удобна NetBeans), Java SDK, описание API bukkit’а. Помните, что совместимость версий идёт по нарастающей: первые версии-1.8.1 и 1.0.0 — … Это два разряда, совместимость между которыми обеспечивать нужно отдельно.
    Главные места я постараюсь выделить так, а места на которые нужно обратить внимание так.
    Мы будем писать на 1.0.0+.
    Если у Вас есть среда, сервер, мы можем приступать.
    Для начала создайте новый проект java: иблиотека классов java.
    После этого мы должны внутрь нашего проекта подключить в качестве библиотеки наш сервер.

    В пакете по умолчанию нам нужно создать файл YAML plugin.yml.
    Как наполнять его описано в http://wiki.bukkit.org/Plugin_YAML/ru .

    Далее создадим пакет. Название пишем любое, но оно не должно содержать пробелы и любые не буквенные символы, кроме точек нижнего подчёркивания, цифр, но он не должен на них начинаться(подчёркивания не касается).

    1. Несовпадения имен пакетов гарантированы при использовании имени домена в обратном порядке: com.javasoft.jag — «com» и «edu» — обычно в верхнем регистре, но теперь — рекомендуется в нижнем.
    2. Имена классов — описательные существительные, с заглавной буквой в начале каждого слова: PolarCoords (ПолярныеКоординаты). Часто (но не всегда) интерфейсы называются «что-то-able», например, «Runnable» (запускаемый), «Sortable» (упорядочиваемый). Предупреждение: java.util.Observable не интерфейс, а java.util.Observer — интерфейс. Эти два имени плохо продуманы.
    3. Переменные и поля — существительное/фраза с существительным, с первой буквой в нижнем регистре и первой буквой подслова — в верхнем: currentLimit (текущийПредел).
    4. Имена методов — глагол/фраза с глаголом, с маленькой буквы, каждое подслово — с большой: calculateCurrentLimit (вычислитьТекущийПредел).
    5. Имена констант (с модификатором final) пишутся большими буквами: UPPER_LIMIT

    Теперь мы начнём наполнять пакет.
    Нам нужен главный класс. Создаём в нашем классе новый класс java и назовём его любым именем (может содержать буквы и цифры (но не сначала)). Рекомендую его назвать main, дабы не путаться.
    Помните: т.к. java является кроссплатформенной, то регистр имеет значение.
    Теперь открываем этот класс (если он не открыт) и видим нечто подобное:

    package названиеВашегоПакета;
    
    public class названиеВашегоКласса
    {
    
    }

    Комментарии не влияют на код и пишутся так: между /* и */ или после // вся строка.
    Класс — структурная единица языка, вторая по величине, после пакета.
    Метод — структурная единица класса, которая может принимать, возвращать значения и выполнять различные действия.
    Переменная — объект, который может хранить некоторую информацию.
    Тела всех методов и классов находятся внутри фигурных скобок, после их объявления.
    У всех классов, методов и переменных есть 3 модификатора доступа: public — доступна из любого места, protected — доступна только внутри пакета и private — доступна только внутри класса.
    Расширим стандартный класс JavaPlugin — добавим после названия Вашего класса и до открывающейся фигурной скобки:
    Компилятор должен среагировать и ввести нас в курс, что у нас в коде ошибка на слове JavaPlugin. Если в компиляторе есть структура, отвечающая за полуавтоматическое исправление ошибок, то она предложит добавить император импорта …JavaPlugin. Разрешим ему. Впоследствии я не буду упоминать такие моменты.
    Переопределяем стандартный метод JavaPlugin, вводим в теле класса:

    @Override
    public void onEnable()
    {
    
    }

    Это метод, который вызывается при старте нашего плагина. Также существуют методы public void onDisable() и public void onLoad(), они соответственно запускаются при выключении и подгрузке плагина.
    В данном методе желательно подгружать все нужные ресурсы для работы плагина, шерстить файл настроек и пр.
    Давайте введём в теле класса пару переменных и произведём подгрузку файла настроек.
    Создадим общедоступную переменную строкового типа:

    public String название_переменной;

    и булеву переменную, для доступа изнутри класса:

    private boolean другое_название;

    Чтобы удобнее взаимодействовать с кодом, создадим приватный метод для проверки и создания файла настроек. Метод не должен принимать никаких данных, и возвращать (void).
    В теле метода инициализируем локальную переменную для работы с конфигом:

    FileConfiguration config = this.getConfig();

    Теперь начинаем шерстить настроечный файл. Проверим, есть ли в нём булево поле «enable», если нет, то создадим его и присвоим ему значение true:

    if(!config.isBoolean("enable"))
        config.set("enable", true);

    Теперь запишем значение этого поля в ранее определённую булеву переменную:

    другое_название = c.getBoolean("enable");

    Проделаем ту же операцию с строковым полем «какое-то_поле»:

    if(!config.isString("название_переменной"))
        config.set("какое-то_поле", "значение_по_умолчанию");
    название_переменной = config.getString("какое-то_поле");

    По окончании операций с файлом настроек, сохраним его:
    Вызываем этот метод в теле onEnable(). Если значение, которое мы получили из поля «enable» равно false, то выключаем плагин:

    if(!другое_название)
    {  getServer().getPluginManager().disablePlugin(this); return; }

    Теперь создадим базу для обработки событий. Для этого нам понадобится класс-инструментарий Listener.
    В качестве такого класса можно использовать основной класс, внутренний класс или новый класс.
    После названия класса вводим:
    Нам подойдёт одна из следующих конструкций, они применяются в зависимости от нужд и количества различных событий, предполагаемых к обработке:

    Bukkit.getServer().getPluginManager().registerEvents(new название_класса_обработчика(), this);
    //Вызываем в главном классе, желательно в методе void onEnable()
    
    Bukkit.getServer().getPluginManager().registerEvents(переменная_объекта_класса_обработчика, this);
    //Вызываем в главном классе, желательно в методе void onEnable(), но после инициализации  переменной
    
    Bukkit.getServer().getPluginManager().registerEvents(this, переменная_содержащая_объект_главного_класса_плагина);
    //Вызываем в самом классе-обработчике, если это основной класс, то не рекомендую использовать для большого количества различных событий

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

    Вот так выглядит обработка простого события входа игрока на сервер и оповещение лично всех игроков, которые онлайн, с проверкой наличия разрешения:

    @EventHandler
    public void onPlayerJoin(PlayerJoinEvent e)
    {
        if(e.getPlayer().hasPermission("не_показывать_что_я_вошёл"))
            return;
        String name = e.getPlayer().getName();
        for(Player p:Bukkit.getServer().getOnlinePlayers())
            if(!p.getName().equals(name))
                p.sendMessage("Игрок "+name+" вошёл на сервер!");
    }

    Переходим к обработчикам команд.
    Каждая команда, которая отправляется в плагин, должна быть заранее описана в plugin.yml.
    В качестве класса-обработчика выступает класс-инструментарий CommandExecutor.
    В качестве такого класса можно использовать основной класс, внутренний класс или новый класс.
    Один класс может обрабатывать несколько различных команд.

    После названия класса вводим:

    implements CommandExecutor

    Нам подойдёт одна из следущих конструкций для определения такого класса для сервера. Но, в отличие от событий, такой метод вызывается от имени плагина:

    getCommand("название_команды").setExecutor(new название_класса_обработчик());
    //Вызываем в главном классе, желательно в методе void onEnable()
    
    getCommand("название_команды").setExecutor(переменная_объекта_класса_обработчика);
    //Вызываем в главном классе, желательно в методе void onEnable(), но после инициализации  переменной
    
    переменная_содержащая_объект_главного_класса_плагина.getCommand("название_команды").setExecutor(this);
    //Вызываем в самом классе-обработчике

    Метод, обрабатывающий команды — переопределённый метод

    public boolean onCommand(CommandSender sender, Command command, String label, String[] args)

    Данный метод принимает несколько значений:
    CommandSender sender — отправляющий сообщение, может быть объектом ConsoleCommandSender, при отправке из консоли, и Player, при отправке игроком.
    Command command — объект отправленной команды.
    String label — название команды без символа ‘/’ и аргументов.
    String[] args — список аргументов команды, если их нет — то равен null.
    Как Вы должны были заметить, метод возвращает boolean, означающий выполнена команда верно или нет.
    Вот так выглядит обработка простой команды, приветствующей определённого игрока с запретом на использование из консоли/без ника игрока/на себя:

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args)
    {
        if(sender instanceof ConsoleCommandSender)
        {  sender.sendMessage("Запрещено использовать из консоли!"); return true; }
        if(args==null||args.length!=1||Bukkit.getServer().getPlayer(strings[0])==null)
        {  sender.sendMessage("Ник игрока указан неверно!"); return false; }
        if(Bukkit.getServer().getPlayer(strings[0]).getName().equals(sender.getName()))
        {  sender.sendMessage("Запрещено использовать на себя!"); return false; }
        Bukkit.getServer().getPlayer(strings[0]).sendMessage("Вас приветствует "+sender.getName()+"!");
        return true;
    }

    Я уверен, что данных знаний достаточно для написания своего собственного плагина. Можно приступать!

    Последнее редактирование: 13 окт 2015
  2. Быстрая раскрутка сервера Minecraft


  3. dimavv

    dimavv
    Активный участник
    Пользователь

    Баллы:
    88
    Имя в Minecraft:
    dimavv

    Cпасиб попробую что-то написать)


  4. Kachalov

    Kachalov
    Старожил
    Пользователь

    Баллы:
    103
    Skype:
    alex-kachalov-01
    Имя в Minecraft:
    Kachalov

    Может быть Вы знаете, как изменить поведение крипера? Например, сделать дружелюбным?


  5. Kachalov

    Kachalov
    Старожил
    Пользователь

    Баллы:
    103
    Skype:
    alex-kachalov-01
    Имя в Minecraft:
    Kachalov

    Нашёл. Вот только, как прикрутить?

        public void onEntityTarget(EntityTargetEvent event)
        {
            if (((event.getTarget() instanceof Player)) &&
            ((event.getEntity() instanceof Creeper)))
            {
            event.setCancelled(true);
            }
        }

  6. Den_Abr

    Den_Abr
    Старожил
    Девелопер
    Пользователь

    Баллы:
    173
    Skype:
    Den_Abr
    Имя в Minecraft:
    Den_Abr

    А почему ничего не сказано про перезагрузку конфига, думаю, что тоже полезно


  7. valexv14

    valexv14
    Старожил
    Пользователь

    Баллы:
    123
    Skype:
    valexv13
    Имя в Minecraft:
    valexv12

    Мда, знаю ошибок много, но чего-то не так я сделал. Я полный нуб в программировании вот учится начинаю… Если кто-то свободен, и есть желание помогите плиз. Посмотрите что у меня не так…. подправьте, и расскажите мне об моих ошибках… Опять же — если не трудно конечно…
    Мой плУгин.
    Смысл плагина — тупо как в туториале выводить сообщение «Игрок зашел», и скрывать эту надпись если есть permissions.


  8. valexv14

    valexv14
    Старожил
    Пользователь

    Баллы:
    123
    Skype:
    valexv13
    Имя в Minecraft:
    valexv12


  9. tOshKa

    tOshKa
    Активный участник
    Заблокирован

    Баллы:
    88
    Skype:
    antloginov
    Имя в Minecraft:
    MegaFlary

    Можете скинуть исходный код? ;)

  10. Я злой лосось, иду хлестать медведей xD

  11. lssion,мой стол шатается когда я читаю твои шутки)))

  12. Я чё-то не понял :(

    Я открываю класс, а там написано:

    /*
    * To change this template, choose Tools | Templates
    * and open the template in the editor.
    */
    package ru.smallpresent.smallpresentplugin;

    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import org.openide.cookies.EditorCookie;
    import org.openide.awt.ActionID;
    import org.openide.awt.ActionReference;
    import org.openide.awt.ActionReferences;
    import org.openide.awt.ActionRegistration;
    import org.openide.util.NbBundle.Messages;

    @ActionID(
    category = «Edit»,
    id = «ru.smallpresent.smallpresentplugin.SmallpresentAction»)
    @ActionRegistration(
    iconBase = «ru/smallpresent/smallpresentplugin/0537.png»,
    displayName = «#CTL_SmallpresentAction»)
    @ActionReferences({
    @ActionReference(path = «Menu/File», position = -100),
    @ActionReference(path = «Toolbars/File», position = -100)
    })
    @Messages(«CTL_SmallpresentAction=SmallPresent»)
    public final class SmallpresentAction implements ActionListener {

    private final EditorCookie context;

    public SmallpresentAction(EditorCookie context) {
    this.context = context;
    }

    @Override
    public void actionPerformed(ActionEvent ev) {
    // TODO use context
    }
    }

    Как понять?


  13. ZloYCRipeeR

    ZloYCRipeeR
    Старожил
    Пользователь

    Баллы:
    153
    Skype:
    cry_zloy
    Имя в Minecraft:
    ZloYCRipeeR

    Можно пример кода простого плагина? И как добавить плагину permissions?

  14. с кодом у меня у самого проблемы… :(
    А про пермишены тут.


  15. hubridos

    hubridos
    Старожил
    Пользователь

    Баллы:
    103
    Skype:
    hubridos
    Имя в Minecraft:
    Hubrid

    А что делать ели я хочу просто добавить команду для всех людей, ну например /trade ***** и в чате отображается [Торг][Hubrid] ***** как добавить команду и пермы к ней?


  16. BeYkeR

    BeYkeR
    Старожил
    Девелопер
    Пользователь

    Команды сам узнаешь:), а пермы можно сделать так:

    if(sender.hasPermission("test.testperm")){
    //какой-то код
    }else{
    //Если у игрока нет этого перма сверху
    sender.sendMessage(ChatColor.RED + "У тебя нет прав!"); 
    }
    

  17. unlimited

    unlimited
    Старожил
    Пользователь

    Баллы:
    123
    Имя в Minecraft:
    infinity

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
    if(command.getName().equalsIgnoreCase("test")){
    //Если ввели команду /test - выполняем код ниже
    Player player = (Player)sender;//переводим сендера в плеера, так удобнее
    if(player.hasPermission("пермишионы")) {
    код если есть пермишионы
    player.SendMessage("текст");//отправит тому кто ввел команду "текст"
    return true;//просто чтоб было
    } else { код если нету }
    }
    return false;

  18. hubridos

    hubridos
    Старожил
    Пользователь

    Баллы:
    103
    Skype:
    hubridos
    Имя в Minecraft:
    Hubrid

    А если писать под MCPC+
    http://ci.md-5.net/job/MCPC-Plus/503/
    я хочу апи добавить, мне нужно просто серв взять или там где приписка api :?


  19. Den_Abr

    Den_Abr
    Старожил
    Девелопер
    Пользователь

    Баллы:
    173
    Skype:
    Den_Abr
    Имя в Minecraft:
    Den_Abr

    Значения не имеет, подойдёт обычный буккит

  20. Ну а что на счёт onUpdate()? Ведь иногда что-то же надо проверять для проверки в каждый тик.


  21. unlimited

    unlimited
    Старожил
    Пользователь

    Баллы:
    123
    Имя в Minecraft:
    infinity

    Создаешь доп. поток, в нем цикл с задержкой

Страница 1 из 3

Поделиться этой страницей

Русское сообщество Bukkit

Bukkit по-русски - свой сервер Minecraft

Привет, форумчане!
Давно хотели написать свой плагин? У меня есть для вас решение! Вы уже сегодня сможете написать свой плагин майнкрафт с небольшим функционалом.

Здесь я показываю как сделать свой первый плагин, если вы хотите пойти дальше, вам нужно изучить Java, а также внимательно изучить  SpigotAPI

Поехали!

1 Этап. Скачивание софта.

Лично я рекомендую использовать IntelliJ IDEA — ССЫЛКА

Очень удобный IDE для разработки на Java.
Скачивайте Community версию, она бесплатная!

Запускаем программу. Кликаем на кнопку Plugins
image.png.89e00f54eefb796a0d8bb9c3f9d4f882.png

В поисковике пишем Minecraft Development
image.png.8899c117ce9340837704f3d8c85592cd.png

Устанавливаем, перезапускаем программу.

2 Этап. Подготовка проекта.

После запуска программы вас встречает окно выбора проектов, если у вас это первый проект, то никаких других  не будет.
Справа сверху находим кнопку New Project , кликаем
image.thumb.png.e3b55f92bc1351f9cc14eaacab5c0bfa.png

После клика мы видим окно с выбором шаблона проекта. Выбираем Minecraft, далее Spigot Plugin.
Project SDK — это версия Java которую вы хотите выбрать для своего проекта, если это плагин ниже 1.12.2, то выбирайте 1.8, если это плагин выше, то выбирайте 11, в моём случае это 1.8 (Java 8).
image.png.b6d2b1a68b44743d24bd7a12840a7e1c.png

Next
image.png.1873f83b70c909bbf5df51c681f802c9.png

Далее видим, что нам предлагает вписать GroupId, ArtifactId и Version

GroupId — путь ко всем вашим класс файлам плагина.

ArtifactId — название плагина, лучше писать латинскими буквами (желательно без пробелов)

Version — версия плагина

Пример:

image.thumb.png.b9393d687d58683925a72fe5e2a3d354.png

Next

Очень много разных значений, обьясню по порядку.

Plugin Name — название плагина


Main Class Name — путь к класс файлу плагина ( Лучше оставить как есть)


Minecraft Version — версия майнкрафта


Description — описание плагина (Можно оставить пустым)


Authors — разработчики плагина (Можно оставить пустым)


WebSite — сайт плагина (Можно оставить пустым)


Log Prefix — префикс логгирования плагина в консоль (Можно оставить пустым, по умолчанию используется название плагина в качестве префикса)


Load Before — загружать плагин после определённых плагинов (Лучше оставить пустым)


Depend — зависимости плагина (Какие плагины обязательно требуются для работы нашего плагина, например Vault. Лучше оставить пустым, если вы не используете API других плагинов)


Soft Depend — не обязательные зависимости плагина (Какие плагины необязательно требуются для работы нашего плагина)

image.thumb.png.a1ff4460cd2b0a9124b573f06551faf8.png

Next

Project name — название проекта (можно оставить как есть)


Project location — путь к проекту

image.thumb.png.1c7457aec253d7981210d057c88d328b.png

Finish

3 Этап. Написание плагина.

Теперь мы перешли к написанию нашего плагина. Я напишу команду /grant для выдачи доната другому игроку.
Открываю главный класс

import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;

public final class TestPlugin extends JavaPlugin {

    @Override
    public void onEnable() {
        // Регистрируем команду /grant
        getCommand("grant").setExecutor((sender, command, label, args) -> {
            // Если у отправителя нету прав test.plugin.grant выводим сообщение с ошибкой
            if (!sender.hasPermission("testplugin.grant")) {
                sender.sendMessage("§7[§c§l!§7] §cУ вас недостаточно прав!");
                // Завершаем выполнение
                return true;
            }
            if (args.length == 0 || args.length == 1) {
                // При вводе /grant отправляем сообщение
                // также с /grant (ник) ,так как не все аргументы учтены
                sender.sendMessage("§7[§c§l!§7] §c/grant (ник) (группа)");
                return true;
            }
            // Аргумент 0 отвечает за строку игрока (ник)
            String player = args[0];
            // Player (переменная) = Bukkit.getPlayer(player) - получаем переменную с игроков доступным на сервере
            Player target = Bukkit.getPlayer(player);
            String groupname = "Неизвестно";
            String group = "";
            // Если аргумент 1 (аргумент с группой) = premium, то
            if (args[1].equalsIgnoreCase("premium")) {
                // Перменная group = premium
                group = "premium";
                // Переменная groupname = §3Premium
                groupname = "§3Premium";
            }
            if (args[1].equalsIgnoreCase("vip")) {
                group = "vip";
                groupname = "§6Vip";
            }
            // Если игрок оффлайн, выводим сообщение и пытаемся выдать донат
            if (target == null) {
                sender.sendMessage(ChatColor.RED + "§7[§c§l!§7] §cДанный игрок не онлайн!");
                sender.sendMessage(ChatColor.RED + "§7[§c§l!§7] §cПопытка выдачи привилегии...");
                Bukkit.getServer().dispatchCommand(Bukkit.getServer().getConsoleSender(), "lp user " + player + " parent add " + group);
                return true;
            }
            // Если аргумент 0 (ник игрока) совпадает с ником отправителя команды, выводим сообщение
            if (target.getName().equals(sender.getName())) {
                sender.sendMessage("§7[§c§l!§7] §cВы не можете выдать привилегию самому себе!");
                return true;
            }
            // Если количество аргументов равняется 2 и аргумент с группой равняется vip или равняется premium и игрок не оффлайн,
            // то выполняем команду через консоль (lp user (ник) parent add (группа)
            if (args.length == 2 && args[1].equalsIgnoreCase("vip") || args[1].equalsIgnoreCase("premium") && target != null) {
                Bukkit.getServer().dispatchCommand(Bukkit.getServer().getConsoleSender(), "lp user " + target.getName() + " parent add " + group);
                sender.sendMessage("§7[§c§l!§7] §aВы успешно выдали игроку §c" + target.getName() + " §aпривилегию " + groupname);
                target.sendMessage("§7[§c§l!§7] §aАдминистратор §d" + sender.getName() + " §aвыдал вам привилегию " + groupname);
                return true;
            }
            return true;
        });

    }

    @Override
    public void onDisable() {
    }
}

image.png.59fffc8b3ae0a6ff3853f54967ba3a84.png

Далее открываю plugin.yml и в конце файла пишу

commands:
  grant: {}

После всех манипуляций я могу спокойно скомпилировать свой плагин, для этого мне нужно справа сверху кликнуть на кнопку Maven

image.thumb.png.f58b0c7892e1f069a5ec447de536dc16.png

И кликнуть на package

image.png.f949936b4031977f255b8d84daa3c477.png

И вуаля, мой плагин успешно скомпилировался. Теперь заходим в папку с проектом и видим папку target
image.png.18b141aa1b72d9a5540182175484e96a.png

В ней и лежит мой готовый плагин

image.png.ad2a18403a058addc661bdd0f12c5188.png

На этом всё, спасибо за внимание!

image.png


Изменено 20 июля, 2022 пользователем Jodex

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

First things first

Для начала стоит определиться с основами.
Думаю, ни для кого не секрет что оригинальный сервер майнкрафта написан и работает на Java.
Java — это обьектно-ориентированный язык программирования. Для работы Вашего сервера, Java клиента майнкрафта и написания плагинов под этот сервер вам нужно установить Java.
Начать можете отсюда, отсюда, или отсюда.

Для того, чтобы начать что-то писать под этот сервер нам нужно воспользоваться API.
Основные, наиболее распространенные и популярные API для оригинального сервера, вот неожиданность — тоже написаны и работают на Java (или на любом другом JVM совместимом языке).
Из этого следует что все плагины для наиболее распространенных и популярных API к оригинальному серверу майнкрафта написаны на… (ну, надеюсь, Вы уже поняли).

Что же это за такие “наиболее распространенные и популярные API к оригинальному серверу”, спросите Вы?
А я приведу вам небольшой список таковых:

  • Bukkit
  • Spigot [документация]
  • Paper [документация]

Я не буду охватывать тут всю историю развития этих API, кто от кого форкнулся и так далее, ведь здесь важно другое — абсолютное большинство публичных плагинов что Вы найдете в сети, так или иначе работают с одним (или с несколькими / всеми) из этих API.

Ещё для написания плагинов нам понадобится “среда разработки”.
Если очень упрощённо, то это программа, позволяющая разрабатывать другие программы на определённом языке программирования на несколько порядков удобнее, чем если бы Вы писали код просто в блокноте.
Дальше по статье я буду приводить примеры разработки с использованием Intelij IDEA от Jetbrains, но, в принципе, Вы можете использовать любую другую удобную вам IDE.

Ну и последнее, но не по важности — сам сервер майнкрафта с нужным нам API на борту. Я для этого туториала взял Paper версии 1.19.2 отсюда и в дальнейшем, в статье, буду ссылаться на его API и документацию.
Сейчас Вам достаточно скачать .jar файл сервера. Позже я обьясню как его запустить и как протестировать плагин на нём.

Стартуем

На данном этапе предполагается, что Вы уже установили Java и IDE (среду разработки) на Java и перед вами сейчас открыт приветственный экран Вашей IDE.
Вот мой, например:
IDEA
Сперва нам нужно создать проект. Этим проектом и будет наш плагин.
Кликаем на кнопку ‘New Project’ и перед нами открывается окно создания нового проекта.
New project
С названием, думаю, всё очевидно.

  • Location’ — место на диске, где будет лежать папка проекта.
  • Create git repository’ — создать ли Git репозиторий для проекта. Если у вас стоит галочка — уберите.
  • Language’ — Java, тут всё понятно.
  • Build System’ — а вот с этого момента поподробнее.


Build System — система, через которую будет собираться Ваш проект.
Под сборкой имеется ввиду компиляция всего Вашего кода в исполняемый файл (jar, например) вместе с автоматическим внедрением в него всех зависимостей, библиотек и так далее.
Есть много различных сборщиков — Maven, Gradle, Ant и т.д., но в этом туториале я остановлюсь на Maven — как на самом простом, по моему мнению, в освоении, для начинающих разработчиков.

  • JDK’ — или Java Development Kit та самая Java которую Вы должны были уже установить ранее. Если вам повезло — IDEA сама автоматически определит доступную вам версию JDK. Если нет — вам придётся указать её локацию на диске вручную.
  • Add sample code’ — просто сразу добавляет в проект готовый пакет с классом в нём.
  • Advanced settings’ — в нашем случае тут расположены настройки для сборщика Maven.
  • Group Id’ — уникальный идентификатор Вашего проекта.
  • Artifact Id’ — название собранного сборщиком скомпилированного файла.


После того, как Вы всё настроили — жмём кнопку ‘Create’, и перед нами открывается окно с проектом.
project created
Немного отвлечёмся и поговорим о структуре плагинов, написанных под Paper API (и все его форки).
Для того, чтобы сервер понимал, что перед ним именно плагин, а не странный файл с расширением .jar — в каждом из плагинов есть файл plugin.yml — в котором подробно описывается что перед сервером за плагин, кто его автор, где расположен главный его класс, какие команды есть у плагина и т.д.

Мы начнём именно с создания и наполнения этого файла. Создаём в папке resources файл plugin.yml -> кликаем правой кнопкой мыши на resources -> New -> File -> вводим plugin.yml.
Заполняем файл следующим образом:
plugin.yml
Подробное описание каждой записи в файле есть здесь.
Из самого важного — нужно указать правильный путь до главного класса плагина (main), в моём случае это me.xezard.firstplugin.FirstPlugin. Если путь будет указан неверно — плагин не запустится.

Теперь нам нужно создать соответствующие указанному ранее пути пакеты и главный класс плагина.
Создаём нужные пакеты — кликаем правой кнопкой мыши на me.xezard (в Вашем случае это может быть Ваш собственный ранее созданный пакет) -> New -> Package -> вводим firstplugin.
Важно: новый пакет в таком случае создаётся относительно уже существующего. Полный путь получится следующим — me.xezard.firstplugin.
⁣Давайте заодно удалим класс автоматически созданный нашей IDE — класс Main.
Жмём правой кнопкой мыши по нему -> Delete -> Ок. Готово.

Вот мы и добрались до создания первого класса плагина и по совместительству — главного его класса.
Жмём правой кнопкой мыши на ранее созданный нами пакет -> New -> Java class -> вводим то имя класса, которое мы ранее указали последним в main секции файла plugin.yml — в моём случае это ‘FirstPlugin’.
Итог всех приведённых выше манипуляций выглядит следующим образом:
main class
Вновь немного отвлечёмся на настройку сборки нашего плагина. В данный момент мы никак не сможем взаимодействовать с API сервера майнкрафт — среда разработки ничего не знает о нём и его структуре. Для того, чтобы это исправить, нам нужно добавить API в проект, в моём случае это будет Paper API (форк Spigot API), в файл pom.xml. pom.xml — это, по сути, подробное описание нашему сборщику как конкретно собирать проект. Я не буду останавливаться на этом и подробно обьяснять что означает каждая из строк в файле — на это вполне может уйти пара отдельных гайдов. Сейчас вам достаточно привести Ваш pom.xml к следующему виду:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">   
<modelVersion>4.0.0</modelVersion>
    <groupId>me.xezard</groupId>
    <artifactId>FirstPlugin</artifactId>
    <version>1.0-SNAPSHOT</version>
    <build>
        <defaultGoal>clean package install</defaultGoal>
        <finalName>FirstPlugin</finalName>
⁣
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
⁣
    <repositories>
        <repository>
            <id>papermc</id>
            <url>https://papermc.io/repo/repository/maven-public/</url>
        </repository>
    </repositories>
⁣
    <dependencies>
        <dependency>
            <groupId>com.destroystokyo.paper</groupId>
            <artifactId>paper-api</artifactId>
            <version>1.16.5-R0.1-SNAPSHOT</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>


Если очень кратко — то в файле мы указали под какую версию Java будет скомпилирован плагин, указали репозиторий, где находится Paper API и указали что наш плагин зависит от этого API в секции <dependencies>.

Код

Пришло время для самого интересного — написания кода. Cтоит начать с того, что любой плагин написанный под Paper API (и его форки) в своём главном классе обязательно должен наследовать класс JavaPlugin.
Для наследования в Java используется ключевое слово extends.
Изменим код класса на следующий:
main class
Я постарался максимально понятно расписать за что отвечает каждая строка в классе. Сейчас, если скомпилировать плагин, добавить на сервер и запустить его — при включении или выключении плагина сервером он выведет в консоль соответствующие сообщения.

Компиляция плагина

Пришло время собрать наш код в jar файл! Если Вы, также как и я, используете IDEA для написания кода — просто откройте панель ‘Maven’ справа в верхнем углу, нажмите левой кнопкой мыши на название Вашего плагина и далее на зелёный значок Play.
build
Если до этого момента Вы всё делали правильно — Вы увидите открывшуюся консоль и по прошествии некоторого времени надпись ‘BUILD SUCCESS’.
build success

Тестируем плагин

Помните сервер, который Вы скачали в начале статьи? Его время пришло.
Переместите .jar сервера, который Вы загрузили в отдельную папку. Если вы являетесь пользователем операционной системы Windows (как я), то создайте в этой же папке скрипт start.bat со следующим содержимым:

java -Dfile.encoding=utf-8 -server -Xms1G -Xmx1G -jar paper.jar nogui
pause


Важно: paper.jar что указан в этом файле — это название Вашего файла сервера в папке. Если название не будет совпадать — скрипт не запустит Ваш сервер. Параметры Xms и Xmx используются для выделения процессу сервера оперативной памяти. Я использовал 1 гигабайт, но Вы можете указать свои значения. Главное, чтобы выделенной оперативной памяти хватало для работы сервера.

После того, как Вы закончили редактировать файл — запустите его двойным кликом мыши. При первом запуске сервера, как ни странно — он не запустится. Вместо этого он сначала предложит вам принять соглашение EULA.
Cейчас Вам достаточно открыть файл eula.txt и поменять строчку eula=false на eula=true и сохранить файл.
Теперь запустите сервер повторно и после надписи ‘Done! For help, type “help”’ напишите в консоли stop и нажмите Enter.
Повторный запуск был необходим для того, чтобы сервер сгенерировал все свои файлы.

Вернитесь в папку в которой лежат файлы Вашего плагина и перейдите в папку target — в ней лежит скомпилированный плагин, в моём случае это FirstPlugin.jar. Переместите его в папку сервера, в подпапку plugins, после этого запустите сервер.

Если вы шли в точности по статье и всё делали правильно, то в консоли сервера вы сможете увидеть заветное сообщение:
success
⁣⁣За сим первая часть этой замечательной статьи подходит к концу. Во второй части мы рассмотрим создание команд, отслеживание событий сервера и работу с конфигурацией плагина.

Like this post? Please share to your friends:
  • Как пишутся письменные буквы русского
  • Как пишутся натуральные числа
  • Как пишутся пиксели
  • Как пишутся нарицательные имена существительные
  • Как пишутся печатные буквы русского алфавита