Skip to content

Instantly share code, notes, and snippets.

@demipixel
Last active September 2, 2015 01:14
Show Gist options
  • Save demipixel/6532869a9fb7454a5a19 to your computer and use it in GitHub Desktop.
Save demipixel/6532869a9fb7454a5a19 to your computer and use it in GitHub Desktop.
fly-squid and The Problem of the Plugins

fly-squid and The Problem of the Plugins

Alright, so this is going to explain my idea using lots and lots of pseudo code, so bear with me.

Let's say I have a little plugin which gives you a diamond when you mine stone:

function inject(serv, player) {
  player.on('block_break', function(block) {
    if (block.id == 1) player.giveItem(264); // it might be player.giveItem(id, amt, data)
  }
}

Alright, so this is neat but let's make a "NO TOUCHY" plugin:

function inject(serv, player) {
  player.on('block_break', function(block, cancel) {
    cancel();
  }
}

Suddenly, this causes an issue. The first plugin has no knowledge of the second plugin and we don't know what order they'll be run in, so no matter what, if the player breaks the stone, the stone will not break (2nd plugin) but they'll still get the diamond (1st plugin)

The solution

The solution is dual events. This sounds silly but go with me:

1st plugin:

function inject(serv, player) {
  player.on('block_break', function(block, cancelled) {
    if (block.id == 1 && !cancelled) player.giveItem(264); // it might be player.giveItem(id, amt, data)
  }
}

2nd plugin:

function inject(serv, player) {
  player.on('block_break_cancel', function(block, cancel) {
    cancel();
  }
}

Suddenly we have lots of oppurtunities. There are two events: X and X_cancel. X_cancel is run before X and gives the plugin the oppurtunity to cancel the event. ONCE THE EVENT IS CANCELLED, IT MAY NOT BE UNCANCELLED (this may sound bad but go with me). In this case, our no-touch plugin cancels the block break, meaning we can have a "cancelled" variable and work around that. Let's look at some other examples:

We start with our lovely "no touchy" plugin

function inject(serv, player) {
  player.on('block_break_cancel', function(block, cancel) {
    cancel();
  }
}

And now we have another plugin: An "endstone health" plugin:

function inject(serv, player) {
  player.on('block_break_cancel', function(block, cancel) {
    if (block.id == 121) { // Endstone
      cancel();
      player.setHealth(player.health+5);
    }
  }
}

Notice how I don't need a block_break event in the endstone-health plugin because we're already cancelling– and nobody can uncancel.

Possibility 1: Manual

If a plugin is that snobby, it can do this: (This plugin will force all blocks to get broken)

function inject(serv, player) {
  player.on('block_break', function(block, cancelled) {
    if (cancelled) {
      serv.setBlock(block.location, 0, { destroyParticles: true }); // Add destroyParticles to look like the player destroyed it
    }
  }
}

This is a manual way of doing it, and when it comes to something a bit more advanced, it may be annoying.

Possibility #2: F#@% IT

This one might be a good idea to implement just to make the above easier, but for the sake of trying to stop people from doing it, it might be best left out. Up to you guys. Basically, it will undo the cancel in a way that prevents anybody from recancelling. Once you cancel, nobody can uncancel. Once you uncancel, nobody can recancel. People who use this need to know that they are screwing with other plugins. That's why I have given the method the appropriate name:

function inject(serv, player) {
  player.on('block_break', function(block, cancelled, fuckOtherPlugins) {
    if (cancelled) {
      fuckOtherPlugins();
    }
  }
}

fuckOtherPlugins() is very unsafe, as it may screw up some other plugins, so use it sparingly.

Tricking other plugins

Let's say we have a plugin which turns any block we place into a gold_block, another plugin which will say "Hi" any time you place a block, and lastly a plugin that prevents you from placing stone:

GOLD:

function inject(serv, player) {
  player.on('place_block_cancel', function(block, cancel) {
    cancel();
    serv.setBlock(block.location, 41); // 41 = gold block
  }
}

"Hi" on place:

function inject(serv, player) {
  player.on('block_place', function(block, cancelled) {
    if (!cancelled) {
      player.sendMessage('HI');
    }
  }
}
function inject(serv, player) {
  player.on('block_place_cancel', function(block, cancel) {
    if (block.id == 1) cancel();
  }
}

If we run this, the player will never see "HI" no matter what. This is because we cancel the event. So what if this plugin tricks other plugins into thinking it's not cancelled? We can do some naughty things:

function inject(serv, player) {
  player.on('place_block_cancel', function(block, cancel) {
    serv.setBlock(block.location, 41); // 41 = gold block
  }
}

There are a few problems with this, specifically that other plugins will not view this block as gold. This means if a plugin wants to do something if gold is placed, well... It's going to be difficult for them to find out what another plugin did in the same event. One possibility is waiting for the serv.on('set_block') immediately after, but this isn't the best either.

But I'm going off on a tangent, because there's another problem with this: If they place stone, it won't say HI but it'll place the gold block. Therefore:

function inject(serv, player) {
  player.on('place_block', function(block, cancelled) {
    if (!cancelled) {
      serv.setBlock(block.location, 41); // 41 = gold block
    }
  }
}

Ruh roh! Now plugins can't even do serv.getBlock(block.location) to see if it was switched to gold!

Unfortunately, I don't have a solution for this problem. Since order is unknown, we can't know if any other plugin will find out that we switched it to gold.

Conclusion

So this method fixes everything but plugin interaction (which is difficult in its own). The best chance for a plugin to find out that it was set to gold is to wait for serv.on('set_block') but then they will be unable to find out what player it was. Who knows if we'll ever find a solution for this problem, but plugin interaction is a bit rare as it is, because in reality, if you wanted to do that, how would you solve THIS problem:

function inject(serv, player) {
  player.on('place_block', function(block, cancelled) {
    if (!cancelled && serv.getBlock(block.location).id == 42) { // 42 = iron block
      serv.setBlock(block.location, 41); // 41 = gold block
    }
  }
}
function inject(serv, player) {
  player.on('place_block', function(block, cancelled) {
    if (!cancelled && serv.getBlock(block.location).id == 41) { // 41 = gold block
      serv.setBlock(block.location, 42); // 42 = iron block
    }
  }
}

This is why you don't do this. Two plugins do this and it becomes seamingly random (until we add some config for plugin order later).

Plugin Order

The above situation is seamingly the only situation where you'd need it. Above isn't a great programming technique either since you're worrying about another plugin even though you don't know what the order will be. Overall, plugin order should only be implemented when we need it, and this system was built to avoid it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment