Туториал Пишем простой плагин для bukkit (v1.2.5+)

Продолжать выкладывать туториалы по написанию плагинов?

  • Да

    Голосов: 157 95,7%
  • Нет

    Голосов: 7 4,3%

  • Всего проголосовало
    164

DmitriyMX

Старожил
Пользователь
Баллы
153
Подготовка
Для написания своих плагинов нам понадобятся:
Так же понадобятся базовые знания по Java. Если таковых нет, то вот ссылки по “поднятию скила”:
Установка и настройка Eclipse

Если вы уже знакомы с Eclipse или увас другая IDE, то пропускайте этот раздел.
Проблем с установкой эклипса возниктуть не должно: его нужно просто распаковать в любоую папку и запустить. Сразу после этого, эклипс попросит указать, какое рабочее пространство(workspace) сейчас открыть. Это папка, в которой будут сохранятся все созданные проекты и где ее располагать – лично ваше решение. Если вам всеранво, то оставляйте все как есть. Если вдруг захотите изменить рабочее пространстро, то не переживайте: при каждом следующем запуске, эклипс сново спросит вас о рабочем пространстве. Таких пространств можно наделать сколько угодно и каждое может быть настроено по разному, под какие-то определенные цели. К примеру у меня есть 2 рабочих пространства: для написания обычных java-приложений и для написания плагинов под “ведро”. Если вдруг вам надоест это сообщение, там есть галочка «Use this as the default and do not ask again», что позволяет задать workspace по-умолчанию.

Как только вы определитесь с выбором места и загрузится Eclipse, то мы увидим пригласительную вкладку… которую сразу же закрываем. Она нам ни к чему.

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

  • Package Explorer
    Здесь будут отображаться ваши проекты, пакеты(о них позже) и всевозможные файлы наших будующих плагинов.
  • Problems
    Этой панелькой мы будем редко пользоваться(если вообще до нее дело дойдет), но сказать о ней стоит. Здесь будут показываться ошибки, допущенные в коде, а так же предупреждающие сообщения о возможных ошибках или неточностях.
  • Outline
    Тут будет отображаться навигация непосредственно по коду открытого java исходника.
Последние 2 описанные панели можно вообще свернуть, т.к. пользоваться ими будем редко.
Рабочее место почти готово. Осталось только поставить еще 2 галочки в настройках.

Переходим в меню Window -> Preferences, далее по древу General -> Editor -> Text Editors и выставляем галочку «Show line numbers», чтобы включить отображение нумерации строк. Теперь переходим в General -> Workspace и в группе «Text file encoding» выставляем кодировку UTF-8, тем самым установив кодировку по-умолчанию.
003.png
004.png
С установкой и настройкой покончено. Теперь поясню, как создавать новый проект для плагина. Это можно сделать одим из 3-х способов:
  • Меню File -> New -> Java Project
  • Кнопка на панели инструментов
  • Правый клик в Package Explorer
eclipce-1.jpg
Перед нами откроется окно New Java Project. В Project name указываем название нашему проекту.
005.png
Нажимаем Next.
В появившемся окне, переходим на вкладку Libraries, нажимаем кнопку Add External JARs и выбираем скаченный Bukkit API.
006.png
Жмем Finish.
Слева, в Package Explorer, показался наш проект с папкой src, в которой будут храниться наши исходные коды. Теперь созданим новый класс. Делается это точно так же, как и с Java Project.
eclipce-3.jpg
В окне New Java Class нам понадобятся только следующие графы:
  • Package
    указывается пакет, в котором будет хранится наш класс. Формат имени должен быть примерно такой: ru.dmitriymx.bukkit.tutorial.
    Если в двух словах и на пальцах, то пакеты в ява – это пространство имен(namespace) или «виртуальные папки», в которые помещаются классы. Подробнее про это можно прочитать здесь: [1], [2], [3].
  • Name
    указываем название класса (у меня это DmxFirstPlugin)
Все остальные пункты оставляем как есть и жмем Finish.
Вот теперь переходим непосредственно к коду.

Пишем простой плагин
Как только мы создали новый класс, нашему взору предстает уже готовый пустой шаблон:
Java:
package ru.dmitriymx.bukkit.tutorial;
 
public class DmxFirstPlugin {
 
}
Пока это всего лишь пустой класс, который абсолютно бесполезен в быту. Будем это исправлять. Изменяем вот это:
Java:
public class DmxFirstPlugin
на это
Java:
public class DmxFirstPlugin extends JavaPlugin
Эклипс подчеркнет нам JavaPlugin, указывая на ошибку в коде. Если подвести мышь к такому подчеркнутому коду, то откроется окошко с описанием ошибки и способы ее решения. В данном случае нам нужно импортировать класс из Bukkit API, для чего выбираем пункт «Import ‘JavaPlugin’(org.bukkit.plugin.java)».
007.png
Сразу же замечаем, как над всем нашим кодом вписалась строчка
Java:
import org.bukkit.plugin.java.JavaPlugin;
Немного теоретического материала. У плагинов есть 3 основных метода: onEnable(), onDisable() и onLoad(). Первые два отвечают за включение и отключение плагина, а последний срабатывает когда плагин непосредственно подгружается в память сервера. Из этого следует, что onLoad выполняется самым первым, но сам плагин начинает работать только после вызова onEnable со стороны сервера. По выключению или перезагрузки сервера, вызывается метод onDisable.
С “точками входа-выхода” разобрались. Давайте теперь напишем что-нибудь более-менее работоспособное. Приведем общий код класса к следующему виду:
Java:
package ru.dmitriymx.bukkit.tutorial;
 
import java.util.logging.Logger;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.plugin.java.JavaPlugin;
 
public class DmxFirstPlugin extends JavaPlugin implements Listener {
  public static final Logger _log = Logger.getLogger("Minecraft");
 
  @Override
  public void onEnable() {
      _log.info("[DMX PLG] onEnable()"); //вывод произвольного текста в консоль сервера
      Bukkit.getPluginManager().registerEvents(this, this);
  }
 
  @EventHandler
  public void onJoin(PlayerJoinEvent event){
      event.getPlayer().sendMessage(ChatColor.GREEN + "Добро подаловать на сервер, " + ChatColor.GOLD + event.getPlayer().getName());
      event.setJoinMessage(ChatColor.GRAY + "Игрок " + ChatColor.GOLD + event.getPlayer().getName() + " зашел на сервер");
  }
 
  @EventHandler
  public void onQuit(PlayerQuitEvent event){
      event.setQuitMessage("");
      getServer().broadcastMessage(ChatColor.GRAY + "Игрок " + ChatColor.GOLD + event.getPlayer().getName() + " покинул сервер");
  }
}
То что мы сейчас написали – готовый плагин. Все что он делает – это пишет в чат о приходе и уходе игрока на сервер. Разбираться в его работе будем по порядку(а как иначе?).
Сперва обращу внимание на строку объявления класса. Как видно, к нашему классу добавилось расширение Listener. Не углубляясь в дебри явы, скажу проще: указывая данное расширение, мы тем самым расширяем область применения класса не только как плагина(JavaPlugin), но и как обработчика событий(Listener). А вот как раз в методе onEnable() мы регистрируем наш класс на сервере как обработчик событий(this – это указание «на самого себя», если кто забыл).
В этом и есть координальное отличие от старой системы обработки событий, которые были мною описаны ранее. Теперь, вместо того, чтобы лепить кучу классов, и запоминатьточные названия методов событий, нам достаточно расширить класс до листенера и указать с помощью «@EventHandler», какие функции будут являтся обработчиками событий. Просто? Да не то слово как просто!!
Далее следует 2 наших обработчика событий onJoin() и onQuit(). Первый отвечает а событие входа игрока на сервер, а второй – за уход. С помощью метода event.getPlayer().sendMessage(), мы можем отправить сообщение тому игроку, которое вызвал данное событие(в нашем случае игроку, который зашел). Статический класс ChatColor хранит в себе константы цветов для окраски. Думаю как им пользоваться уже видно из примера. Так же, оба наших обработчка оповещают других игроков на сервере о произошетших событиях, но делают это по разному. В onJoin() с помощью метода event.setJoinMessage() мы меняем привычное сообщение «Player joined the game» на любое другое. А в onQuit() мы поступили инача(в целях примера): убрали вывод сообщения о выходе и сообщаем об этом через метод getServer().broadcastMessage(), который просто посылает всем игрокам на сервере указанное сообщение.
Дело осталось за малым: написание важного файлика plugin.yml и упаковка всего этого в jar-файл.
plugin.yml – это файл описания плагина. В нем задается название плагина, главный класс, описание и какие комманды зарегестрировать за плагинов(об этом позже) и т.д. В нашем случае, файлик должен выглядеть так:
YAML:
name: Dmx First Plugin
main: ru.dmitriymx.bukkit.tutorial.DmxFirstPlugin
description: Простой плагин с сайта dmitriymx.ru
version: 1.0
Ой, забфл сказать, где этот файлик должен находится. Жмем правой кнопкой мыши в PackageExplorer по папке src и через меню New выбираем File. В поле File name пишем plugin.yml и жмем Finish. В открывшемся текстовом редакторе, пишем то, что я указывал выше. Всё сохраняем и переходим к последней фазе: упаковке.
Правый тык по папке src -> Export. В древе папок, открываем Java и выбираем JAR file и жмем Next. Из голочек оставляем только «Export generated class files and resources» и «Compress the contents of the JAR file». В поле JAR file указываем, куда будем сохранять полученный плагин. После нажатия на Finish, плагин можно класть в папку plugins, запускать сервер и проверять его работу.

008.png
009.png
010.png
Видите? Это совсем просто. С практикой вы наберетесь больше опыта и сможете делать более крутые плагины, даже такие легендарные как WorldEdit и WorldGuard.

Ссылки
Следующая статья:
Документация:
Полезности:
________________________________________​
 
Кстати, не стесняйтесь и пишите в комментариях вопросы, ответы на которые хотели бы увидеть в следующих уроках.
 
Отличная работа!
Крайне полезно для новичков.
 
Listener в main классе это нечто.
Вы не правы. В отдельных случаях, если плагин компактный и обрабатывается одно-единственное событие, то для чего городить целый класс?

Аналогично с обработчиком команд. Для небольшого плагина удобнее его организовывать внутри осного класса. А если у Вас обработчики команд здоровенные - то можно на каждую команду отдельный класс делать.
 
DmitriyMX, Вы молодец! Жалко такого туториала не было когда я пытался чему-то научиться.
 
Когда лепишь до лесу обработчиков событий, обработчики событий одного пакета(например player или block) нужно делить события по разным классам, иначе они начинают материться и частично не работать. Также помимо этого желательно указывать приоритет обработки классов.
 
Когда лепишь до лесу обработчиков событий, обработчики событий одного пакета(например player или block) нужно делить по разным классам, иначе они начинают материться и частично не работать. Также помимо этого желательно указывать приоритет обработки классов.

Они начинают материться и частично неработотать вовсе не из-за этого, уверяю Вас ;)

Деление listener'ов на разные типы player - block -- это наследие старой системы обработчиков событий. Просто тогда можно было ТОЛЬКО так. Сейчас с этим проще. Если у меня 3 небольших обработчика, то зачем мне создавать три разных класса?! Но раньше можно было приоритет задавать при иницилизации, теперь этого уже нельзя (для настраиваемого приоритета сейчас нужно делать два обработчика одного события и уже в зависимости от необходиомого выполнять код в одном или в другом).
Приоритет можно опускать - будет NORMAL.
 
На счёт Вас не знаю, но с версии 1.7.3 точно был(на английском).
Ну я совем не знал Java, никогда в жизни не видел Eclipse, не программировал лет восемь или семь - для меня статьи на bukkit wiki были слишком продвинутые. Я первый плагин сделал по какому-то туториалу с youtube :)
 
DmitriyMX, если Вы пользовались github'ом, то не могли бы рассказать о нём?
Я его не понимаю. Один плагин смог выложить, а второй - какая-то путаница, не то с ключами не то с чем-то ещё. Не могу разобраться.
 
Ну я совем не знал Java, никогда в жизни не видел Eclipse, не программировал лет восемь или семь - для меня статьи на bukkit wiki были слишком продвинутые. Я первый плагин сделал по какому-то туториалу с youtube :)
Я, когда первый раз писал плагин, ничего не знал о Java, были основные знания в C++(мог написать hello world и пару циклов). Первый плагин - я разобрал чужой плагин и собрал что-то нечто своё. Eclipse - до сих пор без-понятия-что-это-такое, ни с чем, кроме жвачки не ассоциируется. Далее пришлось читать статьи на bukkit'е)
 
На счет приоритетов, то они до сих пор могут выставляться(правда, я пока не нашел им применение... да и плохо понимаю). Делается это теперь так:
Код:
@EventHandler(priority=EventPriority.HIGH)

Про GitHub, то пользоваться стал им совсем не давно и только начинаю в нем разбираться. Как станет более-менее понятно, напишу статью про него (точнее связку Eclipse + EGit). Если не терпится, можете написать мне в скайп, расскажу что знаю :)

DmitriyMX, Вы молодец! Жалко такого туториала не было когда я пытался чему-то научиться.
:) Вот потому, что не было туториалов на русском, я и начал всё это писать
 
DmitriyMX, с приоритетами все просто. Жирную точку в обработке события ставить событие с наивысшим приоритетом, зато к раздаче прибывает событие с низшим.

Т.е. если у события приоритет LOWEST оно выполняется первым, если MONITOR - последним. C приоритетом MONITOR выполнять события не рекомендуется, если выполянется - то не рекомендуется тут производить тут какие-то "активные действия". С наивысшим приоритетом должны выполняться обработчики плагинов типа LogBlock и прочие антигриферско-античетерские. Которым важно отследить результат события, а не установить где-то блока или заспавнить моба (примеры с потолка).
В большинстве случаев достаточно приоритета NORMAL, у меня в Lockpick&Rent событие на PlayerInteractEvent выполняется с приоритетом HIGHEST (т.е. до MONITOR'а). Это дает возможность открыть сундук или дверь ("запертый" посредством LWC, например) и в тоже время не перебить у LogBlock'а информацию о том, кто в этот сундук или дверь влез.
 
На счет приоритетов, то они до сих пор могут выставляться
Я имел в виду, что раньше можно было создать переменную типа Priority (или EventPriority) и инициализировать её в соответствии с конфигурацией. А сейчас если ты в коде написал priority=EventPriority.NORMAL, то всё. Этот обработчик без правки кода и перекомпиляции как HIGH не запустишь.
Т.е. если по каким-то причинам Вы выносите в конфиг приоритет события, то нужно создавать количество обработчиков для каждого приоритета и уже в обработчиках проверять - в котором из них код выполнять, в каком нет. Громоздко, но увы больше никак.
 
Я не уверен, но может для таких случаев нужно использовать void registerEvent(Class<? extends Event> event, Listener listener, EventPriority priority, EventExecutor executor, Plugin plugin) ?

Код:
void registerEvent(Class<? extends Event> event,
                Listener listener,
                EventPriority priority,
                EventExecutor executor,
                Plugin plugin)
Регистрирует указанный исполнителем в данном классе событий
(Registers the specified executor to the given event class)
Параметры:
event - Тип события для регистрации (Event type to register)​
listener - Приемник для регистрации (Listener to register)
priority - Приоритетными для регистрации этого события в (Priority to register this event at)
executor - EventExecutor зарегистрироваться (EventExecutor to register)
plugin - Плагин для регистрации (Plugin to register)​
Спонсор перевода: Google Translate.​
 
Назад
Верх