diff --git a/.gitignore b/.gitignore index 32858aa..df77c9f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,11 @@ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* + +# Eclipse +.classpath +.project +.settings +classes +bin + diff --git a/README.md b/README.md index 17382f5..226d629 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,80 @@ -# VehicleControl +VehicleControl +============== Automatically break boats and minecarts. + + +Features +-------- +The features of `VehicleControl` are a superset of equivalent, active +features that were removed from [`KitchenSink`](https://github.com/NerdNu/KitchenSink). +The option to remove special carts (chest, furnace and hopper carts) was not +implemented as it is no longer used on an nerd.nu servers. + + * Optionally break carts and boats when the player exits them. + * Break empty passenger mincarts and boats after they have existed for a + configurable amount of time, with the option to drop each as an item or not. + * Optionally break passenger carts containing configured mob types. + * Optionally exempt from breaking passenger carts containing configured mob + types that have been named. + + +Principle +--------- +Vehicles are scanned periodically and tagged with metadata if they must be +broken in the future. The metadata records whether the vehicle was empty when +tagged, and the system time stamp when the vehicle should break. Vehicles +that aren't eligible to break don't get tagged. + +When the plugin re-scans a vehicle that is already tagged, it checks the +metadata: + + * If the vehicle was tagged while empty, but is now occupied by a vulnerable + mob, then it gets an extension to the time limit for vehicles with + vulnerable passengers. + * If the vehicle was tagged as empty or with a vulnerable mob but is + now occupied by an invulnerable entity (e.g. a player) then the metadata + is cleared. + * If the metadata matches the passenger (or lack thereof) and has reached its + expiry time, the vehicle breaks, and drops if configured to do so. + +Since vehicles must be scanned at least twice before they can be dropped, +configured time limits represent the minimum time that the vehicle will exist +in its current state. It will generally last a little longer before breaking, +depending on the phase of the scan task. + +In the case of boats, the scanning process only considers the primary passenger. + + +Configuration +------------- + +| Setting | Description | +| :--- | :--- | +| `debug.config` | If true, loaded configuration settings are logged. | +| `debug.overhead` | If true, log the time taken to run the scanning task. | +| `debug.break-vehicle` | If true, log breaking of vehicles. | +| `debug.exempt-vehicle` | If true, log vehicles that are exempt from breaking when they are scanned. | +| `scan.period-seconds` | The period, in seconds, between scans for vehicles. | +| `scan.worlds` | The list of names of worlds that are scanned for vehicles. | +| `vehicles.remove-on-exit` | If true, remove carts and boats when the player exits. They will not drop as an item; they simply vanish. | +| `vehicles.drop-item` | If true, vehicles drop as an item when broken as part of the scanning process. Otherwise, they simply vanish. | +| `vehicles.break-empty` | If true, break boats or passenger carts that are empty. | +| `vehicles.break-empty-seconds` | The minimum period, in seconds, that an empty vehicle can persist before breaking. | +| `vehicles.break-with-passenger` | If true, break vehicles with passengers of specified types. | +| `vehicles.break-with-passenger-seconds` | The minimum period, in seconds, that a vehicle with a mob passenger can persist before it is broken automatically. | +| `vehicles.break-with-passenger-types` | Types of passengers that are vulnerable to their vehicle breaking. | +| `vehicles.exempt-with-named-passenger` | If true, protect vehicles with passengers of specified types if the passengers are named. | +| `vehicles.exempt-with-named-passenger-types` | Types of otherwise vulnerable passengers that are exempted from their vehicle breaking if they have been named. This setting carves out exemptions from the list of mob types in `vehicles.break-with-passenger-types`. That is, it is only necessary to exempt a mob type here if it has been previously explicitly listed as vulnerable. | + + +Commands +-------- + + * `/vehiclecontrol reload` - Reload the configuration. + + +Permissions +----------- + + * `vehiclecontrol.admin` - Permission to run `/vehiclecontrol reload`. + diff --git a/config.yml b/config.yml new file mode 100644 index 0000000..8047e93 --- /dev/null +++ b/config.yml @@ -0,0 +1,34 @@ +debug: + configuration: false + overhead: true + break-vehicle: false + exempt-vehicle: false + +# Settings affecting the scan for vehicles (boats and carts). +scan: + period-seconds: 310 + worlds: + - world + - world_nether + - world_the_end + +# Settings that apply to boats and carts. +vehicles: + # Remove the vehicle when the player exits. + remove-on-exit: false + + drop-item: true + break-empty: true + break-empty-seconds: 300 + + # Break vehicles with specific passenger mob types. + break-with-passenger: false + break-with-passenger-seconds: 900 + break-with-passenger-types: + - VILLAGER + + # Exempt (from breaking) vehicles with passenger mobs of these types if the + # mobs are named. + exempt-with-named-passenger: true + exempt-with-named-passenger-types: [] + diff --git a/plugin.yml b/plugin.yml new file mode 100644 index 0000000..0307dcf --- /dev/null +++ b/plugin.yml @@ -0,0 +1,19 @@ +name: ${project.name} +version: ${project.version} +author: totemo +authors: [totemo] +description: ${project.description} +website: ${project.url} +main: nu.nerd.vc.VehicleControl + +permissions: + vehiclecontrol.admin: + description: Permission to administer the plugin. + default: op + +commands: + vehiclecontrol: + description: ${project.name} administrative command. + permission: vehiclecontrol.admin + usage: | + / reload: Reload the configuration. diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..60ef111 --- /dev/null +++ b/pom.xml @@ -0,0 +1,71 @@ + + 4.0.0 + nu.nerd + VehicleControl + ${project.name} + 1.0.0 + jar + Automatically break boats and minecarts. + https://github.com/NerdNu/${project.name} + + scm:git:git://github.com/NerdNu/${project.name}.git + https://github.com/NerdNu/${project.name} + scm:git:git://github.com/NerdNu/${project.name}.git + + + UTF-8 + + + + spigot-repo + https://hub.spigotmc.org/nexus/content/groups/public/ + + + + + org.spigotmc + spigot-api + 1.9-R0.1-SNAPSHOT + + + + clean package + ${basedir}/src + + + . + true + ${basedir} + + plugin.yml + config.yml + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.1 + + + false + + ${project.artifactId}-${project.version} + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.0.2 + + 1.7 + 1.7 + + + + + + diff --git a/src/nu/nerd/vc/Configuration.java b/src/nu/nerd/vc/Configuration.java new file mode 100644 index 0000000..a2dba66 --- /dev/null +++ b/src/nu/nerd/vc/Configuration.java @@ -0,0 +1,204 @@ +package nu.nerd.vc; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Logger; + +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.EntityType; + +// ---------------------------------------------------------------------------- +/** + * Configuration wrapper. + */ +public class Configuration { + /** + * If true, log the configuration on load. + */ + public boolean DEBUG_CONFIGURATION; + + /** + * If true, log the time taken to run the scanning task. + */ + public boolean DEBUG_OVERHEAD; + + /** + * If true, log breaking of vehicles. + */ + public boolean DEBUG_BREAK_VEHICLE; + + /** + * If true, log vehicles that are exempt from breaking when scanned. + */ + public boolean DEBUG_EXEMPT_VEHICLE; + + /** + * The period, in seconds, between scans for carts and boats. + */ + public int SCAN_PERIOD_SECONDS; + + /** + * List of worlds that are scanned for vehicles to process. + */ + public ArrayList SCAN_WORLDS = new ArrayList(); + + /** + * If true, remove carts and boats when the player exits. + * + * They will not drop as an item. They simply vanish. + */ + public boolean VEHICLES_REMOVE_ON_EXIT; + + /** + * If true, drop the vehicle as an item when broken (otherwise, it simply + * vanishes). + */ + public boolean VEHICLES_DROP_ITEM; + + /** + * If true, break boats or passenger carts that are empty. + */ + public boolean VEHICLES_BREAK_EMPTY; + + /** + * The minimum period, in seconds, that an empty vehicle can persist before + * breaking. + */ + public int VEHICLES_BREAK_EMPTY_SECONDS; + + /** + * If true, break vehicles with passengers of specified types. + */ + public boolean VEHICLES_BREAK_WITH_PASSENGER; + + /** + * The minimum period, in seconds, that a vehicle with a mob passenger can + * persist before it is broken automatically. + */ + public int VEHICLES_BREAK_WITH_PASSENGER_SECONDS; + + /** + * Types of passengers that are vulnerable to their vehicle breaking. + */ + public Set VEHICLES_BREAK_WITH_PASSENGER_TYPES = new HashSet(); + + /** + * If true, protect vehicles with passengers of specified types if the + * passengers are named. + */ + public boolean VEHICLES_EXEMPT_WITH_NAMED_PASSENGER; + + /** + * Types of otherwise vulnerable passengers that are exempted from their + * vehicle breaking if they have been named. + */ + public Set VEHICLES_EXEMPT_WITH_NAMED_PASSENGER_TYPES = new HashSet(); + + // ------------------------------------------------------------------------ + /** + * Load the plugin configuration. + */ + public void reload() { + VehicleControl.PLUGIN.reloadConfig(); + + DEBUG_CONFIGURATION = getConfig().getBoolean("debug.configuration"); + DEBUG_OVERHEAD = getConfig().getBoolean("debug.overhead"); + DEBUG_BREAK_VEHICLE = getConfig().getBoolean("debug.break-vehicle"); + DEBUG_EXEMPT_VEHICLE = getConfig().getBoolean("debug.exempt-vehicle"); + + SCAN_PERIOD_SECONDS = getConfig().getInt("scan.period-seconds"); + SCAN_WORLDS.clear(); + for (String worldName : getConfig().getStringList("scan.worlds")) { + World world = Bukkit.getWorld(worldName); + if (world == null) { + getLogger().warning("There is no world named \"" + worldName + "\" to scan."); + } else { + SCAN_WORLDS.add(world); + } + } + + VEHICLES_REMOVE_ON_EXIT = getConfig().getBoolean("vehicles.remove-on-exit"); + VEHICLES_DROP_ITEM = getConfig().getBoolean("vehicles.drop-item"); + VEHICLES_BREAK_EMPTY = getConfig().getBoolean("vehicles.break-empty"); + VEHICLES_BREAK_EMPTY_SECONDS = getConfig().getInt("vehicles.break-empty-seconds"); + + VEHICLES_BREAK_WITH_PASSENGER = getConfig().getBoolean("vehicles.break-with-passenger"); + VEHICLES_BREAK_WITH_PASSENGER_SECONDS = getConfig().getInt("vehicles.break-with-passenger-seconds"); + VEHICLES_BREAK_WITH_PASSENGER_TYPES.clear(); + for (String typeName : getConfig().getStringList("vehicles.break-with-passenger-types")) { + try { + VEHICLES_BREAK_WITH_PASSENGER_TYPES.add(EntityType.valueOf(typeName)); + } catch (IllegalArgumentException ex) { + getLogger().warning("Cannot break vehicles containing invalid entity type \"" + typeName + "\"."); + } + } + + VEHICLES_EXEMPT_WITH_NAMED_PASSENGER = getConfig().getBoolean("vehicles.exempt-with-named-passenger"); + VEHICLES_EXEMPT_WITH_NAMED_PASSENGER_TYPES.clear(); + for (String typeName : getConfig().getStringList("vehicles.exempt-with-named-passenger-types")) { + try { + VEHICLES_EXEMPT_WITH_NAMED_PASSENGER_TYPES.add(EntityType.valueOf(typeName)); + } catch (IllegalArgumentException ex) { + getLogger().warning("Cannot exempt invlid entity type \"" + typeName + "\" from vehicle breakage."); + } + } + + if (DEBUG_CONFIGURATION) { + VehicleControl.PLUGIN.getLogger().info("Configuration: "); + getLogger().info("DEBUG_OVERHEAD: " + DEBUG_OVERHEAD); + getLogger().info("DEBUG_BREAK_VEHICLE: " + DEBUG_BREAK_VEHICLE); + getLogger().info("DEBUG_EXEMPT_VEHICLE: " + DEBUG_EXEMPT_VEHICLE); + + getLogger().info("SCAN_PERIOD_SECONDS: " + SCAN_PERIOD_SECONDS); + StringBuilder scannedWorlds = new StringBuilder(); + for (World world : SCAN_WORLDS) { + scannedWorlds.append(' ').append(world.getName()); + } + getLogger().info("SCAN_WORLDS:" + scannedWorlds.toString()); + + getLogger().info("VEHICLES_REMOVE_ON_EXIT: " + VEHICLES_REMOVE_ON_EXIT); + getLogger().info("VEHICLES_DROP_ITEM: " + VEHICLES_DROP_ITEM); + getLogger().info("VEHICLES_BREAK_EMPTY: " + VEHICLES_BREAK_EMPTY); + getLogger().info("VEHICLES_BREAK_EMPTY_SECONDS: " + VEHICLES_BREAK_EMPTY_SECONDS); + + StringBuilder breakTypes = new StringBuilder(); + for (EntityType type : VEHICLES_BREAK_WITH_PASSENGER_TYPES) { + breakTypes.append(' ').append(type.name()); + } + getLogger().info("VEHICLES_BREAK_WITH_PASSENGER: " + VEHICLES_BREAK_WITH_PASSENGER); + getLogger().info("VEHICLES_BREAK_WITH_PASSENGER_SECONDS: " + VEHICLES_BREAK_WITH_PASSENGER_SECONDS); + getLogger().info("VEHICLES_BREAK_WITH_PASSENGER_TYPES:" + breakTypes.toString()); + + StringBuilder exemptTypes = new StringBuilder(); + for (EntityType type : VEHICLES_EXEMPT_WITH_NAMED_PASSENGER_TYPES) { + exemptTypes.append(' ').append(type.name()); + } + getLogger().info("VEHICLES_EXEMPT_WITH_NAMED_PASSENGER: " + VEHICLES_EXEMPT_WITH_NAMED_PASSENGER); + getLogger().info("VEHICLES_EXEMPT_WITH_NAMED_PASSENGER_TYPES:" + exemptTypes.toString()); + + } + } // reload + + // ------------------------------------------------------------------------ + /** + * Return the plugin's FileConfiguration instance. + * + * @return the plugin's FileConfiguration instance. + */ + protected static FileConfiguration getConfig() { + return VehicleControl.PLUGIN.getConfig(); + } + + // ------------------------------------------------------------------------ + /** + * Return the plugin's Logger. + * + * @return the plugin's Logger. + */ + protected static Logger getLogger() { + return VehicleControl.PLUGIN.getLogger(); + } +} // class Configuration \ No newline at end of file diff --git a/src/nu/nerd/vc/VehicleControl.java b/src/nu/nerd/vc/VehicleControl.java new file mode 100644 index 0000000..bf0d06d --- /dev/null +++ b/src/nu/nerd/vc/VehicleControl.java @@ -0,0 +1,87 @@ +package nu.nerd.vc; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Boat; +import org.bukkit.entity.Vehicle; +import org.bukkit.entity.minecart.RideableMinecart; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.vehicle.VehicleExitEvent; +import org.bukkit.plugin.java.JavaPlugin; + +// ---------------------------------------------------------------------------- +/** + * Plugin and listener class. + */ +public class VehicleControl extends JavaPlugin implements Listener { + /** + * This plugin as a singleton. + */ + public static VehicleControl PLUGIN; + + /** + * Configuration singleton. + */ + public static Configuration CONFIG = new Configuration(); + + // ------------------------------------------------------------------------ + /** + * @see org.bukkit.plugin.java.JavaPlugin#onEnable() + */ + @Override + public void onEnable() { + PLUGIN = this; + + saveDefaultConfig(); + CONFIG.reload(); + + getServer().getPluginManager().registerEvents(this, this); + new VehicleScanTask().scheduleNextRun(); + } + + // ------------------------------------------------------------------------ + /** + * @see org.bukkit.plugin.java.JavaPlugin#onDisable() + */ + @Override + public void onDisable() { + Bukkit.getScheduler().cancelTasks(this); + } + + // ------------------------------------------------------------------------ + /** + * @see org.bukkit.plugin.java.JavaPlugin#onCommand(org.bukkit.command.CommandSender, + * org.bukkit.command.Command, java.lang.String, java.lang.String[]) + */ + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (command.getName().equalsIgnoreCase(getName())) { + if (args.length == 1 && args[0].equalsIgnoreCase("reload")) { + CONFIG.reload(); + sender.sendMessage(ChatColor.GOLD + getName() + " configuration reloaded."); + return true; + } + } + + sender.sendMessage(ChatColor.RED + "Usage: /" + command.getName() + "reload - Reload the configuration."); + return true; + } + + // ------------------------------------------------------------------------ + /** + * Remove boats and minecarts when the player exits them, if configured to + * do so. + */ + @EventHandler(ignoreCancelled = true) + public void onVehicleExit(VehicleExitEvent event) { + if (CONFIG.VEHICLES_REMOVE_ON_EXIT) { + Vehicle vehicle = event.getVehicle(); + if (vehicle instanceof Boat || vehicle instanceof RideableMinecart) { + vehicle.remove(); + } + } + } +} // class VehicleControl \ No newline at end of file diff --git a/src/nu/nerd/vc/VehicleMetadata.java b/src/nu/nerd/vc/VehicleMetadata.java new file mode 100644 index 0000000..74b3713 --- /dev/null +++ b/src/nu/nerd/vc/VehicleMetadata.java @@ -0,0 +1,91 @@ +package nu.nerd.vc; + +import org.bukkit.metadata.MetadataValueAdapter; + +// ------------------------------------------------------------------------ +/** + * Metadata set on vehicles that will break in the future. + * + * The metadata records the system time at which the vehicle is due to break, + * and whether that was computed on the basis of it being empty (in which case, + * a new passenger could extend the timeout, or invalidate it). + */ +public class VehicleMetadata extends MetadataValueAdapter { + // -------------------------------------------------------------------- + /** + * Constructor. + * + * @param occupied true if the vehicle is occupied. + * @parma timeOut the system time at which the vehicle should break. + */ + protected VehicleMetadata(boolean occupied, long timeOut) { + super(VehicleControl.PLUGIN); + _occupied = occupied; + _timeOut = timeOut; + } + + // -------------------------------------------------------------------- + /** + * @see org.bukkit.metadata.MetadataValue#invalidate() + * + * We don't implement the cache invalidation semantics. Metadata is + * trivially computed. + */ + @Override + public void invalidate() { + } + + // -------------------------------------------------------------------- + /** + * @see org.bukkit.metadata.MetadataValue#value() + */ + @Override + public Object value() { + return _timeOut; + } + + // -------------------------------------------------------------------- + /** + * Update the occupied state and timeout time stamp. + * + * @param occupied true if the vehicle is occupied. + * @param timeOut the system time when the vehicle should break. + */ + public void update(boolean occupied, long timeOut) { + _occupied = occupied; + _timeOut = timeOut; + } + + // -------------------------------------------------------------------- + /** + * Return true if the metadata was set when the vehicle was occupied by a + * vulnerable passenger. + * + * @return true if the vehicle was occupied. + */ + public boolean isOccupied() { + return _occupied; + + } + + // -------------------------------------------------------------------- + /** + * Return the system time when the vehicle should break. + * + * @return the system time when the vehicle should break. + */ + public long getTimeOut() { + return _timeOut; + } + + // -------------------------------------------------------------------- + /** + * The system time stamp at which the vehicle should drop. + */ + private long _timeOut; + + /** + * If true, the vehicle was occupied. + */ + private boolean _occupied; +} // class VehicleMetadata \ No newline at end of file diff --git a/src/nu/nerd/vc/VehicleScanTask.java b/src/nu/nerd/vc/VehicleScanTask.java new file mode 100644 index 0000000..31e25e3 --- /dev/null +++ b/src/nu/nerd/vc/VehicleScanTask.java @@ -0,0 +1,244 @@ +package nu.nerd.vc; + +import java.util.List; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.entity.Boat; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Vehicle; +import org.bukkit.entity.minecart.RideableMinecart; +import org.bukkit.inventory.ItemStack; +import org.bukkit.metadata.MetadataValue; + +// ---------------------------------------------------------------------------- +/** + * The task that scans for vehicles and removes them. + * + * Where possible, the system time is used for measuring the lifetime of + * vehicles as it is immune to slow tick rates and works even when the daylight + * cycle is shut off. + */ +public class VehicleScanTask implements Runnable { + // ------------------------------------------------------------------------ + /** + * Schedule the next run of this task. + */ + public void scheduleNextRun() { + Bukkit.getScheduler().scheduleSyncDelayedTask(VehicleControl.PLUGIN, + this, + 20 * VehicleControl.CONFIG.SCAN_PERIOD_SECONDS); + } + + // ------------------------------------------------------------------------ + /** + * @see java.lang.Runnable#run() + */ + @Override + public void run() { + long startNanos = System.nanoTime(); + long now = System.currentTimeMillis(); + for (World world : VehicleControl.CONFIG.SCAN_WORLDS) { + scanWorld(world, now); + } + + if (VehicleControl.CONFIG.DEBUG_OVERHEAD) { + double elapsedMillis = (System.nanoTime() - startNanos) * 1e-6; + VehicleControl.PLUGIN.getLogger().info("Scan task took " + elapsedMillis + " milliseconds"); + } + + scheduleNextRun(); + } // run + + // ------------------------------------------------------------------------ + /** + * Perform all required tasks in the specified world. + * + * @param world the affected world. + * @param now the current system time. + */ + protected void scanWorld(World world, long now) { + for (Boat boat : world.getEntitiesByClass(Boat.class)) { + checkVehicle(boat, now); + } + + for (RideableMinecart cart : world.getEntitiesByClass(RideableMinecart.class)) { + checkVehicle(cart, now); + } + } + + // ------------------------------------------------------------------------ + /** + * Update {@link VehicleMetadata} on a vehicle and break the vehicle if + * required. + * + * @param vehicle the vehicle. + * @param now the current system time. + */ + protected void checkVehicle(Vehicle vehicle, long now) { + VehicleMetadata meta = getVehicleMetadata(vehicle); + Entity passenger = vehicle.getPassenger(); + if (meta == null) { + // If not tagged, tag the vehicle if it will break and we're done. + if (vehicle.isEmpty()) { + if (VehicleControl.CONFIG.VEHICLES_BREAK_EMPTY) { + vehicle.setMetadata(VEHICLE_META_KEY, + new VehicleMetadata(false, + now + MILLIS * VehicleControl.CONFIG.VEHICLES_BREAK_EMPTY_SECONDS)); + } + } else if (isBreakable(vehicle.getPassenger())) { + vehicle.setMetadata(VEHICLE_META_KEY, + new VehicleMetadata(true, + now + MILLIS * VehicleControl.CONFIG.VEHICLES_BREAK_WITH_PASSENGER_SECONDS)); + } else { + // Won't be tagged as scheduled for a break. Log exemption. + if (VehicleControl.CONFIG.DEBUG_EXEMPT_VEHICLE) { + StringBuilder message = new StringBuilder(); + message.append("Exempted ").append(vehicle.getType().name()); + message.append(" at ").append(formatLoc(vehicle.getLocation())); + message.append(", passenger ").append(passenger.getType().name()); + String customName = passenger.getCustomName(); + if (customName != null) { + message.append(" custom name \"").append(customName).append("\""); + } + VehicleControl.PLUGIN.getLogger().info(message.toString()); + } + } + } else { + // Vehicle is already tagged. + if (now >= meta.getTimeOut()) { + if (passenger == null) { + // Currently empty and timed out. + breakVehicle(vehicle); + } else if (isBreakable(passenger)) { + if (meta.isOccupied()) { + // Still occupied by a breakable passenger. + breakVehicle(vehicle); + } else { + // Extend timeout: passenger boarded since tagging. + meta.update(true, now + MILLIS * VehicleControl.CONFIG.VEHICLES_BREAK_WITH_PASSENGER_SECONDS); + } + } else { + // Passenger doesn't allow break. Remove timeout. + vehicle.removeMetadata(VEHICLE_META_KEY, VehicleControl.PLUGIN); + } + } + } + } // processVehicle + + // ------------------------------------------------------------------------ + /** + * Break the vehicle, dropping the item if required and logging the action. + * + * @param vehicle the vehicle. + */ + protected void breakVehicle(Vehicle vehicle) { + Location loc = vehicle.getLocation(); + World world = loc.getWorld(); + + Material dropMaterial = null; + if (VehicleControl.CONFIG.VEHICLES_DROP_ITEM) { + if (vehicle.getType() == EntityType.BOAT) { + Boat boat = (Boat) vehicle; + dropMaterial = BOAT_DROP_TABLE[boat.getWoodType().ordinal()]; + } else if (vehicle.getType() == EntityType.MINECART) { + dropMaterial = Material.MINECART; + } + + world.dropItem(loc, new ItemStack(dropMaterial, 1)); + } + + if (VehicleControl.CONFIG.DEBUG_BREAK_VEHICLE) { + StringBuilder message = new StringBuilder(); + message.append("Breaking ").append(vehicle.getType().name()); + message.append(" at ").append(formatLoc(loc)); + if (dropMaterial != null) { + message.append(" dropping ").append(dropMaterial.name()); + } + Entity passenger = vehicle.getPassenger(); + if (passenger == null) { + message.append(", no passenger"); + } else { + message.append(", passenger ").append(passenger.getType().name()); + } + VehicleControl.PLUGIN.getLogger().info(message.toString()); + } + vehicle.remove(); + } // breakVehicle + + // ------------------------------------------------------------------------ + /** + * Return true if the passenger would allow a vehicle to be broken. + * + * @param passenger the passenger, which must be non-null. + * @return true if the passenger would allow a vehicle to be broken. + */ + protected boolean isBreakable(Entity passenger) { + return VehicleControl.CONFIG.VEHICLES_BREAK_WITH_PASSENGER && + VehicleControl.CONFIG.VEHICLES_BREAK_WITH_PASSENGER_TYPES.contains(passenger.getType()) && + !hasExemptedTypeAndName(passenger); + } + + // ------------------------------------------------------------------------ + /** + * Return true if the passenger is of a type that would be exempted if named + * and has a custom name. + * + * @param passenger the passenger, which must be non-null. + * @return true if the passenger is of a type that would be exempted if + * named and has a custom name. + */ + protected boolean hasExemptedTypeAndName(Entity passenger) { + return VehicleControl.CONFIG.VEHICLES_EXEMPT_WITH_NAMED_PASSENGER && + passenger.getCustomName() != null && + VehicleControl.CONFIG.VEHICLES_EXEMPT_WITH_NAMED_PASSENGER_TYPES.contains(passenger.getType()); + } + + // ------------------------------------------------------------------------ + /** + * Get the VehicleMetadata of the vehicle. + * + * @param vehicle the vehicle. + * @return the VehicleMetadata, or null if not set. + */ + protected VehicleMetadata getVehicleMetadata(Entity vehicle) { + List meta = vehicle.getMetadata(VEHICLE_META_KEY); + return (meta.size() != 0) ? (VehicleMetadata) meta.get(0) : null; + } + + // ------------------------------------------------------------------------ + /** + * Format a Location as a string containing integer coordinates. + * + * @param loc the location. + * @return the location as a string. + */ + protected String formatLoc(Location loc) { + return loc.getWorld().getName() + ", " + + loc.getBlockX() + ", " + + loc.getBlockY() + ", " + + loc.getBlockZ(); + } + + // ------------------------------------------------------------------------ + /** + * Conversion factor from seconds to milliseconds. + */ + private static final long MILLIS = 1000; + + /** + * Look up table mapping TreeSpecies ordinal (boat type) to corresponding + * dropped boat item type. + */ + private static final Material BOAT_DROP_TABLE[] = { Material.BOAT, Material.BOAT_SPRUCE, + Material.BOAT_BIRCH, Material.BOAT_JUNGLE, + Material.BOAT_ACACIA, Material.BOAT_DARK_OAK }; + + /** + * Metadata key for storing the VehicleMetadata on a vehicle. + */ + private static final String VEHICLE_META_KEY = "VC_Meta"; +} // class VehicleScanTask \ No newline at end of file