RollBack Explosions

So I’ve hit a dead spot here. I’m trying to make a loop that’ll wait a certain amount of time before it fixes blocks…any assistance would be awesome :0 thank you

If you want to stop an explosion causing damage then just cancel the ExplosionEvent, or are you looking to specifically allow an explosion then revert it at some point after?

yes i’m trying to revert it after wards for a mini game :slight_smile: please help

Oh an i’m sure i’ll just cancel the drop item event anyway so i’m not too worried about that.

OK so assuming you want to revert a specific explosion (not just a random one like a creeper in the distance) you first need to define a rule so you can target just the explosions you’re interested in.

Now, the event. The event we’re interested in is ExplosionEvent.Detonate, specifically because it is fired when an explosion is calculated and just before it takes effect.
We need to grab all changes the explosion will make. For that make a call to event.getTransactions() this contains every block transaction (i.e before->after) that will be made.
Store this list of transactions, this will be what you rollback later.

In order to be sure the transactions in the event are the transactions that actually take effect in the world, we need to listen to the event at the latest possible time. This means using order=Order.POST in the @Listener annotation.

When it comes to restoring the world, simply grab the stored transaction and revert every change to the original (original block before explosion).

Unless I’ve overlooked something I think that’s all that you need to do it.
Here’s a quick draft up of how you might implement this in code


private final List<List<Transaction<BlockSnapshot>>> capturedExplosionTransactions;

@listener(order = Order.POST)
public void onExplodeEvent(ExplosionEvent.Detonate event) {
    // Your explosion capture rule goes in this 'if' statement
    if (event.getCause().first(Player.class).isPresent() && someOtherConditions) {
        // Capture all changes
        capturedExplosionTransactions.add(event.getTransactions());
    }
}

// Called some point in the future
public void rollbackTransaction(int num) {
    // Get the transaction we're interested in
    List<Transaction<BlockSnapshot>> transactions = capturedExplosionTransactions.remove(num);
    // For every block that was changed:
    for (Transaction<BlockSnapshot> transaction : transactions) {
        transaction.getOriginal().restore(true, true); // Restore the block
    }
}

Disclaimer: This is literally the first thing that popped into by head when I read your question so it may or may not work. Code written directly on this post and not tested.

ok so yes this is part of what i need however where would i imput the amount of time for these to regenerate?

Introducing… The SchedulerService!
The scheduler allows you to create a task that runs at some point in the future and supports repeating tasks.
I suggest you check out the documentation here: https://docs.spongepowered.org/en/plugin/scheduler.html
and the interface source code: https://github.com/SpongePowered/SpongeAPI/blob/master/src/main/java/org/spongepowered/api/service/scheduler/SchedulerService.java

Lets say you want to restore an explosion every minute (you can of course make this configurable in a config or something).

game.getScheduler().createTaskBuilder()
    .execute(task -> {
        // Add stuff to do here
        rollbackTransaction(num);
    })
    .interval(1, TimeUnit.MINUTES) // The interval
    .submit(myPlugin); // Submit the task to the scheduler

Hopefully this will help you get stuck-in creating your plugin :smile:

Ok so quite a bit of issues I followed what you said but i’m getting error’s up and down …help please?

I gave you code snippets, they won’t work on their own. You have to have a basic understanding of how to create a plugin first. I could provide a complete solution but then you won’t learn much,

The variables capturedExplosionTransactions and game are fields in your plugin class.
game is the game instance, you can declare this like so:

@Inject
private Game game;

and capturedExplosionTransactions like:

private final List<List<Transaction<BlockSnapshot>>> capturedExplosionTransactions = Lists.newArrayList();

Remember to import all classes used, in eclipse there is a shortcut, 'Ctrl+Shift+O'

Ok see I have that issue because I was coding in bukkit for the longest times and now i’m trying to recode code to Sponge because I like it :slight_smile: so any and every bit of help is awesome. I only started working on plugins about a year ago…if you could look over what i have and give me any hints that would be awesome…or advice on where to pick up how to’s. cause I have never read api’s before and English was not my first language. So please tell me what i’m missing and i’ll fix it… that’s how i learn if you could do that I would be extremely thankful :slight_smile: cause then i can take the examples I learn from you and move forwards

package me.Cleardragonf.ExplosionGuard;

import com.google.common.collect.Lists;

import org.spongepowered.api.Game;
import org.spongepowered.api.service.scheduler.Task.Builder;
import org.spongepowered.api.service.scheduler.SchedulerService;
import org.spongepowered.api.event.Order;
import org.spongepowered.api.event.block.ChangeBlockEvent.Post;
import org.spongepowered.api.data.Transaction;
import org.spongepowered.api.block.BlockSnapshot;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.entity.explosive.PrimedTNT;import org.spongepowered.api.entity.living.monster.Creeper;
import org.spongepowered.api.event.world.ExplosionEvent;
import org.spongepowered.api.event.game.state.GamePreInitializationEvent;
import org.spongepowered.api.event.Listener;

import java.awt.List;
import java.io.File;
import java.util.logging.Logger;

import org.spongepowered.api.plugin.Plugin;
import org.spongepowered.api.service.config.DefaultConfig;

import com.google.inject.Inject;

import ninja.leaping.configurate.commented.CommentedConfigurationNode;
import ninja.leaping.configurate.loader.ConfigurationLoader;

@Plugin(id = “me.Cleardragonf.ExplosionGuard”, name = “ExplosionGuard”, version = “Beta.1.0”)
public class MainClass {

    @Inject
    private Logger logger;
    
    @Inject
    private void setLogger(Logger logger){
        this.logger = logger;
    }
    public Logger getLogger(){
        return logger;
    }


    @Inject
    @DefaultConfig(sharedRoot = true)
    private File configFile;
   
    @Inject
    @DefaultConfig(sharedRoot = true)
    ConfigurationLoader<CommentedConfigurationNode> configManager;

    @Listener
    public void onInitialization(GamePreInitializationEvent e) {
            ConfigurationManager.getInstance().setup(configFile, configManager);
           
    }
    @Listener
    public void onExplosionEvent(ExplosionEvent.Pre event) {
        if (event.getCause().first(Creeper.class).isPresent()) {
            if (ConfigurationManager.getInstance().getConfig().getNode("Explosions", "Creeper").getString().equalsIgnoreCase("true")){
                event.setCancelled(true);
            }
            if (ConfigurationManager.getInstance().getConfig().getNode("Explosions", "Creeper").getString().equalsIgnoreCase("false")){
                event.setCancelled(false);
            }
        }
        if (event.getCause().first(PrimedTNT.class).isPresent()){
            if (ConfigurationManager.getInstance().getConfig().getNode("Explosions", "TNT").getString().equalsIgnoreCase("true")){
                event.setCancelled(true);
            }
            if (ConfigurationManager.getInstance().getConfig().getNode("Explosions", "TNT").getString().equalsIgnoreCase("false")){
                event.setCancelled(false);
            }
        }
        //this is here for logging what caused the explosiong so it can be logged
        //*getLogger().info(event.getCause().toString());
    }
    
    private final List<List<Transaction<BlockSnapshot>>> capturedExplosionTransactions = Lists.newArrayList();
    @Listener(order = Order.POST)
    public void onRegisterEvent(ExplosionEvent.Detonate event){
        if (event.getCause().first(Creeper.class).isPresent()){
            capturedExplosionTransactions.add(event.getTransactions());
        }
    }
    
    @Inject
    private Game game;
    SchedulerService scheduler = game.getScheduler();
    Builder taskBuilder = scheduler.createTaskBuilder();
    
    game.getScheduler().createTaskBuilder()
        .execute(task -> {
            rollbackTransaction(num);
        })
        .interval(1, TimeUnit.MINUTES)
        .submit(myPlugin);

}

This was the initial code for creepermend which accomplishes exactly this.

However you will run into troubles when it comes to blocks reacting to the explosion, signs falling off etc.

May need to listen for a generic block break caused by the explosion afterwards and add that to the list of blocks to be restored.