buscript
Buscript adds the capability for a server to run Javascript from text files. Beyond what is normally offered by Javascript, buscript adds some global functions to enable scripts to be written easily and hopefully by anyone!
The latest development builds can always be found at http://ci.onarandombox.com/job/Buscript/
Commands
Currently there's just one command
/run <script> [player]
- Permission: buscript.run
- <script> is the name of the file that contains the script you wish to run. Buscript will look in plugins/Buscript/scripts for this file name. Since it indicates a file name, it needs to have the file extension as well. If your script file is myscript.txt, then you need to type /run myscript.txt.
- [player] is optional. This field is used to indicate a target for your script. In general, this should probably be someone that is online if you are going to use it.
- The run command will always reload the file so there is no need to do anything special should you modify the script.
- Should the command not locate a script, it will attempt to create the file for you (though it will be blank and do nothing.)
Scripts
Scripts are text files that contain Javascript code. This may sound scary to people unfamiliar with coding but luckily buscript provides several easy to use methods for creating basic scripts.
Your script files must be located in the scripts directory that the Buscript plugin creates for you automatically. The file extension used can be anything (even nothing) as long as the file contains valid Javascript.
Script Features
- Easy to use global functions including command running, message broadcasting, permission management and more.
- Any string used in these global functions has access to "%target%" which represents the current target of the script. Example: broadcast("Hello, %target%")
- The ability to run another script at a later time with 100% reliability, even if the server crashes!
- Buscript defines the global variable target so more advanced things can be done with the script's target.
- The global variable server is also available which gives direct access to the Bukkit server variable just as if you were working from within a plugin!
Detailed Script Features
Global Functions and Variables
Example Scripts
- /run broadcast-sample.txt dumptruckman
- broadcast-sample.txt:
broadcast("Hey everyone, §c%target% §fis a cool guy!") command("op %target%")
- This is a simple script that tells everyone on the server what a cool guy the target (dumptruckman in this case) is and then ops them.
- It even colors the name, imagine that.
- /run perm-sample.txt dumptruckman
- perm-sample.txt:
message("%target%", "You have just been given flight in the creative world for 30 minutes!") addPerm("creative", "%target%", "flight.permission") runLater("remove-perm.txt", "30m", "%target%")
- remove-perm.txt:
message("%target%", "Your flight power has been removed, hope you weren't up there!") removePerm("creative", "%target%", "flight.permission")
- Please note these two scripts require Vault and a Vault compatible permission manager which allows them to work even when the player is not online.
- It is possible to instead use the command() function to run your own permission commands without using Vault.
- Also note "flight.permission" is not a real permission. Your actual flight enabling permission may vary (or not even exist!)
For Developers
Source code: https://github.com/dumptruckman/Buscript
Javadoc: http://ci.onarandombox.com/view/Other%20Great%20Plugins/job/Buscript/javadoc/
Maven repository: http://repo.onarandombox.com/content/groups/public/
Maven pom information:
<groupId>com.dumptruckman.minecraft</groupId> <artifactId>buscript</artifactId> <version>1.0-SNAPSHOT</version>
You can easily use this as a library instead of a plugin by adding it as a library to your plugin. I would recommend shading it in with maven, possibly relocating the code to a new namespace.
After that, simple create a
new Buscript(yourPlugin)
and from there you will find all the methods you need to run your own scripts and even add your own global methods!
If you wish to extend the functionality of Buscript as a plugin, the main plugin class BuscriptPlugin has a convenient getAPI() method to return the Buscript object it has on hand.
The javadocs are available for download via the maven repository.
Plugin has not been updated here in some time but the Jenkins builds provided via the "development builds link" does have updates as of last year. However, the last version to still work as of 1.18.1 (using OpenJDK 17) as a standalone plugin is development version Snapshot1.1 build#28. It is also identified as a legacy plugin so will still enable legacy materials support accordingly on server startup.
Is there a variable like %target%, but one that is replaced by the issuer of the command /run?
From what I've found is that Buscript is only sync/thread-safe providing asynchronous schedulers and events are not used such as 'org.bukkit.event.player.AsyncPlayerChatEvent' for example. Doing so would cause collisions in assignments with the event global internally prepared such as EntityDamageEvent's global 'event' colliding with the event global being set for AsyncPlayerChatEvent resulting in one getting the event call for the other along with the other event failing or acting unreliably.
Is there any way to register() an event to use a custom supplied global variable name or even have it pass the event directly to a function in the event script such as init() which would have to be supplied by the user? What I'm thinking to accomplish handling events without collisions would be a spin off registerEvent() for example...
When used.. instead of loading a script target, it will call a premade stringscript internally to do '<functionname>(event);' instead of mirroring the event contents itself to the 'event' global scope. This way, in theory, the event call can be isolated to a function call defined by the user in the startup script instead and make the event more thread-safe?
In the eent a global MUST be defined to pass it along, perhaps in the above case could create a random global variable name, pass it along to the function, the delete it after the function finishes execution. Such as...
Creates random global such as '$_276ada2B=eventdata;', run the stringscript '<functionname>($_276ada2B);' then after 'delete $_276ada2B;' from the global scope after completion of the function.
okay , seems I'm stumped or doing something quite wrong. So far I've been able to register/listen to the majority of the events I have needed but have run into a couple that I cannot seem to listen on.
for example buscript cannot listen to but it can listen to EntityDeathEvent without issue so ended up using that with the appropriate getEntityType() checks.
The one that stumps me is the following:
I cannot get buscript to listen to this event at all nor the parent event. I can listen to the majority of EntityEvent's without issue but this one has me blocked with what I'm trying to do (Creature spawn health or equipment manipulation for example). Just like the other mentioned - the log feedback shows '[Buscript] org.bukkit.event.entity.CreatureSpawnEvent cannot be listened for!'
Is there a way to listen to this event through buscript without using RegisterEvent or is it a limitation with the build?
edit in: I do apologize for being a pain in the rump with so many questions. I cannot seem to find a forum as to the such for them in this regard.
@chozo4
All permissions default to OP. So if you make one up, OPs will have access.
(False positive - looking at the BuScript source it just binds to player.hasPermission() so it's unrelated to buscript - sorry about that. Original comment left for posterity sakes. Solution was to rebuild the PermissionsEX Config and set AllowOP's to false under SuperPerms which wasn't there previously.))
Came across an oddball permission issue regarding hasPerm() and PermissionsEx across two server setups. When one is an OPer on the server hasPerm() will always throw TRUE regardless what you throw at it. hasPerm('chozo4','alhfeafboiabbauobveuo') for example will always return TRUE in such cases but will function normally and throw FALSE when not an OPer.
@dumptruckman @dumptruckman
Greatly appreciate your reply dumptruckman. I had actually forgotten about the metadata and will keep that in mind. Knowing that it's run synchronously eases my worries then and shifts primary concern to keeping the scripts as efficient as possible then to avoid delays. I've been keeping cache data to a Per-Player Global object array to hold some data but the Metadata map helps a lot for consistency between restarts and per-instance data.
Keep up the great work on this plugin as without it I wouldn't have been able to add functionality in a language I'm generally familiar with. There is a fair bit of potential in this and far more powerful than initially perceived.
Thanks greatly for both your reply and your hard work with such a wonderful plugin!
@chozo4
Scripts are run synchronously. You should haven't to worry much about overwriting instance variables. Though, if you are really concerned there is the var metaData which is a Java Map<String, Object> which is unique to every script run. If you put data in this map, it will be available later should you choose to runLaterTarget.
<<reply 1041928>> to: Fipsdabitchguetta:
I know it's a rather late reply but it could still help others. You can essentially 'wait' for another's reply by using buscripts registerEvent and use 'org.bukkit.event.player.PlayerCommandPreprocessEvent' then catch any needed commands sent by the player there. Just make sure to add event.setCancelled(true) so that the user doesn't get invalid command notices when it catches a custom command you want.
When your script is waiting for user input...
Simply end the script and create a user only variable such as .. $user['thisuser']={Pg:1}. Then when the user does say... /tour next it will check $user['thisuser']['Pg'] through the commandpreprocess event and calls... run('tourscript_'+$user['thisuser']['Pg'], $UserName);
I know it seems kind-of hacky but it's the best way to go about it I can figure right now.
Method I would use (to my knowledge) to 'pause' a script would be to use a comma delimited runLaterTarget () and run parts of the script accordingly (or as above just use multiple scripts). Such as runLaterTarget('thisscript','time', 'username,part1'); and just target.split(',') the 'user' and 'part'.
I've been essentially replacing a lot of our plugins with homebrew buscript alternatives (along with command hooking and so forth for easy integration). However the more I delve into it the more my paranoia about variable sharing comes into play. I've noticed that when a var is made, every instance of a script run can/will change it through the global scope.
Now while this is nice for say... importing a predefined set of data available to all the script instances it bothers me when I start looking at where everything starts. So my questions are as follows...
1) When running a script instance is the target variable unique to the script or does it overwrite 'target' on any other scripts currently being processed? This concerns me as I've been essentially abusing target in some cases to pass multiple variables through delimited strings.
2) Are the scripts run asynchronously or synchronously.. as in waiting for previous scripts to complete before running the next? If it waits then this won't be as big of an issue - if not if there any way to ensure it does other than coding in a form of class/function to process the order of them? (Which again, brings back to the paranoia of the target var being overwritten).
3) In some cases a script would be run by more than one user at essentially the same time. I've simplified them as much as possible to avoid any race conditions from overriding the vars between both instances (again, more paranoia). Is there any way to make a variable private on an instance by instance basis so they are unique to that script instance only?
I'm basically mostly worried on:
run('testscript','user1') & run('testscript','user2') causing for example user2 to change $thisvariable within user1's instance or vice versa while both are being processed. Would like a way to mark vars as instance-only instead of global to avoid variable clashing with concurrent runs.
Yeah, noticed that, looks really awesome too. Was more for the Sync access, then i could have my javascript plugins do SQL stuff :)
@ray73864
Yes. I believe it'd be something like
Of course null checking would be good also. I do expose a lot of common methods of Vault already in the global scope. http://dev.bukkit.org/server-mods/buscript/pages/global-functions-and-variables/#w-vault-functions
Sounds very nice.
Is it possible for a script to access the public methods of another plugin? say Vault, or Sync?
Both those plugins expose public methods for other plugins to utilise, would make this a super awesome plugin if that was the case.