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 }