Merge pull request #19 from AmauryCarrade/feature-explorer-gui-13

This commit is contained in:
Adrien Prokopowicz 2016-04-02 19:45:38 +02:00
commit a2cdb02834
37 changed files with 1014 additions and 1896 deletions

49
pom.xml
View File

@ -5,24 +5,65 @@
<artifactId>ImageOnMap</artifactId>
<version>2.99</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<configuration>
<artifactSet>
<includes>
<include>fr.zcraft:zlib</include>
</includes>
</artifactSet>
<relocations>
<relocation>
<pattern>fr.zcraft.zlib</pattern>
<shadedPattern>fr.moribus.imageonmap</shadedPattern>
</relocation>
</relocations>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
</repository>
<repository>
<id>zDevelopers</id>
<url>http://maven.carrade.eu/artifactory/snapshots</url>
</repository>
</repositories>
<dependencies>
<!-- Dependency information -->
<dependencies>
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
<version>1.8-R0.1-SNAPSHOT</version>
<version>1.8.3-R0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>fr.zcraft</groupId>
<artifactId>zlib</artifactId>
<version>0.99-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
</project>

View File

@ -18,7 +18,14 @@
package fr.moribus.imageonmap;
import fr.moribus.imageonmap.commands.Commands;
import fr.moribus.imageonmap.commands.maptool.DeleteConfirmCommand;
import fr.moribus.imageonmap.commands.maptool.DeleteNoConfirmCommand;
import fr.moribus.imageonmap.commands.maptool.ExploreCommand;
import fr.moribus.imageonmap.commands.maptool.GetCommand;
import fr.moribus.imageonmap.commands.maptool.GetRemainingCommand;
import fr.moribus.imageonmap.commands.maptool.ListCommand;
import fr.moribus.imageonmap.commands.maptool.MigrateCommand;
import fr.moribus.imageonmap.commands.maptool.NewCommand;
import fr.moribus.imageonmap.image.ImageIOExecutor;
import fr.moribus.imageonmap.image.ImageRendererExecutor;
import fr.moribus.imageonmap.image.MapInitEvent;
@ -26,11 +33,15 @@ import fr.moribus.imageonmap.map.MapManager;
import fr.moribus.imageonmap.migration.MigratorExecutor;
import fr.moribus.imageonmap.migration.V3Migrator;
import fr.moribus.imageonmap.ui.MapItemManager;
import fr.zcraft.zlib.components.commands.Commands;
import fr.zcraft.zlib.components.gui.Gui;
import fr.zcraft.zlib.core.ZPlugin;
import fr.zcraft.zlib.tools.PluginLogger;
import java.io.File;
import java.io.IOException;
import org.bukkit.plugin.java.JavaPlugin;
public final class ImageOnMap extends JavaPlugin
public final class ImageOnMap extends ZPlugin
{
static private final String IMAGES_DIRECTORY_NAME = "images";
static private final String MAPS_DIRECTORY_NAME = "maps";
@ -58,10 +69,10 @@ public final class ImageOnMap extends JavaPlugin
return new File(imagesDirectory, "map"+mapID+".png");
}
@SuppressWarnings ("unchecked")
@Override
public void onEnable()
{
PluginLogger.init(this);
// Creating the images and maps directories if necessary
try
{
@ -70,31 +81,44 @@ public final class ImageOnMap extends JavaPlugin
}
catch(IOException ex)
{
PluginLogger.error("FATAL : " + ex.getMessage(), null);
PluginLogger.error("FATAL : " + ex.getMessage());
this.setEnabled(false);
return;
}
loadComponents(Gui.class, Commands.class, ImageIOExecutor.class, ImageRendererExecutor.class);
//Init all the things !
PluginConfiguration.init(this);
MetricsLite.startMetrics();
ImageIOExecutor.start();
ImageRendererExecutor.start();
MapManager.init();
Commands.init(this);
MapInitEvent.init(this);
MapItemManager.init();
Commands.register(
"maptool",
NewCommand.class,
ListCommand.class,
GetCommand.class,
DeleteConfirmCommand.class,
DeleteNoConfirmCommand.class,
GetRemainingCommand.class,
ExploreCommand.class,
MigrateCommand.class
);
Commands.registerShortcut("maptool", NewCommand.class, "tomap");
Commands.registerShortcut("maptool", ExploreCommand.class, "maps");
}
@Override
public void onDisable()
{
ImageIOExecutor.stop();
ImageRendererExecutor.stop();
MapManager.exit();
MapItemManager.exit();
MigratorExecutor.waitForMigration();
PluginLogger.exit();
super.onDisable();
}
private File checkPluginDirectory(File primaryFile, File... alternateFiles) throws IOException
@ -108,5 +132,4 @@ public final class ImageOnMap extends JavaPlugin
throw new IOException("Could not create '" + primaryFile.getName() + "' plugin directory.");
return primaryFile;
}
}

View File

@ -28,6 +28,7 @@ package fr.moribus.imageonmap;
* either expressed or implied, of anybody else.
*/
import fr.zcraft.zlib.tools.PluginLogger;
import org.bukkit.Bukkit;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;

View File

@ -1,129 +0,0 @@
/*
* Copyright (C) 2013 Moribus
* Copyright (C) 2015 ProkopyL <prokopylmc@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.moribus.imageonmap;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import org.bukkit.plugin.Plugin;
abstract public class PluginLogger
{
static private Plugin plugin;
static private Thread mainThread;
static private HashMap<Thread, PluginThreadLogger> loggers;
static public void init(Plugin plugin)
{
PluginLogger.plugin = plugin;
mainThread = Thread.currentThread();
loggers = new HashMap<>();
}
static public void exit()
{
plugin = null;
mainThread = null;
loggers = null;
}
static public void log(Level level, String message, Throwable ex)
{
getLogger().log(level, message, ex);
}
static public void log(Level level, String message, Object...args)
{
getLogger().log(level, message, args);
}
static public void log(Level level, String message, Throwable ex, Object... args)
{
log(level, message + " : " + ex.getMessage(), args);
}
static public void info(String message, Object...args)
{
log(Level.INFO, message, args);
}
static public void warning(String message, Object... args)
{
log(Level.WARNING, message, args);
}
static public void warning(String message, Throwable ex)
{
warning(message + " : " + ex.getMessage());
}
static public void error(String message)
{
log(Level.SEVERE, message);
}
static public void error(String message, Throwable ex)
{
log(Level.SEVERE, message, ex);
}
static public void error(String message, Throwable ex, Object... args)
{
log(Level.SEVERE, message, ex, args);
}
static private Logger getLogger()
{
Thread currentThread = Thread.currentThread();
if(currentThread.equals(mainThread)) return plugin.getLogger();
return getLogger(currentThread);
}
static private Logger getLogger(Thread thread)
{
PluginThreadLogger logger = loggers.get(thread);
if(logger == null)
{
logger = new PluginThreadLogger(thread);
loggers.put(thread, logger);
}
return logger;
}
static private class PluginThreadLogger extends Logger
{
private final String loggerName;
public PluginThreadLogger(Thread thread)
{
super(plugin.getClass().getCanonicalName(), null);
setParent(plugin.getLogger());
setLevel(Level.ALL);
loggerName = "[" + thread.getName() + "] ";
}
@Override
public void log(LogRecord logRecord)
{
logRecord.setMessage(loggerName + logRecord.getMessage());
super.log(logRecord);
}
}
}

View File

@ -1,268 +0,0 @@
/*
* Copyright (C) 2013 Moribus
* Copyright (C) 2015 ProkopyL <prokopylmc@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.moribus.imageonmap.commands;
import fr.moribus.imageonmap.commands.CommandException.Reason;
import fr.moribus.imageonmap.map.ImageMap;
import fr.moribus.imageonmap.map.MapManager;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
abstract public class Command
{
static private final String IMAGEONMAP_GLOBAL_PERMISSION = "imageonmap.userender";
protected final Commands commandGroup;
protected final String commandName;
protected final String usageParameters;
protected final String commandDescription;
protected final String[] aliases;
protected CommandSender sender;
protected String[] args;
abstract protected void run() throws CommandException;
public Command(Commands commandGroup)
{
this.commandGroup = commandGroup;
CommandInfo commandInfo = this.getClass().getAnnotation(CommandInfo.class);
if(commandInfo == null)
throw new IllegalArgumentException("Command has no CommandInfo annotation");
commandName = commandInfo.name().toLowerCase();
usageParameters = commandInfo.usageParameters();
commandDescription = commandGroup.getDescription(commandName);
aliases = commandInfo.aliases();
}
public boolean canExecute(CommandSender sender)
{
return sender.hasPermission("imageonmap." + commandGroup.getUsualName())
|| sender.hasPermission(IMAGEONMAP_GLOBAL_PERMISSION);
}
protected List<String> complete() throws CommandException
{
return null;
}
public void execute(CommandSender sender, String[] args)
{
this.sender = sender; this.args = args;
try
{
if(!canExecute(sender))
throw new CommandException(this, Reason.SENDER_NOT_AUTHORIZED);
run();
}
catch(CommandException ex)
{
warning(ex.getReasonString());
}
this.sender = null; this.args = null;
}
public List<String> tabComplete(CommandSender sender, String[] args)
{
List<String> result = null;
this.sender = sender; this.args = args;
try
{
if(canExecute(sender))
result = complete();
}
catch(CommandException ex){}
this.sender = null; this.args = null;
if(result == null) result = new ArrayList<String>();
return result;
}
public String getUsageString()
{
return "/" + commandGroup.getUsualName() + " " + commandName + " " + usageParameters;
}
public String getName()
{
return commandName;
}
public Commands getCommandGroup()
{
return commandGroup;
}
public String[] getAliases()
{
return aliases;
}
public boolean matches(String name)
{
if(commandName.equals(name.toLowerCase())) return true;
for(String alias : aliases)
{
if(alias.equals(name)) return true;
}
return false;
}
///////////// Common methods for commands /////////////
protected void throwInvalidArgument(String reason) throws CommandException
{
throw new CommandException(this, Reason.INVALID_PARAMETERS, reason);
}
protected Player playerSender() throws CommandException
{
if(!(sender instanceof Player))
throw new CommandException(this, Reason.COMMANDSENDER_EXPECTED_PLAYER);
return (Player)sender;
}
protected ImageMap getMapFromArgs() throws CommandException
{
return getMapFromArgs(playerSender(), 0, true);
}
protected ImageMap getMapFromArgs(Player player, int index, boolean expand) throws CommandException
{
if(args.length <= index) throwInvalidArgument("You need to give a map name.");
ImageMap map;
String mapName = args[index];
if(expand)
{
for(int i = index + 1, c = args.length; i < c; i++)
{
mapName += " " + args[i];
}
}
mapName = mapName.trim();
map = MapManager.getMap(player.getUniqueId(), mapName);
if(map == null) error("This map does not exist.");
return map;
}
///////////// Methods for command execution /////////////
static protected void info(CommandSender sender, String message)
{
sender.sendMessage("§7" + message);
}
protected void info(String message)
{
info(sender, message);
}
static protected void warning(CommandSender sender, String message)
{
sender.sendMessage("§c" + message);
}
protected void warning(String message)
{
info(sender, message);
}
protected void error(String message) throws CommandException
{
throw new CommandException(this, Reason.COMMAND_ERROR, message);
}
protected void tellRaw(String rawMessage) throws CommandException
{
Player player = playerSender();
Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(),
"tellraw " + player.getName() + " " + rawMessage);
}
///////////// Methods for autocompletion /////////////
protected List<String> getMatchingSubset(String prefix, String... list)
{
return getMatchingSubset(Arrays.asList(list), prefix);
}
protected List<String> getMatchingSubset(Iterable<? extends String> list, String prefix)
{
List<String> matches = new ArrayList<String>();
for(String item : list)
{
if(item.startsWith(prefix)) matches.add(item);
}
return matches;
}
protected List<String> getMatchingPlayerNames(String prefix)
{
return getMatchingPlayerNames(Bukkit.getOnlinePlayers(), prefix);
}
protected List<String> getMatchingPlayerNames(Iterable<? extends Player> players, String prefix)
{
List<String> matches = new ArrayList<String>();
for(Player player : players)
{
if(player.getName().startsWith(prefix)) matches.add(player.getName());
}
return matches;
}
protected List<String> getMatchingMapNames(Player player, String prefix)
{
return getMatchingToolNames(MapManager.getMapList(player.getUniqueId()), prefix);
}
protected List<String> getMatchingToolNames(Iterable<? extends ImageMap> maps, String prefix)
{
List<String> matches = new ArrayList<String>();
for(ImageMap map : maps)
{
if(map.getId().startsWith(prefix)) matches.add(map.getId());
}
return matches;
}
}

View File

@ -1,72 +0,0 @@
/*
* Copyright (C) 2013 Moribus
* Copyright (C) 2015 ProkopyL <prokopylmc@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.moribus.imageonmap.commands;
import fr.moribus.imageonmap.PluginLogger;
public class CommandException extends Exception
{
public enum Reason
{
COMMANDSENDER_EXPECTED_PLAYER,
INVALID_PARAMETERS,
COMMAND_ERROR,
SENDER_NOT_AUTHORIZED
}
private final Reason reason;
private final Command command;
private final String extra;
public CommandException(Command command, Reason reason, String extra)
{
this.command = command;
this.reason = reason;
this.extra = extra;
}
public CommandException(Command command, Reason reason)
{
this(command, reason, "");
}
public Reason getReason() { return reason; }
public String getReasonString()
{
switch(reason)
{
case COMMANDSENDER_EXPECTED_PLAYER:
return "You must be a player to use this command.";
case INVALID_PARAMETERS:
return "Invalid arguments : " + extra +"\n§r" +
"Usage : " + command.getUsageString() + "\n" +
"For more information, use /" +
command.getCommandGroup().getUsualName() + " help " +
command.getName();
case COMMAND_ERROR:
return extra.isEmpty() ? "An unknown error suddenly happened." : extra;
case SENDER_NOT_AUTHORIZED:
return "You do not have the permission to use this command.";
default:
PluginLogger.warning("Unknown CommandException caught", this);
return "An unknown error suddenly happened.";
}
}
}

View File

@ -1,329 +0,0 @@
/*
* Copyright (C) 2013 Moribus
* Copyright (C) 2015 ProkopyL <prokopylmc@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.moribus.imageonmap.commands;
import fr.moribus.imageonmap.commands.maptool.MigrateCommand;
import fr.moribus.imageonmap.PluginLogger;
import fr.moribus.imageonmap.commands.maptool.*;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Scanner;
import org.apache.commons.lang.StringUtils;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.plugin.java.JavaPlugin;
public enum Commands implements TabCompleter, CommandExecutor
{
MAPTOOL(new String[]{"maptool"},
NewCommand.class,
ListCommand.class,
GetCommand.class,
DeleteConfirmCommand.class,
DeleteNoConfirmCommand.class,
GetRemainingCommand.class,
MigrateCommand.class
),
TOMAP(MAPTOOL, NewCommand.class, "tomap");
static private final Commands[] commandGroups = Commands.class.getEnumConstants();
private final Commands shortcutCommandGroup;
private final String[] names;
private final Class<? extends Command>[] commandsClasses;
private final ArrayList<Command> commands = new ArrayList<>();
private final HashMap<String, String> commandsDescriptions = new HashMap<>();
private String description = "";
private Commands(Commands shortcutCommandGroup, Class<? extends Command> commandClass, String ... names)
{
this.names = names;
this.commandsClasses = new Class[]{commandClass};
this.shortcutCommandGroup = shortcutCommandGroup;
initCommands();
}
private Commands(String[] names, Class<? extends Command> ... commandsClasses)
{
this.names = names;
this.commandsClasses = commandsClasses;
this.shortcutCommandGroup = null;
initDescriptions();
initCommands();
}
private void initDescriptions()
{
String fileName = "help/" + getUsualName() + ".txt";
InputStream stream = getClass().getClassLoader().getResourceAsStream(fileName);
if(stream == null)
{
PluginLogger.warning("Could not load description file for the " + getUsualName() + " command");
return;
}
Scanner scanner = new Scanner(stream);
StringBuilder builder = new StringBuilder();
//Getting the group's description
//And then each command's description
int colonIndex, firstSpaceIndex;
boolean isGroupDescription = true;
while (scanner.hasNextLine())
{
String line = scanner.nextLine();
colonIndex = line.indexOf(':');
if(isGroupDescription)
{
firstSpaceIndex = line.indexOf(' ');
if(colonIndex > 0 && firstSpaceIndex > colonIndex)
isGroupDescription = false;
}
if(isGroupDescription)
{
builder.append(line).append('\n');
}
else
{
commandsDescriptions.put(line.substring(0, colonIndex).trim(),
line.substring(colonIndex + 1).trim());
}
}
scanner.close();
description = builder.toString().trim();
}
private void initCommands()
{
for (Class<? extends Command> commandClass : commandsClasses)
{
addCommand(commandClass);
}
if(!isShortcutCommand()) addCommand(HelpCommand.class);
}
private void addCommand(Class<? extends Command> commandClass)
{
Constructor<? extends Command> constructor;
try
{
constructor = commandClass.getConstructor(Commands.class);
commands.add(constructor.newInstance(isShortcutCommand() ? shortcutCommandGroup : this));
}
catch (Exception ex)
{
PluginLogger.warning("Exception while initializing command", ex);
}
}
public boolean executeMatchingCommand(CommandSender sender, String[] args)
{
if(isShortcutCommand())
{
commands.get(0).execute(sender, args);
return true;
}
if(args.length <= 0)
{
sender.sendMessage(getUsage()); return false;
}
String commandName = args[0];
String[] commandArgs = getCommandArgsFromGroupArgs(args);
return executeMatchingCommand(sender, commandName, commandArgs);
}
private boolean executeMatchingCommand(CommandSender sender, String commandName, String[] args)
{
Command command = getMatchingCommand(commandName);
if(command != null)
{
command.execute(sender, args);
}
else
{
sender.sendMessage(getUsage());
}
return command != null;
}
static public void init(JavaPlugin plugin)
{
org.bukkit.command.PluginCommand bukkitCommand;
for(Commands commandGroup : commandGroups)
{
bukkitCommand = plugin.getCommand(commandGroup.getUsualName());
bukkitCommand.setAliases(commandGroup.getAliases());
bukkitCommand.setExecutor(commandGroup);
bukkitCommand.setTabCompleter(commandGroup);
}
}
@Override
public List<String> onTabComplete(CommandSender sender, org.bukkit.command.Command cmd, String label, String[] args)
{
return tabComplete(sender, args);
}
@Override
public boolean onCommand(CommandSender sender, org.bukkit.command.Command cmd, String label, String[] args)
{
return executeMatchingCommand(sender, args);
}
public List<String> tabComplete(CommandSender sender, String[] args)
{
if(isShortcutCommand()) return commands.get(0).tabComplete(sender, args);
if(args.length <= 1) return tabComplete(sender, args.length == 1 ? args[0] : null);
String commandName = args[0];
String[] commandArgs = getCommandArgsFromGroupArgs(args);
return tabCompleteMatching(sender, commandName, commandArgs);
}
public List<String> tabComplete(CommandSender sender, String commandName)
{
ArrayList<String> matchingCommands = new ArrayList<String>();
for(Command command : commands)
{
if(!command.canExecute(sender)) continue;
if(commandName == null || command.getName().startsWith(commandName.toLowerCase()))
{
matchingCommands.add(command.getName());
}
}
return matchingCommands;
}
private List<String> tabCompleteMatching(CommandSender sender, String commandName, String[] args)
{
Command command = getMatchingCommand(commandName);
if(command != null)
{
return command.tabComplete(sender, args);
}
else
{
return new ArrayList<String>();
}
}
static public String[] getCommandArgsFromGroupArgs(String[] args)
{
String[] commandArgs = new String[args.length - 1];
for(int i = 0; i < commandArgs.length; i++)
{
commandArgs[i] = args[i + 1];
}
return commandArgs;
}
public Command getMatchingCommand(String commandName)
{
for(Command command : commands)
{
if(command.matches(commandName))
{
return command;
}
}
return null;
}
static public boolean execute(CommandSender sender, String commandName, String[] args)
{
Commands commandGroup = getMatchingCommandGroup(commandName);
if(commandGroup == null) return false;
commandGroup.executeMatchingCommand(sender, args);
return true;
}
static public List<String> tabComplete(CommandSender sender, String commandName, String[] args)
{
Commands commandGroup = getMatchingCommandGroup(commandName);
if(commandGroup == null) return new ArrayList<String>();
return commandGroup.tabComplete(sender, args);
}
static private Commands getMatchingCommandGroup(String commandName)
{
Commands commandGroup = null;
for(Commands tCommandGroup : commandGroups)
{
if(tCommandGroup.matches(commandName))
{
commandGroup = tCommandGroup;
break;
}
}
return commandGroup;
}
public boolean matches(String name)
{
name = name.toLowerCase();
for(String commandName : names)
{
if(commandName.equals(name)) return true;
}
return false;
}
public String[] getCommandsNames()
{
String[] commandsNames = new String[commands.size()];
for(int i = 0; i < commands.size(); i++)
{
commandsNames[i] = commands.get(i).getName();
}
return commandsNames;
}
protected String getUsage()
{
if(isShortcutCommand()) return "§cUsage : " + commands.get(0).getUsageString();
return "§cUsage : /" + getUsualName() +
" <" + StringUtils.join(getCommandsNames(), "|") + ">";
}
public String getUsualName() { return names[0]; }
public String[] getNames() { return names.clone(); }
public List<String> getAliases() { return Arrays.asList(names).subList(1, names.length);}
public Command[] getCommands() { return commands.toArray(new Command[commands.size()]);}
public String getDescription() { return description; }
public String getDescription(String commandName) { return commandsDescriptions.get(commandName); }
public boolean isShortcutCommand() { return shortcutCommandGroup != null; }
public Commands getShortcutCommandGroup() { return shortcutCommandGroup; }
}

View File

@ -1,140 +0,0 @@
/*
* Copyright (C) 2013 Moribus
* Copyright (C) 2015 ProkopyL <prokopylmc@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.moribus.imageonmap.commands;
import fr.moribus.imageonmap.PluginLogger;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
@CommandInfo(name = "help", usageParameters = "<command name>")
public class HelpCommand extends Command
{
public HelpCommand(Commands commandGroup) {
super(commandGroup);
}
@Override
protected void run() throws CommandException
{
if(args.length < 1)
groupHelp();
else
commandHelp();
}
private void groupHelp() throws CommandException
{
sender.sendMessage(commandGroup.getDescription());
String tCommandName;
String tDescription;
for(Command tCommand: commandGroup.getCommands())
{
if(!tCommand.canExecute(sender)) continue;
tCommandName = tCommand.getName();
tDescription = commandGroup.getDescription(tCommandName);
tCommandName = commandGroup.getUsualName() + " " + tCommandName;
if(tDescription == null)
sender.sendMessage("§6/" + tCommandName + "§r");
else
sender.sendMessage("§6/" + tCommandName + " : §r" + tDescription);
}
}
private void commandHelp() throws CommandException
{
Command command = commandGroup.getMatchingCommand(args[0]);
if(command == null)
{
error("The specified command does not exist.");
return;
}
if(!command.canExecute(sender))
warning("You do not have the permission to use this command.");
String message = "§l§6 ||== ImageOnMap help ==||\n" +
"§l§6 |Usage : §r" + command.getUsageString();
try
{
String help = getHelpText(command);
if(help.isEmpty())
{
sender.sendMessage(message);
warning("There is no help message for this command.");
}
else
{
sender.sendMessage(message + "\n" + help);
}
}
catch(IOException ex)
{
sender.sendMessage(message);
warning("Could not read help for this command.");
PluginLogger.warning("Could not read help for the command : " + command.getName(), ex);
}
}
private String getHelpText(Command command) throws IOException
{
String fileName = "help/"+ commandGroup.getUsualName() +
"/" + command.getName() + ".txt";
StringBuilder result = new StringBuilder("");
InputStream stream = getClass().getClassLoader().getResourceAsStream(fileName);
if(stream == null) return "";
Scanner scanner = new Scanner(stream);
while (scanner.hasNextLine())
{
String line = scanner.nextLine();
result.append("§l§9 |§r").append(line).append("\n");
}
scanner.close();
return result.toString().trim();
}
@Override
protected List<String> complete() throws CommandException
{
if(args.length != 1) return null;
ArrayList<String> matches = new ArrayList<String>();
for(Command command : commandGroup.getCommands())
{
if(command.getName().startsWith(args[0]))
matches.add(command.getName());
}
return matches;
}
}

View File

@ -0,0 +1,77 @@
/*
* Copyright (C) 2013 Moribus
* Copyright (C) 2015 ProkopyL <prokopylmc@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.moribus.imageonmap.commands;
import fr.moribus.imageonmap.map.ImageMap;
import fr.moribus.imageonmap.map.MapManager;
import fr.zcraft.zlib.components.commands.Command;
import fr.zcraft.zlib.components.commands.CommandException;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
public abstract class IoMCommand extends Command
{
protected ImageMap getMapFromArgs() throws CommandException
{
return getMapFromArgs(playerSender(), 0, true);
}
protected ImageMap getMapFromArgs(Player player, int index, boolean expand) throws CommandException
{
if(args.length <= index) throwInvalidArgument("You need to give a map name.");
ImageMap map;
String mapName = args[index];
if(expand)
{
for(int i = index + 1, c = args.length; i < c; i++)
{
mapName += " " + args[i];
}
}
mapName = mapName.trim();
map = MapManager.getMap(player.getUniqueId(), mapName);
if(map == null) error("This map does not exist.");
return map;
}
protected List<String> getMatchingMapNames(Player player, String prefix)
{
return getMatchingMapNames(MapManager.getMapList(player.getUniqueId()), prefix);
}
protected List<String> getMatchingMapNames(Iterable<? extends ImageMap> maps, String prefix)
{
List<String> matches = new ArrayList<>();
for(ImageMap map : maps)
{
if(map.getId().startsWith(prefix)) matches.add(map.getId());
}
return matches;
}
}

View File

@ -18,20 +18,18 @@
package fr.moribus.imageonmap.commands.maptool;
import fr.moribus.imageonmap.commands.*;
import fr.moribus.imageonmap.commands.IoMCommand;
import fr.moribus.imageonmap.map.ImageMap;
import fr.zcraft.zlib.components.commands.CommandException;
import fr.zcraft.zlib.components.commands.CommandInfo;
import java.util.List;
@CommandInfo(name = "delete", usageParameters = "[tool name]")
public class DeleteConfirmCommand extends Command
@CommandInfo (name = "delete", usageParameters = "[tool name]")
public class DeleteConfirmCommand extends IoMCommand
{
public DeleteConfirmCommand(Commands commandGroup) {
super(commandGroup);
}
@Override
protected void run() throws CommandException
protected void run() throws CommandException
{
ImageMap map = getMapFromArgs();
tellRaw("{text:\"You are going to delete \",extra:[{text:\""+ map.getId() +"\",color:gold},{text:\". Are you sure ? \",color:white}," +
@ -45,8 +43,7 @@ public class DeleteConfirmCommand extends Command
{
if(args.length == 1)
return getMatchingMapNames(playerSender(), args[0]);
return null;
}
}

View File

@ -18,25 +18,23 @@
package fr.moribus.imageonmap.commands.maptool;
import fr.moribus.imageonmap.PluginLogger;
import fr.moribus.imageonmap.commands.*;
import fr.moribus.imageonmap.commands.IoMCommand;
import fr.moribus.imageonmap.map.ImageMap;
import fr.moribus.imageonmap.map.MapManager;
import fr.moribus.imageonmap.map.MapManagerException;
import java.util.List;
import fr.zcraft.zlib.components.commands.CommandException;
import fr.zcraft.zlib.components.commands.CommandInfo;
import fr.zcraft.zlib.tools.PluginLogger;
import org.bukkit.entity.Player;
import java.util.List;
@CommandInfo(name = "delete-noconfirm", usageParameters = "[map name]")
public class DeleteNoConfirmCommand extends Command
@CommandInfo (name = "delete-noconfirm", usageParameters = "[map name]")
public class DeleteNoConfirmCommand extends IoMCommand
{
public DeleteNoConfirmCommand(Commands commandGroup) {
super(commandGroup);
}
@Override
protected void run() throws CommandException
protected void run() throws CommandException
{
Player player = playerSender();
ImageMap map = getMapFromArgs();

View File

@ -16,15 +16,22 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.moribus.imageonmap.commands;
package fr.moribus.imageonmap.commands.maptool;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface CommandInfo
import fr.moribus.imageonmap.commands.IoMCommand;
import fr.moribus.imageonmap.gui.MapListGui;
import fr.zcraft.zlib.components.commands.CommandException;
import fr.zcraft.zlib.components.commands.CommandInfo;
import fr.zcraft.zlib.components.gui.Gui;
@CommandInfo (name = "explore")
public class ExploreCommand extends IoMCommand
{
String name();
String usageParameters() default "";
String[] aliases() default {};
@Override
protected void run() throws CommandException
{
Gui.open(playerSender(), new MapListGui());
}
}

View File

@ -18,17 +18,16 @@
package fr.moribus.imageonmap.commands.maptool;
import fr.moribus.imageonmap.commands.*;
import java.util.List;
import fr.moribus.imageonmap.commands.IoMCommand;
import fr.zcraft.zlib.components.commands.CommandException;
import fr.zcraft.zlib.components.commands.CommandInfo;
import org.bukkit.entity.Player;
@CommandInfo(name = "get")
public class GetCommand extends Command
import java.util.List;
@CommandInfo (name = "get")
public class GetCommand extends IoMCommand
{
public GetCommand(Commands commandGroup) {
super(commandGroup);
}
@Override
protected void run() throws CommandException
{

View File

@ -18,20 +18,15 @@
package fr.moribus.imageonmap.commands.maptool;
import fr.moribus.imageonmap.commands.Command;
import fr.moribus.imageonmap.commands.CommandException;
import fr.moribus.imageonmap.commands.CommandInfo;
import fr.moribus.imageonmap.commands.Commands;
import fr.moribus.imageonmap.commands.IoMCommand;
import fr.moribus.imageonmap.ui.MapItemManager;
import fr.zcraft.zlib.components.commands.CommandException;
import fr.zcraft.zlib.components.commands.CommandInfo;
import org.bukkit.entity.Player;
@CommandInfo(name = "getremaining", aliases = {"getrest"})
public class GetRemainingCommand extends Command
@CommandInfo (name = "getremaining", aliases = {"getrest"})
public class GetRemainingCommand extends IoMCommand
{
public GetRemainingCommand(Commands commandGroup) {
super(commandGroup);
}
@Override
protected void run() throws CommandException
{

View File

@ -18,19 +18,18 @@
package fr.moribus.imageonmap.commands.maptool;
import fr.moribus.imageonmap.commands.*;
import fr.moribus.imageonmap.commands.IoMCommand;
import fr.moribus.imageonmap.map.ImageMap;
import fr.moribus.imageonmap.map.MapManager;
import java.util.List;
import fr.zcraft.zlib.components.commands.CommandException;
import fr.zcraft.zlib.components.commands.CommandInfo;
import org.bukkit.entity.Player;
@CommandInfo(name = "list")
public class ListCommand extends Command
import java.util.List;
@CommandInfo (name = "list")
public class ListCommand extends IoMCommand
{
public ListCommand(Commands commandGroup) {
super(commandGroup);
}
@Override
protected void run() throws CommandException
{
@ -52,5 +51,4 @@ public class ListCommand extends Command
}
player.sendMessage(sMapList);
}
}

View File

@ -18,21 +18,18 @@
package fr.moribus.imageonmap.commands.maptool;
import fr.moribus.imageonmap.commands.*;
import fr.moribus.imageonmap.commands.IoMCommand;
import fr.moribus.imageonmap.migration.MigratorExecutor;
import fr.zcraft.zlib.components.commands.CommandException;
import fr.zcraft.zlib.components.commands.CommandInfo;
import org.bukkit.command.CommandSender;
@CommandInfo(name = "migrate")
public class MigrateCommand extends Command
@CommandInfo (name = "migrate")
public class MigrateCommand extends IoMCommand
{
public MigrateCommand(Commands commandGroup) {
super(commandGroup);
}
@Override
protected void run() throws CommandException
{
final CommandSender cmdSender = sender;
if(MigratorExecutor.isRunning())
{
error("A migration process is already running. Check console for details.");

View File

@ -18,25 +18,21 @@
package fr.moribus.imageonmap.commands.maptool;
import fr.moribus.imageonmap.PluginLogger;
import fr.moribus.imageonmap.commands.Command;
import fr.moribus.imageonmap.commands.CommandException;
import fr.moribus.imageonmap.commands.CommandInfo;
import fr.moribus.imageonmap.commands.Commands;
import fr.moribus.imageonmap.commands.IoMCommand;
import fr.moribus.imageonmap.image.ImageRendererExecutor;
import fr.moribus.imageonmap.map.ImageMap;
import fr.moribus.imageonmap.worker.WorkerCallback;
import java.net.MalformedURLException;
import java.net.URL;
import fr.zcraft.zlib.components.commands.CommandException;
import fr.zcraft.zlib.components.commands.CommandInfo;
import fr.zcraft.zlib.components.worker.WorkerCallback;
import fr.zcraft.zlib.tools.PluginLogger;
import org.bukkit.entity.Player;
@CommandInfo(name = "new", usageParameters = "<URL> [resize]")
public class NewCommand extends Command
import java.net.MalformedURLException;
import java.net.URL;
@CommandInfo (name = "new", usageParameters = "<URL> [resize]")
public class NewCommand extends IoMCommand
{
public NewCommand(Commands commandGroup) {
super(commandGroup);
}
@Override
protected void run() throws CommandException
{
@ -83,5 +79,4 @@ public class NewCommand extends Command
}
});
}
}

View File

@ -0,0 +1,194 @@
/*
* Copyright (C) 2013 Moribus
* Copyright (C) 2015 ProkopyL <prokopylmc@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.moribus.imageonmap.gui;
import fr.moribus.imageonmap.map.ImageMap;
import fr.moribus.imageonmap.map.MapManager;
import fr.moribus.imageonmap.map.MapManagerException;
import fr.zcraft.zlib.components.gui.ActionGui;
import fr.zcraft.zlib.components.gui.Gui;
import fr.zcraft.zlib.components.gui.GuiAction;
import fr.zcraft.zlib.tools.PluginLogger;
import org.bukkit.ChatColor;
import org.bukkit.DyeColor;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.material.Dye;
import java.util.Arrays;
import java.util.Random;
public class ConfirmDeleteMapGui extends ActionGui
{
static private final int BUTTONS_WIDTH = 4;
static private final int FIRST_SLOT_DELETE_BUTTON = 27;
static private final int SHIFT_CANCEL_BUTTON = 5;
/**
* The messages randomly displayed in the lore of the delete buttons.
*/
static private final String[] DELETE_MESSAGES = new String[]{
"Please", "I'm still alive", "Don't do that", "I'm still loving you", "I want to live",
"Please please", "Please please please", "What are you doing?!", "Nooooo!",
"Click and I'll be dead", "Why?", "Please don't do that", "Think about my family",
"Click, I don't like you anymore.", "I don't hate you.", "Click, I'm ready.",
"I'm a green button.", "I'm different.", "Thanks anyway.", "Excuse me.", "Get mad.",
"Sorry!", "My fault!", "I don't blame you.", "No hard feelings.",
"But I need to protect the humans!", "Noooo!", "I'm scared!", "What are you doing?",
"It burns.", "This is not good.", "Can't breathe.", "Thanks anyway.", "These things happen.",
"That was nobody's fault.", "I probably deserved it.", "I blame myself."
};
/**
* The messages randomly displayed in the lore of the cancel buttons.
*/
static private final String[] CANCEL_MESSAGES = new String[] {
"Yay!", "Still aliiiive!", "Click click click", "Yes do that", "I'm a red button.",
"Please click here", "The other button is ugly", "Save me", "This is the good choice",
"Click, I want to live!", "I'll be dead another day anyway", "Are you sure?",
"So you're still loving me?", "Please save me", "Take me with you.",
"Excuse me.", "Don't make lemonade.", "Sleep mode activated.", "Hybernating.",
"Your business is appreciated.", "Hey! It's me! Don't shoot!", "Wheee!", "Hurray!",
"You have excellent aim!", "Please please please"
};
/**
* The map being deleted.
*/
private final ImageMap mapToDelete;
/**
* A source of randomness.
*
* Yes, this javadoc comment is REALLY useful. Trust me.
*/
private final Random random = new Random();
/**
*
* @param mapToDelete The map being deleted.
*/
public ConfirmDeleteMapGui(ImageMap mapToDelete)
{
this.mapToDelete = mapToDelete;
}
@Override
protected void onUpdate()
{
setTitle(mapToDelete.getName() + " » " + ChatColor.BLACK + "Confirm deletion");
setSize(6 * 9);
/* ** Item representation of the image being deleted ** */
ItemStack beingDeleted = new ItemStack(Material.EMPTY_MAP);
ItemMeta meta = beingDeleted.getItemMeta();
meta.setDisplayName(ChatColor.RED + "You're about to destroy this map...");
meta.setLore(Arrays.asList(
ChatColor.RED + "..." + ChatColor.ITALIC + "forever" + ChatColor.RED + ".",
"",
ChatColor.GRAY + "Name: " + ChatColor.WHITE + mapToDelete.getName(),
ChatColor.GRAY + "Map ID: " + ChatColor.WHITE + mapToDelete.getId(),
ChatColor.GRAY + "Maps inside: " + ChatColor.WHITE + mapToDelete.getMapsIDs().length
));
beingDeleted.setItemMeta(meta);
action("", 13, beingDeleted);
/* ** Buttons ** */
int slot = FIRST_SLOT_DELETE_BUTTON;
for(; slot < getSize() - (9 - BUTTONS_WIDTH); slot++)
{
action("delete", slot, createDeleteSubButton());
action("cancel", slot + SHIFT_CANCEL_BUTTON, createCancelSubButton());
if((slot + 1) % 9 == (9 - BUTTONS_WIDTH - 1))
slot += 5;
}
}
private ItemStack createDeleteSubButton()
{
// Orange? Nooo. In the real world this is red. True story.
return createSubButton(DyeColor.ORANGE, ChatColor.RED + "Delete the map", DELETE_MESSAGES);
}
private ItemStack createCancelSubButton()
{
// YES. Purple = lime. BECAUSE. Just accept it.
return createSubButton(DyeColor.PURPLE, ChatColor.GREEN + "Cancel", CANCEL_MESSAGES);
}
private ItemStack createSubButton(DyeColor color, String title, String[] messages)
{
Dye pane = new Dye(Material.STAINED_GLASS_PANE);
pane.setColor(color);
ItemStack subButton = pane.toItemStack(1);
ItemMeta meta = subButton.getItemMeta();
meta.setDisplayName(title);
meta.setLore(Arrays.asList(
"",
ChatColor.GRAY + messages[random.nextInt(messages.length)]
));
subButton.setItemMeta(meta);
return subButton;
}
@GuiAction ("cancel")
protected void cancel()
{
close();
}
@GuiAction ("delete")
protected void delete()
{
MapManager.clear(getPlayer().getInventory(), mapToDelete);
try
{
MapManager.deleteMap(mapToDelete);
getPlayer().sendMessage(ChatColor.GRAY + "Map successfully deleted.");
}
catch (MapManagerException ex)
{
PluginLogger.warning("Error while deleting map", ex);
getPlayer().sendMessage(ChatColor.RED + ex.getMessage());
}
// We try to open the map list GUI, if the map was deleted, before the details GUI
// (so the grandparent GUI)..
if (getParent() != null && getParent().getParent() != null)
Gui.open(getPlayer(), getParent().getParent());
else
close();
}
}

View File

@ -0,0 +1,166 @@
/*
* Copyright (C) 2013 Moribus
* Copyright (C) 2015 ProkopyL <prokopylmc@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.moribus.imageonmap.gui;
import fr.moribus.imageonmap.map.ImageMap;
import fr.moribus.imageonmap.map.PosterMap;
import fr.moribus.imageonmap.map.SingleMap;
import fr.moribus.imageonmap.ui.MapItemManager;
import fr.zcraft.zlib.components.gui.ExplorerGui;
import fr.zcraft.zlib.components.gui.Gui;
import fr.zcraft.zlib.components.gui.GuiAction;
import fr.zcraft.zlib.components.gui.GuiUtils;
import fr.zcraft.zlib.components.gui.PromptGui;
import fr.zcraft.zlib.tools.Callback;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.Arrays;
import java.util.Collections;
public class MapDetailGui extends ExplorerGui
{
private final ImageMap map;
public MapDetailGui(ImageMap map)
{
this.map = map;
}
@Override
protected ItemStack getViewItem(int x, int y)
{
Material partMaterial = Material.PAPER;
if((y % 2 == 0 && x % 2 == 0) || (y % 2 == 1 && x % 2 == 1))
partMaterial = Material.EMPTY_MAP;
ItemStack part = new ItemStack(partMaterial);
ItemMeta meta = part.getItemMeta();
meta.setDisplayName(ChatColor.GREEN + "Map part");
meta.setLore(Arrays.asList(
ChatColor.GRAY + "Column: " + ChatColor.WHITE + (y + 1),
ChatColor.GRAY + "Row: " + ChatColor.WHITE + (x + 1),
"",
ChatColor.GRAY + "» Click to get only this part"
));
part.setItemMeta(meta);
return part;
}
@Override
protected ItemStack getPickedUpItem(int x, int y)
{
if(map instanceof SingleMap)
{
return MapItemManager.createMapItem((SingleMap)map);
}
else if(map instanceof PosterMap)
{
return MapItemManager.createMapItem((PosterMap)map, x, y);
}
throw new IllegalStateException("Unsupported map type : " + map.getType());
}
@Override
protected ItemStack getEmptyViewItem()
{
if(map instanceof SingleMap)
{
return getViewItem(0, 0);
}
else return super.getEmptyViewItem();
}
@Override
protected void onUpdate()
{
setTitle("Your maps » " + ChatColor.BLACK + map.getName());
setKeepHorizontalScrollingSpace(true);
if(map instanceof PosterMap)
setDataShape(((PosterMap) map).getColumnCount(), ((PosterMap) map).getRowCount());
else
setData(null); // Fallback to the empty view item.
action("rename", getSize() - 7, GuiUtils.makeItem(Material.BOOK_AND_QUILL, ChatColor.BLUE + "Rename this image", Arrays.asList(
ChatColor.GRAY + "Click here to rename this image;",
ChatColor.GRAY + "this is used for your own organization."
)));
action("delete", getSize() - 6, GuiUtils.makeItem(Material.BARRIER, ChatColor.RED + "Delete this image", Arrays.asList(
ChatColor.GRAY + "Deletes this map " + ChatColor.WHITE + "forever" + ChatColor.GRAY + ".",
ChatColor.GRAY + "This action cannot be undone!",
"",
ChatColor.GRAY + "You will be asked to confirm your",
ChatColor.GRAY + "choice if you click here."
)));
// To keep the controls centered, the back button is shifted to the right when the
// arrow isn't displayed, so when the map fit on the grid without sliders.
int backSlot = getSize() - 4;
if(map instanceof PosterMap && ((PosterMap) map).getColumnCount() <= INVENTORY_ROW_SIZE)
backSlot++;
action("back", backSlot, GuiUtils.makeItem(Material.EMERALD, ChatColor.GREEN + "« Back", Collections.singletonList(
ChatColor.GRAY + "Go back to the list."
)));
}
@GuiAction ("rename")
public void rename()
{
PromptGui.prompt(getPlayer(), new Callback<String>()
{
@Override
public void call(String newName)
{
if (newName == null || newName.isEmpty())
{
getPlayer().sendMessage(ChatColor.RED + "Map names can't be empty.");
return;
}
map.rename(newName);
getPlayer().sendMessage(ChatColor.GRAY + "Map successfully renamed.");
}
}, map.getName(), this);
}
@GuiAction ("delete")
public void delete()
{
Gui.open(getPlayer(), new ConfirmDeleteMapGui(map), this);
}
@GuiAction ("back")
public void back()
{
close();
}
}

View File

@ -0,0 +1,185 @@
/*
* Copyright (C) 2013 Moribus
* Copyright (C) 2015 ProkopyL <prokopylmc@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.moribus.imageonmap.gui;
import fr.moribus.imageonmap.PluginConfiguration;
import fr.moribus.imageonmap.map.ImageMap;
import fr.moribus.imageonmap.map.MapManager;
import fr.moribus.imageonmap.map.PosterMap;
import fr.moribus.imageonmap.map.SingleMap;
import fr.moribus.imageonmap.ui.MapItemManager;
import fr.zcraft.zlib.components.gui.ExplorerGui;
import fr.zcraft.zlib.components.gui.Gui;
import fr.zcraft.zlib.components.gui.GuiUtils;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
public class MapListGui extends ExplorerGui<ImageMap>
{
private final NumberFormat bigNumbersFormatter = new DecimalFormat("###,###,###,###", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
@Override
protected ItemStack getViewItem(ImageMap map)
{
String mapDescription;
if (map instanceof SingleMap)
mapDescription = "Single map";
else
mapDescription = "Poster map (" + ((PosterMap) map).getColumnCount() + "×" + ((PosterMap) map).getRowCount() + ")";
ItemStack icon = GuiUtils.makeItem(Material.MAP, ChatColor.GREEN + "" + ChatColor.BOLD + map.getName(), Arrays.asList(
ChatColor.WHITE + mapDescription,
"",
ChatColor.GRAY + "Map ID: " + map.getId(),
"",
ChatColor.GRAY + "» Left-click to get this map",
ChatColor.GRAY + "» Right-click for details and options"
));
return GuiUtils.hideItemAttributes(icon);
}
@Override
protected ItemStack getEmptyViewItem()
{
ItemStack empty = new ItemStack(Material.BARRIER);
ItemMeta meta = empty.getItemMeta();
meta.setDisplayName(ChatColor.RED + "You don't have any map.");
meta.setLore(Arrays.asList(
ChatColor.GRAY + "Get started by creating a new one",
ChatColor.GRAY + "using " + ChatColor.WHITE + "/tomap <URL> [resize]" + ChatColor.GRAY + "!"
));
empty.setItemMeta(meta);
return empty;
}
@Override
protected void onRightClick(ImageMap data)
{
Gui.open(getPlayer(), new MapDetailGui(data), this);
}
@Override
protected ItemStack getPickedUpItem(ImageMap map)
{
if (map instanceof SingleMap)
{
return MapItemManager.createMapItem(map.getMapsIDs()[0], map.getName());
}
MapItemManager.give(getPlayer(), map);
return null;
}
@Override
protected void onUpdate()
{
ImageMap[] maps = MapManager.getMaps(getPlayer().getUniqueId());
setData(maps);
setTitle(ChatColor.BLACK + "Your maps " + ChatColor.RESET + "(" + maps.length + ")");
setKeepHorizontalScrollingSpace(true);
/* ** Statistics ** */
int imagesCount = MapManager.getMapList(getPlayer().getUniqueId()).size();
int mapPartCount = MapManager.getMapPartCount(getPlayer().getUniqueId());
int mapGlobalLimit = PluginConfiguration.MAP_GLOBAL_LIMIT.getInteger();
int mapPersonalLimit = PluginConfiguration.MAP_PLAYER_LIMIT.getInteger();
int mapPartGloballyLeft = mapGlobalLimit - MapManager.getMapCount();
int mapPartPersonallyLeft = mapPersonalLimit - mapPartCount;
int mapPartLeft;
if (mapGlobalLimit <= 0 && mapPersonalLimit <= 0)
mapPartLeft = -1;
else if (mapGlobalLimit <= 0)
mapPartLeft = mapPartPersonallyLeft;
else if (mapPersonalLimit <= 0)
mapPartLeft = mapPartGloballyLeft;
else
mapPartLeft = Math.min(mapPartGloballyLeft, mapPartPersonallyLeft);
double percentageUsed = mapPartLeft < 0 ? 0 : ((double) mapPartCount) / ((double) (mapPartCount + mapPartLeft)) * 100;
ItemStack statistics = new ItemStack(Material.ENCHANTED_BOOK);
ItemMeta meta = statistics.getItemMeta();
meta.setDisplayName(ChatColor.BLUE + "Usage statistics");
meta.setLore(Arrays.asList(
"",
getStatisticText("Images rendered", imagesCount),
getStatisticText("Minecraft maps used", mapPartCount)
));
if (mapPartLeft >= 0)
{
List<String> lore = meta.getLore();
lore.add("");
lore.add(ChatColor.BLUE + "Minecraft maps limits");
lore.add("");
lore.add(getStatisticText("Server-wide limit", mapGlobalLimit, true));
lore.add(getStatisticText("Per-player limit", mapPersonalLimit, true));
lore.add("");
lore.add(getStatisticText("Current consumption", ((int) Math.rint(percentageUsed)) + " %"));
lore.add(getStatisticText("Maps left", mapPartLeft));
meta.setLore(lore);
}
GuiUtils.hideItemAttributes(meta);
statistics.setItemMeta(meta);
action("", getSize() - 5, statistics);
}
private String getStatisticText(String title, Integer value)
{
return getStatisticText(title, value, false);
}
private String getStatisticText(String title, Integer value, boolean zeroIsUnlimited)
{
return getStatisticText(title, zeroIsUnlimited && value <= 0 ? "unlimited" : bigNumbersFormatter.format(value));
}
private String getStatisticText(String title, String value)
{
return ChatColor.GRAY + title + ": " + ChatColor.WHITE + value;
}
}

View File

@ -20,53 +20,36 @@ package fr.moribus.imageonmap.image;
import fr.moribus.imageonmap.ImageOnMap;
import fr.moribus.imageonmap.map.ImageMap;
import fr.moribus.imageonmap.worker.Worker;
import fr.moribus.imageonmap.worker.WorkerCallback;
import fr.moribus.imageonmap.worker.WorkerRunnable;
import fr.zcraft.zlib.components.worker.Worker;
import fr.zcraft.zlib.components.worker.WorkerAttributes;
import fr.zcraft.zlib.components.worker.WorkerRunnable;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.nio.file.Files;
import javax.imageio.ImageIO;
@WorkerAttributes (name = "Image IO")
public class ImageIOExecutor extends Worker
{
static private ImageIOExecutor instance;
static public void start()
{
if(instance != null) stop();
instance = new ImageIOExecutor();
instance.init();
}
static public void stop()
{
instance.exit();
instance = null;
}
private ImageIOExecutor()
{
super("Image IO");
}
static public void loadImage(final File file, final Renderer mapRenderer)
{
instance.submitQuery(new WorkerRunnable<Void>()
submitQuery(new WorkerRunnable<Void>()
{
@Override
public Void run() throws Exception
{
@Override
public Void run() throws Exception
{
BufferedImage image = ImageIO.read(file);
mapRenderer.setImage(image);
return null;
}
});
BufferedImage image = ImageIO.read(file);
mapRenderer.setImage(image);
return null;
}
});
}
static public void saveImage(final File file, final BufferedImage image)
{
instance.submitQuery(new WorkerRunnable<Void>()
submitQuery(new WorkerRunnable<Void>()
{
@Override
public Void run() throws Throwable
@ -101,7 +84,7 @@ public class ImageIOExecutor extends Worker
static public void deleteImage(final File file)
{
instance.submitQuery(new WorkerRunnable<Void>()
submitQuery(new WorkerRunnable<Void>()
{
@Override
public Void run() throws Throwable
@ -109,7 +92,6 @@ public class ImageIOExecutor extends Worker
Files.delete(file.toPath());
return null;
}
});
}
}

View File

@ -18,46 +18,29 @@
package fr.moribus.imageonmap.image;
import fr.moribus.imageonmap.PluginLogger;
import fr.moribus.imageonmap.map.ImageMap;
import fr.moribus.imageonmap.map.MapManager;
import fr.moribus.imageonmap.worker.Worker;
import fr.moribus.imageonmap.worker.WorkerCallback;
import fr.moribus.imageonmap.worker.WorkerRunnable;
import java.awt.Graphics;
import fr.zcraft.zlib.components.worker.Worker;
import fr.zcraft.zlib.components.worker.WorkerAttributes;
import fr.zcraft.zlib.components.worker.WorkerCallback;
import fr.zcraft.zlib.components.worker.WorkerRunnable;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import javax.imageio.ImageIO;
@WorkerAttributes (name = "Image Renderer", queriesMainThread = true)
public class ImageRendererExecutor extends Worker
{
static private ImageRendererExecutor instance;
static public void start()
{
if(instance != null) stop();
instance = new ImageRendererExecutor();
instance.init();
}
static public void stop()
{
instance.exit();
instance = null;
}
private ImageRendererExecutor()
{
super("Image IO", true);
}
static public void Test(WorkerCallback callback)
{
instance.submitQuery(new WorkerRunnable<Void>()
submitQuery(new WorkerRunnable<Void>()
{
@Override
public Void run() throws Throwable
@ -70,15 +53,15 @@ public class ImageRendererExecutor extends Worker
static public void Render(final URL url, final boolean scaling, final UUID playerUUID, WorkerCallback<ImageMap> callback)
{
instance.submitQuery(new WorkerRunnable<ImageMap>()
submitQuery(new WorkerRunnable<ImageMap>()
{
@Override
public ImageMap run() throws Throwable
{
final BufferedImage image = ImageIO.read(url);
if(image == null) throw new IOException("The given URL is not a valid image");
if(scaling) return RenderSingle(image, playerUUID);
if (image == null) throw new IOException("The given URL is not a valid image");
if (scaling) return RenderSingle(image, playerUUID);
else return RenderPoster(image, playerUUID);
}
}, callback);
@ -87,7 +70,7 @@ public class ImageRendererExecutor extends Worker
static private ImageMap RenderSingle(final BufferedImage image, final UUID playerUUID) throws Throwable
{
MapManager.checkMapLimit(1, playerUUID);
Future<Short> futureMapID = instance.submitToMainThread(new Callable<Short>()
Future<Short> futureMapID = submitToMainThread(new Callable<Short>()
{
@Override
public Short call() throws Exception
@ -101,7 +84,7 @@ public class ImageRendererExecutor extends Worker
final short mapID = futureMapID.get();
ImageIOExecutor.saveImage(mapID, finalImage);
instance.submitToMainThread(new Callable<Void>()
submitToMainThread(new Callable<Void>()
{
@Override
public Void call() throws Exception
@ -121,7 +104,7 @@ public class ImageRendererExecutor extends Worker
final int mapCount = poster.getImagesCount();
MapManager.checkMapLimit(mapCount, playerUUID);
final Future<short[]> futureMapsIds = instance.submitToMainThread(new Callable<short[]>()
final Future<short[]> futureMapsIds = submitToMainThread(new Callable<short[]>()
{
@Override
public short[] call() throws Exception
@ -136,7 +119,7 @@ public class ImageRendererExecutor extends Worker
ImageIOExecutor.saveImage(mapsIDs, poster);
instance.submitToMainThread(new Callable<Void>()
submitToMainThread(new Callable<Void>()
{
@Override
public Void call() throws Exception
@ -178,5 +161,4 @@ public class ImageRendererExecutor extends Worker
graphics.dispose();
return newImage;
}
}

View File

@ -28,7 +28,9 @@ import org.bukkit.entity.ItemFrame;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerItemHeldEvent;
import org.bukkit.event.player.PlayerPickupItemEvent;
import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.map.MapView;
@ -73,6 +75,26 @@ public class MapInitEvent implements Listener
initMap(item);
}
@EventHandler
public void onPlayerPickup(PlayerPickupItemEvent event)
{
ItemStack item = event.getItem().getItemStack();
initMap(item);
}
@EventHandler
public void onPlayerInventoryPlace(InventoryClickEvent event)
{
switch(event.getAction())
{
case PLACE_ALL:
case PLACE_ONE:
case PLACE_SOME:
case SWAP_WITH_CURSOR:
initMap(event.getCursor());
}
}
static protected void initMap(ItemStack item)
{
if (item != null && item.getType() == Material.MAP)

View File

@ -18,15 +18,14 @@
package fr.moribus.imageonmap.map;
import fr.moribus.imageonmap.ui.MapItemManager;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.bukkit.Material;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import fr.moribus.imageonmap.ui.*;
import org.bukkit.*;
import org.bukkit.configuration.*;
import org.bukkit.configuration.serialization.*;
import org.bukkit.entity.*;
import org.bukkit.inventory.*;
import java.util.*;
public abstract class ImageMap implements ConfigurationSerializable
{
@ -160,6 +159,11 @@ public abstract class ImageMap implements ConfigurationSerializable
return id;
}
public synchronized Type getType()
{
return mapType;
}
public synchronized void rename(String id, String name)
{
this.id = id;

View File

@ -61,7 +61,7 @@ abstract public class MapManager
}
return false;
}
static public boolean managesMap(ItemStack item)
{
synchronized(playerMaps)
@ -73,7 +73,7 @@ abstract public class MapManager
}
return false;
}
static public ImageMap createMap(UUID playerUUID, short mapID) throws MapManagerException
{
ImageMap newMap = new SingleMap(playerUUID, mapID);
@ -139,10 +139,66 @@ abstract public class MapManager
return getPlayerMapStore(playerUUID).getMapList();
}
static public ImageMap[] getMaps(UUID playerUUID)
{
return getPlayerMapStore(playerUUID).getMaps();
}
/**
* Returns the number of minecraft maps used by the images rendered by the given player.
*
* @param playerUUID The player's UUID.
*
* @return The count.
*/
static public int getMapPartCount(UUID playerUUID)
{
return getPlayerMapStore(playerUUID).getMapCount();
}
static public ImageMap getMap(UUID playerUUID, String mapId)
{
return getPlayerMapStore(playerUUID).getMap(mapId);
}
/**
* Returns the {@link ImageMap} this map belongs to.
*
* @param mapId The ID of the Minecraft map.
* @return The {@link ImageMap}.
*/
static public ImageMap getMap(short mapId)
{
synchronized(playerMaps)
{
for(PlayerMapStore mapStore : playerMaps)
{
if(mapStore.managesMap(mapId))
{
for(ImageMap map : mapStore.getMapList())
{
if(map.managesMap(mapId))
{
return map;
}
}
}
}
}
return null;
}
/**
* Returns the {@link ImageMap} this map belongs to.
*
* @param item The map, as an {@link ItemStack}.
* @return The {@link ImageMap}.
*/
static public ImageMap getMap(ItemStack item)
{
return getMap(item.getDurability());
}
static public void clear(Inventory inventory)
{
@ -192,7 +248,12 @@ abstract public class MapManager
}
getPlayerMapStore(userUUID).checkMapLimit(newMapsCount);
}
/**
* Returns the total number of minecraft maps used by ImageOnMap images.
*
* @return The count.
*/
static public int getMapCount()
{
int mapCount = 0;

View File

@ -20,8 +20,15 @@ package fr.moribus.imageonmap.map;
import fr.moribus.imageonmap.ImageOnMap;
import fr.moribus.imageonmap.PluginConfiguration;
import fr.moribus.imageonmap.PluginLogger;
import fr.moribus.imageonmap.map.MapManagerException.Reason;
import fr.zcraft.zlib.tools.PluginLogger;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.inventory.ItemStack;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
@ -29,12 +36,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.inventory.ItemStack;
public class PlayerMapStore implements ConfigurationSerializable
{
@ -127,6 +128,11 @@ public class PlayerMapStore implements ConfigurationSerializable
return new ArrayList(mapList);
}
public synchronized ImageMap[] getMaps()
{
return mapList.toArray(new ImageMap[mapList.size()]);
}
public synchronized ImageMap getMap(String mapId)
{
for(ImageMap map : mapList)

View File

@ -18,10 +18,11 @@
package fr.moribus.imageonmap.map;
import org.bukkit.configuration.InvalidConfigurationException;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.bukkit.configuration.InvalidConfigurationException;
public class PosterMap extends ImageMap
{
@ -115,6 +116,20 @@ public class PosterMap extends ImageMap
if(columnCount == 0) return 0;
return (i / columnCount) + 1;
}
/**
* Returns the map id at the given column and line.
*
* @param col The column. Starts at 0.
* @param row The row. Starts at 0.
* @return The Minecraft map ID.
*
* @throws ArrayIndexOutOfBoundsException if the given coordinates are too big (out of the poster).
*/
public short getMapIdAt(int col, int row)
{
return mapsIDs[getColumnCount() * col + row];
}
public boolean hasColumnData()
{

View File

@ -19,7 +19,8 @@
package fr.moribus.imageonmap.migration;
import fr.moribus.imageonmap.ImageOnMap;
import fr.moribus.imageonmap.PluginLogger;
import fr.zcraft.zlib.tools.PluginLogger;
public class MigratorExecutor
{

View File

@ -1,222 +0,0 @@
/*
* Copyright (C) 2013 Moribus
* Copyright (C) 2015 ProkopyL <prokopylmc@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.moribus.imageonmap.migration;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
abstract public class UUIDFetcher
{
/**
* The maximal amount of usernames to send to mojang per request
* This allows not to overload mojang's service with too many usernames at a time
*/
static private final int MOJANG_USERNAMES_PER_REQUEST = 100;
/**
* The maximal amount of requests to send to Mojang
* The time limit for this amount is MOJANG_MAX_REQUESTS_TIME
* Read : You can only send MOJANG_MAX_REQUESTS in MOJANG_MAX_REQUESTS_TIME seconds
*/
static private final int MOJANG_MAX_REQUESTS = 600;
/**
* The timeframe for the Mojang request limit (in seconds)
*/
static private final int MOJANG_MAX_REQUESTS_TIME = 600;
/**
* The minimum time between two requests to mojang (in milliseconds)
*/
static private final int TIME_BETWEEN_REQUESTS = 200;
/**
* The (approximative) timestamp of the date when Mojang name changing feature
* was announced to be released
*/
static private final int NAME_CHANGE_TIMESTAMP = 1420844400;
static private final String PROFILE_URL = "https://api.mojang.com/profiles/minecraft";
static private final String TIMED_PROFILE_URL = "https://api.mojang.com/users/profiles/minecraft/";
static public Map<String, UUID> fetch(List<String> names) throws IOException, InterruptedException
{
return fetch(names, MOJANG_USERNAMES_PER_REQUEST);
}
static public Map<String, UUID> fetch(List<String> names, int limitByRequest) throws IOException, InterruptedException
{
Map<String, UUID> UUIDs = new HashMap<String, UUID>();
int requests = (names.size() / limitByRequest) + 1;
List<String> tempNames;
Map<String, UUID> tempUUIDs;
for(int i = 0; i < requests; i++)
{
tempNames = names.subList(limitByRequest * i, Math.min((limitByRequest * (i+1)) - 1, names.size()));
tempUUIDs = rawFetch(tempNames);
UUIDs.putAll(tempUUIDs);
Thread.sleep(TIME_BETWEEN_REQUESTS);
}
return UUIDs;
}
static private Map<String, UUID> rawFetch(List<String> names) throws IOException
{
Map<String, UUID> uuidMap = new HashMap<String, UUID>();
HttpURLConnection connection = getPOSTConnection(PROFILE_URL);
writeBody(connection, names);
JSONArray array;
try
{
array = (JSONArray) readResponse(connection);
}
catch(ParseException ex)
{
throw new IOException("Invalid response from server, unable to parse received JSON : " + ex.toString());
}
for (Object profile : array)
{
JSONObject jsonProfile = (JSONObject) profile;
String id = (String) jsonProfile.get("id");
String name = (String) jsonProfile.get("name");
uuidMap.put(name, fromMojangUUID(id));
}
return uuidMap;
}
static public void fetchRemaining(Collection<String> names, Map<String, UUID> uuids) throws IOException, InterruptedException
{
ArrayList<String> remainingNames = new ArrayList<>();
for(String name : names)
{
if(!uuids.containsKey(name)) remainingNames.add(name);
}
int timeBetweenRequests;
if(remainingNames.size() > MOJANG_MAX_REQUESTS)
{
timeBetweenRequests = (MOJANG_MAX_REQUESTS / MOJANG_MAX_REQUESTS_TIME) * 1000;
}
else
{
timeBetweenRequests = TIME_BETWEEN_REQUESTS;
}
User user;
for(String name : remainingNames)
{
user = fetchOriginalUUID(name);
uuids.put(name, user.uuid);
Thread.sleep(timeBetweenRequests);
}
}
static private User fetchOriginalUUID(String name) throws IOException
{
HttpURLConnection connection = getGETConnection(TIMED_PROFILE_URL + name + "?at=" + NAME_CHANGE_TIMESTAMP);
JSONObject object;
try
{
object = (JSONObject) readResponse(connection);
}
catch(ParseException ex)
{
throw new IOException("Invalid response from server, unable to parse received JSON : " + ex.toString());
}
User user = new User();
user.name = (String) object.get("name");
user.uuid = fromMojangUUID((String)object.get("id"));
return user;
}
static private HttpURLConnection getPOSTConnection(String url) throws IOException
{
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setUseCaches(false);
connection.setDoInput(true);
connection.setDoOutput(true);
return connection;
}
static private HttpURLConnection getGETConnection(String url) throws IOException
{
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
connection.setRequestMethod("GET");
connection.setUseCaches(false);
connection.setDoInput(true);
connection.setDoOutput(true);
return connection;
}
private static void writeBody(HttpURLConnection connection, List<String> names) throws IOException
{
OutputStream stream = connection.getOutputStream();
String body = JSONArray.toJSONString(names);
stream.write(body.getBytes());
stream.flush();
stream.close();
}
private static Object readResponse(HttpURLConnection connection) throws IOException, ParseException
{
return new JSONParser().parse(new InputStreamReader(connection.getInputStream()));
}
private static UUID fromMojangUUID(String id) //Mojang sends string UUIDs without dashes ...
{
return UUID.fromString(id.substring(0, 8) + "-" + id.substring(8, 12) + "-" +
id.substring(12, 16) + "-" + id.substring(16, 20) + "-" +
id.substring(20, 32));
}
static private class User
{
public String name;
public UUID uuid;
}
}

View File

@ -19,8 +19,14 @@
package fr.moribus.imageonmap.migration;
import fr.moribus.imageonmap.ImageOnMap;
import fr.moribus.imageonmap.PluginLogger;
import fr.moribus.imageonmap.map.MapManager;
import fr.zcraft.zlib.tools.PluginLogger;
import fr.zcraft.zlib.tools.mojang.UUIDFetcher;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@ -37,10 +43,6 @@ import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin;
/**
* This class represents and executes the ImageOnMap v3.x migration process
@ -350,7 +352,7 @@ public class V3Migrator implements Runnable
}
catch(IOException ex)
{
PluginLogger.error("An error occured while fetching the UUIDs from Mojang", ex);
PluginLogger.error("An error occurred while fetching the UUIDs from Mojang", ex);
throw ex;
}
catch(InterruptedException ex)
@ -358,12 +360,12 @@ public class V3Migrator implements Runnable
PluginLogger.error("The migration worker has been interrupted", ex);
throw ex;
}
PluginLogger.info("Fetching done. {0} UUIDs have been retreived.", usersUUIDs.size());
PluginLogger.info("Fetching done. {0} UUIDs have been retrieved.", usersUUIDs.size());
}
/**
* Fetches the UUIDs that could not be retreived via Mojang's standard API
* @return true if at least one UUID has been retreived, false otherwise
* Fetches the UUIDs that could not be retrieved via Mojang's standard API
* @return true if at least one UUID has been retrieved, false otherwise
*/
private boolean fetchMissingUUIDs() throws IOException, InterruptedException
{
@ -378,7 +380,7 @@ public class V3Migrator implements Runnable
}
catch(IOException ex)
{
PluginLogger.error("An error occured while fetching the UUIDs from Mojang");
PluginLogger.error("An error occurred while fetching the UUIDs from Mojang");
throw ex;
}
catch(InterruptedException ex)

View File

@ -21,10 +21,8 @@ package fr.moribus.imageonmap.ui;
import fr.moribus.imageonmap.map.ImageMap;
import fr.moribus.imageonmap.map.PosterMap;
import fr.moribus.imageonmap.map.SingleMap;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.Queue;
import java.util.UUID;
import fr.zcraft.zlib.components.gui.GuiUtils;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
@ -32,6 +30,11 @@ import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.Queue;
import java.util.UUID;
public class MapItemManager implements Listener
{
static private HashMap<UUID, Queue<ItemStack>> mapItemCache;
@ -56,7 +59,7 @@ public class MapItemManager implements Listener
static public boolean give(Player player, SingleMap map)
{
return give(player, createMapItem(map.getMapsIDs()[0], map.getName()));
return give(player, createMapItem(map));
}
static public boolean give(Player player, PosterMap map)
@ -69,7 +72,7 @@ public class MapItemManager implements Listener
{
if(map.hasColumnData())
{
mapName = map.getName() +
mapName = map.getName() +
" (row " + map.getRowAt(i) +
", column " + map.getColumnAt(i) + ")";
}
@ -112,16 +115,70 @@ public class MapItemManager implements Listener
}
}
static public ItemStack createMapItem(SingleMap map)
{
return createMapItem(map.getMapsIDs()[0], map.getName());
}
static public ItemStack createMapItem(PosterMap map, int x, int y)
{
String mapName;
if(map.hasColumnData())
{
mapName = map.getName() +
" (row " + x +
", column " + y + ")";
}
else
{
mapName = map.getName();
}
return createMapItem(map.getMapIdAt(x, y), mapName);
}
static public ItemStack createMapItem(short mapID, String text)
{
ItemStack itemMap = new ItemStack(Material.MAP, 1, mapID);
ItemMeta meta = itemMap.getItemMeta();
meta.setDisplayName(text);
meta.setDisplayName(ChatColor.RESET + text);
GuiUtils.hideItemAttributes(meta);
itemMap.setItemMeta(meta);
return itemMap;
}
/**
* Returns the item to place to display the (col;row) part of the given poster.
*
* @param col The column to display. Starts at 0.
* @param row The row to display. Starts at 0.
*
* @return The map.
*
* @throws ArrayIndexOutOfBoundsException If col;row is not inside the map.
*/
static public ItemStack createSubMapItem(ImageMap map, int col, int row)
{
if(map instanceof PosterMap && ((PosterMap) map).hasColumnData())
{
return MapItemManager.createMapItem(
((PosterMap) map).getMapIdAt(row, col),
map.getName() +
" (row " + (row + 1) +
", column " + (col + 1) + ")"
);
}
else
{
if(row != 0 || col != 0)
{
throw new ArrayIndexOutOfBoundsException(); // Coherence
}
return MapItemManager.createMapItem(map.getMapsIDs()[0], map.getName());
}
}
static public int getCacheSize(Player player)
{

View File

@ -1,132 +0,0 @@
/*
* Copyright (C) 2013 Moribus
* Copyright (C) 2015 ProkopyL <prokopylmc@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.moribus.imageonmap.worker;
import fr.moribus.imageonmap.PluginLogger;
import java.util.ArrayDeque;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
public abstract class Worker
{
private final String name;
private final ArrayDeque<WorkerRunnable> runQueue = new ArrayDeque<>();
private final WorkerCallbackManager callbackManager;
private final WorkerMainThreadExecutor mainThreadExecutor;
private Thread thread;
protected Worker(String name)
{
this(name, false);
}
protected Worker(String name, boolean runMainThreadExecutor)
{
this.name = name;
this.callbackManager = new WorkerCallbackManager(name);
this.mainThreadExecutor = runMainThreadExecutor ? new WorkerMainThreadExecutor(name) : null;
}
protected void init()
{
if(thread != null && thread.isAlive())
{
PluginLogger.warning("Restarting '{0}' thread.", name);
exit();
}
callbackManager.init();
if(mainThreadExecutor != null) mainThreadExecutor.init();
thread = createThread();
thread.start();
}
protected void exit()
{
thread.interrupt();
callbackManager.exit();
if(mainThreadExecutor != null) mainThreadExecutor.exit();
thread = null;
}
private void run()
{
WorkerRunnable currentRunnable;
while(!Thread.interrupted())
{
synchronized(runQueue)
{
try
{
while(runQueue.isEmpty()) runQueue.wait();
}
catch(InterruptedException ex)
{
break;
}
currentRunnable = runQueue.pop();
}
try
{
callbackManager.callback(currentRunnable, currentRunnable.run());
}
catch(Throwable ex)
{
callbackManager.callback(currentRunnable, null, ex);
}
}
}
protected void submitQuery(WorkerRunnable runnable)
{
synchronized(runQueue)
{
runQueue.add(runnable);
runQueue.notify();
}
}
protected void submitQuery(WorkerRunnable runnable, WorkerCallback callback)
{
callbackManager.setupCallback(runnable, callback);
submitQuery(runnable);
}
protected <T> Future<T> submitToMainThread(Callable<T> callable)
{
if(mainThreadExecutor != null) return mainThreadExecutor.submit(callable);
return null;
}
private Thread createThread()
{
return new Thread("ImageOnMap-" + name)
{
@Override
public void run()
{
Worker.this.run();
}
};
}
}

View File

@ -1,24 +0,0 @@
/*
* Copyright (C) 2013 Moribus
* Copyright (C) 2015 ProkopyL <prokopylmc@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.moribus.imageonmap.worker;
public interface WorkerCallback<T>
{
public void finished(T result);
public void errored(Throwable exception);
}

View File

@ -1,152 +0,0 @@
/*
* Copyright (C) 2013 Moribus
* Copyright (C) 2015 ProkopyL <prokopylmc@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.moribus.imageonmap.worker;
import fr.moribus.imageonmap.ImageOnMap;
import java.util.ArrayDeque;
import java.util.HashMap;
import org.bukkit.Bukkit;
import org.bukkit.scheduler.BukkitTask;
class WorkerCallbackManager implements Runnable
{
static private final int WATCH_LOOP_DELAY = 5;
private final HashMap<WorkerRunnable, WorkerRunnableInfo> callbacks;
private final ArrayDeque<WorkerRunnableInfo> callbackQueue;
private final String name;
private BukkitTask selfTask;
public WorkerCallbackManager(String name)
{
callbacks = new HashMap<>();
callbackQueue = new ArrayDeque<>();
this.name = name;
}
public void init()
{
selfTask = Bukkit.getScheduler().runTaskTimer(ImageOnMap.getPlugin(), this, 0, WATCH_LOOP_DELAY);
}
public void setupCallback(WorkerRunnable runnable, WorkerCallback callback)
{
synchronized(callbacks)
{
callbacks.put(runnable, new WorkerRunnableInfo(callback));
}
}
public <T> void callback(WorkerRunnable<T> runnable, T result)
{
callback(runnable, result, null);
}
public <T> void callback(WorkerRunnable<T> runnable, T result, Throwable exception)
{
WorkerRunnableInfo<T> runnableInfo;
synchronized(callbacks)
{
runnableInfo = callbacks.get(runnable);
}
if(runnableInfo == null) return;
runnableInfo.setRunnableException(exception);
runnableInfo.setResult(result);
enqueueCallback(runnableInfo);
}
public void exit()
{
if(selfTask != null) selfTask.cancel();
}
private void enqueueCallback(WorkerRunnableInfo runnableInfo)
{
synchronized(callbackQueue)
{
callbackQueue.add(runnableInfo);
}
}
@Override
public void run()
{
WorkerRunnableInfo currentRunnableInfo;
synchronized(callbackQueue)
{
if(callbackQueue.isEmpty()) return;
currentRunnableInfo = callbackQueue.pop();
}
currentRunnableInfo.runCallback();
}
private class WorkerRunnableInfo<T>
{
private final WorkerCallback<T> callback;
private T result;
private Throwable runnableException;
public WorkerRunnableInfo(WorkerCallback callback)
{
this.callback = callback;
this.runnableException = null;
}
public WorkerCallback getCallback()
{
return callback;
}
public void runCallback()
{
if(runnableCrashed())
{
callback.errored(runnableException);
}
else
{
callback.finished(result);
}
}
public void setResult(T result)
{
this.result = result;
}
public Throwable getRunnableException()
{
return runnableException;
}
public void setRunnableException(Throwable runnableException)
{
this.runnableException = runnableException;
}
public boolean runnableCrashed()
{
return this.runnableException != null;
}
}
}

View File

@ -1,199 +0,0 @@
/*
* Copyright (C) 2013 Moribus
* Copyright (C) 2015 ProkopyL <prokopylmc@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.moribus.imageonmap.worker;
import fr.moribus.imageonmap.ImageOnMap;
import java.util.ArrayDeque;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.bukkit.Bukkit;
import org.bukkit.scheduler.BukkitTask;
class WorkerMainThreadExecutor implements Runnable
{
static private final int WATCH_LOOP_DELAY = 1;
private final String name;
private final ArrayDeque<WorkerFuture> mainThreadQueue = new ArrayDeque<>();
private BukkitTask mainThreadTask;
public WorkerMainThreadExecutor(String name)
{
this.name = name;
}
public void init()
{
mainThreadTask = Bukkit.getScheduler().runTaskTimer(ImageOnMap.getPlugin(), this, 0, WATCH_LOOP_DELAY);
}
public void exit()
{
mainThreadTask.cancel();
mainThreadTask = null;
}
public <T> Future<T> submit(Callable<T> callable)
{
WorkerFuture<T> future = new WorkerFuture<T>(callable);
synchronized(mainThreadQueue)
{
mainThreadQueue.add(future);
}
return future;
}
@Override
public void run()
{
WorkerFuture currentFuture;
synchronized(mainThreadQueue)
{
if(mainThreadQueue.isEmpty()) return;
currentFuture = mainThreadQueue.pop();
}
currentFuture.runCallable();
}
private class WorkerFuture<T> implements Future<T>
{
private final Callable<T> callable;
private boolean isCancelled;
private boolean isDone;
private Exception executionException;
private T value;
public WorkerFuture(Callable<T> callable)
{
this.callable = callable;
}
public void runCallable()
{
try
{
value = callable.call();
}
catch(Exception ex)
{
executionException = ex;
}
finally
{
isDone = true;
synchronized(this){this.notifyAll();}
}
}
@Override
public boolean cancel(boolean mayInterruptIfRunning)
{
if(this.isCancelled || this.isDone) return false;
this.isCancelled = true;
this.isDone = true;
return true;
}
@Override
public boolean isCancelled()
{
return this.isCancelled;
}
@Override
public boolean isDone()
{
return this.isDone;
}
@Override
public T get() throws InterruptedException, ExecutionException
{
waitForCompletion();
if(executionException != null) throw new ExecutionException(executionException);
return value;
}
@Override
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
{
waitForCompletion(timeout, unit);
if(executionException != null) throw new ExecutionException(executionException);
return value;
}
private void waitForCompletion(long timeout) throws InterruptedException, TimeoutException
{
synchronized(this)
{
long remainingTime;
long timeoutTime = System.currentTimeMillis() + timeout;
while(!isDone)
{
remainingTime = timeoutTime - System.currentTimeMillis();
if(remainingTime <= 0) throw new TimeoutException();
this.wait(remainingTime);
}
}
}
private void waitForCompletion() throws InterruptedException
{
synchronized(this)
{
while(!isDone) this.wait();
}
}
private void waitForCompletion(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException
{
long millis = 0;
switch(unit)
{
case NANOSECONDS:
millis = timeout / 10^6;
break;
case MICROSECONDS:
millis = timeout / 10^3;
break;
case MILLISECONDS:
millis = timeout;
break;
case SECONDS:
millis = timeout * 10^3;
break;
case MINUTES:
millis = timeout * 10^3 * 60;
break;
case HOURS:
millis = timeout * 10^3 * 3600;
break;
case DAYS:
millis = timeout * 10^3 * 3600 * 24;
}
waitForCompletion(millis);
}
}
}

View File

@ -1,23 +0,0 @@
/*
* Copyright (C) 2013 Moribus
* Copyright (C) 2015 ProkopyL <prokopylmc@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.moribus.imageonmap.worker;
public interface WorkerRunnable<T>
{
public T run() throws Throwable;
}

View File

@ -8,6 +8,9 @@ commands:
usage: /<command> [URL]
maptool:
description: manage maps
maps:
description: manage maps through a GUI
permissions:
imageonmap.userender:
description: Allows you to use /tomap