diff --git a/src/net/citizensnpcs/Citizens.java b/src/net/citizensnpcs/Citizens.java index cc0e9d5e2..e0a66a3ad 100644 --- a/src/net/citizensnpcs/Citizens.java +++ b/src/net/citizensnpcs/Citizens.java @@ -1,11 +1,13 @@ package net.citizensnpcs; import java.io.File; +import java.io.IOException; import java.util.List; import java.util.logging.Level; import net.citizensnpcs.Settings.Setting; import net.citizensnpcs.api.CitizensAPI; +import net.citizensnpcs.api.DataKey; import net.citizensnpcs.api.exception.NPCException; import net.citizensnpcs.api.exception.NPCLoadException; import net.citizensnpcs.api.npc.NPC; @@ -16,10 +18,6 @@ import net.citizensnpcs.api.npc.trait.trait.Inventory; import net.citizensnpcs.api.npc.trait.trait.Owner; import net.citizensnpcs.api.npc.trait.trait.SpawnLocation; import net.citizensnpcs.api.npc.trait.trait.Spawned; -import net.citizensnpcs.api.util.DataKey; -import net.citizensnpcs.api.util.DatabaseStorage; -import net.citizensnpcs.api.util.Storage; -import net.citizensnpcs.api.util.YamlStorage; import net.citizensnpcs.command.CommandManager; import net.citizensnpcs.command.Injector; import net.citizensnpcs.command.command.AdminCommands; @@ -32,9 +30,13 @@ import net.citizensnpcs.command.exception.ServerCommandException; import net.citizensnpcs.command.exception.UnhandledCommandException; import net.citizensnpcs.command.exception.WrappedCommandException; import net.citizensnpcs.npc.CitizensNPCManager; +import net.citizensnpcs.storage.DatabaseStorage; +import net.citizensnpcs.storage.Storage; +import net.citizensnpcs.storage.YamlStorage; import net.citizensnpcs.trait.LookClose; import net.citizensnpcs.trait.Sneak; import net.citizensnpcs.util.Messaging; +import net.citizensnpcs.util.Metrics; import net.citizensnpcs.util.StringHelper; import org.bukkit.Bukkit; @@ -46,6 +48,7 @@ import org.bukkit.entity.CreatureType; import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; +import com.google.common.collect.Iterators; import com.google.common.collect.Lists; public class Citizens extends JavaPlugin { @@ -58,16 +61,16 @@ public class Citizens extends JavaPlugin { private volatile CitizensNPCManager npcManager; private final DefaultInstanceFactory characterManager = new DefaultInstanceFactory(); private final DefaultInstanceFactory traitManager = new DefaultInstanceFactory(); - private final CommandManager cmdManager = new CommandManager(); + private final CommandManager commands = new CommandManager(); private Settings config; private Storage saves; private Storage templates; private boolean compatible; - private boolean handleMistake(CommandSender sender, String command, String modifier) { + private boolean suggestClosestModifier(CommandSender sender, String command, String modifier) { int minDist = Integer.MAX_VALUE; String closest = ""; - for (String string : cmdManager.getAllCommandModifiers(command)) { + for (String string : commands.getAllCommandModifiers(command)) { int distance = StringHelper.getLevenshteinDistance(modifier, string); if (minDist > distance) { minDist = distance; @@ -94,16 +97,10 @@ public class Citizens extends JavaPlugin { System.arraycopy(args, 0, split, 1, args.length); split[0] = cmd.getName().toLowerCase(); - String modifier = ""; - if (args.length > 0) - modifier = args[0]; + String modifier = args.length > 0 ? args[0] : ""; - // No command found! - if (!cmdManager.hasCommand(split[0], modifier)) { - if (!modifier.isEmpty()) { - boolean value = handleMistake(sender, split[0], modifier); - return value; - } + if (!commands.hasCommand(split[0], modifier) && !modifier.isEmpty()) { + return suggestClosestModifier(sender, split[0], modifier); } NPC npc = null; @@ -111,7 +108,7 @@ public class Citizens extends JavaPlugin { npc = npcManager.getSelectedNPC(player); try { - cmdManager.execute(split, player, player == null ? sender : player, npc); + commands.execute(split, player, player == null ? sender : player, npc); } catch (ServerCommandException ex) { sender.sendMessage("You must be in-game to execute that command."); } catch (NoPermissionsException ex) { @@ -167,10 +164,34 @@ public class Citizens extends JavaPlugin { config.load(); // NPC storage - if (Setting.USE_DATABASE.asBoolean()) + if (Setting.USE_DATABASE.asBoolean()) { saves = new DatabaseStorage(); - else + } else { saves = new YamlStorage(getDataFolder() + File.separator + "saves.yml", "Citizens NPC Storage"); + } + + new Thread() { + @Override + public void run() { + try { + Metrics metrics = new Metrics(); + metrics.addCustomData(Citizens.this, new Metrics.Plotter() { + @Override + public int getValue() { + return Iterators.size(npcManager.iterator()); + } + + @Override + public String getColumnName() { + return "Total NPCs"; + } + }); + metrics.beginMeasuringPlugin(Citizens.this); + } catch (IOException ex) { + Messaging.log("Unable to load metrics"); + } + } + }.start(); // Templates templates = new YamlStorage(getDataFolder() + File.separator + "templates.yml", "NPC Templates"); @@ -221,7 +242,7 @@ public class Citizens extends JavaPlugin { } public CommandManager getCommandManager() { - return cmdManager; + return commands; } public Storage getStorage() { @@ -233,12 +254,12 @@ public class Citizens extends JavaPlugin { } private void registerCommands() { - cmdManager.setInjector(new Injector(this)); + commands.setInjector(new Injector(this)); // Register command classes - cmdManager.register(AdminCommands.class); - cmdManager.register(NPCCommands.class); - cmdManager.register(HelpCommands.class); + commands.register(AdminCommands.class); + commands.register(NPCCommands.class); + commands.register(HelpCommands.class); } private void saveNPCs() { @@ -254,15 +275,16 @@ public class Citizens extends JavaPlugin { if (!key.keyExists("name")) throw new NPCLoadException("Could not find a name for the NPC with ID '" + id + "'."); - String type = key.getString("traits.type"); + String type = key.getString("traits.type").toUpperCase(); NPC npc = npcManager.createNPC( - type.equalsIgnoreCase("DEFAULT") ? null : CreatureType.valueOf(key.getString("traits.type") - .toUpperCase()), id, key.getString("name"), null); + type.equalsIgnoreCase("DEFAULT") ? CreatureType.MONSTER : CreatureType.valueOf(type), id, + key.getString("name"), null); try { npc.load(key); } catch (NPCException ex) { Messaging.log(ex.getMessage()); } + ++created; if (npc.isSpawned()) ++spawned; diff --git a/src/net/citizensnpcs/Settings.java b/src/net/citizensnpcs/Settings.java index 324aa6457..d21ee1963 100644 --- a/src/net/citizensnpcs/Settings.java +++ b/src/net/citizensnpcs/Settings.java @@ -2,8 +2,8 @@ package net.citizensnpcs; import java.io.File; -import net.citizensnpcs.api.util.DataKey; -import net.citizensnpcs.api.util.YamlStorage; +import net.citizensnpcs.api.DataKey; +import net.citizensnpcs.storage.YamlStorage; import net.citizensnpcs.util.Messaging; public class Settings { @@ -30,9 +30,11 @@ public class Settings { } public enum Setting { - DEBUG_MODE("general.debug-mode", false), USE_DATABASE("use-database", false), SELECTION_ITEM( - "npc.selection.item", 280), SELECTION_MESSAGE("npc.selection.message", "You selected !"), QUICK_SELECT( - "npc.selection.quick-select", false); + DEBUG_MODE("general.debug-mode", false), + USE_DATABASE("use-database", false), + SELECTION_ITEM("npc.selection.item", 280), + SELECTION_MESSAGE("npc.selection.message", "You selected !"), + QUICK_SELECT("npc.selection.quick-select", false); private String path; private Object value; diff --git a/src/net/citizensnpcs/Template.java b/src/net/citizensnpcs/Template.java index 25ace8bf2..1137c1eb7 100644 --- a/src/net/citizensnpcs/Template.java +++ b/src/net/citizensnpcs/Template.java @@ -1,6 +1,6 @@ package net.citizensnpcs; -import net.citizensnpcs.api.util.DataKey; +import net.citizensnpcs.api.DataKey; public class Template { private final DataKey template; diff --git a/src/net/citizensnpcs/command/command/NPCCommands.java b/src/net/citizensnpcs/command/command/NPCCommands.java index 497fd44b3..2681e4765 100644 --- a/src/net/citizensnpcs/command/command/NPCCommands.java +++ b/src/net/citizensnpcs/command/command/NPCCommands.java @@ -51,7 +51,7 @@ public class NPCCommands { Messaging.sendError(player, "NPC names cannot be longer than 16 characters. The name has been shortened."); name = name.substring(0, 15); } - CreatureType type = null; + CreatureType type = CreatureType.MONSTER; // Default NPC type if (args.hasValueFlag("type")) try { type = CreatureType.valueOf(args.getFlag("type").toUpperCase().replace('-', '_')); @@ -89,7 +89,7 @@ public class NPCCommands { create.addTrait(new Owner(player.getName())); // Set the mob type - create.addTrait(new MobType(type == null ? "DEFAULT" : type.toString())); + create.addTrait(new MobType(type == CreatureType.MONSTER ? "DEFAULT" : type.toString())); create.spawn(player.getLocation()); npcManager.selectNPC(player, create); diff --git a/src/net/citizensnpcs/npc/CitizensNPCManager.java b/src/net/citizensnpcs/npc/CitizensNPCManager.java index 546031681..ce7a04017 100644 --- a/src/net/citizensnpcs/npc/CitizensNPCManager.java +++ b/src/net/citizensnpcs/npc/CitizensNPCManager.java @@ -9,7 +9,7 @@ import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPCManager; import net.citizensnpcs.api.npc.trait.Character; import net.citizensnpcs.api.npc.trait.trait.SpawnLocation; -import net.citizensnpcs.api.util.Storage; +import net.citizensnpcs.storage.Storage; import net.citizensnpcs.util.ByIdArray; import net.citizensnpcs.util.NPCBuilder; diff --git a/src/net/citizensnpcs/npc/entity/CitizensHumanNPC.java b/src/net/citizensnpcs/npc/entity/CitizensHumanNPC.java index 1d1e9d14b..c548cb159 100644 --- a/src/net/citizensnpcs/npc/entity/CitizensHumanNPC.java +++ b/src/net/citizensnpcs/npc/entity/CitizensHumanNPC.java @@ -1,5 +1,7 @@ package net.citizensnpcs.npc.entity; +import net.citizensnpcs.api.DataKey; +import net.citizensnpcs.api.exception.NPCLoadException; import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.CitizensNPCManager; import net.citizensnpcs.resource.lib.EntityHumanNPC; @@ -53,4 +55,9 @@ public class CitizensHumanNPC extends CitizensNPC { handle.setPositionRotation(loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch()); return handle; } + + @Override + public void load(DataKey key) throws NPCLoadException { + super.load(key); + } } \ No newline at end of file diff --git a/src/net/citizensnpcs/storage/DatabaseStorage.java b/src/net/citizensnpcs/storage/DatabaseStorage.java new file mode 100644 index 000000000..ff8b801d3 --- /dev/null +++ b/src/net/citizensnpcs/storage/DatabaseStorage.java @@ -0,0 +1,143 @@ +package net.citizensnpcs.storage; + +import java.util.List; + +import net.citizensnpcs.api.DataKey; + +public class DatabaseStorage implements Storage { + + @Override + public DataKey getKey(String root) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void load() { + // TODO Auto-generated method stub + + } + + @Override + public void save() { + // TODO Auto-generated method stub + + } + + public class DatabaseKey extends DataKey { + + @Override + public void copy(String to) { + // TODO Auto-generated method stub + + } + + @Override + public boolean getBoolean(String key) { + // TODO Auto-generated method stub + return false; + } + + @Override + public double getDouble(String key) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getInt(String key) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public List getIntegerSubKeys() { + // TODO Auto-generated method stub + return null; + } + + @Override + public long getLong(String key) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public Object getRaw(String key) { + // TODO Auto-generated method stub + return null; + } + + @Override + public DataKey getRelative(String relative) { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getString(String key) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Iterable getSubKeys() { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean keyExists(String key) { + // TODO Auto-generated method stub + return false; + } + + @Override + public String name() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void removeKey(String key) { + // TODO Auto-generated method stub + + } + + @Override + public void setBoolean(String key, boolean value) { + // TODO Auto-generated method stub + + } + + @Override + public void setDouble(String key, double value) { + // TODO Auto-generated method stub + + } + + @Override + public void setInt(String key, int value) { + // TODO Auto-generated method stub + + } + + @Override + public void setLong(String key, long value) { + // TODO Auto-generated method stub + + } + + @Override + public void setRaw(String path, Object value) { + // TODO Auto-generated method stub + + } + + @Override + public void setString(String key, String value) { + // TODO Auto-generated method stub + + } + } +} \ No newline at end of file diff --git a/src/net/citizensnpcs/storage/Storage.java b/src/net/citizensnpcs/storage/Storage.java new file mode 100644 index 000000000..769017081 --- /dev/null +++ b/src/net/citizensnpcs/storage/Storage.java @@ -0,0 +1,12 @@ +package net.citizensnpcs.storage; + +import net.citizensnpcs.api.DataKey; + +public interface Storage { + + public DataKey getKey(String root); + + public void load(); + + public void save(); +} \ No newline at end of file diff --git a/src/net/citizensnpcs/storage/YamlStorage.java b/src/net/citizensnpcs/storage/YamlStorage.java new file mode 100644 index 000000000..c97cb8da9 --- /dev/null +++ b/src/net/citizensnpcs/storage/YamlStorage.java @@ -0,0 +1,261 @@ +package net.citizensnpcs.storage; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.logging.Level; + +import net.citizensnpcs.api.DataKey; +import net.citizensnpcs.util.Messaging; + +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; + +public class YamlStorage implements Storage { + private final FileConfiguration config; + private final File file; + + public YamlStorage(String fileName, String header) { + config = new YamlConfiguration(); + file = new File(fileName); + if (!file.exists()) { + create(); + config.options().header(header); + save(); + } else + load(); + } + + private void create() { + try { + Messaging.log("Creating file: " + file.getName()); + file.getParentFile().mkdirs(); + file.createNewFile(); + } catch (IOException ex) { + Messaging.log(Level.SEVERE, "Could not create file: " + file.getName()); + } + } + + @Override + public DataKey getKey(String root) { + return new YamlKey(root); + } + + @Override + public void load() { + try { + config.load(file); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + private boolean pathExists(String key) { + return config.get(key) != null; + } + + @Override + public void save() { + try { + config.save(file); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + public class YamlKey extends DataKey { + private final String current; + + public YamlKey(String root) { + current = root; + } + + @Override + public void copy(String to) { + ConfigurationSection root = config.getConfigurationSection(current); + if (root == null) + return; + config.createSection(to, root.getValues(true)); + } + + @Override + public boolean getBoolean(String key) { + String path = getKeyExt(key); + if (pathExists(path)) { + if (config.getString(path) == null) + return config.getBoolean(path); + return Boolean.parseBoolean(config.getString(path)); + } + return false; + } + + @Override + public boolean getBoolean(String key, boolean def) { + return config.getBoolean(getKeyExt(key), def); + } + + @Override + public double getDouble(String key) { + String path = getKeyExt(key); + if (pathExists(path)) { + if (config.getString(path) == null) { + if (config.get(path) instanceof Integer) + return config.getInt(path); + return config.getDouble(path); + } + return Double.parseDouble(config.getString(path)); + } + return 0; + } + + @Override + public double getDouble(String key, double def) { + return config.getDouble(getKeyExt(key), def); + } + + @Override + public int getInt(String key) { + String path = getKeyExt(key); + if (pathExists(path)) { + if (config.getString(path) == null) + return config.getInt(path); + return Integer.parseInt(config.getString(path)); + } + return 0; + } + + @Override + public int getInt(String key, int def) { + return config.getInt(getKeyExt(key), def); + } + + @Override + public List getIntegerSubKeys() { + List res = new ArrayList(); + ConfigurationSection section = config.getConfigurationSection(current); + if (section == null) + return res; + List keys = new ArrayList(); + for (String key : section.getKeys(false)) { + try { + keys.add(Integer.parseInt(key)); + } catch (NumberFormatException ex) { + } + } + Collections.sort(keys); + for (int key : keys) + res.add(getRelative(Integer.toString(key))); + return res; + } + + private String getKeyExt(String from) { + if (from.isEmpty()) + return current; + if (from.charAt(0) == '.') + return current.isEmpty() ? from.substring(1, from.length()) : current + from; + return current.isEmpty() ? from : current + "." + from; + } + + @Override + public long getLong(String key) { + String path = getKeyExt(key); + if (pathExists(path)) { + if (config.getString(path) == null) { + if (config.get(path) instanceof Integer) + return config.getInt(path); + return config.getLong(path); + } + return Long.parseLong(config.getString(path)); + } + return 0; + } + + @Override + public long getLong(String key, long def) { + return config.getLong(getKeyExt(key), def); + } + + @Override + public Object getRaw(String key) { + return config.get(getKeyExt(key)); + } + + @Override + public DataKey getRelative(String relative) { + if (relative == null || relative.isEmpty()) + return this; + return new YamlKey(getKeyExt(relative)); + } + + @Override + public String getString(String key) { + String path = getKeyExt(key); + if (pathExists(path)) { + return config.get(path).toString(); + } + return ""; + } + + @Override + public Iterable getSubKeys() { + List res = new ArrayList(); + ConfigurationSection section = config.getConfigurationSection(current); + if (section == null) + return res; + for (String key : section.getKeys(false)) { + res.add(getRelative(key)); + } + return res; + } + + @Override + public boolean keyExists(String key) { + return config.get(getKeyExt(key)) != null; + } + + @Override + public String name() { + int last = current.lastIndexOf('.'); + return current.substring(last == 0 ? 0 : last + 1); + } + + @Override + public void removeKey(String key) { + config.set(getKeyExt(key), null); + save(); + } + + @Override + public void setBoolean(String key, boolean value) { + config.set(getKeyExt(key), value); + } + + @Override + public void setDouble(String key, double value) { + config.set(getKeyExt(key), String.valueOf(value)); + } + + @Override + public void setInt(String key, int value) { + config.set(getKeyExt(key), value); + } + + @Override + public void setLong(String key, long value) { + config.set(getKeyExt(key), value); + } + + @Override + public void setRaw(String key, Object value) { + config.set(getKeyExt(key), value); + } + + @Override + public void setString(String key, String value) { + config.set(getKeyExt(key), value); + } + } +} \ No newline at end of file diff --git a/src/net/citizensnpcs/trait/LookClose.java b/src/net/citizensnpcs/trait/LookClose.java index b44ba5108..1eaaae964 100644 --- a/src/net/citizensnpcs/trait/LookClose.java +++ b/src/net/citizensnpcs/trait/LookClose.java @@ -3,11 +3,11 @@ package net.citizensnpcs.trait; import org.bukkit.Location; import org.bukkit.entity.Entity; +import net.citizensnpcs.api.DataKey; import net.citizensnpcs.api.exception.NPCLoadException; import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.trait.SaveId; import net.citizensnpcs.api.npc.trait.Trait; -import net.citizensnpcs.api.util.DataKey; import net.citizensnpcs.npc.CitizensNPC; import net.minecraft.server.EntityLiving; diff --git a/src/net/citizensnpcs/trait/Sneak.java b/src/net/citizensnpcs/trait/Sneak.java index 2c6735e7b..b394e2951 100644 --- a/src/net/citizensnpcs/trait/Sneak.java +++ b/src/net/citizensnpcs/trait/Sneak.java @@ -1,10 +1,10 @@ package net.citizensnpcs.trait; +import net.citizensnpcs.api.DataKey; import net.citizensnpcs.api.exception.NPCLoadException; import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.trait.SaveId; import net.citizensnpcs.api.npc.trait.Trait; -import net.citizensnpcs.api.util.DataKey; import net.citizensnpcs.npc.entity.CitizensHumanNPC; import net.minecraft.server.EntityPlayer; diff --git a/src/net/citizensnpcs/util/Metrics.java b/src/net/citizensnpcs/util/Metrics.java new file mode 100644 index 000000000..6a09193df --- /dev/null +++ b/src/net/citizensnpcs/util/Metrics.java @@ -0,0 +1,300 @@ +/* + * Copyright 2011 Tyler Blair. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and contributors and should not be interpreted as representing official policies, + * either expressed or implied, of anybody else. + */ +package net.citizensnpcs.util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.net.Proxy; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLEncoder; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.Plugin; + +/** + * Tooling to post to metrics.griefcraft.com + */ +public class Metrics { + + /** + * Interface used to collect custom data for a plugin + */ + public static abstract class Plotter { + + /** + * Get the column name for the plotted point + * + * @return the plotted point's column name + */ + public abstract String getColumnName(); + + /** + * Get the current value for the plotted point + * + * @return + */ + public abstract int getValue(); + + /** + * Called after the website graphs have been updated + */ + public void reset() { + } + + @Override + public int hashCode() { + return getColumnName().hashCode() + getValue(); + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof Plotter)) { + return false; + } + + Plotter plotter = (Plotter) object; + return plotter.getColumnName().equals(getColumnName()) && plotter.getValue() == getValue(); + } + + } + + /** + * The metrics revision number + */ + private final static int REVISION = 4; + + /** + * The base url of the metrics domain + */ + private static final String BASE_URL = "http://metrics.griefcraft.com"; + + /** + * The url used to report a server's status + */ + private static final String REPORT_URL = "/report/%s"; + + /** + * The file where guid and opt out is stored in + */ + private static final String CONFIG_FILE = "plugins/PluginMetrics/config.yml"; + + /** + * Interval of time to ping in minutes + */ + private final static int PING_INTERVAL = 10; + + /** + * A map of the custom data plotters for plugins + */ + private final Map> customData = Collections + .synchronizedMap(new HashMap>()); + + /** + * The plugin configuration file + */ + private final YamlConfiguration configuration; + + /** + * Unique server id + */ + private final String guid; + + public Metrics() throws IOException { + // load the config + File file = new File(CONFIG_FILE); + configuration = YamlConfiguration.loadConfiguration(file); + + // add some defaults + configuration.addDefault("opt-out", false); + configuration.addDefault("guid", UUID.randomUUID().toString()); + + // Do we need to create the file? + if (configuration.get("guid", null) == null) { + configuration.options().header("http://metrics.griefcraft.com").copyDefaults(true); + configuration.save(file); + } + + // Load the guid then + guid = configuration.getString("guid"); + } + + /** + * Adds a custom data plotter for a given plugin + * + * @param plugin + * @param plotter + */ + public void addCustomData(Plugin plugin, Plotter plotter) { + Set plotters = customData.get(plugin); + + if (plotters == null) { + plotters = Collections.synchronizedSet(new LinkedHashSet()); + customData.put(plugin, plotters); + } + + plotters.add(plotter); + } + + /** + * Begin measuring a plugin + * + * @param plugin + */ + public void beginMeasuringPlugin(final Plugin plugin) throws IOException { + // Did we opt out? + if (configuration.getBoolean("opt-out", false)) { + return; + } + + // First tell the server about us + postPlugin(plugin, false); + + // Ping the server in intervals + plugin.getServer().getScheduler().scheduleAsyncRepeatingTask(plugin, new Runnable() { + @Override + public void run() { + try { + postPlugin(plugin, true); + } catch (IOException e) { + System.out.println("[Metrics] " + e.getMessage()); + } + } + }, PING_INTERVAL * 1200, PING_INTERVAL * 1200); + } + + /** + * Generic method that posts a plugin to the metrics website + * + * @param plugin + */ + private void postPlugin(Plugin plugin, boolean isPing) throws IOException { + // Construct the post data + String response = "ERR No response"; + String data = encode("guid") + '=' + encode(guid) + '&' + encode("version") + '=' + + encode(plugin.getDescription().getVersion()) + '&' + encode("server") + '=' + + encode(Bukkit.getVersion()) + '&' + encode("players") + '=' + + encode(String.valueOf(Bukkit.getServer().getOnlinePlayers().length)) + '&' + encode("revision") + '=' + + encode(REVISION + ""); + + // If we're pinging, append it + if (isPing) { + data += '&' + encode("ping") + '=' + encode("true"); + } + + // Add any custom data (if applicable) + Set plotters = customData.get(plugin); + + if (plotters != null) { + for (Plotter plotter : plotters) { + data += "&" + encode("Custom" + plotter.getColumnName()) + "=" + + encode(Integer.toString(plotter.getValue())); + } + } + + // Create the url + URL url = new URL(BASE_URL + String.format(REPORT_URL, plugin.getDescription().getName())); + + // Connect to the website + URLConnection connection; + + // Mineshafter creates a socks proxy, so we can safely bypass it + if (isMineshafterPresent()) { + connection = url.openConnection(Proxy.NO_PROXY); + } else { + connection = url.openConnection(); + } + + connection.setDoOutput(true); + + // Write the data + OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream()); + writer.write(data); + writer.flush(); + + // Now read the response + BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); + response = reader.readLine(); + + // close resources + writer.close(); + reader.close(); + + if (response.startsWith("ERR")) { + throw new IOException(response); // Throw the exception + } else { + // Is this the first update this hour? + if (response.contains("OK This is your first update this hour")) { + if (plotters != null) { + for (Plotter plotter : plotters) { + plotter.reset(); + } + } + } + } + // if (response.startsWith("OK")) - We should get "OK" followed by an + // optional description if everything goes right + } + + /** + * Check if mineshafter is present. If it is, we need to bypass it to send + * POST requests + * + * @return + */ + private boolean isMineshafterPresent() { + try { + Class.forName("mineshafter.MineServer"); + return true; + } catch (Exception e) { + return false; + } + } + + /** + * Encode text as UTF-8 + * + * @param text + * @return + */ + private static String encode(String text) throws UnsupportedEncodingException { + return URLEncoder.encode(text, "UTF-8"); + } + +} \ No newline at end of file diff --git a/src/net/citizensnpcs/util/NPCBuilder.java b/src/net/citizensnpcs/util/NPCBuilder.java index d0100a98a..dc5deff9e 100644 --- a/src/net/citizensnpcs/util/NPCBuilder.java +++ b/src/net/citizensnpcs/util/NPCBuilder.java @@ -46,6 +46,7 @@ public class NPCBuilder { types.put(CreatureType.GHAST, CitizensGhastNPC.class); types.put(CreatureType.GIANT, CitizensGiantNPC.class); types.put(CreatureType.MAGMA_CUBE, CitizensMagmaCubeNPC.class); + types.put(CreatureType.MONSTER, CitizensHumanNPC.class); types.put(CreatureType.MUSHROOM_COW, CitizensMushroomCowNPC.class); types.put(CreatureType.PIG, CitizensPigNPC.class); types.put(CreatureType.PIG_ZOMBIE, CitizensPigZombieNPC.class); @@ -63,9 +64,6 @@ public class NPCBuilder { public CitizensNPC getByType(CreatureType type, CitizensNPCManager npcManager, int id, String name) { Class npcClass = types.get(type); - if (npcClass == null) - npcClass = CitizensHumanNPC.class; - try { return npcClass.getConstructor(CitizensNPCManager.class, int.class, String.class).newInstance(npcManager, id, name);