From 7387e360161dbcd67b1b443721288e7be0c3881d Mon Sep 17 00:00:00 2001 From: sk89q Date: Sat, 2 Oct 2010 14:52:42 -0700 Subject: [PATCH] Added basic undo/redo functionality with a history size of 15. --- manifest.mf | 2 +- src/EditScriptMinecraftContext.java | 15 +++- src/EditSession.java | 107 ++++++++++++++++++++++++++++ src/WorldEdit.java | 99 +++++++++++++------------ src/WorldEditSession.java | 55 ++++++++++++++ src/com/sk89q/worldedit/Point.java | 75 +++++++++++++++++++ 6 files changed, 306 insertions(+), 47 deletions(-) create mode 100644 src/EditSession.java create mode 100644 src/com/sk89q/worldedit/Point.java diff --git a/manifest.mf b/manifest.mf index a9afe32fc..5b0e0b1d4 100644 --- a/manifest.mf +++ b/manifest.mf @@ -1,3 +1,3 @@ Manifest-Version: 1.0 -Class-Path: js.jar +Class-Path: js.jar commons-lang3-3.0-beta.jar diff --git a/src/EditScriptMinecraftContext.java b/src/EditScriptMinecraftContext.java index 99fd7c9a3..e743427ff 100644 --- a/src/EditScriptMinecraftContext.java +++ b/src/EditScriptMinecraftContext.java @@ -22,6 +22,17 @@ * @author Albert */ 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. * @@ -32,7 +43,7 @@ public class EditScriptMinecraftContext { * @return */ 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 */ public int getBlock(int x, int y, int z) { - return etc.getMCServer().e.a(x, y, z); + return editSession.getBlock(x, y, z); } } diff --git a/src/EditSession.java b/src/EditSession.java new file mode 100644 index 000000000..fd0965fc3 --- /dev/null +++ b/src/EditSession.java @@ -0,0 +1,107 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010 sk89q + * + * 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 . +*/ + +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 original = new HashMap(); + private HashMap current = new HashMap(); + + /** + * 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 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 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(); + } +} diff --git a/src/WorldEdit.java b/src/WorldEdit.java index e4ddc5ced..476687fd4 100644 --- a/src/WorldEdit.java +++ b/src/WorldEdit.java @@ -39,6 +39,8 @@ public class WorldEdit extends Plugin { commands.put("/editpos1", "Set editing position #1"); 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("/editset", " - Set all blocks inside region"); commands.put("/editreplace", " - 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 @@ -214,6 +191,7 @@ public class WorldEdit extends Plugin { InsufficientArgumentsException, DisallowedItemException { WorldEditSession session = getSession(player); + EditSession editSession = new EditSession(); // Set edit position #1 if (split[0].equalsIgnoreCase("/editpos1")) { @@ -231,6 +209,24 @@ public class WorldEdit extends Plugin { player.sendMessage(Colors.LightPurple + "Second edit position set."); 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 } else if (split[0].equalsIgnoreCase("/editfill")) { checkArgs(split, 1); @@ -243,11 +239,14 @@ public class WorldEdit extends Plugin { int cz = (int)Math.floor(player.getZ()); 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"); player.sendMessage(Colors.LightPurple + affected + " block(s) have been created."); + session.remember(editSession); + return true; // 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 z = cz - size; z <= cz + size; z++) { for (int y = cy; y <= 127; y++) { - if (getBlock(x, y, z) != 0) { - setBlock(x, y, z, 0); + if (editSession.getBlock(x, y, z) != 0) { + editSession.setBlock(x, y, z, 0); affected++; } } @@ -273,6 +272,8 @@ public class WorldEdit extends Plugin { logger.log(Level.INFO, player.getName() + " used /removeabove"); player.sendMessage(Colors.LightPurple + affected + " block(s) have been removed."); + session.remember(editSession); + return true; // Run an editscript @@ -321,7 +322,8 @@ public class WorldEdit extends Plugin { Context.javaToJS(scriptPlayer, scope)); // Add Minecraft context - EditScriptMinecraftContext minecraft = new EditScriptMinecraftContext(); + EditScriptMinecraftContext minecraft = + new EditScriptMinecraftContext(editSession); ScriptableObject.putProperty(scope, "minecraft", Context.javaToJS(minecraft, scope)); @@ -332,6 +334,7 @@ public class WorldEdit extends Plugin { re.printStackTrace(); } finally { Context.exit(); + session.remember(editSession); } return true; @@ -363,7 +366,7 @@ public class WorldEdit extends Plugin { for (int x = lowerX; x <= upperX; x++) { for (int y = lowerY; y <= upperY; y++) { for (int z = lowerZ; z <= upperZ; z++) { - setBlock(x, y, z, blockType); + editSession.setBlock(x, y, z, blockType); affected++; } } @@ -372,6 +375,8 @@ public class WorldEdit extends Plugin { logger.log(Level.INFO, player.getName() + " used /editset"); player.sendMessage(Colors.LightPurple + affected + " block(s) have been set."); + session.remember(editSession); + return true; // Replace all blocks in the region @@ -384,8 +389,8 @@ public class WorldEdit extends Plugin { for (int x = lowerX; x <= upperX; x++) { for (int y = lowerY; y <= upperY; y++) { for (int z = lowerZ; z <= upperZ; z++) { - if (getBlock(x, y, z) != 0) { - setBlock(x, y, z, blockType); + if (editSession.getBlock(x, y, z) != 0) { + editSession.setBlock(x, y, z, blockType); affected++; } } @@ -395,6 +400,8 @@ public class WorldEdit extends Plugin { logger.log(Level.INFO, player.getName() + " used /editreplace"); player.sendMessage(Colors.LightPurple + affected + " block(s) have been replaced."); + session.remember(editSession); + return true; // Lay blocks over an area @@ -411,8 +418,8 @@ public class WorldEdit extends Plugin { for (int x = lowerX; x <= upperX; x++) { for (int z = lowerZ; z <= upperZ; z++) { for (int y = upperY; y >= lowerY; y--) { - if (y + 1 <= 127 && getBlock(x, y, z) != 0 && getBlock(x, y + 1, z) == 0) { - setBlock(x, y + 1, z, blockType); + if (y + 1 <= 127 && editSession.getBlock(x, y, z) != 0 && editSession.getBlock(x, y + 1, z) == 0) { + editSession.setBlock(x, y + 1, z, blockType); affected++; break; } @@ -423,13 +430,16 @@ public class WorldEdit extends Plugin { logger.log(Level.INFO, player.getName() + " used /editoverlay"); player.sendMessage(Colors.LightPurple + affected + " block(s) have been overlayed."); + session.remember(editSession); + return true; } 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)); int affected = 0; @@ -437,26 +447,27 @@ public class WorldEdit extends Plugin { return 0; } - if (getBlock(x, cy, z) == 0) { - affected = fillY(x, cy, z, blockType, minY); + if (editSession.getBlock(x, cy, z) == 0) { + affected = fillY(editSession, x, cy, z, blockType, minY); } else { return 0; } - affected += fill(x + 1, z, cx, cy, cz, blockType, radius, minY); - affected += fill(x - 1, z, cx, cy, cz, blockType, radius, minY); - affected += fill(x, z + 1, cx, cy, cz, blockType, radius, minY); - affected += fill(x, z - 1, cx, cy, cz, blockType, radius, minY); + affected += fill(editSession, x + 1, z, cx, cy, cz, blockType, radius, minY); + affected += fill(editSession, x - 1, z, cx, cy, cz, blockType, radius, minY); + affected += fill(editSession, x, z + 1, cx, cy, cz, blockType, radius, minY); + affected += fill(editSession, x, z - 1, cx, cy, cz, blockType, radius, minY); 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; for (int y = cy; y > minY; y--) { - if (getBlock(x, y, z) == 0) { - setBlock(x, y, z, blockType); + if (editSession.getBlock(x, y, z) == 0) { + editSession.setBlock(x, y, z, blockType); affected++; } else { break; diff --git a/src/WorldEditSession.java b/src/WorldEditSession.java index e6d739e73..7104b511f 100644 --- a/src/WorldEditSession.java +++ b/src/WorldEditSession.java @@ -18,16 +18,71 @@ */ import com.sk89q.worldedit.*; +import java.util.LinkedList; /** * * @author sk89q */ public class WorldEditSession { + public static final int MAX_HISTORY_SIZE = 15; private int[] pos1 = new int[3]; private int[] pos2 = new int[3]; private boolean hasSetPos1 = false; private boolean hasSetPos2 = false; + private LinkedList history = new LinkedList(); + 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 { if (!hasSetPos1) { diff --git a/src/com/sk89q/worldedit/Point.java b/src/com/sk89q/worldedit/Point.java new file mode 100644 index 000000000..2d9ce5dec --- /dev/null +++ b/src/com/sk89q/worldedit/Point.java @@ -0,0 +1,75 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010 sk89q + * + * 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 . +*/ + +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(); + } + +}