7

I am trying to update blocks in Minecraft within a Bukkit mod and be able to //undo those changes within Minecraft. I can change the block but I cannot //undo the change.

I must be missing something simple since Google hasn't helped me find a solution.

Here is my mod. It sets a single block from the currently selected region to air. The commented out lines are things I have tried that didn't work for me.

public class Main extends JavaPlugin implements Listener
{
    // ... //

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) 
    {
        if (command.getName().equalsIgnoreCase("setair")) 
        {           
            org.bukkit.entity.Player bukkitPlayer = (org.bukkit.entity.Player) sender;  

            WorldEditPlugin worldEditPlugin = null;
            worldEditPlugin = (WorldEditPlugin) Bukkit.getServer().getPluginManager().getPlugin("WorldEdit");
            if(worldEditPlugin == null){
                bukkitPlayer.sendMessage("Error: WorldEdit is null.");   
            }
            else
            {               
                com.sk89q.worldedit.bukkit.selections.Selection s = worldEditPlugin.getSelection(bukkitPlayer);
                com.sk89q.worldedit.LocalSession localSession = worldEditPlugin.getSession(bukkitPlayer);
                com.sk89q.worldedit.world.World localWorld = localSession.getSelectionWorld();
                com.sk89q.worldedit.bukkit.BukkitPlayer wrappedPlayer = worldEditPlugin.wrapPlayer(bukkitPlayer);
                com.sk89q.worldedit.LocalPlayer localPlayer = wrappedPlayer;
                //com.sk89q.worldedit.world.World localWorld2 = localPlayer.getWorld();

                com.sk89q.worldedit.EditSession editSession = worldEditPlugin.getWorldEdit().getEditSessionFactory().getEditSession(localWorld, -1, localPlayer);
                //com.sk89q.worldedit.EditSession editSession = worldEditPlugin.createEditSession(bukkitPlayer);

                //localSession.remember(editSession);

                Vector minV = s.getNativeMinimumPoint();
                try {
                    editSession.setBlock(minV, new com.sk89q.worldedit.blocks.BaseBlock(0,0));
                } catch (MaxChangedBlocksException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }

                //try {
                //  localWorld.setBlock(minV, new com.sk89q.worldedit.blocks.BaseBlock(0,0));
                //} catch (WorldEditException e) {
                    // TODO Auto-generated catch block
                //  e.printStackTrace();
                //}


                localSession.getRegionSelector(localWorld).learnChanges();
                localSession.getRegionSelector(localWorld).explainRegionAdjust(localPlayer, localSession);

                bukkitPlayer.performCommand("tellraw @p \"Done setair\"");
            }

            return true;
        }
    }
}

EDIT: Here is what works. Thanks sorifiend for the answer below. To get it to work, I also had to move localSession.remember(editSession) to after the setBlock call.

@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) 
{
    if (command.getName().equalsIgnoreCase("setair")) 
    {           
        org.bukkit.entity.Player bukkitPlayer = (org.bukkit.entity.Player) sender;  

        WorldEditPlugin worldEditPlugin = null;
        worldEditPlugin = (WorldEditPlugin) Bukkit.getServer().getPluginManager().getPlugin("WorldEdit");
        if(worldEditPlugin == null){
            bukkitPlayer.sendMessage("Error: WorldEdit is null.");   
        }
        else
        {               
            com.sk89q.worldedit.bukkit.selections.Selection s = worldEditPlugin.getSelection(bukkitPlayer);
            com.sk89q.worldedit.LocalSession localSession = worldEditPlugin.getSession(bukkitPlayer);

            com.sk89q.worldedit.EditSession editSession = worldEditPlugin.createEditSession(bukkitPlayer);

            Vector minV = s.getNativeMinimumPoint();
            try {
                editSession.setBlock(minV, new com.sk89q.worldedit.blocks.BaseBlock(0,0));
            } catch (MaxChangedBlocksException e) {
                e.printStackTrace();
            }

            localSession.remember(editSession);

            bukkitPlayer.performCommand("tellraw @p \"Done setair\"");
        }

        return true;
    }
}

Now I can select something with WorldEdit, run /setair to set one of the blocks to air. And //undo does what you'd expect.

Ian
  • 841
  • 5
  • 10
  • I don't understand why this doesn't work `editSession = worldEditPlugin.createEditSession(bukkitPlayer);`. What sort of errors do you get? If you are doing it your way then you need to use `editSession.enableQueue();` before setting the block. – sorifiend Aug 21 '18 at 04:05

1 Answers1

2

I don't understand why this doesn't work editSession = worldEditPlugin.createEditSession(bukkitPlayer);, however because you have chosen to do it the longer way worldEditPlugin.getWorldEdit().getEditSessionFactory().getEditSession(bukkitPlayer) you will also need to use: editSession.enableQueue(); afterwards.


The next issue may be in how you are setting the block. Take a quick look at the setBlock methods in the source code. there is a number that state:

/**
 * Set a block, bypassing history but still utilizing block re-ordering.
 *
 * @param position the position to set the block at
 * @param block the block
 * @return whether the block changed
 */ 
 public boolean setBlock(Vector position, BlockStateHolder block) {

Note how it says "Set a block, bypassing both history and block re-ordering".

So if you want to remember the block change you are going to need to keep track of it yourself, or use a different setBlock method:

/**
 * Sets the block at a position, subject to both history and block re-ordering.
 *
 * @param position the position
 * @param pattern a pattern to use
 * @return Whether the block changed -- not entirely dependable
 * @throws MaxChangedBlocksException thrown if too many blocks are changed
 */
 public boolean setBlock(Vector position, Pattern pattern)

Note how it says "Sets the block at a position, subject to both history and block re-ordering".

For example this will set a block to air and will also retain the block history:

Pattern pattern = new BlockPattern(BlockTypes.AIR.getDefaultState());
editSession.setBlock(minV, pattern);

Now you can use the undo method later on like this:

//use a different EditSession to perform the undo in:
EditSession undoSession = ......;
editSession.undo(undoSession);

Note that the undoSession should not be the same as the session you are trying to undo.

Edit: The source code is currently going through a number of edits/changes for version 1.13 support (The EditSession class was updated 23h ago). So your WorldEdit library/plugin may need updating before you continue.

sorifiend
  • 5,927
  • 1
  • 28
  • 45
  • Thanks a lot! I use `public boolean setBlock(Vector position, BlockStateHolder block)` because in [worldedit/EditSession.java](https://github.com/sk89q/WorldEdit/blob/master/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java) it does **not** have the comment that says it bypasses history. – Ian Aug 22 '18 at 19:07