- 215
- 1
- 20
Прошу не кидаться в меня палками. Идея - моя. Возможно, баян или просто мусор. Делюсь с теми, кому понравится и/или пригодится - целиком или кусками.
Описание:
Администратор создает на сайте некий файл-список ссылок и названий к ним. Эти ссылки в виде кнопок доступны для игроков через клиент. Если администратор редактирует список (добавляет/удаляет/временно отключает ссылки), то при очередном входе в гуи список кнопок соответственно изменяется. Как-то так.
Видео с наглядной демонстрацией работы (2 минуты):
Как это сделано:
В EventsFmlClient я добавил такое:
при этом стандартная кнопка "Открыть для сети" уменьшена вдвое, чтоб было куда засунуть мою кнопку. Строка стала выглядеть так:
Далее идем в GameMenu - этот класс является клоном [font=Monaco, Consolas, Courier, monospace]GuiIngameMenu[/font], но с моими изменениями, то есть:
1) В InitGui() добавлена кнопка:
В том же классе, в ActionPerformed(GuiButton btn) добавлен переход в моё гуи:
Далее привожу весь класс гуи с подробными комментариями:
И напоследок класс StreamGobbler, который я нагуглил и использовал в реализации этой задумки:
Последний нужен для того, чтобы определить, доступен ли сервер, иначе клиент игры вешается. :blush:
Если вы посмотрели видео, то заметили, что клиент зависает при нажатии на кнопку вызова гуи на время, которое тратится, чтобы пропинговать сервер с файлом-списком - секунда-две. Не знаю, как это исправить, просто извещаю вас о том, что такой баг существует, и я о нём знаю.
Спасибо за внимание.
Update:
Код плагина, который даст возможность редактировать список через команды в игре. Комментариев меньше и я написал их по-английски машинально... ну там редактировать особо нечего, заменить, если нужно, divider и listPath и скомпилировать.
Команды:
/links - помощь
/links list - показать список
/links add <buttonname> <url> - добавить новую строку
/links del <number> - удалить строку под номером
Редактирование разрешено через консоль, опам и тем, у кого есть пермишн links.edit
Описание:
Администратор создает на сайте некий файл-список ссылок и названий к ним. Эти ссылки в виде кнопок доступны для игроков через клиент. Если администратор редактирует список (добавляет/удаляет/временно отключает ссылки), то при очередном входе в гуи список кнопок соответственно изменяется. Как-то так.
Видео с наглядной демонстрацией работы (2 минуты):
[video=youtube]https://www.youtube.com/watch?v=wo-GECFxLKU&feature=youtu.be[/video]
Как это сделано:
В EventsFmlClient я добавил такое:
Код:
@SubscribeEvent
public void onGuiOpenEvent(GuiOpenEvent event)
{
if(event.gui instanceof GuiIngameMenu){event.gui = new GameMenu();}
}
Код:
this.buttonList.add(guibutton = new GuiButton(7, this.width / 2 - 100, this.height / 4 + 72 + b0, 98, 20, I18n.format("menu.shareToLan", new Object[0])));
Далее идем в GameMenu - этот класс является клоном [font=Monaco, Consolas, Courier, monospace]GuiIngameMenu[/font], но с моими изменениями, то есть:
1) В InitGui() добавлена кнопка:
Код:
this.buttonList.add(new GuiButton(2004, width / 2 + 2, this.height / 4 + 72 + b0, 98, 20, StatCollector.translateToLocal("menu.rhlinks")));
В том же классе, в ActionPerformed(GuiButton btn) добавлен переход в моё гуи:
Код:
case 2004:
this.mc.displayGuiScreen(new RHDLinks(this));
break;
Далее привожу весь класс гуи с подробными комментариями:
Код:
package ru.lao.rha.gui;
import java.awt.Desktop;
import java.util.List;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.util.StatCollector;
import org.apache.commons.io.Charsets;
import org.apache.commons.io.FileUtils;
import scala.Console;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
public class RHDLinks extends GuiScreen {
private static final String __OBFID = "CL_00000700";
private String mainSiteDomain = "russianhostility.mooo.com"; // сайт
private String folderOnServer = "extra"; // папка на сайте (без крайних '/'), можно так: extra/files; нельзя так: /extra/files или так extra/files/
private String listFileOnServer = "links.lst"; // без '/', только название файла
private String folderInClient = "extra"; // папка в клиенте (без крайних '/'), можно так: extra/files; нельзя так: /extra/files или так extra/files/
private String listFileInClient = "links.lst"; // без '/', только название файла
private String divider = " :: "; // разделитель в файле-списке, по которому будет разбиваться строка (пробелы учитываются!!!)
private final GuiScreen backgui; // гуискрин, откуда пришли и куда вернемся, если нажмем кнопку "Вернуться"
protected String title = ""; // название гуи
protected String nolinks = ""; // фраза "список пуст или не найден"
private Map<String, String> links = new HashMap<String, String>(); // мап с названиями кнопок и ссылками к ним
private int startid = 2005; // ID первой кнопки, остальные будут назначены подряд, начиная с этого
private int quitnumber = 5000; // ID кнопки выхода из гуи
private static boolean debug = true, stacktrace = true; // рапортовать ли о наличии ошибки? выдавать ли содержимое стека ошибки?
public RHDLinks(GuiScreen gui){ backgui = gui; } // вызываем наш гуи извне так: RHDLinks(this);, где this - предыдущий гуи, куда можно будет потом вернуться
public void initGui(){
title = StatCollector.translateToLocal("menu.rhlinks.title"); // название берем из нашего lang-файла
getLinks(); // скачиваем и заполняем лист ссылок
int btnheight = 20, start = height - 230, row = 0, id = startid, i = 0; // btnheight - высота кнопок, start - Y-координата верхней кнопки, row - ряд кнопок, id - первый ID, i - итератор кнопок
if(links.size() == 0){ nolinks = StatCollector.translateToLocal("menu.nolinks"); row += 2;} else {nolinks = "";} // если список пуст, то берем фразу об этом из нашего lang-файла
buttonList.clear(); // далее расставляем кнопки по экрану в два ряда
GuiButton btn;
while(i < links.size()){
buttonList.add(btn = new GuiButton(id, width / 2 - 100, start + (btnheight + 4) * row, 98, btnheight, getLinkOrButtonName(i, true))); i++; id++;
if(i < links.size()){
buttonList.add(new GuiButton(id, width / 2 + 2, start + (btnheight + 4) * row, 98, btnheight, getLinkOrButtonName(i, true))); i++; id++;
} else { btn.xPosition = width / 2 - 100; btn.width = 200;} // число кнопок нечетное - последнюю кнопку ставим по центру и
row++;
}
row++;
buttonList.add(new GuiButton(quitnumber, this.width / 2 - 100, start + (btnheight + 4) * row, StatCollector.translateToLocal("menu.back"))); // название кнопки "Вернуться" берем из нашего lang-файла
}
// извлекаем из Map название кнопки (isKey=true) или ссылку(isKey=false)
private String getLinkOrButtonName(int lineNumber, boolean isKey) {
int i = 0;
for (Entry<String, String> linkline : links.entrySet()) {
if(i != lineNumber){i++; continue;}
if(isKey){return linkline.getKey();} else {return linkline.getValue();}
}
return "";
}
// обработка нажатия кнопок
@Override
protected void actionPerformed(GuiButton button) {
int id = button.id;
if (id == quitnumber) { mc.displayGuiScreen(backgui); } // кнопка "Вернуться", уходим в предыдущее гуи
if(links.size() == 0 || id < startid || id > startid + links.size() - 1){return;} // ссылок нет, либо номер кнопки неверный, ничего не делаем
String link = getLinkOrButtonName(id - startid, false);
openWebpage(link); // открываем ссылку в браузере игрока по умолчанию
}
// открытие ссылки
private void openWebpage(String url){
try {
if(!url.startsWith("http://") && !url.startsWith("https://")){url = "http://" + url;}
Desktop.getDesktop().browse(new URL(url).toURI());
} catch (MalformedURLException e) {reportError(1, e);} catch (IOException e) {reportError(2, e);} catch (URISyntaxException e) {reportError(3, e);}
}
// скачивание и заполнение листа ссылок (Map <название_кнопки, адрес_в_интернете>)
private boolean getLinks(){
if(!loadList(folderOnServer + "/" + listFileOnServer))return false; // если не удалось загрузить файл-список в клиент, то уходим
BufferedReader bufferedreader = null;
try {// считываем построчно файл-список
bufferedreader = new BufferedReader(new InputStreamReader(new FileInputStream(folderInClient + "/" + listFileInClient), Charsets.UTF_8));
String s;
links.clear(); // очищаем старый лист
while ((s = bufferedreader.readLine()) != null) {
s = s.trim(); // убираем случайные паразитные пробелы по краям строки
if (!s.isEmpty() && !s.startsWith("//")){ // если строка пустая или начинается с '//' (то есть, временно закомментирована администрацией сервера), то пропускаем её
String [] strings = s.split(divider); // разбиваем строку
if(strings.length == 2){links.put(strings[0].trim(), strings[1].trim());} // если она разбилась пополам, то считаем ее валидной и забиваем в Map,
// попутно убирая случайные паразитные пробелы по краям значений
}
}
} catch (IOException e){reportError(4, e);}
finally
{ if (bufferedreader != null) { try { bufferedreader.close(); return false;} catch (IOException e){ reportError(5, e); return false; }}}
return true;
}
// скачиваем список
private boolean loadList(String name){
if(!IsAvailableDomain(mainSiteDomain)){return false;} // сайт не пингуется - не выдаем кнопки со ссылками
URL url = null;
File target = new File(folderInClient + "/" + listFileInClient); // путь к файлу-списку в клиенте
try {
url = new URL("http://" + mainSiteDomain + "/" + name); // пытаемся получить доступ по ссылке к файлу
} catch (MalformedURLException e) { reportError(6, e); return false;} catch (Exception e){ reportError(7, e); return false;}
if(url != null){
if(target.exists()){target.delete();} // если файл-лист в клиенте уже есть, удаляем его, чтобы заменить старый список актуальным
try {
FileUtils.copyURLToFile(url, target); // пытаемся скопировать файл-список
} catch (IOException e) { reportError(8, e); return false;}
}
return true;
}
// пингуем сервер, проверяя его доступность
public static boolean IsAvailableDomain(String domain) {
try {
Process proc = Runtime.getRuntime().exec("ping " + domain); // стандартный пинг через cmd
StreamGobbler outputGobbler = new StreamGobbler(proc.getInputStream()/*, "OUTPUT"*/);
outputGobbler.start();
proc.waitFor();
return checkAvailability(outputGobbler.getOutputLines()); // возвращаем результат в зависимости от результатов пингования
} catch(Exception e) {reportError(9, e);}
return false;
}
// проверяем полученный от команды ping результат и возвращаем, доступен ли сайт
private static boolean checkAvailability(List<String> outputLines) {
for(String line : outputLines) {
if(line.contains("unreachable")) { return false; }
if(line.contains("TTL=")) { return true; }
}
return false;
}
// отрисовываем: бекграунд, надпись вверху с названием окна, кнопки со ссылками, если они есть, либо надпись, что их нет и только одну кнопку ("Вернуться")
@Override
public void drawScreen(int a, int b, float c)
{
drawDefaultBackground();
drawCenteredString(fontRendererObj, title, width / 2, 15, 16777215);
if(nolinks.length() > 0)drawCenteredString(fontRendererObj, nolinks, width / 2, 50, 16777215);
super.drawScreen(a, b, c);
}
// дебаггер с нумерацией ошибок
private static void reportError(int number, Throwable error){
if(debug){
String errorline = "Links error # " + number;
if(stacktrace)errorline += ", check your code. See stacktrace below to get info:";
Console.println(errorline);
}
if(stacktrace){error.printStackTrace();}
}
}
И напоследок класс StreamGobbler, который я нагуглил и использовал в реализации этой задумки:
Код:
package ru.lao.rha.gui;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
public class StreamGobbler extends Thread
{
protected InputStream is;
protected List<String> outputLines;
public StreamGobbler(InputStream inputstream) {
is = inputstream;
outputLines = new ArrayList<String>();
}
public List<String> getOutputLines() {
return outputLines;
}
public void run()
{
try {
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line;
while((line = br.readLine()) != null) {
outputLines.add(line);
}
} catch(IOException ex) {
Logger.getLogger(StreamGobbler.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
Если вы посмотрели видео, то заметили, что клиент зависает при нажатии на кнопку вызова гуи на время, которое тратится, чтобы пропинговать сервер с файлом-списком - секунда-две. Не знаю, как это исправить, просто извещаю вас о том, что такой баг существует, и я о нём знаю.
Спасибо за внимание.
Update:
Код плагина, который даст возможность редактировать список через команды в игре. Комментариев меньше и я написал их по-английски машинально... ну там редактировать особо нечего, заменить, если нужно, divider и listPath и скомпилировать.
Код:
package ru.lao;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
public class linkseditor extends JavaPlugin {
private String cap = "[LinksEditor] ", divider = " :: ";
private String listPath = "X:/Apache2/localhost/www/extra/links.lst";
private File listFile = new File(listPath);
public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String args[]) {
if(sender.isOp() || sender.hasPermission("links.edit") || !(sender instanceof Player)){
if(args.length == 0){ showHelp(sender); return true; }
if(commandLabel.equalsIgnoreCase("links")){
if(args.length > 0 && args[0].equalsIgnoreCase("list")){ showList(sender); return true; }
if(args.length > 2 && args[0].equalsIgnoreCase("add")){ addLine(sender, args); return true; }
if(args.length == 2 && args[0].equalsIgnoreCase("del")){ removeLine(sender, args[1]); return true; }
showHelp(sender);
}
} else {
sender.sendMessage(cap + "Access denied.");
}
return true;
}
// removes line from the file
private void removeLine(CommandSender sender, String string) {
if(!listFile.exists() && !createlistFile()){sender.sendMessage(cap + "Can't create list file."); return;}
int number = 0;
try {if((number = Integer.parseInt(string)) <= 0){sender.sendMessage(cap + "Number must be > 0."); return;}} catch (Exception e){sender.sendMessage(cap + "Number is incorrect."); return;}
List<String> contents = readList();
if(contents.size() < number){sender.sendMessage(cap + "No such line in this file."); return;}
contents = readListExemptLine(number);
if(writeList(contents)){
sender.sendMessage(cap + "Line deleted successfully.");
} else {
sender.sendMessage(cap + "Can't write list file.");
}
}
// adds new line to list file
private void addLine(CommandSender sender, String[] args) {
if(!listFile.exists() && !createlistFile()){sender.sendMessage(cap + "Can't create list file."); return;}
List<String> contents = readList();
contents.add(getButtonName(args) + divider + args[args.length - 1]);
if(writeList(contents)){
sender.sendMessage(cap + "New line added successfully.");
} else {
sender.sendMessage(cap + "Can't write list file.");
}
}
// shows list of links
private void showList(CommandSender sender) {
if(!listFile.exists() && !createlistFile()){sender.sendMessage(cap + "Can't create list file."); return;}
List<String> contents = readList();
if(contents.size() > 0){
sender.sendMessage("Links list content:");
for(int i = 0; i < contents.size(); i++){sender.sendMessage((i + 1) + " " + contents.get(i));}
} else {sender.sendMessage(cap + "Links list is empty.");}
}
// shows help when you wrong with command
private void showHelp(CommandSender sender) {
sender.sendMessage("------------ LINKS EDITOR HELP ------------");
sender.sendMessage("/links list - shows list of links");
sender.sendMessage("/links add <buttonname> <url> - adds new line to list");
sender.sendMessage("links del <linenumber> - deletes line with your number");
}
// creates empty list file READY
private boolean createlistFile() {
try {
listFile.createNewFile();
} catch (Exception e){return false;}
return true;
}
// reads list file to the list <String>
private List<String> readList(){
List<String> contents = new ArrayList<String>();
BufferedReader listfile = null;
try {
listfile = new BufferedReader(new InputStreamReader(new FileInputStream(listPath),"UTF-8"));
String line;
while ((line = listfile.readLine()) != null){
if(line.isEmpty()){line = "";}
line = line.trim();
if(line.isEmpty()){line = "";}
contents.add(line);
}
} catch (Exception e){
} finally {
if(listfile != null){try{listfile.close();} catch (Exception e){}}
}
return contents;
}
// reads list without line to delete
private List<String> readListExemptLine(int deletenumber) {
List<String> contents = new ArrayList<String>();
BufferedReader listfile = null;
try {
listfile = new BufferedReader(new InputStreamReader(new FileInputStream(listPath),"UTF-8"));
String line;
int i = 1;
while ((line = listfile.readLine()) != null){
if(line.isEmpty()){line = "";}
line = line.trim();
if(line.isEmpty()){line = "";}
if (deletenumber != i) contents.add(line);
i++;
}
} catch (Exception e){
} finally {
if(listfile != null){try{listfile.close();} catch (Exception e){}}
}
return contents;
}
// writes list <String> to list file and deletes old before
private boolean writeList(List<String> contents) {
BufferedOutputStream listfile = null;
try {
if(listFile.exists())listFile.delete();
listfile = new BufferedOutputStream(new FileOutputStream(listPath, true));
for(int i = 0; i < contents.size(); i++){ listfile.write((contents.get(i) + "\n").getBytes("UTF-8")); }
} catch (Exception e){
return false;
} finally {
if(listfile != null){try{listfile.close();} catch (Exception e){}}
}
return true;
}
// makes button name from all words in command, except last, that is link to the web
private String getButtonName(String[] args) {
String buttonname = "";
for (int i = 1; i < args.length - 1; i++){ buttonname += ChatColor.translateAlternateColorCodes('&', args[i]); }
return buttonname.trim();
}
}
/links - помощь
/links list - показать список
/links add <buttonname> <url> - добавить новую строку
/links del <number> - удалить строку под номером
Редактирование разрешено через консоль, опам и тем, у кого есть пермишн links.edit