package speedytools.serverside.actions;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.init.Blocks;
import net.minecraft.item.Item;
import net.minecraft.util.BlockPos;
import net.minecraft.util.EnumFacing;
import net.minecraft.world.World;
import net.minecraft.world.WorldServer;
import net.minecraftforge.common.DimensionManager;
import speedytools.common.SpeedyToolsOptions;
import speedytools.common.blocks.BlockWithMetadata;
import speedytools.common.items.RegistryForItems;
import speedytools.common.network.ServerStatus;
import speedytools.common.selections.VoxelSelectionWithOrigin;
import speedytools.common.utilities.ErrorLog;
import speedytools.common.utilities.Pair;
import speedytools.common.utilities.QuadOrientation;
import speedytools.common.utilities.ResultWithReason;
import speedytools.serverside.ServerSide;
import speedytools.serverside.ServerVoxelSelections;
import speedytools.serverside.backup.MinecraftSaveFolderBackups;
import speedytools.serverside.network.SpeedyToolsNetworkServer;
import speedytools.serverside.worldmanipulation.AsynchronousToken;
import speedytools.serverside.worldmanipulation.WorldHistory;
import java.nio.file.Path;
import java.util.List;
/**
* Created by TheGreyGhost on 7/03/14.
*/
public class SpeedyToolServerActions
{
public SpeedyToolServerActions(ServerVoxelSelections i_serverVoxelSelections, WorldHistory i_worldHistory)
{
worldHistory = i_worldHistory;
serverVoxelSelections = i_serverVoxelSelections;
}
public void setCloneToolsNetworkServer(SpeedyToolsNetworkServer server)
{
speedyToolsNetworkServer = server;
}
/**
* performed in response to a "I've made a selection" message from the client
* @return true for success, false otherwise
*/
public ResultWithReason prepareForToolAction(EntityPlayerMP player)
{
assert (minecraftSaveFolderBackups != null);
assert (speedyToolsNetworkServer != null);
if (ServerSide.getInGameStatusSimulator().isTestModeActivated()) { // testing only
ResultWithReason resultWithReason = null;
resultWithReason = ServerSide.getInGameStatusSimulator().prepareForToolAction(speedyToolsNetworkServer, player);
if (resultWithReason != null) return resultWithReason;
}
speedyToolsNetworkServer.changeServerStatus(ServerStatus.PERFORMING_BACKUP, null, (byte)0);
minecraftSaveFolderBackups.backupWorld();
speedyToolsNetworkServer.changeServerStatus(ServerStatus.IDLE, null, (byte)0);
return ResultWithReason.success();
}
/**
* Performs a server Simple Speedy Tools action in response to an incoming packet from the client: either place or undo
* @param entityPlayerMP the user sending the packet
* @param buttonClicked 0 = left (undo), 1 = right (place)
* @param blockToPlace the Block and metadata to fill the selection with (buttonClicked = 1 only)
* @param sideToPlace
* @param blockSelection the blocks in the selection to be filled (buttonClicked = 1 only)
*/
public void performSimpleAction(EntityPlayerMP entityPlayerMP, int buttonClicked, BlockWithMetadata blockToPlace, EnumFacing sideToPlace, List<BlockPos> blockSelection)
{
WorldServer worldServer = entityPlayerMP.getServerForPlayer();
switch (buttonClicked) {
case 0: {
worldHistory.performSimpleUndo(entityPlayerMP, worldServer);
return;
}
case 1: {
worldHistory.writeToWorldWithUndo(worldServer, entityPlayerMP, blockToPlace, sideToPlace, blockSelection);
return;
}
default: {
return;
}
}
}
/**
* Starts a complex (asynchronous) action. It will be progressed automatically whenever tick() is called.
* @param player
* @param sequenceNumber
* @param toolID
* @param fillBlock for fill tools, the block that will be used to fill
*@param wxpos
* @param wypos
* @param wzpos
* @param quadOrientation @return
*/
public ResultWithReason performComplexAction(EntityPlayerMP player, int sequenceNumber, int toolID,
BlockWithMetadata fillBlock,
int wxpos, int wypos, int wzpos, QuadOrientation quadOrientation,
BlockPos initialSelectionOrigin)
{
assert (!isAsynchronousActionInProgress());
// System.out.println("Server: Tool Action received sequence #" + sequenceNumber + ": tool " + toolID + " at [" + wxpos + ", " + wypos + ", " + wzpos
// + "], rotated:" + quadOrientation.getClockwiseRotationCount() + ", flippedX:" + quadOrientation.isFlippedX());
VoxelSelectionWithOrigin voxelSelection = serverVoxelSelections.getVoxelSelection(player);
if (voxelSelection == null) {
return ResultWithReason.failure("Must wait for spell preparation to finish ...");
}
if (!minecraftSaveFolderBackups.isBackedUpRecently()) {
ResultWithReason result = prepareForToolAction(player);
if (!result.succeeded()) {
return ResultWithReason.failure("Too risky! World backup failed!");
}
}
if (ServerSide.getInGameStatusSimulator().isTestModeActivated()) { // testing only
ResultWithReason resultWithReason = null;
resultWithReason = ServerSide.getInGameStatusSimulator().performToolAction(speedyToolsNetworkServer, player,
sequenceNumber, toolID,
wxpos, wypos, wzpos, quadOrientation,
initialSelectionOrigin);
if (resultWithReason != null) return resultWithReason;
}
WorldServer worldServer = (WorldServer)player.theItemInWorldManager.theWorld;
// System.out.println("initialSelectionOrigin:" + initialSelectionOrigin);
// System.out.println("voxelSelection: [" + voxelSelection.getWxOrigin() + "," + voxelSelection.getWyOrigin()
// + ", " + voxelSelection.getWzOrigin() + "]");
// System.out.println("placement pos before: [" + wxpos + "," + wypos + ", " + wzpos + "]");
// System.out.println("placement QuadOrientation size before:" + quadOrientation.getWXsize() + ", " + quadOrientation.getWZSize());
// In some cases, the client will still be using the client-side-generated selection while the server has calculated
// a new, expanded selection to include the chunks that the client couldn't see. In this case, the selection
// origins may not match; and if not, the placement position must be corrected to account for it, also the
// QuadOrientation. This is done by looking at what happens to the [0,0] grid coordinate of the client selection:
// 1) the client selection is a rect inside the server selection, with the client [0,0] at [dx,dz] within the server selection
// 2) the [0,0] to [xsize-1, zsize-1] of the client selection maps to a rectangle at wx0, wz0 in world coordinates using the client QuadOrientation.
// 3) hence, [dx,dz] to [dx+xsize-1, dz+zsize-1] must map to the same world rectangle when using the server QuadOrientation
// it actually maps to swx0, swz0, hence the difference corresponds to the adjustment to wxpos, wypos
int dx = initialSelectionOrigin.getX() - voxelSelection.getWxOrigin();
int dy = initialSelectionOrigin.getY() - voxelSelection.getWyOrigin();
int dz = initialSelectionOrigin.getZ() - voxelSelection.getWzOrigin();
QuadOrientation clientQuadOrientation = quadOrientation;
QuadOrientation serverQuadOrientation = quadOrientation.getResizedCopy(voxelSelection.getxSize(), voxelSelection.getzSize());
// System.out.println("[dx,dy,dz] = " + dx + ", " + dy + ", " + dz);
Pair<Integer, Integer> serverDisplacement = clientQuadOrientation.calculateDisplacementForExpandedSelection(serverQuadOrientation,
dx, dz);
wxpos -= serverDisplacement.getFirst();
wypos -= dy;
wzpos -= serverDisplacement.getSecond();
// System.out.println("placement pos after: [" + wxpos + "," + wypos + ", " + wzpos + "]");
quadOrientation = serverQuadOrientation;
// System.out.println("placement QuadOrientation size after:" + quadOrientation.getWXsize() + ", " + quadOrientation.getWZSize());
AsynchronousActionBase token;
if (toolID == Item.getIdFromItem(RegistryForItems.itemComplexCopy)) {
token = new AsynchronousActionCopy(worldServer, player, worldHistory, voxelSelection, sequenceNumber, toolID, wxpos, wypos, wzpos, quadOrientation);
} else if (toolID == Item.getIdFromItem(RegistryForItems.itemComplexDelete)) {
BlockWithMetadata airBWM = new BlockWithMetadata(Blocks.air, 0);
token = new AsynchronousActionFill(worldServer, player, worldHistory, voxelSelection, airBWM, sequenceNumber, toolID, wxpos, wypos, wzpos, quadOrientation);
} else if (toolID == Item.getIdFromItem(RegistryForItems.itemComplexMove)) {
token = new AsynchronousActionMove(worldServer, player, worldHistory, voxelSelection, sequenceNumber, toolID, wxpos, wypos, wzpos, quadOrientation);
} else if (toolID == Item.getIdFromItem(RegistryForItems.itemSpeedyOrb)) {
token = new AsynchronousActionFill(worldServer, player, worldHistory, voxelSelection, fillBlock, sequenceNumber, toolID, wxpos, wypos, wzpos, quadOrientation);
} else if (toolID == Item.getIdFromItem(RegistryForItems.itemSpeedySceptre)) {
token = new AsynchronousActionFill(worldServer, player, worldHistory, voxelSelection, fillBlock, sequenceNumber, toolID, wxpos, wypos, wzpos, quadOrientation);
} else {
ErrorLog.defaultLog().info("Invalid toolID received in performComplexAction:" + toolID);
return ResultWithReason.failure();
}
token.setTimeOfInterrupt(AsynchronousToken.IMMEDIATE_TIMEOUT);
speedyToolsNetworkServer.changeServerStatus(ServerStatus.PERFORMING_YOUR_ACTION, player, (byte) 0);
token.continueProcessing();
asynchronousTaskInProgress = token;
asynchronousTaskActionType = ActionType.ACTION;
asynchronousTaskSequenceNumber = sequenceNumber;
asynchronousTaskEntityPlayerMP = player;
return ResultWithReason.success();
}
// /**
// * sets the current selection for the given player
// * @param player
// * @return true if accepted
// */
//
// public boolean setCurrentSelection(EntityPlayerMP player, VoxelSelection newSelection)
// {
// return true;
// }
public ResultWithReason performUndoOfCurrentComplexAction(EntityPlayerMP player, int undoSequenceNumber, int actionSequenceNumber)
{
// System.out.println("Server: Tool Undo Current Action received: action sequenceNumber " + actionSequenceNumber + ", undo seq number " + undoSequenceNumber);
if (ServerSide.getInGameStatusSimulator().isTestModeActivated()) { // testing only
ResultWithReason resultWithReason = null;
resultWithReason = ServerSide.getInGameStatusSimulator().performUndoOfCurrentAction(speedyToolsNetworkServer, player, undoSequenceNumber, actionSequenceNumber);
if (resultWithReason != null) return resultWithReason;
}
// we're currently still synchronous undo so this is not relevant yet; just call performUndoOfLastAction instead
if (asynchronousTaskInProgress != null && !asynchronousTaskInProgress.isTaskComplete()) {
asynchronousTaskInProgress.rollback(undoSequenceNumber);
asynchronousTaskActionType = ActionType.UNDO;
asynchronousTaskSequenceNumber = undoSequenceNumber;
asynchronousTaskEntityPlayerMP = player;
return ResultWithReason.success();
}
return performUndoOfLastComplexAction(player, undoSequenceNumber);
}
public ResultWithReason performUndoOfLastComplexAction(EntityPlayerMP player, int undoSequenceNumber)
{
// System.out.println("Server: Tool Undo Last Completed Action received, undo seq number " + undoSequenceNumber);
if (ServerSide.getInGameStatusSimulator().isTestModeActivated()) { // testing only
ResultWithReason resultWithReason = null;
resultWithReason = ServerSide.getInGameStatusSimulator().performUndoOfLastAction(speedyToolsNetworkServer, player, undoSequenceNumber);
if (resultWithReason != null) return resultWithReason;
}
WorldServer worldServer = (WorldServer)player.theItemInWorldManager.theWorld;
AsynchronousActionBase token = new AsynchronousActionUndo(speedyToolsNetworkServer, worldServer, player, worldHistory, undoSequenceNumber);
asynchronousTaskActionType = ActionType.UNDO;
asynchronousTaskSequenceNumber = undoSequenceNumber;
asynchronousTaskEntityPlayerMP = player;
token.setTimeOfInterrupt(AsynchronousToken.IMMEDIATE_TIMEOUT);
token.continueProcessing();
if (token.isTaskAborted()) {
return ResultWithReason.failure("There are no more spells to undo...");
}
asynchronousTaskInProgress = token;
return ResultWithReason.success();
}
public void tick() {
if (ServerSide.getInGameStatusSimulator().isTestModeActivated()) {
ServerSide.getInGameStatusSimulator().updateServerStatus(speedyToolsNetworkServer);
}
long stopTimeNS = System.nanoTime() + SpeedyToolsOptions.getMaxServerBusyTimeMS() * 1000L * 1000L;
final int STATUS_UPDATE_PERIOD_TICKS = 10;
if (asynchronousTaskInProgress != null && !asynchronousTaskInProgress.isTaskComplete()) {
asynchronousTaskInProgress.setTimeOfInterrupt(stopTimeNS);
asynchronousTaskInProgress.continueProcessing();
if (asynchronousTaskInProgress.isTaskComplete()) {
if (asynchronousTaskActionType == ActionType.ACTION) {
speedyToolsNetworkServer.actionCompleted(asynchronousTaskEntityPlayerMP, asynchronousTaskSequenceNumber);
} else {
speedyToolsNetworkServer.undoCompleted(asynchronousTaskEntityPlayerMP, asynchronousTaskSequenceNumber);
}
asynchronousTaskInProgress = null;
} else if (0 == (ServerSide.getGlobalTickCount() % STATUS_UPDATE_PERIOD_TICKS)) { // task not complete
speedyToolsNetworkServer.changeServerStatus((asynchronousTaskActionType == ActionType.ACTION) ? ServerStatus.PERFORMING_YOUR_ACTION : ServerStatus.UNDOING_YOUR_ACTION,
asynchronousTaskEntityPlayerMP,
(byte) (100 * asynchronousTaskInProgress.getFractionComplete()));
}
}
if (!ServerSide.getInGameStatusSimulator().isTestModeActivated()) {
if (asynchronousTaskInProgress == null || asynchronousTaskInProgress.isTaskComplete()) {
speedyToolsNetworkServer.changeServerStatus(ServerStatus.IDLE, null, (byte) 0);
}
}
}
/**
* ensure that the save folder backups are initialised
* @param world
*/
public static void worldLoadEvent(World world)
{
if (minecraftSaveFolderBackups == null) {
minecraftSaveFolderBackups = new MinecraftSaveFolderBackups();
} else {
Path savePath = DimensionManager.getCurrentSaveRootDirectory().toPath();
if (!savePath.equals(minecraftSaveFolderBackups.getSourceSaveFolder())) {
minecraftSaveFolderBackups = new MinecraftSaveFolderBackups();
}
}
}
public static void worldUnloadEvent(World world)
{
// for now - don't need to do anything
}
// return true if an asynchronous action is in progress - backup, complex placement, complex undo
public boolean isAsynchronousActionInProgress()
{
if (asynchronousTaskInProgress != null && !asynchronousTaskInProgress.isTaskComplete()) return true;
return false;
}
private static MinecraftSaveFolderBackups minecraftSaveFolderBackups;
private static SpeedyToolsNetworkServer speedyToolsNetworkServer;
private WorldHistory worldHistory;
protected ServerVoxelSelections serverVoxelSelections; // protected for test stub
private AsynchronousActionBase asynchronousTaskInProgress;
private int asynchronousTaskSequenceNumber;
enum ActionType {ACTION, UNDO};
private ActionType asynchronousTaskActionType;
private EntityPlayerMP asynchronousTaskEntityPlayerMP;
}