bentobox/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java

444 lines
15 KiB
Java

package world.bentobox.bentobox.blueprints;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Banner;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.block.CreatureSpawner;
import org.bukkit.block.Sign;
import org.bukkit.block.sign.Side;
import org.bukkit.entity.AbstractHorse;
import org.bukkit.entity.Ageable;
import org.bukkit.entity.ChestedHorse;
import org.bukkit.entity.Horse;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Tameable;
import org.bukkit.entity.Villager;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.material.Attachable;
import org.bukkit.material.Colorable;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.util.BoundingBox;
import org.bukkit.util.Vector;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBlock;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintCreatureSpawner;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity;
import world.bentobox.bentobox.hooks.MythicMobsHook;
/**
* The clipboard provides the holding spot for an active blueprint that is being
* manipulated by a user. It supports copying from the world and setting of coordinates
* such as the bounding box around the cuboid copy area.
* Pasting is done by the {@link BlueprintPaster} class.
* @author tastybento
* @since 1.5.0
*/
public class BlueprintClipboard {
private @Nullable Blueprint blueprint;
private @Nullable Location pos1;
private @Nullable Location pos2;
private @Nullable Vector origin;
private BukkitTask copyTask;
private int count;
private boolean copying;
private int index;
private int lastPercentage;
private final Map<Vector, List<BlueprintEntity>> bpEntities = new LinkedHashMap<>();
private final Map<Vector, BlueprintBlock> bpAttachable = new LinkedHashMap<>();
private final Map<Vector, BlueprintBlock> bpBlocks = new LinkedHashMap<>();
private final BentoBox plugin = BentoBox.getInstance();
private Optional<MythicMobsHook> mmh;
/**
* Create a clipboard for blueprint
* @param blueprint - the blueprint to load into the clipboard
*/
public BlueprintClipboard(@NonNull Blueprint blueprint) {
this.blueprint = blueprint;
// MythicMobs
mmh = plugin.getHooks().getHook("MythicMobs").filter(MythicMobsHook.class::isInstance)
.map(MythicMobsHook.class::cast);
}
public BlueprintClipboard() {
// MythicMobs
mmh = plugin.getHooks().getHook("MythicMobs").filter(MythicMobsHook.class::isInstance)
.map(MythicMobsHook.class::cast);
}
/**
* Copy the blocks between pos1 and pos2 into the clipboard for a user.
* This will erase any previously registered data from the clipboard.
* Copying is done async.
* @param user - user
* @return true if successful, false if pos1 or pos2 are undefined.
*/
public boolean copy(User user, boolean copyAir, boolean copyBiome) {
if (copying) {
user.sendMessage("commands.admin.blueprint.mid-copy");
return false;
}
if (pos1 == null || pos2 == null) {
user.sendMessage("commands.admin.blueprint.need-pos1-pos2");
return false;
}
if (origin == null) {
setOrigin(user.getLocation().toVector());
}
user.sendMessage("commands.admin.blueprint.copying");
// World
World world = pos1.getWorld();
if (world == null) {
return false;
}
// Clear the clipboard
blueprint = new Blueprint();
bpEntities.clear();
bpAttachable.clear();
bpBlocks.clear();
count = 0;
index = 0;
lastPercentage = 0;
BoundingBox toCopy = BoundingBox.of(pos1, pos2);
blueprint.setxSize((int)toCopy.getWidthX());
blueprint.setySize((int)toCopy.getHeight());
blueprint.setzSize((int)toCopy.getWidthZ());
int speed = plugin.getSettings().getPasteSpeed();
List<Vector> vectorsToCopy = getVectors(toCopy);
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> copyAsync(world, user, vectorsToCopy, speed, copyAir, copyBiome));
return true;
}
private void copyAsync(World world, User user, List<Vector> vectorsToCopy, int speed, boolean copyAir, boolean copyBiome) {
copying = false;
copyTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
if (copying) {
return;
}
copying = true;
vectorsToCopy.stream().skip(index).limit(speed).forEach(v -> {
List<LivingEntity> ents = world.getLivingEntities().stream()
.filter(Objects::nonNull)
.filter(e -> !(e instanceof Player))
.filter(e -> new Vector(Math.rint(e.getLocation().getX()),
Math.rint(e.getLocation().getY()),
Math.rint(e.getLocation().getZ())).equals(v))
.toList();
if (copyBlock(v.toLocation(world), copyAir, copyBiome, ents)) {
count++;
}
});
index += speed;
int percent = (int)(index * 100 / (double)vectorsToCopy.size());
if (percent != lastPercentage && percent % 10 == 0) {
user.sendMessage("commands.admin.blueprint.copied-percent", TextVariables.NUMBER, String.valueOf(percent));
lastPercentage = percent;
}
if (index > vectorsToCopy.size()) {
copyTask.cancel();
assert blueprint != null;
blueprint.setAttached(bpAttachable);
blueprint.setBlocks(bpBlocks);
blueprint.setEntities(bpEntities);
user.sendMessage("general.success");
user.sendMessage("commands.admin.blueprint.copied-blocks", TextVariables.NUMBER, String.valueOf(count));
}
copying = false;
}, 0L, 1L);
}
/**
* Get all the x,y,z coords that must be copied
* @param b - bounding box
* @return - list of vectors
*/
protected List<Vector> getVectors(BoundingBox b) {
List<Vector> r = new ArrayList<>();
for (int y = (int) Math.floor(b.getMinY()); y <= b.getMaxY(); y++) {
for (int x = (int) Math.floor(b.getMinX()); x <= b.getMaxX(); x++) {
for (int z = (int) Math.floor(b.getMinZ()); z <= b.getMaxZ(); z++) {
r.add(new Vector(x,y,z));
}
}
}
return r;
}
private boolean copyBlock(Location l, boolean copyAir, boolean copyBiome, Collection<LivingEntity> entities) {
Block block = l.getBlock();
if (!copyAir && block.getType().equals(Material.AIR) && entities.isEmpty()) {
return false;
}
// Create position
Vector origin2 = origin == null ? new Vector(0,0,0) : origin;
int x = l.getBlockX() - origin2.getBlockX();
int y = l.getBlockY() - origin2.getBlockY();
int z = l.getBlockZ() - origin2.getBlockZ();
Vector pos = new Vector(x, y, z);
// Set entities
List<BlueprintEntity> bpEnts = setEntities(entities);
// Store
if (!bpEnts.isEmpty()) {
bpEntities.put(pos, bpEnts);
}
// Return if this is just air block
if (!copyAir && block.getType().equals(Material.AIR) && !entities.isEmpty()) {
return true;
}
BlueprintBlock b = bluePrintBlock(pos, block, copyBiome);
if (b != null) {
this.bpBlocks.put(pos, b);
}
return true;
}
private BlueprintBlock bluePrintBlock(Vector pos, Block block, boolean copyBiome) {
// Block state
BlockState blockState = block.getState();
BlueprintBlock b = new BlueprintBlock(block.getBlockData().getAsString());
if (copyBiome) {
// Biome
b.setBiome(block.getBiome());
}
// Signs
if (blockState instanceof Sign sign) {
for (Side side : Side.values()) {
b.setSignLines(side, Arrays.asList(sign.getSide(side).getLines()));
b.setGlowingText(side, sign.getSide(side).isGlowingText());
}
}
// Set block data
if (blockState.getData() instanceof Attachable) {
// Placeholder for attachment
bpBlocks.put(pos, new BlueprintBlock("minecraft:air"));
bpAttachable.put(pos, b);
return null;
}
if (block.getType().equals(Material.BEDROCK)) {
// Find highest bedrock
if(blueprint.getBedrock() == null) {
blueprint.setBedrock(pos);
} else {
if (pos.getBlockY() > blueprint.getBedrock().getBlockY()) {
blueprint.setBedrock(pos);
}
}
}
// Chests
if (blockState instanceof InventoryHolder ih) {
b.setInventory(new HashMap<>());
for (int i = 0; i < ih.getInventory().getSize(); i++) {
ItemStack item = ih.getInventory().getItem(i);
if (item != null) {
b.getInventory().put(i, item);
}
}
}
if (blockState instanceof CreatureSpawner spawner) {
b.setCreatureSpawner(getSpawner(spawner));
}
// Banners
if (blockState instanceof Banner banner) {
b.setBannerPatterns(banner.getPatterns());
}
return b;
}
private BlueprintCreatureSpawner getSpawner(CreatureSpawner spawner) {
BlueprintCreatureSpawner cs = new BlueprintCreatureSpawner();
cs.setSpawnedType(spawner.getSpawnedType());
cs.setDelay(spawner.getDelay());
cs.setMaxNearbyEntities(spawner.getMaxNearbyEntities());
cs.setMaxSpawnDelay(spawner.getMaxSpawnDelay());
cs.setMinSpawnDelay(spawner.getMinSpawnDelay());
cs.setRequiredPlayerRange(spawner.getRequiredPlayerRange());
cs.setSpawnRange(spawner.getSpawnRange());
return cs;
}
private List<BlueprintEntity> setEntities(Collection<LivingEntity> entities) {
List<BlueprintEntity> bpEnts = new ArrayList<>();
for (LivingEntity entity: entities) {
BlueprintEntity bpe = new BlueprintEntity();
bpe.setType(entity.getType());
bpe.setCustomName(entity.getCustomName());
if (entity instanceof Villager villager) {
setVillager(villager, bpe);
}
if (entity instanceof Colorable c && c.getColor() != null) {
bpe.setColor(c.getColor());
}
if (entity instanceof Tameable tameable) {
bpe.setTamed(tameable.isTamed());
}
if (entity instanceof ChestedHorse chestedHorse) {
bpe.setChest(chestedHorse.isCarryingChest());
}
// Only set if child. Most animals are adults
if (entity instanceof Ageable ageable && !ageable.isAdult()) {
bpe.setAdult(false);
}
if (entity instanceof AbstractHorse horse) {
bpe.setDomestication(horse.getDomestication());
bpe.setInventory(new HashMap<>());
for (int i = 0; i < horse.getInventory().getSize(); i++) {
ItemStack item = horse.getInventory().getItem(i);
if (item != null) {
bpe.getInventory().put(i, item);
}
}
}
if (entity instanceof Horse horse) {
bpe.setStyle(horse.getStyle());
}
mmh.filter(mm -> mm.isMythicMob(entity)).map(mm -> mm.getMythicMob(entity))
.ifPresent(bpe::setMythicMobsRecord);
bpEnts.add(bpe);
}
return bpEnts;
}
/**
* Set the villager stats
* @param v - villager
* @param bpe - Blueprint Entity
*/
private void setVillager(Villager v, BlueprintEntity bpe) {
bpe.setExperience(v.getVillagerExperience());
bpe.setLevel(v.getVillagerLevel());
bpe.setProfession(v.getProfession());
bpe.setVillagerType(v.getVillagerType());
}
/**
* @return the origin
*/
@Nullable
public Vector getOrigin() {
return origin;
}
/**
* @return the pos1
*/
@Nullable
public Location getPos1() {
return pos1;
}
/**
* @return the pos2
*/
@Nullable
public Location getPos2() {
return pos2;
}
public boolean isFull() {
return blueprint != null;
}
/**
* @param origin the origin to set
*/
public void setOrigin(@Nullable Vector origin) {
this.origin = origin;
}
/**
* @param pos1 the pos1 to set
*/
public void setPos1(@Nullable Location pos1) {
origin = null;
if (pos1 != null) {
final int minHeight = pos1.getWorld() == null ? 0 : pos1.getWorld().getMinHeight();
final int maxHeight = pos1.getWorld() == null ? 255 : pos1.getWorld().getMaxHeight();
if (pos1.getBlockY() < minHeight)
{
pos1.setY(minHeight);
}
if (pos1.getBlockY() > maxHeight)
{
pos1.setY(maxHeight);
}
}
this.pos1 = pos1;
}
/**
* @param pos2 the pos2 to set
*/
public void setPos2(@Nullable Location pos2) {
origin = null;
if (pos2 != null) {
final int minHeight = pos2.getWorld() == null ? 0 : pos2.getWorld().getMinHeight();
final int maxHeight = pos2.getWorld() == null ? 255 : pos2.getWorld().getMaxHeight();
if (pos2.getBlockY() < minHeight)
{
pos2.setY(minHeight);
}
if (pos2.getBlockY() > maxHeight)
{
pos2.setY(maxHeight);
}
}
this.pos2 = pos2;
}
/**
* @return the blueprint
*/
public @Nullable Blueprint getBlueprint() {
return blueprint;
}
/**
* @param blueprint the blueprint to set
*/
public BlueprintClipboard setBlueprint(Blueprint blueprint) {
this.blueprint = blueprint;
return this;
}
}