PageRenderTime 114ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/src/main/java/com/sk89q/commandbook/CommandBookPlugin.java

https://github.com/Noobidoo/commandbook
Java | 1381 lines | 854 code | 184 blank | 343 comment | 247 complexity | f9b98e2ba18b5f89b18a3f7f90ef88b9 MD5 | raw file
  1. // $Id$
  2. /*
  3. * CommandBook
  4. * Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com>
  5. *
  6. * This program is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. package com.sk89q.commandbook;
  20. import java.io.*;
  21. import java.util.*;
  22. import java.util.logging.Logger;
  23. import java.util.regex.Matcher;
  24. import java.util.regex.Pattern;
  25. import org.bukkit.ChatColor;
  26. import org.bukkit.DyeColor;
  27. import org.bukkit.Location;
  28. import org.bukkit.Material;
  29. import org.bukkit.World;
  30. import org.bukkit.World.Environment;
  31. import org.bukkit.block.Block;
  32. import org.bukkit.command.Command;
  33. import org.bukkit.command.CommandSender;
  34. import org.bukkit.command.ConsoleCommandSender;
  35. import org.bukkit.entity.CreatureType;
  36. import org.bukkit.entity.Player;
  37. import org.bukkit.event.Event;
  38. import org.bukkit.event.Listener;
  39. import org.bukkit.event.Event.Priority;
  40. import org.bukkit.event.player.PlayerListener;
  41. import org.bukkit.event.world.WorldListener;
  42. import org.bukkit.inventory.ItemStack;
  43. import org.bukkit.plugin.java.JavaPlugin;
  44. import org.bukkit.util.config.Configuration;
  45. import com.sk89q.bukkit.migration.PermissionsResolverManager;
  46. import com.sk89q.bukkit.migration.PermissionsResolverServerListener;
  47. import com.sk89q.commandbook.bans.BanDatabase;
  48. import com.sk89q.commandbook.bans.FlatFileBanDatabase;
  49. import com.sk89q.commandbook.commands.*;
  50. import com.sk89q.commandbook.kits.FlatFileKitsManager;
  51. import com.sk89q.commandbook.kits.KitManager;
  52. import com.sk89q.commandbook.locations.FlatFileLocationsManager;
  53. import com.sk89q.commandbook.locations.LocationManager;
  54. import com.sk89q.commandbook.locations.LocationManagerFactory;
  55. import com.sk89q.commandbook.locations.RootLocationManager;
  56. import com.sk89q.commandbook.locations.NamedLocation;
  57. import com.sk89q.jinglenote.JingleNoteManager;
  58. import com.sk89q.minecraft.util.commands.*;
  59. import com.sk89q.worldedit.blocks.BlockID;
  60. import com.sk89q.worldedit.blocks.BlockType;
  61. import com.sk89q.worldedit.blocks.ClothColor;
  62. import com.sk89q.worldedit.blocks.ItemType;
  63. import static com.sk89q.commandbook.CommandBookUtil.*;
  64. import com.sk89q.commandbook.locations.WrappedSpawnManager;
  65. /**
  66. * Base plugin class for CommandBook.
  67. *
  68. * @author sk89q
  69. */
  70. public class CommandBookPlugin extends JavaPlugin {
  71. /**
  72. * Logger for messages.
  73. */
  74. protected static final Logger logger = Logger.getLogger("Minecraft.CommandBook");
  75. /**
  76. * Pattern for 12-hour time.
  77. */
  78. private static final Pattern twelveHourTime
  79. = Pattern.compile("^([0-9]+(?::[0-9]+)?)([apmAPM\\.]+)$");
  80. /**
  81. * Pattern for macros.
  82. */
  83. protected static final Pattern macroPattern =
  84. Pattern.compile("%([^% ]+)%");
  85. /**
  86. * The permissions resolver in use.
  87. */
  88. private PermissionsResolverManager perms;
  89. /**
  90. * List of commands.
  91. */
  92. protected CommandsManager<CommandSender> commands;
  93. /**
  94. * Bans database.
  95. */
  96. protected BanDatabase bans;
  97. /**
  98. * Warps database.
  99. */
  100. protected RootLocationManager<NamedLocation> warps;
  101. /**
  102. * Homes database.
  103. */
  104. protected RootLocationManager<NamedLocation> homes;
  105. /**
  106. * Time lock manager.
  107. */
  108. protected TimeLockManager timeLockManager;
  109. /**
  110. * Jingle note manager.
  111. */
  112. protected JingleNoteManager jingleNoteManager;
  113. /**
  114. * Spawn pitch and yaw storage
  115. */
  116. private WrappedSpawnManager spawns;
  117. public boolean disableMidi;
  118. public boolean verifyNameFormat;
  119. public boolean broadcastChanges;
  120. public boolean broadcastKicks;
  121. public boolean broadcastBans;
  122. public boolean useItemPermissionsOnly;
  123. public Set<Integer> allowedItems;
  124. public Set<Integer> disallowedItems;
  125. public Map<String, Integer> itemNames;
  126. public Set<Integer> thorItems;
  127. public KitManager kits;
  128. public String banMessage;
  129. public boolean opPermissions;
  130. public boolean useDisplayNames;
  131. public String consoleSayFormat;
  132. public String broadcastFormat;
  133. public int defaultItemStackSize;
  134. public boolean exactSpawn;
  135. public boolean playersListColoredNames;
  136. public boolean playersListGroupedNames;
  137. public boolean playersListMaxPlayers;
  138. public boolean crappyWrapperCompat;
  139. protected Map<String, String> messages = new HashMap<String, String>();
  140. protected Map<String, UserSession> sessions =
  141. new HashMap<String, UserSession>();
  142. protected Map<String, AdministrativeSession> adminSessions =
  143. new HashMap<String, AdministrativeSession>();;
  144. protected Map<String, Integer> lockedTimes =
  145. new HashMap<String, Integer>();
  146. /**
  147. * Called when the plugin is enabled. This is where configuration is loaded,
  148. * and the plugin is setup.
  149. */
  150. public void onEnable() {
  151. logger.info(getDescription().getName() + " "
  152. + getDescription().getVersion() + " enabled.");
  153. // Make the data folder for the plugin where configuration files
  154. // and other data files will be stored
  155. getDataFolder().mkdirs();
  156. createDefaultConfiguration("config.yml");
  157. createDefaultConfiguration("kits.txt");
  158. // Setup the time locker
  159. timeLockManager = new TimeLockManager(this);
  160. // Load configuration
  161. populateConfiguration();
  162. // Setup the ban database
  163. bans = new FlatFileBanDatabase(getDataFolder(), this);
  164. bans.load();
  165. // Setup kits
  166. kits = new FlatFileKitsManager(new File(getDataFolder(), "kits.txt"), this);
  167. kits.load();
  168. // Jingle note manager
  169. jingleNoteManager = new JingleNoteManager(this);
  170. // Prepare permissions
  171. perms = new PermissionsResolverManager(
  172. getConfiguration(), getServer(), getDescription().getName(), logger);
  173. perms.load();
  174. // Register the commands that we want to use
  175. final CommandBookPlugin plugin = this;
  176. commands = new CommandsManager<CommandSender>() {
  177. @Override
  178. public boolean hasPermission(CommandSender player, String perm) {
  179. return plugin.hasPermission(player, perm);
  180. }
  181. };
  182. commands.register(GeneralCommands.class);
  183. commands.register(FunCommands.class);
  184. commands.register(TeleportCommands.class);
  185. commands.register(MessageCommands.class);
  186. commands.register(DebuggingCommands.class);
  187. commands.register(ModerationCommands.class);
  188. commands.register(KitCommands.class);
  189. commands.register(WarpCommands.class);
  190. commands.register(HomeCommands.class);
  191. // Register events
  192. registerEvents();
  193. // Cleanup
  194. getServer().getScheduler().scheduleAsyncRepeatingTask(
  195. this, new SessionChecker(this),
  196. SessionChecker.CHECK_FREQUENCY, SessionChecker.CHECK_FREQUENCY);
  197. getServer().getScheduler().scheduleAsyncRepeatingTask(
  198. this, new GarbageCollector(this),
  199. GarbageCollector.CHECK_FREQUENCY, GarbageCollector.CHECK_FREQUENCY);
  200. // The permissions resolver has some hooks of its own
  201. (new PermissionsResolverServerListener(perms)).register(this);
  202. }
  203. /**
  204. * Register the events that are used.
  205. */
  206. protected void registerEvents() {
  207. PlayerListener playerListener = new CommandBookPlayerListener(this);
  208. WorldListener worldListener = new CommandBookWorldListener(this);
  209. registerEvent(Event.Type.PLAYER_LOGIN, playerListener);
  210. registerEvent(Event.Type.PLAYER_JOIN, playerListener);
  211. registerEvent(Event.Type.PLAYER_INTERACT, playerListener);
  212. registerEvent(Event.Type.PLAYER_QUIT, playerListener);
  213. registerEvent(Event.Type.PLAYER_CHAT, playerListener);
  214. registerEvent(Event.Type.PLAYER_RESPAWN, playerListener);
  215. registerEvent(Event.Type.PLAYER_TELEPORT, playerListener);
  216. registerEvent(Event.Type.WORLD_LOAD, worldListener);
  217. }
  218. /**
  219. * Called when the plugin is disabled. Shutdown and clearing of any
  220. * temporary data occurs here.
  221. */
  222. public void onDisable() {
  223. jingleNoteManager.stopAll();
  224. bans.unload();
  225. this.getServer().getScheduler().cancelTasks(this);
  226. }
  227. /**
  228. * Called on a command.
  229. */
  230. @Override
  231. public boolean onCommand(CommandSender sender, Command cmd,
  232. String commandLabel, String[] args) {
  233. try {
  234. commands.execute(cmd.getName(), args, sender, this, sender);
  235. } catch (CommandPermissionsException e) {
  236. sender.sendMessage(ChatColor.RED + "You don't have permission.");
  237. } catch (MissingNestedCommandException e) {
  238. sender.sendMessage(ChatColor.RED + e.getUsage());
  239. } catch (CommandUsageException e) {
  240. sender.sendMessage(ChatColor.RED + e.getMessage());
  241. sender.sendMessage(ChatColor.RED + e.getUsage());
  242. } catch (WrappedCommandException e) {
  243. if (e.getCause() instanceof NumberFormatException) {
  244. sender.sendMessage(ChatColor.RED + "Number expected, string received instead.");
  245. } else {
  246. sender.sendMessage(ChatColor.RED + "An error has occurred. See console.");
  247. e.printStackTrace();
  248. }
  249. } catch (CommandException e) {
  250. sender.sendMessage(ChatColor.RED + e.getMessage());
  251. }
  252. return true;
  253. }
  254. /**
  255. * Register an event.
  256. *
  257. * @param type
  258. * @param listener
  259. * @param priority
  260. */
  261. protected void registerEvent(Event.Type type, Listener listener, Priority priority) {
  262. getServer().getPluginManager()
  263. .registerEvent(type, listener, priority, this);
  264. }
  265. /**
  266. * Register an event at normal priority.
  267. *
  268. * @param type
  269. * @param listener
  270. */
  271. protected void registerEvent(Event.Type type, Listener listener) {
  272. getServer().getPluginManager()
  273. .registerEvent(type, listener, Priority.Normal, this);
  274. }
  275. /**
  276. * Loads the configuration.
  277. */
  278. @SuppressWarnings("unchecked")
  279. public void populateConfiguration() {
  280. Configuration config = getConfiguration();
  281. config.load();
  282. // Load item disallow/allow lists
  283. useItemPermissionsOnly = config.getBoolean("item-permissions-only", false);
  284. allowedItems = new HashSet<Integer>(
  285. config.getIntList("allowed-items", null));
  286. disallowedItems = new HashSet<Integer>(
  287. config.getIntList("disallowed-items", null));
  288. loadItemList();
  289. // Load messages
  290. messages.put("motd", config.getString("motd", null));
  291. messages.put("rules", config.getString("rules", null));
  292. playersListColoredNames = config.getBoolean("online-list.colored-names", false);
  293. playersListGroupedNames = config.getBoolean("online-list.grouped-names", false);
  294. playersListMaxPlayers = config.getBoolean("online-list.show-max-players", true);
  295. opPermissions = config.getBoolean("op-permissions", true);
  296. useDisplayNames = config.getBoolean("use-display-names", true);
  297. banMessage = config.getString("bans.message", "You were banned.");
  298. disableMidi = config.getBoolean("disable-midi", false);
  299. verifyNameFormat = config.getBoolean("verify-name-format", true);
  300. broadcastChanges = config.getBoolean("broadcast-changes", true);
  301. broadcastBans = config.getBoolean("broadcast-bans", false);
  302. broadcastKicks = config.getBoolean("broadcast-kicks", false);
  303. consoleSayFormat = config.getString("console-say-format", "<`r*Console`w> %s");
  304. broadcastFormat = config.getString("broadcast-format", "`r[Broadcast] %s");
  305. defaultItemStackSize = config.getInt("default-item-stack-size", 1);
  306. exactSpawn = config.getBoolean("exact-spawn", false);
  307. crappyWrapperCompat = config.getBoolean("crappy-wrapper-compat", true);
  308. thorItems = new HashSet<Integer>(config.getIntList(
  309. "thor-hammer-items", Arrays.asList(new Integer[]{278, 285, 257, 270})));
  310. LocationManagerFactory<LocationManager<NamedLocation>> warpsFactory =
  311. new FlatFileLocationsManager.LocationsFactory(getDataFolder(), this, "Warps");
  312. warps = new RootLocationManager<NamedLocation>(warpsFactory,
  313. config.getBoolean("per-world-warps", false));
  314. LocationManagerFactory<LocationManager<NamedLocation>> homesFactory =
  315. new FlatFileLocationsManager.LocationsFactory(getDataFolder(), this, "Homes");
  316. homes = new RootLocationManager<NamedLocation>(homesFactory,
  317. config.getBoolean("per-world-homes", false));
  318. if (disableMidi) {
  319. logger.info("CommandBook: MIDI support is disabled.");
  320. }
  321. if (crappyWrapperCompat) {
  322. logger.info("CommandBook: Maximum wrapper compatibility is enabled. " +
  323. "Some features have been disabled to be compatible with " +
  324. "poorly written server wrappers.");
  325. }
  326. Object timeLocks = config.getProperty("time-lock");
  327. if (timeLocks != null && timeLocks instanceof Map) {
  328. for (Map.Entry<String, Object> entry : ((Map<String, Object>) timeLocks).entrySet()) {
  329. int time = 0;
  330. try {
  331. time = matchTime(String.valueOf(entry.getValue()));
  332. } catch (CommandException e) {
  333. logger.warning("CommandBook: Time lock: Failed to parse time '"
  334. + entry.getValue() + "'");
  335. }
  336. lockedTimes.put(entry.getKey(), time);
  337. World world = getServer().getWorld(entry.getKey());
  338. if (world == null) {
  339. logger.info("CommandBook: Could not time-lock unknown world '"
  340. + entry.getKey() + "'");
  341. continue;
  342. }
  343. world.setTime(time);
  344. timeLockManager.lock(world);
  345. logger.info("CommandBook: Time locked to '"
  346. + CommandBookUtil.getTimeString(time) + "' for world '"
  347. + world.getName() + "'");
  348. }
  349. }
  350. spawns = new WrappedSpawnManager(new File(getDataFolder(), "spawns.yml"));
  351. }
  352. /**
  353. * Loads the item list.
  354. */
  355. @SuppressWarnings("unchecked")
  356. protected void loadItemList() {
  357. Configuration config = getConfiguration();
  358. // Load item names aliases list
  359. Object itemNamesTemp = config.getProperty("item-names");
  360. if (itemNamesTemp != null && itemNamesTemp instanceof Map) {
  361. itemNames = new HashMap<String, Integer>();
  362. try {
  363. Map<Object, Object> temp = (Map<Object, Object>) itemNamesTemp;
  364. for (Map.Entry<Object, Object> entry : temp.entrySet()) {
  365. String name = entry.getKey().toString().toLowerCase();
  366. // Check if the item ID is a number
  367. if (entry.getValue() instanceof Integer) {
  368. itemNames.put(name, (Integer) entry.getValue());
  369. }
  370. }
  371. } catch (ClassCastException e) {
  372. }
  373. } else {
  374. itemNames = new HashMap<String, Integer>();
  375. }
  376. }
  377. /**
  378. * Create a default configuration file from the .jar.
  379. *
  380. * @param name
  381. */
  382. protected void createDefaultConfiguration(String name) {
  383. File actual = new File(getDataFolder(), name);
  384. if (!actual.exists()) {
  385. InputStream input =
  386. this.getClass().getResourceAsStream("/defaults/" + name);
  387. if (input != null) {
  388. FileOutputStream output = null;
  389. try {
  390. output = new FileOutputStream(actual);
  391. byte[] buf = new byte[8192];
  392. int length = 0;
  393. while ((length = input.read(buf)) > 0) {
  394. output.write(buf, 0, length);
  395. }
  396. logger.info(getDescription().getName()
  397. + ": Default configuration file written: " + name);
  398. } catch (IOException e) {
  399. e.printStackTrace();
  400. } finally {
  401. try {
  402. if (input != null)
  403. input.close();
  404. } catch (IOException e) {}
  405. try {
  406. if (output != null)
  407. output.close();
  408. } catch (IOException e) {}
  409. }
  410. }
  411. }
  412. }
  413. /**
  414. * Checks permissions.
  415. *
  416. * @param sender
  417. * @param perm
  418. * @return
  419. */
  420. public boolean hasPermission(CommandSender sender, String perm) {
  421. if (!(sender instanceof Player)) {
  422. return true;
  423. }
  424. if (sender.isOp() && opPermissions) {
  425. return true;
  426. }
  427. // Invoke the permissions resolver
  428. if (sender instanceof Player) {
  429. return perms.hasPermission(((Player) sender).getName(), perm);
  430. }
  431. return false;
  432. }
  433. /**
  434. * Checks permissions and throws an exception if permission is not met.
  435. *
  436. * @param sender
  437. * @param perm
  438. * @throws CommandPermissionsException
  439. */
  440. public void checkPermission(CommandSender sender, String perm)
  441. throws CommandPermissionsException {
  442. if (!hasPermission(sender, perm)) {
  443. throw new CommandPermissionsException();
  444. }
  445. }
  446. /**
  447. * Checks to see if a user can use an item.
  448. *
  449. * @param sender
  450. * @param id
  451. * @throws CommandException
  452. */
  453. public void checkAllowedItem(CommandSender sender, int id)
  454. throws CommandException {
  455. if (id < 1 || (id > 96 && id < 256)
  456. || (id > 359 && id < 2256)
  457. || id > 2257) {
  458. if (Material.getMaterial(id) == null || id == 0) {
  459. throw new CommandException("Non-existent item specified.");
  460. }
  461. }
  462. // Check if the user has an override
  463. if (hasPermission(sender, "commandbook.override.any-item")) {
  464. return;
  465. }
  466. boolean hasPermissions = hasPermission(sender, "commandbook.items." + id);
  467. // Also check the permissions system
  468. if (hasPermissions) {
  469. return;
  470. }
  471. if (useItemPermissionsOnly) {
  472. if (!hasPermissions) {
  473. throw new CommandException("That item is not allowed.");
  474. }
  475. }
  476. if (allowedItems.size() > 0) {
  477. if (!allowedItems.contains(id)) {
  478. throw new CommandException("That item is not allowed.");
  479. }
  480. }
  481. if (disallowedItems.contains((id))) {
  482. throw new CommandException("That item is disallowed.");
  483. }
  484. }
  485. /**
  486. * Checks to see if the sender is a player, otherwise throw an exception.
  487. *
  488. * @param sender
  489. * @return
  490. * @throws CommandException
  491. */
  492. public Player checkPlayer(CommandSender sender)
  493. throws CommandException {
  494. if (sender instanceof Player) {
  495. return (Player) sender;
  496. } else {
  497. throw new CommandException("A player context is required. (Specify a world or player if the command supports it.)");
  498. }
  499. }
  500. /**
  501. * Attempts to match a creature type.
  502. *
  503. * @param sender
  504. * @param filter
  505. * @return
  506. * @throws CommandException
  507. */
  508. public CreatureType matchCreatureType(CommandSender sender,
  509. String filter) throws CommandException {
  510. CreatureType type = CreatureType.fromName(filter);
  511. if (type != null) {
  512. return type;
  513. }
  514. for (CreatureType testType : CreatureType.values()) {
  515. if (testType.getName().toLowerCase().startsWith(filter.toLowerCase())) {
  516. return testType;
  517. }
  518. }
  519. throw new CommandException("Unknown mob specified! You can "
  520. + "choose from the list of: "
  521. + CommandBookUtil.getCreatureTypeNameList());
  522. }
  523. /**
  524. * Match player names.
  525. *
  526. * @param filter
  527. * @return
  528. */
  529. public List<Player> matchPlayerNames(String filter) {
  530. Player[] players = getServer().getOnlinePlayers();
  531. filter = filter.toLowerCase();
  532. // Allow exact name matching
  533. if (filter.charAt(0) == '@' && filter.length() >= 2) {
  534. filter = filter.substring(1);
  535. for (Player player : players) {
  536. if (player.getName().equalsIgnoreCase(filter)) {
  537. List<Player> list = new ArrayList<Player>();
  538. list.add(player);
  539. return list;
  540. }
  541. }
  542. return new ArrayList<Player>();
  543. // Allow partial name matching
  544. } else if (filter.charAt(0) == '*' && filter.length() >= 2) {
  545. filter = filter.substring(1);
  546. List<Player> list = new ArrayList<Player>();
  547. for (Player player : players) {
  548. if (player.getName().toLowerCase().contains(filter)) {
  549. list.add(player);
  550. }
  551. }
  552. return list;
  553. // Start with name matching
  554. } else {
  555. List<Player> list = new ArrayList<Player>();
  556. for (Player player : players) {
  557. if (player.getName().toLowerCase().startsWith(filter)) {
  558. list.add(player);
  559. }
  560. }
  561. return list;
  562. }
  563. }
  564. /**
  565. * Checks if the given list of players is greater than size 0, otherwise
  566. * throw an exception.
  567. *
  568. * @param players
  569. * @return
  570. * @throws CommandException
  571. */
  572. protected Iterable<Player> checkPlayerMatch(List<Player> players)
  573. throws CommandException {
  574. // Check to see if there were any matches
  575. if (players.size() == 0) {
  576. throw new CommandException("No players matched query.");
  577. }
  578. return players;
  579. }
  580. /**
  581. * Checks permissions and throws an exception if permission is not met.
  582. *
  583. * @param source
  584. * @param filter
  585. * @return iterator for players
  586. * @throws CommandException no matches found
  587. */
  588. public Iterable<Player> matchPlayers(CommandSender source, String filter)
  589. throws CommandException {
  590. if (getServer().getOnlinePlayers().length == 0) {
  591. throw new CommandException("No players matched query.");
  592. }
  593. if (filter.equals("*")) {
  594. return checkPlayerMatch(Arrays.asList(getServer().getOnlinePlayers()));
  595. }
  596. // Handle special hash tag groups
  597. if (filter.charAt(0) == '#') {
  598. // Handle #world, which matches player of the same world as the
  599. // calling source
  600. if (filter.equalsIgnoreCase("#world")) {
  601. List<Player> players = new ArrayList<Player>();
  602. Player sourcePlayer = checkPlayer(source);
  603. World sourceWorld = sourcePlayer.getWorld();
  604. for (Player player : getServer().getOnlinePlayers()) {
  605. if (player.getWorld().equals(sourceWorld)) {
  606. players.add(player);
  607. }
  608. }
  609. return checkPlayerMatch(players);
  610. // Handle #near, which is for nearby players.
  611. } else if (filter.equalsIgnoreCase("#near")) {
  612. List<Player> players = new ArrayList<Player>();
  613. Player sourcePlayer = checkPlayer(source);
  614. World sourceWorld = sourcePlayer.getWorld();
  615. org.bukkit.util.Vector sourceVector
  616. = sourcePlayer.getLocation().toVector();
  617. for (Player player : getServer().getOnlinePlayers()) {
  618. if (player.getWorld().equals(sourceWorld)
  619. && player.getLocation().toVector().distanceSquared(
  620. sourceVector) < 900) {
  621. players.add(player);
  622. }
  623. }
  624. return checkPlayerMatch(players);
  625. } else {
  626. throw new CommandException("Invalid group '" + filter + "'.");
  627. }
  628. }
  629. List<Player> players = matchPlayerNames(filter);
  630. return checkPlayerMatch(players);
  631. }
  632. /**
  633. * Match a single player exactly.
  634. *
  635. * @param sender
  636. * @param filter
  637. * @return
  638. * @throws CommandException
  639. */
  640. public Player matchPlayerExactly(CommandSender sender, String filter)
  641. throws CommandException {
  642. Player[] players = getServer().getOnlinePlayers();
  643. for (Player player : players) {
  644. if (player.getName().equalsIgnoreCase(filter)) {
  645. return player;
  646. }
  647. }
  648. throw new CommandException("No player found!");
  649. }
  650. /**
  651. * Match only a single player.
  652. *
  653. * @param sender
  654. * @param filter
  655. * @return
  656. * @throws CommandException
  657. */
  658. public Player matchSinglePlayer(CommandSender sender, String filter)
  659. throws CommandException {
  660. // This will throw an exception if there are no matches
  661. Iterator<Player> players = matchPlayers(sender, filter).iterator();
  662. Player match = players.next();
  663. // We don't want to match the wrong person, so fail if if multiple
  664. // players were found (we don't want to just pick off the first one,
  665. // as that may be the wrong player)
  666. if (players.hasNext()) {
  667. throw new CommandException("More than one player found! " +
  668. "Use @<name> for exact matching.");
  669. }
  670. return match;
  671. }
  672. /**
  673. * Match only a single player or console.
  674. *
  675. * @param sender
  676. * @param filter
  677. * @return
  678. * @throws CommandException
  679. */
  680. public CommandSender matchPlayerOrConsole(CommandSender sender, String filter)
  681. throws CommandException {
  682. // Let's see if console is wanted
  683. if (filter.equalsIgnoreCase("#console")
  684. || filter.equalsIgnoreCase("*console*")
  685. || filter.equalsIgnoreCase("!")) {
  686. return new ConsoleCommandSender(getServer());
  687. }
  688. return matchSinglePlayer(sender, filter);
  689. }
  690. /**
  691. * Get a single player as an iterator for players.
  692. *
  693. * @param player
  694. * @return iterator for players
  695. */
  696. public Iterable<Player> matchPlayers(Player player) {
  697. return Arrays.asList(new Player[] {player});
  698. }
  699. /**
  700. * Match a target.
  701. *
  702. * @param source
  703. * @param filter
  704. * @return iterator for players
  705. * @throws CommandException no matches found
  706. */
  707. public Location matchLocation(CommandSender source, String filter)
  708. throws CommandException {
  709. // Handle coordinates
  710. if (filter.matches("^[\\-0-9\\.]+,[\\-0-9\\.]+,[\\-0-9\\.]+(?:.+)?$")) {
  711. checkPermission(source, "commandbook.locations.coords");
  712. String[] args = filter.split(":");
  713. String[] parts = args[0].split(",");
  714. double x, y, z;
  715. try {
  716. x = Double.parseDouble(parts[0]);
  717. y = Double.parseDouble(parts[1]);
  718. z = Double.parseDouble(parts[2]);
  719. } catch (NumberFormatException e) {
  720. throw new CommandException("Coordinates expected numbers!");
  721. }
  722. if (args.length > 1) {
  723. return new Location(matchWorld(source, args[1]), x, y, z);
  724. } else {
  725. Player player = checkPlayer(source);
  726. return new Location(player.getWorld(), x, y, z);
  727. }
  728. // Handle special hash tag groups
  729. } else if (filter.charAt(0) == '#') {
  730. checkPermission(source, "commandbook.spawn");
  731. String[] args = filter.split(":");
  732. // Handle #world, which matches player of the same world as the
  733. // calling source
  734. if (args[0].equalsIgnoreCase("#spawn")) {
  735. if (args.length > 1) {
  736. return matchWorld(source, args[1]).getSpawnLocation();
  737. } else {
  738. Player sourcePlayer = checkPlayer(source);
  739. return sourcePlayer.getLocation().getWorld().getSpawnLocation();
  740. }
  741. // Handle #target, which matches the player's target position
  742. } else if (args[0].equalsIgnoreCase("#target")) {
  743. Player player = checkPlayer(source);
  744. Location playerLoc = player.getLocation();
  745. Block targetBlock = player.getTargetBlock(null, 100);
  746. if (targetBlock == null) {
  747. throw new CommandException("Failed to find a block in your target!");
  748. } else {
  749. Location loc = targetBlock.getLocation();
  750. playerLoc.setX(loc.getX());
  751. playerLoc.setY(loc.getY());
  752. playerLoc.setZ(loc.getZ());
  753. return CommandBookUtil.findFreePosition(playerLoc);
  754. }
  755. // Handle #home and #warp, which matches a player's home or a warp point
  756. } else if (args[0].equalsIgnoreCase("#home")
  757. || args[0].equalsIgnoreCase("#warp")) {
  758. String type = args[0].substring(1);
  759. checkPermission(source, "commandbook.locations." + type);
  760. RootLocationManager<NamedLocation> manager = type.equalsIgnoreCase("warp")
  761. ? getWarpsManager()
  762. : getHomesManager();
  763. if (args.length == 1) {
  764. if (type.equalsIgnoreCase("warp")) {
  765. throw new CommandException("Please specify a warp name.");
  766. }
  767. // source player home
  768. Player ply = checkPlayer(source);
  769. NamedLocation loc = manager.get(ply.getWorld(), ply.getName());
  770. if (loc == null) {
  771. throw new CommandException("You have not set your home yet.");
  772. }
  773. return loc.getLocation();
  774. } else if (args.length == 2) {
  775. if (source instanceof Player) {
  776. Player player = (Player) source;
  777. NamedLocation loc = manager.get(player.getWorld(), args[1]);
  778. if (loc != null && !(loc.getCreatorName().equalsIgnoreCase(player.getName()))) {
  779. checkPermission(source, "commandbook.locations." + type + ".other");
  780. }
  781. }
  782. return getManagedLocation(manager, checkPlayer(source).getWorld(), args[1]);
  783. } else if (args.length == 3) {
  784. if (source instanceof Player) {
  785. Player player = (Player) source;
  786. NamedLocation loc = manager.get(matchWorld(source, args[2]), args[1]);
  787. if (loc != null && !(loc.getCreatorName().equalsIgnoreCase(player.getName()))) {
  788. checkPermission(source, "commandbook.locations." + type + ".other");
  789. }
  790. }
  791. return getManagedLocation(manager, matchWorld(source, args[2]), args[1]);
  792. }
  793. // Handle #me, which is for when a location argument is required
  794. } else if (args[0].equalsIgnoreCase("#me")) {
  795. return checkPlayer(source).getLocation();
  796. } else {
  797. throw new CommandException("Invalid group '" + filter + "'.");
  798. }
  799. }
  800. List<Player> players = matchPlayerNames(filter);
  801. // Check to see if there were any matches
  802. if (players.size() == 0) {
  803. throw new CommandException("No players matched query.");
  804. }
  805. return players.get(0).getLocation();
  806. }
  807. /**
  808. * Get a location from a location manager.
  809. *
  810. * @param manager RootLocationManager to look in
  811. * @param world
  812. * @param id name of the location
  813. * @return a Bukkit location
  814. * @throws CommandException if the location by said id does not exist
  815. */
  816. public Location getManagedLocation(RootLocationManager<NamedLocation> manager,
  817. World world, String id) throws CommandException {
  818. NamedLocation loc = manager.get(world, id);
  819. if (loc == null) throw new CommandException("A location by that name could not be found.");
  820. return loc.getLocation();
  821. }
  822. /**
  823. * Match a world.
  824. * @param sender
  825. *
  826. * @param filter
  827. * @return
  828. * @throws CommandException
  829. */
  830. public World matchWorld(CommandSender sender, String filter) throws CommandException {
  831. List<World> worlds = getServer().getWorlds();
  832. // Handle special hash tag groups
  833. if (filter.charAt(0) == '#') {
  834. // #main for the main world
  835. if (filter.equalsIgnoreCase("#main")) {
  836. return worlds.get(0);
  837. // #normal for the first normal world
  838. } else if (filter.equalsIgnoreCase("#normal")) {
  839. for (World world : worlds) {
  840. if (world.getEnvironment() == Environment.NORMAL) {
  841. return world;
  842. }
  843. }
  844. throw new CommandException("No normal world found.");
  845. // #nether for the first nether world
  846. } else if (filter.equalsIgnoreCase("#nether")) {
  847. for (World world : worlds) {
  848. if (world.getEnvironment() == Environment.NETHER) {
  849. return world;
  850. }
  851. }
  852. throw new CommandException("No nether world found.");
  853. // #skylands for the first skylands world
  854. } else if (filter.equalsIgnoreCase("#skylands")) {
  855. for (World world : worlds) {
  856. if (world.getEnvironment() == Environment.SKYLANDS) {
  857. return world;
  858. }
  859. }
  860. throw new CommandException("No skylands world found.");
  861. // Handle getting a world from a player
  862. } else if (filter.matches("^#player$")) {
  863. String parts[] = filter.split(":", 2);
  864. // They didn't specify an argument for the player!
  865. if (parts.length == 1) {
  866. throw new CommandException("Argument expected for #player.");
  867. }
  868. return matchPlayers(sender, parts[1]).iterator().next().getWorld();
  869. } else {
  870. throw new CommandException("Invalid identifier '" + filter + "'.");
  871. }
  872. }
  873. for (World world : worlds) {
  874. if (world.getName().equals(filter)) {
  875. return world;
  876. }
  877. }
  878. throw new CommandException("No world by that exact name found.");
  879. }
  880. /**
  881. * Parse a time string.
  882. *
  883. * @param timeStr
  884. * @return
  885. * @throws CommandException
  886. */
  887. public int matchTime(String timeStr) throws CommandException {
  888. Matcher matcher;
  889. try {
  890. int time = Integer.parseInt(timeStr);
  891. // People tend to enter just a number of the hour
  892. if (time <= 24) {
  893. return ((time - 8) % 24) * 1000;
  894. }
  895. return time;
  896. } catch (NumberFormatException e) {
  897. // Not an integer!
  898. }
  899. // Tick time
  900. if (timeStr.matches("^*[0-9]+$")) {
  901. return Integer.parseInt(timeStr.substring(1));
  902. // Allow 24-hour time
  903. } else if (timeStr.matches("^[0-9]+:[0-9]+$")) {
  904. String[] parts = timeStr.split(":");
  905. int hours = Integer.parseInt(parts[0]);
  906. int mins = Integer.parseInt(parts[1]);
  907. int n = (int) (((hours - 8) % 24) * 1000
  908. + Math.round((mins % 60) / 60.0 * 1000));
  909. return n;
  910. // Or perhaps 12-hour time
  911. } else if ((matcher = twelveHourTime.matcher(timeStr)).matches()) {
  912. String time = matcher.group(1);
  913. String period = matcher.group(2);
  914. int shift = 0;
  915. if (period.equalsIgnoreCase("am")
  916. || period.equalsIgnoreCase("a.m.")) {
  917. shift = 0;
  918. } else if (period.equalsIgnoreCase("pm")
  919. || period.equalsIgnoreCase("p.m.")) {
  920. shift = 12;
  921. } else {
  922. throw new CommandException("'am' or 'pm' expected, got '"
  923. + period + "'.");
  924. }
  925. String[] parts = time.split(":");
  926. int hours = Integer.parseInt(parts[0]);
  927. int mins = parts.length >= 2 ? Integer.parseInt(parts[1]) : 0;
  928. int n = (int) ((((hours % 12) + shift - 8) % 24) * 1000
  929. + (mins % 60) / 60.0 * 1000);
  930. return n;
  931. // Or some shortcuts
  932. } else if (timeStr.equalsIgnoreCase("dawn")) {
  933. return (6 - 8 + 24) * 1000;
  934. } else if (timeStr.equalsIgnoreCase("sunrise")) {
  935. return (7 - 8 + 24) * 1000;
  936. } else if (timeStr.equalsIgnoreCase("morning")) {
  937. return (8 - 8 + 24) * 1000;
  938. } else if (timeStr.equalsIgnoreCase("day")) {
  939. return (8 - 8 + 24) * 1000;
  940. } else if (timeStr.equalsIgnoreCase("midday")
  941. || timeStr.equalsIgnoreCase("noon")) {
  942. return (12 - 8 + 24) * 1000;
  943. } else if (timeStr.equalsIgnoreCase("afternoon")) {
  944. return (14 - 8 + 24) * 1000;
  945. } else if (timeStr.equalsIgnoreCase("evening")) {
  946. return (16 - 8 + 24) * 1000;
  947. } else if (timeStr.equalsIgnoreCase("sunset")) {
  948. return (21 - 8 + 24) * 1000;
  949. } else if (timeStr.equalsIgnoreCase("dusk")) {
  950. return (21 - 8 + 24) * 1000 + (int) (30 / 60.0 * 1000);
  951. } else if (timeStr.equalsIgnoreCase("night")) {
  952. return (22 - 8 + 24) * 1000;
  953. } else if (timeStr.equalsIgnoreCase("midnight")) {
  954. return (0 - 8 + 24) * 1000;
  955. }
  956. throw new CommandException("Time input format unknown.");
  957. }
  958. /**
  959. * Gets the IP address of a command sender.
  960. *
  961. * @param sender
  962. * @return
  963. */
  964. public String toInetAddressString(CommandSender sender) {
  965. if (sender instanceof Player) {
  966. return ((Player) sender).getAddress().getAddress().getHostAddress();
  967. } else {
  968. return "127.0.0.1";
  969. }
  970. }
  971. /**
  972. * Gets the name of a command sender. This is a unique name and this
  973. * method should never return a "display name".
  974. *
  975. * @param sender
  976. * @return
  977. */
  978. public String toUniqueName(CommandSender sender) {
  979. if (sender instanceof Player) {
  980. return ((Player) sender).getName();
  981. } else {
  982. return "*Console*";
  983. }
  984. }
  985. /**
  986. * Gets the name of a command sender. This play be a display name.
  987. *
  988. * @param sender
  989. * @return
  990. */
  991. public String toName(CommandSender sender) {
  992. if (sender instanceof Player) {
  993. String name = useDisplayNames
  994. ? ((Player) sender).getDisplayName()
  995. : ((Player) sender).getName();
  996. return ChatColor.stripColor(name);
  997. } else {
  998. return "*Console*";
  999. }
  1000. }
  1001. /**
  1002. * Gets the name of a command sender. This play be a display name.
  1003. *
  1004. * @param sender
  1005. * @param endColor
  1006. * @return
  1007. */
  1008. public String toColoredName(CommandSender sender, ChatColor endColor) {
  1009. if (sender instanceof Player) {
  1010. String name = useDisplayNames
  1011. ? ((Player) sender).getDisplayName()
  1012. : ((Player) sender).getName();
  1013. if (endColor != null && name.contains("\u00A7")) {
  1014. name = name + endColor;
  1015. }
  1016. return name;
  1017. } else {
  1018. return "*Console*";
  1019. }
  1020. }
  1021. /**
  1022. * Gets the name of an item.
  1023. *
  1024. * @param id
  1025. * @return
  1026. */
  1027. public String toItemName(int id) {
  1028. ItemType type = ItemType.fromID(id);
  1029. if (type != null) {
  1030. return type.getName();
  1031. } else {
  1032. return "#" + id;
  1033. }
  1034. }
  1035. /**
  1036. * Returns a matched item.
  1037. *
  1038. * @param name
  1039. * @return item
  1040. */
  1041. public ItemStack getItem(String name) {
  1042. int id = 0;
  1043. int dmg = 0;
  1044. String dataName = null;
  1045. if (name.contains(":")) {
  1046. String[] parts = name.split(":");
  1047. dataName = parts[1];
  1048. name = parts[0];
  1049. }
  1050. try {
  1051. id = Integer.parseInt(name);
  1052. } catch (NumberFormatException e) {
  1053. // First check the configurable list of aliases
  1054. Integer idTemp = itemNames.get(name.toLowerCase());
  1055. if (idTemp != null) {
  1056. id = (int) idTemp;
  1057. } else {
  1058. // Then check WorldEdit
  1059. ItemType type = ItemType.lookup(name);
  1060. if (type == null) {
  1061. return null;
  1062. }
  1063. id = type.getID();
  1064. }
  1065. }
  1066. // If the user specified an item data or damage value, let's try
  1067. // to parse it!
  1068. if (dataName != null) {
  1069. try {
  1070. dmg = matchItemData(id, dataName);
  1071. } catch (CommandException e) {
  1072. return null;
  1073. }
  1074. }
  1075. return new ItemStack(id, 1, (short)dmg, (byte)dmg);
  1076. }
  1077. /**
  1078. * Matches an item and gets the appropriate item stack.
  1079. *
  1080. * @param source
  1081. * @param name
  1082. * @return iterator for players
  1083. * @throws CommandException
  1084. */
  1085. public ItemStack matchItem(CommandSender source, String name)
  1086. throws CommandException {
  1087. int id = 0;
  1088. int dmg = 0;
  1089. String dataName = null;
  1090. if (name.contains(":")) {
  1091. String[] parts = name.split(":");
  1092. dataName = parts[1];
  1093. name = parts[0];
  1094. }
  1095. try {
  1096. id = Integer.parseInt(name);
  1097. } catch (NumberFormatException e) {
  1098. // First check the configurable list of aliases
  1099. Integer idTemp = itemNames.get(name.toLowerCase());
  1100. if (idTemp != null) {
  1101. id = (int) idTemp;
  1102. } else {
  1103. // Then check WorldEdit
  1104. ItemType type = ItemType.lookup(name);
  1105. if (type == null) {
  1106. throw new CommandException("No item type known by '" + name + "'");
  1107. }
  1108. id = type.getID();
  1109. }
  1110. }
  1111. // If the user specified an item data or damage value, let's try
  1112. // to parse it!
  1113. if (dataName != null) {
  1114. dmg = matchItemData(id, dataName);
  1115. }
  1116. return new ItemStack(id, 1, (short)dmg, (byte)dmg);
  1117. }
  1118. /**
  1119. * Attempt to match item data values.
  1120. *
  1121. * @param id
  1122. * @param filter
  1123. * @return
  1124. * @throws CommandException
  1125. */
  1126. public int matchItemData(int id, String filter) throws CommandException {
  1127. try {
  1128. // First let's try the filter as if it was a number
  1129. return Integer.parseInt(filter);
  1130. } catch (NumberFormatException e) {
  1131. }
  1132. // So the value isn't a number, but it may be an alias!
  1133. switch (id) {
  1134. case BlockID.WOOD:
  1135. if (filter.equalsIgnoreCase("redwood")) {
  1136. return 1;
  1137. } else if (filter.equalsIgnoreCase("birch")) {
  1138. return 2;
  1139. }
  1140. throw new CommandException("Unknown wood type name of '" + filter + "'.");
  1141. case BlockID.STEP:
  1142. case BlockID.DOUBLE_STEP:
  1143. BlockType dataType = BlockType.lookup(filter);
  1144. if (dataType != null) {
  1145. if (dataType == BlockType.STONE) {
  1146. return 0;
  1147. } else if (dataType == BlockType.SANDSTONE) {
  1148. return 1;
  1149. } else if (dataType == BlockType.WOOD) {
  1150. return 2;
  1151. } else if (dataType == BlockType.COBBLESTONE) {
  1152. return 3;
  1153. } else {
  1154. throw new CommandException("Invalid slab material of '" + filter + "'.");
  1155. }
  1156. } else {
  1157. throw new CommandException("Unknown slab material of '" + filter + "'.");
  1158. }
  1159. case BlockID.CLOTH:
  1160. ClothColor col = ClothColor.lookup(filter);
  1161. if (col != null) {
  1162. return col.getID();
  1163. }
  1164. throw new CommandException("Unknown wool color name of '" + filter + "'.");
  1165. case 351: // Dye
  1166. ClothColor dyeCol = ClothColor.lookup(filter);
  1167. if (dyeCol != null) {
  1168. return 15 - dyeCol.getID();
  1169. }
  1170. throw new CommandException("Unknown dye color name of '" + filter + "'.");
  1171. default:
  1172. throw new CommandException("Invalid data value of '" + filter + "'.");
  1173. }
  1174. }
  1175. /**
  1176. * Attempt to match a dye color for sheep wool.
  1177. *
  1178. * @param filter
  1179. * @return
  1180. * @throws CommandException
  1181. */
  1182. public DyeColor matchDyeColor(String filter) throws CommandException {
  1183. if (filter.equalsIgnoreCase("random")) {
  1184. return DyeColor.getByData((byte) new Random().nextInt(15));
  1185. }
  1186. try {
  1187. DyeColor match = DyeColor.valueOf(filter.toUpperCase());
  1188. if (match != null) {
  1189. return match;
  1190. }
  1191. } catch (IllegalArgumentException e) {}
  1192. throw new CommandException("Unknown dye color name of '" + filter + "'.");
  1193. }
  1194. /**
  1195. * Get preprogrammed messages.
  1196. *