Tapir - tapir is a script-loading system which lets you create JavaScript plugins for the Sponge API

This is a discussion topic for the Ore project, tapir. View the full project on Ore for downloads and more information.


tapir is a script-loading system which lets you create JavaScript plugins for the Sponge API.

about

  • tapir uses the Nashorn JavaScript library (introduced in Java 8) to handle execution of the JS code
  • ScriptController is used to handle most of the script loading logic.

features

  • Write code in JavaScript using all of the same APIs you’d have access to in a Java plugin!
  • Changes to script files are detected and applied automagically - just hit the save button “save” on your text editor
  • Code can be hotswapped at runtime - you can completely change the behaviour of a command/listener/whatever without restarting or reloading the server.

the not-so-good bits

  • It’s JavaScript - that means no type safety, no IDEs that autocomplete most things for you, no way to know that your code is going to run properly before you actually try running it.
  • If you want script reloading to work nicely, you have to program with it in mind. State that should be preserved between reloads has to be exported, and any custom listeners or registrations have to be bound to the script instance. It’s not hard or time-consuming to do, you’ve just got to remember to do it!

examples

Example of a simple event listener and command.

logger.info("Hello World!");

registerListener(ClientConnectionEvent.Join.class, Order.EARLY, function(e) {
    var player = e.getTargetEntity();
    var message = Text.builder("Hello and welcome to my server!").color(TextColors.GREEN).build();
    player.sendMessage(message);
})

var command = {
    "process": function(source, arguments) {
        source.gameMode().set(GameModes.CREATIVE);
        
        var message = Text.builder("You are now in creative mode!").color(TextColors.GREEN).build();
        source.sendMessage(message);

        return CommandResult.success();
    },
    "getSuggestions": function(source, arguments, targetPosition) {
        return ["foo", "bar"];
    },
    "testPermission": function(source) {
        return source.hasPermission("myplugin.cheat");
    },
    "shortDescription": "Enable a super-secret cheat mode. Do it, I dare you!",
    "help": "<insert super useful help message here>",
    "usage": "/cheat"
}

registerCommand(command, "cheat");
10 Likes

I thought it was a trap and then I saw the author.

You made something great here ! It might help non-java coder to slowly migrate to good coding habits while being able to make a javascript plugin.

2 Likes

oooh interesting plugin nice

1 Like

How do you get it to load other files then init.js?

I’m still working on getting some documentation sorted, I’ll update this post when I have some stuff. :slight_smile:

To load other scripts, you have to call the “loader” and ask it to “watch other files”.

So, for example, in init.js you could have:

loader.watch("patches.js");

loader.watch([
    "listeners/player-join.js",
    "listeners/player-leave.js",
    "listeners/player-move.js"
]);

loader is a global variable (internally called “bindings”) which refers to the ScriptLoader instance used by the platform.

https://github.com/lucko/ScriptController/blob/master/src/main/java/me/lucko/scriptcontroller/environment/loader/ScriptLoader.java

However, there are also lots of other “bindings” which you can use.

Again at some point I’ll work on documenting all of this stuff, but for now, a brief description is:

Hopefully that’s useful, and not just super confusing. :slight_smile:

1 Like

Does anything speak against watching all files ending with .js?

When you do that loading order becomes ambiguous. e.g. one script that depends on another

I personally prefer to just define everything explicitly.

1 Like