diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandGoCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandGoCommand.java index 37e1719e0..aae461e8a 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandGoCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandGoCommand.java @@ -7,6 +7,7 @@ import org.apache.commons.lang.math.NumberUtils; import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.lists.Flags; /** * @author tastybento @@ -31,6 +32,12 @@ public class IslandGoCommand extends CompositeCommand { user.sendMessage("general.errors.no-island"); return false; } + if ((getIWM().inWorld(user.getWorld()) && Flags.PREVENT_TELEPORT_WHEN_FALLING.isSetForWorld(user.getWorld())) + && user.getPlayer().getFallDistance() > 0) { + // We're sending the "hint" to the player to tell them they cannot teleport while falling. + user.sendMessage(Flags.PREVENT_TELEPORT_WHEN_FALLING.getHintReference()); + return true; + } if (!args.isEmpty() && NumberUtils.isDigits(args.get(0))) { int homeValue = Integer.parseInt(args.get(0)); int maxHomes = user.getPermissionValue(getPermissionPrefix() + "island.maxhomes", getIWM().getMaxHomes(getWorld())); diff --git a/src/main/java/world/bentobox/bentobox/api/flags/Flag.java b/src/main/java/world/bentobox/bentobox/api/flags/Flag.java index 4abb21fc6..7ab37abc8 100644 --- a/src/main/java/world/bentobox/bentobox/api/flags/Flag.java +++ b/src/main/java/world/bentobox/bentobox/api/flags/Flag.java @@ -280,7 +280,7 @@ public class Flag implements Comparable { private Type type = Type.PROTECTION; // Default settings - private boolean defaultSetting; + private boolean defaultSetting = false; private int defaultRank = RanksManager.MEMBER_RANK; // ClickHandler - default depends on the type diff --git a/src/main/java/world/bentobox/bentobox/lists/Flags.java b/src/main/java/world/bentobox/bentobox/lists/Flags.java index cbe83eb8b..9013e61e9 100644 --- a/src/main/java/world/bentobox/bentobox/lists/Flags.java +++ b/src/main/java/world/bentobox/bentobox/lists/Flags.java @@ -95,7 +95,7 @@ public final class Flags { public static final Flag COLLECT_LAVA = new Flag.Builder("COLLECT_LAVA", Material.LAVA_BUCKET).build(); public static final Flag COLLECT_WATER = new Flag.Builder("COLLECT_WATER", Material.WATER_BUCKET).build(); public static final Flag MILKING = new Flag.Builder("MILKING", Material.MILK_BUCKET).build(); - public static final Flag FISH_SCOOPING = new Flag.Builder("FISH_SCOOPING", Material.TROPICAL_FISH_BUCKET).defaultSetting(false).build(); + public static final Flag FISH_SCOOPING = new Flag.Builder("FISH_SCOOPING", Material.TROPICAL_FISH_BUCKET).build(); // Chorus Fruit and Enderpearls public static final Flag CHORUS_FRUIT = new Flag.Builder("CHORUS_FRUIT", Material.CHORUS_FRUIT).listener(new TeleportationListener()).build(); @@ -148,7 +148,7 @@ public final class Flags { public static final Flag EXPERIENCE_PICKUP = new Flag.Builder("EXPERIENCE_PICKUP", Material.EXPERIENCE_BOTTLE).listener(new ExperiencePickupListener()).build(); // TNT - public static final Flag TNT = new Flag.Builder("TNT", Material.TNT).listener(new TNTListener()).defaultSetting(false).build(); + public static final Flag TNT = new Flag.Builder("TNT", Material.TNT).listener(new TNTListener()).build(); // Island lock public static final Flag LOCK = new Flag.Builder("LOCK", Material.TRIPWIRE_HOOK).defaultSetting(true) @@ -177,9 +177,8 @@ public final class Flags { * World Settings - they apply to every island in the game worlds. */ - // World Settings - apply to every island in the game worlds public static final Flag ENDER_CHEST = new Flag.Builder("ENDER_CHEST", Material.ENDER_CHEST) - .defaultSetting(false).type(Type.WORLD_SETTING) + .type(Type.WORLD_SETTING) .listener(new EnderChestListener()) .build(); @@ -207,7 +206,7 @@ public final class Flags { .listener(new RemoveMobsListener()).defaultSetting(true).build(); public static final Flag ITEM_FRAME_DAMAGE = new Flag.Builder("ITEM_FRAME_DAMAGE", Material.ITEM_FRAME).type(Type.WORLD_SETTING) - .listener(new ItemFrameListener()).defaultSetting(false).build(); + .listener(new ItemFrameListener()).build(); public static final Flag ISLAND_RESPAWN = new Flag.Builder("ISLAND_RESPAWN", Material.TORCH).type(Type.WORLD_SETTING) .listener(new IslandRespawnListener()).defaultSetting(true).build(); @@ -216,23 +215,25 @@ public final class Flags { .listener(new OfflineRedstoneListener()).defaultSetting(true).build(); public static final Flag CLEAN_SUPER_FLAT = new Flag.Builder("CLEAN_SUPER_FLAT", Material.BEDROCK).type(Type.WORLD_SETTING) - .listener(new CleanSuperFlatListener()).defaultSetting(false).build(); + .listener(new CleanSuperFlatListener()).build(); public static final Flag CHEST_DAMAGE = new Flag.Builder("CHEST_DAMAGE", Material.TRAPPED_CHEST).type(Type.WORLD_SETTING) - .listener(new ChestDamageListener()).defaultSetting(false).build(); + .listener(new ChestDamageListener()).build(); public static final Flag CREEPER_DAMAGE = new Flag.Builder("CREEPER_DAMAGE", Material.GREEN_SHULKER_BOX).listener(new CreeperListener()).type(Type.WORLD_SETTING) .defaultSetting(true).build(); /** * Prevents creeper griefing. This is where a visitor will trigger a creeper to blow up an island. */ public static final Flag CREEPER_GRIEFING = new Flag.Builder("CREEPER_GRIEFING", Material.CREEPER_HEAD).type(Type.WORLD_SETTING) - .defaultSetting(false).build(); + .build(); public static final Flag COMMAND_RANKS = new Flag.Builder("COMMAND_RANKS", Material.PLAYER_HEAD).type(Type.WORLD_SETTING) .clickHandler(new CommandRankClickListener()).usePanel(true).build(); public static final Flag COARSE_DIRT_TILLING = new Flag.Builder("COARSE_DIRT_TILLING", Material.COARSE_DIRT).type(Type.WORLD_SETTING).defaultSetting(true).listener(new CoarseDirtTillingListener()).build(); + public static final Flag PREVENT_TELEPORT_WHEN_FALLING = new Flag.Builder("PREVENT_TELEPORT_WHEN_FALLING", Material.FEATHER).type(Type.WORLD_SETTING).build(); + /** * @return List of all the flags in this class */ diff --git a/src/main/java/world/bentobox/bentobox/managers/AddonsManager.java b/src/main/java/world/bentobox/bentobox/managers/AddonsManager.java index 71cc709cc..b4ae91a0e 100644 --- a/src/main/java/world/bentobox/bentobox/managers/AddonsManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/AddonsManager.java @@ -6,7 +6,6 @@ import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Arrays; -import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; @@ -34,7 +33,6 @@ import world.bentobox.bentobox.api.events.addon.AddonEvent; */ public class AddonsManager { - private static final String LOCALE_FOLDER = "locales"; private List addons; private Map loaders; private final Map> classes = new HashMap<>(); @@ -126,26 +124,30 @@ public class AddonsManager { // try loading the addon // Get description in the addon.yml file YamlConfiguration data = addonDescription(jar); + // Load the addon AddonClassLoader addonClassLoader = new AddonClassLoader(this, data, f, this.getClass().getClassLoader()); + // Get the addon itself addon = addonClassLoader.getAddon(); + // Initialize some settings addon.setDataFolder(new File(f.getParent(), addon.getDescription().getName())); addon.setFile(f); - File localeDir = new File(plugin.getDataFolder(), LOCALE_FOLDER + File.separator + addon.getDescription().getName()); - // Obtain any locale files and save them - for (String localeFile : listJarFiles(jar, LOCALE_FOLDER, ".yml")) { - addon.saveResource(localeFile, localeDir, false, true); - } + // Locales + plugin.getLocalesManager().copyLocalesFromAddonJar(addon); plugin.getLocalesManager().loadLocalesFromFile(addon.getDescription().getName()); + // Fire the load event Bukkit.getPluginManager().callEvent(new AddonEvent().builder().addon(addon).reason(AddonEvent.Reason.LOAD).build()); + // Add it to the list of addons addons.add(addon); + // Add to the list of loaders loaders.put(addon, addonClassLoader); + // Run the onLoad. addon.onLoad(); // If this is a GameModeAddon create the worlds, register it and load the schems @@ -215,33 +217,6 @@ public class AddonsManager { classes.putIfAbsent(name, clazz); } - /** - * Lists files found in the jar in the folderPath with the suffix given - * @param jar - the jar file - * @param folderPath - the path within the jar - * @param suffix - the suffix required - * @return a list of files - */ - public List listJarFiles(JarFile jar, String folderPath, String suffix) { - List result = new ArrayList<>(); - - Enumeration entries = jar.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - String path = entry.getName(); - - if (!path.startsWith(folderPath)) { - continue; - } - - if (entry.getName().endsWith(suffix)) { - result.add(entry.getName()); - } - - } - return result; - } - private void sortAddons() { // Lists all available addons as names. List names = addons.stream().map(a -> a.getDescription().getName()).collect(Collectors.toList()); diff --git a/src/main/java/world/bentobox/bentobox/managers/LocalesManager.java b/src/main/java/world/bentobox/bentobox/managers/LocalesManager.java index ffe8c18db..307282a1f 100644 --- a/src/main/java/world/bentobox/bentobox/managers/LocalesManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/LocalesManager.java @@ -11,13 +11,16 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.jar.JarFile; import org.bukkit.configuration.file.YamlConfiguration; import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.addons.Addon; import world.bentobox.bentobox.api.localization.BentoBoxLocale; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.util.FileLister; +import world.bentobox.bentobox.util.Util; /** * @author tastybento, Poslovitch @@ -31,7 +34,7 @@ public class LocalesManager { public LocalesManager(BentoBox plugin) { this.plugin = plugin; - copyLocalesFromJar(BENTOBOX); + copyLocalesFromPluginJar(BENTOBOX); loadLocalesFromFile(BENTOBOX); // Default } @@ -101,12 +104,29 @@ public class LocalesManager { return result == null ? defaultText : result; } + /** + * Copies locale files from the addon jar to the file system + * @param addon - addon + */ + void copyLocalesFromAddonJar(Addon addon) { + try (JarFile jar = new JarFile(addon.getFile())) { + File localeDir = new File(plugin.getDataFolder(), LOCALE_FOLDER + File.separator + addon.getDescription().getName()); + if (!localeDir.exists()) { + localeDir.mkdirs(); + // Obtain any locale files and save them + Util.listJarFiles(jar, LOCALE_FOLDER, ".yml").forEach(lf -> addon.saveResource(lf, localeDir, false, true)); + } + } catch (Exception e) { + plugin.logError(e.getMessage()); + } + } + /** * Copies all the locale files from the plugin jar to the filesystem. * Only done if the locale folder does not already exist. * @param folderName - the name of the destination folder */ - private void copyLocalesFromJar(String folderName) { + private void copyLocalesFromPluginJar(String folderName) { // Run through the files and store the locales File localeDir = new File(plugin.getDataFolder(), LOCALE_FOLDER + File.separator + folderName); // If the folder does not exist, then make it and fill with the locale files from the jar @@ -210,9 +230,12 @@ public class LocalesManager { */ public void reloadLanguages() { languages.clear(); - copyLocalesFromJar(BENTOBOX); + copyLocalesFromPluginJar(BENTOBOX); loadLocalesFromFile(BENTOBOX); - plugin.getAddonsManager().getAddons().forEach(addon -> loadLocalesFromFile(addon.getDescription().getName())); + plugin.getAddonsManager().getAddons().forEach(addon -> { + copyLocalesFromAddonJar(addon); + loadLocalesFromFile(addon.getDescription().getName()); + }); } diff --git a/src/main/java/world/bentobox/bentobox/managers/SchemsManager.java b/src/main/java/world/bentobox/bentobox/managers/SchemsManager.java index 13d870a01..cdf6d763c 100644 --- a/src/main/java/world/bentobox/bentobox/managers/SchemsManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/SchemsManager.java @@ -17,6 +17,7 @@ import world.bentobox.bentobox.api.addons.Addon; import world.bentobox.bentobox.api.addons.GameModeAddon; import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.schems.Clipboard; +import world.bentobox.bentobox.util.Util; public class SchemsManager { @@ -43,7 +44,7 @@ public class SchemsManager { } // Save any schems that are in the jar try (JarFile jar = new JarFile(addon.getFile())) { - plugin.getAddonsManager().listJarFiles(jar, "schems", ".schem").forEach(name -> addon.saveResource(name, false)); + Util.listJarFiles(jar, "schems", ".schem").forEach(name -> addon.saveResource(name, false)); } catch (IOException e) { plugin.logError("Could not load schem files from addon jar " + e.getMessage()); } diff --git a/src/main/java/world/bentobox/bentobox/util/Util.java b/src/main/java/world/bentobox/bentobox/util/Util.java index 91847adc5..2a44b82cc 100644 --- a/src/main/java/world/bentobox/bentobox/util/Util.java +++ b/src/main/java/world/bentobox/bentobox/util/Util.java @@ -1,7 +1,10 @@ package world.bentobox.bentobox.util; import java.util.ArrayList; +import java.util.Enumeration; import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; import java.util.stream.Collectors; import org.bukkit.Bukkit; @@ -195,6 +198,33 @@ public class Util { return world.getEnvironment().equals(Environment.NORMAL) ? world : Bukkit.getWorld(world.getName().replaceAll(NETHER, "").replaceAll(THE_END, "")); } + /** + * Lists files found in the jar in the folderPath with the suffix given + * @param jar - the jar file + * @param folderPath - the path within the jar + * @param suffix - the suffix required + * @return a list of files + */ + public static List listJarFiles(JarFile jar, String folderPath, String suffix) { + List result = new ArrayList<>(); + + Enumeration entries = jar.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String path = entry.getName(); + + if (!path.startsWith(folderPath)) { + continue; + } + + if (entry.getName().endsWith(suffix)) { + result.add(entry.getName()); + } + + } + return result; + } + /** * Converts block face direction to radial degrees. Returns 0 if block face * is not radial. diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 17123beb5..ba11f5c47 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -728,6 +728,13 @@ protection: name: "Experience pickup" description: "Toggle experience orb pickup" hint: "cannot pickup experience orb" + PREVENT_TELEPORT_WHEN_FALLING: + name: "Prevent teleport when falling" + description: |- + &aPrevent players from teleporting + &aback to their island using commands + &aif they are falling. + hint: "&cYou cannot teleport back to your island while you are falling." locked: "&cThis island is locked!" protected: "&cIsland protected: [description]" spawn-protected: "&cSpawn protected: [description]" diff --git a/src/test/java/world/bentobox/bentobox/managers/LocalesManagerTest.java b/src/test/java/world/bentobox/bentobox/managers/LocalesManagerTest.java index aae3bb2e2..12562f100 100644 --- a/src/test/java/world/bentobox/bentobox/managers/LocalesManagerTest.java +++ b/src/test/java/world/bentobox/bentobox/managers/LocalesManagerTest.java @@ -9,13 +9,20 @@ import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.io.BufferedInputStream; import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; import org.bukkit.Bukkit; import org.bukkit.configuration.file.YamlConfiguration; @@ -79,7 +86,7 @@ public class LocalesManagerTest { */ @After public void cleanUp() throws Exception { - + // Delete locale folder File localeDir = new File(plugin.getDataFolder(), LOCALE_FOLDER); if (localeDir.exists()) { // Remove it @@ -90,6 +97,16 @@ public class LocalesManagerTest { } + // Delete addon folder + localeDir = new File(plugin.getDataFolder(), "addons"); + if (localeDir.exists()) { + // Remove it + Files.walk(localeDir.toPath()) + .map(Path::toFile) + .sorted((o1, o2) -> -o1.compareTo(o2)) + .forEach(File::delete); + + } } /** @@ -123,7 +140,7 @@ public class LocalesManagerTest { LocalesManager lm = new LocalesManager(plugin); assertNull(lm.get("test.test.test")); } - + /** * Test method for {@link world.bentobox.bentobox.managers.LocalesManager#getOrDefault(java.lang.String, java.lang.String)}. * @throws IOException @@ -170,7 +187,7 @@ public class LocalesManagerTest { when(user.getLocale()).thenReturn(Locale.US); assertEquals("test string", lm.get(user, "test.test")); } - + /** * Test method for {@link world.bentobox.bentobox.managers.LocalesManager#getOrDefault(world.bentobox.bentobox.api.user.User, java.lang.String, java.lang.String)}. * @throws IOException @@ -283,17 +300,98 @@ public class LocalesManagerTest { List none = new ArrayList<>(); Addon addon = mock(Addon.class); AddonDescription desc = new AddonDescription(); - desc.setName(BENTOBOX); + desc.setName("AcidIsland"); when(addon.getDescription()).thenReturn(desc); + // Create a tmp folder to jar up + File localeDir = new File(LOCALE_FOLDER); + localeDir.mkdirs(); + // Create a fake locale file for this jar + File english = new File(localeDir, Locale.US.toLanguageTag() + ".yml"); + YamlConfiguration yaml = new YamlConfiguration(); + yaml.set("test.test", "test string"); + yaml.save(english); + // Create a temporary jar file + File jar = new File("addons", "AcidIsland.jar"); + jar.getParentFile().mkdirs(); + Manifest manifest = new Manifest(); + manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); + JarOutputStream target = new JarOutputStream(new FileOutputStream("addons" + File.separator + "AcidIsland.jar"), manifest); + add(english, target); + target.close(); + // When the file is requested, return it + when(addon.getFile()).thenReturn(jar); none.add(addon); when(am.getAddons()).thenReturn(none); when(plugin.getAddonsManager()).thenReturn(am); makeFakeLocaleFile(); LocalesManager lm = new LocalesManager(plugin); + + // RELOAD!!! lm.reloadLanguages(); - Mockito.verify(addon).getDescription(); - File localeDir = new File(plugin.getDataFolder(), LOCALE_FOLDER + File.separator + BENTOBOX); - assertTrue(localeDir.exists()); + + // Verify that the resources have been saved (note that they are not actually saved because addon is a mock) + Mockito.verify(addon).saveResource( + Mockito.eq("locales/en-US.yml"), + Mockito.any(), + Mockito.eq(false), + Mockito.eq(true) + ); + + // Clean up + // Delete the temp folder we made. Other clean up is done globally + localeDir = new File(plugin.getDataFolder(), "AcidIsland"); + if (localeDir.exists()) { + // Remove it + Files.walk(localeDir.toPath()) + .map(Path::toFile) + .sorted((o1, o2) -> -o1.compareTo(o2)) + .forEach(File::delete); + } + + } + + private void add(File source, JarOutputStream target) throws IOException + { + BufferedInputStream in = null; + try + { + if (source.isDirectory()) + { + String name = source.getPath().replace("\\", "/"); + if (!name.isEmpty()) + { + if (!name.endsWith("/")) + name += "/"; + JarEntry entry = new JarEntry(name); + entry.setTime(source.lastModified()); + target.putNextEntry(entry); + target.closeEntry(); + } + for (File nestedFile: source.listFiles()) + add(nestedFile, target); + return; + } + + JarEntry entry = new JarEntry(source.getPath().replace("\\", "/")); + entry.setTime(source.lastModified()); + target.putNextEntry(entry); + in = new BufferedInputStream(new FileInputStream(source)); + + byte[] buffer = new byte[1024]; + while (true) + { + int count = in.read(buffer); + if (count == -1) + break; + target.write(buffer, 0, count); + } + target.closeEntry(); + } + finally + { + if (in != null) + in.close(); + } } /**