Command Source Transferring with SetBlock Method

Hello,

In one of my command classes extending CommandCallable I use the setBlock() method as seen below.

playerWorld.setBlock(x, y, z, newState);

This works, except I’m having a problem where my event Listeners elsewhere intercept the block changes, interpreting them as block changes being made by the player. I think this is because the source is continuous (or that’s how I would try to describe it), meaning once a source is established, it remains with all code called from the same thread/stack/whatever it’s called. Thus, I am listening to the block changes and reading them as being done by the player, which has different implications in my mind.

Here’s the code where I am listening to the block changes.

@Listener
public void onPlayerBreakBlockEvent(final ChangeBlockEvent.Break event) {
    if (isCausedByPlayer(event)) {
        return; //Does not follow elegance rules, but it's easier for me to read in this case than if else...
    }
final Player p = event.getCause().first(Player.class).get();
...

private boolean isCausedByPlayer(final ChangeBlockEvent.Break event) {
    return !(event.getCause().containsType(Player.class));
}

I can think of a few possible options, but I would like some advice from you guys, since you’re more experienced and knowledgable! I’m thinking I could:

  • Set the source to something else before calling the SetBlock method, if possible
  • Find a different way to check if the block break event was caused by a Player (I wrote the current way without reference or help, which means it’s probably wrong haha)
  • Use a different method for setting blocks if it exists
  • Find a way to mark incoming block changes to listeners as done by my code (seems troublesome and not elegant)

Thanks for the help!

If a player is typing the command, arn’t they the ones causing the change?

One way to avoid this would be to schedule the block changes on the scheduler, this would cause it to be executed outside the player capturing unlike the commands.

I’m not 100% sure this is intended, but I could see uses for it, just needs to have a flexible way to see if it’s caused by the plugin command as well, and give you the chance to ignore it.

Well they are the ones causing the chain of events, but if they run a command and something else conditionally happens, it’s not always because they are responsible for the change? I’m not sure if that makes sense, but it’s how I need this feature of my plugin to work. I could try to make a different class or methods to call for the block setting actions, but as far as I’m aware the same thing would happen. Do you have any links, references, or examples of how I would use the scheduler? And this means it’s not possible to change the source, right? Thanks a ton @ryantheleach!

Hehe… I am SOOO sorry about this.

Let me explain.

When I was developing FoxGuard, I approached the sponge devs and pretty much told them that their event causes sucked. So as a result, @blood completely overhauled the event causes and added a cause tracker to sponge, which captures any events that occur due to specific calls.

Basically any direct calls to setBlock are captured and the entry point is captured as a cause.

So if you wanted to make it so block changes don’t get reported as belonging to a player… you’ll have to use the scheduler. Although I might talk to blood about ways in which the capture system could be specifically avoided.

Really though i think you would need to use the scheduler, or make your own.

Thanks for the reply @gravityfox! What’s the reasoning behind making it so

any direct calls to setBlock are captured and the entry point is captured as a cause ?

I’m not questioning this decision, I’m just curious why. Using the scheduler seems like an inelegant approach, although I’m not familiar with how it’s implemented. Are there any risks with which I should be aware? I see it can be used synchronously or asynchronously, so design mistakes could probably be made (I feel like synchronously running my tasks is the safe route, but correct me if I’m wrong).

How does the capture system work? Would it be possible to add a method like you mentioned? (Those are directed at blood probably, since they were tagged and you said you weren’t sure).

Lastly, what was the context behind you having this issue? I feel like I would learn from knowing and, if I recall correctly, when I saw FoxGuard it was open source, so I would love to read how you used the scheduler and/or setBlock method if possible. Thanks again!

For anyone reading this in the future who is new to the scheduler like me, this is the documentation! :slightly_smiling:

Actually, FoxGuard is world protection, but sometimes mods can do screwy things to change blocks and can bypass the sponge event system.

Blood’s capture system records block events at the minecraft setBlock method, which is called by LITERALLY EVERYTHING THAT WANTS TO CHANGE A BLOCK IN THE WORLD. That way my plugin can catch and prevent players trying to use mod exploits to get around protections.

On the flip side, this makes it more difficult to write code to SPECIFICALLY GET AROUND this capture system. Normally you wouldn’t need to, but scheduling a synchronized task to run immediately might be the only way to go.

Okay it sounds like I will definitely use the scheduler then :laughing: And that sounds really, really cool. I’m going to look into it asap! I’ll post back here if I have any other related troubles or if @blood replies. Thanks again for the help!

Ideally, you wouldn’t need to use the scheduler for this (though you may have to for a little while). Here’s some background:

As @gravityfox correctly pointed out, the tracking system written by @blood is able to capture every single block change made in the world. While this is amazing for preventing block places for mods, or anything else, it poses some complications for plugins.

The root of the issue you’re having is the the tracking system predates several major API refactors - most notably, the removal of CauseTracked. Previously, only certain events (those extending CauseTracked had a Cause associated with them. Recently, however, Cause was moved into Event itself, meaning that all events have Cause (though not necessarily a meaningful or useful one).

Since all events have a Cause, any API method which triggers or can trigger an event, such as setBlock, really should take a Cause as a parameter, in order to allow plugins to override any tracking which would normally occur.

Since setBlock, along with other methods, don’t yet provide a Cause parameter, you’ll have to use the scheduler for now. However, this is only temporary - there will be a much cleaner solution soon.

4 Likes

Thanks for the detailed reply @Aaron1011, this makes a lot of sense. Do you have any idea of the time frame for those methods (especially setBlock) taking a Cause parameter? Or any idea of who will most likely implement it, so I can ask them? In the mean time I will use the scheduler!

I’m trying to use the scheduler, but having problems. I’m really exhausted, so I’m probably making a stupid mistake, but when I run the following code with a valid instance of the game as seen in the (maybe outdated) documentation for the scheduler, the only line that prints is “Please work scheduler ugh”. This isn’t the only thing I’ve tried of course (I actually have a class extending Runnable for the setBlock method now), but it’s not working probably due to an error present in this code. Any help would be appreciated!

Scheduler scheduler = game.getScheduler();
Task.Builder taskBuilder = scheduler.createTaskBuilder();
taskBuilder.execute(() -> log.info("Yay! Schedulers!"));
log.info("Please work scheduler ugh");
taskBuilder.execute(new Runnable() {
    public void run() {
        log.info("Yay! Schedulers!");
    }
});
theScheduler.createTaskBuilder().execute(() -> log.info("It's working !")).delayTicks(ticks).submit(myPlugin);

I solved the original question posted in this comment on my own, so I will ask a different question. Is it possible to access the plugin instance in non-main classes without passing it or making and assigning a static variable in the other class?

Yes, you can fetch it from the sponge plugin manager but it’s kind of hairy. Just pass in through your constructors, it’s the arguably correct way.

I’m not sure, sorry. There are still discussions taking place about which methods should take a Cause parameter, and/or firing events.

I’ll let you know when there’s been some progress.

Okay I’m now using the scheduler to run the setBlock method. I made a custom class implementing Runnable. I create a Set of blocks to change with appropriate information where I used to be running the SetBlock command and then pass it to the Runnable class. I execute the task with the scheduler shortly after.

However, the cause of the setBlock method is still Player. I could try moving the call of the scheduler to a different class, but this logically won’t work, since it’s still the stack of method calls starting from the Player’s command call causing this chain of events. What do I do?

Edit: I guess a possibility would be me being wrong assuming moving the call of the scheduler won’t change the source being the Player, although I doubt this. I don’t know how the scheduler/cause tracking works in this respect, though.

Edit 2: Yes, I moved all of the scheduler code outside of the CommandCallable class and simply passed a Set of blocks to be changed, but since it originated from the CommandCallable stack it’s of the Player source.