/*
* This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT).
*
* Copyright (c) JCThePants (www.jcwhatever.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.jcwhatever.nucleus.regions;
import com.jcwhatever.nucleus.internal.NucMsg;
import com.jcwhatever.nucleus.managed.scheduler.Scheduler;
import com.jcwhatever.nucleus.regions.data.RegionChunkSection;
import com.jcwhatever.nucleus.storage.IDataNode;
import com.jcwhatever.nucleus.utils.observer.future.FutureSubscriber;
import com.jcwhatever.nucleus.utils.observer.future.IFuture.FutureStatus;
import com.jcwhatever.nucleus.utils.performance.queued.Iteration3DTask;
import com.jcwhatever.nucleus.utils.performance.queued.QueueProject;
import com.jcwhatever.nucleus.utils.performance.queued.QueueTask;
import com.jcwhatever.nucleus.utils.performance.queued.QueueWorker;
import com.jcwhatever.nucleus.utils.performance.queued.TaskConcurrency;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.ChunkSnapshot;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.material.MaterialData;
import org.bukkit.plugin.Plugin;
import javax.annotation.Nullable;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.List;
import java.util.Queue;
/**
* An abstract implementation of a region that can easily build within itself.
*/
public abstract class BuildableRegion extends Region {
private boolean _isBuilding;
/**
* Specifies the build speed.
*/
public enum BuildSpeed {
/**
* Maximize server performance at the cost of build speed.
*/
PERFORMANCE,
/**
* Balanced between speed and performance.
*/
BALANCED,
/**
* Build as fast as possible.
*/
FAST
}
/**
* Constructor.
*
* @param plugin The owning plugin.
* @param name The name of the region.
*/
public BuildableRegion(Plugin plugin, String name) {
super(plugin, name, null);
}
/**
* Constructor.
*
* @param plugin The owning plugin.
* @param name The name of the region.
* @param dataNode The regions data node.
*/
public BuildableRegion(Plugin plugin, String name, @Nullable IDataNode dataNode) {
super(plugin, name, dataNode);
}
/**
* Determine if the region is in the process of building.
*/
public final boolean isBuilding() {
return _isBuilding;
}
/**
* Create an empty 3D array representing the region which can be used to specify what to build.
*/
public final MaterialData[][][] getBuildArray() {
return new MaterialData[getXBlockWidth()][getYBlockHeight()][getZBlockWidth()];
}
/**
* Build in the region using the specified chunk snapshots.
*
* @param buildSpeed The speed of building.
* @param snapshots The snapshots representing the build.
*
* @return True if build started, false if build already in progress or region is not defined.
*/
public final boolean build(BuildSpeed buildSpeed, Collection<? extends ChunkSnapshot> snapshots) {
// already building
if (_isBuilding)
return false;
if (!isDefined())
return false;
_isBuilding = true;
QueueProject project = new QueueProject(getPlugin());
for (ChunkSnapshot snapshot : snapshots) {
World world = Bukkit.getWorld(snapshot.getWorldName());
if (world == null) {
NucMsg.debug(getPlugin(),
"Failed to get world named '{0}' while building region '{1}'.",
snapshot.getWorldName(), getName());
continue;
}
Chunk chunk = world.getChunkAt(snapshot.getX(), snapshot.getZ());
if (chunk == null) {
NucMsg.debug(getPlugin(),
"Failed to get chunk ({0}, {1}) in world '{0}' while building region '{1}'.",
snapshot.getX(), snapshot.getZ(), snapshot.getWorldName(), getName());
continue;
}
if (!chunk.isLoaded()) {
chunk.load();
}
// get region chunk section calculations
RegionChunkSection section = new RegionChunkSection(this, snapshot);
// get build chunk iterator task
BuildChunkIterator iterator = new BuildChunkIterator(this, snapshot, -1,
section.getStartChunkX(), section.getStartY(), section.getStartChunkZ(),
section.getEndChunkX(), section.getEndY(), section.getEndChunkZ());
// add task to project
project.addTask(iterator);
}
switch (buildSpeed) {
case PERFORMANCE:
QueueWorker.get().addTask(project);
break;
case BALANCED:
project.run();
break;
case FAST:
List<QueueTask> tasks = project.getTasks();
for (QueueTask task : tasks) {
task.run();
}
break;
}
project.getResult().onStatus(new FutureSubscriber() {
@Override
public void on(FutureStatus status, @Nullable CharSequence message) {
_isBuilding = false;
}
});
return true;
}
/*
* Iteration worker for building in region area within the specified chunk.
*/
private static final class BuildChunkIterator extends Iteration3DTask {
private final ChunkSnapshot snapshot;
private final Chunk chunk;
private final Queue<BlockState> blocks;
public BuildChunkIterator (Region region, ChunkSnapshot snapshot, long segmentSize,
int xStart, int yStart, int zStart,
int xEnd, int yEnd, int zEnd) {
super(region.getPlugin(), TaskConcurrency.ASYNC, segmentSize, xStart, yStart, zStart, xEnd, yEnd, zEnd);
this.blocks = new ArrayDeque<>((int)this.getVolume());
this.snapshot = snapshot;
//noinspection ConstantConditions
this.chunk = region.getWorld().getChunkAt(snapshot.getX(), snapshot.getZ());
}
@Override
public void onIterateItem(int x, int y, int z) {
Material type = Material.getMaterial(snapshot.getBlockTypeId(x, y, z));
int data = snapshot.getBlockData(x, y, z);
Block block = chunk.getBlock(x, y, z);
BlockState state = block.getState();
if (state.getType() != type || (type != Material.AIR && state.getData().getData() != data)) {
state.setType(type);
state.setRawData((byte)data);
this.blocks.add(state);
}
}
@Override
protected void onPreComplete() {
// schedule block update
Scheduler.runTaskSync(getPlugin(), 1, new UpdateBlocks());
}
/*
* Update block states for chunk all at once on the
* the main thread.
*/
final class UpdateBlocks implements Runnable {
@Override
public final void run() {
while (!blocks.isEmpty()) {
BlockState state = blocks.remove();
state.update(true, false);
}
}
}
}
}