Позавчера, когда нечего было делать, пришла в голову мысль о том, как можно интересно организовывать ивенты на серверах minecraft. Веселые «крысиные» бега это очень интересно и даже местами забавно, но больше интересно это для админов, наблюдать с высоты как копошатся внизу игроки и кидать им хлеб и мясо. Во время подобных ивентов ЧСВ организаторов поднимается до over 9000. Я, лично, подобные мероприятия недолюбливаю (странно, да?).
В чем же заключается моя «супермегаидея»? А в том, что бы организовать те же бега, но более организовано, с хитрыми головоломками. Самих головоломок я, естественно, еще не придумал, но вот способ выдачи подсказок меня и занял. Суть в том, что бы передавая особый сигнал от клиента к серверу заставлять клиент открыть ссылку в броузере по-умолчанию. Задание не архисложное, но за минуту я до него не дошел. Была безумная идея менять что либо в обмене данными сервер-клиент. Но потом, пришла в голову более простая, но, как мне кажется, более красивая идея.
Самый простой способ обмена информацией между клиентом и сервером — это чат. Так что нет смысла изобретать велосипед, если все у же есть. Просто отсылаем игроку сообщение вида: LINK: example.com, в клиенте это обрабатывается и в случае если строка не подходит отправляет ее в чат, если же пришла ссылка, то открывается страничка. Работает все в связке мод-плагин.
Первое за что я взялся — это клиент, тут мне казалось организовать процесс более сложным. Я ошибался, так как найти место, где обрабатывается чат, было минутным делом. К делу:
Нужно декомпилировать клиент при помощи MCP и в файле net.minecraft.src.ChatLine производить все изменения. Первое, что я добавил — функцию для перехода по ссылке:
private void openLink(String link)
{
try
{
Process p = Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + link);//вызов rundll32
}
catch (IOException e)
{
e.printStackTrace();
}
}
Я думаю, что вопросов тут возникнуть не должно. И чуть не забыл, код предназначен для Windows, что бы обеспечить универсальность нужно использовать класс Desctop, но этот способ требует подключения дополнительных библиотек.
import java.net.URI;
import java.net.URISyntaxException;
import java.awt.Desktop;
private void openLink(String link)
{
Desktop desktop;
if (Desktop.isDesktopSupported())
{
desktop = Desktop.getDesktop();
if (desktop.isSupported(Desktop.Action.BROWSE))
{
URI uri;
try
{
uri = new URI(link);
desktop.browse(uri);
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
catch (URISyntaxException use)
{
use.printStackTrace();
}
}
С переходом разобрались, теперь нужно отфильтровать из потока чата нужную нам ссылку. Плагин будет отсылать код типа LINK: dmwatson.ru:
Фильтрующую процедуру я описал так:
public String ParseLink(String Str2parse)
{
if(Str2parse.startsWith("LINK: "))//если строка начинается с LINE:, то
{
openLink((String) Str2parse.subSequence("LINK: ".length(), Str2parse.length()));//открывает ссылку в броузере
return "Opening page " + (String) Str2parse.subSequence("LINK: ".length(), Str2parse.length());//и показывает об этом сообщение
}
return Str2parse;//иначе просто возвращаем строку как есть.
}
Далее просто цепляем ParseLink в ChatLine:
public ChatLine(String par1Str)
{
message = ParseLink(par1Str);
updateCounter = 0;
}
Всё. С клиентом закончено.
Далее переходим к серверной части. Тут все просто элементарно, тот, кто хоть раз сталкивался с написанием плагина для Bukkit, тот быстро сможет сделать подобное.
Но на всякий случай серверную часть я тоже опишу. Суть плагина:
Пишем на табличке в первой строке [LINK], далее на трех строках ссылку (знаков там мало, но bit.ly никто не отменял). Так что приступаем. Плагин будет очень простым, так как сложности мне не нужны. Так что к делу.
Первым делом я создаю файл Main.java. Он у меня один на все плагины, меняются вызовы и прочее, но часть стандартных функций всегда со мной.
Плагин будет называться LinkSender, через пару дней, когда я его доделаю, отправлю на dev.bukkit.org, пока же опишу его тут.
Итак, любой плагин начинается с plugin.yml:
name: LinkSender
main: ncc.ls.Main
author: Dale Martin Watson
website: http://nextcraft.ru
version: 0.0.1
description: Send link to client.
commands:
lsend:
description: Send link to all.
usage: "/lsend http://nextcraft.ru"
permissions:
ls.*:
description: Allows you to use all the functions.
default: op
ls.send:
description: Allows you to send link to all users.
default: op
ls.create:
description: Allows you to create sign.
default: op
Далее, как я указал во второй строке plugin.yml, создаю класс Main в пакете ncc.ls.
Он наследуется от JavaPlugin и содержит две стандартные функции onEnable и onDisable, а также две функции для отправки сообщений в консоль и чат от имени плагина, и одна для отправки ссылки.
package ncc.ls;
import java.util.logging.Logger;
import ncc.ls.commands.LSendListener;
import ncc.ls.player.InteractListener;
import ncc.ls.player.PlaceListener;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
public class Main extends JavaPlugin
{
public static Main statthis;
public final static Logger log = Logger.getLogger("Minecraft");
@Override
public void onEnable()
{
this.getCommand("lsend").setExecutor(new LSendListener(this));
PluginManager pm = getServer().getPluginManager();
pm.registerEvents(new InteractListener(), this);
pm.registerEvents(new PlaceListener(), this);
toLog("Plugin enabled.");
}
@Override
public void onDisable()
{
toLog("Plugin disabled.");
}
public static void toLog(String msg)
{
log.info("[LinkSender] " + msg);
}
public static void toChat(CommandSender cs, String m)
{
cs.sendMessage(ChatColor.GOLD + "[LinkSender] " + m);
}
public static void sendLink(CommandSender cs, String link)
{
cs.sendMessage("LINK: " + link);
}
}
В onEnable регистрируется три обработчика для трех событий:
Отправка в чат администратором команды /lsend example.com;
Установка таблички;
Правый клик по табличке;
Для проверки введенных URL'ов также нужна функция. ncc.ls.Validator:
package ncc.ls;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Validator
{
public static boolean validURL(String URL)
{
Pattern pattern = Pattern.compile("(https?:\/\/([a-zA-Z0-9]+\.)+[a-zA-Z0-9]{2,}((\/[a-zA-Z0-9%-]*)*|\?(([a-zA-Z0-9%-]* = [a-zA-Z0-9%-]&?)*)*))");
Matcher matcher = pattern.matcher(URL);
return matcher.matches();
}
}
LSendListener.java:
package ncc.ls.commands;
import ncc.ls.Main;
import ncc.ls.Validator;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
public class LSendListener implements CommandExecutor
{
public Main plugin;
public LSendListener(Main plugin)
{
this.plugin = plugin;
}
@Override
public boolean onCommand(CommandSender cs, Command cmd, String label, String[] args)
{
if (!cs.hasPermission("ls.send"))
{
Main.toChat(cs, "You have no permissions to send link.");
return false;
}
else
{
if(!Validator.validURL(args[0]))
{
Main.toChat(cs, "Not valid URL.");
return false;
}
else
{
sendLinkGlobal(args[0]);
Main.toChat(cs, "Link sended.");
return true;
}
}
}
private void sendLinkGlobal(String URL)
{
this.plugin.getServer().broadcastMessage(URL);
}
}
InteractListener.java
package ncc.ls.player;
import ncc.ls.Main;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.Sign;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
public class InteractListener implements Listener
{
private final Main plugin;
public InteractListener(Main plugin)
{
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerInteract(PlayerInteractEvent event)
{
Block block = event.getClickedBlock();
if(block.getType().equals(Material.WALL_SIGN) || block.getType().equals(Material.SIGN_POST))
{
if (event.getAction() == Action.RIGHT_CLICK_BLOCK)
{
Sign s = (Sign) block.getState();
if (s.getLine(0).equalsIgnoreCase("[Link]"))
{
Main.sendLink(event.getPlayer(), s.getLine(1)+s.getLine(2)+s.getLine(3));
}
}
}
}
}
PlaceListener.java
package ncc.ls.player;
import ncc.ls.Main;
import ncc.ls.Validator;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.SignChangeEvent;
import org.bukkit.inventory.ItemStack;
public class PlaceListener implements Listener
{
private Player player;
private Block block;
@EventHandler(priority = EventPriority.MONITOR)
public void onSignChange(SignChangeEvent arg0)
{
if (!arg0.getLine(0).equalsIgnoreCase("[Link]")) return;
block = arg0.getBlock();
if (block == null) return;
player = arg0.getPlayer();
if (!player.hasPermission("ls.create"))
{
signDrop();
Main.toChat(player, "You have no permissions to place link.");
return;
}
if(!Validator.validURL(arg0.getLine(1) + arg0.getLine(2) + arg0.getLine(3)))
{
signDrop();
Main.toChat(player, "Not valid URL.");
return;
}
Main.toChat(player, "Link successfully created.");
}
private void signDrop()
{
ItemStack sign = new ItemStack(323, 1);
player.getWorld().dropItem(block.getLocation(), sign);
block.setTypeId(0);
}
}
Далее просто ставим табличку, вписываем URL и пользуемся. Естественно, что использовать можно для чего угодно, от ивентов, до голосований в топах. Удачи.
P.S. Статья писалась не для профессиональных java-программистов, так как я сам еще новичок и код может смотреться некрасиво, возможно нерационально, с радостью приму все замечания и пожелания.
Автор: DaleMartinWatson