This repository has been archived on 2021-07-14. You can view files and clone it, but cannot push or open issues or pull requests.
scriptcraft/src/main/js/lib/scriptcraft.js

708 lines
23 KiB
JavaScript
Raw Normal View History

'use strict';
2013-02-10 14:42:32 +01:00
/************************************************************************
2013-12-24 11:02:34 +01:00
## Modules in Scriptcraft
ScriptCraft has a simple module loading system. In ScriptCraft, files
and modules are in one-to-one correspondence. As an example, foo.js
loads the module circle.js in the same directory.
*ScriptCraft now uses the same module system as Node.js - see [Node.js Modules][njsmod] for more details.*
[njsmod]: http://nodejs.org/api/modules.html
The contents of foo.js:
```javascript
var circle = require('./circle.js');
console.log( 'The area of a circle of radius 4 is '
+ circle.area(4));
```
2013-12-24 11:02:34 +01:00
The contents of circle.js:
```javascript
var PI = Math.PI;
exports.area = function (r) {
return PI * r * r;
};
exports.circumference = function (r) {
return 2 * PI * r;
};
```
2013-12-24 11:02:34 +01:00
The module circle.js has exported the functions area() and
circumference(). To add functions and objects to the root of your
module, you can add them to the special exports object.
Variables local to the module will be private, as though the module
was wrapped in a function. In this example the variable PI is private
to circle.js.
If you want the root of your module's export to be a function (such as
a constructor) or if you want to export a complete object in one
assignment instead of building it one property at a time, assign it to
module.exports instead of exports.
2013-12-18 00:49:00 +01:00
## Module Loading
2013-12-24 11:02:34 +01:00
When the ScriptCraft Java plugin is first installed, a new
subdirectory is created in the craftbukkit/plugins directory. If your
2013-12-24 11:02:34 +01:00
craftbukkit directory is called 'craftbukkit' then the new
subdirectories will be ...
* craftbukkit/plugins/scriptcraft/
* craftbukkit/plugins/scriptcraft/plugins
* craftbukkit/plugins/scriptcraft/modules
* craftbukkit/plugins/scriptcraft/lib
2013-12-24 11:02:34 +01:00
... The `plugins`, `modules` and `lib` directories each serve a different purpose.
2013-12-24 23:47:57 +01:00
### The plugins directory
2013-12-24 11:02:34 +01:00
At server startup the ScriptCraft Java plugin is loaded and begins
automatically loading and executing all of the modules (javascript
files with the extension `.js`) it finds in the `scriptcraft/plugins`
directory. All modules in the plugins directory are automatically
loaded into the `global` namespace. What this means is that anything a
module in the `plugins` directory exports becomes a global
variable. For example, if you have a module greeting.js in the plugins
directory....
```javascript
exports.greet = function(player) {
player.sendMessage('Hello ' + player.name);
};
```
2013-12-24 11:02:34 +01:00
... then `greet` becomes a global function and can be used at the
in-game (or server) command prompt like so...
/js greet(self)
2013-12-24 11:02:34 +01:00
... This differs from how modules (in NodeJS and commonJS
environments) normally work. If you want your module to be exported
globally, put it in the `plugins` directory. If you don't want your
module to be exported globally but only want it to be used by other
modules, then put it in the `modules` directory instead. If you've
used previous versions of ScriptCraft and have put your custom
javascript modules in the `js-plugins` directory, then put them in the
`scriptcraft/plugins` directory. To summarise, modules in this directory are ...
* Automatically loaded and run at server startup.
* Anything exported by modules becomes a global variable.
2013-12-24 23:47:57 +01:00
### The modules directory
2013-12-24 11:02:34 +01:00
The module directory is where you should place your modules if you
don't want to export globally. In javascript, it's considered best
practice not to have too many global variables, so if you want to
develop modules for others to use, or want to develop more complex
mods then your modules should be placed in the `modules` directory.
*Modules in the `modules` directory are not automatically loaded at
startup*, instead, they are loaded and used by other modules/plugins
using the standard `require()` function. This is the key difference
between modules in the `plugins` directory and modules in the
`modules` directory. Modules in the `plugins` directory are
automatically loaded and exported in to the global namespace at server
startup, modules in the `modules` directory are not.
2013-12-24 23:47:57 +01:00
### The lib directory
2013-12-24 11:02:34 +01:00
Modules in the `lib` directory are for use by ScriptCraft and some
core functions for use by module and plugin developers are also
provided. The `lib` directory is for internal use by ScriptCraft.
Modules in this directory are not automatically loaded nor are they
globally exported.
2013-02-10 20:36:39 +01:00
2013-12-28 09:44:40 +01:00
### plugins sub-directories
2013-12-18 00:49:00 +01:00
2013-12-24 11:02:34 +01:00
As of December 24 2013, the `scriptcraft/plugins` directory has the following sub-directories...
2013-02-10 20:36:39 +01:00
* drone - Contains the drone module and drone extensions. Drone was the first scriptcraft module.
* mini-games - Contains mini-games
2013-12-28 09:44:40 +01:00
* arrows - The arrows module - Changes the behaviour of Arrows: Explosive, Fireworks, Teleportation etc.
* signs - The signs module (includes example signs) - create interactive signs.
* chat - The chat plugin/module
* alias - The alias plugin/module - for creating custom aliases for commonly used commands.
2013-12-24 11:02:34 +01:00
* home - The home module - for setting homes and visiting other homes.
2013-02-10 20:36:39 +01:00
## Global variables
There are a couple of special javascript variables available in ScriptCraft...
### __plugin variable
The ScriptCraft JavaPlugin object.
### server variable
The Minecraft Server object
### self variable
The current player. (Note - this value should not be used in
multi-threaded scripts or event-handling code - it's not
thread-safe). This variable is only safe to use at the in-game prompt
and should *never* be used in modules. For example you can use it here...
/js console.log(self.name)
... but not in any javascript module you create yourself or in any
event handling code. `self` is a temporary short-lived variable which
only exists in the context of the in-game or server command prompts.
### config variable
ScriptCraft configuration - this object is loaded and saved at startup/shutdown.
### events variable
The events object is used to add new event handlers to Minecraft.
## Module variables
The following variables are available only within the context of Modules. (not available at in-game prompt).
### __filename variable
The current file - this variable is only relevant from within the context of a Javascript module.
### __dirname variable
The current directory - this variable is only relevant from within the context of a Javascript module.
2013-12-28 09:44:40 +01:00
## Global functions
ScripCraft provides some global functions which can be used by all plugins/modules...
### echo function
The `echo()` function displays a message on the in-game screen. The
message is displayed to the `self` player (this is usually the player
who issued the `/js` or `/jsp` command).
2013-12-28 09:44:40 +01:00
#### Example
2013-12-28 09:44:40 +01:00
/js echo('Hello World')
2013-12-18 00:49:00 +01:00
For programmers familiar with Javascript web programming, an `alert`
function is also provided. `alert` works exactly the same as `echo`
e.g. `alert('Hello World')`.
#### Notes
2013-12-24 01:17:07 +01:00
The `echo` and `alert` functions are provided as convenience functions
for beginning programmers. The use of these 2 functions is not
recommended in event-handling code or multi-threaded code. In such
cases, if you want to send a message to a given player then use the
Bukkit API's [Player.sendMessage()][plsm] function instead.
[plsm]: http://jd.bukkit.org/dev/apidocs/org/bukkit/command/CommandSender.html#sendMessage(java.lang.String)
2013-12-18 00:49:00 +01:00
2013-12-24 11:02:34 +01:00
### require() function
ScriptCraft's `require()` function is used to load modules. The
`require()` function takes a module name as a parameter and will try
to load the named module.
2013-12-24 11:02:34 +01:00
#### Parameters
* modulename - The name of the module to be loaded. Can be one of the following...
- A relative file path (with or without `.js` suffix)
- An absolute file path (with or without `.js` suffix)
- A relative directory path (uses node.js rules for directories)
- An absolute directory path (uses node.js rules for directories)
- A name of the form `'events'` - in which case the `lib` directory and `modules` directories are searched for the module.
#### Return
require() will return the loaded module's exports.
### scload() function
2013-12-24 11:02:34 +01:00
#### No longer recommended for use by Plugin/Module developers (deprecated)
2013-02-10 14:42:32 +01:00
scload() should only be used to load .json data.
2013-02-10 20:36:39 +01:00
2013-12-18 00:49:00 +01:00
#### Parameters
2013-02-10 20:36:39 +01:00
2013-12-18 00:49:00 +01:00
* filename - The name of the file to load.
2013-02-10 20:36:39 +01:00
* warnOnFileNotFound (optional - default: false) - warn if the file was not found.
2013-12-28 09:44:40 +01:00
#### Returns
2013-12-18 00:49:00 +01:00
scload() will return the result of the last statement evaluated in the file.
2013-02-10 20:36:39 +01:00
2013-12-18 00:49:00 +01:00
#### Example
2013-02-10 20:36:39 +01:00
scload("myFile.js"); // loads a javascript file and evaluates it.
2013-02-10 20:36:39 +01:00
var myData = scload("myData.json"); // loads a javascript file and evaluates it - eval'd contents are returned.
2013-02-10 20:36:39 +01:00
##### myData.json contents...
2013-02-10 20:36:39 +01:00
{ players: {
walterh: {
h: ["jsp home {1}"],
sunny:["time set 0",
"weather clear"]
}
}
}
2013-12-18 00:49:00 +01:00
### scsave() function
2013-12-18 00:49:00 +01:00
The scsave() function saves an in-memory javascript object to a
specified file. Under the hood, scsave() uses JSON (specifically
2013-02-17 18:43:28 +01:00
json2.js) to save the object. Again, there will usually be no need to
call this function directly as all javascript plugins' state are saved
automatically if they are declared using the `plugin()` function. Any
in-memory object saved using the `scsave()` function can later be
restored using the `scload()` function.
2013-02-17 18:43:28 +01:00
2013-12-18 00:49:00 +01:00
#### Parameters
2013-02-17 18:43:28 +01:00
* objectToSave : The object you want to save.
* filename : The name of the file you want to save it to.
2013-12-18 00:49:00 +01:00
#### Example
2013-02-17 18:43:28 +01:00
```javascript
var myObject = { name: 'John Doe',
aliases: ['John Ray', 'John Mee'],
date_of_birth: '1982/01/31' };
scsave(myObject, 'johndoe.json');
```
2013-02-17 18:43:28 +01:00
##### johndoe.json contents...
2013-02-17 18:43:28 +01:00
{ "name": "John Doe",
"aliases": ["John Ray", "John Mee"],
"date_of_birth": "1982/01/31"
};
2013-02-17 18:43:28 +01:00
2013-12-18 00:49:00 +01:00
### plugin() function
2013-02-17 18:43:28 +01:00
The `plugin()` function should be used to declare a javascript module
whose state you want to have managed by ScriptCraft - that is - a
Module whose state will be loaded at start up and saved at shut down.
A plugin is just a regular javascript object whose state is managed by
ScriptCraft. The only member of the plugin which whose persistence is
2013-12-24 01:17:07 +01:00
managed by Scriptcraft is `store` - this special member will be
2013-02-17 18:43:28 +01:00
automatically saved at shutdown and loaded at startup by
ScriptCraft. This makes it easier to write plugins which need to
persist data.
2013-12-18 00:49:00 +01:00
#### Parameters
2013-02-17 18:43:28 +01:00
* pluginName (String) : The name of the plugin - this becomes a global variable.
* pluginDefinition (Object) : The various functions and members of the plugin object.
* isPersistent (boolean - optional) : Specifies whether or not the
plugin/object state should be loaded and saved by ScriptCraft.
2013-02-17 18:43:28 +01:00
2013-12-18 00:49:00 +01:00
#### Example
2013-02-17 18:43:28 +01:00
See chat/color.js for an example of a simple plugin - one which lets
players choose a default chat color. See also [Anatomy of a
ScriptCraft Plugin][anatomy].
[anatomy]: ./Anatomy-of-a-Plugin.md
2013-02-17 18:43:28 +01:00
2013-12-18 00:49:00 +01:00
### command() function
2013-02-17 18:43:28 +01:00
The `command()` function is used to expose javascript functions for
use by non-operators (regular players). Only operators should be
allowed use raw javascript using the `/js ` command because it is too
powerful for use by regular players and can be easily abused. However,
the `/jsp ` command lets you (the operator / server administrator /
plugin author) safely expose javascript functions for use by players.
2013-12-18 00:49:00 +01:00
#### Parameters
2013-02-17 18:43:28 +01:00
* commandName : The name to give your command - the command will
be invoked like this by players `/jsp commandName`
* commandFunction: The javascript function which will be invoked when
the command is invoked by a player. The callback function in turn
takes 2 parameters...
* params : An Array of type String - the list of parameters
passed to the command.
* sender : The [CommandSender][bukcs] object that invoked the
command (this is usually a Player object but can be a Block
([BlockCommandSender][bukbcs]).
2013-02-17 18:43:28 +01:00
* options (Array - optional) : An array of command options/parameters
which the player can supply (It's useful to supply an array so that
Tab-Completion works for the `/jsp ` commands.
* intercepts (boolean - optional) : Indicates whether this command
can intercept Tab-Completion of the `/jsp ` command - advanced
usage - see alias/alias.js for example.
2013-12-18 00:49:00 +01:00
#### Example
See chat/colors.js or alias/alias.js or homes/homes.js for examples of
how to use the `command()` function.
2013-02-17 18:43:28 +01:00
### setTimeout() function
2013-10-13 22:08:31 +02:00
This function mimics the setTimeout() function used in browser-based
javascript. However, the function will only accept a function
reference, not a string of javascript code. Where setTimeout() in the
browser returns a numeric value which can be subsequently passed to
clearTimeout(), This implementation returns a [BukkitTask][btdoc]
object which can be subsequently passed to ScriptCraft's own
clearTimeout() implementation.
2013-10-13 22:08:31 +02:00
If Node.js supports setTimeout() then it's probably good for ScriptCraft to support it too.
2013-02-17 18:43:28 +01:00
[btdoc]: http://jd.bukkit.org/beta/apidocs/org/bukkit/scheduler/BukkitTask.html
2013-12-28 09:44:40 +01:00
#### Example
```javascript
//
// start a storm in 5 seconds
//
setTimeout( function() {
var world = server.worlds.get(0);
world.setStorm(true);
}, 5000);
```
### clearTimeout() function
A scriptcraft implementation of clearTimeout().
### setInterval() function
This function mimics the setInterval() function used in browser-based
javascript. However, the function will only accept a function
reference, not a string of javascript code. Where setInterval() in
the browser returns a numeric value which can be subsequently passed
to clearInterval(), This implementation returns a [BukkitTask][btdoc]
object which can be subsequently passed to ScriptCraft's own
clearInterval() implementation.
If Node.js supports setInterval() then it's probably good for
ScriptCraft to support it too.
[btdoc]: http://jd.bukkit.org/beta/apidocs/org/bukkit/scheduler/BukkitTask.html
### clearInterval() function
A scriptcraft implementation of clearInterval().
### refresh() function
The refresh() function can be used to only reload the ScriptCraft
plugin (it's like the `reload` command except it only reloads
ScriptCraft). The refresh() function will ...
1. Disable the ScriptCraft plugin.
2. Unload all event listeners associated with the ScriptCraft plugin.
3. Enable the ScriptCraft plugin.
... refresh() can be used during development to reload only scriptcraft javascript files.
See [issue #69][issue69] for more information.
[issue69]: https://github.com/walterhiggins/ScriptCraft/issues/69
### addUnloadHandler() function
The addUnloadHandler() function takes a callback function as a
parameter. The callback will be called when the ScriptCraft plugin is
unloaded (usually as a result of a a `reload` command or server
shutdown).
2013-02-10 14:42:32 +01:00
This function provides a way for ScriptCraft modules to do any
required cleanup/housekeeping just prior to the ScriptCraft Plugin
unloading.
2013-02-10 14:42:32 +01:00
***/
2013-01-25 00:47:36 +01:00
/*
wph 20130124 - make self, plugin and server public - these are far more useful now that tab-complete works.
2013-01-25 00:47:36 +01:00
*/
var global = this;
2013-01-26 14:47:16 +01:00
var server = org.bukkit.Bukkit.server;
/*
private implementation
*/
function __onEnable ( __engine, __plugin, __script )
{
var File = java.io.File,
FileReader = java.io.FileReader,
BufferedReader = java.io.BufferedReader,
PrintWriter = java.io.PrintWriter,
FileWriter = java.io.FileWriter;
var debug = function(msg){
java.lang.System.out.println('DEBUG:' + msg);
};
var _canonize = function( file ) {
return '' + file.getCanonicalPath().replaceAll( '\\\\', '/' );
};
// lib (assumes scriptcraft.js is in craftbukkit/plugins/scriptcraft/lib directory
var libDir = __script.parentFile,
jsPluginsRootDir = libDir.parentFile, // scriptcraft
jsPluginsRootDirName = _canonize(jsPluginsRootDir),
logger = __plugin.logger;
/*
Save a javascript object to a file (saves using JSON notation)
*/
var _save = function( object, filename ) {
var objectToStr = null,
f,
out;
try {
objectToStr = JSON.stringify( object, null, 2 );
} catch( e ) {
print( 'ERROR: ' + e.getMessage() + ' while saving ' + filename );
return;
}
f = (filename instanceof File) ? filename : new File(filename);
out = new PrintWriter(new FileWriter(f));
out.println( objectToStr );
out.close();
};
/*
make sure eval is present: it's present on JRE 6, 7, and 8 on Linux
*/
if ( typeof eval == 'undefined' ) {
global.eval = function( str ) {
return __engine.eval( str );
};
}
/*
Load the contents of the file and evaluate as javascript
*/
var _load = function( filename, warnOnFileNotFound )
{
var result = null,
file = filename,
r,
parent,
reader,
br,
code,
wrappedCode;
2013-01-13 22:06:46 +01:00
if ( !( filename instanceof File ) ) {
file = new File(filename);
}
var canonizedFilename = _canonize( file );
if ( file.exists() ) {
reader = new FileReader( file );
br = new BufferedReader( reader );
code = '';
try {
while ( (r = br.readLine()) !== null ) {
code += r + '\n';
}
wrappedCode = '(' + code + ')';
result = __engine.eval( wrappedCode );
// issue #103 avoid side-effects of || operator on Mac Rhino
} catch ( e ) {
logger.severe( 'Error evaluating ' + canonizedFilename + ', ' + e );
}
finally {
try {
reader.close();
} catch ( re ) {
// fail silently on reader close error
2013-12-24 01:17:07 +01:00
}
}
} else {
if ( warnOnFileNotFound ) {
logger.warning( canonizedFilename + ' not found' );
}
}
return result;
};
/*
now that load is defined, use it to load a global config object
*/
var configFile = new File(jsPluginsRootDir, 'data/');
configFile.mkdirs();
configFile = new File(configFile,'global-config.json');
var config = _load( configFile );
if ( !config ) {
config = { verbose: false };
}
global.config = config;
global.__plugin = __plugin;
/*
wph 20131229 Issue #103 JSON is not bundled with javax.scripting / Rhino on Mac.
*/
(function(){
var jsonFileReader = new FileReader( new File( jsPluginsRootDirName + '/lib/json2.js' ) );
var jsonLoaded = __engine['eval(java.io.Reader)']( jsonFileReader );
}());
/*
Unload Handlers
*/
var unloadHandlers = [];
var _addUnloadHandler = function( f ) {
unloadHandlers.push( f );
};
var _runUnloadHandlers = function() {
for ( var i = 0; i < unloadHandlers.length; i++ ) {
unloadHandlers[i]( );
}
};
global.addUnloadHandler = _addUnloadHandler;
global.refresh = function( ) {
2014-03-08 22:01:25 +01:00
if ( typeof self !== 'undefined' ) {
if ( !self.op ) {
self.sendMessage('Only operators can refresh()');
return;
}
}
__plugin.pluginLoader.disablePlugin( __plugin );
__plugin.pluginLoader.enablePlugin( __plugin );
};
var _echo = function ( msg ) {
if ( typeof self == 'undefined' ) {
return;
}
self.sendMessage( msg );
};
global.echo = _echo;
global.alert = _echo;
global.scload = _load;
global.scsave = _save;
var configRequire = _load( jsPluginsRootDirName + '/lib/require.js', true );
/*
setup paths to search for modules
*/
var modulePaths = [ jsPluginsRootDirName + '/lib/',
jsPluginsRootDirName + '/modules/' ];
if ( config.verbose ) {
logger.info( 'Setting up CommonJS-style module system. Root Directory: ' + jsPluginsRootDirName );
logger.info( 'Module paths: ' + JSON.stringify(modulePaths) );
}
var requireHooks = {
loading: function( path ) {
if ( config.verbose ) {
logger.info( 'loading ' + path );
}
},
loaded: function( path ) {
if ( config.verbose ) {
logger.info( 'loaded ' + path );
}
}
};
global.require = configRequire(
jsPluginsRootDirName,
modulePaths,
requireHooks,
2014-03-14 23:23:35 +01:00
function( code ) {
return __engine.eval( code );
}
);
require('js-patch')( global );
global.console = require('console');
/*
setup persistence
*/
require('persistence')( jsPluginsRootDir, global );
var cmdModule = require('command');
global.command = cmdModule.command;
var plugins = require('plugin');
global.__onTabComplete = require('tabcomplete');
global.plugin = plugins.plugin;
var events = require('events');
events.on( 'server.PluginDisableEvent', function( evt ) {
// save config
_save( global.config, new File( jsPluginsRootDir, 'data/global-config.json' ) );
_runUnloadHandlers();
org.bukkit.event.HandlerList['unregisterAll(org.bukkit.plugin.Plugin)'](__plugin);
});
// wph 20131226 - make events global as it is used by many plugins/modules
global.events = events;
global.__onCommand = function( sender, cmd, label, args) {
var jsArgs = [],
i = 0,
jsResult,
result,
cmdName,
fnBody;
for ( ; i < args.length ; i++ ) {
jsArgs.push( '' + args[i] );
}
result = false;
cmdName = ( '' + cmd.name ).toLowerCase();
if (cmdName == 'js')
{
result = true;
fnBody = jsArgs.join(' ');
global.self = sender;
global.__engine = __engine;
try {
2014-03-11 20:57:40 +01:00
jsResult = __engine.eval(fnBody);
if ( typeof jsResult != 'undefined' ) {
if ( jsResult == null) {
sender.sendMessage('(null)');
} else {
sender.sendMessage(jsResult);
}
}
} catch ( e ) {
logger.severe( 'Error while trying to evaluate javascript: ' + fnBody + ', Error: '+ e );
sender.sendMessage( 'Error while trying to evaluate javascript: ' + fnBody + ', Error: '+ e );
throw e;
} finally {
2014-03-11 20:57:40 +01:00
/*
wph 20140312 don't delete self on nashorn until https://bugs.openjdk.java.net/browse/JDK-8034055 is fixed
*/
if ( typeof Java === 'undefined' ) { // Java is an object in Nashorn
delete global.self;
delete global.__engine;
}
}
}
if ( cmdName == 'jsp' ) {
cmdModule.exec( jsArgs, sender );
result = true;
}
return result;
};
plugins.autoload( global, new File(jsPluginsRootDir,'plugins'), logger );
/*
wph 20140102 - warn if legacy 'craftbukkit/js-plugins' or 'craftbukkit/scriptcraft' directories are present
*/
(function(){
var cbPluginsDir = jsPluginsRootDir.parentFile,
cbDir = new File(cbPluginsDir.canonicalPath).parentFile,
legacyExists = false,
legacyDirs = [new File( cbDir, 'js-plugins' ),
new File( cbDir, 'scriptcraft' )];
for ( var i = 0; i < legacyDirs.length; i++ ) {
if ( legacyDirs[i].exists()
&& legacyDirs[i].isDirectory() ) {
2013-12-24 01:17:07 +01:00
legacyExists = true;
console.warn('Legacy ScriptCraft directory %s was found. This directory is no longer used.',
legacyDirs[i].canonicalPath);
}
}
if ( legacyExists ) {
console.info( 'Please note that the working directory for %s is %s',
__plugin, jsPluginsRootDir.canonicalPath );
}
})();
}