Making Block changes non-permanent

Discussion in 'Plugin Development' started by Jatoc, Jun 7, 2013.

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


    Off the top of my head, a method of making player changes non-permanent would be to set up a hashmap of all the blocks a player places/breaks and then run a scheduler that loops through this hashmap and reverts changes every time it's run if enough time has passed. However, can a experienced coder who has worked with this type of anti-grief tell me how this will affect server load, or if there's a more efficient way to do it? (I'll probably replace ores with regular stone blocks during the revert btw, to keep material inflation in check.)
  2. Offline


    You could just replace the world with a backup copy.
  3. Offline


    That would mean replacing all the blocks... I want this to happen every 5 minutes or so.
  4. Offline


    Run a delayed task to restore the block.
  5. Offline


    Specifically, you can use Google Guava to simplify the code (download):
    1. public class TemporaryModifcations extends JavaPlugin implements Listener {
    2. public static int TICKS_PER_SECOND = 20;
    4. private static class BlockData {
    5. private WeakReference<World> worldReference;
    6. private int typeID;
    7. private byte data;
    9. public BlockData(Block block) {
    10. this.worldReference = new WeakReference<World>(block.getWorld());
    11. this.typeID = block.getTypeId();
    12. = block.getData();
    13. }
    15. /**
    16.   * Restore the current block.
    17.   * @return TRUE if the block was restored, FALSE otherwise.
    18.   */
    19. public boolean restore(BlockVector vector) {
    20. if (getWorld() != null) {
    21. Block block = getWorld().getBlockAt(vector.toLocation(getWorld()));
    23. // Restore type and data
    24. block.setTypeId(typeID);
    25. block.setData(data);
    26. return true;
    27. } else {
    28. return false;
    29. }
    30. }
    32. public World getWorld() {
    33. return worldReference.get();
    34. }
    35. }
    37. private Cache<BlockVector, BlockData> restoreQueue;
    39. @Override
    40. public void onLoad() {
    41. restoreQueue = CacheBuilder.
    42. newBuilder().
    43. concurrencyLevel(2).
    44. weakValues().
    45. expireAfterWrite(10, TimeUnit.SECONDS).
    46. removalListener(new RemovalListener<BlockVector, BlockData>() {
    47. @Override
    48. public void onRemoval(RemovalNotification<BlockVector, BlockData> entry) {
    49. entry.getValue().restore(entry.getKey());
    50. }
    51. }).
    52. build(new CacheLoader<BlockVector, BlockData>() {
    53. @Override
    54. public BlockData load(BlockVector arg0) throws Exception {
    55. throw new RuntimeException("Unsupported! Cannot generate entries, must be placed manually.");
    56. }
    57. });
    58. }
    60. @Override
    61. public void onEnable() {
    62. PluginManager manager = getServer().getPluginManager();
    63. manager.registerEvents(this, this);
    65. // Run the block cleanup once per second
    66. getServer().getScheduler().scheduleSyncRepeatingTask(this, new Runnable() {
    67. @Override
    68. public void run() {
    69. restoreQueue.cleanUp();
    70. }
    72. }
    74. @EventHandler
    75. public void onBlockBreakEvent(BlockBreakEvent event) {
    76. BlockData data = new BlockData(event.getBlock());
    77. BlockVector vector = new BlockVector(event.getBlock().getLocation().toVector());
    79. // Expire after two minutes
    80. restoreQueue.asMap().put(vector, data);
    81. }
    82. }

    Undoing placed blocks is left as an exercise to the reader. :p
    microgeek likes this.
  6. Offline


    Why letting the block be destroyed in the first place?
    Just block the destruction if the player is not allowed to destroy it.

    Setting blocks after some time can cause a player to get stuck if suddenly blocks will appear around him.
    I my eyes this is very inefficient and way to much side effects have to be dealt with.
  7. Offline


    No, I understand where he's coming from. From a design point of view, doing it this way would create an effect of self restoration. Wouldn't necessarily have to be used for anti griefing.

    I've been thinking about an effect like this for a while, and I can't come up with a better way than what you've already proposed. Only I wouldn't use a hashmap, I would use a List. Hasmap are made for key and value pairs and I don't see that being majorly important in this situation. A List of blocks would just store the blocks, reducing size. Plus, it's easier to run through a List than a hashmap, IMO. Lists are just auto sizing arrays that don't need a specific size when declared.

    I don't think there would be much server load unless your using this system in a vanilla-esk like server where players are constantly breaking blocks. If that's the case, I would try limiting the amount of blocks being saved. Find a way to specify what blocks need to be saved, and if any of those blocks are broken, then put it into the array to be restored later. I can see this being used for city buildings. If this building is broke in any way, restore it later. But in that scenario, I would just stop the breaking in the first place.
  8. Offline


    Well basically, I wanted to use this feature for a wilderness area where players would be able to run around, build temporary shelters to hide out, etc. but I don't want it to create a permanent mark on the landscape. Maybe I should only store blocks within a world-edit region in order to minimize on what will be stored/restored? Also, I was thinking hashmap because <key, value> = <blockID, location> unless you were thinking of something else with lists?
  9. Offline


    Well, Blocks hold their location and type. So I was thinking to make it easier, just store the block and get the location and type from there. Plus its easier to iterate through lists than hashmaps, IMO. On break events, store the block they broke. On place events, store an air block at that location.
  10. Offline


    oh yea. In that case, what about timestamp? something like <time, block> (in order to ensure you don't replace a block too quickly)
  11. Offline


    What about a recursive task that would run every 5 minutes or so (as you said), pull recently broken/placed block locations with their previous Material and data from a Map (emptying the Map), and schedule a task 5 minutes later to restore those blocks? It wouldn't be much more efficient, but I'm just throwing ideas out there.

    Alternatively, you could do everything async, except the actual changes, seeing as the vast majority of the Bukkit API isn't thread-safe.
  12. Offline


    Yea that's what sorta I was originally planning on doing. Have a hashmap with <timestamp, block> and have a scheduled task that does something like
    if(currentTime > timestamp + 600)
    My only worry is how it will affect player inventories and performance. For inventories, I'm thinking:

    Make all blocks that drops within the region specified (the auto-regen region) drop blocks that have a a metadata value with the region they belong to. Players can throw away the blocks (item will disappear if dropped), or they can place them again (only within the region the block originally came from). The only problem is metadata isn't persistent (at least from what I've done with a cursory test) so that things could screw up if the server restarts/etc. Do you have any ideas on how to go about making blocks "untradable"?

    As for performance, I'm new to this so I don't know how much load this would put. Also, if you do things async, how would that work? (Never done that before)
  13. Offline


    So, if I'm understanding you correctly, you want to make it so that players can't give other players the blocks they break, yes? If that's the case, what I would do is add lore containing the player's name to the item, and check whether or not the player picking up the item has a name equalling the lore. If you need to do more than that, you can just add more lore.

    Doing things async would be a bit more complicated, but it might be worth it. Basically, you'd have a separate thread running that would periodically poll a synchronized HashMap (or a ConcurrentHashMap), and whenever you needed to access Bukkit API methods, you would create a scheduler task. You would still need to add the data to the Map and restore the blocks on the main thread, but everything else could be done async.
  14. Offline


    But isn't the dataadding and block restoration kinda the entire point of the plugin? What stuff would even need to be done async?
  15. Offline


    Iteration, mostly. Besides, adding data to a Map is an extremely quick operation, so you really don't need to worry about optimization there. As for block restoration, depending on the number of blocks that need to be changed, you could group them together in small bundles and schedule them for separate ticks to help reduce the load.
  16. Offline


    Interesting. Do you know where I can learn more about async scheduling?
  17. Offline


    Definitely read up on the concept of concurrency in Java. It's extremely useful to know. Aside from that, just be familiar with the Bukkit scheduler.
Thread Status:
Not open for further replies.

Share This Page