Перейти к содержимому

Результаты поиска

Найдено 6 результатов с тегом bukkit

По типу контента

По секции

Сортировать                 Порядок  
  1. Команда молодости нашей... (с)

    Единственное различие при вводе команд с консоли сервера и через строку общего чата - это использование слеша ("/") в начале строки во втором случае. Что, в общем-то, логично - надо же как-то серверу различать обычный треп в чате между игроками и осмысленные команды, адресуемые именно ему. В дальнейшем для примеров использования команд будет преимущественно использоваться вариант /command, как наиболее наглядный. Однако следует иметь в виду, что при использовании той же команды с консоли сервера ведущий слеш следует опускать. Кстати, ограничений на дальнейшее использование знаков препинания в названии команд не замечено (кроме пробела, который используется для деления строки команды на элементы), чем иногда пользуются разработчики плагинов (см. команды //size, //paste плагина WorldEdit).

    Сама команда знаками пробела делится на элементы: первым элементом является имя команды. Далее следуют аргументы команды. Будут это какие-то опции или значения параметров - дело и проблемы исключительно ваши. И возиться с их парсингом и обработкой опять же вам. Как вы будете это делать - каскадами операторов if() либо парсингом строки команды через регулярные выражения - решать снова вам.

    Теперь рассмотрим подробнее те ровно два с половиной способа, с помощью которых можно в нашем плагине обработать эти команды. Первый способ, наиболее простой и универсальный, идеально подходит для небольших плагинов с более-менее статичным контентом. Если плагин ваш заточен под решение одной задачи и подразумевает работу всего-лишь с одной-двумя-тремя командами - это как раз ваш случай. Все, что вам необходимо - это наследовать в основном классе вашего плагина метод onCommand (возвращающий false в классе JavaPlugin, от которого и наследуется класс вашего плагина):

    /********************************************************************************
     * обработка события выполнения команды
    ********************************************************************************/
    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args)
    {
    	return super.onCommand(sender, command, label, args);
    }
    
    Метод onCommand является типичным обработчиком т.н. псевдособытия, которые были описаны в предыдущей статье, посвященной этим самым событиям. Баккит вызывает его в том случае, если в процессе обработки входящих данных встречает команду, подходящую для данного плагина. Что это значит? Это, прежде всего значит, что с помощью данного способа можно получить доступ только к тем командам, на которые есть "подписка" у вашего плагина. Получить эту самую подписку в простейшем случае через файл plugin.yml (также описанный в одной из предыдущих статей), который содержит основную информацию о вашем плагине. Чтобы закрепить за ним какую-либо команду, необходимо в этом файле создать секцию commands (очень подробно описана на bukkit-wiki), а в ней - подсекцию с названием, совпадающим с именем команды:
    commands:
      test:
        description: просто тестовая команда
        aliases: [tst, teeeest]
        permission: test_plugin.test
        permission-message: Все в сад!
        usage: Что я делаю не так?!
    
    Как видно из приведенного куска файла plugin.yml, плагин резервирует за собой команду /test. Тут надо понимать, что никто, в принципе, не накладывает ограничений на пересечение имен команд, описываемых в данной секции. И, в принципе, они могут совпадать с названиями команд, используемыми другими плагинами. Однако при вызове метода onCommand действует вполне логичное правило "Кто первый встал - того и тапки", так что ваш плагин вполне может оказаться с носом, но без тапок, если какой-то другой плагин, "подписанный" на данную команду, в своем методе onCommand при обработке этой же команды уже вернул true. Так что для перехвата "чужих" команд данный метод вряд ли подойдет.

    Что касается остальных параметров подсекции описания команды:
    • description: содержит произвольную строку описания. Применяется в основном для генерации "хэлпа" командой /help.
    • aliases: здесь можно перечислить т.н. псевдонимы вашей команды, которые можно использовать равноценно наряду с основным именем команды. Поскольку перечисление псевдонимов является неименованным списком, его элементы разделяются запятыми и заключаются в прямые скобки (см. статью YML и все-все-все...).
    • permission: разрешение, необходимое для использования данной команды. Если разрешение не указано, команда становится доступна всем игрокам сервера. Команды, вводимые с консоли сервера, под действие системы разрешений не попадают - это абсолютный доступ непосредственно от имени сервера.
    • permission-message: сообщение, которое будет выдано игроку, не имеющему доступ к данной команду.
    • usage: сообщение, которое выдается пользователю, если команда была введена неправильно. Применительно к разработке плагинов это звучит так: если для плагина, который был "подписан" на команду, был вызван метод onCommand для обработки данной команды и он вернул результат false.
    Теперь обратимся к параметрам самого метода onCommand:
    • CommandSender sender: объект, отправивший данную команду. Странная система иерархии классов баккита вполне позволяет отправлять команды как от имени игроков или сервера, так и (!) от имени, например, хрюнделя или летящего снежка. Однако обычно отслеживаются только первые два случая - отправка от имени игрока (sender instanceof Player) и отправка команды с консоли (sender instanceof ConsoleCommandSender).
    • Command command: сам объект команды. Поскольку методу onCommand достаются только команды, на которые у плагина есть "подписка" (читай, имеющие описание в plugin.yml), баккит по описанию команды генерит объект данного типа, для которого можно получить/установить все те параметры, что я перечислил чуть выше.
    • String label: название отправленной команды. Обратите внимание, данный параметр содержит название именно введенной команды, а не команды, описанной в конфиге. То есть, если вы воспользуетесь псевдонимом команды, параметр label будет содержать именно этот псевдоним. Добраться же до основного имени команды можно с помощью конструкции command.getName(). Что интересно, вызов метода command.getLabel() также возвращает основное имя команды, даже если был использован ее псевдоним. Чудны дела твои, Господи.
    • String[] args: массив строк, содержащих аргументы команды. Как уже говорилось выше, введенная строка тупо разбивается по пробелам и все, что следует за названием, обзывается аргументами и попадает в этот список. Логично, что массив агрументов может быть и нулевой длины.
    Что же со всем этим делать? Конечно же, обрабатывать. Для начала необходимо определить, о какой именно команде идет речь, поскольку секция commands файла plugin.yml может описывать любое их количество. Если команд предполагается не очень много, можно обойтись последовательными if():
    if (command.getName().equalsIgnoreCase("test1"))
    {
        ...
        return true;
    }
    else if (command.getName().equalsIgnoreCase("test2"))
    {
    ​    ...
        return true;
    }
    else ...
    else return false;
    
    При большем, нежели несколько штук, количестве команд можно перейти к конструкции switch-case. Суть дела от этого сильно не изменится. Следует помнить лишь об одном: в случае удачного парсинга команды ваш метод onCommand должен вернуть true. При этом вы, конечно, можете производить дополнительные проверки на корректность аргументов команды и, в случае ошибки, возвращать отправителю грозные сообщения. Но все же лучше в том случае, если название команды вами распознано, давать понять баккиту, что команда обработана. Также, чтобы вам не приходилось муторно проверять все возможные варианты, лучше всего приводить имя команды к нижнему регистру и использовать именно ее основное имя (getName), а не использовать аргумент label, который может и не совпадать с основным именем.

    Что делать с остальными аргументами команды (массив args), если они, конечно, будут? Тут однозначного ответа нет. Ведь в качестве аргументов команды могут использоваться самые различные по структуре выражения: опции: -option, -option:value, -option:"bla bla value", причем желательно, чтобы опции можно было вставлять в любом месте, а также чтобы можно было использовать сокращенные названия опций, в идеале - чтобы можно было устанавливать перекрестные зависимости между используемыми опциями; целочисленные, дробные аргументы (в том числе и значения опций), строковые выражения (как односложные так и разделенные пробелами), а так же текст до конца строки как единый аргумент. Кроме того, в некоторых случаях команды могут в зависимости от контекста использовать разные наборы аргументов, и эти ситуации также необходимо отслеживать и корректно обрабатывать. Ну и на десерт: каждая команда (а иногда и определенный набор ее параметров) могут требовать отдельного разрешения (пермишенса) на использование.

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

    В целом, если ваша команда предполагает использование простейших аргументов (например, целочисленных x y z), можно, наверное, обойтись простым их парсингом в числа непосредственно из массива аргументов args. А вот если ситуация более запущенная, возможно, лучшим выходом будет объединить все строковые аргументы обратно в строку (через пробел) и далее уже парсить ее своими средствами с учетом контекста.

    К слову сказать, основной класс вашего плагина вовсе не обязательно должен быть обработчиком консольных команд посредством метода onCommand. Данное свойство у главного класса реализуется благодаря интерфейсу CommandExecutor, который как раз и определяет требование к реализации данного метода. В принципе, вы можете создать специальный класс-обработчик команд:
    class CommandHandler implements CommandExecutor
    {
        public boolean onCommand(CommandSender sender, Command command, String label, String[] args)
        {
            ...
        }
    }
    
    который будет реализовывать метод onCommand интерфейса CommandExecutor, а затем при старте плагина создать объект этого класса и зарегистрировать его как обработчик ваших команд:
    @Override
    public void onEnable()
    {
        CommandHandler handler= new CommandHandler(...);
        this.getCommand("test").setExecutor(handler);
    }
    
    Обратите внимание, команда, объект которой (PluginCommand) вы пытаетесь получить вызовом метода getCommand, должна быть описана в файле plugin.yml, в противном случае метод вернет null, а следующий за ним вызов setExecutor, соответственно, обрушится эксцепшеном NullPointerException.

    Теперь разберем вторые полтора варианта перехвата вводимых команд, которые представляют собой не что иное, как обычные баккитовские события - PlayerCommandPreprocessEvent и ServerCommandEvent - числом 2 (два штуки). Такая вот шизофрения, случай типичный, описан в "Ярбух фюр психоаналитик унд психопатологик". Как, наверное, понятно из названий классов событий, одно из них отвечает за команды, употребляемые игроками через общий чат (посредством пресловутого "/"), а вторые поступаю в обработку непосредственно с консоли сервера. А происходят они: первое - от PlayerEvent (что позволяет через event.getPlayer() получить доступ объекту игрока, запустившего команду), а второе - от ServerEvent, который объединяет все общие события самого сервера:
    /********************************************************************************
     * обработка события выполнения команды, отправленной игроком
    ********************************************************************************/
    @EventHandler
    public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event)
    {
        String cmd_line= event.getMessage();
        ...
    }
    
    /********************************************************************************
     * обработка события выполнения команды, введенной с консоли
    ********************************************************************************/
    @EventHandler
    public void onServerCommandPreprocess(ServerCommandEvent event)
    {
        String cmd_line= event.getCommand();
        ...
    }
    
    Оба типа событий наследуют интерфейс Cancellable, что позволяет в особо запущенных случаях произвести отмену выполнения какой-либо команды. Например, создав обработчик события PlayerCommandPreprocessEvent с приоритетом MONITOR (см. статью Не думай о событиях свысока!) для обработки единственной команды /seed с единственным действием - отменой введенной команды, можно полностью перекрыть к ней доступ даже для операторов сервера. Сид мира - это святое.

    Однако главное отличие данного метода (через обработку событий) в другом: в отличие от метода onCommand они НЕ ТРЕБУЮТ описания (или регистрации, см далее) используемых команд в файле plugin.yml плагина. То есть (теоретически), вы можете через обработчик события перехватить ЛЮБУЮ команду любого плагина. "Теоретичность" заключается в том, что ваш плагин для этого должен в очереди на обработку события оказаться впереди плагина, чью команду вы хотите перехватить. Достигается это, как правило, повышением уровня приоритета обработчика события. Конечно, если плагин, чью команду вы перехватили, зарегистрировал обработчик команд с опцией ignoreCancelled= true, это вас не спасет. Хотя и тут можно нарезать левую резьбу: заменить название команды на что-нибудь типа /dummy и отдельным обработчиком с наименьшим приоритетом "погасить" ее в самом конце "очереди".

    Из преимущества, которое не требует предварительной регистрации команды, проистекает главный недостаток данного метода: для команды не создается (да и не может) объект типа Command, в котором перечислены все ее основные характеристики. Команда поступает на обработку именно в том виде, в каком она была введена в консоли сервера (ServerCommandEvent) или в чате (PlayerCommandPreprocessEvent). Однако и тут имеются различия: для ServerCommandEvent эта строка получается вызовом event.getCommand(), а для PlayerCommandPreprocessEvent - через вызов event.getMessage() (шизофрения прогрессирует). Причем во втором случае, обратите внимание, полученная строка команды будет начинаться со слеша ("/").

    Что со всем этим делать - опять же решать вам. Выше я уже описал типичные трудности и наметил некоторые варианты их преодоления. Разница тут только в том, что в случае с использованием обработчиков "командных" событий вам придется вручную разбираться с алиасами (псевдонимами), но не придется собирать в строку массив аргументов.

    Кроме того, отсутствие явной регистрации команды приводит к еще одному серьезному недостатку: при вводе части команды пользователь может нажать клавишу Tab и получить контекстную подсказку о подходящих командах. Если команда не будет явно зарегистрирована в системе, логично, что она не войдет в этот т.н. таб-комплит (TabComplete) список. Что же делать, если команд у вашего плагина набралось уже порядочно, структуру они имеют довольно сложную, и к тому же активно допиливаются, что превращает их синхронизацию с описанием в plugin.yml в сущий кошмар? Тогда, наверное, будет проще организовать их декларацию в коде плагина (к чему я, собственно, и пришел в своих плагинах) - вместе с возможными наборами аргументов, их типами, допустимыми опциями и их перекрестными зависимостями. А регистрацию команд производить программно - при запуске плагина (в методе onEnable).

    К сожалению, подобная схема регистрации команд требует особых низкоуровневых методов ведения допроса доступа к закрытым полям объектов сервера, а их мы еще не проходили. Обещаю, что как только мы рассмотрим тему "рефлексных методов", первым делом опишу как раз способ программной регистрации команд в системе. Для знакомых с данными методами даю небольшую подсказку: метод public boolean register(String label, String fallbackPrefix, Command command) объекта private final SimpleCommandMap commandMap менеджера плагинов вашего сервера.

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

    • 11.01.2016 00:05
    • by vasco
  2. Не думай о событиях свысока!

    Тех, кто привык к системе обработке событий Windows, где события кидаются в общую очередь, и далее все, кто заинтересован в их обработке, должны периодически обращаться к менеджеру этой очереди с вопросом "Есть чо?", поначалу, наверное, будут немного напрягаться от методики, принятой в бакките: здесь менеджер событий при поступлении нового объекта бросает все дела, тормозит всех, вытаскивает список подписчиков данного события и начинает тормошить их всех вопросом "Ннада?". Сложно сказать, какой метод лучше или хуже, и у того и у другого есть свои плюсы и минусы. Тем более, что процесс обработки я описал только черновыми штрихами, без конкретики. Например, событие можно объявить асинхронным, что, по идее, вынесет его обработку в отдельный поток.

    Самое главное, что должен уметь делать класс, ответственный за обработку события (если, конечно, он не абстрактный), это выдавать по запросу список обработчиков (подписчиков, слушателей, наблюдателей за наблюдающими) данного события. Как понятно из контекста, список этот должен быть статическим, то есть привязанным именно к классу, а не к конкретному объекту события:

    public class BlaBlaEvent extends Event
    {
        private static final HandlerList handlers = new HandlerList();
     
        public HandlerList getHandlers()
        {
            return BlaBlaEvent.handlers;
        }
    }
    Обратите внимание: несмотря на то, что сам список обработчиков является статическим, метод getHandlers(), возвращающий его, не является статическим. В этом просто нет особой необходимости, поскольку он используется при обработке конкретного экземпляра события данного бла-бла-типа (при регистрации обработчиков баккит действует через функции пакета java.lang.reflect, которым статичность свойств и методов до одного места). В общем и целом, как раз именно этот список и является тем самым списком обработчиков, по которому проходится менеджер событий баккита с вопросом "Ннада?".

    Еще одной немаловажной особенностью многих баккитовских событий является возможность их отмены. Конечно, далеко не все события обладают такой способностью всилу естественных причин: например, какой смысл делать отменяемым событие PlayerQuitEvent, если игрок все равно уже вышел с сервера, хотите вы этого или нет. Однако, например, PlayerMoveEvent вполне логично сделано отменяемым, чтобы можно было в зависимости от текущих условий отказать игроку в перемещении в заданную точку. Некоторые события, как например PlayerLoginEvent, реализуют механизм отмены собственными методами (allow и disallow) - флаг Гондураса им для этого в руки. Однако если событие следует классической схеме отмены, оно должно наледовать интерфейс Cancellable и реализовывать его методы isCancelled и setCancelled:
    public class BlaBlaEvent extends Event
    {
        private static final HandlerList handlers= new HandlerList();
        private boolean cancel= false;
     
        public HandlerList getHandlers()
        {
    	    return BlaBlaEvent.handlers;
        }
     
        public boolean isCancelled()
        {
            return this.cancel;
        }
     
        public void setCancelled(boolean cancel)
        {
            this.cancel= cancel;
        }
    }
    Смысл механизма отмены разъясню чуть ниже.

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

    Всего в окружении баккита генерится несколько десятков видов событий, разбитых на глобальные группы (производные от глобальных классов): события игрока (PlayerEvent), события блоков (BlockEvent), события сущностей (EntityEvent), события мира (WorldEvent), общие события сервера (ServerEvent), события погоды (WeatherEvent) и еще несколько несистематизированных событий. В целом они покрывают 99% потребностей разработчиков плагинов, хотя иногда бывает чертовски обидно, когда не находишь даже примерно подходящего события, за которое можно было бы зацепиться: например, как уже говорилось в предыдущих статьях, полная засада ожидает при попытке отловить проведение сделки с деревенским жителем.

    В любом случае, как бы ни был объявлен класс события, какие бы аргументы он ни содержал в конструкторе, рано или поздно все заканчивается созданием экземпляра этого события и запуском его в обработку:
    BlaBlaEvent event= new BlaBlaEvent(...);
        Bukkit.getServer().getPluginManager().callEvent(event);
    Это как раз та самая точка, где генерится сообщение-индикатор возникшей ситуации. Располагаться она может в любом месте кода вашего плагина. В первой строчке приведенного примера создается экземпляр нужного класса события. Второй строчкой мы отправляем созданный объект события менеджеру событий баккита (который по совместительству является и менеджером плагинов баккита).

    Поскольку все пространство классов java-машины едино, вы (или кто-то еще) можете получить доступ к обработке абсолютно любого события (если, конечно, оно объявлено как public). Таким образом, создание кастомных событий интересно больше не вам самому внутри собственного плагина (у вас и так море возможностей для внутриплагинного взаимодействия компонентов), а скорее для того, чтобы предоставить возможность разработчикам сторонних плагинов получить доступ к "достижениям" вашего плагина.

    А вот теперь настало время взглянуть на проблему с другой стороны, поскольку нас, как правило, будет интересовать не генерация событий, а их обработка. Давайте присмотримся к этому механизьму поближе. Как уже было сказано выше, менеджер событий опрашивает все слушателей... как же нам попасть в ряды этих самых слушателей? Для этого вы должны выполнить 3 следующих действия:

    1. Класс, методы которого будут заниматься обработкой тех или иных событий, должен реализовывать интерфейс Listener. неважно, будет ли это ваш главный ласс плагина или один из второстепенных, но "заточенных" именно под это. Пускай это (для простоты) будет основной класс нашего тестового плагина:
    public class TestPlugin extends JavaPlugin implements Listener
    {
        ...
    }
    2. Каждый, метод, промышляющий обработкой событий, должен иметь один единственный параметр, параметр этот должен быть производным от класса Event. Собственно, класс этого параметра как раз и определяет - какой тип событий будет обрабатывать данный метод. Кроме того, метод-обработчик должен предваряться директивой @EventHandler (о директивах как-нибудь постараюсь написать чуть подробнее):
    @EventHandler
    public void onBlaBla(BlaBlaEvent event)
    {
        ...
    }
    Название метода значения не имеет (можно даже в 1 классе создать несколько методов, обрабатывающих одно и то же событие). Однако хорошим тоном считается начинать название метода с "on" и заканчивать названием класса события без "Event".

    3. "Где-нибудь" (обычно при запуске, в методе onEnable класса плагина) необходимо произвести регистрацию объекта класса-обработчика событий (обратите внимание: не метода, а именно объекта класса) в менеджере событий баккита:
    @Override
    public void onEnable()
    {
        getServer().getPluginManager().registerEvents(this, this);
    }
    Первым аргументом вызова метода registerEvents является класс-обработчик событий. Вторым - плагин, содержащий данный класс. Поскольку в нашем случае и то и другое являются одним и тем же объектом, а вызов производится из внутреннего метода, в качестве обоих аргументов выступает this.

    В очень упрощенном виде алгоритм работы регистратора событий баккита (на самом деле - регистратора обработчиков событий) выглядит примерно так: указанный класс просматривается на предмет поиска методов, помеченных директивой @EventHandler и имеющих в качестве единственного аргумента объект класса, производного от Event. При нахождении такого метода менеджер получает список обработчиков handlers такого класса события и добавляет к этому списку найденный метод (на самом деле, там особо извращенным способом создается специальный метод-исполнитель, но это уже никому не интересные детали). Таким образом, каждый метод, помеченный директивой @EventHandler класса, производного от Listener, ставится в очередь (- В очередь, сукины дети, в очередь!! (с)) подписчиков того или иного типа (класса) событий.

    И вот тут самое время вернуться к свойству отмены события, а также рассмотреть систему приоритетов. В самом общем виде директива @EventHandler объявляется следующим образом:
    @EventHandler(ignoreCancelled= true|false, priority= EventPriority.XXXX)
    Параметр ignoreCancelled (по-умолчанию false) сообщает, должен ли обработчик получать т.н. "отмененные" события. Как было описано выше, для сообщений, реализующих интерфейс Cancellable, можно установить (через вызов setCancelled) признак отмены данного события. После этого данное событие будет передаваться только обработчикам, для которых установлен признак ignoreCancelled (true).

    Свойство priority может принимать следующие значения: LOWEST, LOW, NORMAL (по-умолчанию), HIGH, HIGHEST, MONITOR. Как понятно из контекста, наименьший приоритет имеют обработчики LOWEST, наибольший - нет, не HIGHEST, - MONITOR. Их в документации настоятельно рекомендуют использовать только для регистрации самого факта наступления события, без попытки его изменения. Например, это может пригодиться для логгера действий игрока (см. Prism, LogBlock, CoreProtect). В целом, если нет веских причин, следует использовать значение NORMAL.

    В свете вышесказанного, алгоритм действий менеджера при обработке того или иного события может быть примерно следующим: список обработчиков события просматривается несколько раз, сначала событие передается обработчикам, имеющим статус приоритета MONITOR, затем обработчикам с приоритетам HIGHEST, и так далее до LOWEST. При этом, если событие наследует интерфейс Cancellable и в какой-то момент оно помечено как отмененное, в дальнейшем оно передается только обработчикам, у которых признак ignoreCancelled равен true. Такие вот пирожки с котятами.

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

    Что же можно сделать при обработке события? Да что угодно. Можно, например, при обработке события перемещения игрока проверять его попадание в определенный регион и по достижении его выдавать соответствующее сообщение. Следует помнить лишь о том, что обработка большинства событий производится в синхронном режиме (в общем потоке сервера, с приостановкой всех остальных действий), что потенциально может привести к лагам, а в исключительных случаях - к падению сервера (как правило, через 15-30 секунд зависания). Так что при выполнении операций, требующих значительных временных ресурсов, выделяйте подобные действия в отдельный поток (постараюсь потом поподробнее остановиться на классе Runnable и всем, что с ним связано).

    Что я помимо этого имею сказать за события: кроме описанных выше классических событий, я бы выделил еще т.н. псевдособытия: для некоторых баккитовких классов (в основном это касается класса JavaPlugin) предусмотрен ряд шаблонных методов, название которых, как и рекомендуемое мной для обработчиков событий, начинается с "on". Эти методы также вызываются в конкретных ситуациях и служат индикаторами их наступления, а, соответственно, при наследовании могут быть напрямую использованы как обработчики данных "событий". В частности, для JavaPlugin определены следующие подобные методы:


    - onCommand - запускается при получении плагином консольной команды (команда должна быть описана в файле plugin.yml в секции commands (см. предыдущую статью). Особо обращаю внимание: если за плагином не зарезервирована конкретная команда, вызова данного метода не произойдет.
    - onTabComplete - вызывается в ситуации, когда пользователь нажимает клавишу Tab после ввода команды. Можно использовать для того, чтобы отфильтровать список команд, которые стоит показывать пользователю в качестве подсказки, а которые нет.
    - onLoad - вызывается при загрузке плагина (не путать с onEnable, о различиях между этими моментами рассказано в предыдущей статье).
    - onEnable - вызывается в момент запуска плагина (вызовом setEnabled(true|false) можно управлять данным процессом в зависимости от проведенных проверок. Однако будьте внимательны, в зависимости от значения аргумента могут повторно вызываться методы onEnable или onDisable, что потенциально может привести к зацикливанию и крашу сервера).

    - onDisable - вызывается в момент остановки плагина (по команде /reload, ныне репрессированной, или в момент остановки сервера).


    Данные псевдособытия, хоть и не попадают под каноническое описание, тем не менее позволяют без особых затрат для простейших плагинов реализовать базовые действия. А уж методы onEnable и onDisable по-любому перекрываются всеми плагинами.

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

    • 18.01.2016 00:35
    • by vasco
  3. Эклипсом по баккиту

    Дабы не изобретать велосипед, дам лучше ссылку на прекрасную статью Пишем простой плагин для Bukkit за авторством DmitriyMX, в которой он простыми русскими буквами описал в том числе и процесс установки и первоначальной настройки Эклипса. В свое время осваивал сей процесс именно по этой статье, не вижу причин, почему бы вам не сделать то же самое. К сожалению, некоторые моменты (и ссылки) в статье уже устарели, но в общем и целом она пока является вполне годным материалом для новичков.

    Тезисно изложу некоторые основные моменты:


    1. Эклипс качается в виде инсталлятора (для пользователей Windows, предпочтительно 64-битный - если позволяет система). Запускается, в открывшемся списке выбирается первый пункт (что-то вроде "Eclipse IDE for Java developers"). Далее выбирается версия среды (Eclipse - "затмение", поэтому названия разных версий посвящаются тому или иному объекту на небесной тверди), указываются путь установки, путь к установленному JRE. После этого производится непосредственно установка среды.
    2. При первом запуске Эклипса вас попросят определиться с т.н. рабочим пространством (Workspace), то есть, указать каталог, где в дальнейшем будут размещаться ваши проекты. Надеюсь, вы достаточно опытны, чтобы не кидать все это на рабочем столе или в каталоге с установленным Эклипсом? Выделите под это дело отдельный каталог на диске D, например, d:\Projects\ - и искать будет проще, и меньше риска, что в один прекрасный солнечный день вы начнете биться головой об угол двери, когда ваша винда (да, вышесказанное относится в основном к счастливым обладателям продукта от дяди Билли) прикажет жить долго и счастливо.
    3. Желательно в настройках среды (Window->Preferences) включить нумерацию строк в текстах исходных файлах (раздел General->Editor->Text Editors, галочка "Show line numbers"), а также в разделе General->Workspace установить кодировку по-умолчанию (в "Text file encoding" выбрать "UTF-8") - это позволит вам в дальнейшем избежать проблем с русскими символами при запуске ваших плагинов. Я обычно еще в разделе General->Editors->File associations добавляю расширения "*.properties", "*.yml" и "*.json", которым в качестве дефолтного текстового редактора назначаю Notepad++.


    Теперь пару слов о том, куда будет складываться конечный результат, то есть откомпилированный jar-файл плагина. Падать он должен в папку plugins/ вашего сервера. А какой версии должен быть сервер, спросите вы? В идеале - самой новой (1.8.8 на момент написания статьи). Однако на деле это часто зависит от того, какая версия используется на вашем рабочем сервере.

    А сколько вообще нужно серверов? Тут тоже одной фразой не ответишь. Если вы пишете плагины исключительно для собственного сервера, то их должно быть как минимум два: один, назовем его условно "рабочий" (work) - это сервер, на котором, собственно, и тусуется народ. Он неприкосновенен, плагины на нем обновляются только тогда, когда есть полная уверенность, что они будут работать на нем бэз шуму и пыли. Причем делать это желательно через остановку сервера (в крайнем случае - через его перезапуск сразу после замены файлов). Второй, назовем его "подопытный" (dev) - на нем вы будете тренироваться, как на кошечках, именно в его папку plugins/ и следует сваливать скомпилированные jar-файлы ваших плагинов. При этом перезапуск плагинов на нем производится, как правило, командой reload с консоли сервера (либо /reload из клиента). Хоть баккит в последних версиях настоятельно не рекомендует использовать эту команду, но от ее поддержки не отказался. Да, при подобном способе перезапуска возможны ошибки (и рано или поздно они случаются), но в 99% релоадов этот процесс проходит безболезненно.

    Очень желательно, чтобы серверы эти располагались на разных машинах, дабы эксперименты с dev-сервером не привели к подвисанию рабочего. Конечно, запускаются они в разных "песочницах" ява-машин, однако и без этого есть множество способов сделать каку: от конфликтов при доступе к орбщим файлам до манипуляций с общими базами данных. Да и настраивать серверы при наличии двух машин будет проще - можно будет полностью скопировать каталог рабочего сервера на свой компьютер, сохранив порт доступа и получив идентичный набор плагинов и используя копию рабочей карты (которую в дальнейшем можно будет просто обновлять с рабочего сервера).

    В особых случаях, когда рабочий сервер является серьезным объектом, как например наш Мinecrafting.ru, в пару к нему на той же машине поднимается тестовый сервер (с другим рабочим портом и вайт-листом). Он может либо работать в постоянном режиме, как рабочий, либо запускаться по требованию. С некоторой периодичностью на него заливается бэкап рабочей карты. Используется он для финишной шлифовки плагинов, отработки пермишенсов (система прав на нем копируется, как правило, с рабочего сервера). На нем же производится строительство объектов для рабочей карты, которые затем в виде схематиков копируются на рабочий сервер.

    В общем и целом, ваш dev-сервер должен располагаться в легкодоступном месте, например, d:\bukkit_1.8\. Указывать версию для каталога сервера очень желательно, ведь у вас еще может быть сервер для более старой версии, например, d:\bukkit_1.7\, на котором вы будете тестировать совместимость вашего плагина. Просто менять jar-файл сервера разных мажорных версий в одном и том же каталоге не получится, поскольку структура каталогов и файлов в разных версиях различается довольно серьезно.

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


    1. Не пихайте все в одну кучу. Если ваш плагин выполняет какую-то простейшую задачу (например, выдает игроку сообщение при входе на сервер), это еще прокатит, однако в дальнейшем ни к чему, кроме путаницы, не приведет. Образно это можно представить как 103: десять строк -> десять методов -> десять классов. То есть, методы ваших классов должны содержать не более чем по 10 строк, в классе не должно быть больше десятка методов, а пакет должен состоять не более чем из 10 классов. Конечно же, это недостижимый идеал. Не нужно упираться рогом для того, чтобы подогнать под него ваш проект. Однако ненавязчивое стремление к нему очень хорошо дисциплинирует, значительно повышает читабельность текста и в итоге позволяет значительно отодвинуть тот неизбежный момент, когда проект без коренной модернизации попросту начинает "сыпаться". Если есть возможность вынести некий функционал в отдельный метод, а то и в отдельный класс - это надо сделать. Это позволит вам в дальнейшем просто вставлять вызов необходимого метода, а не заниматься копипастом. Опять же, если в одном пакете скопилось несколько десятков файлов - это уже повод задуматься о создании подпакетов и вынесении туда определенных классов, группируя их по схожести функций.


    2. Давайте переменным (и уж тем более методам, классам и пакетам) осмысленные названия. Я еще допускаю варианты i, j, k в качестве названий переменных цикла - это вполне себе устоявшаяся практика. Но все остальное должно иметь название, отражающее их смысл: buffer, UserSystem, ru.vasco.UserSystem. Как только вы опуститесь до употребления чего-то вроде var12, Class1, Class2 - ждите проблем. Также желательно, чтобы названия элементов были как можно более полными (читай, длинными). Как ни странно, это, как правило, ускоряет доведение кода до рабочего состояния. Какую конкретно стилистическую систему применять для именования объектов - дело ваше, но система эта должна быть.


    3. Забудьте про использование операции конкатенации для склейки строк:

    String str= "Пользователь " + player.getName() + " вошел на сервер";

    Одна строка, конечно, погоды не сделает, но вот сотни строк, ежесекундно сливающиеся в объектно-ориентированном экстазе, рано или поздно скушают память вашего сервера, сколько бы вы ее не выделили. Причем проблема даже не в скушанной памяти как таковой, а во времени, которое сборщик мусора (Garbage collector) виртуальной машины (JVM) потратит на ее освобождение. Проблема эта (конкатенации строк) связана с особенностями реализации строк в языке Java и лечится следующим способом:

    StringBuilder str= new StringBuilder("Пользователь ").append(player.getName()).append(" вошел на сервер");
    ...
    ... str.ToString() ... // преобразование объекта StringBuilder в строку

    Да, получается немного длиннее, но, поверьте, ваш сервер вам будет весьма благодарен.


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


    5. Не используйте без необходимости конструкции try...catch как средство "исправления" ошибок времени выполнения программы. Вернее, используйте их только тех местах, где их использование совершенно необходимо. Например, при вызове методов пакета lang.reflect или при работе с запросами к базам данных. По-первости очень соблазняет возможность закрыть все проблемы конструкцией:

    try
    {
    ...
    }
    catch (Exception e) {}

    Казалось бы, нет ошибки - нет проблемы. Однако на самом деле ошибка никуда не делась, она просто не отображается в логе и в консоли. Только узнаете вы об этом какое-то время спустя и только по косвенным результатам - в основном по неправильной работе алгоритма обработки данных. И лечить эти ошибки, поверьте, будет гораздо сложнее. Так что учитывайте парадоксальную вещь: если в консоль сыпятся исключения - это хорошо, значит можно посмотреть - что это за исключения, найти метод, который их генерит и быстро устранить причину этого. В дополнение к этому небольшое замечание: если при вынужденном использовании конструкции try...catch есть возможность как-то конкретизировать ситуацию - это, опять же, надо сделать. Например, при работе с базами данных следует использовать конструкции вида:

    try
    {
    ...
    }
    catch (SQLException e)
    {
    ...
    }

    Это позволит перехватывать только исключения sql-запросов, пропуская все остальное дальше. Причем даже в этом случае очень желательно не "гасить" ошибки, а как-то реагировать на них, например, сообщением в консоль сервера.


    6. Основная часть кода программы, плотно взаимодействующей с пользователем - это т.н. "защита от дурака". Поэтому код вашего плагина не менее чем на 75% должен состоять из конструкций if(). Не ленитесь проверять и перепроверять все значения, поступающие на вход. Это убережет вас от постоянных эксцепшенов в консоли а то и от крашей сервера. Желательно все проверки вынести на самую раннюю стадию: во-первых, это разгрузит ваш основной код от лишнего обвеса, как-бы разделив его на две части - блок взаимодействия с пользователем и основной целевой блок, а во-вторых, лишние проверки, вынесенные наверх из тела рабочего цикла (а то и неоднократно вложенных циклов) позволят сэкономить пару-тройку шекелей милисекунд/мегабайт из выделенных плагину ресурсов сервера.


    7. При использовании т.н. generic-классов, например, ArrayList или HashMap не ленитесь указывать, чем параметризируется данный класс:

    Map<String, Player> players= new HashMap<String, Player>();
    List<ItemStack> items= new ArrayList<ItemStack>();

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


    8. Заведите в корне вашего проекта файлик todo.txt и записывайте туда построчно все, что вам хотелось бы (или вас попросили) допилить в вашем плагине. Если этого не делать, через две недели вы благополучно об этом забудете. Замечания и пожелания можно предварять знаком '-', после реализации задуманного можно менять его на '+'.


    Вот теперь, наверное, можно уже поговорить о том, из чего состоит плагин и как он работает.

    • 15.12.2015 23:20
    • by vasco
  4. Ведро с гайками

    Основным достижением данной модификации сервера майна является серьезная переработка серверного цикла: была проведена серьезная работа по оптимизации обработки чанков, сущностей, обмена с клиентской частью. Переработан компонент спавна мобов, в бакките тысячи мобов уже не являются проблемой для сервера. Также под это дело появились дополнительные настройки в собственном конфигурационном файле (bukkit.yml). Однако с точки зрения разработчика плагинов главная заслуга баккитовцев не в этом. Главное, что они сделали - это сконструировали великолепный механизм взаимодействия (Bukkit API) между плагинами и сервером. Хотя, надо сказать, сделали они это несколько странным способом: создали в пакете org.bukkit дополнительную иерархическую структуру с интерфейсами, которые полностью повторяют своими названиями классы в пакете org.bukkit.craftbukkit.v1_X_RY, только не имеют в названии префикс <Craft>. То есть, класс CraftEntity из пакета org.bukkit.craftbukkit.v1_X_RY.Entity наследует интерфейс Entity из пакета org.bukkit.Entity, причем все события и методы работают именно с объектами Entity, по сути, с интерфейсами. Умом я понимаю, что это связано с ограниченностью явы в плане множественного наследования классов, но в душе воротит от такой двойной бессмысленной работы.

    Что еще ощутимо напрягает, так это методы, которыми действуют (а может - уже "действовали") "баккитовцы": баккит не является в чистом виде надстройкой над NMS, как, казалось бы, должно быть при строгом следовании принципам ООП, он с NMS образует чудную мешанину из кода, вызовы идут не только сверху вниз, как обычно происходит, в коде классов NMS не менее часто встречаются вызовы "наверх", к методам баккита. Особенно часто это происходит в ключевых точках генерации событий (баккитовских). В принципе, логика этого явления понятна: если у исходного продукта (NMS) нет достаточного количества контрольных точек (событий), к которым можно "прицепиться" для обработки тех или иных ситуаций, нет другого пути, кроме как вручную формировать такие точки, меняя напрямую его код. Однако методы, с помощью которых это было достигнуто, вызывают иногда по меньшей мере удивление. Хотя, нам тут просто сидеть и рассуждать - как оно на самом деле надо было сделать, и как бы мы на самом деле сделали все гораздо лучше (быстрее, дешевле - нужное подчеркнуть).

    Собственно, вот эта самая совокупность интерфейсов из пакета org.bukkit, уже, как видно из названия, изолированная от версии NMS, и соответствующий им набор классов org.bukkit.craftbukkit.v1_X_RY, все еще привязанный к конкретному NMS, и называются Bukkit, он же баккит, он же ведро - прошу любить и жаловать. По сути, его же можно называть и CraftBukkit, что иногда и делают, хотя идеологически это все же немного разные вещи: CraftBukkit является модернизацией оригинального майнкрафт-сервера, а Bukkit является программным интерфейсом (API), связующим звеном между сервером и плагинами. Однако все уже давно привыкли рассматривать это как единое целое и привычно используют термин Bukkit.

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

    Зачем же люди морочатся с низкоуровневым доступом к объектам NMS, рискуя заработать несовместимость своего плагина с будущими версиями сервера? Дело в том, что даже баккит, при все богатстве его средств, не всегда может обеспечить полный спектр, так сказать, услуг и сервиса. Например, в нем до сих пор нет нормального интерфейса для работы со сделками торговца-непися, нет возможности отправить клиенту кастомный пакет, многие характеристики сущностей также доступны только средствами NMS. Поэтому волей-неволей периодически приходится лезть на уровень NMS и добывать информацию оттуда. Делается это, как уже было сказано выше, через метод getHandle():

    void process(Entity entity)
    {
    CraftEntity ce= (CraftEntity)entity;
    net.minecraft.server.v1_8_R3.Entity handle= ce.getHandle();
    if (handle instanceof net.minecraft.server.v1_8_R3.Entity)
    {
    ...
    }
    }

    То есть, сначала объект приводится к классу CraftBukkitа, соответствующему данному интерфейсу, а уж затем для полученного объекта пытаемся получить соответствующий ему NMS-объект. Желательно при этом проверять полученный результат на "вшивость", дабы не получить исключение со всеми вытекающими. Можно, в принципе, опустить эту проверку, поскольку баккитовский объект в отрыве от соответствующего ему NMS-объекта - это нонсенс. Но береженого, как говорится, бог бережет. Тем более, что сам баккит именно так и делает внутри методов своих классов. Отдельно нужно отметить, что иногда (в редких случаях) нет прямого соответствия классов баккита и NMS и, соответственно, у такого класса нет метода getHandle(), либо он может называться иначе. Исходники вам в помощь, бесценный кладезь информации по программированию на яве вообще и по устройству майнкрафтовского сервера в частности.

    Самое интересное, что на этом дело не закончилось. Компания еще более ушлых товарищей решила, что баккит уже не торт, и запилила свою сборку на его основе, назвав ее Spigot. С потоками и таймингами. Функционально они добавили всего-ничего, но вот оптимизировали они его еще более серьезно, добавив по сложившейся традиции в систему еще один конфиг-файл spigot.yml, в котором также имеется куча настроек по дальнейшему тюнингу системы. Особенно хорошо преимущества спигота раскрываются на многопроцессорных системах и с хорошим онлайном.

    А потом, в один прекрасный день, баккит как-бы приказал жить долго и счастливо. По крайней мере, народ высказал свое твердое и категоричное "фи" на известную тему (см. Страсти по ведру, еще и еще). А потом, после слива толстого небритого мужика в дурацкой шляпе и продажей всего нажитого жадному Билли дела и вовсе пошли по известному маршруту. В итоге компания негодующих активистов объединилась для создания мегамодификации мейнкрафт-сервера под названием Sponge (губка) на основе не менее легендарной системы тюнинга клиента майнкрафта Forge. Обещают задвинуть и баккит и спигот (тем более, что спиготовцы, вроде как, тоже решили поучаствовать в гешефте). Что они там наваяли еще не видел, но боюсь даже представить, во что выльется переделка всех написанных плагинов под новый API. Но, похоже, рано или поздно сделать это все равно придется.

    Кстати, об исходниках. Поскольку исходных кодов ни NMS ни баккита в свободном доступе нет и не предвидится, приходится как-то выкручиваться. Для выкручивания используется обычно маленькая, но гордая весьма полезная утилита FernFlowe. Я использую ее с небольшим патчиком, который исправляет кое-какие огрехи декомпиляции сложных классов. Пропатченный FernFlower можно скачать с нашего сайта. Утилита консольная, поэтому для декомпиляции jar-файлов использую обычно командный файл такого вида:
    java -jar fernflower.jar %1 decomp\
    pause
    Как видно из примера, в каталоге с утилитой необходимо создать подкаталог decomp/. После этого перетаскиваем мышью (в винде, конечно) jar-файл, который нуждается в декомпиляции, на командный файл, в результате чего тот стартует и запускает утилиту fernflower.jar, передавая ему в качестве параметра декомпилируемый файл. Утилита шуршит некоторое время, захламляя черный экран белыми букафками, а затем в каталог decomp/ скидывает декомпилированный файл (с тем же именем и тем же расширением), в котором все файлы типа .class заменены декомпилированными файлами типа .java. Обычно я сразу распаковываю полученный jar с исходниками в каталог decomp/, в результате получаю каталог с именем исходного файла и набором распакованных исходников в нем. Очень удобно копаться, не нужно каждый раз лазить в архив.

    Первое, что вы должны декомпилировать, будет сам баккит (bukkit.jar), либо spigot.jar, если вы используете данную модификацию. Процесс это неппыстрый, минут 20 на покурить-оправиться у вас точно будет. Зато потом вы получите неплохой справочник по внутренностям вашего сервера, причем в текущей рабочей версии. Поверьте, это вам не раз еще пригодится для понимания особо сложных моментов. Кроме того, с помощью данной утилиты я частенько декомпилирую различные плагины (не всегда удается добыть исходники, да и иногда проще закинуть его на декомпиляцию, чем искать их в инете, благо обфускацию из плагинописателей почти никто не использует) на предмет посмотреть - как оно там все устроено унутре. Очень познавательное чтиво, надо сказать - почерпнул много ценных идей, чего и вам желаю.

    В общем, баккит - зло, но меньшее из имеющихся. Работать с ним можно и нужно. И совсем неплохо было бы уметь это делать.
    - У меня вопрос к свидетелю, можно? Спасибо. Скажите, подсудимый испытывал личную неприязнь к потерпевшему?
    - Да, испытывал... Он мне сказал: "Такую личную неприязнь я испытываю к потерпевшему... что кушать не могу!"
    к/ф "Мимино"

    В следующий раз, наверное, слегка затронем конфигурационные файлы (yml и json) и перейдем непосредственно к плагинам.

    • 26.11.2015 20:10
    • by vasco
  5. Страсти по Ведру. Пишу вам, дорогая редакция...

    Когда мы только решили попробовать улучшить то, что предложил сообществу hMod три с половиной года назад, никто из нас не мог предсказать грядущий успех проекта Bukkit. После довольно тяжелого старта(hMod внезапно исчез без предупреждения, оставив нас, только-только начавших свой проект, заполнять получившийся вакуум), мы приняли вызов и сумели сделать удобную для использования и пользующуюся популярностью альтернативу другим модифицированным версиям сервера. Мы зашли так далеко, что наша деятельность даже помогла улучшить исходный сервер Minecraft. Я уверенно и с гордостью могу заявить, что Bukkit был и является успехом: мы не только сумели предоставить платформу для сотен и тысяч разработчиков, мы также часто предоставляя нашим администраторов серверов со спокойной душой, зная, что последние подвиги и дыры в безопасности были рассмотрены своевременно и надлежащим образом.

    С самого начала мы столкнулись с различными препятствиями, одно из которых мы, к сожалению, так и не смогли преодолеть, несмотря на все наши усилия: юридический барьер лицензирования и разрешений. С начала работы над Bukkit и даже во время нашей работы с hMod мы знали, что весь наш труд – несмотря на все благие намерения – вступает в сумрачную зону юридического права. Поэтому, моим приоритетом на старте было желание сделать все максимально корректно: уведомить Mojang и постараться получить разрешение на продолжение проекта и обсудить вопросы лицензирования. К несчастью, мы не смогли наладить контакт с Mojang и лично пообщаться с Нотчем и Джебом (которые заявили, что, несмотря на то, что им не нравятся наши методы работы, они понимают, что альтернатив нашей разработке нет, поэтому они согласны с тем, что мы делаем), мы не смогли встретиться с юристами Mojang чтобы получить юридическое разрешение на продолжение нашей работы, и мы не смогли разобраться с нашими проблемами лицензирования. До сегодняшнего дня наш проект находился в юридическом Лимбо (vasco: ближайший аналог - междумирье, чистилище), с весьма сомнительным статусом лицензирования, и с нашей невозможностью что-либо изменить в сложившийся ситуации.

    С осознанием этого факта пришёл страх того, что в любой момент Mojang могут решить, что им не нравится то, что мы делаем, и запретить нам продолжать работу – то, что мы ожидали в течении всего первого года разработки, и что, к нашему удивлению, так и не случилось. В итоге, сознательное решение Mojang не предпринимать каких-либо действий по отношению к Bukkit дало нам уверенность в продолжении работы (то, что я уверен, администраторы серверов могут соотнести с недавним введением правил EULA), и мы даже получили подтверждение от Нотча о том, что мы можем продолжать разработку как и раньше.
    Перейдем к недавней ситуации, вызванной тем, что Mojang сделали четкое и внезапное решение о внедрении EULA с целью изменить сложившийся облик Minecraft как Pay-to-win (vasco: донат, да-да, он самый :) ) игры. Хоть я и целиком понимаю причины этого решения и целиком поддерживаю их миссию по закрытию Pay-to-win серверов, мне тяжело поддерживать их резкий разворот на 180 градусов и стремление к принудительному внедрению тех правил, которые игнорировались с начала существования Minecraft. К тому же, нельзя не обратить внимание на тот факт, что внезапное вредрение EULA может повредить дальнейшему развитию Bukkit.
    Особенно отметим: «Главным правилом является то, что вы не можете распространять всё, что мы сделали». Хоть EULA и пытается обозначить, что подразумевается под этим «всё, что мы сделали», я чувствую, что это только делает сложившуюся ситуацию еще более непонятной. В конце концов, кажется, что Mojang способны определить, что является «Модом, Плагином или Хаком» в их игре на ходу, и их внезапная смена курса вполне обоснованно нас беспокоит. Прибавьте к этому ту неопределенную ситуацию, вызванную юридическим статусом Bukkit, чтобы понять наше будущее.

    На данный момент, мне кажется, будет вполне разумным сказать, что не имеет смысла вести борьбу за право существования Bukkit и моддинга в целом. С глобальными изменениями, грядущими в 1.8 и большими сложностями с его поддержкой, вызванными также и недостатком помощи со стороны Mojang с самого момента перекупки основной команды (ранее, Mojang помогали нам с определением изменений для ускорения процесса обновления), остаётся не так уж и много причин для нас продолжать поддерживать различные аспекты нашего проекта. С точки зрения управления, нам становится чрезвычайно сложно находить способных и полных энтузиазма людей для помощи в некоммерческом проекте, ввиду падения интереса к Minecraft или других причин. Проще говоря, это был последний гвоздь в крышу гроба нашего проекта.
    Несмотря на всё это, никто не может отрицать, что это было фантастическое время для проекта. Всё это произошло благодаря той помощи, которую мы получали и продолжаем получать как от сообщества Minecraft, так и от множества компаний и организаций, которые решили снабдить нас до смешного большим количеством ресурсов, оборудования и финансирования – гораздо больше, чем мы могли когда-либо попросить. Никакое количество слов не может адекватно выразить благодарность нашим спонсорам, которые помогали нам в течении нашего путешествия и помогли стать Bukkit тем оглушительным успехом, которым он ныне является. Чтобы только перечислить всех тех, кто помог нам, придется написать пост такой же длины как и этот, поэтому будем краткими. Мы хотим глубоко поблагодарить eXophase.com за помощь на старте проекта; Multiplay и Curse за подстраховку и предоставление нам экстренной помощи в хостинге dl.bukkit.org и нашего сервиса BukkitDev; а так же AllGamer за предоставление нам серверов для тестирования рекомендованных сборок.

    И в последнюю очередь , по порядку но не по значимости, мы хотели бы поблагодарить множество энтузиастов, которые бесчисленное число раз вызывались помочь. У меня нет слов, чтобы выразить то, как много эти люди значат для проекта и для меня. Спасибо основной команде, которая работала усердно и постоянно над тем, чтобы Bukkit не только вовремя обновлялся, но и развивался. Огромная благодарность команде BukkitDev, которая провела бесчисленные часы проверяя фантастические плагины, созданные нашим сообществом. Спасибо нашему составу модераторов, за поддержание порядка и безопасности на форуме для всех тех людей, пришедших туда в поисках дружного сообщества. Спасибо тому множеству людей, что так или иначе помогли нам. В конце-концов, спасибо нашим администраторам, которые без устали помогали мне сохранить проект как единое целое. Особенно, я бы хотел поблагодарить @TnT и @mbaxter за то, что прошли через всё это вместе со мной, будучи всегда готовыми обсудить проблему или предоставить мне совет

    Это были поразительные три с половиной года создания того, что, как мы надеялись, было абсолютно лучшей моддинг-платформой для сообщества Minecraft. Нам нравилось наблюдать за тем, какие удивительные возможности создавали множество разработчиков и администраторов благодаря нашему продукту, и продолжаем удивляться единому творческому гению нашего сообщества. Спасибо всем вам за вашу постоянную поддержку! Это было и остается быть тем, что придаёт смысл всей нашей работе. Вместе мы смогли создать сервер-платформу, используемую сотнями и тысячами серверами (только наша последняя рекомендуемая сборка достигла показателя в 2.6 миллиона скачиваний!), что определенно является поводом для гордостью и мажорной нотой, на которой не стыдно закончить наш путь.

    Это конец (подумал Штирлиц) и время сказать «До свидания». Это было удивительное приключение и мы достигли таких высоких результатов, что в конечном итоге Mojang наняли нашу основную команду. К сожалению, всё хорошее должно заканчиваться, и проект Bukkit проделал свой путь, оставив за мной одно последнее, невероятно сложное решение - закрыть проект, которому я посвятил три с половиной года своей жизни и который стал столь много для меня значить. Мы больше не способны уверенно предоставлять нашу модифицированную версию сервера Minecraft и мы не видим в качестве правильного решения и в дальнейшем продолжать процесс обновления Bukkit.

    К сожалению, это означает, что мы НЕ БУДЕМ обновлять Bukkit и CraftBukkit до версии Minecraft 1.8, и, так как 1.7.10 ввёл в действие правила EULA, мы замораживаем наш проект на неопределенное время. Также, в силу очевидных юридических причин, мы не будем помогать кому-либо с обновлениями и не будем делится своими методами работы, несмотря ни на какие причины. И, хотя проект в дальнейшем фактически не будет существовать, мы продолжим поддерживать наше сообщество, форумы, IRC и BukkitDev так долго, как только сможем и как долго наши партнёры будут помогать нам с необходимыми для этого ресурсами и инфраструктурой.

    Что касаемо нас? Что-ж, кто знает? Может быть, мы найдем другую игру, программу или объект, способный увлечь нас, и через какое-то время вернемся, чтобы свершить своё возмездие? Только время покажет, но я надеюсь, что увижу вас в нашем следующем «Проекте мечты», когда он случится

    • 10.09.2014 20:21
    • by vasco
  6. Страсти по Ведру. Вступительно-пояснительное.

    Вот уже третью неделю все майнкрафт-сообщество сотрясает незримый остальными обывателями катаклизьм: приказал жить долго и счастливо основополагающий проект сообщества - Bukkit, дававший всем нам уникальную возможность пользоваться сотнями жизненно необходимых плагинов, значительно облегчавших жизнь как рядовых майнкрафтеров, так и владельцев майнкрафт-серверов. Кроме того что Bukkit API использовался разработчиками плагинов как API доступа к телу манкрафт-сервера, он также являлся основой для множества модифицированных серверных сборок, таких как Spigot или Cauldron, значительно расширяющих и оптимизирующих возможности ванильного сервера minecraft.

    Как вы помните, пару-тройку месяцев назад Mojang обратила пристальное внимание на пользовательское соглашение (EULA), определяющее правила использования клиентской и серверной части игры, расставив все точки в вопросе "Что такое хорошо и что такое плохо" и раз и навсегда отделив агнцев от козлищ. К сожалению, наряду с положительными моментами от этого действия, неожиданно (как приход зимы в России) выяснилось, что Bukkit не соответствует (и, о боже! никогда не соответствовал!) некоторым пунктам моджанговской лицензии. Если уж быть предельно точным, он не соответствует некоторым пунктам собственной лицензии GNU GPL (v.3), но об этом позже.

    Можно было бы, конечно, в двух фразах новости описать суть конфликта и на этом успокоиться. Однако нам самим хочется разобраться в сути вопроса, дабы понять, чего нам стоит ожидать в ближайшем будущем, а также получить ответы на животрепещущие вопросы: Что делать с работающими серверами? Ждать ли нам Баккита под 1.8? Есть ли альтернатива Баккиту в ближайшей перспективе? Поэтому мы решили сделать серию статей, последовательно раскрывающих суть проблемы, возможно, с нашими комментариями, чтобы с их помощью ответить (в меру своего понимания) на поставленные вопросы.

    • 10.09.2014 17:38
    • by vasco