import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.minecraft.server.MinecraftServer;
/**
* PluginLoader.java - Used to load plugins, toggle them, etc.
*
* @author James
*/
public class PluginLoader {
/**
* Hook - Used for adding a listener to listen on specific hooks
*/
public enum Hook {
/**
* Calls onLoginChecks
*/
LOGINCHECK,
/**
* Calls onLogin
*/
LOGIN,
/**
* Calls onChat
*/
CHAT,
/**
* Calls onCommand
*/
COMMAND,
/**
* Calls onConsoleCommand
*/
SERVERCOMMAND,
/**
* Calls onBan
*/
BAN,
/**
* Calls onIpBan
*/
IPBAN,
/**
* Calls onKick
*/
KICK,
/**
* Calls onBlockCreate
*/
BLOCK_CREATED,
/**
* Calls onBlockDestroy
*/
BLOCK_DESTROYED,
/**
* Calls onDisconnect
*/
DISCONNECT,
/**
* Calls onPlayerMove
*/
PLAYER_MOVE,
/**
* Calls onArmSwing
*/
ARM_SWING,
/**
* Calls onItemDrop
*/
ITEM_DROP,
/**
* Calls onItemPickUp
*/
ITEM_PICK_UP,
/**
* Calls onTeleport
*/
TELEPORT,
/**
* Calls onBlockBreak
*/
BLOCK_BROKEN,
/**
* Calls onIgnite
*/
IGNITE,
/**
* Calls onFlow
*/
FLOW,
/**
* Calls onExplode
*/
EXPLODE,
/**
* Calls onMobSpawn
*/
MOB_SPAWN,
/**
* Calls onDamage
*/
DAMAGE,
/**
* Calls onHealthChange
*/
HEALTH_CHANGE,
/**
* Calls onRedstoneChange
*/
REDSTONE_CHANGE,
/**
* Calls onBlockPhysics
*/
BLOCK_PHYSICS,
/**
* Calls onVehicleCreate
*/
VEHICLE_CREATE,
/**
* Calls onVehicleUpdate
*/
VEHICLE_UPDATE,
/**
* Calls onVehicleDamage
*/
VEHICLE_DAMAGE,
/**
* Calls onVehicleCollision
*/
VEHICLE_COLLISION,
/**
* Calls onVehicleDestroyed
*/
VEHICLE_DESTROYED,
/**
* Calls onVehicleEntered
*/
VEHICLE_ENTERED,
/**
* Calls onVehiclePositionChange
*/
VEHICLE_POSITIONCHANGE,
/**
* Calls onItemUse
*/
ITEM_USE,
/**
* Calls onBlockPlace
*/
BLOCK_PLACE,
/**
* Calls onBlockRightClicked
*/
BLOCK_RIGHTCLICKED,
/**
* Calls onLiquidDestroy
*/
LIQUID_DESTROY,
/**
* Calls onAttack
*/
ATTACK,
/**
* Calls onOpenInventory
*/
OPEN_INVENTORY,
/**
* Calls onSignShow
*/
SIGN_SHOW,
/**
* Calls onSignChange
*/
SIGN_CHANGE,
/**
* Calls onLeafDecay
*/
LEAF_DECAY,
/**
* Unused.
*/
NUM_HOOKS
}
/**
* HookResult - Used where returning a boolean isn't enough.
*/
public enum HookResult {
/**
* Prevent the action
*/
PREVENT_ACTION,
/**
* Allow the action
*/
ALLOW_ACTION,
/**
* Do whatever it would normally do, continue processing
*/
DEFAULT_ACTION
}
public enum DamageType {
/**
* Creeper explosion
*/
CREEPER_EXPLOSION,
/**
* Damage dealt by another entity
*/
ENTITY,
/**
* Damage caused by explosion
*/
EXPLOSION,
/**
* Damage caused from falling (fall distance - 3.0)
*/
FALL,
/**
* Damage caused by fire (1)
*/
FIRE,
/**
* Low periodic damage caused by burning (1)
*/
FIRE_TICK,
/**
* Damage caused from lava (4)
*/
LAVA,
/**
* Damage caused from drowning (2)
*/
WATER,
/**
* Damage caused by cactus (1)
*/
CACTUS,
/**
* Damage caused by suffocating(1)
*/
SUFFOCATION
}
private static final Logger log = Logger.getLogger("Minecraft");
private static final Object lock = new Object();
private List<Plugin> plugins = new ArrayList<Plugin>();
private List<List<PluginRegisteredListener>> listeners = new ArrayList<List<PluginRegisteredListener>>();
private HashMap<String, PluginInterface> customListeners = new HashMap<String, PluginInterface>();
private Server server;
private PropertiesFile properties;
private volatile boolean loaded = false;
/**
* Creates a plugin loader
*
* @param server
* server to use
*/
public PluginLoader(MinecraftServer server) {
properties = new PropertiesFile("server.properties");
this.server = new Server(server);
for (int h = 0; h < Hook.NUM_HOOKS.ordinal(); ++h)
listeners.add(new ArrayList<PluginRegisteredListener>());
}
/**
* Loads all plugins.
*/
public void loadPlugins() {
if (loaded)
return;
log.info("hMod: Loading plugins...");
String[] classes = properties.getString("plugins", "").split(",");
for (String sclass : classes) {
if (sclass.equals(""))
continue;
loadPlugin(sclass.trim());
}
log.info("hMod: Loaded " + plugins.size() + " plugins.");
loaded = true;
}
/**
* Loads the specified plugin
*
* @param fileName
* file name of plugin to load
* @return if the operation was successful
*/
public Boolean loadPlugin(String fileName) {
if (getPlugin(fileName) != null)
return false; // Already exists.
return load(fileName);
}
/**
* Reloads the specified plugin
*
* @param fileName
* file name of plugin to reload
* @return if the operation was successful
*/
public Boolean reloadPlugin(String fileName) {
/* Not sure exactly how much of this is necessary */
Plugin toNull = getPlugin(fileName);
if (toNull != null)
if (toNull.isEnabled())
toNull.disable();
synchronized (lock) {
plugins.remove(toNull);
for (List<PluginRegisteredListener> regListeners : listeners) {
Iterator<PluginRegisteredListener> iter = regListeners.iterator();
while (iter.hasNext())
if (iter.next().getPlugin() == toNull)
iter.remove();
}
}
toNull = null;
return load(fileName);
}
private Boolean load(String fileName) {
try {
File file = new File("plugins/" + fileName + ".jar");
if (!file.exists()) {
log.log(Level.SEVERE, "Failed to find plugin file: plugins/" + fileName + ".jar. Please ensure the file exists");
return false;
}
URLClassLoader child = null;
try {
child = new MyClassLoader(new URL[] { file.toURI().toURL() }, Thread.currentThread().getContextClassLoader());
} catch (MalformedURLException ex) {
log.log(Level.SEVERE, "Exception while loading class", ex);
return false;
}
Class<?> c = child.loadClass(fileName);
Plugin plugin = (Plugin) c.newInstance();
plugin.setName(fileName);
plugin.enable();
synchronized (lock) {
plugins.add(plugin);
plugin.initialize();
}
} catch (Throwable ex) {
log.log(Level.SEVERE, "Exception while loading plugin", ex);
return false;
}
return true;
}
/**
* Returns the specified plugin
*
* @param name
* name of plugin
* @return plugin
*/
public Plugin getPlugin(String name) {
synchronized (lock) {
for (Plugin plugin : plugins)
if (plugin.getName().equalsIgnoreCase(name))
return plugin;
}
return null;
}
/**
* Returns a string list of plugins
*
* @return String of plugins
*/
public String getPluginList() {
StringBuilder sb = new StringBuilder();
synchronized (lock) {
for (Plugin plugin : plugins) {
sb.append(plugin.getName());
sb.append(" ");
sb.append(plugin.isEnabled() ? "(E)" : "(D)");
sb.append(",");
}
}
String str = sb.toString();
if (str.length() > 1)
return str.substring(0, str.length() - 1);
else
return "Empty";
}
/**
* Enables the specified plugin (Or adds and enables it)
*
* @param name
* name of plugin to enable
* @return whether or not this plugin was enabled
*/
public boolean enablePlugin(String name) {
Plugin plugin = getPlugin(name);
if (plugin != null) {
if (!plugin.isEnabled()) {
plugin.toggleEnabled();
plugin.enable();
}
} else { // New plugin, perhaps?
File file = new File("plugins/" + name + ".jar");
if (file.exists())
return loadPlugin(name);
else
return false;
}
return true;
}
/**
* Disables specified plugin
*
* @param name
* name of the plugin to disable
*/
public void disablePlugin(String name) {
Plugin plugin = getPlugin(name);
if (plugin != null)
if (plugin.isEnabled()) {
plugin.toggleEnabled();
plugin.disable();
}
}
/**
* Returns the server
*
* @return server
*/
public Server getServer() {
return server;
}
/**
* Calls a plugin hook.
*
* @param h
* Hook to call
* @param parameters
* Parameters of call
* @return Object returned by call
*/
@SuppressWarnings("deprecation")
public Object callHook(Hook h, Object... parameters) {
Object toRet;
switch (h) {
case REDSTONE_CHANGE:
toRet = parameters[2];
break;
case LIQUID_DESTROY:
toRet = HookResult.DEFAULT_ACTION;
break;
default:
toRet = false;
break;
}
if (!loaded)
return toRet;
synchronized (lock) {
PluginListener listener = null;
try {
List<PluginRegisteredListener> registeredListeners = listeners.get(h.ordinal());
for (PluginRegisteredListener regListener : registeredListeners) {
if (!regListener.getPlugin().isEnabled())
continue;
listener = regListener.getListener();
try {
switch (h) {
case LOGINCHECK:
String result = listener.onLoginChecks((String) parameters[0]);
if (result != null)
toRet = result;
break;
case LOGIN:
listener.onLogin((Player) parameters[0]);
break;
case DISCONNECT:
listener.onDisconnect((Player) parameters[0]);
break;
case CHAT:
if (listener.onChat((Player) parameters[0], (String) parameters[1]))
toRet = true;
break;
case COMMAND:
if (listener.onCommand((Player) parameters[0], (String[]) parameters[1]))
toRet = true;
break;
case SERVERCOMMAND:
if (listener.onConsoleCommand((String[]) parameters[0]))
toRet = true;
break;
case BAN:
listener.onBan((Player) parameters[0], (Player) parameters[1], (String) parameters[2]);
break;
case IPBAN:
listener.onIpBan((Player) parameters[0], (Player) parameters[1], (String) parameters[2]);
break;
case KICK:
listener.onKick((Player) parameters[0], (Player) parameters[1], (String) parameters[2]);
break;
case BLOCK_CREATED:
if (listener.onBlockCreate((Player) parameters[0], (Block) parameters[1], (Block) parameters[2], (Integer) parameters[3]))
toRet = true;
break;
case BLOCK_DESTROYED:
if (listener.onBlockDestroy((Player) parameters[0], (Block) parameters[1]))
toRet = true;
break;
case PLAYER_MOVE:
listener.onPlayerMove((Player) parameters[0], (Location) parameters[1], (Location) parameters[2]);
break;
case ARM_SWING:
listener.onArmSwing((Player) parameters[0]);
break;
case ITEM_DROP:
if (listener.onItemDrop((Player) parameters[0], (Item) parameters[1]))
toRet = true;
break;
case ITEM_PICK_UP:
if (listener.onItemPickUp((Player) parameters[0], (Item) parameters[1]))
toRet = true;
break;
case TELEPORT:
if (listener.onTeleport((Player) parameters[0], (Location) parameters[1], (Location) parameters[2]))
toRet = true;
break;
case BLOCK_BROKEN:
if (listener.onBlockBreak((Player) parameters[0], (Block) parameters[1]))
toRet = true;
break;
case FLOW:
if (listener.onFlow((Block) parameters[0], (Block) parameters[1]))
toRet = true;
break;
case IGNITE:
if (listener.onIgnite((Block) parameters[0], (parameters[1] == null ? null : (Player) parameters[1])))
toRet = true;
break;
case EXPLODE:
if (listener.onExplode((Block) parameters[0]))
toRet = true;
break;
case MOB_SPAWN:
if (listener.onMobSpawn((Mob) parameters[0]))
toRet = true;
break;
case DAMAGE:
if (listener.onDamage((DamageType) parameters[0], (BaseEntity) parameters[1], (BaseEntity) parameters[2], (Integer) parameters[3]))
toRet = true;
break;
case HEALTH_CHANGE:
if (listener.onHealthChange((Player) parameters[0], (Integer) parameters[1], (Integer) parameters[2]))
toRet = true;
break;
case REDSTONE_CHANGE:
toRet = listener.onRedstoneChange((Block) parameters[0], (Integer) parameters[1], (Integer) toRet);
break;
case BLOCK_PHYSICS:
if (listener.onBlockPhysics((Block) parameters[0], (Boolean) parameters[1]))
toRet = true;
break;
case VEHICLE_CREATE:
listener.onVehicleCreate((BaseVehicle) parameters[0]);
break;
case VEHICLE_UPDATE:
listener.onVehicleUpdate((BaseVehicle) parameters[0]);
break;
case VEHICLE_DAMAGE:
if (listener.onVehicleDamage((BaseVehicle) parameters[0], (BaseEntity) parameters[1], (Integer) parameters[2]))
toRet = true;
break;
case VEHICLE_COLLISION:
if (listener.onVehicleCollision((BaseVehicle) parameters[0], (BaseEntity) parameters[1]))
toRet = true;
break;
case VEHICLE_DESTROYED:
listener.onVehicleDestroyed((BaseVehicle) parameters[0]);
break;
case VEHICLE_ENTERED:
listener.onVehicleEnter((BaseVehicle) parameters[0], (HumanEntity) parameters[1]);
break;
case VEHICLE_POSITIONCHANGE:
listener.onVehiclePositionChange((BaseVehicle) parameters[0], (Integer) parameters[1], (Integer) parameters[2], (Integer) parameters[3]);
break;
case ITEM_USE:
if (listener.onItemUse((Player) parameters[0], (Block) parameters[1], (Block) parameters[2], (Item) parameters[3]))
toRet = true;
break;
case BLOCK_RIGHTCLICKED:
listener.onBlockRightClicked((Player) parameters[0], (Block) parameters[1], (Item) parameters[2]);
break;
case BLOCK_PLACE:
if (listener.onBlockPlace((Player) parameters[0], (Block) parameters[1], (Block) parameters[2], (Item) parameters[3]))
toRet = true;
break;
case LIQUID_DESTROY:
HookResult ret = listener.onLiquidDestroy((HookResult) toRet, (Integer) parameters[0], (Block) parameters[1]);
if (ret != HookResult.DEFAULT_ACTION && (HookResult) toRet == HookResult.DEFAULT_ACTION)
toRet = ret;
break;
case ATTACK:
if (listener.onAttack((LivingEntity) parameters[0], (LivingEntity) parameters[1], (Integer) parameters[2]))
toRet = true;
break;
case OPEN_INVENTORY:
if (listener.onOpenInventory((Player) parameters[0], (Inventory) parameters[1]))
toRet = true;
break;
case SIGN_SHOW:
listener.onSignShow((Player) parameters[0], (Sign) parameters[1]);
break;
case SIGN_CHANGE:
if (listener.onSignChange((Player) parameters[0], (Sign) parameters[1]))
toRet = true;
break;
case LEAF_DECAY:
if (listener.onLeafDecay((Block) parameters[0]))
toRet = true;
break;
}
} catch (UnsupportedOperationException ex) {
}
}
} catch (Exception ex) {
String listenerString = listener == null ? "null(unknown listener)" : listener.getClass().toString();
log.log(Level.SEVERE, "Exception while calling plugin function in '" + listenerString + "' while calling hook: '" + h.toString() + "'.", ex);
} catch (Throwable ex) { // The 'exception' thrown is so severe it's
// not even an exception!
log.log(Level.SEVERE, "Throwable while calling plugin (Outdated?)", ex);
}
}
return toRet;
}
/**
* Calls a custom hook
*
* @param name
* name of hook
* @param parameters
* parameters for the hook
* @return object returned by call
*/
public Object callCustomHook(String name, Object[] parameters) {
Object toRet = false;
synchronized (lock) {
try {
PluginInterface listener = customListeners.get(name);
if (listener == null) {
log.log(Level.SEVERE, "Cannot find custom hook: " + name);
return false;
}
String msg = listener.checkParameters(parameters);
if (msg != null) {
log.log(Level.SEVERE, msg);
return false;
}
toRet = listener.run(parameters);
} catch (Exception ex) {
log.log(Level.SEVERE, "Exception while calling custom plugin function", ex);
}
}
return toRet;
}
/**
* Calls a plugin hook.
*
* @param hook
* The hook to call on
* @param listener
* The listener to use when calling
* @param plugin
* The plugin of this listener
* @param priorityEnum
* The priority of this listener
* @return PluginRegisteredListener
*/
public PluginRegisteredListener addListener(Hook hook, PluginListener listener, Plugin plugin, PluginListener.Priority priorityEnum) {
int priority = priorityEnum.ordinal();
PluginRegisteredListener reg = new PluginRegisteredListener(hook, listener, plugin, priority);
synchronized (lock) {
List<PluginRegisteredListener> regListeners = listeners.get(hook.ordinal());
int pos = 0;
for (PluginRegisteredListener other : regListeners) {
if (other.getPriority() < priority)
break;
++pos;
}
regListeners.add(pos, reg);
}
return reg;
}
/**
* Adds a custom listener
*
* @param listener
* listener to add
*/
public void addCustomListener(PluginInterface listener) {
synchronized (lock) {
if (customListeners.get(listener.getName()) != null)
log.log(Level.SEVERE, "Replacing existing listener: " + listener.getName());
customListeners.put(listener.getName(), listener);
log.info("Registered custom hook: " + listener.getName());
}
}
/**
* Removes the specified listener from the list of listeners
*
* @param reg
* listener to remove
*/
public void removeListener(PluginRegisteredListener reg) {
List<PluginRegisteredListener> regListeners = listeners.get(reg.getHook().ordinal());
synchronized (lock) {
regListeners.remove(reg);
}
}
/**
* Removes a custom listener
*
* @param name
* name of listener
*/
public void removeCustomListener(String name) {
synchronized (lock) {
customListeners.remove(name);
}
}
public boolean isLoaded() {
return loaded;
}
}