diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5e35210 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.apt_generated +.apt_generated_tests +.vscode +target \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..88ffc1d --- /dev/null +++ b/pom.xml @@ -0,0 +1,120 @@ + + + + 4.0.0 + + xyz.dcaron.bridges + RetractableBridges + 1.0-SNAPSHOT + + RetractableBridges + + http://www.example.com + + + UTF-8 + 1.8 + 1.8 + + + + + papermc + https://papermc.io/repo/repository/maven-public/ + + + + + + junit + junit + 4.11 + test + + + io.papermc.paper + paper-api + 1.19-R0.1-SNAPSHOT + provided + + + org.projectlombok + lombok + 1.18.24 + provided + + + + + + + src/main/resources + true + + *.yml + + + + + + + + + maven-clean-plugin + 3.1.0 + + + + maven-resources-plugin + 3.0.2 + + + maven-compiler-plugin + 3.8.0 + + 17 + + + + maven-surefire-plugin + 2.22.1 + + + maven-jar-plugin + 3.0.2 + + + maven-install-plugin + 2.5.2 + + + maven-deploy-plugin + 2.8.2 + + + + maven-site-plugin + 3.7.1 + + + maven-project-info-reports-plugin + 3.0.0 + + + org.projectlombok + lombok-maven-plugin + 1.18.20.0 + + + generate-sources + + delombok + + + + + + + + diff --git a/src/main/java/xyz/dcaron/bridges/Bridge.java b/src/main/java/xyz/dcaron/bridges/Bridge.java new file mode 100644 index 0000000..ed73380 --- /dev/null +++ b/src/main/java/xyz/dcaron/bridges/Bridge.java @@ -0,0 +1,53 @@ +package xyz.dcaron.bridges; + +import java.util.Set; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +@EqualsAndHashCode +public class Bridge { + + @Getter + private final String worldName; + @Getter + @Setter + private int x, z; + @Getter + private final int y, width, height; + @Getter + private final Material type; + @Getter + @Setter + private Set blockedDirections; + + public Bridge(final String worldName, final int x, final int z, final int y, final int width, final int height, + final Material type, final Set blockedDirections) { + this.worldName = worldName; + this.x = x; + this.z = z; + this.y = y; + this.width = width; + this.height = height; + this.type = type; + this.blockedDirections = blockedDirections; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("x: ").append(x); + sb.append(" z: ").append(z); + sb.append(" y: ").append(y); + sb.append(" width: ").append(width); + sb.append(" height: ").append(height); + sb.append(" material: ").append(type); + sb.append(" directions blocked: ").append(blockedDirections.toString()); + return sb.toString(); + } + +} diff --git a/src/main/java/xyz/dcaron/bridges/BridgeBlockListener.java b/src/main/java/xyz/dcaron/bridges/BridgeBlockListener.java new file mode 100644 index 0000000..69a6e57 --- /dev/null +++ b/src/main/java/xyz/dcaron/bridges/BridgeBlockListener.java @@ -0,0 +1,243 @@ +package xyz.dcaron.bridges; + +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.logging.Level; + +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockRedstoneEvent; + +public class BridgeBlockListener implements Listener { + + private final BridgeOptions options; + + private final BlockFace[] searchDirections = { + BlockFace.UP, + BlockFace.NORTH, + BlockFace.EAST, + BlockFace.WEST, + BlockFace.SOUTH + }; + + private final Set bridgeMovers = new HashSet(); + + public BridgeBlockListener(final BridgeOptions options) { + this.options = options; + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onRedstoneBlockChange(final BlockRedstoneEvent event) { + final Block block = event.getBlock(); + final boolean isPowerOn = event.getOldCurrent() == 0; + + if (!this.isPowerOnOrOffEvent(event)) { + return; + } + + for (final BlockFace direction : this.searchDirections) { + this.findBridge(block, direction).ifPresent((bridge) -> { + BridgesPlugin.log("Bridge found " + bridge.toString(), Level.FINE); + + Set blockedDirections = bridge.getBlockedDirections(); + + if (isPowerOn) { + if (blockedDirections.size() == 3) { + if (!blockedDirections.contains(BlockFace.SOUTH)) { + move(bridge, BlockFace.SOUTH); + } else if (!blockedDirections.contains(BlockFace.EAST)) { + move(bridge, BlockFace.EAST); + } + } else { + if (blockedDirections.contains(BlockFace.NORTH) && + blockedDirections.contains(BlockFace.SOUTH)) { + move(bridge, BlockFace.EAST); + } else { + move(bridge, BlockFace.SOUTH); + } + } + } else { + if (blockedDirections.size() == 3) { + if (!blockedDirections.contains(BlockFace.NORTH)) { + move(bridge, BlockFace.NORTH); + } else if (!blockedDirections.contains(BlockFace.WEST)) { + move(bridge, BlockFace.WEST); + } + } else { + if (blockedDirections.contains(BlockFace.NORTH) && + blockedDirections.contains(BlockFace.SOUTH)) { + move(bridge, BlockFace.WEST); + } else { + move(bridge, BlockFace.NORTH); + } + } + } + + }); + } + + } + + /** + * Find a bridge above the adjacent block. A bridge is a rectangular area of + * slabs or double slabs, parallel to the ground. It can have no holes or bits + * sticking out, and it must be contrained on three sides, or on two opposite + * sides. + * + * @param block Origin block + * @param direction Direction of adjacent block + * @return Some Bridge or None + */ + private Optional findBridge(final Block block, final BlockFace direction) { + final Block targetBlock = block.getRelative(direction); + + if (!isBlockBridgePowerBlock(targetBlock.getType())) { + BridgesPlugin.log("Block is not bridge power block", Level.FINE); + return Optional.empty(); + } + + final Block aboveBlock = targetBlock.getRelative(BlockFace.UP); + if (isPotentialBridgeBlock(aboveBlock.getType())) { + + int x = aboveBlock.getX(); + int y = aboveBlock.getY(); + int z = aboveBlock.getZ(); + int width = 1, height = 1; + final Material material = aboveBlock.getType(); + + Block adjacentBlock = aboveBlock.getRelative(BlockFace.WEST); + while (adjacentBlock.getType().equals(material)) { + x--; + width++; + adjacentBlock = adjacentBlock.getRelative(BlockFace.WEST); + } + + adjacentBlock = aboveBlock.getRelative(BlockFace.NORTH); + while (adjacentBlock.getType().equals(material)) { + z--; + height++; + adjacentBlock = adjacentBlock.getRelative(BlockFace.NORTH); + } + + adjacentBlock = aboveBlock.getRelative(BlockFace.EAST); + while (adjacentBlock.getType().equals(material)) { + width++; + adjacentBlock = adjacentBlock.getRelative(BlockFace.EAST); + } + + adjacentBlock = aboveBlock.getRelative(BlockFace.SOUTH); + while (adjacentBlock.getType().equals(material)) { + height++; + adjacentBlock = adjacentBlock.getRelative(BlockFace.SOUTH); + } + + if (width >= 2 && height >= 2) { + // Bridges must be completely filled with no holes and + // have no material obtruding it's square form. + final World world = targetBlock.getWorld(); + Set blockedDirections = EnumSet.noneOf(BlockFace.class); + + for (int dz = 0; dz < height; dz++) { + Block edgeBlock = world.getBlockAt(x - 1, y, z + dz); + if (edgeBlock.getType().equals(material)) { + // A block is sticking out. + return Optional.empty(); + } else if (!edgeBlock.getType().isAir()) { + blockedDirections.add(BlockFace.WEST); + } + + edgeBlock = world.getBlockAt(x + width, y, z + dz); + if (edgeBlock.getType().equals(material)) { + // A block is sticking out. + return Optional.empty(); + } else if (!edgeBlock.getType().isAir()) { + blockedDirections.add(BlockFace.EAST); + } + } + + for (int dx = 0; dx < width; dx++) { + Block edgeBlock = world.getBlockAt(x + dx, y, z - 1); + if (edgeBlock.getType().equals(material)) { + // A block is sticking out. + return Optional.empty(); + } else if (!edgeBlock.getType().isAir()) { + blockedDirections.add(BlockFace.NORTH); + } + + for (int dz = 0; dz < height; dz++) { + final Block bridgeBlock = world.getBlockAt(x + dx, y, z + dz); + if (!bridgeBlock.getType().equals(material)) { + // There is a hole in the bridge. + return Optional.empty(); + } + } + + edgeBlock = world.getBlockAt(x + dx, y, z + height); + if (edgeBlock.getType().equals(material)) { + // A block is sticking out. + return Optional.empty(); + } else if (!edgeBlock.getType().isAir()) { + blockedDirections.add(BlockFace.SOUTH); + } + } + + // The bridge is a proper rectangle. + if (this.blockedInThreeDirections(blockedDirections) || + this.opposingDirectionsAreBlocked(blockedDirections)) { + return Optional.of(new Bridge(world.getName(), x, z, y, width, height, material, blockedDirections)); + } + } + + } + + return Optional.empty(); + } + + private void move(final Bridge bridge, final BlockFace direction) { + this.bridgeMovers.stream() + .filter(potentialMover -> potentialMover.getBridge().equals(bridge)) + .findFirst() + .ifPresentOrElse( + mover -> { + // The bridge material may have changed since it was last moved. + mover.setBridge(bridge); + mover.move(direction); + }, + () -> { + BridgeMover mover = new BridgeMover(bridge, this.options); + this.bridgeMovers.add(mover); + mover.move(direction); + } + ); + } + + private boolean blockedInThreeDirections(final Set blockedDirections) { + return blockedDirections.size() == 3; + } + + private boolean opposingDirectionsAreBlocked(final Set blockedDirections) { + return blockedDirections.size() == 2 && + ((blockedDirections.contains(BlockFace.NORTH) && blockedDirections.contains(BlockFace.SOUTH)) || + (blockedDirections.contains(BlockFace.EAST) && blockedDirections.contains(BlockFace.WEST))); + } + + private boolean isPotentialBridgeBlock(final Material material) { + return this.options.getBridgeMaterials().contains(material); + } + + private boolean isBlockBridgePowerBlock(final Material material) { + return this.options.isAllPowerBlocksAllowed() || this.options.getBridgePowerBlocks().contains(material); + } + + private boolean isPowerOnOrOffEvent(final BlockRedstoneEvent event) { + return event.getOldCurrent() == 0 || !(event.getNewCurrent() == 0); + } + +} diff --git a/src/main/java/xyz/dcaron/bridges/BridgeMover.java b/src/main/java/xyz/dcaron/bridges/BridgeMover.java new file mode 100644 index 0000000..7983341 --- /dev/null +++ b/src/main/java/xyz/dcaron/bridges/BridgeMover.java @@ -0,0 +1,287 @@ +package xyz.dcaron.bridges; + +import java.awt.Point; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Level; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.type.Slab; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.scheduler.BukkitScheduler; + +import lombok.Getter; +import lombok.Setter; + +public class BridgeMover implements Runnable { + + @Getter + @Setter + private Bridge bridge; + private final BridgeOptions options; + + private BlockFace direction = null; + private int taskId = 0, boosts, movingDelay; + + public BridgeMover(final Bridge bridge, final BridgeOptions options) { + this.bridge = bridge; + this.options = options; + } + + public void move(final BlockFace direction) { + if (direction != this.direction) { + this.direction = direction; + this.movingDelay = this.options.getTicksPerBridgeMovement(); + } else if (this.boosts < this.options.getMaximumMultiplePowerBoost()) { + this.movingDelay /= 2; + if (this.movingDelay < 1) { + this.movingDelay = 1; + } + this.boosts++; + } + + BukkitScheduler scheduler = BridgesPlugin.getPlugin().getServer().getScheduler(); + if (this.taskId != 0) { + scheduler.cancelTask(this.taskId); + } + this.taskId = scheduler.scheduleSyncRepeatingTask(BridgesPlugin.getPlugin(), + this, Math.max(this.movingDelay / 2, 1), this.movingDelay); + } + + @Override + public void run() { + if (!tryMoveBridge()) { + BridgesPlugin.getPlugin().getServer().getScheduler().cancelTask(this.taskId); + this.taskId = 0; + this.direction = null; + } + } + + private boolean tryMoveBridge() { + final World world = BridgesPlugin.getPlugin().getServer().getWorld(bridge.getWorldName()); + if (world == null) { + BridgesPlugin.log("World of name " + bridge.getWorldName() + " is missing!", Level.FINE); + return false; + } + + Set chunkCoordinates = getChunkCoords(bridge.getX(), bridge.getZ(), bridge.getWidth(), bridge.getHeight()); + if (!this.areAllChunksLoaded(world, chunkCoordinates)) { + BridgesPlugin.log("Chunks not loaded, cancelling bridge move!", Level.FINE); + return false; + } + + if (!this.isBridgeWhole(world)) { + BridgesPlugin.log("Bridge is no longer valid, cancelling bridge move!", Level.FINE); + return false; + } + + if (!this.tryToMove(direction, world, chunkCoordinates)) { + return false; + } + + return true; + } + + private boolean bridgeFloatingChecks(final int dx, final int dz, final World world) { + boolean floatingOnAir = true; + final int ddx = (int) Math.signum(dx); + final int ddz = (int) Math.signum(dz); + for (int x = bridge.getX() + ddx; x < bridge.getX() + ddx + bridge.getWidth(); x++) { + for (int z = bridge.getZ() + ddz; z < bridge.getZ() + ddz + bridge.getHeight(); z++) { + final Material materialBeneathBridge = world.getBlockAt(x, bridge.getY() - 1, z).getType(); + + if (!materialBeneathBridge.isAir()) { + floatingOnAir = false; + } + } + } + + if (floatingOnAir && !this.options.isAllowFloatingBridges()) { + BridgesPlugin.log("No solid block beneath bridge and floating not allowed", Level.FINE); + return false; + } + + return true; + } + + private boolean tryToMove(final BlockFace direction, final World world, final Set chunksLoaded) { + int length, dx = 0, dz = 0, xMult = 0, zMult = 0; + switch (direction) { + case WEST: + length = bridge.getHeight(); + dx = -1; + zMult = 1; + break; + case NORTH: + length = bridge.getWidth(); + dz = -1; + xMult = 1; + break; + case EAST: + length = bridge.getHeight(); + dx = bridge.getWidth(); + zMult = 1; + break; + case SOUTH: + length = bridge.getWidth(); + dz = bridge.getHeight(); + xMult = 1; + break; + default: + throw new AssertionError(direction); + } + + for (int i = 0; i < length; i++) { + final Material blockType = world.getBlockAt(bridge.getX() + dx + i * xMult, bridge.getY(), bridge.getZ() + dz + i * zMult).getType(); + if (!blockType.isAir()) { + BridgesPlugin.log("Not enough room besides bridge; blocked by " + blockType.toString(), Level.FINE); + return false; + } + } + + if (!bridgeFloatingChecks(dx, dz, world)) { + return false; + } + + // Its bridge moving time. + BridgesPlugin.log("Moving bridge " + direction + " one row", Level.FINE); + for (int i = 0; i < length; i++) { + world.getBlockAt(bridge.getX() + dx + i * xMult, bridge.getY(), bridge.getZ() + dz + i * zMult).setType(bridge.getType()); + } + + // Moves entities standing on the bridge. + tryMoveEntities(world, chunksLoaded); + + if (dx == -1) { + dx = bridge.getWidth() - 1; + } else if (dx == bridge.getWidth()) { + dx = 0; + } else if (dz == -1) { + dz = bridge.getHeight() - 1; + } else { + dz = 0; + } + + for (int i = 0; i < length; i++) { + // Set block behind to air. + world.getBlockAt(bridge.getX() + dx + i * xMult, bridge.getY(), bridge.getZ() + dz + i * zMult).setType(Material.AIR); + } + + bridge.setX(bridge.getX() + direction.getModX()); + bridge.setZ(bridge.getZ() + direction.getModZ()); + + return true; + } + + private void tryMoveEntities(final World world, final Set chunksLoaded) { + if (this.options.isMoveEntitiesOnBridge()) { + chunksLoaded.stream() + .map(point -> world.getChunkAt(point.x, point.y)) + .map(chunk -> Arrays.asList(chunk.getEntities())) + .flatMap(Collection::stream) + .forEach(entity -> { + if (this.isOnBridge(entity.getLocation()) && isSpaceToMove(world, entity, entity.getLocation())) { + final Location location = entity.getLocation(); + location.setX(entity.getLocation().getX() + direction.getModX()); + location.setZ(entity.getLocation().getZ() + direction.getModZ()); + entity.teleport(location); + } + }); + } + } + + private boolean isSpaceToMove(final World world, final Entity entity, final Location location) { + final int newBlockX = location.getBlockX() + direction.getModX(); + final int newBlockY = location.getBlockY() + direction.getModY() + ((this.materialIsBottomSlab(world.getBlockAt(location))) ? 1 : 0); + final int newBlockZ = location.getBlockZ() + direction.getModZ(); + + if (!world.getBlockAt(newBlockX, newBlockY, newBlockZ).getType().isAir()) { + return false; + } + + if (entity instanceof LivingEntity && ((LivingEntity) entity).getEyeHeight(true) > 1.0) { + // Check the block above for double high entities. + if (!world.getBlockAt(newBlockX, newBlockY + 1, newBlockZ).getType().isAir()) { + return false; + } + } + + return true; + } + + private boolean materialIsBottomSlab(final Block block) { + final BlockData data = block.getBlockData(); + return (data instanceof Slab && ((Slab) data).getType().equals(Slab.Type.BOTTOM)); + } + + private boolean isOnBridge(final Location location) { + final float dy = (materialIsBottomSlab(location.getBlock().getRelative(BlockFace.SOUTH))) ? 0.5f : 1.0f; + return location.getX() >= bridge.getX() && location.getX() < bridge.getX() + bridge.getWidth() && + location.getZ() >= bridge.getZ() && location.getZ() < bridge.getZ() + bridge.getHeight() && + location.getY() >= bridge.getY() + dy && location.getY() < bridge.getY() + dy + 0.25; + } + + private boolean isBridgeWhole(final World world) { + int bridgeX1 = bridge.getX(), bridgeX2 = bridgeX1 + bridge.getWidth(); + int bridgeZ1 = bridge.getZ(), bridgeZ2 = bridgeZ1 + bridge.getHeight(); + final Material bridgeMaterial = bridge.getType(); + + for (int x = bridgeX1; x < bridgeX2; x++) { + for (int z = bridgeZ1; z < bridgeZ2; z++) { + final Block block = world.getBlockAt(x, bridge.getY(), z); + if (!block.getType().equals(bridgeMaterial)) { + BridgesPlugin.log("Bridge is no longer valid, cancelling bridge move!", Level.FINE); + return false; + } + } + } + + return true; + } + + private boolean areAllChunksLoaded(final World world, final Set chunkCoords) { + return !chunkCoords.stream() + .anyMatch(point -> !world.isChunkLoaded(point.x, point.y)); + } + + private Set getChunkCoords(int x, int z, int width, int height) { + switch (direction) { + case WEST: + x--; + width++; + break; + case NORTH: + z--; + height++; + break; + case EAST: + width++; + break; + case SOUTH: + height++; + break; + default: + break; + } + + Set chunkCoords = new HashSet(); + for (int u = x; u <= (x + width - 1); u += 16) { + for (int v = z; v <= (z + height - 1); v += 16) { + chunkCoords.add(new Point(u >> 4, v >> 4)); + } + chunkCoords.add(new Point(u >> 4, (z + height - 1) >> 4)); + } + chunkCoords.add(new Point((x + width - 1) >> 4, (z + height - 1) >> 4)); + + return chunkCoords; + } + +} diff --git a/src/main/java/xyz/dcaron/bridges/BridgeOptions.java b/src/main/java/xyz/dcaron/bridges/BridgeOptions.java new file mode 100644 index 0000000..51492b5 --- /dev/null +++ b/src/main/java/xyz/dcaron/bridges/BridgeOptions.java @@ -0,0 +1,64 @@ +package xyz.dcaron.bridges; + +import java.util.Set; +import java.util.stream.Collectors; + +import org.bukkit.Material; +import org.bukkit.configuration.file.FileConfiguration; + +import lombok.Getter; + +public class BridgeOptions { + + @Getter + private final boolean moveEntitiesOnBridge; + @Getter + private final int ticksPerBridgeMovement; + @Getter + private final Set bridgeMaterials; + @Getter + private final int maximumMultiplePowerBoost; + @Getter + private final boolean allowFloatingBridges; + @Getter + private final Set bridgePowerBlocks; + @Getter + private final boolean allPowerBlocksAllowed; + + public BridgeOptions(final FileConfiguration configuration) { + + moveEntitiesOnBridge = configuration.getBoolean("moveEntitiesOnBridge"); + + ticksPerBridgeMovement = configuration.getInt("ticksPerBridgeMovement"); + + bridgeMaterials = configuration.getStringList("bridgeMaterials") + .stream() + .map(material -> Material.getMaterial(material)) + .collect(Collectors.toUnmodifiableSet()); + + maximumMultiplePowerBoost = configuration.getInt("maximumMultiplePowerBoost"); + + allowFloatingBridges = configuration.getBoolean("allowFloatingBridges"); + + bridgePowerBlocks = configuration.getStringList("bridgePowerBlocks") + .stream() + .map(material -> Material.getMaterial(material)) + .collect(Collectors.toUnmodifiableSet()); + + allPowerBlocksAllowed = bridgePowerBlocks.isEmpty(); + } + + public String getOptionsPrintable() { + final StringBuilder sb = new StringBuilder(); + sb.append("\n"); + sb.append("moveEntitiesOnBridge: ").append(moveEntitiesOnBridge).append("\n"); + sb.append("ticksPerBridgeMovement: ").append(ticksPerBridgeMovement).append("\n"); + sb.append("bridgeMaterials: ").append(bridgeMaterials.toString()).append("\n"); + sb.append("maximumMultiplePowerBoost: ").append(maximumMultiplePowerBoost).append("\n"); + sb.append("allowFloatingBridges: ").append(allowFloatingBridges).append("\n"); + sb.append("bridgePowerBlocks: ").append(bridgePowerBlocks.toString()).append("\n"); + sb.append("allPowerBlocksAllowed: ").append(allPowerBlocksAllowed); + return sb.toString(); + } + +} diff --git a/src/main/java/xyz/dcaron/bridges/BridgesPlugin.java b/src/main/java/xyz/dcaron/bridges/BridgesPlugin.java new file mode 100644 index 0000000..f3f0cc4 --- /dev/null +++ b/src/main/java/xyz/dcaron/bridges/BridgesPlugin.java @@ -0,0 +1,79 @@ +package xyz.dcaron.bridges; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginManager; +import org.bukkit.plugin.java.JavaPlugin; + +public class BridgesPlugin extends JavaPlugin { + + private static Plugin plugin; + + public static Plugin getPlugin() { + return BridgesPlugin.plugin; + } + + private static final Logger LOGGER = Logger.getLogger("Minecraft.xyz.dcaron.bridges"); + private static final String CONFIG_YML = "config.yml"; + + public static void log(final String message, final Level level) { + if (BridgesPlugin.LOGGER.isLoggable(level)) { + BridgesPlugin.LOGGER.log(level, "[RetractableBridges] " + message); + } + } + + @Override + public void onDisable() { + BridgesPlugin.log("Plugin disabled.", Level.INFO); + } + + @Override + public void onEnable() { + BridgesPlugin.log("Plugin enabled.", Level.INFO); + BridgesPlugin.plugin = this; + + final BridgeOptions bridgeOptions = readConfigurationFiles(); + BridgesPlugin.log(bridgeOptions.getOptionsPrintable(), Level.INFO); + + final PluginManager pluginManager = super.getServer().getPluginManager(); + pluginManager.registerEvents(new BridgeBlockListener(bridgeOptions), this); + } + + private BridgeOptions readConfigurationFiles() { + final File configurationFile = new File(super.getDataFolder(), CONFIG_YML); + firstRun(configurationFile); + final FileConfiguration configData = YamlConfiguration.loadConfiguration(configurationFile); + return new BridgeOptions(configData); + } + + private void firstRun(final File configurationFile) { + if (!configurationFile.exists()) { + configurationFile.getParentFile().mkdirs(); + copyFile(getResource(CONFIG_YML), configurationFile); + } + } + + private void copyFile(InputStream in, File file) { + try { + final OutputStream out = new FileOutputStream(file); + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + out.close(); + in.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..710bc2f --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,99 @@ +# Whether or not to move entities on the bridge along: +moveEntitiesOnBridge: true + +# The speed of the bridge. The number is the number of "ticks" between +# movements of the bridge. A tick is 1/20 second. In other words, the +# default value of 30 means 1.5 seconds between movements. +# +# If you set this too low your server load may increase exponentially, +# especially if you have many bridges on your server! +# +# Also don't forget that this is a server wide setting for all bridges. +# Make sure that your users have a say and know about it if you change +# this setting: +ticksPerBridgeMovement: 30 + +# The allowed materials for the bridge. This is a list of block materials. +# You can find a list of all possible block materials at +# https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Material.html +# +# Be very careful when adding block materials to this list. You don't want any +# flat surface which just happens to be next to a bit of redstone to +# start flying around! +# +# Also don't forget that this is a server wide setting for all bridges. +# Make sure that your users have a say and know about it if you change +# this setting: +bridgeMaterials: [ + COBBLESTONE_SLAB, + DARK_OAK_SLAB, + DIORITE_SLAB, + ANDESITE_SLAB, + GRANITE_SLAB, + JUNGLE_SLAB, + NETHER_BRICK_SLAB, + OAK_SLAB, + QUARTZ_SLAB, + SPRUCE_SLAB, + STONE_BRICK_SLAB, + STONE_SLAB, + BIRCH_SLAB, + ACACIA_SLAB, + MANGROVE_SLAB, + SMOOTH_STONE_SLAB +] + +# The maximum number of speed boosts allowed by having multiple power +# blocks (the support blocks beneath the bridge through which the +# redstone power is delivered to the bridge). +# +# If you set this too high your server load may increase exponentially, +# especially if you have many bridges on your server! +# +# Also don't forget that this is a server wide setting for all bridges. +# Make sure that your users have a say and know about it if you change +# this setting: +maximumMultiplePowerBoost: 2 + +# Whether to allow "floating" bridges; which in this case specifically +# means bridges that are not touching any solid blocks directly +# underneath. You can use this to prevent people from having bridges fly +# away uncontrolled, either by accident or on purpose. +# +# Note that water or lava counts as a solid block for this purpose, so +# you can still create ferries if you disable this. +# +# If you set this to "false", bridges will stop moving if they would +# lose contact with all solid blocks directly underneath them. In other +# words, it will always remain in contact with at least one solid block +# underneath. +# +# Note that it can still lose contact with its power block(s). This means +# that it is still possible to bridge large distances or to have two +# power blocks at the extreme ends of the bridge's travel. +# +# Also don't forget that this is a server wide setting for all bridges. +# Make sure that your users have a say and know about it if you change +# this setting: +allowFloatingBridges: true + +# The allowed materials for the power blocks (the support blocks beneath +# the bridge through which the redstone power is delivered to the +# bridge). This is a list of block materials. You can find a list of all +# possible block materials at +# https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Material.html. +# If it is empty (the default), any solid block is allowed. +# +# By default, the power blocks can be of any solid type. This setting allows +# you to restrict that, in order to make it harder to build bridges. It +# may also slightly improve the performance of the plugin. +# +# The value is a list of block materials between brackets, separated by +# commas. For instance, if you want to limit power blocks to diamond or +# dirt blocks, use the value [DIAMOND_BLOCK, DIRT]. If you want to allow any +# solid block material, the list should be empty. +# +# Don't forget that this is a server wide setting for all bridges. Make +# sure that your users have a say and know about it if you change this +# setting: +bridgePowerBlocks: [] \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..51faa65 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,4 @@ +name: RetractableBridges +version: 1.0.0 +main: xyz.dcaron.bridges.BridgesPlugin +api-version: 1.19 \ No newline at end of file diff --git a/src/test/java/xyz/dcaron/bridges/AppTest.java b/src/test/java/xyz/dcaron/bridges/AppTest.java new file mode 100644 index 0000000..49620f9 --- /dev/null +++ b/src/test/java/xyz/dcaron/bridges/AppTest.java @@ -0,0 +1,14 @@ +package xyz.dcaron.bridges; + +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class AppTest +{ + @Test + public void shouldAnswerWithTrue() + { + assertTrue( true ); + } +}