Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Minecraft's WorldEdit undo stack in a Bukkit mod

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.

like image 601
Ian Avatar asked Aug 20 '18 22:08

Ian


People also ask

Can you undo in WorldEdit?

Undo and Redo By default, WorldEdit stores your last 15 actions (this can be changed in the configuration). If you ever want to undo an action, you can use the //undo command. This will undo your last action. To redo it, use //redo .

How do you stack with WorldEdit?

The most basic command one can use is //stack without any arguments. This will duplicate the selected region in the direction where the player is facing. The duplicated regions are directly adjacent to each other.

How do you flip WorldEdit?

Hover your bottom half directly next to the plane or place a block and stand on it. The player's position is indicated for 'flipping'. Look directly into the region then do "//copy" then "//flip down". The player's facing direction is also indicated for 'flipping'.


1 Answers

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.

like image 56
sorifiend Avatar answered Sep 19 '22 21:09

sorifiend