Best way to roll back an area?

Discussion in 'Plugin Development' started by Etsijä, Jun 2, 2014.

Thread Status:
Not open for further replies.
  1. Offline


    Remember the good old Scorched Earth video game?

    I'm planning to create a plugin which simulates this game in Minecraft world. It will be based on my plugin, which already has "dispenser cannons" which can be redirected.

    Play area will be quite large, some 400 x 400 blocks, and the area will consist solely of falling blocks: sand and gravel. This means that there will be quite a lot of environment demolishing during the game. I am considering writing my own rollback functionality into the plugin, but before I decide whether I'll do it or instead use one of the minigame plugins available (none are _quite_ up to my expectations when it comes to rollback), I would like to ask my fellow plugin developers who have done this before:

    What would be the most efficient and lag-free implementation of a rollback of a 3D area? I'm thinking textfile as a datastore (storing the blocks in the start) would be just terrible. Binary files, certainly better. SQLite/MySQL then? And, timed tasks for the actual rollback, so as not to kill the whole server?

    And, how actually would you do this (or have done it)? How to implement a full rollback of a 3D area which might be something like 400 x 100 x 400 blocks in size?

    Any thoughts on this matter are welcome.
  2. Offline


    I do this pretty regularly - regenerating PvP arenas being the main example (My plugin is extremely destructive).

    I use schematics for that kind of thing, generally. You can use the WorldEdit API to load schematics. You can theoretically use it to paste the schematic, too, but not in a "lag free" way as you request.

    I have my own construction framework that spreads large modifications out over multiple ticks- so you walk through every block in the schematic, and apply 100-1000 or so changes to the world per tick.

    Another option that may be easier (and which I also use to great effect for a "Repair" spell) is to keep a secondary "backup" world, that is a pristine copy of your world. Then instead of loading a schematic, you just copy blocks from one world to the other- but you still have to spread it out over multiple ticks.

    If you are interested, you could actually just use Magic to do all of this. You don't even need to use the wands or any of the actual gameplay features of the plugin- but a scripted call to the "repair" on a redstone clock or whatever would get you what you want here. Let me know if you want I can help you set that up.
  3. Offline


    Many thanks for your insights, NathanWolf. I will consider the options and ask more when I know what to ask :) I have zero experience on schematics, so don't really have a clue as to how to use them. Copying world-to-world sounds pretty simple, but the overhead of the world copy worries me.

    NathanWolf: If you could spare a minute, I would be grateful to hear more of both approaches - schematics and the world-to-world copy. As I said, I have zero knowledge of schematics. Always wondered what they are in WE but never really bothered to check. World-to-world copy sounds simple enough. If I understand correctly, in my plugin (if I want to do this inside my plugin, rather than By Magic) I would just simply iterate through all blocks in my area and do a copy from the "store world" to my world (spread over multiple ticks, of course). But what about the datavalues and things with inventories (I use dispensers as cannons in my plugin)? Can I also roll back those with contents etc. this way?

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
    Last edited by a moderator: Jun 30, 2016
  4. Offline


    List<BlockState> :D. Every change, push the BlockState to the List. When you need to recover, reverse the List, then iterate over them calling update(true);
  5. Offline


    fireblast709 's method is actually what I use the majority of the time for "rollback". I'm not really reverting an area, I'm undoing changes I've queued up in a list.

    However- I did *not* suggest that here because it's an extremely challenging approach to take when dealing with falling blocks- which, it sounds, is a huge part of what you're trying to do.

    If you want to start looking at the WorldEdit approach, see here:

    A simple test you could do is just save the area to a schematic, and then load it- see how bad the lag is. If it's not bad, then just stop there, you don't need a custom plugin :)

    Option 1: Just use WorldEdit
    1. (select region via //pos1 and //pos2 or the WE wand)
    2. //copy
    3. //schematic save test
    4. ... mess up the area real bad
    5. //schematic load test
    6. //paste
    For a smallish area, this won't be bad. for 400x400x256, though - it might lag for a minute.

    If you want to continue with the WorldEdit approach, see this reference:

    You can create one of these for your saved region, and then load a schematic into it, and call "paste". This is the programmatic way to do what you just did above. It will have the same lag issue, but you're now a step closer.

    Instead of calling "paste", you can pull the blocks out of the schematic, put them into a list, and then walk through that list a few thousand blocks at a time each tick, on a repeating task.

    Keep in mind that .paste() actually does a lot of important stuff that might be an issue if you try to reimplement it. It builds in stages to avoid breaking attachables, and other helpful stuff. If you have a lot of signs, torches, etc- you may have to do that yourself as well.

    Option 2: Use Magic to restore from a schematic
    1. Do steps 1-3 from above to get your region saved as a schematic (You'll still need WorldEdit for this approach)
    2. Download and install my Magic plugin
    3. Use "/wand admin" to give yourself an admin wand
    4. Use "/wand add superblob" to add SuperBlob to your wand
    5. Use "/wand add material schematic:test" to add the schematic you saved to your wand
    6. Cast SuperBlob anywhere in your world to test out restoring/building from a schematic. This takes two clicks- the first click in the center of the area, the second at the "outer edge" (the two clicks define a radius to fill- it's hard to explain but makes sense once you get the hang of it).
    If you followed all that and it seems to work for you, you could script magic to do the same thing - like "/cast blob brush schematic:test x 0 y 64 z 0 radius 200 type box", for instance.

    Option 3: Use Magic to restore from a "backup" world
    1. Download and install my Magic Plugin
    2. Use "/wand admin" to give yourself an admin wand
    3. Select the "Backup" spell (Right-click with wand, shift+click diamond pickaxe icon in menu) - or /wand add backup
    4. Cast Backup in the area you want to save - same as SuperBlob, it's two clicks for the center and radius
    5. Mess up the area real bad
    6. Select SuperRepair (gold pickaxe icon, or /wand add superrepair)
    7. Two-click cast SuperRepair to restore the messed up area
    Similar to Option 2, if that works for you we can script up Magic to do it. I'm not sure what sort of overhead is incurred in having a backup world- I believe it will be almost entirely unloaded most of them time.

    Obviously, I hope- make sure you've backed up your world before trying and of these methods!
  6. Offline


    OK, sounds like I have a lot of options, when using other plugins for this. But what if I want to make a self-contained plugin which does rollback, what are my options then? List<BlockState> really isn't the solution, as all of the blocks in my area are falling ones.

    The simplest solution that comes to my mind is this:
    1. make a selection
    2. store all blocks inside the selection into a binary datafile (this may have to be implemented as a timed task)
    3. do damage to the area
    4. for restoring, loop through the area and see if a block is the same as the block in the datafile. If it is, leave it alone, otherwise replace it. All this certainly has to be implemented as a timed task
    With falling blocks, I'm thinking I have to start the restoring from the bottom of my area and work my way upwards one block layer at the time, to prevent falling issues.

    How does this sound? I have doubts about the efficiency of it: since every block has to be checked, this will be terribly inefficient, if the area is not badly damaged. But, recording only changes would be a terrible mess to revert when almost every block in my area is falling.

    From looking into the CuboidClipboard class, it sounds like I would be best off using WorldEdit programmatically for my rollback implementation. So NathanWolf, have you actually ever implemented your Option 1 in a plugin? I've never used WE's API so am looking for any hints for using it. The way I see this all could be implemented is this:
    1. Use WE to select my region (with //pos or a wand)
    2. Implement my own command to copy this area to a CuboidClipboard and save it (as a schematic? or a snapshot?)
    3. Play the game, messing the area badly
    4. After the game ends, implement paste() method but restoring only a thousand or so blocks per tick (how would I do this?)
    I know my list might be really vague; that's because I've never user WE API. So bear with me :)

    Actually, ChessCraft has some good examples of using the WE API for rollback functionality. Have to study them carefully and do some trials.

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
    Last edited by a moderator: Jun 30, 2016
    NathanWolf likes this.
  7. Offline


    I think you're on the right track here!

    For #2, though- don't implement that yourself, you can programmatically save a schematic through WorldEdit's API, or just use the in-game commands, probably easier.

    I have effectively implemented option #1 in a plugin, since that is what Option # 2 is :) The code is not very self-contained, but if you want to look around at how I do it, here are the three main classes involved:

    WorldEditSchematic: Mainly just an interface layer on top of WE's schematic class. I'm hoping to write my own Schematic loader class eventually.

    ConstructBatch: This is the most common "BlockBatch" in my plugin, it constructs different kind of shapes at different sizes, and powers most of my staple engineering spells, like SuperBlob. This batch object gets created when you cast the spell, and then processed once per tick. You may be able to use some of this logic directly if you want, it's not all that complex- just strip out all the sphere/pyramid stuff.

    FillBatch: Or, actually, just use this one. :p ... now that I think of it, this is more what you want, but I already wrote the ConstructBatch description so meh.

    MaterialBrush: This is my "brush" class that my "engineering" spells use for building. It controls what blocks get places while a construction spell i processing. In this case, it routes to the Schematic, basically asking it "what block goes here?" for every block it builds. This class is really complex for a number of reasons, so maybe ignore it for the most part :)

    Good luck! I hope some of that helps - the only really tricky part here is spreading the world out over multiple ticks. If ChessCraft has some good/clean examples of that, then that may be the way to go. My code for that is kind of engrained in my plugin and hard to really show well (maybe indicating not great architecture on my part...)
  8. Offline


  9. Offline


    NathanWolf Let me thank you sincerely in shedding some light to using WE as an API for what I'm trying to achieve. Your posts have helped me a lot!
    nlthijs48 Thanks a million for the example methods you provided! They seem pretty straightforward.

    The only thing what I'm missing seems to be the knowledge of how to access the schematic N blocks at a time and restore. I will need this, since my game areas are so big. And, I need good control anyway for the order of restore, since my blocks are falling. NathanWolf your WorldEditSchematic class's getBlock() method seems to implement this? I think I could code something I need by combining both of your examples...
    NathanWolf likes this.
  10. Offline


    Yup! That's exactly what I wrote my Schematic class for. In particular I found that WorldEdit itself didn't properly translate some blocks, like tile entities, though that situation may have improved.
  11. Offline


    OK I'm already seeing a class forming up, combining the best of your code, NathanWolf and nlthijs48. I think it is only fair to ask your permission to use the code you've written, for my purposes? I might contribute back by providing a class which does WE schematic save/restore, spread out over ticks, implemented from the bottom up to handle also falling blocks, in case anyone else needs it one day. :)
    NathanWolf likes this.
  12. Offline


    Sounds great!

    Any of my code is free for the taking, no credit or anything required. Hack away!

    Btw, Etsijä - I absolutely loved Scorched Earth when I as a kid, I'd love to see this project once it's working!

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
    Last edited by a moderator: Jun 30, 2016
    Etsijä likes this.
  13. Offline


    Will let you know when it's up and running. I have quite a lot to implement and test before we're even close, so don't expect anything really soon :D
    NathanWolf likes this.
Thread Status:
Not open for further replies.

Share This Page