WIP: Enables async-style pasting of schems.

Speed of schem pasting is in the config.yml of BentoBox.

Old schems will work and paste async, but attached blocks, e.g.,
torches, may fall off due to being pasted ticks before the supporting
block is pasted. Newer schems will not have this issue.

Further work is needed to optimize entity pasting.

Known issue: if a player logs out during the pasting, things go wrong
until the next reload. Needs investigation and mitigation.

There's no mitigation against the server shutting down or crashing
mid-paste.
This commit is contained in:
tastybento 2018-12-30 20:34:50 -08:00
parent c4a5eb2c88
commit 38e82ee617
3 changed files with 60 additions and 25 deletions

View File

@ -201,10 +201,7 @@ public class BentoBox extends JavaPlugin {
if (islandsManager != null) { if (islandsManager != null) {
islandsManager.shutdown(); islandsManager.shutdown();
} }
// Save settings // Save settings - not required
if (settings != null) {
new Config<>(this, Settings.class).saveConfigObject(settings);
}
} }
/** /**

View File

@ -42,6 +42,7 @@ import org.bukkit.entity.Tameable;
import org.bukkit.inventory.Inventory; import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.material.Attachable;
import org.bukkit.material.Colorable; import org.bukkit.material.Colorable;
import org.bukkit.scheduler.BukkitTask; import org.bukkit.scheduler.BukkitTask;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
@ -61,7 +62,7 @@ public class Clipboard {
private int pasteSpeed = 200; private int pasteSpeed = 200;
// Commonly used texts along this class. // Commonly used texts along this class.
private static final String ATTACHED = "attached"; private static final String ATTACHED = "attached.";
private static final String BLOCK = "blocks"; private static final String BLOCK = "blocks";
private static final String BEDROCK = "bedrock"; private static final String BEDROCK = "bedrock";
private static final String INVENTORY = "inventory"; private static final String INVENTORY = "inventory";
@ -197,14 +198,32 @@ public class Clipboard {
// Paste // Paste
if (blockConfig.contains(BLOCK)) { if (blockConfig.contains(BLOCK)) {
Iterator<String> it = blockConfig.getConfigurationSection(BLOCK).getKeys(false).iterator(); Iterator<String> it = blockConfig.getConfigurationSection(BLOCK).getKeys(false).iterator();
Iterator<String> attachmentsIt = blockConfig.contains(ATTACHED) ? blockConfig.getConfigurationSection(ATTACHED).getKeys(false).iterator() : null;
pastingTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> { pastingTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
int count = 0; int count = 0;
boolean done = false;
while (count < pasteSpeed && it.hasNext()) { while (count < pasteSpeed && it.hasNext()) {
pasteBlock(world, island, loc, blockConfig.getConfigurationSection(BLOCK + "." + it.next())); pasteBlock(world, island, loc, blockConfig.getConfigurationSection(BLOCK + "." + it.next()), false);
count++; count++;
} }
// If the pasting has been completed, then cancel and run any follow on task // If the blocks are done, paste attachments.
if (!it.hasNext()) { if (!it.hasNext()) {
// Paste attachments
if (attachmentsIt != null) {
while (count < pasteSpeed && attachmentsIt.hasNext()) {
pasteBlock(world, island, loc, blockConfig.getConfigurationSection(ATTACHED + "." + attachmentsIt.next()), false);
count++;
}
if (!attachmentsIt.hasNext()) {
done = true;
}
} else {
done = true;
}
}
if (done) {
// Paste entities (all at once)
blockConfig.getConfigurationSection(BLOCK).getKeys(false).stream().filter(xyz -> blockConfig.getConfigurationSection(BLOCK + "." + xyz).contains(ENTITY)).forEach(e -> pasteBlock(world, island, loc, blockConfig.getConfigurationSection(BLOCK + "." + e), true));
// Cancel task // Cancel task
pastingTask.cancel(); pastingTask.cancel();
if (task != null) { if (task != null) {
@ -224,7 +243,7 @@ public class Clipboard {
*/ */
public void pasteClipboard(Location location) { public void pasteClipboard(Location location) {
if (blockConfig.contains(BLOCK)) { if (blockConfig.contains(BLOCK)) {
blockConfig.getConfigurationSection(BLOCK).getKeys(false).forEach(b -> pasteBlock(location.getWorld(), null, location, blockConfig.getConfigurationSection(BLOCK + "." + b))); blockConfig.getConfigurationSection(BLOCK).getKeys(false).forEach(b -> pasteBlock(location.getWorld(), null, location, blockConfig.getConfigurationSection(BLOCK + "." + b), true));
} else { } else {
plugin.logError("Clipboard has no block data in it to paste!"); plugin.logError("Clipboard has no block data in it to paste!");
} }
@ -268,7 +287,7 @@ public class Clipboard {
} }
private void pasteBlock(World world, Island island, Location location, ConfigurationSection config) { private void pasteBlock(World world, Island island, Location location, ConfigurationSection config, boolean pasteEntities) {
String[] pos = config.getName().split(","); String[] pos = config.getName().split(",");
int x = location.getBlockX() + Integer.valueOf(pos[0]); int x = location.getBlockX() + Integer.valueOf(pos[0]);
int y = location.getBlockY() + Integer.valueOf(pos[1]); int y = location.getBlockY() + Integer.valueOf(pos[1]);
@ -277,14 +296,10 @@ public class Clipboard {
Block block = world.getBlockAt(x, y, z); Block block = world.getBlockAt(x, y, z);
String blockData = config.getString("bd"); String blockData = config.getString("bd");
if (blockData != null) { if (blockData != null) {
if (config.getBoolean(ATTACHED)) { setBlock(island, block, config, blockData);
plugin.getServer().getScheduler().runTask(plugin, () -> setBlock(island, block, config, blockData));
} else {
setBlock(island, block, config, blockData);
}
} }
// Entities // Entities - only paste here if this is a clipboard paste, otherwise, wait until island is pasted
if (config.isConfigurationSection(ENTITY)) { if (pasteEntities && config.isConfigurationSection(ENTITY)) {
setEntity(block.getLocation(), config); setEntity(block.getLocation(), config);
} }
} }
@ -305,7 +320,7 @@ public class Clipboard {
ConfigurationSection en = config.getConfigurationSection(ENTITY); ConfigurationSection en = config.getConfigurationSection(ENTITY);
en.getKeys(false).forEach(k -> { en.getKeys(false).forEach(k -> {
ConfigurationSection ent = en.getConfigurationSection(k); ConfigurationSection ent = en.getConfigurationSection(k);
Location center = location.add(new Vector(0.5, 0.0, 0.5)); Location center = location.add(new Vector(0.5, 1.0, 0.5));
LivingEntity e = (LivingEntity)location.getWorld().spawnEntity(center, EntityType.valueOf(ent.getString("type", "PIG"))); LivingEntity e = (LivingEntity)location.getWorld().spawnEntity(center, EntityType.valueOf(ent.getString("type", "PIG")));
if (e != null) { if (e != null) {
e.setCustomName(ent.getString("name")); e.setCustomName(ent.getString("name"));
@ -433,15 +448,35 @@ public class Clipboard {
return true; return true;
} }
// Block state
BlockState bs = block.getState();
// Set block data // Set block data
s.set("bd", block.getBlockData().getAsString()); if (bs.getData() instanceof Attachable) {
plugin.logDebug("Attachable");
ConfigurationSection a = blockConfig.createSection(ATTACHED + pos);
a.set("bd", block.getBlockData().getAsString());
// Placeholder
s.set("bd", "minecraft:stone");
// Signs
if (bs instanceof Sign) {
Sign sign = (Sign)bs;
a.set("lines", Arrays.asList(sign.getLines()));
}
return true;
} else {
s.set("bd", block.getBlockData().getAsString());
// Signs
if (bs instanceof Sign) {
Sign sign = (Sign)bs;
s.set("lines", Arrays.asList(sign.getLines()));
}
}
if (block.getType().equals(Material.BEDROCK)) { if (block.getType().equals(Material.BEDROCK)) {
blockConfig.set(BEDROCK, x + "," + y + "," + z); blockConfig.set(BEDROCK, x + "," + y + "," + z);
} }
// Block state
BlockState bs = block.getState();
// Chests // Chests
if (bs instanceof InventoryHolder) { if (bs instanceof InventoryHolder) {
InventoryHolder ih = (InventoryHolder)bs; InventoryHolder ih = (InventoryHolder)bs;
@ -452,11 +487,7 @@ public class Clipboard {
} }
} }
} }
// Signs
if (bs instanceof Sign) {
Sign sign = (Sign)bs;
s.set("lines", Arrays.asList(sign.getLines()));
}
if (bs instanceof CreatureSpawner) { if (bs instanceof CreatureSpawner) {
CreatureSpawner spawner = (CreatureSpawner)bs; CreatureSpawner spawner = (CreatureSpawner)bs;
s.set("spawnedType",spawner.getSpawnedType().name()); s.set("spawnedType",spawner.getSpawnedType().name());

View File

@ -36,6 +36,7 @@ import org.bukkit.entity.Sheep;
import org.bukkit.inventory.HorseInventory; import org.bukkit.inventory.HorseInventory;
import org.bukkit.inventory.Inventory; import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.material.MaterialData;
import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
import org.junit.Before; import org.junit.Before;
@ -154,6 +155,12 @@ public class ClipboardTest {
when(settings.getPasteSpeed()).thenReturn(200); when(settings.getPasteSpeed()).thenReturn(200);
when(plugin.getSettings()).thenReturn(settings); when(plugin.getSettings()).thenReturn(settings);
// Default block state
BlockState bs = mock(BlockState.class);
when(block.getState()).thenReturn(bs);
MaterialData md = mock(MaterialData.class);
when(bs.getData()).thenReturn(md);
} }
@Test @Test