Added basic undo/redo functionality with a history size of 15.

This commit is contained in:
sk89q 2010-10-02 14:52:42 -07:00
parent 6364ba720b
commit 7387e36016
6 changed files with 306 additions and 47 deletions

View File

@ -1,3 +1,3 @@
Manifest-Version: 1.0 Manifest-Version: 1.0
Class-Path: js.jar Class-Path: js.jar commons-lang3-3.0-beta.jar

View File

@ -22,6 +22,17 @@
* @author Albert * @author Albert
*/ */
public class EditScriptMinecraftContext { public class EditScriptMinecraftContext {
private EditSession editSession;
/**
* Construct this Minecraft object with an EditSession.
*
* @param editSession
*/
public EditScriptMinecraftContext(EditSession editSession) {
this.editSession = editSession;
}
/** /**
* Sets the block at position x, y, z with a block type. * Sets the block at position x, y, z with a block type.
* *
@ -32,7 +43,7 @@ public class EditScriptMinecraftContext {
* @return * @return
*/ */
public boolean setBlock(int x, int y, int z, int blockType) { public boolean setBlock(int x, int y, int z, int blockType) {
return etc.getMCServer().e.d(x, y, z, blockType); return editSession.setBlock(x, y, z, blockType);
} }
/** /**
@ -44,6 +55,6 @@ public class EditScriptMinecraftContext {
* @return * @return
*/ */
public int getBlock(int x, int y, int z) { public int getBlock(int x, int y, int z) {
return etc.getMCServer().e.a(x, y, z); return editSession.getBlock(x, y, z);
} }
} }

107
src/EditSession.java Normal file
View File

@ -0,0 +1,107 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010 sk89q <http://www.sk89q.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import java.util.Map;
import java.util.HashMap;
import com.sk89q.worldedit.*;
/**
*
* @author Albert
*/
public class EditSession {
/**
* Stores the original blocks before modification.
*/
private HashMap<Point,Integer> original = new HashMap<Point,Integer>();
private HashMap<Point,Integer> current = new HashMap<Point,Integer>();
/**
* Sets a block without changing history.
*
* @param x
* @param y
* @param z
* @param blockType
* @return Whether the block changed
*/
private boolean rawSetBlock(int x, int y, int z, int blockType) {
return etc.getMCServer().e.d(x, y, z, blockType);
}
/**
* Sets the block at position x, y, z with a block type.
*
* @param x
* @param y
* @param z
* @param blockType
* @return Whether the block changed
*/
public boolean setBlock(int x, int y, int z, int blockType) {
Point pt = new Point(x, y, z);
if (!original.containsKey(pt)) {
original.put(pt, getBlock(x, y, z));
}
current.put(pt, blockType);
return rawSetBlock(x, y, z, blockType);
}
/**
* Gets the block type at a position x, y, z.
*
* @param x
* @param y
* @param z
* @return Block type
*/
public int getBlock(int x, int y, int z) {
return etc.getMCServer().e.a(x, y, z);
}
/**
* Restores all blocks to their initial state.
*/
public void undo() {
for (Map.Entry<Point,Integer> entry : original.entrySet()) {
Point pt = (Point)entry.getKey();
rawSetBlock((int)pt.getX(), (int)pt.getY(),(int)pt.getZ(),
(int)entry.getValue());
}
}
/**
* Sets to new state.
*/
public void redo() {
for (Map.Entry<Point,Integer> entry : current.entrySet()) {
Point pt = (Point)entry.getKey();
rawSetBlock((int)pt.getX(), (int)pt.getY(),(int)pt.getZ(),
(int)entry.getValue());
}
}
/**
* Get the number of changed blocks.
*
*/
public int size() {
return original.size();
}
}

View File

@ -39,6 +39,8 @@ public class WorldEdit extends Plugin {
commands.put("/editpos1", "Set editing position #1"); commands.put("/editpos1", "Set editing position #1");
commands.put("/editpos2", "Set editing position #2"); commands.put("/editpos2", "Set editing position #2");
commands.put("/editundo", "Undo");
commands.put("/editredo", "Redo");
commands.put("/editsize", "Get size of selected region"); commands.put("/editsize", "Get size of selected region");
commands.put("/editset", "<Type> - Set all blocks inside region"); commands.put("/editset", "<Type> - Set all blocks inside region");
commands.put("/editreplace", "<ID> - Replace all existing blocks inside region"); commands.put("/editreplace", "<ID> - Replace all existing blocks inside region");
@ -128,31 +130,6 @@ public class WorldEdit extends Plugin {
} }
} }
/**
* Sets the block at position x, y, z with a block type.
*
* @param x
* @param y
* @param z
* @param blockType
* @return
*/
private boolean setBlock(int x, int y, int z, int blockType) {
return etc.getMCServer().e.d(x, y, z, blockType);
}
/**
* Gets the block type at a position x, y, z.
*
* @param x
* @param y
* @param z
* @return
*/
private int getBlock(int x, int y, int z) {
return etc.getMCServer().e.a(x, y, z);
}
/** /**
* *
* @override * @override
@ -214,6 +191,7 @@ public class WorldEdit extends Plugin {
InsufficientArgumentsException, DisallowedItemException InsufficientArgumentsException, DisallowedItemException
{ {
WorldEditSession session = getSession(player); WorldEditSession session = getSession(player);
EditSession editSession = new EditSession();
// Set edit position #1 // Set edit position #1
if (split[0].equalsIgnoreCase("/editpos1")) { if (split[0].equalsIgnoreCase("/editpos1")) {
@ -231,6 +209,24 @@ public class WorldEdit extends Plugin {
player.sendMessage(Colors.LightPurple + "Second edit position set."); player.sendMessage(Colors.LightPurple + "Second edit position set.");
return true; return true;
// Undo
} else if (split[0].equalsIgnoreCase("/editundo")) {
if (session.undo()) {
player.sendMessage(Colors.LightPurple + "Undo successful.");
} else {
player.sendMessage(Colors.Rose + "Nothing to undo.");
}
return true;
// Redo
} else if (split[0].equalsIgnoreCase("/editredo")) {
if (session.redo()) {
player.sendMessage(Colors.LightPurple + "Redo successful.");
} else {
player.sendMessage(Colors.Rose + "Nothing to redo.");
}
return true;
// Fill a hole // Fill a hole
} else if (split[0].equalsIgnoreCase("/editfill")) { } else if (split[0].equalsIgnoreCase("/editfill")) {
checkArgs(split, 1); checkArgs(split, 1);
@ -243,11 +239,14 @@ public class WorldEdit extends Plugin {
int cz = (int)Math.floor(player.getZ()); int cz = (int)Math.floor(player.getZ());
int minY = Math.max(-128, cy - depth); int minY = Math.max(-128, cy - depth);
int affected = fill(cx, cz, cx, cy, cz, blockType, radius, minY); int affected = fill(editSession, cx, cz, cx, cy, cz,
blockType, radius, minY);
logger.log(Level.INFO, player.getName() + " used /editfill"); logger.log(Level.INFO, player.getName() + " used /editfill");
player.sendMessage(Colors.LightPurple + affected + " block(s) have been created."); player.sendMessage(Colors.LightPurple + affected + " block(s) have been created.");
session.remember(editSession);
return true; return true;
// Remove blocks above current position // Remove blocks above current position
@ -262,8 +261,8 @@ public class WorldEdit extends Plugin {
for (int x = cx - size; x <= cx + size; x++) { for (int x = cx - size; x <= cx + size; x++) {
for (int z = cz - size; z <= cz + size; z++) { for (int z = cz - size; z <= cz + size; z++) {
for (int y = cy; y <= 127; y++) { for (int y = cy; y <= 127; y++) {
if (getBlock(x, y, z) != 0) { if (editSession.getBlock(x, y, z) != 0) {
setBlock(x, y, z, 0); editSession.setBlock(x, y, z, 0);
affected++; affected++;
} }
} }
@ -273,6 +272,8 @@ public class WorldEdit extends Plugin {
logger.log(Level.INFO, player.getName() + " used /removeabove"); logger.log(Level.INFO, player.getName() + " used /removeabove");
player.sendMessage(Colors.LightPurple + affected + " block(s) have been removed."); player.sendMessage(Colors.LightPurple + affected + " block(s) have been removed.");
session.remember(editSession);
return true; return true;
// Run an editscript // Run an editscript
@ -321,7 +322,8 @@ public class WorldEdit extends Plugin {
Context.javaToJS(scriptPlayer, scope)); Context.javaToJS(scriptPlayer, scope));
// Add Minecraft context // Add Minecraft context
EditScriptMinecraftContext minecraft = new EditScriptMinecraftContext(); EditScriptMinecraftContext minecraft =
new EditScriptMinecraftContext(editSession);
ScriptableObject.putProperty(scope, "minecraft", ScriptableObject.putProperty(scope, "minecraft",
Context.javaToJS(minecraft, scope)); Context.javaToJS(minecraft, scope));
@ -332,6 +334,7 @@ public class WorldEdit extends Plugin {
re.printStackTrace(); re.printStackTrace();
} finally { } finally {
Context.exit(); Context.exit();
session.remember(editSession);
} }
return true; return true;
@ -363,7 +366,7 @@ public class WorldEdit extends Plugin {
for (int x = lowerX; x <= upperX; x++) { for (int x = lowerX; x <= upperX; x++) {
for (int y = lowerY; y <= upperY; y++) { for (int y = lowerY; y <= upperY; y++) {
for (int z = lowerZ; z <= upperZ; z++) { for (int z = lowerZ; z <= upperZ; z++) {
setBlock(x, y, z, blockType); editSession.setBlock(x, y, z, blockType);
affected++; affected++;
} }
} }
@ -372,6 +375,8 @@ public class WorldEdit extends Plugin {
logger.log(Level.INFO, player.getName() + " used /editset"); logger.log(Level.INFO, player.getName() + " used /editset");
player.sendMessage(Colors.LightPurple + affected + " block(s) have been set."); player.sendMessage(Colors.LightPurple + affected + " block(s) have been set.");
session.remember(editSession);
return true; return true;
// Replace all blocks in the region // Replace all blocks in the region
@ -384,8 +389,8 @@ public class WorldEdit extends Plugin {
for (int x = lowerX; x <= upperX; x++) { for (int x = lowerX; x <= upperX; x++) {
for (int y = lowerY; y <= upperY; y++) { for (int y = lowerY; y <= upperY; y++) {
for (int z = lowerZ; z <= upperZ; z++) { for (int z = lowerZ; z <= upperZ; z++) {
if (getBlock(x, y, z) != 0) { if (editSession.getBlock(x, y, z) != 0) {
setBlock(x, y, z, blockType); editSession.setBlock(x, y, z, blockType);
affected++; affected++;
} }
} }
@ -395,6 +400,8 @@ public class WorldEdit extends Plugin {
logger.log(Level.INFO, player.getName() + " used /editreplace"); logger.log(Level.INFO, player.getName() + " used /editreplace");
player.sendMessage(Colors.LightPurple + affected + " block(s) have been replaced."); player.sendMessage(Colors.LightPurple + affected + " block(s) have been replaced.");
session.remember(editSession);
return true; return true;
// Lay blocks over an area // Lay blocks over an area
@ -411,8 +418,8 @@ public class WorldEdit extends Plugin {
for (int x = lowerX; x <= upperX; x++) { for (int x = lowerX; x <= upperX; x++) {
for (int z = lowerZ; z <= upperZ; z++) { for (int z = lowerZ; z <= upperZ; z++) {
for (int y = upperY; y >= lowerY; y--) { for (int y = upperY; y >= lowerY; y--) {
if (y + 1 <= 127 && getBlock(x, y, z) != 0 && getBlock(x, y + 1, z) == 0) { if (y + 1 <= 127 && editSession.getBlock(x, y, z) != 0 && editSession.getBlock(x, y + 1, z) == 0) {
setBlock(x, y + 1, z, blockType); editSession.setBlock(x, y + 1, z, blockType);
affected++; affected++;
break; break;
} }
@ -423,13 +430,16 @@ public class WorldEdit extends Plugin {
logger.log(Level.INFO, player.getName() + " used /editoverlay"); logger.log(Level.INFO, player.getName() + " used /editoverlay");
player.sendMessage(Colors.LightPurple + affected + " block(s) have been overlayed."); player.sendMessage(Colors.LightPurple + affected + " block(s) have been overlayed.");
session.remember(editSession);
return true; return true;
} }
return false; return false;
} }
private int fill(int x, int z, int cx, int cy, int cz, int blockType, int radius, int minY) { private int fill(EditSession editSession, int x, int z, int cx, int cy,
int cz, int blockType, int radius, int minY) {
double dist = Math.sqrt(Math.pow(cx - x, 2) + Math.pow(cz - z, 2)); double dist = Math.sqrt(Math.pow(cx - x, 2) + Math.pow(cz - z, 2));
int affected = 0; int affected = 0;
@ -437,26 +447,27 @@ public class WorldEdit extends Plugin {
return 0; return 0;
} }
if (getBlock(x, cy, z) == 0) { if (editSession.getBlock(x, cy, z) == 0) {
affected = fillY(x, cy, z, blockType, minY); affected = fillY(editSession, x, cy, z, blockType, minY);
} else { } else {
return 0; return 0;
} }
affected += fill(x + 1, z, cx, cy, cz, blockType, radius, minY); affected += fill(editSession, x + 1, z, cx, cy, cz, blockType, radius, minY);
affected += fill(x - 1, z, cx, cy, cz, blockType, radius, minY); affected += fill(editSession, x - 1, z, cx, cy, cz, blockType, radius, minY);
affected += fill(x, z + 1, cx, cy, cz, blockType, radius, minY); affected += fill(editSession, x, z + 1, cx, cy, cz, blockType, radius, minY);
affected += fill(x, z - 1, cx, cy, cz, blockType, radius, minY); affected += fill(editSession, x, z - 1, cx, cy, cz, blockType, radius, minY);
return affected; return affected;
} }
private int fillY(int x, int cy, int z, int blockType, int minY) { private int fillY(EditSession editSession, int x, int cy,
int z, int blockType, int minY) {
int affected = 0; int affected = 0;
for (int y = cy; y > minY; y--) { for (int y = cy; y > minY; y--) {
if (getBlock(x, y, z) == 0) { if (editSession.getBlock(x, y, z) == 0) {
setBlock(x, y, z, blockType); editSession.setBlock(x, y, z, blockType);
affected++; affected++;
} else { } else {
break; break;

View File

@ -18,16 +18,71 @@
*/ */
import com.sk89q.worldedit.*; import com.sk89q.worldedit.*;
import java.util.LinkedList;
/** /**
* *
* @author sk89q * @author sk89q
*/ */
public class WorldEditSession { public class WorldEditSession {
public static final int MAX_HISTORY_SIZE = 15;
private int[] pos1 = new int[3]; private int[] pos1 = new int[3];
private int[] pos2 = new int[3]; private int[] pos2 = new int[3];
private boolean hasSetPos1 = false; private boolean hasSetPos1 = false;
private boolean hasSetPos2 = false; private boolean hasSetPos2 = false;
private LinkedList<EditSession> history = new LinkedList<EditSession>();
private int historyPointer = 0;
/**
* Get the edit session.
*
* @return
*/
public void remember(EditSession editSession) {
// Don't store anything if no changes were made
if (editSession.size() == 0) { return; }
// Destroy any sessions after this undo point
while (historyPointer < history.size()) {
history.remove(historyPointer);
}
history.add(editSession);
while (history.size() > MAX_HISTORY_SIZE) {
history.remove(0);
}
historyPointer = history.size();
}
/**
* Undo.
*
* @return whether anything was undoed
*/
public boolean undo() {
historyPointer--;
if (historyPointer >= 0) {
history.get(historyPointer).undo();
return true;
} else {
historyPointer = 0;
return false;
}
}
/**
* Redo.
*
* @return whether anything was redoed
*/
public boolean redo() {
if (historyPointer < history.size()) {
history.get(historyPointer).redo();
historyPointer++;
return true;
}
return false;
}
private void checkPos1() throws IncompleteRegionException { private void checkPos1() throws IncompleteRegionException {
if (!hasSetPos1) { if (!hasSetPos1) {

View File

@ -0,0 +1,75 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010 sk89q <http://www.sk89q.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit;
import org.apache.commons.lang3.builder.HashCodeBuilder;
/**
*
* @author Albert
*/
public final class Point {
private final double x, y, z;
public Point(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
}
/**
* @return the x
*/
public double getX() {
return x;
}
/**
* @return the y
*/
public double getY() {
return y;
}
/**
* @return the z
*/
public double getZ() {
return z;
}
public boolean equals(Object obj) {
if (!(obj instanceof Point)) {
return false;
}
Point other = (Point)obj;
return other.x == x && other.y == y && other.z == z;
}
public int hashCode() {
return new HashCodeBuilder(451, 41).
append(x).
append(y).
append(z).
toHashCode();
}
}