001    import java.io.File;
002    import java.net.MalformedURLException;
003    import java.net.URL;
004    import java.net.URLClassLoader;
005    import java.util.ArrayList;
006    import java.util.HashMap;
007    import java.util.List;
008    import java.util.logging.Level;
009    import java.util.logging.Logger;
010    import java.util.Iterator;
011    import net.minecraft.server.MinecraftServer;
012    
013    /**
014     * PluginLoader.java - Used to load plugins, toggle them, etc.
015     * @author James
016     */
017    public class PluginLoader {
018    
019        /**
020         * Hook - Used for adding a listener to listen on specific hooks
021         */
022        public enum Hook {
023    
024            /**
025             * Calls onLoginChecks
026             */
027            LOGINCHECK,
028            /**
029             * Calls onLogin
030             */
031            LOGIN,
032            /**
033             * Calls onChat
034             */
035            CHAT,
036            /**
037             * Calls onCommand
038             */
039            COMMAND,
040            /**
041             * Calls onConsoleCommand
042             */
043            SERVERCOMMAND,
044            /**
045             * Calls onBan
046             */
047            BAN,
048            /**
049             * Calls onIpBan
050             */
051            IPBAN,
052            /**
053             * Calls onKick
054             */
055            KICK,
056            /**
057             * Calls onBlockCreate
058             */
059            BLOCK_CREATED,
060            /**
061             * Calls onBlockDestroy
062             */
063            BLOCK_DESTROYED,
064            /**
065             * Calls onDisconnect
066             */
067            DISCONNECT,
068            /**
069             * Calls onPlayerMove
070             */
071            PLAYER_MOVE,
072            /**
073             * Calls onArmSwing
074             */
075            ARM_SWING,
076            /**
077             * Calls onItemDrop
078             */
079            ITEM_DROP,
080            /**
081             * Calls onItemPickUp
082             */
083            ITEM_PICK_UP,
084            /**
085             * Calls onTeleport
086             */
087            TELEPORT,
088            /**
089             * Calls onBlockBreak
090             */
091            BLOCK_BROKEN,
092            /**
093             * Calls onIgnite
094             */
095            IGNITE,
096            /**
097             * Calls onFlow
098             */
099            FLOW,
100            /**
101             * Calls onExplode
102             */
103            EXPLODE,
104            /**
105             * Calls onMobSpawn
106             */
107            MOB_SPAWN,
108            /**
109             * Calls onDamage
110             */
111            DAMAGE,
112            /**
113             * Calls onHealthChange
114             */
115            HEALTH_CHANGE,
116            /**
117             * Calls onRedstoneChange
118             */
119            REDSTONE_CHANGE,
120            /**
121             * Calls onBlockPhysics
122             */
123            BLOCK_PHYSICS,
124            /**
125             * Calls onVehicleCreate
126             */
127            VEHICLE_CREATE,
128            /**
129             * Calls onVehicleUpdate
130             */
131            VEHICLE_UPDATE,
132            /**
133             * Calls onVehicleDamage
134             */
135            VEHICLE_DAMAGE,
136            /**
137             * Calls onVehicleCollision
138             */
139            VEHICLE_COLLISION,
140            /**
141             * Calls onVehicleDestroyed
142             */
143            VEHICLE_DESTROYED,
144            /**
145             * Calls onVehicleEntered
146             */
147            VEHICLE_ENTERED,
148            /**
149             * Calls onVehiclePositionChange
150             */
151            VEHICLE_POSITIONCHANGE,
152            /**
153             * Calls onItemUse
154             */
155            ITEM_USE,
156            /**
157             * Calls onBlockPlace
158             */
159            BLOCK_PLACE,
160            /**
161             * Calls onBlockRightClicked
162             */
163            BLOCK_RIGHTCLICKED,
164            /**
165             * Calls onLiquidDestroy
166             */
167            LIQUID_DESTROY,
168            /**
169             * Calls onAttack
170             */
171            ATTACK,
172            /**
173             * Calls onOpenInventory
174             */
175            OPEN_INVENTORY,
176            /**
177             * Calls onSignShow
178             */
179            SIGN_SHOW,
180            /**
181             * Calls onSignChange
182             */
183            SIGN_CHANGE,
184            /**
185             * Calls onInventoryPlaceItem
186             */
187            INVENTORY_PLACE,
188            /**
189             * Calls onInventoryTakeItem
190             */
191            INVENTORY_TAKE,
192            /**
193             * Calls onInventoryCursorSwap
194             */
195            INVENTORY_SWAP,
196            /**
197             * Calls onLeafDecay
198             */
199            LEAF_DECAY,
200            /**
201             * Unused.
202             */
203            NUM_HOOKS
204        }
205        
206        /**
207         * HookResult - Used where returning a boolean isn't enough.
208         */
209        public enum HookResult {
210            /**
211             * Prevent the action
212             */
213            PREVENT_ACTION,
214            /**
215             * Allow the action
216             */
217            ALLOW_ACTION,
218            /**
219             * Do whatever it would normally do, continue processing
220             */
221            DEFAULT_ACTION
222        }
223        
224        public enum DamageType {
225            /*
226             * Creeper explosion
227             */
228            CREEPER_EXPLOSION,
229            /*
230             * Damage dealt by another entity
231             */
232            ENTITY,
233            /*
234             * Damage caused by explosion
235             */
236            EXPLOSION,
237            /*
238             * Damage caused from falling (fall distance - 3.0)
239             */
240            FALL,
241            /*
242             * Damage caused by fire (1)
243             */
244            FIRE,
245            /*
246             * Low periodic damage caused by burning (1)
247             */
248            FIRE_TICK,
249            /*
250             * Damage caused from lava (4)
251             */
252            LAVA,
253            /*
254             * Damage caused from drowning (2)
255             */
256            WATER,
257            /*
258             * Damaged caused by cactus (1)
259             */
260            CACTUS
261        }
262        
263        private static final Logger log = Logger.getLogger("Minecraft");
264        private static final Object lock = new Object();
265        private List<Plugin> plugins = new ArrayList<Plugin>();
266        private List<List<PluginRegisteredListener>> listeners = new ArrayList<List<PluginRegisteredListener>>();
267        private HashMap<String, PluginInterface> customListeners = new HashMap<String, PluginInterface>();
268        private Server server;
269        private PropertiesFile properties;
270    
271        /**
272         * Creates a plugin loader
273         * @param server server to use
274         */
275        public PluginLoader(MinecraftServer server) {
276            properties = new PropertiesFile("server.properties");
277            this.server = new Server(server);
278    
279            for (int h = 0; h < Hook.NUM_HOOKS.ordinal(); ++h) {
280                listeners.add(new ArrayList<PluginRegisteredListener>());
281            }
282        }
283    
284        /**
285         * Loads all plugins.
286         */
287        public void loadPlugins() {
288            String[] classes = properties.getString("plugins", "").split(",");
289            for (String sclass : classes) {
290                if (sclass.equals("")) {
291                    continue;
292                }
293                loadPlugin(sclass.trim());
294            }
295        }
296    
297        /**
298         * Loads the specified plugin
299         * @param fileName file name of plugin to load
300         * @return if the operation was successful
301         */
302        public Boolean loadPlugin(String fileName) {
303            if (getPlugin(fileName) != null) {
304                return false; // Already exists.
305            }
306            return load(fileName);
307        }
308    
309        /**
310         * Reloads the specified plugin
311         * @param fileName file name of plugin to reload
312         * @return if the operation was successful
313         */
314        public Boolean reloadPlugin(String fileName) {
315            /* Not sure exactly how much of this is necessary */
316            Plugin toNull = getPlugin(fileName);
317            if (toNull != null) {
318                if (toNull.isEnabled()) {
319                    toNull.disable();
320                }
321            }
322            synchronized (lock) {
323                plugins.remove(toNull);
324                for (List<PluginRegisteredListener> regListeners : listeners) {
325                    Iterator<PluginRegisteredListener> iter = regListeners.iterator();
326                    while (iter.hasNext()) {
327                        if (iter.next().getPlugin() == toNull) {
328                            iter.remove();
329                        }
330                    }
331                }
332            }
333            toNull = null;
334    
335            return load(fileName);
336        }
337    
338        private Boolean load(String fileName) {
339            try {
340                File file = new File("plugins/" + fileName + ".jar");
341                if (!file.exists()) {
342                    log.log(Level.SEVERE, "Failed to find plugin file: plugins/" + fileName + ".jar. Please ensure the file exists");
343                    return false;
344                }
345                URLClassLoader child = null;
346                try {
347                    child = new MyClassLoader(new URL[]{file.toURI().toURL()}, Thread.currentThread().getContextClassLoader());
348                } catch (MalformedURLException ex) {
349                    log.log(Level.SEVERE, "Exception while loading class", ex);
350                    return false;
351                }
352                Class c = child.loadClass(fileName);
353    
354                Plugin plugin = (Plugin) c.newInstance();
355                plugin.setName(fileName);
356                plugin.enable();
357                synchronized (lock) {
358                    plugins.add(plugin);
359                    plugin.initialize();
360                }
361            } catch (Throwable ex) {
362                log.log(Level.SEVERE, "Exception while loading plugin", ex);
363                return false;
364            }
365            return true;
366        }
367    
368        /**
369         * Returns the specified plugin
370         * @param name name of plugin
371         * @return plugin
372         */
373        public Plugin getPlugin(String name) {
374            synchronized (lock) {
375                for (Plugin plugin : plugins) {
376                    if (plugin.getName().equalsIgnoreCase(name)) {
377                        return plugin;
378                    }
379                }
380            }
381            return null;
382        }
383    
384        /**
385         * Returns a string list of plugins
386         * @return String of plugins
387         */
388        public String getPluginList() {
389            StringBuilder sb = new StringBuilder();
390            synchronized (lock) {
391                for (Plugin plugin : plugins) {
392                    sb.append(plugin.getName());
393                    sb.append(" ");
394                    sb.append(plugin.isEnabled() ? "(E)" : "(D)");
395                    sb.append(",");
396                }
397            }
398            String str = sb.toString();
399            if (str.length() > 1) {
400                return str.substring(0, str.length() - 1);
401            } else {
402                return "Empty";
403            }
404        }
405    
406        /**
407         * Enables the specified plugin (Or adds and enables it)
408         * @param name name of plugin to enable
409         * @return whether or not this plugin was enabled
410         */
411        public boolean enablePlugin(String name) {
412            Plugin plugin = getPlugin(name);
413            if (plugin != null) {
414                if (!plugin.isEnabled()) {
415                    plugin.toggleEnabled();
416                    plugin.enable();
417                }
418            } else { // New plugin, perhaps?
419                File file = new File("plugins/" + name + ".jar");
420                if (file.exists()) {
421                    return loadPlugin(name);
422                } else {
423                    return false;
424                }
425            }
426            return true;
427        }
428    
429        /**
430         * Disables specified plugin
431         * @param name name of the plugin to disable
432         */
433        public void disablePlugin(String name) {
434            Plugin plugin = getPlugin(name);
435            if (plugin != null) {
436                if (plugin.isEnabled()) {
437                    plugin.toggleEnabled();
438                    plugin.disable();
439                }
440            }
441        }
442    
443        /**
444         * Returns the server
445         * @return server
446         */
447        public Server getServer() {
448            return server;
449        }
450    
451        /**
452         * Calls a plugin hook.
453         * @param h Hook to call
454         * @param parameters Parameters of call
455         * @return Object returned by call
456         */
457        public Object callHook(Hook h, Object... parameters) {
458            Object toRet = false;
459    
460            if (h == Hook.REDSTONE_CHANGE) {
461                toRet = (Integer) parameters[2];
462            } else if (h == Hook.LIQUID_DESTROY) {
463                toRet = HookResult.DEFAULT_ACTION;
464            }
465    
466            synchronized (lock) {
467                PluginListener listener = null;
468                try {
469                    List<PluginRegisteredListener> registeredListeners = listeners.get(h.ordinal());
470                    for (PluginRegisteredListener regListener : registeredListeners) {
471                        if (!regListener.getPlugin().isEnabled()) {
472                            continue;
473                        }
474    
475                        listener = regListener.getListener();
476    
477                        try {
478                            switch (h) {
479                                case LOGINCHECK:
480                                    String result = listener.onLoginChecks((String) parameters[0]);
481                                    if (result != null) {
482                                        toRet = result;
483                                    }
484                                    break;
485                                case LOGIN:
486                                    listener.onLogin((Player) parameters[0]);
487                                    break;
488                                case DISCONNECT:
489                                    listener.onDisconnect((Player) parameters[0]);
490                                    break;
491                                case CHAT:
492                                    if (listener.onChat((Player) parameters[0], (String) parameters[1])) {
493                                        toRet = true;
494                                    }
495                                    break;
496                                case COMMAND:
497                                    if (listener.onCommand((Player) parameters[0], (String[]) parameters[1])) {
498                                        toRet = true;
499                                    }
500                                    break;
501                                case SERVERCOMMAND:
502                                    if (listener.onConsoleCommand((String[]) parameters[0])) {
503                                        toRet = true;
504                                    }
505                                    break;
506                                case BAN:
507                                    listener.onBan((Player) parameters[0], (Player) parameters[1], (String) parameters[2]);
508                                    break;
509                                case IPBAN:
510                                    listener.onIpBan((Player) parameters[0], (Player) parameters[1], (String) parameters[2]);
511                                    break;
512                                case KICK:
513                                    listener.onKick((Player) parameters[0], (Player) parameters[1], (String) parameters[2]);
514                                    break;
515                                case BLOCK_CREATED:
516                                    if (listener.onBlockCreate((Player) parameters[0], (Block) parameters[1], (Block) parameters[2], (Integer) parameters[3])) {
517                                        toRet = true;
518                                    }
519                                    break;
520                                case BLOCK_DESTROYED:
521                                    if (listener.onBlockDestroy((Player) parameters[0], (Block) parameters[1])) {
522                                        toRet = true;
523                                    }
524                                    break;
525                                case PLAYER_MOVE:
526                                    listener.onPlayerMove((Player) parameters[0], (Location) parameters[1], (Location) parameters[2]);
527                                    break;
528                                case ARM_SWING:
529                                    listener.onArmSwing((Player) parameters[0]);
530                                    break;
531                                case ITEM_DROP:
532                                    if (listener.onItemDrop((Player) parameters[0], (Item) parameters[1])) {
533                                        toRet = true;
534                                    }
535                                    break;
536                                case ITEM_PICK_UP:
537                                    if (listener.onItemPickUp((Player) parameters[0], (Item) parameters[1])) {
538                                        toRet = true;
539                                    }
540                                    break;
541                                case TELEPORT:
542                                    if (listener.onTeleport((Player) parameters[0], (Location) parameters[1], (Location) parameters[2])) {
543                                        toRet = true;
544                                    }
545                                    break;
546                                case BLOCK_BROKEN:
547                                    if (listener.onBlockBreak((Player) parameters[0], (Block) parameters[1])) {
548                                        toRet = true;
549                                    }
550                                    break;
551                                case FLOW:
552                                    if (listener.onFlow((Block) parameters[0], (Block) parameters[1])) {
553                                        toRet = true;
554                                    }
555                                    break;
556                                case IGNITE:
557                                    if (listener.onIgnite((Block) parameters[0], (parameters[1] == null ? null : (Player) parameters[1]))) {
558                                        toRet = true;
559                                    }
560                                    break;
561                                case EXPLODE:
562                                    if (listener.onExplode((Block) parameters[0])) {
563                                        toRet = true;
564                                    }
565                                    break;
566                                case MOB_SPAWN:
567                                    if (listener.onMobSpawn((Mob) parameters[0])) {
568                                        toRet = true;
569                                    }
570                                    break;
571                                case DAMAGE:
572                                    if (listener.onDamage((DamageType) parameters[0], (BaseEntity) parameters[1], (BaseEntity) parameters[2], (Integer) parameters[3])) {
573                                        toRet = true;
574                                    }
575                                    break;
576                                case HEALTH_CHANGE:
577                                    if (listener.onHealthChange((Player) parameters[0], (Integer) parameters[1], (Integer) parameters[2])) {
578                                        toRet = true;
579                                    }
580                                    break;
581                                case REDSTONE_CHANGE:
582                                    toRet = listener.onRedstoneChange((Block) parameters[0], (Integer) parameters[1], (Integer) toRet);
583                                    break;
584                                case BLOCK_PHYSICS:
585                                    if (listener.onBlockPhysics((Block) parameters[0], (Boolean) parameters[1])) {
586                                        toRet = true;
587                                    }
588                                    break;
589                                case VEHICLE_CREATE:
590                                    listener.onVehicleCreate((BaseVehicle) parameters[0]);
591                                    break;
592                                case VEHICLE_UPDATE:
593                                    listener.onVehicleUpdate((BaseVehicle) parameters[0]);
594                                    break;
595                                case VEHICLE_DAMAGE:
596                                    if (listener.onVehicleDamage((BaseVehicle) parameters[0], (BaseEntity) parameters[1], (Integer) parameters[2])) {
597                                        toRet = true;
598                                    }
599                                    break;
600                                case VEHICLE_COLLISION:
601                                    if (listener.onVehicleCollision((BaseVehicle) parameters[0], (BaseEntity) parameters[1])) {
602                                        toRet = true;
603                                    }
604                                    break;
605                                case VEHICLE_DESTROYED:
606                                    listener.onVehicleDestroyed((BaseVehicle) parameters[0]);
607                                    break;
608                                case VEHICLE_ENTERED:
609                                    listener.onVehicleEnter((BaseVehicle) parameters[0], (HumanEntity) parameters[1]);
610                                    break;
611                                case VEHICLE_POSITIONCHANGE:
612                                    listener.onVehiclePositionChange((BaseVehicle) parameters[0], (Integer) parameters[1], (Integer) parameters[2], (Integer) parameters[3]);
613                                    break;
614                                case ITEM_USE:
615                                    if (listener.onItemUse((Player) parameters[0], (Block) parameters[1], (Block) parameters[2], (Item) parameters[3])) {
616                                        toRet = true;
617                                    }
618                                    break;
619                                case BLOCK_RIGHTCLICKED:
620                                    listener.onBlockRightClicked((Player) parameters[0], (Block) parameters[1], (Item) parameters[2]);
621                                    break;
622                                case BLOCK_PLACE:
623                                    if (listener.onBlockPlace((Player) parameters[0], (Block) parameters[1], (Block) parameters[2], (Item) parameters[3])) {
624                                        toRet = true;
625                                    }
626                                    break;
627                                case LIQUID_DESTROY:
628                                    HookResult ret = listener.onLiquidDestroy((HookResult) toRet, (Integer) parameters[0], (Block) parameters[1]);
629                                    if (ret != HookResult.DEFAULT_ACTION && (HookResult) toRet == HookResult.DEFAULT_ACTION) {
630                                        toRet = ret;
631                                    }
632                                    break;
633                                case ATTACK:
634                                    if (listener.onAttack((LivingEntity) parameters[0], (LivingEntity) parameters[1], (Integer) parameters[2])) {
635                                        toRet = true;
636                                    }
637                                    break;
638                                case OPEN_INVENTORY:
639                                    if (listener.onOpenInventory((Player) parameters[0], (Inventory) parameters[1])) {
640                                        toRet = true;
641                                    }
642                                    break;
643                                case SIGN_SHOW:
644                                    listener.onSignShow((Player) parameters[0], (Sign) parameters[1]);
645                                    break;
646                                case SIGN_CHANGE:
647                                    if (listener.onSignChange((Player) parameters[0], (Sign) parameters[1])) {
648                                        toRet = true;
649                                    }
650                                    break;
651                                case INVENTORY_PLACE:
652                                    if(listener.onInventoryPlaceItem((Player) parameters[0], (Inventory) parameters[1], (Item) parameters[2], (Integer) parameters[3])) {
653                                            toRet = true;
654                                    }
655                                    break;
656                                case INVENTORY_TAKE:
657                                    if(listener.onInventoryTakeItem((Player) parameters[0], (Inventory) parameters[1], (Item) parameters[2], (Integer) parameters[3])) {
658                                            toRet = true;
659                                    }
660                                    break;
661                                case INVENTORY_SWAP:
662                                    if(listener.onInventoryCursorSwap((Player) parameters[0], (Inventory) parameters[1], (Integer) parameters[2], (Item) parameters[3], (Item) parameters[4])) {
663                                            toRet = true;
664                                    }
665                                    break;
666                                case LEAF_DECAY:
667                                    if (listener.onLeafDecay((Block) parameters[0])) {
668                                        toRet = true;
669                                    }
670                                    break;
671                            }
672                        } catch (UnsupportedOperationException ex) {
673                        }
674                    }
675                } catch (Exception ex) {
676                    String listenerString = listener == null ? "null(unknown listener)" : listener.getClass().toString();
677                    log.log(Level.SEVERE, "Exception while calling plugin function in '" + listenerString + "' while calling hook: '" + h.toString() + "'.", ex);
678                } catch (Throwable ex) { // The 'exception' thrown is so severe it's
679                    // not even an exception!
680                    log.log(Level.SEVERE, "Throwable while calling plugin (Outdated?)", ex);
681                }
682            }
683    
684            return toRet;
685        }
686    
687        /**
688         * Calls a custom hook
689         * @param name name of hook
690         * @param parameters parameters for the hook
691         * @return object returned by call
692         */
693        public Object callCustomHook(String name, Object[] parameters) {
694            Object toRet = false;
695            synchronized (lock) {
696                try {
697                    PluginInterface listener = customListeners.get(name);
698    
699                    if (listener == null) {
700                        log.log(Level.SEVERE, "Cannot find custom hook: " + name);
701                        return false;
702                    }
703    
704                    String msg = listener.checkParameters(parameters);
705                    if (msg != null) {
706                        log.log(Level.SEVERE, msg);
707                        return false;
708                    }
709    
710                    toRet = listener.run(parameters);
711                } catch (Exception ex) {
712                    log.log(Level.SEVERE, "Exception while calling custom plugin function", ex);
713                }
714            }
715            return toRet;
716        }
717    
718        /**
719         * Calls a plugin hook.
720         * @param hook The hook to call on
721         * @param listener The listener to use when calling
722         * @param plugin The plugin of this listener
723         * @param priorityEnum The priority of this listener
724         * @return PluginRegisteredListener
725         */
726        public PluginRegisteredListener addListener(Hook hook, PluginListener listener, Plugin plugin, PluginListener.Priority priorityEnum) {
727            int priority = priorityEnum.ordinal();
728            PluginRegisteredListener reg = new PluginRegisteredListener(hook, listener, plugin, priority);
729    
730            synchronized (lock) {
731                List<PluginRegisteredListener> regListeners = listeners.get(hook.ordinal());
732    
733                int pos = 0;
734                for (PluginRegisteredListener other : regListeners) {
735                    if (other.getPriority() < priority) {
736                        break;
737                    }
738                    ++pos;
739                }
740    
741                regListeners.add(pos, reg);
742            }
743    
744            return reg;
745        }
746    
747        /**
748         * Adds a custom listener
749         * @param listener listener to add
750         */
751        public void addCustomListener(PluginInterface listener) {
752            synchronized (lock) {
753                if (customListeners.get(listener.getName()) != null) {
754                    log.log(Level.SEVERE, "Replacing existing listener: " + listener.getName());
755                }
756                customListeners.put(listener.getName(), listener);
757                log.info("Registered custom hook: " + listener.getName());
758            }
759        }
760    
761        /**
762         * Removes the specified listener from the list of listeners
763         * @param reg listener to remove
764         */
765        public void removeListener(PluginRegisteredListener reg) {
766            List<PluginRegisteredListener> regListeners = listeners.get(reg.getHook().ordinal());
767            synchronized (lock) {
768                regListeners.remove(reg);
769            }
770        }
771    
772        /**
773         * Removes a custom listener
774         * @param name name of listener
775         */
776        public void removeCustomListener(String name) {
777            synchronized (lock) {
778                customListeners.remove(name);
779            }
780        }
781    }