Uses current API to enable multiple schems.

This will load all schems in an add-on's schem folder if it exists and
associate them with this world set (overworld, nether and end). Schems
can be named anything, but the partner nether or end worlds must be
pre-fixed with "nether-" or "end-" in the filename.
Additional schems can be added by the admin into the schem folder, or
they can be stored in the jar file of the add-on. Both are supported.

No changes are required to current add-ons. I.e., there is no API
breakage here, but I would like to rename the SchemsManager method
loadIslands(World world) to be loadSchems(World world) in the future.

Related issues/PR:
https://github.com/BentoBoxWorld/BentoBox/issues/104
https://github.com/BentoBoxWorld/BentoBox/issues/207
https://github.com/BentoBoxWorld/BentoBox/issues/378
https://github.com/BentoBoxWorld/BentoBox/pull/408
This commit is contained in:
tastybento 2018-12-23 19:31:00 -08:00
parent c45250044f
commit 0adc2e3078
10 changed files with 163 additions and 81 deletions

View File

@ -286,10 +286,9 @@ public abstract class Addon {
/**
* Set the file that contains this addon
*
* @param f
* the file to set
* @param f the file to set
*/
public void setAddonFile(File f) {
public void setFile(File f) {
file = f;
}

View File

@ -2,9 +2,11 @@ package world.bentobox.bentobox.api.commands.island;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.events.island.IslandEvent.Reason;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.managers.island.NewIsland;
@ -27,6 +29,7 @@ public class IslandCreateCommand extends CompositeCommand {
public void setup() {
setPermission("island.create");
setOnlyPlayer(true);
setParametersHelp("commands.island.create.parameters");
setDescription("commands.island.create.description");
}
@ -45,11 +48,30 @@ public class IslandCreateCommand extends CompositeCommand {
}
user.sendMessage("commands.island.create.creating-island");
// Default schem is 'island'
String name = "island";
if (!args.isEmpty()) {
name = args.get(0).toLowerCase(java.util.Locale.ENGLISH);
// Permission check
String permission = this.getPermissionPrefix() + "island.create." + name;
if (!user.isOp() && !user.hasPermission(permission)) {
user.sendMessage("general.errors.no-permission", TextVariables.PERMISSION, permission);
return false;
}
// Check the schem name exists
Set<String> validNames = getPlugin().getSchemsManager().get(getWorld()).keySet();
if (!validNames.contains(name)) {
user.sendMessage("commands.island.create.unknown-schem");
return false;
}
}
try {
NewIsland.builder()
.player(user)
.world(getWorld())
.reason(Reason.CREATE)
.name(name)
.build();
return true;
} catch (IOException e) {

View File

@ -2,6 +2,7 @@ package world.bentobox.bentobox.api.commands.island;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import org.bukkit.GameMode;
import org.bukkit.entity.Player;
@ -24,6 +25,7 @@ public class IslandResetCommand extends ConfirmableCommand {
public void setup() {
setPermission("island.create");
setOnlyPlayer(true);
setParametersHelp("commands.island.reset.parameters");
setDescription("commands.island.reset.description");
}
@ -56,17 +58,35 @@ public class IslandResetCommand extends ConfirmableCommand {
user.sendMessage("commands.island.reset.resets-left", TextVariables.NUMBER, String.valueOf(resetsLeft));
}
}
// Default schem is 'island'
String name = args.isEmpty() ? "island" : args.get(0).toLowerCase(java.util.Locale.ENGLISH);
if (!args.isEmpty()) {
// Permission check
String permission = this.getPermissionPrefix() + "island.create." + name;
if (!user.isOp() && !user.hasPermission(permission)) {
user.sendMessage("general.errors.no-permission", TextVariables.PERMISSION, permission);
return false;
}
// Check the schem name exists
Set<String> validNames = getPlugin().getSchemsManager().get(getWorld()).keySet();
if (!validNames.contains(name)) {
user.sendMessage("commands.island.create.unknown-schem");
return false;
}
}
// Request confirmation
if (getSettings().isResetConfirmation()) {
this.askConfirmation(user, () -> resetIsland(user));
this.askConfirmation(user, () -> resetIsland(user, name));
return true;
} else {
return resetIsland(user);
return resetIsland(user, name);
}
}
private boolean resetIsland(User user) {
private boolean resetIsland(User user, String name) {
// Reset the island
Player player = user.getPlayer();
player.setGameMode(GameMode.SPECTATOR);
@ -92,6 +112,7 @@ public class IslandResetCommand extends ConfirmableCommand {
.player(user)
.reason(Reason.RESET)
.oldIsland(oldIsland)
.name(name)
.build();
} catch (IOException e) {
getPlugin().logError("Could not create island for player. " + e.getMessage());

View File

@ -131,11 +131,11 @@ public class AddonsManager {
addon = addonClassLoader.getAddon();
// Initialize some settings
addon.setDataFolder(new File(f.getParent(), addon.getDescription().getName()));
addon.setAddonFile(f);
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 : listJarYamlFiles(jar, LOCALE_FOLDER)) {
for (String localeFile : listJarFiles(jar, LOCALE_FOLDER, ".yml")) {
addon.saveResource(localeFile, localeDir, false, true);
}
plugin.getLocalesManager().loadLocalesFromFile(addon.getDescription().getName());
@ -206,12 +206,13 @@ public class AddonsManager {
}
/**
* Lists all the yml files found in the jar in the folder
* 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<String> listJarYamlFiles(JarFile jar, String folderPath) {
public List<String> listJarFiles(JarFile jar, String folderPath, String suffix) {
List<String> result = new ArrayList<>();
Enumeration<JarEntry> entries = jar.entries();
@ -223,7 +224,7 @@ public class AddonsManager {
continue;
}
if (entry.getName().endsWith(".yml")) {
if (entry.getName().endsWith(suffix)) {
result.add(entry.getName());
}

View File

@ -52,12 +52,12 @@ public class LocalesManager {
// No translation could be gotten from the player's locale, trying more generic solutions
return get(reference);
}
/**
* Gets the translated String corresponding to the reference from the locale file for this user.
* @param user the User
* @param reference a reference that can be found in a locale file
* @param default to return if the reference cannot be found anywhere
* @param defaultText to return if the reference cannot be found anywhere
* @return the translated String from the User's locale or from the server's locale or from the en-US locale, or null.
*/
public String getOrDefault(User user, String reference, String defaultText) {
@ -88,12 +88,12 @@ public class LocalesManager {
}
return null;
}
/**
* Gets the translated String corresponding to the reference from the server's or the en-US locale file
* or if it cannot be found anywhere, use the default text supplied.
* @param reference a reference that can be found in a locale file
* @param default text to return if the reference cannot be found anywhere
* @param defaultText text to return if the reference cannot be found anywhere
* @return the translated String from the server's locale or from the en-US locale, or default.
*/
public String getOrDefault(String reference, String defaultText) {

View File

@ -1,10 +1,13 @@
package world.bentobox.bentobox.managers;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Objects;
import java.util.jar.JarFile;
import org.bukkit.World;
import org.bukkit.configuration.InvalidConfigurationException;
@ -17,9 +20,7 @@ import world.bentobox.bentobox.schems.Clipboard;
public class SchemsManager {
private BentoBox plugin;
private Map<World, Clipboard> islandSchems;
private static final String SCHEM = ".schem";
private Map<World, Map<String, Clipboard>> islandSchems;
/**
* @param plugin - plugin
@ -29,89 +30,108 @@ public class SchemsManager {
islandSchems = new HashMap<>();
}
/**
* @param schems - schems folder for either the addon or the plugin
* @param world - world
* @param name - name of the schem to save (excluding .schem)
*/
private void copySchems(File schems, World world, String name) {
private void copySchems(Addon addon, File schems) {
if (schems.exists()) {
// If the folder exists, do not copy anything from the jar
return;
}
if (!schems.exists() && !schems.mkdirs()) {
plugin.logError("Could not make schems folder!");
return;
}
File schem = new File(schems, name + SCHEM);
if (schem.exists()) {
// No overwriting
return;
// Save any schems that
try (JarFile jar = new JarFile(addon.getFile())) {
plugin.getAddonsManager().listJarFiles(jar, "schems", ".schem").forEach(name -> {
addon.saveResource("schems/" + name, false);
});
} catch (IOException e) {
plugin.logError("Could not load schem files from addon jar " + e.getMessage());
}
Optional<Addon> addon = plugin.getIWM().getAddon(world);
if (addon.isPresent()) {
addon.get().saveResource("schems/" + name + SCHEM, false);
} else {
plugin.saveResource("schems/" + name + SCHEM, false);
}
}
public Clipboard get(World world) {
return islandSchems.get(world);
}
/**
* Load schems for world. Will try and load nether and end schems too if settings are set.
* Get all the schems for this world
* @param world world
* @return map of schems for this world or an empty map if there are none registered
*/
public Map<String, Clipboard> get(World world) {
return islandSchems.getOrDefault(world, new HashMap<>());
}
/**
* Load schems for addon. Will try and load nether and end schems too if settings are set.
* @param world - world
*/
public void loadIslands(World world) {
if (!plugin.getSchemsManager().loadSchem(world, "island")) {
plugin.logError("Could not load island.schem for " + plugin.getIWM().getFriendlyName(world));
}
if (plugin.getIWM().isNetherGenerate(world) && plugin.getIWM().isNetherIslands(world)
&& !plugin.getSchemsManager().loadSchem(plugin.getIWM().getNetherWorld(world), "nether-island")) {
plugin.logError("Could not load nether-island.schem for " + plugin.getIWM().getFriendlyName(world));
}
if (plugin.getIWM().isEndGenerate(world) && plugin.getIWM().isEndIslands(world)
&& !plugin.getSchemsManager().loadSchem(plugin.getIWM().getEndWorld(world), "end-island")) {
plugin.logError("Could not load end-island.schem for " + plugin.getIWM().getFriendlyName(world));
}
plugin.getIWM().getAddon(world).ifPresent(addon -> {
File schems = new File(addon.getDataFolder(), "schems");
// Copy any schems fould in the jar
copySchems(addon, schems);
// Load all schems in folder
// Look through the folder
FilenameFilter schemFilter = (File dir, String name) -> name.toLowerCase(java.util.Locale.ENGLISH).endsWith(".schem")
&& !name.toLowerCase(java.util.Locale.ENGLISH).startsWith("nether-")
&& !name.toLowerCase(java.util.Locale.ENGLISH).startsWith("end-");
Arrays.stream(Objects.requireNonNull(schems.list(schemFilter))).map(name -> name.substring(0, name.length() - 6)).forEach(name -> {
if (!plugin.getSchemsManager().loadSchem(world, schems, name)) {
plugin.logError("Could not load " + name + ".schem for " + plugin.getIWM().getFriendlyName(world));
}
if (plugin.getIWM().isNetherGenerate(world) && plugin.getIWM().isNetherIslands(world)
&& !plugin.getSchemsManager().loadSchem(plugin.getIWM().getNetherWorld(world), schems, "nether-" + name)) {
plugin.logError("Could not load nether-" + name + ".schem for " + plugin.getIWM().getFriendlyName(world));
}
if (plugin.getIWM().isEndGenerate(world) && plugin.getIWM().isEndIslands(world)
&& !plugin.getSchemsManager().loadSchem(plugin.getIWM().getEndWorld(world), schems, "end-" + name)) {
plugin.logError("Could not load end-" + name + ".schem for " + plugin.getIWM().getFriendlyName(world));
}
});
});
}
private boolean loadSchem(World world, String name) {
private boolean loadSchem(World world, File schems, String name) {
plugin.log("Loading " + name + ".schem for " + world.getName());
File schems = new File(plugin.getIWM().getDataFolder(world), "schems");
copySchems(schems, world, name);
Map<String, Clipboard> schemList = islandSchems.getOrDefault(world, new HashMap<>());
try {
Clipboard cb = new Clipboard(plugin, schems);
cb.load(name);
islandSchems.put(world, cb);
schemList.put(name, cb);
islandSchems.put(world, schemList);
} catch (IOException | InvalidConfigurationException e) {
plugin.logError("Could not load " + name + " schem");
plugin.logError("Could not load " + name + " schem, skipping!");
return false;
}
return true;
}
/**
* Paste the schem to world for island
* @param world - world
* @param island - island
* @param name - file name of schematic (without the .schem suffix)
*/
public void paste(World world, Island island, String name) {
paste(world, island, name, null);
}
/**
* Paste the schem for world to the island center location and run task afterwards
* @param world - world to paste to
* @param island - the island who owns this schem
* @param name - file name of schematic (without the .schem suffix)
* @param task - task to run after pasting is completed
*/
public void paste(World world, Island island, Runnable task) {
if (islandSchems.containsKey(world)) {
islandSchems.get(world).pasteIsland(world, island, task);
public void paste(World world, Island island, String name, Runnable task) {
if (islandSchems.containsKey(world) && islandSchems.get(world).containsKey(name)) {
islandSchems.get(world).get(name).pasteIsland(world, island, task);
} else {
plugin.logError("Tried to paste schem for " + world.getName() + " but the schem is not loaded!");
plugin.logError("Tried to paste schem '" + name + "' for " + world.getName() + " but the schem is not loaded!");
plugin.log("This might be due to an invalid schem format. Keep in mind that schems are not schematics.");
}
}
/**
* Paste the schem to world for island
* @param world - world
* @param island - island
*/
public void paste(World world, Island island) {
paste(world, island, null);
}
}

View File

@ -30,6 +30,8 @@ public class NewIsland {
private final User user;
private final Reason reason;
private final World world;
private final String name;
private enum Result {
ISLAND_FOUND,
BLOCK_AT_CENTER,
@ -37,12 +39,13 @@ public class NewIsland {
FREE
}
private NewIsland(Island oldIsland, User user, Reason reason, World world) {
private NewIsland(Island oldIsland, User user, Reason reason, World world, String name) {
super();
plugin = BentoBox.getInstance();
this.user = user;
this.reason = reason;
this.world = world;
this.name = name;
newIsland();
if (oldIsland != null) {
// Delete the old island
@ -75,6 +78,7 @@ public class NewIsland {
private User user2;
private Reason reason2;
private World world2;
private String name2 = "island";
public Builder oldIsland(Island oldIsland) {
this.oldIsland2 = oldIsland;
@ -98,9 +102,17 @@ public class NewIsland {
return this;
}
/**
* @param name - filename of schematic
*/
public Builder name(String name) {
this.name2 = name;
return this;
}
public Island build() throws IOException {
if (user2 != null) {
NewIsland newIsland = new NewIsland(oldIsland2, user2, reason2, world2);
NewIsland newIsland = new NewIsland(oldIsland2, user2, reason2, world2, name2);
return newIsland.getIsland();
}
throw new IOException("Insufficient parameters. Must have a schematic and a player");
@ -142,7 +154,7 @@ public class NewIsland {
return;
}
// Create island
plugin.getSchemsManager().paste(world, island, () -> {
plugin.getSchemsManager().paste(world, island, name, () -> {
// Set initial spawn point if one exists
if (island.getSpawnPoint(Environment.NORMAL) != null) {
plugin.getPlayers().setHomeLocation(user, island.getSpawnPoint(Environment.NORMAL), 1);
@ -156,12 +168,12 @@ public class NewIsland {
});
// Make nether island
if (plugin.getIWM().isNetherGenerate(world) && plugin.getIWM().isNetherIslands(world) && plugin.getIWM().getNetherWorld(world) != null) {
plugin.getSchemsManager().paste(plugin.getIWM().getNetherWorld(world), island);
plugin.getSchemsManager().paste(plugin.getIWM().getNetherWorld(world), island, "nether-" + name);
}
// Make end island
if (plugin.getIWM().isEndGenerate(world) && plugin.getIWM().isEndIslands(world) && plugin.getIWM().getEndWorld(world) != null) {
plugin.getSchemsManager().paste(plugin.getIWM().getEndWorld(world), island);
plugin.getSchemsManager().paste(plugin.getIWM().getEndWorld(world), island, "end-" + name);
}
// Set default settings

View File

@ -240,16 +240,19 @@ commands:
spawn:
description: "teleport you to the spawn"
create:
description: "create an island"
description: "create an island, using optional schem (requires permission)"
parameters: "<schem>"
too-many-islands: "&cThere are too many islands in this world: there isn't enough room for yours to be created."
unable-create-island: "&cYour island could not be generated, please contact an administrator."
creating-island: "&aCreating your island..."
pick-world: "&cPick a world from [worlds]."
unknown-schem: "&cThat schem has not been loaded yet."
info:
description: "display info about your island or the player's island"
parameters: "<player>"
reset:
description: "restart your island and remove the old one"
parameters: "<schem>"
must-remove-members: "&cYou must remove all members from your island before you can restart it (/island team kick <player>)."
none-left: "&cYou have no more resets left!"
resets-left: "&cYou have [number] resets left"

View File

@ -129,7 +129,7 @@ public class AddonTest {
TestClass test = new TestClass();
File file = mock(File.class);
assertNull(test.getFile());
test.setAddonFile(file);
test.setFile(file);
assertEquals(file, test.getFile());
}
@ -173,7 +173,7 @@ public class AddonTest {
File jarFile = new File("addon.jar");
File dataFolder = new File("dataFolder");
test.setDataFolder(dataFolder);
test.setAddonFile(jarFile);
test.setFile(jarFile);
test.saveDefaultConfig();
}
@ -195,7 +195,7 @@ public class AddonTest {
File jarFile = new File("addon.jar");
File dataFolder = new File("dataFolder");
test.setDataFolder(dataFolder);
test.setAddonFile(jarFile);
test.setFile(jarFile);
test.saveResource("no_such_file", true);
}
@ -205,7 +205,7 @@ public class AddonTest {
File jarFile = new File("addon.jar");
File dataFolder = new File("dataFolder");
test.setDataFolder(dataFolder);
test.setAddonFile(jarFile);
test.setFile(jarFile);
test.saveResource("no_such_file", jarFile, false, false);
test.saveResource("no_such_file", jarFile, false, true);
test.saveResource("no_such_file", jarFile, true, false);
@ -219,7 +219,7 @@ public class AddonTest {
File jarFile = new File("addon.jar");
File dataFolder = new File("dataFolder");
test.setDataFolder(dataFolder);
test.setAddonFile(jarFile);
test.setFile(jarFile);
assertNull(test.getResource("nothing"));
}
@ -227,7 +227,7 @@ public class AddonTest {
public void testSetAddonFile() {
TestClass test = new TestClass();
File jarFile = new File("addon.jar");
test.setAddonFile(jarFile);
test.setFile(jarFile);
assertEquals(jarFile, test.getFile());
}

View File

@ -192,6 +192,7 @@ public class IslandResetCommandTest {
when(builder.player(Mockito.any())).thenReturn(builder);
when(builder.oldIsland(Mockito.any())).thenReturn(builder);
when(builder.reason(Mockito.any())).thenReturn(builder);
when(builder.name(Mockito.any())).thenReturn(builder);
when(builder.build()).thenReturn(mock(Island.class));
PowerMockito.mockStatic(NewIsland.class);
when(NewIsland.builder()).thenReturn(builder);
@ -226,6 +227,7 @@ public class IslandResetCommandTest {
when(builder.player(Mockito.any())).thenReturn(builder);
when(builder.oldIsland(Mockito.any())).thenReturn(builder);
when(builder.reason(Mockito.any())).thenReturn(builder);
when(builder.name(Mockito.any())).thenReturn(builder);
when(builder.build()).thenReturn(mock(Island.class));
PowerMockito.mockStatic(NewIsland.class);
when(NewIsland.builder()).thenReturn(builder);
@ -263,6 +265,7 @@ public class IslandResetCommandTest {
when(builder.player(Mockito.any())).thenReturn(builder);
when(builder.oldIsland(Mockito.any())).thenReturn(builder);
when(builder.reason(Mockito.any())).thenReturn(builder);
when(builder.name(Mockito.any())).thenReturn(builder);
when(builder.build()).thenReturn(mock(Island.class));
PowerMockito.mockStatic(NewIsland.class);
when(NewIsland.builder()).thenReturn(builder);
@ -304,6 +307,7 @@ public class IslandResetCommandTest {
when(builder.player(Mockito.any())).thenReturn(builder);
when(builder.oldIsland(Mockito.any())).thenReturn(builder);
when(builder.reason(Mockito.any())).thenReturn(builder);
when(builder.name(Mockito.any())).thenReturn(builder);
when(builder.build()).thenThrow(new IOException());
PowerMockito.mockStatic(NewIsland.class);
when(NewIsland.builder()).thenReturn(builder);