From 55563a090e0fcc6bd6e0bc8a6573f4b1f16c9327 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Mon, 21 Oct 2013 20:24:29 +0100 Subject: [PATCH 001/456] added version to plugin.yml --- build.properties | 2 +- build.xml | 43 +++++++++++------------- src/main/javascript/core/_primitives.js | 6 +++- src/main/javascript/core/_scriptcraft.js | 13 ++++++- src/main/javascript/drone/drone.js | 1 + 5 files changed, 39 insertions(+), 26 deletions(-) diff --git a/build.properties b/build.properties index 27e9f1b..f7c14f9 100644 --- a/build.properties +++ b/build.properties @@ -1 +1 @@ -bukkit-version=1.4.7 +bukkit-version=1.6.4 diff --git a/build.xml b/build.xml index 98cb1eb..7640914 100644 --- a/build.xml +++ b/build.xml @@ -1,12 +1,18 @@ - Builds the scriptcraft.jar file - a plugin for bukkit - + Builds the scriptcraft.jar file - a plugin for bukkit - + + + + + @@ -23,9 +29,10 @@ unless="minecraft.present"> - Retrieving CraftBukkit artifact info - - Retrieving CraftBukkit artifact info + - Retrieving CraftBukkit jar - + Retrieving CraftBukkit jar - Creating default ops.txt for your user - + Creating default ops.txt for your user - Starting Bukkit with ScriptCraft - - + Starting Bukkit with ScriptCraft + @@ -72,8 +72,7 @@ - Retrieving Coffeescript compiler - + Retrieving Coffeescript compiler @@ -86,14 +85,12 @@ - + - [[version]] - + [[version]] diff --git a/src/main/javascript/core/_primitives.js b/src/main/javascript/core/_primitives.js index 1ba6f78..27590a1 100644 --- a/src/main/javascript/core/_primitives.js +++ b/src/main/javascript/core/_primitives.js @@ -131,5 +131,9 @@ var global = this; global.putSign = _putSign; global.notifyAdministrators = _notifyAdministrators; global.echo = _echo; - + /* + wph 20131020 - add 'alert' - behaves just like echo. For programmers familiar with browser-based js + */ + global.alert = _echo; + }()); diff --git a/src/main/javascript/core/_scriptcraft.js b/src/main/javascript/core/_scriptcraft.js index e012e8b..e2550e2 100644 --- a/src/main/javascript/core/_scriptcraft.js +++ b/src/main/javascript/core/_scriptcraft.js @@ -548,6 +548,7 @@ var server = org.bukkit.Bukkit.server; return result; }; var _javaLangObjectMethods = ["equals","getClass","class","getClass","hashCode","notify","notifyAll","toString","wait","clone","finalize"]; + var _getProperties = function(o) { var result = []; @@ -563,7 +564,17 @@ var server = org.bukkit.Bukkit.server; for (var j = 0;j < _javaLangObjectMethods.length; j++) if (_javaLangObjectMethods[j] == i) continue propertyLoop; - if (typeof o[i] == "function" ) + var typeofProperty = null; + try { + typeofProperty = typeof o[i]; + }catch( e ){ + if (e.message == "java.lang.IllegalStateException: Entity not leashed"){ + // wph 20131020 fail silently for Entity leashing in craftbukkit + }else{ + throw e; + } + } + if (typeofProperty == "function" ) result.push(i+"()"); else result.push(i); diff --git a/src/main/javascript/drone/drone.js b/src/main/javascript/drone/drone.js index 590ec11..463a2fb 100644 --- a/src/main/javascript/drone/drone.js +++ b/src/main/javascript/drone/drone.js @@ -783,6 +783,7 @@ Used when placing torches so that they face towards the drone. return result; }; }; + /************************************************************************** Drone.times() Method ==================== From cfb21f827328b6a4c37f5d12815fded23c1a0a78 Mon Sep 17 00:00:00 2001 From: Walter Higgins Date: Fri, 25 Oct 2013 08:15:59 +0100 Subject: [PATCH 002/456] Update YoungPersonsGuideToProgrammingMinecraft.md --- docs/YoungPersonsGuideToProgrammingMinecraft.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/YoungPersonsGuideToProgrammingMinecraft.md b/docs/YoungPersonsGuideToProgrammingMinecraft.md index db7890b..e951244 100644 --- a/docs/YoungPersonsGuideToProgrammingMinecraft.md +++ b/docs/YoungPersonsGuideToProgrammingMinecraft.md @@ -919,7 +919,7 @@ different objects and methods available for use by ScriptCraft. [buk]: http://wiki.bukkit.org/Setting_up_a_server [dlbuk]: http://dl.bukkit.org/ -[sc-plugin]: files/scriptcraft/ +[sc-plugin]: http://scriptcraftjs.org/downloads/ [ce]: http://www.codecademy.com/ [mcdv]: http://www.minecraftwiki.net/wiki/Data_values [np]: http://notepad-plus-plus.org/ From 872d062716cf9cfa92b0cb850dd1599c15315974 Mon Sep 17 00:00:00 2001 From: Walter Higgins Date: Fri, 25 Oct 2013 08:16:56 +0100 Subject: [PATCH 003/456] Fixed scriptcraft download link. --- docs/YoungPersonsGuideToProgrammingMinecraft.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/YoungPersonsGuideToProgrammingMinecraft.md b/docs/YoungPersonsGuideToProgrammingMinecraft.md index e951244..83c5211 100644 --- a/docs/YoungPersonsGuideToProgrammingMinecraft.md +++ b/docs/YoungPersonsGuideToProgrammingMinecraft.md @@ -919,7 +919,7 @@ different objects and methods available for use by ScriptCraft. [buk]: http://wiki.bukkit.org/Setting_up_a_server [dlbuk]: http://dl.bukkit.org/ -[sc-plugin]: http://scriptcraftjs.org/downloads/ +[sc-plugin]: http://scriptcraftjs.org/download/ [ce]: http://www.codecademy.com/ [mcdv]: http://www.minecraftwiki.net/wiki/Data_values [np]: http://notepad-plus-plus.org/ From e6c1801a9b9732fbe0f2100e1b5ef23d36eecf2c Mon Sep 17 00:00:00 2001 From: Walter Higgins Date: Fri, 25 Oct 2013 08:18:38 +0100 Subject: [PATCH 004/456] Update YoungPersonsGuideToProgrammingMinecraft.md --- docs/YoungPersonsGuideToProgrammingMinecraft.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/YoungPersonsGuideToProgrammingMinecraft.md b/docs/YoungPersonsGuideToProgrammingMinecraft.md index 83c5211..b858f67 100644 --- a/docs/YoungPersonsGuideToProgrammingMinecraft.md +++ b/docs/YoungPersonsGuideToProgrammingMinecraft.md @@ -30,7 +30,7 @@ Install ScriptCraft on your computer... 1. [Download and install CraftBukkit][dlbuk]. -2. [Download the ScriptCraft Mod][sc-plugin]. Then copy it to the +2. [Download the latest version of the ScriptCraft Mod][sc-plugin]. Then copy the ScriptCraft.jar file to the `craftbukkit/plugins` folder you created in step 1. 3. Start the CraftBukkit server. From 4f390374ef285a220580d106a42bd9f93e887e61 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sun, 3 Nov 2013 18:37:18 +0000 Subject: [PATCH 005/456] updated first steps --- ...YoungPersonsGuideToProgrammingMinecraft.md | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/docs/YoungPersonsGuideToProgrammingMinecraft.md b/docs/YoungPersonsGuideToProgrammingMinecraft.md index b858f67..1212b5f 100644 --- a/docs/YoungPersonsGuideToProgrammingMinecraft.md +++ b/docs/YoungPersonsGuideToProgrammingMinecraft.md @@ -28,19 +28,27 @@ easy addition of 'Mods' and extensions to Minecraft. ScriptCraft is a difficult but CraftBukkit makes it easy. Follow these steps to Install ScriptCraft on your computer... -1. [Download and install CraftBukkit][dlbuk]. +1. [Download and install CraftBukkit][dlbuk]. Then follow the [Bukkit Installation Instructions][bii]. +[bii]: http://wiki.bukkit.org/Setting_up_a_server -2. [Download the latest version of the ScriptCraft Mod][sc-plugin]. Then copy the ScriptCraft.jar file to the -`craftbukkit/plugins` folder you created in step 1. +2. Start the CraftBukkit server, then once it has started up, stop it + by typing 'stop'. If you go to the craftbukkit folder (see step 1) you + should see some new files and subfolders. -3. Start the CraftBukkit server. +3. [Download the latest version of the ScriptCraft Mod][sc-plugin]. Then copy the ScriptCraft.jar file to the + `craftbukkit/plugins` folder (This folder won't be created until you run Bukkit for the first time (see previous step). 4. In the CraftBukkit command window type `op {your_username}` and hit -enter, replacing {your_username} with your own minecraft -username. This will give you `operator` access meaning you can perform -more commands than are normally available in Minecraft. + enter, replacing {your_username} with your own minecraft + username. This will give you `operator` access meaning you can perform + more commands than are normally available in Minecraft. You should + make yourself a server operator (Server operators have full privileges + for the server) permanently by editing the craftbukkit/ops.txt file + and adding your username (one username per line). -5. In the CraftBukkit command window type `js 1 + 1` and hit enter. You should see `> 2` . +5. Start up the craftbukkit server again (see [instructions for starting the server][bii]). + +6. In the CraftBukkit command window type `js 1 + 1` and hit enter. You should see `> 2` . ... Congratulations! You just installed your own Minecraft Server with the ScriptCraft Mod and are now ready to begin programming in Minecraft. From 5263ad1b5dbdda4e95d896ef45ec66a80371cce9 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sun, 8 Dec 2013 12:16:41 +0000 Subject: [PATCH 006/456] fix issue #97 --- src/main/javascript/alias/alias.js | 8 +++++--- .../javascript/drone/contrib/chessboard.js | 13 ++++++++----- .../drone/contrib/skyscraper-example.js | 11 +++++++---- src/main/javascript/drone/drone.js | 14 ++++++-------- src/main/javascript/homes/homes.js | 19 ++++++++++++++----- src/main/javascript/http/request.js | 8 ++++++-- src/main/javascript/utils/utils.js | 2 +- 7 files changed, 47 insertions(+), 28 deletions(-) diff --git a/src/main/javascript/alias/alias.js b/src/main/javascript/alias/alias.js index c4cf0d7..a8b2edf 100644 --- a/src/main/javascript/alias/alias.js +++ b/src/main/javascript/alias/alias.js @@ -12,7 +12,8 @@ plugin("alias", { set: function(player, alias, commands){ var aliases = this.store.players; var name = player.name; - aliases[name] = aliases[name] || {}; + if (typeof aliases[name] == "undefined") + aliases[name] = {}; aliases[name][alias] = commands; }, remove: function(player, alias){ @@ -29,7 +30,8 @@ plugin("alias", { } },true); -alias.store.players = alias.store.players || {}; +if (typeof alias.store.players == "undefined") + alias.store.players = {}; command("alias",function(params){ /* @@ -67,7 +69,7 @@ command("alias",function(params){ for (var i = 0;i < commands.length; i++){ // fill in template var cmd = commands[i]; - cmd = cmd.replace(/{([0-9]*)}/g,function(dummy,index){ return params[index] || "";}) + cmd = cmd.replace(/{([0-9]*)}/g,function(dummy,index){ return params[index] ? params[index] : "";}) self.performCommand(cmd); } return true; diff --git a/src/main/javascript/drone/contrib/chessboard.js b/src/main/javascript/drone/contrib/chessboard.js index 8c985bb..e05679d 100644 --- a/src/main/javascript/drone/contrib/chessboard.js +++ b/src/main/javascript/drone/contrib/chessboard.js @@ -9,10 +9,14 @@ */ Drone.extend("chessboard", function(whiteBlock, blackBlock, width, depth) { this.chkpt('chessboard-start'); - width = width || 8; - depth = depth || width; - blackBlock = blackBlock || blocks.wool.black; - whiteBlock = whiteBlock || blocks.wool.white; + if (typeof whiteBlock == "undefined") + whiteBlock = blocks.wool.white; + if (typeof blackBlock == "undefined") + blackBlock = blocks.wool.black; + if (typeof width == "undefined") + width = 8; + if (typeof depth == "undefined") + depth = width; for(var i = 0; i < width; ++i) { for(var j = 0; j < depth; ++j) { @@ -25,6 +29,5 @@ Drone.extend("chessboard", function(whiteBlock, blackBlock, width, depth) { } this.move('chessboard-start').fwd(i+1); } - return this.move('chessboard-start'); }); diff --git a/src/main/javascript/drone/contrib/skyscraper-example.js b/src/main/javascript/drone/contrib/skyscraper-example.js index b5b70cb..87c25d1 100644 --- a/src/main/javascript/drone/contrib/skyscraper-example.js +++ b/src/main/javascript/drone/contrib/skyscraper-example.js @@ -1,12 +1,15 @@ Drone.extend('skyscraper',function(floors){ - floors = floors || 10; + + if (typeof floors == "undefined") + floors = 10; this.chkpt('skyscraper'); for (var i = 0;i < floors; i++) { - this.box(blocks.iron,20,1,20) + this + .box(blocks.iron,20,1,20) .up() - .box0(blocks.glass_pane,20,3,20); - this.up(3); + .box0(blocks.glass_pane,20,3,20) + .up(3); } return this.move('skyscraper'); }); diff --git a/src/main/javascript/drone/drone.js b/src/main/javascript/drone/drone.js index 463a2fb..3de3d24 100644 --- a/src/main/javascript/drone/drone.js +++ b/src/main/javascript/drone/drone.js @@ -1213,10 +1213,8 @@ Another example: This statement creates a row of trees 2 by 3 ... var ddF_y = -2 * radius; var x = 0; var y = radius; - quadrants = quadrants || {topleft: true, - topright: true, - bottomleft: true, - bottomright: true}; + var defaultQuadrants = {topleft: true, topright: true, bottomleft: true, bottomright: true}; + quadrants = quadrants?quadrants:defaultQuadrants; /* II | I ------------ @@ -1284,16 +1282,16 @@ Another example: This statement creates a row of trees 2 by 3 ... var _arc2 = function( params ) { var drone = params.drone; - var orientation = params.orientation || "horizontal"; - var quadrants = params.quadrants || { + var orientation = params.orientation?params.orientation:"horizontal"; + var quadrants = params.quadrants?params.quadrants:{ topright:1, topleft:2, bottomleft:3, bottomright:4 }; - var stack = params.stack || 1; + var stack = params.stack?params.stack:1; var radius = params.radius; - var strokeWidth = params.strokeWidth || 1; + var strokeWidth = params.strokeWidth?params.strokeWidth:1; drone.chkpt('arc2'); var x0, y0, gotoxy,setPixel; diff --git a/src/main/javascript/homes/homes.js b/src/main/javascript/homes/homes.js index bf8f680..8fc4c1f 100644 --- a/src/main/javascript/homes/homes.js +++ b/src/main/javascript/homes/homes.js @@ -109,7 +109,10 @@ plugin("homes", { if (online[i].name != player.name) result.push(online[i].name); }else{ - result = this.store.invites[player.name] || []; + if (this.store.invites[player.name]) + result = this.store.invites[player.name]; + else + result = []; } return result; }, @@ -119,7 +122,9 @@ plugin("homes", { invite: function(host, guest){ host = utils.getPlayerObject(host); guest = utils.getPlayerObject(guest); - var invitations = this.store.invites[host.name] || []; + var invitations = []; + if (this.store.invites[host.name]) + invitations = this.store.invites[host.name]; invitations.push(guest.name); this.store.invites[host.name] = invitations; guest.sendMessage(host.name + " has invited you to their home."); @@ -275,7 +280,11 @@ plugin("homes", { /* initialize the store */ - homes.store.houses = homes.store.houses || {}; - homes.store.openHouses = homes.store.openHouses || {}; - homes.store.invites = homes.store.invites || {}; + if (typeof homes.store.houses == "undefined") + homes.store.houses = {}; + if (typeof homes.store.openHouses == "undefined") + homes.store.openHouses = {}; + if (typeof homes.store.invites == "undefined") + homes.store.invites = {}; + }()); diff --git a/src/main/javascript/http/request.js b/src/main/javascript/http/request.js index 3d25136..520a9df 100644 --- a/src/main/javascript/http/request.js +++ b/src/main/javascript/http/request.js @@ -39,7 +39,7 @@ The following example illustrates how to use http.request to make a request to a }); ***/ -var http = http || {}; +var http = http ? http : {}; http.request = function( request, callback) { @@ -65,7 +65,11 @@ http.request = function( request, callback) requestMethod = "GET"; }else{ paramsAsString = paramsToString(request.params); - requestMethod = request.method || "GET"; + if (request.method) + requestMethod = request.method + else + requestMethod = "GET"; + if (requestMethod == "GET" && request.params){ // append each parameter to the URL url = request.url + "?" + paramsAsString; diff --git a/src/main/javascript/utils/utils.js b/src/main/javascript/utils/utils.js index f2165fe..97a56ae 100644 --- a/src/main/javascript/utils/utils.js +++ b/src/main/javascript/utils/utils.js @@ -9,7 +9,7 @@ Miscellaneous utility functions and classes to help with programming. player or `self` if no name is provided. ***/ -var utils = utils || { +var utils = utils ? utils : { locationToString: function(location){ return JSON.stringify([""+location.world.name,location.x, location.y, location.z]); }, From bcf44fe3453f5a1bd217ca9f1ba427022c70c2eb Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sun, 15 Dec 2013 21:26:28 +0000 Subject: [PATCH 007/456] Adding experimental require() function like commonjs (barebones support for require() and exports) --- src/main/javascript/core/_scriptcraft.js | 119 +++++++++++++---------- 1 file changed, 70 insertions(+), 49 deletions(-) diff --git a/src/main/javascript/core/_scriptcraft.js b/src/main/javascript/core/_scriptcraft.js index e2550e2..12a949d 100644 --- a/src/main/javascript/core/_scriptcraft.js +++ b/src/main/javascript/core/_scriptcraft.js @@ -310,6 +310,31 @@ var server = org.bukkit.Bukkit.server; var jsPluginsRootDir = parentFileObj.getParentFile(); var jsPluginsRootDirName = _canonize(jsPluginsRootDir); + /* + wph 20131215 Experimental + */ + var _loadedModules = {}; + var _require = function(path) + { + var file = new java.io.File(path); + var canonizedFilename = _canonize(file); + if (_loadedModules[canonizedFilename]){ + return _loadedModules[canonizedFilename]; + } + if (verbose){ + print("loading module " + canonizedFilename); + } + var reader = new java.io.FileReader(file); + var br = new java.io.BufferedReader(reader); + var code = ""; + while ((r = br.readLine()) !== null) code += r + "\n"; + code = "var result = {};(function(exports){ " + code + "; return exports;}(result))"; + _loadedModules[canonizedFilename] = __engine.eval(code); + return _loadedModules[canonizedFilename]; + }; + global.loadedModules = _loadedModules; + global.require = _require; + var _loaded = {}; /* @@ -317,57 +342,53 @@ var server = org.bukkit.Bukkit.server; */ var _load = function(filename,warnOnFileNotFound) { - var filenames = []; - if (filename.constructor == Array) - filenames = filename; - else - filenames = [filename]; - var result = null; - for (var i =0;i < filenames.length; i++) { - - var file = new java.io.File(filenames[0]); - var canonizedFilename = _canonize(file); - // - // wph 20130123 don't load the same file more than once. - // - if (_loaded[canonizedFilename]) - continue; - - if (verbose) - print("loading " + canonizedFilename); - - if (file.exists()) { - var parent = file.getParentFile(); - var reader = new java.io.FileReader(file); - var br = new java.io.BufferedReader(reader); - __engine.put("__script",canonizedFilename); - __engine.put("__folder",(parent?_canonize(parent):"")+"/"); - - var code = ""; - try{ - if (file.getCanonicalPath().endsWith(".coffee")) { - var r = undefined; - while ((r = br.readLine()) !== null) code += "\"" + r + "\" +\n"; - code += "\"\""; - var code = "load(__folder + \"../core/_coffeescript.js\"); var ___code = "+code+"; eval(CoffeeScript.compile(___code, {bare: true}))"; - } else { - while ((r = br.readLine()) !== null) code += r + "\n"; - } - - result = __engine.eval(code); - _loaded[canonizedFilename] = true; - reader.close(); - }catch (e){ - __plugin.logger.severe("Error evaluating " + canonizedFilename + ", " + e ); - } - }else{ - if (warnOnFileNotFound) - __plugin.logger.warning(canonizedFilename + " not found"); - } - } + var file = new java.io.File(filename); + var canonizedFilename = _canonize(file); + // + // wph 20130123 don't load the same file more than once. + // + if (_loaded[canonizedFilename]) + return _loaded[canonizedFilename]; + if (verbose) + print("loading " + canonizedFilename); + + if (file.exists()) { + var parent = file.getParentFile(); + var reader = new java.io.FileReader(file); + var br = new java.io.BufferedReader(reader); + __engine.put("__script",canonizedFilename); + __engine.put("__folder",(parent?_canonize(parent):"")+"/"); + + var code = ""; + try{ + if (file.getCanonicalPath().endsWith(".coffee")) { + var r = undefined; + while ((r = br.readLine()) !== null) code += "\"" + r + "\" +\n"; + code += "\"\""; + var code = "load(__folder + \"../core/_coffeescript.js\"); var ___code = "+code+"; eval(CoffeeScript.compile(___code, {bare: true}))"; + } else { + while ((r = br.readLine()) !== null) code += r + "\n"; + } + + result = __engine.eval(code); + _loaded[canonizedFilename] = result || true; + }catch (e){ + __plugin.logger.severe("Error evaluating " + canonizedFilename + ", " + e ); + } + finally { + try { + reader.close(); + }catch (re){ + // fail silently on reader close error + } + } + }else{ + if (warnOnFileNotFound) + __plugin.logger.warning(canonizedFilename + " not found"); + } return result; }; /* @@ -472,7 +493,7 @@ var server = org.bukkit.Bukkit.server; // don't load plugin more than once // if (typeof _plugins[moduleName] != "undefined") - return; + return _plugins[moduleName].module; var pluginData = {persistent: isPersistent, module: moduleObject}; moduleObject.store = moduleObject.store || {}; From 03220a03e823520f92274d03252498545d37fabc Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sun, 15 Dec 2013 21:27:23 +0000 Subject: [PATCH 008/456] src/main/javascript/events/events.js tidy up variable names --- src/main/javascript/homes/homes.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/javascript/homes/homes.js b/src/main/javascript/homes/homes.js index 8fc4c1f..bc6fe8a 100644 --- a/src/main/javascript/homes/homes.js +++ b/src/main/javascript/homes/homes.js @@ -185,10 +185,10 @@ plugin("homes", { define a set of command options that can be used by players */ var options = { - set: function(){homes.set();}, + 'set': function(){homes.set();}, 'delete': function(){ homes.remove();}, - help: function(){ self.sendMessage(homes.help());}, - list: function(){ + 'help': function(){ self.sendMessage(homes.help());}, + 'list': function(){ var visitable = homes.list(); if (visitable.length == 0){ self.sendMessage("There are no homes to visit"); @@ -200,7 +200,7 @@ plugin("homes", { ]); } }, - ilist: function(){ + 'ilist': function(){ var potentialVisitors = homes.ilist(); if (potentialVisitors.length == 0) self.sendMessage("No one can visit your home"); @@ -209,7 +209,7 @@ plugin("homes", { "These " + potentialVisitors.length + "players can visit your home", potentialVisitors.join(", ")]); }, - invite: function(params){ + 'invite': function(params){ if (params.length == 1){ self.sendMessage("You must provide a player's name"); return; @@ -221,7 +221,7 @@ plugin("homes", { else homes.invite(self,guest); }, - uninvite: function(params){ + 'uninvite': function(params){ if (params.length == 1){ self.sendMessage("You must provide a player's name"); return; @@ -241,13 +241,13 @@ plugin("homes", { homes.close(); self.sendMessage("Your home is closed to the public"); }, - listall: function(){ + 'listall': function(){ if (!self.isOp()) self.sendMessage("Only operators can do this"); else self.sendMessage(homes.listall().join(", ")); }, - clear: function(params){ + 'clear': function(params){ if (!self.isOp()) self.sendMessage("Only operators can do this"); else @@ -269,7 +269,7 @@ plugin("homes", { if (option) option(params); else{ - var host = utils.getPlayerObject(params[0]); + var host = utils.getPlayerObject(params[0]); if (!host) self.sendMessage(params[0] + " is not here"); else From db51bdc57e481203654f19cfa0dd57373ca27844 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sun, 15 Dec 2013 21:27:48 +0000 Subject: [PATCH 009/456] tidy up variable names --- src/main/javascript/events/events.js | 42 ++++++++++++---------------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/src/main/javascript/events/events.js b/src/main/javascript/events/events.js index eadbb82..7ead348 100644 --- a/src/main/javascript/events/events.js +++ b/src/main/javascript/events/events.js @@ -1,4 +1,3 @@ -var global = this; /************************************************************************ events Module ============= @@ -88,52 +87,47 @@ var events = events || { // // private implementation from here on in... // -(function(){ +(function(events){ if (events._eventsLoaded){ return; } - var _event = org.bukkit.event; - var _plugin = org.bukkit.plugin; + var bkEvent = org.bukkit.event; + var bkEvtExecutor = org.bukkit.plugin.EventExecutor; + var bkRegListener = org.bukkit.plugin.RegisteredListener; var _on = function(eventType, handler, priority) { if (typeof priority == "undefined"){ - priority = _event.EventPriority.HIGHEST; + priority = bkEvent.EventPriority.HIGHEST; }else{ - priority = _event.EventPriority[priority]; + priority = bkEvent.EventPriority[priority]; } if (typeof eventType == "string"){ var subPkgs = eventType.split('.'); - eventType = _event[subPkgs[0]]; + eventType = bkEvent[subPkgs[0]]; for (var i = 1;i < subPkgs.length; i++){ eventType = eventType[subPkgs[i]]; } } var handlerList = eventType.getHandlerList(); var listener = {}; - var eventExecutor = new _plugin.EventExecutor(){ + var eventExecutor = new bkEvtExecutor(){ execute: function(l,e){ handler(listener.reg,e); } }; - listener.reg = new _plugin.RegisteredListener( - /* - wph 20130222 issue #64 bad interaction with Essentials plugin - if another plugin tries to unregister a Listener (not a Plugin or a RegisteredListener) - then BOOM! the other plugin will throw an error because Rhino can't coerce an - equals() method from an Interface. - The workaround is to make the ScriptCraftPlugin java class a Listener. - Should only unregister() registered plugins in ScriptCraft js code. - */ - __plugin - ,eventExecutor - ,priority - ,__plugin - ,true - ) + /* + wph 20130222 issue #64 bad interaction with Essentials plugin + if another plugin tries to unregister a Listener (not a Plugin or a RegisteredListener) + then BOOM! the other plugin will throw an error because Rhino can't coerce an + equals() method from an Interface. + The workaround is to make the ScriptCraftPlugin java class a Listener. + Should only unregister() registered plugins in ScriptCraft js code. + */ + listener.reg = new bkRegListener( __plugin, eventExecutor, priority, __plugin, true); handlerList.register(listener.reg); return listener.reg; }; events.on = _on; events._eventsLoaded = true; -}()); +}(events)); From 4fb370bfeafba77030ca4160d24ae698b78a3b9c Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 17 Dec 2013 21:42:25 +0000 Subject: [PATCH 010/456] changed api doc to API-Reference and tweaked Young Person's guide to use blocks variable --- build.xml | 2 +- ...YoungPersonsGuideToProgrammingMinecraft.md | 63 ++++++++++++------- 2 files changed, 42 insertions(+), 23 deletions(-) diff --git a/build.xml b/build.xml index 7640914..530179e 100644 --- a/build.xml +++ b/build.xml @@ -62,7 +62,7 @@ - + diff --git a/docs/YoungPersonsGuideToProgrammingMinecraft.md b/docs/YoungPersonsGuideToProgrammingMinecraft.md index 1212b5f..301eefa 100644 --- a/docs/YoungPersonsGuideToProgrammingMinecraft.md +++ b/docs/YoungPersonsGuideToProgrammingMinecraft.md @@ -136,8 +136,8 @@ execute this command... Variables can be created and changed easily in Javascript. Along with the variables you'll create in your in-game commands and scripts, there -are handy variables created for you by ScriptCraft. One such variable is -`self`, it contains information about the current player... +are handy "free" variables created for you by ScriptCraft. One such variable is +`self`, it contains information about the current player (that's you)... /js echo ( self ) @@ -224,7 +224,7 @@ called. You'll notice the above statement didn't actually do anything ... The current time is displayed. Congrats! You've just written your first Javascript function - you're well on your way to becoming a -Minecraft Modder :-) There are many functions for working with Text, +Minecraft Modder. There are many functions for working with Text, numbers and dates in Javascript... /js Math.random() @@ -255,13 +255,19 @@ function. You must tell the function what material you want the shape to be made of. For example, in the game, point the cross hairs at the ground, then type the following and hit enter... - /js box("5") + /js box( blocks.oak ) -... This will change the targeted block to wood. What's happened here is -the `box()` function has created a single new wooden block. The text -`"5"` is taken by Minecraft to mean Wood. You can see many more -materials and the number Minecraft uses for them by visiting the -[Minecraft Data Values][mcdv] site. +... This will change the targeted block to wood. What's happened here +is the `box()` function has created a single new wooden +block. `blocks` is another one of those "free" variables you get in +ScriptCraft, you can see a list of block materials by typing ... + + /js blocks. + +... then pressing the `TAB` key. Repeatedly pressing the `TAB` key +will cycle through all of the block materials. Alternatively, you can +see many more current materials and the numbers Minecraft uses for +them by visiting the [Minecraft Data Values][mcdv] site. ### Common Block Materials @@ -273,16 +279,25 @@ wood so the text "5:1" means Spruce, "5:2" means Birch and "5:3" means Jungle wood. There are many different materials in the Minecraft world, the most commonly used materials for building are: - * "4" - Cobblestone - * "5" - Wooden Planks - * "5:2" - Birch wood Planks (light wood) - * "98" - Stone bricks - * "45" - Red bricks - * "68" - Doors - * "102" - Glass panes (for windows) - -For reference, here is a chart of all of the blocks (not items) in the Minecraft -world... + * "4" - Cobblestone or `blocks.cobblestone` + * "5" - Wooden Planks or `blocks.oak` + * "5:2" - Birch wood Planks (light wood) or `blocks.birch` + * "98" - Stone bricks or `blocks.brick.stone` + * "45" - Red bricks or `blocks.brick.red` + * "68" - Sign or `blocks.sign` + * "102" - Glass panes (for windows) or `blocks.glass_pane` + +You can create a single wooden block using the numeric values or the `blocks` variable. For example... + + /js box( "5" ) + +... and ... + + /js box( blocks.oak ) + +... both do exactly the same thing but I personally prefer `/js box( +blocks.oak )` because it's easier to remember. For reference, here is +a chart of all of the blocks (not items) in the Minecraft world... ![Minecraft Data Values][img_dv] @@ -350,7 +365,7 @@ You can make a Drone move around before and after building by *daisy-chaining* the building and movement functions together. In the game, point at the ground then type the following... - /js up(1).box(5).fwd(3).box(5) + /js up(1).box( blocks.oak ).fwd(3).box( blocks.oak ) A series of 2 boxes is created 3 blocks apart. @@ -414,7 +429,9 @@ again when you quit the game and start it up again. installed on every Windows machine) that is well suited for writing code. If you don't already have it on your machine, you can [install Notepad++ here][np]. I recommend using NotePad++ rather than plain old -Notepad because it understands Javascript. +Notepad because it understands Javascript. If you prefer coding on a +Macintosh, then [TextWrangler][twl] is a good programming editor which +also understands Javascript code. ### Your First Minecraft Mod! @@ -436,7 +453,8 @@ type... /reload -... to reload all of the server plugins. Your mod has just been loaded. Try it out by typing this command... +... to reload all of the server plugins. Your mod has just been +loaded. Try it out by typing this command... /js greet() @@ -936,6 +954,7 @@ different objects and methods available for use by ScriptCraft. [soundapi]: http://jd.bukkit.org/beta/apidocs/org/bukkit/Sound.html [ap]: http://walterhiggins.net/blog/ScriptCraft-1-Month-later [api]: api.md +[twl]: http://www.barebones.com/products/textwrangler/ [img_echo_date]: img/ypgpm_echo_date.png [img_3d_shapes]: img/ypgpm_3dshapes.jpg From 249d3dda314e5f942f24174f2c8de242f89cb265 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 17 Dec 2013 23:49:00 +0000 Subject: [PATCH 011/456] improved require function --- docs/API-Reference.md | 1486 ++++++++++++++++++++++ src/main/javascript/core/_scriptcraft.js | 268 ++-- src/main/javascript/drone/drone.js | 23 +- src/main/javascript/signs/menu.js | 2 +- 4 files changed, 1653 insertions(+), 126 deletions(-) create mode 100644 docs/API-Reference.md diff --git a/docs/API-Reference.md b/docs/API-Reference.md new file mode 100644 index 0000000..63a0378 --- /dev/null +++ b/docs/API-Reference.md @@ -0,0 +1,1486 @@ +# ScriptCraft API Reference + +Walter Higgins + +[walter.higgins@gmail.com][email] + +[email]: mailto:walter.higgins@gmail.com?subject=ScriptCraft_API_Reference + +## Module Loading + +At server startup the ScriptCraft Java plugin is loaded and once +loaded the Java plugin will in turn begin loading all of the +javascript (.js) files it finds in the js-plugins directory (in the +current working directory). If this is the first time the ScriptCraft +plugin is loaded, then the js-plugins directory will not yet exist, it +will be created and all of the bundled javascript files will be +unzipped into it from a bundled resource within the Java plugin. The +very first javascript file to load will always be +js-plugins/core/_scriptcraft.js. Then all other javascript files are +loaded except for filenames begin with `_` (underscore), such files +are considered to be private modules and will not be automatically +loaded at startup. + +### Directory structure + +The js-plugins directory is loosely organised into subdirectories - +one for each module. Each subdirectory in turn can contain one or more +javascript files. Within each directory, a javascript file with the +same filename as the directory will always be loaded before all other +files in the same directory. So for example, drone/drone.js will +always load before any other files in the drone/ directory. Similarly +utils/utils.js will always load before any other files in the utils/ +directory. + +### Directories + +As of February 10 2013, the js-plugins directory has the following sub-directories... + + * core - Contains javascript files containing Core functionality crucial to ScriptCraft and modules which use it. + * drone - Contains the drone module and drone extensions. Drone was the first scriptcraft module. + * ext - Contains external 3rd party javascript libraries (e.g. json2.js - the JSON lib) + * mini-games - Contains mini-games + * arrows - The arrows module + * signs - The signs module + * chat - The chat plugin/module + * alias - The alias plugin/module + +## Core Module + +This module defines commonly used functions by all plugins... + + * load (filename,warnOnFileNotFound) - loads and evaluates a javascript file, returning the evaluated object. + + * save (object, filename) - saves an object to a file. + + * plugin (name, interface, isPersistent) - defines a new plugin. If + isPersistent is true then the plugin doesn't have to worry about + loading and saving state - that will be done by the framework. Just + make sure that anything you want to save (and restore) is in the plugin's + 'store' property - this will be created automatically if not + already defined. (its type is object {} ) . More on plugins below. + + * ready (function) - specifies code to be executed only when all the plugins have loaded. + + * command (name, function) - defines a command that can be used by + non-operators. The `command` function provides a way for plugin + developers to provide new commands for use by players. + +### load() function + +The load() function is used by ScriptCraft at startup to load all of +the javascript modules and data. You normally wouldn't need to call +this function directly. If you put a javascript file anywhere in the +craftbukkit/js-plugins directory tree it will be loaded automatically +when craftbukkit starts up. The exception is files whose name begins +with an underscore `_` character. These files will not be +automatically loaded at startup as they are assumed to be files +managed / loaded by plugins. + +#### Parameters + + * filename - The name of the file to load. + * warnOnFileNotFound (optional - default: false) - warn if the file was not found. + +#### Return + +load() will return the result of the last statement evaluated in the file. + +#### Example + + load(__folder + "myFile.js"); // loads a javascript file and evaluates it. + + var myData = load("myData.json"); // loads a javascript file and evaluates it - eval'd contents are returned. + +myData.json contents... + + __data = { + players: { + walterh: { + h: ["jsp home {1}"], + sunny:["time set 0", + "weather clear"] + } + } + } + +### save() function + +The save() function saves an in-memory javascript object to a +specified file. Under the hood, save() uses JSON (specifically +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 `save()` function can later be +restored using the `load()` function. + +#### Parameters + + * objectToSave : The object you want to save. + * filename : The name of the file you want to save it to. + +#### Example + + var myObject = { name: 'John Doe', + aliases: ['John Ray', 'John Mee'], + date_of_birth: '1982/01/31' }; + save(myObject, 'johndoe.json'); + +johndoe.json contents... + + var __data = { "name": "John Doe", + "aliases": ["John Ray", "John Mee"], + "date_of_birth": "1982/01/31" }; + +### plugin() function + +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 +managed by Scriptcraft is `state` - this special member will be +automatically saved at shutdown and loaded at startup by +ScriptCraft. This makes it easier to write plugins which need to +persist data. + +#### Parameters + + * 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. + +#### Example + +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]: http://walterhiggins.net/blog/ScriptCraft-1-Month-later + +### command() function + +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. + +#### Parameters + + * 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. + * 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. + +#### Example + +See chat/colors.js or alias/alias.js or homes/homes.js for examples of how to use the `command()` function. + +### ready() function + +The `ready()` function provides a way for plugins to do additional +setup once all of the other plugins/modules have loaded. For example, +event listener registration can only be done after the +events/events.js module has loaded. A plugin author could load the +file explicilty like this... + + load(__folder + "../events/events.js"); + + // event listener registration goes here + +... or better still, just do event regristration using the `ready()` +handler knowing that by the time the `ready()` callback is invoked, +all of the scriptcraft modules have been loaded... + + ready(function(){ + // event listener registration goes here + // code that depends on other plugins/modules also goes here + }); + +The execution of the function object passed to the `ready()` function +is *deferred* until all of the plugins/modules have loaded. That way +you are guaranteed that when the function is invoked, all of the +plugins/modules have been loaded and evaluated and are ready to use. + +Core Module - Special Variables +=============================== +There are a couple of special javascript variables available in ScriptCraft... + + * __folder - The current working directory - this variable is only to be used within the main body of a .js file. + * __plugin - The ScriptCraft JavaPlugin object. + * server - The Minecraft Server object. + * self - the current player. (Note - this value should not be used in multi-threaded scripts - it's not thread-safe) + +## Miscellaneous Core Functions + +### setTimeout() function + +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. + +If Node.js supports setTimeout() then it's probably good for ScriptCraft to support it too. + +[btdoc]: http://jd.bukkit.org/beta/apidocs/org/bukkit/scheduler/BukkitTask.html + +#### Example + + // + // 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 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 + +Drone Module +============ +The Drone is a convenience class for building. It can be used for... + + 1. Building + 2. Copying and Pasting + +It uses a fluent interface which means all of the Drone's methods return `this` and can +be chained together like so... + + var theDrone = new Drone(); + theDrone.up().left().box(blocks.oak).down().fwd(3).cylinder0(blocks.lava,8); + +TLDNR; (Just read this if you're impatient) +=========================================== +At the in-game command prompt type... + + /js box( blocks.oak ) + +... creates a single wooden block at the cross-hairs or player location + + /js box( blocks.oak ).right(2).box( blocks.wool.black, 4, 9, 1) + +... creates a single wooden block and a 2001 black obelisk that is 4 +wide x 9 tall x 1 long in size. If you want to see what else +ScriptCraft's Drone can do, read on... + +Constructing a Drone Object +=========================== + +Drones can be created in any of the following ways... + + 1. Calling any one of the methods listed below will return a Drone object. For example... + + var d = box( blocks.oak ) + + ... creates a 1x1x1 wooden block at the cross-hairs or player's location and returns a Drone + object. This might look odd (if you're familiar with Java's Object-dot-method syntax) but all + of the Drone class's methods are also global functions that return new Drone objects. + This is short-hand for creating drones and is useful for playing around with Drones at the in-game + command prompt. It's shorter than typing ... + + var d = new Drone().box( blocks.oak ) + + ... All of the Drone's methods return `this` (self) so you can chain operations together like this... + + var d = box( blocks.oak ) + .up() + .box( blocks.oak ,3,1,3) + .down() + .fwd(2) + .box( blocks.oak ) + .turn() + .fwd(2) + .box( blocks.oak ) + .turn() + .fwd(2) + .box( blocks.oak ); + + 2. Using the following form... + + d = new Drone() + + ...will create a new Drone. If the cross-hairs are pointing at a + block at the time then, that block's location becomes the drone's + starting point. If the cross-hairs are _not_ pointing at a block, + then the drone's starting location will be 2 blocks directly in + front of the player. TIP: Building always happens right and front + of the drone's position... + + Plan View: + + ^ + | + | + D----> + + For convenience you can use a _corner stone_ to begin building. + The corner stone should be located just above ground level. If + the cross-hair is point at or into ground level when you create a + new Drone(), then building begins at that point. You can get + around this by pointing at a 'corner stone' just above ground + level or alternatively use the following statement... + + d = new Drone().up(); + + ... which will move the drone up one block as soon as it's created. + + ![corner stone](img/cornerstone1.png) + + 3. Or by using the following form... + + d = new Drone(x,y,z,direction,world); + + This will create a new Drone at the location you specified using + x, y, z In minecraft, the X axis runs west to east and the Z axis runs + north to south. The direction parameter says what direction you want + the drone to face: 0 = east, 1 = south, 2 = west, 3 = north. If the + direction parameter is omitted, the player's direction is used + instead. + + Both the `direction` and `world` parameters are optional. + + 4. Create a new Drone based on a Bukkit Location object... + + d = new Drone(location); + + This is useful when you want to create a drone at a given + `org.bukkit.Location` . The `Location` class is used throughout + the bukkit API. For example, if you want to create a drone when a + block is broken at the block's location you would do so like + this... + + events.on('block.BlockBreakEvent',function(listener,event){ + var location = event.block.location; + var drone = new Drone(location); + // do more stuff with the drone here... + }); + +Parameters +---------- + * location (optional) : *NB* If an `org.bukkit.Location` object is provided as a parameter, then it should be the only parameter. + * x (optional) : The x coordinate of the Drone + * y (optional) : The y coordinate of the Drone + * z (optional) : The z coordinate of the Drone + * direction (optional) : The direction in which the Drone is + facing. Possible values are 0 (east), 1 (south), 2 (west) or 3 (north) + * world (optional) : The world in which the drone is created. + +Drone.box() method +================== +the box() method is a convenience method for building things. (For the more performance-oriented method - see cuboid) + +parameters +---------- + * b - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. + Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` + * w (optional - default 1) - the width of the structure + * h (optional - default 1) - the height of the structure + * d (optional - default 1) - the depth of the structure - NB this is + not how deep underground the structure lies - this is how far + away (depth of field) from the drone the structure will extend. + +Example +------- +To create a black structure 4 blocks wide, 9 blocks tall and 1 block long... + + box(blocks.wool.black, 4, 9, 1); + +... or the following code does the same but creates a variable that can be used for further methods... + + var drone = new Drone(); + drone.box(blocks.wool.black, 4, 9, 1); + +![box example 1](img/boxex1.png) + +Drone.box0() method +=================== +Another convenience method - this one creates 4 walls with no floor or ceiling. + +Parameters +---------- + * block - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. + Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` + * width (optional - default 1) - the width of the structure + * height (optional - default 1) - the height of the structure + * length (optional - default 1) - the length of the structure - how far + away (depth of field) from the drone the structure will extend. + +Example +------- +To create a stone building with the insided hollowed out 7 wide by 3 tall by 6 long... + + box0( blocks.stone, 7, 3, 6); + +![example box0](img/box0ex1.png) + +Drone.boxa() method +=================== +Construct a cuboid using an array of blocks. As the drone moves first along the width axis, +then the height (y axis) then the length, each block is picked from the array and placed. + +Parameters +---------- + * blocks - An array of blocks - each block in the array will be placed in turn. + * width + * height + * length + +Example +------- +Construct a rainbow-colored road 100 blocks long... + + var rainbowColors = [blocks.wool.red, blocks.wool.orange, blocks.wool.yellow, blocks.wool.lime, + blocks.wool.lightblue, blocks.wool.blue, blocks.wool.purple]; + + boxa(rainbowColors,7,1,30); + +![boxa example](img/boxaex1.png) + +Drone Movement +============== +Drones can move freely in minecraft's 3-D world. You control the +Drone's movement using any of the following methods.. + + * up() + * down() + * left() + * right() + * fwd() + * back() + * turn() + +... Each of these methods takes a single optional parameter +`numBlocks` - the number of blocks to move in the given direction. If +no parameter is given, the default is 1. + +to change direction use the `turn()` method which also takes a single +optional parameter (numTurns) - the number of 90 degree turns to make. +Turns are always clock-wise. If the drone is facing north, then +drone.turn() will make the turn face east. If the drone is facing east +then drone.turn(2) will make the drone turn twice so that it is facing +west. + +Drone Positional Info +===================== + + * getLocation() - Returns a Bukkit Location object for the drone + +Drone Markers +============= +Markers are useful when your Drone has to do a lot of work. You can +set a check-point and return to the check-point using the move() +method. If your drone is about to undertake a lot of work - +e.g. building a road, skyscraper or forest you should set a +check-point before doing so if you want your drone to return to its +current location. + +A 'start' checkpoint is automatically created when the Drone is first created. + +Markers are created and returned to using the followng two methods... + + * chkpt - Saves the drone's current location so it can be returned to later. + * move - moves the drone to a saved location. Alternatively you can provide an + org.bukkit.Location object or x,y,z and direction parameters. + +Parameters +---------- + * name - the name of the checkpoint to save or return to. + +Example +------- + + drone.chkpt('town-square'); + // + // the drone can now go off on a long excursion + // + for (i = 0; i< 100; i++){ + drone.fwd(12).box(6); + } + // + // return to the point before the excursion + // + drone.move('town-square'); + +Drone.prism() method +==================== +Creates a prism. This is useful for roofs on houses. + +Parameters +---------- + + * block - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. + Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` + * width - the width of the prism + * length - the length of the prism (will be 2 time its height) + +Example +------- + + prism(blocks.oak,3,12); + +![prism example](img/prismex1.png) + +Drone.prism0() method +===================== +A variation on `prism` which hollows out the inside of the prism. It uses the same parameters as `prism`. + +Drone.cylinder() method +======================= +A convenience method for building cylinders. Building begins radius blocks to the right and forward. + +Parameters +---------- + + * block - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. + Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` + * radius + * height + +Example +------- +To create a cylinder of Iron 7 blocks in radius and 1 block high... + + cylinder(blocks.iron, 7 , 1); + +![cylinder example](img/cylinderex1.png) + +Drone.cylinder0() method +======================== +A version of cylinder that hollows out the middle. + +Example +------- +To create a hollow cylinder of Iron 7 blocks in radius and 1 block high... + + cylinder0(blocks.iron, 7, 1); + +![cylinder0 example](img/cylinder0ex1.png) + +Drone.arc() method +================== +The arc() method can be used to create 1 or more 90 degree arcs in the horizontal or vertical planes. +This method is called by cylinder() and cylinder0() and the sphere() and sphere0() methods. + +Parameters +---------- +arc() takes a single parameter - an object with the following named properties... + + * radius - The radius of the arc. + * blockType - The type of block to use - this is the block Id only (no meta). See [Data Values][dv]. + * meta - The metadata value. See [Data Values][dv]. + * orientation (default: 'horizontal') - the orientation of the arc - can be 'vertical' or 'horizontal'. + * stack (default: 1) - the height or length of the arc (depending on + the orientation - if orientation is horizontal then this parameter + refers to the height, if vertical then it refers to the length). + * strokeWidth (default: 1) - the width of the stroke (how many + blocks) - if drawing nested arcs it's usually a good idea to set + strokeWidth to at least 2 so that there are no gaps between each + arc. The arc method uses a [bresenham algorithm][bres] to plot + points along the circumference. + * fill - If true (or present) then the arc will be filled in. + * quadrants (default: + `{topleft:true,topright:true,bottomleft:true,bottomright:true}` - An + object with 4 properties indicating which of the 4 quadrants of a + circle to draw. If the quadrants property is absent then all 4 + quadrants are drawn. + +Examples +-------- +To draw a 1/4 circle (top right quadrant only) with a radius of 10 and stroke width of 2 blocks ... + + arc({blockType: blocks.iron, + meta: 0, + radius: 10, + strokeWidth: 2, + quadrants: { topright: true }, + orientation: 'vertical', + stack: 1, + fill: false + }); + +![arc example 1](img/arcex1.png) + +[bres]: http://en.wikipedia.org/wiki/Midpoint_circle_algorithm +[dv]: http://www.minecraftwiki.net/wiki/Data_values +Drone.door() method +=================== +create a door - if a parameter is supplied an Iron door is created otherwise a wooden door is created. + +Parameters +---------- + * doorType (optional - default wood) - If a parameter is provided then the door is Iron. + +Example +------- +To create a wooden door at the crosshairs/drone's location... + + var drone = new Drone(); + drone.door(); + +To create an iron door... + + drone.door( blocks.door_iron ); + +![iron door](img/doorex1.png) + +Drone.door2() method +==================== +Create double doors (left and right side) + +Parameters +---------- + * doorType (optional - default wood) - If a parameter is provided then the door is Iron. + +Example +------- +To create double-doors at the cross-hairs/drone's location... + + drone.door2(); + +![double doors](img/door2ex1.png) + +Drone.sign() method +=================== +Signs must use block 63 (stand-alone signs) or 68 (signs on walls) + +Parameters +---------- + * message - can be a string or an array of strings. + * block - can be 63 or 68 + +Example +------- +To create a free-standing sign... + + drone.sign(["Hello","World"],63); + +![ground sign](img/signex1.png) + +... to create a wall mounted sign... + + drone.sign(["Welcome","to","Scriptopia"], 68); + +![wall sign](img/signex2.png) + +Drone Trees methods +=================== + + * oak() + * spruce() + * birch() + * jungle() + +Example +------- +To create 4 trees in a row, point the cross-hairs at the ground then type `/js ` and ... + + up().oak().right(8).spruce().right(8).birch().right(8).jungle(); + +Trees won't always generate unless the conditions are right. You +should use the tree methods when the drone is directly above the +ground. Trees will usually grow if the drone's current location is +occupied by Air and is directly above an area of grass (That is why +the `up()` method is called first). + +![tree example](img/treeex1.png) + + +None of the tree methods require parameters. Tree methods will only be successful +if the tree is placed on grass in a setting where trees can grow. +Drone.garden() method +===================== +places random flowers and long grass (similar to the effect of placing bonemeal on grass) + +Parameters +---------- + + * width - the width of the garden + * length - how far from the drone the garden extends + +Example +------- +To create a garden 10 blocks wide by 5 blocks long... + + garden(10,5); + +![garden example](img/gardenex1.png) + +Drone.rand() method +=================== +rand takes either an array (if each blockid has the same chance of occurring) +or an object where each property is a blockid and the value is it's weight (an integer) + +Example +------- +place random blocks stone, mossy stone and cracked stone (each block has the same chance of being picked) + + rand( [blocks.brick.stone, blocks.brick.mossy, blocks.brick.cracked ],w,d,h) + +to place random blocks stone has a 50% chance of being picked, + + rand({blocks.brick.stone: 5, blocks.brick.mossy: 3, blocks.brick.cracked: 2},w,d,h) + +regular stone has a 50% chance, mossy stone has a 30% chance and cracked stone has just a 20% chance of being picked. + +Copy & Paste using Drone +======================== +A drone can be used to copy and paste areas of the game world. + +Drone.copy() method +=================== +Copies an area so it can be pasted elsewhere. The name can be used for +pasting the copied area elsewhere... + +Parameters +---------- + + * name - the name to be given to the copied area (used by `paste`) + * width - the width of the area to copy + * height - the height of the area to copy + * length - the length of the area (extending away from the drone) to copy + +Example +------- + + drone.copy('somethingCool',10,5,10).right(12).paste('somethingCool'); + +Drone.paste() method +==================== +Pastes a copied area to the current location. + +Example +------- +To copy a 10x5x10 area (using the drone's coordinates as the starting +point) into memory. the copied area can be referenced using the name +'somethingCool'. The drone moves 12 blocks right then pastes the copy. + + drone.copy('somethingCool',10,5,10) + .right(12) + .paste('somethingCool'); + +Chaining +======== + +All of the Drone methods return a Drone object, which means methods +can be 'chained' together so instead of writing this... + + drone = new Drone(); + drone.fwd(3); + drone.left(2); + drone.box(2); // create a grass block + drone.up(); + drone.box(2); // create another grass block + drone.down(); + +...you could simply write ... + + var drone = new Drone().fwd(3).left(2).box(2).up().box(2).down(); + +... since each Drone method is also a global function that constructs +a drone if none is supplied, you can shorten even further to just... + + fwd(3).left(2).box(2).up().box(2).down() + +The Drone object uses a [Fluent Interface][fl] to make ScriptCraft +scripts more concise and easier to write and read. Minecraft's +in-game command prompt is limited to about 80 characters so chaining +drone commands together means more can be done before hitting the +command prompt limit. For complex building you should save your +commands in a new script file and load it using /js load() + +[fl]: http://en.wikipedia.org/wiki/Fluent_interface + +Drone Properties +================ + + * x - The Drone's position along the west-east axis (x increases as you move east) + * y - The Drone's position along the vertical axis (y increses as you move up) + * z - The Drone's position along the north-south axis (z increases as you move south) + * dir - The Drone's direction 0 is east, 1 is south , 2 is west and 3 is north. + +Extending Drone +=============== +The Drone object can be easily extended - new buidling recipes/blue-prints can be added and can +become part of a Drone's chain using the *static* method `Drone.extend`. + +Drone.extend() static method +============================ +Use this method to add new methods (which also become chainable global functions) to the Drone object. + +Parameters +---------- + * name - The name of the new method e.g. 'pyramid' + * function - The method body. + +Example +------- + + // submitted by [edonaldson][edonaldson] + Drone.extend('pyramid', function(block,height){ + this.chkpt('pyramid'); + for (var i = height; i > 0; i -= 2) { + this.box(block, i, 1, i).up().right().fwd(); + } + return this.move('pyramid'); + }); + +Once the method is defined (it can be defined in a new pyramid.js file) it can be used like so... + + var d = new Drone(); + d.pyramid(blocks.brick.stone, 12); + +... or simply ... + + pyramid(blocks.brick.stone, 12); + +[edonaldson]: https://github.com/edonaldson + +Drone Constants +=============== + +Drone.PLAYER_STAIRS_FACING +-------------------------- +An array which can be used when constructing stairs facing in the Drone's direction... + + var d = new Drone(); + d.box(blocks.stairs.oak + ':' + Drone.PLAYER_STAIRS_FACING[d.dir]); + +... will construct a single oak stair block facing the drone. + +Drone.PLAYER_SIGN_FACING +------------------------ +An array which can be used when placing signs so they face in a given direction. +This is used internally by the Drone.sign() method. It should also be used for placing +any of the following blocks... + + * chest + * ladder + * furnace + * dispenser + +To place a chest facing the Drone ... + + drone.box( blocks.chest + ':' + Drone.PLAYER_SIGN_FACING[drone.dir]); + +Drone.PLAYER_TORCH_FACING +------------------------- +Used when placing torches so that they face towards the drone. + + drone.box( blocks.torch + ':' + Drone.PLAYER_TORCH_FACING[drone.dir]); + +Drone.times() Method +==================== +The times() method makes building multiple copies of buildings easy. It's possible to create rows or grids of buildings without resorting to `for` or `while` loops. + +Parameters +---------- + * numTimes (optional - default 2) : The number of times you want to repeat the preceding statements. + +Example +------- +Say you want to do the same thing over and over. You have a couple of options... + + * You can use a for loop... + + d = new Drone(); for (var i =0;i < 4; i++){ d.cottage().right(8); } + +While this will fit on the in-game prompt, it's awkward. You need to +declare a new Drone object first, then write a for loop to create the +4 cottages. It's also error prone, even the `for` loop is too much +syntax for what should really be simple. + + * You can use a while loop... + + d = new Drone(); var i=4; while (i--){ d.cottage().right(8); } + +... which is slightly shorter but still too much syntax. Each of the +above statements is fine for creating a 1-dimensional array of +structures. But what if you want to create a 2-dimensional or +3-dimensional array of structures? Enter the `times()` method. + +The `times()` method lets you repeat commands in a chain any number of +times. So to create 4 cottages in a row you would use the following +statement... + + cottage().right(8).times(4); + +...which will build a cottage, then move right 8 blocks, then do it +again 4 times over so that at the end you will have 4 cottages in a +row. What's more the `times()` method can be called more than once in +a chain. So if you wanted to create a *grid* of 20 houses ( 4 x 5 ), +you would do so using the following statement... + + cottage().right(8).times(4).fwd(8).left(32).times(5); + +... breaking it down... + + 1. The first 3 calls in the chain ( `cottage()`, `right(8)`, + `times(4)` ) build a single row of 4 cottages. + + 2. The last 3 calls in the chain ( `fwd(8)`, `left(32)`, `times(5)` ) + move the drone forward 8 then left 32 blocks (4 x 8) to return to + the original x coordinate, then everything in the chain is + repeated again 5 times so that in the end, we have a grid of 20 + cottages, 4 x 5. Normally this would require a nested loop but + the `times()` method does away with the need for loops when + repeating builds. + +Another example: This statement creates a row of trees 2 by 3 ... + + oak().right(10).times(2).left(20).fwd(10).times(3) + +... You can see the results below. + +![times example 1](img/times-trees.png) + +Drone.blocktype() method +======================== +Creates the text out of blocks. Useful for large-scale in-game signs. + +Parameters +---------- + + * message - The message to create - (use `\n` for newlines) + * foregroundBlock (default: black wool) - The block to use for the foreground + * backgroundBlock (default: none) - The block to use for the background + +Example +------- +To create a 2-line high message using glowstone... + + blocktype("Hello\nWorld",blocks.glowstone); + +![blocktype example][imgbt1] + +[imgbt1]: img/blocktype1.png + +Blocks Module +============= +You hate having to lookup [Data Values][dv] when you use ScriptCraft's Drone() functions. So do I. +So I created this blocks object which is a helper object for use in construction. + +Examples +-------- + + box( blocks.oak ); // creates a single oak wood block + box( blocks.sand, 3, 2, 1 ); // creates a block of sand 3 wide x 2 high x 1 long + box( blocks.wool.green, 2 ); // creates a block of green wool 2 blocks wide + +Color aliased properties that were a direct descendant of the blocks object are no longer used to avoid confusion with carpet and stained clay blocks. In addition, there's a convenience array `blocks.rainbow` which is an array of the 7 colors of the rainbow (or closest approximations). + +Drone.sphere() method +===================== +Creates a sphere. + +Parameters +---------- + + * block - The block the sphere will be made of. + * radius - The radius of the sphere. + +Example +------- +To create a sphere of Iron with a radius of 10 blocks... + + sphere( blocks.iron, 10); + +![sphere example](img/sphereex1.png) + +Spheres are time-consuming to make. You *can* make large spheres (250 radius) but expect the +server to be very busy for a couple of minutes while doing so. + +Drone.sphere0() method +====================== +Creates an empty sphere. + +Parameters +---------- + + * block - The block the sphere will be made of. + * radius - The radius of the sphere. + +Example +------- +To create a sphere of Iron with a radius of 10 blocks... + + sphere0( blocks.iron, 10); + +Spheres are time-consuming to make. You *can* make large spheres (250 radius) but expect the +server to be very busy for a couple of minutes while doing so. + +Drone.hemisphere() method +========================= +Creates a hemisphere. Hemispheres can be either north or south. + +Parameters +---------- + + * block - the block the hemisphere will be made of. + * radius - the radius of the hemisphere + * northSouth - whether the hemisphere is 'north' or 'south' + +Example +------- +To create a wood 'north' hemisphere with a radius of 7 blocks... + + hemisphere(blocks.oak, 7, 'north'); + +![hemisphere example](img/hemisphereex1.png) + +Drone.hemisphere0() method +========================= +Creates a hollow hemisphere. Hemispheres can be either north or south. + +Parameters +---------- + + * block - the block the hemisphere will be made of. + * radius - the radius of the hemisphere + * northSouth - whether the hemisphere is 'north' or 'south' + +Example +------- +To create a glass 'north' hemisphere with a radius of 20 blocks... + + hemisphere0(blocks.glass, 20, 'north'); + +![hemisphere example](img/hemisphereex2.png) + +Drone.rainbow() method +====================== +Creates a Rainbow. + +Parameters +---------- + + * radius (optional - default:18) - The radius of the rainbow + +Example +------- + + var d = new Drone(); + d.rainbow(30); + +![rainbow example](img/rainbowex1.png) + +Drone.spiral_stairs() method +============================ +Constructs a spiral staircase with slabs at each corner. + +Parameters +---------- + + * stairBlock - The block to use for stairs, should be one of the following... + - 'oak' + - 'spruce' + - 'birch' + - 'jungle' + - 'cobblestone' + - 'brick' + - 'stone' + - 'nether' + - 'sandstone' + - 'quartz' + * flights - The number of flights of stairs to build. + +![Spiral Staircase](img/spiralstair1.png) + +Example +------- +To construct a spiral staircase 5 floors high made of oak... + + spiral_stairs('oak', 5); + +Classroom Module +================ +The `classroom` object contains a couple of utility functions for use +in a classroom setting. The goal of these functions is to make it +easier for tutors to facilitate ScriptCraft for use by students in a +classroom environment. Although granting ScriptCraft access to +students on a shared server is potentially risky (Students can +potentially abuse it), it is slighlty less risky than granting +operator privileges to each student. (Enterprising students will +quickly realise how to grant themselves and others operator privileges +once they have access to ScriptCraft). + +The goal of this module is not so much to enforce restrictions +(security or otherwise) but to make it easier for tutors to setup a shared server +so students can learn Javascript. + +classroom.allowScripting() function +=================================== +Allow or disallow anyone who connects to the server (or is already +connected) to use ScriptCraft. This function is preferable to granting 'ops' privileges +to every student in a Minecraft classroom environment. + +Parameters +---------- + + * canScript : true or false + +Example +------- +To allow all players (and any players who connect to the server) to +use the `js` and `jsp` commands... + + /js classroom.allowScripting(true) + +To disallow scripting (and prevent players who join the server from using the commands)... + + /js classroom.allowScripting(false) + +Only ops users can run the classroom.allowScripting() function - this is so that students +don't try to bar themselves and each other from scripting. + +events Module +============= +The Events module provides a thin wrapper around Bukkit's +Event-handling API. Bukkit's Events API makes use of Java Annotations +which are not available in Javascript, so this module provides a +simple way to listen to minecraft events in javascript. + +events.on() static method +========================= +This method is used to register event listeners. + +Parameters +---------- + + * eventName - A string or java class. If a string is supplied it must + be part of the Bukkit event class name. See [Bukkit API][buk] for + details of the many bukkit event types. When a string is supplied + there is no need to provide the full class name - you should omit + the 'org.bukkit.event' prefix. e.g. if the string + "block.BlockBreakEvent" is supplied then it's converted to the + org.bukkit.event.block.BlockBreakEvent class . + + If a java class is provided (say in the case where you've defined + your own custom event) then provide the full class name (without + enclosing quotes). + + * callback - A function which will be called whenever the event + fires. The callback should take 2 parameters, listener (the Bukkit + registered listener for this callback) and event (the event fired). + + * priority (optional - default: "HIGHEST") - The priority the + listener/callback takes over other listeners to the same + event. Possible values are "HIGH", "HIGHEST", "LOW", "LOWEST", + "NORMAL", "MONITOR". For an explanation of what the different + priorities mean refer to bukkit's [Event API Reference][buk2]. + +Returns +------- +An org.bukkit.plugin.RegisteredListener object which can be used to +unregister the listener. This same object is passed to the callback +function each time the event is fired. + +Example: +------ +The following code will print a message on screen every time a block is broken in the game + + events.on("block.BlockBreakEvent", function(listener, evt){ + echo (evt.player.name + " broke a block!"); + }); + +To handle an event only once and unregister from further events... + + events.on("block.BlockBreakEvent", function(listener, evt){ + print (evt.player.name + " broke a block!"); + evt.handlers.unregister(listener); + }); + +To unregister a listener *outside* of the listener function... + + var myBlockBreakListener = events.on("block.BlockBreakEvent",function(l,e){ ... }); + ... + var handlers = org.bukkit.event.block.BlockBreakEvent.getHandlerList(); + handlers.unregister(myBlockBreakListener); + +[buk2]: http://wiki.bukkit.org/Event_API_Reference +[buk]: http://jd.bukkit.org/dev/apidocs/index.html?org/bukkit/event/Event.html + +Fireworks Module +================ +The fireworks module makes it easy to create fireworks using +ScriptCraft. The module has a single function `firework` which takes +a `org.bukkit.Location` as its 1 and only parameter. + +Examples +-------- +The module also extends the `Drone` object adding a `firework` method +so that fireworks can be created as a part of a Drone chain. For +Example.... + + /js firework() + +... creates a single firework, while .... + + /js firework.fwd(3).times(5) + +... creates 5 fireworks in a row. Fireworks have also been added as a +possible option for the `arrow` module. To have a firework launch +where an arrow strikes... + + /js arrows.firework() + +To call the fireworks.firework() function directly, you must provide a +location. For example... + + /js fireworks.firework(self.location); + +![firework example](img/firework.png) + +http.request() function +==================== +The http.request() function will fetch a web address asynchronously (on a +separate thread)and pass the URL's response to a callback function +which will be executed synchronously (on the main thread). In this +way, http.request() can be used to fetch web content without blocking the +main thread of execution. + +Parameters +---------- + + * request: The request details either a plain URL e.g. "http://scriptcraft.js/sample.json" or an object with the following properties... + + - url: The URL of the request. + - method: Should be one of the standard HTTP methods, GET, POST, PUT, DELETE (defaults to GET). + - params: A Javascript object with name-value pairs. This is for supplying parameters to the server. + + * callback: The function to be called when the Web request has completed. This function takes the following parameters... + - responseCode: The numeric response code from the server. If the server did not respond with 200 OK then the response parameter will be undefined. + - response: A string (if the response is of type text) or object containing the HTTP response body. + +Example +------- +The following example illustrates how to use http.request to make a request to a JSON web service and evaluate its response... + + var jsResponse; + http.request("http://scriptcraftjs.org/sample.json",function(responseCode, responseBody){ + jsResponse = eval("(" + responseBody + ")"); + }); + +... The following example illustrates a more complex use-case POSTing parameters to a CGI process on a server... + + http.request({ url: "http://pixenate.com/pixenate/pxn8.pl", + method: "POST", + params: {script: "[]"} + }, function( responseCode, responseBody){ + var jsObj = eval("(" + responseBody + ")"); + }); + +Utilities Module +================ +Miscellaneous utility functions and classes to help with programming. + + * locationToString(Location) - returns a bukkit Location object in string form. + + * getPlayerObject(playerName) - returns the Player object for a named + player or `self` if no name is provided. + +utils.foreach() function +======================== +The utils.foreach() function is a utility function for iterating over +an array of objects (or a java.util.Collection of objects) and processing each object in turn. Where +utils.foreach() differs from other similar functions found in +javascript libraries, is that utils.foreach can process the array +immediately or can process it *nicely* by processing one item at a +time then delaying processing of the next item for a given number of +server ticks (there are 20 ticks per second on the minecraft main +thread). This method relies on Bukkit's [org.bukkit.scheduler][sched] +package for scheduling processing of arrays. + +[sched]: http://jd.bukkit.org/beta/apidocs/org/bukkit/scheduler/package-summary.html + +Parameters +---------- + + * array : The array to be processed - It can be a javascript array, a java array or java.util.Collection + * callback : The function to be called to process each item in the + array. The callback function should have the following signature + `callback(item, index, object, array)`. That is the callback will + be called with the following parameters.... + + - item : The item in the array + - index : The index at which the item can be found in the array. + - object : Additional (optional) information passed into the foreach method. + - array : The entire array. + + * object (optional) : An object which may be used by the callback. + * delay (optional, numeric) : If a delay is specified (in ticks - 20 + ticks = 1 second), then the processing will be scheduled so that + each item will be processed in turn with a delay between the completion of each + item and the start of the next. This is recommended for big builds (say 200 x 200 x 200 + blocks) or any CPU-intensive process. + * onDone (optional, function) : A function to be executed when all processing + is complete. This parameter is only used when the processing is delayed. (It's optional even if a + delay parameter is supplied). + +If called with a delay parameter then foreach() will return +immediately after processing just the first item in the array (all +subsequent items are processed later). If your code relies on the +completion of the array processing, then provide an `onDone` parameter +and put the code there. + +Example +------- +The following example illustrates how to use foreach for immediate processing of an array... + + var players = ["moe", "larry", "curly"]; + utils.foreach (players, function(item){ + server.getPlayer(item).sendMessage("Hi " + item); + }); + +... The `utils.foreach()` function can work with Arrays or any Java-style collection. This is important +because many objects in the Bukkit API use Java-style collections... + + utils.foreach( server.onlinePlayers, function(player){ + player.chat("Hello!"); + }); + +... the above code sends a "Hello!" to every online player. + +The following example is a more complex use case - The need to build an enormous structure +without hogging CPU usage... + + // build a structure 200 wide x 200 tall x 200 long + // (That's 8 Million Blocks - enough to tax any machine!) + + var a = []; + a.length = 200; + var drone = new Drone(); + var processItem = function(item, index, object, array){ + // build a box 200 wide by 200 long then move up + drone.box(blocks.wood, 200, 1, 200).up(); + }; + // by the time the job's done 'self' might be someone else + // assume this code is within a function/closure + var player = self; + var onDone = function(){ + player.sendMessage("Job Done!"); + }; + utils.foreach (a, processItem, null, 10, onDone); + +utils.nicely() function +======================= +The utils.nicely() function is for performing processing using the +[org.bukkit.scheduler][sched] package/API. utils.nicely() lets you +process with a specified delay between the completion of each `next()` +function and the start of the next `next()` function. +`utils.nicely()` is a recursive function - that is - it calls itself +(schedules itself actually) repeatedly until `hasNext` returns false. + +Parameters +---------- + + * next : A function which will be called if processing is to be done. + * hasNext : A function which is called to determine if the `next` + callback should be invoked. This should return a boolean value - + true if the `next` function should be called (processing is not + complete), false otherwise. + * onDone : A function which is to be called when all processing is complete (hasNext returned false). + * delay : The delay (in server ticks - 20 per second) between each call. + +Example +------- +See the source code to utils.foreach for an example of how utils.nicely is used. + +utils.at() function +=================== +The utils.at() function will perform a given task at a given time every +(minecraft) day. + +Parameters +---------- + + * time24hr : The time in 24hr form - e.g. 9:30 in the morning is "09:30" while + 9:30 pm is "21:30", midnight is "00:00" and midday is "12:00" + * callback : A javascript function which will be invoked at the given time. + * world : (optional) Each world has its own clock. If no world is specified, the server's first world is used. + +Example +------- + +To warn players when night is approaching... + + utils.at( "19:00", function() { + + utils.foreach( server.onlinePlayers, function(player){ + player.chat("The night is dark and full of terrors!"); + }); + + }, self.world); + +String class extensions +----------------------- +The following chat-formatting methods are added to the javascript String class.. + + * black() + * darkblue() + * blue() + * darkgreen() + * darkaqua() + * darkred() + * purple() + * gold() + * gray() + * darkgray() + * indigo() + * brightgreen() + * green() + * aqua() + * red() + * pink() + * yellow() + * white() + * bold() + * random() + * strike() + * underline() + * italic() + * reset() + +Example +------- + + var boldGoldText = "Hello World".bold().gold(); + self.sendMessage(boldGoldText); + +

Hello World

+ diff --git a/src/main/javascript/core/_scriptcraft.js b/src/main/javascript/core/_scriptcraft.js index 12a949d..cb3ba0c 100644 --- a/src/main/javascript/core/_scriptcraft.js +++ b/src/main/javascript/core/_scriptcraft.js @@ -1,6 +1,5 @@ /************************************************************************ -ScriptCraft API Reference -========================= +# ScriptCraft API Reference Walter Higgins @@ -8,8 +7,8 @@ Walter Higgins [email]: mailto:walter.higgins@gmail.com?subject=ScriptCraft_API_Reference -Module Loading -============== +## Module Loading + At server startup the ScriptCraft Java plugin is loaded and once loaded the Java plugin will in turn begin loading all of the javascript (.js) files it finds in the js-plugins directory (in the @@ -19,10 +18,12 @@ will be created and all of the bundled javascript files will be unzipped into it from a bundled resource within the Java plugin. The very first javascript file to load will always be js-plugins/core/_scriptcraft.js. Then all other javascript files are -loaded. +loaded except for filenames begin with `_` (underscore), such files +are considered to be private modules and will not be automatically +loaded at startup. + +### Directory structure -Directory structure -------------------- The js-plugins directory is loosely organised into subdirectories - one for each module. Each subdirectory in turn can contain one or more javascript files. Within each directory, a javascript file with the @@ -32,8 +33,8 @@ always load before any other files in the drone/ directory. Similarly utils/utils.js will always load before any other files in the utils/ directory. -Directories ------------ +### Directories + As of February 10 2013, the js-plugins directory has the following sub-directories... * core - Contains javascript files containing Core functionality crucial to ScriptCraft and modules which use it. @@ -45,8 +46,8 @@ As of February 10 2013, the js-plugins directory has the following sub-directori * chat - The chat plugin/module * alias - The alias plugin/module -Core Module -=========== +## Core Module + This module defines commonly used functions by all plugins... * load (filename,warnOnFileNotFound) - loads and evaluates a javascript file, returning the evaluated object. @@ -56,16 +57,18 @@ This module defines commonly used functions by all plugins... * plugin (name, interface, isPersistent) - defines a new plugin. If isPersistent is true then the plugin doesn't have to worry about loading and saving state - that will be done by the framework. Just - make sure that anything you want to save (and restore) is in the + make sure that anything you want to save (and restore) is in the plugin's 'store' property - this will be created automatically if not - already defined. (its type is object {} ) + already defined. (its type is object {} ) . More on plugins below. * ready (function) - specifies code to be executed only when all the plugins have loaded. - * command (name, function) - defines a command that can be used by non-operators. + * command (name, function) - defines a command that can be used by + non-operators. The `command` function provides a way for plugin + developers to provide new commands for use by players. + +### load() function -load() function ---------------- The load() function is used by ScriptCraft at startup to load all of the javascript modules and data. You normally wouldn't need to call this function directly. If you put a javascript file anywhere in the @@ -75,18 +78,16 @@ with an underscore `_` character. These files will not be automatically loaded at startup as they are assumed to be files managed / loaded by plugins. -Parameters ----------- +#### Parameters - * filenames - An array of file names or a single file name. + * filename - The name of the file to load. * warnOnFileNotFound (optional - default: false) - warn if the file was not found. -Return ------- +#### Return + load() will return the result of the last statement evaluated in the file. -Example -------- +#### Example load(__folder + "myFile.js"); // loads a javascript file and evaluates it. @@ -94,17 +95,18 @@ Example myData.json contents... - __data = {players:{ - walterh:{ - h: ["jsp home {1}"], - sunny:["time set 0", - "weather clear"] - } - } - } + __data = { + players: { + walterh: { + h: ["jsp home {1}"], + sunny:["time set 0", + "weather clear"] + } + } + } + +### save() function -save() function ---------------- The save() function saves an in-memory javascript object to a specified file. Under the hood, save() uses JSON (specifically json2.js) to save the object. Again, there will usually be no need to @@ -113,14 +115,12 @@ automatically if they are declared using the `plugin()` function. Any in-memory object saved using the `save()` function can later be restored using the `load()` function. -Parameters ----------- +#### Parameters * objectToSave : The object you want to save. * filename : The name of the file you want to save it to. -Example -------- +#### Example var myObject = { name: 'John Doe', aliases: ['John Ray', 'John Mee'], @@ -133,8 +133,8 @@ johndoe.json contents... "aliases": ["John Ray", "John Mee"], "date_of_birth": "1982/01/31" }; -plugin() function ------------------ +### plugin() function + 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. @@ -145,23 +145,22 @@ automatically saved at shutdown and loaded at startup by ScriptCraft. This makes it easier to write plugins which need to persist data. -Parameters ----------- +#### Parameters * 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. -Example -------- +#### Example + 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]: http://walterhiggins.net/blog/ScriptCraft-1-Month-later -command() function ------------------- +### command() function + 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 @@ -169,8 +168,7 @@ 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. -Parameters ----------- +#### Parameters * 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. @@ -181,12 +179,12 @@ Parameters can intercept Tab-Completion of the `/jsp ` command - advanced usage - see alias/alias.js for example. -Example -------- +#### Example + See chat/colors.js or alias/alias.js or homes/homes.js for examples of how to use the `command()` function. -ready() function ----------------- +### ready() function + The `ready()` function provides a way for plugins to do additional setup once all of the other plugins/modules have loaded. For example, event listener registration can only be done after the @@ -210,72 +208,11 @@ The execution of the function object passed to the `ready()` function is *deferred* until all of the plugins/modules have loaded. That way you are guaranteed that when the function is invoked, all of the plugins/modules have been loaded and evaluated and are ready to use. + ***/ var global = this; -/************************************************************************* -setTimeout() function ---------------------- - -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. - -If Node.js supports setTimeout() then it's probably good for ScriptCraft to support it too. - -[btdoc]: http://jd.bukkit.org/beta/apidocs/org/bukkit/scheduler/BukkitTask.html - -***/ - global.setTimeout = function( callback, delayInMillis){ - // - // javascript programmers familiar with setTimeout know that it expects - // a delay in milliseconds. However, bukkit's scheduler expects a delay in ticks - // (where 1 tick = 1/20th second) - // - var bukkitTask = server.scheduler.runTaskLater(__plugin, callback, delayInMillis/50); - return bukkitTask; - }; - -/************************************************************************* -clearTimeout() function ---------------------- -A scriptcraft implementation of clearTimeout(). - -***/ - global.clearTimeout = function(bukkitTask){ - bukkitTask.cancel(); - }; - -/************************************************************************* -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 - -***/ - global.setInterval = function(callback, intervalInMillis){ - var delay = intervalInMillis/ 50; - var bukkitTask = server.scheduler.runTaskTimer(__plugin, callback, delay, delay); - return bukkitTask; - }; -/************************************************************************* -clearInterval() function ---------------------- -A scriptcraft implementation of clearInterval(). - -***/ - global.clearInterval = function(bukkitTask){ - bukkitTask.cancel(); - }; /************************************************************************* Core Module - Special Variables @@ -317,6 +254,23 @@ var server = org.bukkit.Bukkit.server; var _require = function(path) { var file = new java.io.File(path); + if (!file.exists()){ + if (path.match(/\.js$/i)){ + __plugin.logger.warning('require("' + path + '") failed. File not found'); + return; + }else{ + path = path + '.js'; + file = new java.io.File(path); + if (!file.exists()){ + __plugin.logger.warning('require("' + path + '") failed. File not found'); + return; + } + } + } + if (file.isDirectory()){ + __plugin.logger.warning('require("' + path + '") directories not yet supported.'); + return; + } var canonizedFilename = _canonize(file); if (_loadedModules[canonizedFilename]){ return _loadedModules[canonizedFilename]; @@ -327,12 +281,17 @@ var server = org.bukkit.Bukkit.server; var reader = new java.io.FileReader(file); var br = new java.io.BufferedReader(reader); var code = ""; + var module = {id: canonizedFilename}; while ((r = br.readLine()) !== null) code += r + "\n"; - code = "var result = {};(function(exports){ " + code + "; return exports;}(result))"; + + var head = "var result = {};(function(exports,module){ "; + var tail = "; return exports;}(result," + JSON.stringify(module) + "))"; + code = head + code + tail; + _loadedModules[canonizedFilename] = __engine.eval(code); + return _loadedModules[canonizedFilename]; }; - global.loadedModules = _loadedModules; global.require = _require; @@ -769,8 +728,81 @@ var server = org.bukkit.Bukkit.server; }; /************************************************************************* -refresh() function ------------------- +## Miscellaneous Core Functions + +### setTimeout() function + +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. + +If Node.js supports setTimeout() then it's probably good for ScriptCraft to support it too. + +[btdoc]: http://jd.bukkit.org/beta/apidocs/org/bukkit/scheduler/BukkitTask.html + +#### Example + + // + // start a storm in 5 seconds + // + setTimeout( function() { + var world = server.worlds.get(0); + world.setStorm(true); + }, 5000); + +***/ + global.setTimeout = function( callback, delayInMillis){ + // + // javascript programmers familiar with setTimeout know that it expects + // a delay in milliseconds. However, bukkit's scheduler expects a delay in ticks + // (where 1 tick = 1/20th second) + // + var bukkitTask = server.scheduler.runTaskLater(__plugin, callback, delayInMillis/50); + return bukkitTask; + }; + +/************************************************************************* +### clearTimeout() function + +A scriptcraft implementation of clearTimeout(). + +***/ + global.clearTimeout = function(bukkitTask){ + bukkitTask.cancel(); + }; + +/************************************************************************* +### 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 + +***/ + global.setInterval = function(callback, intervalInMillis){ + var delay = intervalInMillis/ 50; + var bukkitTask = server.scheduler.runTaskTimer(__plugin, callback, delay, delay); + return bukkitTask; + }; +/************************************************************************* +### clearInterval() function + +A scriptcraft implementation of clearInterval(). + +***/ + global.clearInterval = function(bukkitTask){ + bukkitTask.cancel(); + }; + +/************************************************************************* +### refresh() function + The refresh() function will ... 1. Disable the ScriptCraft plugin. @@ -822,8 +854,6 @@ See [issue #69][issue69] for more information. _runUnloadHandlers(); org.bukkit.event.HandlerList["unregisterAll(org.bukkit.plugin.Plugin)"](__plugin); }); - - }()); diff --git a/src/main/javascript/drone/drone.js b/src/main/javascript/drone/drone.js index 3de3d24..2d4ccee 100644 --- a/src/main/javascript/drone/drone.js +++ b/src/main/javascript/drone/drone.js @@ -19,11 +19,11 @@ TLDNR; (Just read this if you're impatient) =========================================== At the in-game command prompt type... - /js box(blocks.oak) + /js box( blocks.oak ) ... creates a single wooden block at the cross-hairs or player location - /js box(5).right(2).box('35:15',4,9,1) + /js box( blocks.oak ).right(2).box( blocks.wool.black, 4, 9, 1) ... creates a single wooden block and a 2001 black obelisk that is 4 wide x 9 tall x 1 long in size. If you want to see what else @@ -36,7 +36,7 @@ Drones can be created in any of the following ways... 1. Calling any one of the methods listed below will return a Drone object. For example... - var d = box(blocks.oak) + var d = box( blocks.oak ) ... creates a 1x1x1 wooden block at the cross-hairs or player's location and returns a Drone object. This might look odd (if you're familiar with Java's Object-dot-method syntax) but all @@ -44,11 +44,22 @@ Drones can be created in any of the following ways... This is short-hand for creating drones and is useful for playing around with Drones at the in-game command prompt. It's shorter than typing ... - var d = new Drone().box(5) + var d = new Drone().box( blocks.oak ) ... All of the Drone's methods return `this` (self) so you can chain operations together like this... - var d = box(5).up().box(5,3,1,3).down().fwd(2).box(5).turn().fwd(2).box(5).turn().fwd(2).box(5) + var d = box( blocks.oak ) + .up() + .box( blocks.oak ,3,1,3) + .down() + .fwd(2) + .box( blocks.oak ) + .turn() + .fwd(2) + .box( blocks.oak ) + .turn() + .fwd(2) + .box( blocks.oak ); 2. Using the following form... @@ -1768,7 +1779,7 @@ Another example: This statement creates a row of trees 2 by 3 ... // which return a drone object // this way drones can be created and used as follows... // - // /js box(5,7,3,4) + // /js box( blocks.oak, 7, 3, 4) // // ... which is a short-hand way to create a wooden building 7x3x4 // diff --git a/src/main/javascript/signs/menu.js b/src/main/javascript/signs/menu.js index 03729b9..eb33eb3 100644 --- a/src/main/javascript/signs/menu.js +++ b/src/main/javascript/signs/menu.js @@ -137,7 +137,7 @@ var signs = signs || plugin("signs", { /* a new sign definition - need to store (in-memory only) - it's behaviour and bring back to life other signs of the + its behaviour and bring back to life other signs of the same type in the world. Look for other static signs in the world with this same label and make dynamic again. */ From a760cec44c0571c228b0a3f63e94ad6121102fa9 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 17 Dec 2013 23:52:53 +0000 Subject: [PATCH 012/456] spelling error in docs --- docs/API-Reference.md | 2 +- src/main/javascript/core/_scriptcraft.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 63a0378..e407e9d 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -17,7 +17,7 @@ will be created and all of the bundled javascript files will be unzipped into it from a bundled resource within the Java plugin. The very first javascript file to load will always be js-plugins/core/_scriptcraft.js. Then all other javascript files are -loaded except for filenames begin with `_` (underscore), such files +loaded except for filenames which begin with `_` (underscore), such files are considered to be private modules and will not be automatically loaded at startup. diff --git a/src/main/javascript/core/_scriptcraft.js b/src/main/javascript/core/_scriptcraft.js index cb3ba0c..e7aecdd 100644 --- a/src/main/javascript/core/_scriptcraft.js +++ b/src/main/javascript/core/_scriptcraft.js @@ -18,7 +18,7 @@ will be created and all of the bundled javascript files will be unzipped into it from a bundled resource within the Java plugin. The very first javascript file to load will always be js-plugins/core/_scriptcraft.js. Then all other javascript files are -loaded except for filenames begin with `_` (underscore), such files +loaded except for filenames which begin with `_` (underscore), such files are considered to be private modules and will not be automatically loaded at startup. From 90c11716274ffbb9b10468a5d160578e15512d9b Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 17 Dec 2013 23:54:06 +0000 Subject: [PATCH 013/456] now named API-Reference.md --- docs/api.md | 1466 --------------------------------------------------- 1 file changed, 1466 deletions(-) delete mode 100644 docs/api.md diff --git a/docs/api.md b/docs/api.md deleted file mode 100644 index 6125c04..0000000 --- a/docs/api.md +++ /dev/null @@ -1,1466 +0,0 @@ -ScriptCraft API Reference -========================= - -Walter Higgins - -[walter.higgins@gmail.com][email] - -[email]: mailto:walter.higgins@gmail.com?subject=ScriptCraft_API_Reference - -Module Loading -============== -At server startup the ScriptCraft Java plugin is loaded and once -loaded the Java plugin will in turn begin loading all of the -javascript (.js) files it finds in the js-plugins directory (in the -current working directory). If this is the first time the ScriptCraft -plugin is loaded, then the js-plugins directory will not yet exist, it -will be created and all of the bundled javascript files will be -unzipped into it from a bundled resource within the Java plugin. The -very first javascript file to load will always be -js-plugins/core/_scriptcraft.js. Then all other javascript files are -loaded. - -Directory structure -------------------- -The js-plugins directory is loosely organised into subdirectories - -one for each module. Each subdirectory in turn can contain one or more -javascript files. Within each directory, a javascript file with the -same filename as the directory will always be loaded before all other -files in the same directory. So for example, drone/drone.js will -always load before any other files in the drone/ directory. Similarly -utils/utils.js will always load before any other files in the utils/ -directory. - -Directories ------------ -As of February 10 2013, the js-plugins directory has the following sub-directories... - - * core - Contains javascript files containing Core functionality crucial to ScriptCraft and modules which use it. - * drone - Contains the drone module and drone extensions. Drone was the first scriptcraft module. - * ext - Contains external 3rd party javascript libraries (e.g. json2.js - the JSON lib) - * mini-games - Contains mini-games - * arrows - The arrows module - * signs - The signs module - * chat - The chat plugin/module - * alias - The alias plugin/module - -Core Module -=========== -This module defines commonly used functions by all plugins... - - * load (filename,warnOnFileNotFound) - loads and evaluates a javascript file, returning the evaluated object. - - * save (object, filename) - saves an object to a file. - - * plugin (name, interface, isPersistent) - defines a new plugin. If - isPersistent is true then the plugin doesn't have to worry about - loading and saving state - that will be done by the framework. Just - make sure that anything you want to save (and restore) is in the - 'store' property - this will be created automatically if not - already defined. (its type is object {} ) - - * ready (function) - specifies code to be executed only when all the plugins have loaded. - - * command (name, function) - defines a command that can be used by non-operators. - -load() function ---------------- -The load() function is used by ScriptCraft at startup to load all of -the javascript modules and data. You normally wouldn't need to call -this function directly. If you put a javascript file anywhere in the -craftbukkit/js-plugins directory tree it will be loaded automatically -when craftbukkit starts up. The exception is files whose name begins -with an underscore `_` character. These files will not be -automatically loaded at startup as they are assumed to be files -managed / loaded by plugins. - -Parameters ----------- - - * filenames - An array of file names or a single file name. - * warnOnFileNotFound (optional - default: false) - warn if the file was not found. - -Return ------- -load() will return the result of the last statement evaluated in the file. - -Example -------- - - load(__folder + "myFile.js"); // loads a javascript file and evaluates it. - - var myData = load("myData.json"); // loads a javascript file and evaluates it - eval'd contents are returned. - -myData.json contents... - - __data = {players:{ - walterh:{ - h: ["jsp home {1}"], - sunny:["time set 0", - "weather clear"] - } - } - } - -save() function ---------------- -The save() function saves an in-memory javascript object to a -specified file. Under the hood, save() uses JSON (specifically -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 `save()` function can later be -restored using the `load()` function. - -Parameters ----------- - - * objectToSave : The object you want to save. - * filename : The name of the file you want to save it to. - -Example -------- - - var myObject = { name: 'John Doe', - aliases: ['John Ray', 'John Mee'], - date_of_birth: '1982/01/31' }; - save(myObject, 'johndoe.json'); - -johndoe.json contents... - - var __data = { "name": "John Doe", - "aliases": ["John Ray", "John Mee"], - "date_of_birth": "1982/01/31" }; - -plugin() function ------------------ -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 -managed by Scriptcraft is `state` - this special member will be -automatically saved at shutdown and loaded at startup by -ScriptCraft. This makes it easier to write plugins which need to -persist data. - -Parameters ----------- - - * 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. - -Example -------- -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]: http://walterhiggins.net/blog/ScriptCraft-1-Month-later - -command() function ------------------- -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. - -Parameters ----------- - - * 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. - * 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. - -Example -------- -See chat/colors.js or alias/alias.js or homes/homes.js for examples of how to use the `command()` function. - -ready() function ----------------- -The `ready()` function provides a way for plugins to do additional -setup once all of the other plugins/modules have loaded. For example, -event listener registration can only be done after the -events/events.js module has loaded. A plugin author could load the -file explicilty like this... - - load(__folder + "../events/events.js"); - - // event listener registration goes here - -... or better still, just do event regristration using the `ready()` -handler knowing that by the time the `ready()` callback is invoked, -all of the scriptcraft modules have been loaded... - - ready(function(){ - // event listener registration goes here - // code that depends on other plugins/modules also goes here - }); - -The execution of the function object passed to the `ready()` function -is *deferred* until all of the plugins/modules have loaded. That way -you are guaranteed that when the function is invoked, all of the -plugins/modules have been loaded and evaluated and are ready to use. -setTimeout() function ---------------------- - -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. - -If Node.js supports setTimeout() then it's probably good for ScriptCraft to support it too. - -[btdoc]: http://jd.bukkit.org/beta/apidocs/org/bukkit/scheduler/BukkitTask.html - -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(). - -Core Module - Special Variables -=============================== -There are a couple of special javascript variables available in ScriptCraft... - - * __folder - The current working directory - this variable is only to be used within the main body of a .js file. - * __plugin - The ScriptCraft JavaPlugin object. - * server - The Minecraft Server object. - * self - the current player. (Note - this value should not be used in multi-threaded scripts - it's not thread-safe) - -refresh() function ------------------- -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 - -Drone Module -============ -The Drone is a convenience class for building. It can be used for... - - 1. Building - 2. Copying and Pasting - -It uses a fluent interface which means all of the Drone's methods return `this` and can -be chained together like so... - - var theDrone = new Drone(); - theDrone.up().left().box(blocks.oak).down().fwd(3).cylinder0(blocks.lava,8); - -TLDNR; (Just read this if you're impatient) -=========================================== -At the in-game command prompt type... - - /js box(blocks.oak) - -... creates a single wooden block at the cross-hairs or player location - - /js box(5).right(2).box('35:15',4,9,1) - -... creates a single wooden block and a 2001 black obelisk that is 4 -wide x 9 tall x 1 long in size. If you want to see what else -ScriptCraft's Drone can do, read on... - -Constructing a Drone Object -=========================== - -Drones can be created in any of the following ways... - - 1. Calling any one of the methods listed below will return a Drone object. For example... - - var d = box(blocks.oak) - - ... creates a 1x1x1 wooden block at the cross-hairs or player's location and returns a Drone - object. This might look odd (if you're familiar with Java's Object-dot-method syntax) but all - of the Drone class's methods are also global functions that return new Drone objects. - This is short-hand for creating drones and is useful for playing around with Drones at the in-game - command prompt. It's shorter than typing ... - - var d = new Drone().box(5) - - ... All of the Drone's methods return `this` (self) so you can chain operations together like this... - - var d = box(5).up().box(5,3,1,3).down().fwd(2).box(5).turn().fwd(2).box(5).turn().fwd(2).box(5) - - 2. Using the following form... - - d = new Drone() - - ...will create a new Drone. If the cross-hairs are pointing at a - block at the time then, that block's location becomes the drone's - starting point. If the cross-hairs are _not_ pointing at a block, - then the drone's starting location will be 2 blocks directly in - front of the player. TIP: Building always happens right and front - of the drone's position... - - Plan View: - - ^ - | - | - D----> - - For convenience you can use a _corner stone_ to begin building. - The corner stone should be located just above ground level. If - the cross-hair is point at or into ground level when you create a - new Drone(), then building begins at that point. You can get - around this by pointing at a 'corner stone' just above ground - level or alternatively use the following statement... - - d = new Drone().up(); - - ... which will move the drone up one block as soon as it's created. - - ![corner stone](img/cornerstone1.png) - - 3. Or by using the following form... - - d = new Drone(x,y,z,direction,world); - - This will create a new Drone at the location you specified using - x, y, z In minecraft, the X axis runs west to east and the Z axis runs - north to south. The direction parameter says what direction you want - the drone to face: 0 = east, 1 = south, 2 = west, 3 = north. If the - direction parameter is omitted, the player's direction is used - instead. - - Both the `direction` and `world` parameters are optional. - - 4. Create a new Drone based on a Bukkit Location object... - - d = new Drone(location); - - This is useful when you want to create a drone at a given - `org.bukkit.Location` . The `Location` class is used throughout - the bukkit API. For example, if you want to create a drone when a - block is broken at the block's location you would do so like - this... - - events.on('block.BlockBreakEvent',function(listener,event){ - var location = event.block.location; - var drone = new Drone(location); - // do more stuff with the drone here... - }); - -Parameters ----------- - * location (optional) : *NB* If an `org.bukkit.Location` object is provided as a parameter, then it should be the only parameter. - * x (optional) : The x coordinate of the Drone - * y (optional) : The y coordinate of the Drone - * z (optional) : The z coordinate of the Drone - * direction (optional) : The direction in which the Drone is - facing. Possible values are 0 (east), 1 (south), 2 (west) or 3 (north) - * world (optional) : The world in which the drone is created. - -Drone.box() method -================== -the box() method is a convenience method for building things. (For the more performance-oriented method - see cuboid) - -parameters ----------- - * b - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. - Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` - * w (optional - default 1) - the width of the structure - * h (optional - default 1) - the height of the structure - * d (optional - default 1) - the depth of the structure - NB this is - not how deep underground the structure lies - this is how far - away (depth of field) from the drone the structure will extend. - -Example -------- -To create a black structure 4 blocks wide, 9 blocks tall and 1 block long... - - box(blocks.wool.black, 4, 9, 1); - -... or the following code does the same but creates a variable that can be used for further methods... - - var drone = new Drone(); - drone.box(blocks.wool.black, 4, 9, 1); - -![box example 1](img/boxex1.png) - -Drone.box0() method -=================== -Another convenience method - this one creates 4 walls with no floor or ceiling. - -Parameters ----------- - * block - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. - Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` - * width (optional - default 1) - the width of the structure - * height (optional - default 1) - the height of the structure - * length (optional - default 1) - the length of the structure - how far - away (depth of field) from the drone the structure will extend. - -Example -------- -To create a stone building with the insided hollowed out 7 wide by 3 tall by 6 long... - - box0( blocks.stone, 7, 3, 6); - -![example box0](img/box0ex1.png) - -Drone.boxa() method -=================== -Construct a cuboid using an array of blocks. As the drone moves first along the width axis, -then the height (y axis) then the length, each block is picked from the array and placed. - -Parameters ----------- - * blocks - An array of blocks - each block in the array will be placed in turn. - * width - * height - * length - -Example -------- -Construct a rainbow-colored road 100 blocks long... - - var rainbowColors = [blocks.wool.red, blocks.wool.orange, blocks.wool.yellow, blocks.wool.lime, - blocks.wool.lightblue, blocks.wool.blue, blocks.wool.purple]; - - boxa(rainbowColors,7,1,30); - -![boxa example](img/boxaex1.png) - -Drone Movement -============== -Drones can move freely in minecraft's 3-D world. You control the -Drone's movement using any of the following methods.. - - * up() - * down() - * left() - * right() - * fwd() - * back() - * turn() - -... Each of these methods takes a single optional parameter -`numBlocks` - the number of blocks to move in the given direction. If -no parameter is given, the default is 1. - -to change direction use the `turn()` method which also takes a single -optional parameter (numTurns) - the number of 90 degree turns to make. -Turns are always clock-wise. If the drone is facing north, then -drone.turn() will make the turn face east. If the drone is facing east -then drone.turn(2) will make the drone turn twice so that it is facing -west. - -Drone Positional Info -===================== - - * getLocation() - Returns a Bukkit Location object for the drone - -Drone Markers -============= -Markers are useful when your Drone has to do a lot of work. You can -set a check-point and return to the check-point using the move() -method. If your drone is about to undertake a lot of work - -e.g. building a road, skyscraper or forest you should set a -check-point before doing so if you want your drone to return to its -current location. - -A 'start' checkpoint is automatically created when the Drone is first created. - -Markers are created and returned to using the followng two methods... - - * chkpt - Saves the drone's current location so it can be returned to later. - * move - moves the drone to a saved location. Alternatively you can provide an - org.bukkit.Location object or x,y,z and direction parameters. - -Parameters ----------- - * name - the name of the checkpoint to save or return to. - -Example -------- - - drone.chkpt('town-square'); - // - // the drone can now go off on a long excursion - // - for (i = 0; i< 100; i++){ - drone.fwd(12).box(6); - } - // - // return to the point before the excursion - // - drone.move('town-square'); - -Drone.prism() method -==================== -Creates a prism. This is useful for roofs on houses. - -Parameters ----------- - - * block - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. - Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` - * width - the width of the prism - * length - the length of the prism (will be 2 time its height) - -Example -------- - - prism(blocks.oak,3,12); - -![prism example](img/prismex1.png) - -Drone.prism0() method -===================== -A variation on `prism` which hollows out the inside of the prism. It uses the same parameters as `prism`. - -Drone.cylinder() method -======================= -A convenience method for building cylinders. Building begins radius blocks to the right and forward. - -Parameters ----------- - - * block - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. - Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` - * radius - * height - -Example -------- -To create a cylinder of Iron 7 blocks in radius and 1 block high... - - cylinder(blocks.iron, 7 , 1); - -![cylinder example](img/cylinderex1.png) - -Drone.cylinder0() method -======================== -A version of cylinder that hollows out the middle. - -Example -------- -To create a hollow cylinder of Iron 7 blocks in radius and 1 block high... - - cylinder0(blocks.iron, 7, 1); - -![cylinder0 example](img/cylinder0ex1.png) - -Drone.arc() method -================== -The arc() method can be used to create 1 or more 90 degree arcs in the horizontal or vertical planes. -This method is called by cylinder() and cylinder0() and the sphere() and sphere0() methods. - -Parameters ----------- -arc() takes a single parameter - an object with the following named properties... - - * radius - The radius of the arc. - * blockType - The type of block to use - this is the block Id only (no meta). See [Data Values][dv]. - * meta - The metadata value. See [Data Values][dv]. - * orientation (default: 'horizontal') - the orientation of the arc - can be 'vertical' or 'horizontal'. - * stack (default: 1) - the height or length of the arc (depending on - the orientation - if orientation is horizontal then this parameter - refers to the height, if vertical then it refers to the length). - * strokeWidth (default: 1) - the width of the stroke (how many - blocks) - if drawing nested arcs it's usually a good idea to set - strokeWidth to at least 2 so that there are no gaps between each - arc. The arc method uses a [bresenham algorithm][bres] to plot - points along the circumference. - * fill - If true (or present) then the arc will be filled in. - * quadrants (default: - `{topleft:true,topright:true,bottomleft:true,bottomright:true}` - An - object with 4 properties indicating which of the 4 quadrants of a - circle to draw. If the quadrants property is absent then all 4 - quadrants are drawn. - -Examples --------- -To draw a 1/4 circle (top right quadrant only) with a radius of 10 and stroke width of 2 blocks ... - - arc({blockType: blocks.iron, - meta: 0, - radius: 10, - strokeWidth: 2, - quadrants: { topright: true }, - orientation: 'vertical', - stack: 1, - fill: false - }); - -![arc example 1](img/arcex1.png) - -[bres]: http://en.wikipedia.org/wiki/Midpoint_circle_algorithm -[dv]: http://www.minecraftwiki.net/wiki/Data_values -Drone.door() method -=================== -create a door - if a parameter is supplied an Iron door is created otherwise a wooden door is created. - -Parameters ----------- - * doorType (optional - default wood) - If a parameter is provided then the door is Iron. - -Example -------- -To create a wooden door at the crosshairs/drone's location... - - var drone = new Drone(); - drone.door(); - -To create an iron door... - - drone.door( blocks.door_iron ); - -![iron door](img/doorex1.png) - -Drone.door2() method -==================== -Create double doors (left and right side) - -Parameters ----------- - * doorType (optional - default wood) - If a parameter is provided then the door is Iron. - -Example -------- -To create double-doors at the cross-hairs/drone's location... - - drone.door2(); - -![double doors](img/door2ex1.png) - -Drone.sign() method -=================== -Signs must use block 63 (stand-alone signs) or 68 (signs on walls) - -Parameters ----------- - * message - can be a string or an array of strings. - * block - can be 63 or 68 - -Example -------- -To create a free-standing sign... - - drone.sign(["Hello","World"],63); - -![ground sign](img/signex1.png) - -... to create a wall mounted sign... - - drone.sign(["Welcome","to","Scriptopia"], 68); - -![wall sign](img/signex2.png) - -Drone Trees methods -=================== - - * oak() - * spruce() - * birch() - * jungle() - -Example -------- -To create 4 trees in a row, point the cross-hairs at the ground then type `/js ` and ... - - up().oak().right(8).spruce().right(8).birch().right(8).jungle(); - -Trees won't always generate unless the conditions are right. You -should use the tree methods when the drone is directly above the -ground. Trees will usually grow if the drone's current location is -occupied by Air and is directly above an area of grass (That is why -the `up()` method is called first). - -![tree example](img/treeex1.png) - - -None of the tree methods require parameters. Tree methods will only be successful -if the tree is placed on grass in a setting where trees can grow. -Drone.garden() method -===================== -places random flowers and long grass (similar to the effect of placing bonemeal on grass) - -Parameters ----------- - - * width - the width of the garden - * length - how far from the drone the garden extends - -Example -------- -To create a garden 10 blocks wide by 5 blocks long... - - garden(10,5); - -![garden example](img/gardenex1.png) - -Drone.rand() method -=================== -rand takes either an array (if each blockid has the same chance of occurring) -or an object where each property is a blockid and the value is it's weight (an integer) - -Example -------- -place random blocks stone, mossy stone and cracked stone (each block has the same chance of being picked) - - rand( [blocks.brick.stone, blocks.brick.mossy, blocks.brick.cracked ],w,d,h) - -to place random blocks stone has a 50% chance of being picked, - - rand({blocks.brick.stone: 5, blocks.brick.mossy: 3, blocks.brick.cracked: 2},w,d,h) - -regular stone has a 50% chance, mossy stone has a 30% chance and cracked stone has just a 20% chance of being picked. - -Copy & Paste using Drone -======================== -A drone can be used to copy and paste areas of the game world. - -Drone.copy() method -=================== -Copies an area so it can be pasted elsewhere. The name can be used for -pasting the copied area elsewhere... - -Parameters ----------- - - * name - the name to be given to the copied area (used by `paste`) - * width - the width of the area to copy - * height - the height of the area to copy - * length - the length of the area (extending away from the drone) to copy - -Example -------- - - drone.copy('somethingCool',10,5,10).right(12).paste('somethingCool'); - -Drone.paste() method -==================== -Pastes a copied area to the current location. - -Example -------- -To copy a 10x5x10 area (using the drone's coordinates as the starting -point) into memory. the copied area can be referenced using the name -'somethingCool'. The drone moves 12 blocks right then pastes the copy. - - drone.copy('somethingCool',10,5,10) - .right(12) - .paste('somethingCool'); - -Chaining -======== - -All of the Drone methods return a Drone object, which means methods -can be 'chained' together so instead of writing this... - - drone = new Drone(); - drone.fwd(3); - drone.left(2); - drone.box(2); // create a grass block - drone.up(); - drone.box(2); // create another grass block - drone.down(); - -...you could simply write ... - - var drone = new Drone().fwd(3).left(2).box(2).up().box(2).down(); - -... since each Drone method is also a global function that constructs -a drone if none is supplied, you can shorten even further to just... - - fwd(3).left(2).box(2).up().box(2).down() - -The Drone object uses a [Fluent Interface][fl] to make ScriptCraft -scripts more concise and easier to write and read. Minecraft's -in-game command prompt is limited to about 80 characters so chaining -drone commands together means more can be done before hitting the -command prompt limit. For complex building you should save your -commands in a new script file and load it using /js load() - -[fl]: http://en.wikipedia.org/wiki/Fluent_interface - -Drone Properties -================ - - * x - The Drone's position along the west-east axis (x increases as you move east) - * y - The Drone's position along the vertical axis (y increses as you move up) - * z - The Drone's position along the north-south axis (z increases as you move south) - * dir - The Drone's direction 0 is east, 1 is south , 2 is west and 3 is north. - -Extending Drone -=============== -The Drone object can be easily extended - new buidling recipes/blue-prints can be added and can -become part of a Drone's chain using the *static* method `Drone.extend`. - -Drone.extend() static method -============================ -Use this method to add new methods (which also become chainable global functions) to the Drone object. - -Parameters ----------- - * name - The name of the new method e.g. 'pyramid' - * function - The method body. - -Example -------- - - // submitted by [edonaldson][edonaldson] - Drone.extend('pyramid', function(block,height){ - this.chkpt('pyramid'); - for (var i = height; i > 0; i -= 2) { - this.box(block, i, 1, i).up().right().fwd(); - } - return this.move('pyramid'); - }); - -Once the method is defined (it can be defined in a new pyramid.js file) it can be used like so... - - var d = new Drone(); - d.pyramid(blocks.brick.stone, 12); - -... or simply ... - - pyramid(blocks.brick.stone, 12); - -[edonaldson]: https://github.com/edonaldson - -Drone Constants -=============== - -Drone.PLAYER_STAIRS_FACING --------------------------- -An array which can be used when constructing stairs facing in the Drone's direction... - - var d = new Drone(); - d.box(blocks.stairs.oak + ':' + Drone.PLAYER_STAIRS_FACING[d.dir]); - -... will construct a single oak stair block facing the drone. - -Drone.PLAYER_SIGN_FACING ------------------------- -An array which can be used when placing signs so they face in a given direction. -This is used internally by the Drone.sign() method. It should also be used for placing -any of the following blocks... - - * chest - * ladder - * furnace - * dispenser - -To place a chest facing the Drone ... - - drone.box( blocks.chest + ':' + Drone.PLAYER_SIGN_FACING[drone.dir]); - -Drone.PLAYER_TORCH_FACING -------------------------- -Used when placing torches so that they face towards the drone. - - drone.box( blocks.torch + ':' + Drone.PLAYER_TORCH_FACING[drone.dir]); - -Drone.times() Method -==================== -The times() method makes building multiple copies of buildings easy. It's possible to create rows or grids of buildings without resorting to `for` or `while` loops. - -Parameters ----------- - * numTimes (optional - default 2) : The number of times you want to repeat the preceding statements. - -Example -------- -Say you want to do the same thing over and over. You have a couple of options... - - * You can use a for loop... - - d = new Drone(); for (var i =0;i < 4; i++){ d.cottage().right(8); } - -While this will fit on the in-game prompt, it's awkward. You need to -declare a new Drone object first, then write a for loop to create the -4 cottages. It's also error prone, even the `for` loop is too much -syntax for what should really be simple. - - * You can use a while loop... - - d = new Drone(); var i=4; while (i--){ d.cottage().right(8); } - -... which is slightly shorter but still too much syntax. Each of the -above statements is fine for creating a 1-dimensional array of -structures. But what if you want to create a 2-dimensional or -3-dimensional array of structures? Enter the `times()` method. - -The `times()` method lets you repeat commands in a chain any number of -times. So to create 4 cottages in a row you would use the following -statement... - - cottage().right(8).times(4); - -...which will build a cottage, then move right 8 blocks, then do it -again 4 times over so that at the end you will have 4 cottages in a -row. What's more the `times()` method can be called more than once in -a chain. So if you wanted to create a *grid* of 20 houses ( 4 x 5 ), -you would do so using the following statement... - - cottage().right(8).times(4).fwd(8).left(32).times(5); - -... breaking it down... - - 1. The first 3 calls in the chain ( `cottage()`, `right(8)`, - `times(4)` ) build a single row of 4 cottages. - - 2. The last 3 calls in the chain ( `fwd(8)`, `left(32)`, `times(5)` ) - move the drone forward 8 then left 32 blocks (4 x 8) to return to - the original x coordinate, then everything in the chain is - repeated again 5 times so that in the end, we have a grid of 20 - cottages, 4 x 5. Normally this would require a nested loop but - the `times()` method does away with the need for loops when - repeating builds. - -Another example: This statement creates a row of trees 2 by 3 ... - - oak().right(10).times(2).left(20).fwd(10).times(3) - -... You can see the results below. - -![times example 1](img/times-trees.png) - -Drone.blocktype() method -======================== -Creates the text out of blocks. Useful for large-scale in-game signs. - -Parameters ----------- - - * message - The message to create - (use `\n` for newlines) - * foregroundBlock (default: black wool) - The block to use for the foreground - * backgroundBlock (default: none) - The block to use for the background - -Example -------- -To create a 2-line high message using glowstone... - - blocktype("Hello\nWorld",blocks.glowstone); - -![blocktype example][imgbt1] - -[imgbt1]: img/blocktype1.png - -Blocks Module -============= -You hate having to lookup [Data Values][dv] when you use ScriptCraft's Drone() functions. So do I. -So I created this blocks object which is a helper object for use in construction. - -Examples --------- - - box( blocks.oak ); // creates a single oak wood block - box( blocks.sand, 3, 2, 1 ); // creates a block of sand 3 wide x 2 high x 1 long - box( blocks.wool.green, 2 ); // creates a block of green wool 2 blocks wide - -Color aliased properties that were a direct descendant of the blocks object are no longer used to avoid confusion with carpet and stained clay blocks. In addition, there's a convenience array `blocks.rainbow` which is an array of the 7 colors of the rainbow (or closest approximations). - -Drone.sphere() method -===================== -Creates a sphere. - -Parameters ----------- - - * block - The block the sphere will be made of. - * radius - The radius of the sphere. - -Example -------- -To create a sphere of Iron with a radius of 10 blocks... - - sphere( blocks.iron, 10); - -![sphere example](img/sphereex1.png) - -Spheres are time-consuming to make. You *can* make large spheres (250 radius) but expect the -server to be very busy for a couple of minutes while doing so. - -Drone.sphere0() method -====================== -Creates an empty sphere. - -Parameters ----------- - - * block - The block the sphere will be made of. - * radius - The radius of the sphere. - -Example -------- -To create a sphere of Iron with a radius of 10 blocks... - - sphere0( blocks.iron, 10); - -Spheres are time-consuming to make. You *can* make large spheres (250 radius) but expect the -server to be very busy for a couple of minutes while doing so. - -Drone.hemisphere() method -========================= -Creates a hemisphere. Hemispheres can be either north or south. - -Parameters ----------- - - * block - the block the hemisphere will be made of. - * radius - the radius of the hemisphere - * northSouth - whether the hemisphere is 'north' or 'south' - -Example -------- -To create a wood 'north' hemisphere with a radius of 7 blocks... - - hemisphere(blocks.oak, 7, 'north'); - -![hemisphere example](img/hemisphereex1.png) - -Drone.hemisphere0() method -========================= -Creates a hollow hemisphere. Hemispheres can be either north or south. - -Parameters ----------- - - * block - the block the hemisphere will be made of. - * radius - the radius of the hemisphere - * northSouth - whether the hemisphere is 'north' or 'south' - -Example -------- -To create a glass 'north' hemisphere with a radius of 20 blocks... - - hemisphere0(blocks.glass, 20, 'north'); - -![hemisphere example](img/hemisphereex2.png) - -Drone.rainbow() method -====================== -Creates a Rainbow. - -Parameters ----------- - - * radius (optional - default:18) - The radius of the rainbow - -Example -------- - - var d = new Drone(); - d.rainbow(30); - -![rainbow example](img/rainbowex1.png) - -Drone.spiral_stairs() method -============================ -Constructs a spiral staircase with slabs at each corner. - -Parameters ----------- - - * stairBlock - The block to use for stairs, should be one of the following... - - 'oak' - - 'spruce' - - 'birch' - - 'jungle' - - 'cobblestone' - - 'brick' - - 'stone' - - 'nether' - - 'sandstone' - - 'quartz' - * flights - The number of flights of stairs to build. - -![Spiral Staircase](img/spiralstair1.png) - -Example -------- -To construct a spiral staircase 5 floors high made of oak... - - spiral_stairs('oak', 5); - -Classroom Module -================ -The `classroom` object contains a couple of utility functions for use -in a classroom setting. The goal of these functions is to make it -easier for tutors to facilitate ScriptCraft for use by students in a -classroom environment. Although granting ScriptCraft access to -students on a shared server is potentially risky (Students can -potentially abuse it), it is slighlty less risky than granting -operator privileges to each student. (Enterprising students will -quickly realise how to grant themselves and others operator privileges -once they have access to ScriptCraft). - -The goal of this module is not so much to enforce restrictions -(security or otherwise) but to make it easier for tutors to setup a shared server -so students can learn Javascript. - -classroom.allowScripting() function -=================================== -Allow or disallow anyone who connects to the server (or is already -connected) to use ScriptCraft. This function is preferable to granting 'ops' privileges -to every student in a Minecraft classroom environment. - -Parameters ----------- - - * canScript : true or false - -Example -------- -To allow all players (and any players who connect to the server) to -use the `js` and `jsp` commands... - - /js classroom.allowScripting(true) - -To disallow scripting (and prevent players who join the server from using the commands)... - - /js classroom.allowScripting(false) - -Only ops users can run the classroom.allowScripting() function - this is so that students -don't try to bar themselves and each other from scripting. - -events Module -============= -The Events module provides a thin wrapper around Bukkit's -Event-handling API. Bukkit's Events API makes use of Java Annotations -which are not available in Javascript, so this module provides a -simple way to listen to minecraft events in javascript. - -events.on() static method -========================= -This method is used to register event listeners. - -Parameters ----------- - - * eventName - A string or java class. If a string is supplied it must - be part of the Bukkit event class name. See [Bukkit API][buk] for - details of the many bukkit event types. When a string is supplied - there is no need to provide the full class name - you should omit - the 'org.bukkit.event' prefix. e.g. if the string - "block.BlockBreakEvent" is supplied then it's converted to the - org.bukkit.event.block.BlockBreakEvent class . - - If a java class is provided (say in the case where you've defined - your own custom event) then provide the full class name (without - enclosing quotes). - - * callback - A function which will be called whenever the event - fires. The callback should take 2 parameters, listener (the Bukkit - registered listener for this callback) and event (the event fired). - - * priority (optional - default: "HIGHEST") - The priority the - listener/callback takes over other listeners to the same - event. Possible values are "HIGH", "HIGHEST", "LOW", "LOWEST", - "NORMAL", "MONITOR". For an explanation of what the different - priorities mean refer to bukkit's [Event API Reference][buk2]. - -Returns -------- -An org.bukkit.plugin.RegisteredListener object which can be used to -unregister the listener. This same object is passed to the callback -function each time the event is fired. - -Example: ------- -The following code will print a message on screen every time a block is broken in the game - - events.on("block.BlockBreakEvent", function(listener, evt){ - echo (evt.player.name + " broke a block!"); - }); - -To handle an event only once and unregister from further events... - - events.on("block.BlockBreakEvent", function(listener, evt){ - print (evt.player.name + " broke a block!"); - evt.handlers.unregister(listener); - }); - -To unregister a listener *outside* of the listener function... - - var myBlockBreakListener = events.on("block.BlockBreakEvent",function(l,e){ ... }); - ... - var handlers = org.bukkit.event.block.BlockBreakEvent.getHandlerList(); - handlers.unregister(myBlockBreakListener); - -[buk2]: http://wiki.bukkit.org/Event_API_Reference -[buk]: http://jd.bukkit.org/dev/apidocs/index.html?org/bukkit/event/Event.html - -Fireworks Module -================ -The fireworks module makes it easy to create fireworks using -ScriptCraft. The module has a single function `firework` which takes -a `org.bukkit.Location` as its 1 and only parameter. - -Examples --------- -The module also extends the `Drone` object adding a `firework` method -so that fireworks can be created as a part of a Drone chain. For -Example.... - - /js firework() - -... creates a single firework, while .... - - /js firework.fwd(3).times(5) - -... creates 5 fireworks in a row. Fireworks have also been added as a -possible option for the `arrow` module. To have a firework launch -where an arrow strikes... - - /js arrows.firework() - -To call the fireworks.firework() function directly, you must provide a -location. For example... - - /js fireworks.firework(self.location); - -![firework example](img/firework.png) - -http.request() function -==================== -The http.request() function will fetch a web address asynchronously (on a -separate thread)and pass the URL's response to a callback function -which will be executed synchronously (on the main thread). In this -way, http.request() can be used to fetch web content without blocking the -main thread of execution. - -Parameters ----------- - - * request: The request details either a plain URL e.g. "http://scriptcraft.js/sample.json" or an object with the following properties... - - - url: The URL of the request. - - method: Should be one of the standard HTTP methods, GET, POST, PUT, DELETE (defaults to GET). - - params: A Javascript object with name-value pairs. This is for supplying parameters to the server. - - * callback: The function to be called when the Web request has completed. This function takes the following parameters... - - responseCode: The numeric response code from the server. If the server did not respond with 200 OK then the response parameter will be undefined. - - response: A string (if the response is of type text) or object containing the HTTP response body. - -Example -------- -The following example illustrates how to use http.request to make a request to a JSON web service and evaluate its response... - - var jsResponse; - http.request("http://scriptcraftjs.org/sample.json",function(responseCode, responseBody){ - jsResponse = eval("(" + responseBody + ")"); - }); - -... The following example illustrates a more complex use-case POSTing parameters to a CGI process on a server... - - http.request({ url: "http://pixenate.com/pixenate/pxn8.pl", - method: "POST", - params: {script: "[]"} - }, function( responseCode, responseBody){ - var jsObj = eval("(" + responseBody + ")"); - }); - -Utilities Module -================ -Miscellaneous utility functions and classes to help with programming. - - * locationToString(Location) - returns a bukkit Location object in string form. - - * getPlayerObject(playerName) - returns the Player object for a named - player or `self` if no name is provided. - -utils.foreach() function -======================== -The utils.foreach() function is a utility function for iterating over -an array of objects (or a java.util.Collection of objects) and processing each object in turn. Where -utils.foreach() differs from other similar functions found in -javascript libraries, is that utils.foreach can process the array -immediately or can process it *nicely* by processing one item at a -time then delaying processing of the next item for a given number of -server ticks (there are 20 ticks per second on the minecraft main -thread). This method relies on Bukkit's [org.bukkit.scheduler][sched] -package for scheduling processing of arrays. - -[sched]: http://jd.bukkit.org/beta/apidocs/org/bukkit/scheduler/package-summary.html - -Parameters ----------- - - * array : The array to be processed - It can be a javascript array, a java array or java.util.Collection - * callback : The function to be called to process each item in the - array. The callback function should have the following signature - `callback(item, index, object, array)`. That is the callback will - be called with the following parameters.... - - - item : The item in the array - - index : The index at which the item can be found in the array. - - object : Additional (optional) information passed into the foreach method. - - array : The entire array. - - * object (optional) : An object which may be used by the callback. - * delay (optional, numeric) : If a delay is specified (in ticks - 20 - ticks = 1 second), then the processing will be scheduled so that - each item will be processed in turn with a delay between the completion of each - item and the start of the next. This is recommended for big builds (say 200 x 200 x 200 - blocks) or any CPU-intensive process. - * onDone (optional, function) : A function to be executed when all processing - is complete. This parameter is only used when the processing is delayed. (It's optional even if a - delay parameter is supplied). - -If called with a delay parameter then foreach() will return -immediately after processing just the first item in the array (all -subsequent items are processed later). If your code relies on the -completion of the array processing, then provide an `onDone` parameter -and put the code there. - -Example -------- -The following example illustrates how to use foreach for immediate processing of an array... - - var players = ["moe", "larry", "curly"]; - utils.foreach (players, function(item){ - server.getPlayer(item).sendMessage("Hi " + item); - }); - -... The `utils.foreach()` function can work with Arrays or any Java-style collection. This is important -because many objects in the Bukkit API use Java-style collections... - - utils.foreach( server.onlinePlayers, function(player){ - player.chat("Hello!"); - }); - -... the above code sends a "Hello!" to every online player. - -The following example is a more complex use case - The need to build an enormous structure -without hogging CPU usage... - - // build a structure 200 wide x 200 tall x 200 long - // (That's 8 Million Blocks - enough to tax any machine!) - - var a = []; - a.length = 200; - var drone = new Drone(); - var processItem = function(item, index, object, array){ - // build a box 200 wide by 200 long then move up - drone.box(blocks.wood, 200, 1, 200).up(); - }; - // by the time the job's done 'self' might be someone else - // assume this code is within a function/closure - var player = self; - var onDone = function(){ - player.sendMessage("Job Done!"); - }; - utils.foreach (a, processItem, null, 10, onDone); - -utils.nicely() function -======================= -The utils.nicely() function is for performing processing using the -[org.bukkit.scheduler][sched] package/API. utils.nicely() lets you -process with a specified delay between the completion of each `next()` -function and the start of the next `next()` function. -`utils.nicely()` is a recursive function - that is - it calls itself -(schedules itself actually) repeatedly until `hasNext` returns false. - -Parameters ----------- - - * next : A function which will be called if processing is to be done. - * hasNext : A function which is called to determine if the `next` - callback should be invoked. This should return a boolean value - - true if the `next` function should be called (processing is not - complete), false otherwise. - * onDone : A function which is to be called when all processing is complete (hasNext returned false). - * delay : The delay (in server ticks - 20 per second) between each call. - -Example -------- -See the source code to utils.foreach for an example of how utils.nicely is used. - -utils.at() function -=================== -The utils.at() function will perform a given task at a given time every -(minecraft) day. - -Parameters ----------- - - * time24hr : The time in 24hr form - e.g. 9:30 in the morning is "09:30" while - 9:30 pm is "21:30", midnight is "00:00" and midday is "12:00" - * callback : A javascript function which will be invoked at the given time. - * world : (optional) Each world has its own clock. If no world is specified, the server's first world is used. - -Example -------- - -To warn players when night is approaching... - - utils.at( "19:00", function() { - - utils.foreach( server.onlinePlayers, function(player){ - player.chat("The night is dark and full of terrors!"); - }); - - }, self.world); - -String class extensions ------------------------ -The following chat-formatting methods are added to the javascript String class.. - - * black() - * darkblue() - * blue() - * darkgreen() - * darkaqua() - * darkred() - * purple() - * gold() - * gray() - * darkgray() - * indigo() - * brightgreen() - * green() - * aqua() - * red() - * pink() - * yellow() - * white() - * bold() - * random() - * strike() - * underline() - * italic() - * reset() - -Example -------- - - var boldGoldText = "Hello World".bold().gold(); - self.sendMessage(boldGoldText); - -

Hello World

- From d889b301b3302c4afcf22329e47a11dab658ea81 Mon Sep 17 00:00:00 2001 From: Walter Higgins Date: Thu, 19 Dec 2013 10:12:38 +0000 Subject: [PATCH 014/456] Create readme.md --- docs/readme.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 docs/readme.md diff --git a/docs/readme.md b/docs/readme.md new file mode 100644 index 0000000..e7d7516 --- /dev/null +++ b/docs/readme.md @@ -0,0 +1,18 @@ +# Additional Resources + +CoderDojo Athenry have some [excellent tutorials][cda] for younger programmers who have used [Scratch][scr] and are interested in Modding Minecraft using Javascript. +In particular, they have an excellent [Scratch - to - Javascript][sj] tutorial which explains Scratch programs and how to do the same thing in Javascript. + +[scr]: http://scratch.mit.edu/ +[cda]: http://cdathenry.wordpress.com/category/modderdojo/ +[sj]: http://cdathenry.wordpress.com/2013/10/12/modderdojo-week-2-moving-from-scratch-to-javascript/ + +I highly recommend the series of tutorials provided by CoderDojo Athenry. + +Developer Chris Cacciatore has created some interesting tools using Scriptcraft... + + * [A wolf-bot][wb] + * [L-Systems (Large-scale fractal structures in Minecraft)][ls] + +[wb]: https://github.com/cacciatc/wolfbot +[ls]: https://github.com/cacciatc/scriptcraft-lsystems From 06a88a10730a3fa8ce1fcda0ba52ebf52d12c788 Mon Sep 17 00:00:00 2001 From: Walter Higgins Date: Thu, 19 Dec 2013 10:37:28 +0000 Subject: [PATCH 015/456] Update readme.md --- docs/readme.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/readme.md b/docs/readme.md index e7d7516..9061851 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -1,3 +1,10 @@ +# Let's begin... + +I created ScriptCraft to make it easier for children (and anyone curious about programming) to create their own Minecraft Mods. ScriptCraft makes it easier for new programmers to create Minecraft mods. Mods are written using the Javascript programming language and once the ScriptCraft mod is installed, you can add your own new Mods by adding Javascript (.js) files in a directory. + + * If you're new to programming and want to start modding Minecraft, then [Start Here][ypgpm]. + * If you've already used [Scratch][scr], have attended a few [CoderDojo][cd] sessions, or have already dabbled with Javascript, then [Start Here][cda]. + # Additional Resources CoderDojo Athenry have some [excellent tutorials][cda] for younger programmers who have used [Scratch][scr] and are interested in Modding Minecraft using Javascript. @@ -16,3 +23,4 @@ Developer Chris Cacciatore has created some interesting tools using Scriptcraft. [wb]: https://github.com/cacciatc/wolfbot [ls]: https://github.com/cacciatc/scriptcraft-lsystems +[ypgpm]: YoungPersonsGuideToProgrammingMinecraft.md From 508d6f0622bc23ec7ae016c02715dc8dc4ceb1bb Mon Sep 17 00:00:00 2001 From: Walter Higgins Date: Thu, 19 Dec 2013 10:43:13 +0000 Subject: [PATCH 016/456] Update readme.md --- docs/readme.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/readme.md b/docs/readme.md index 9061851..a480fbf 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -10,10 +10,6 @@ I created ScriptCraft to make it easier for children (and anyone curious about p CoderDojo Athenry have some [excellent tutorials][cda] for younger programmers who have used [Scratch][scr] and are interested in Modding Minecraft using Javascript. In particular, they have an excellent [Scratch - to - Javascript][sj] tutorial which explains Scratch programs and how to do the same thing in Javascript. -[scr]: http://scratch.mit.edu/ -[cda]: http://cdathenry.wordpress.com/category/modderdojo/ -[sj]: http://cdathenry.wordpress.com/2013/10/12/modderdojo-week-2-moving-from-scratch-to-javascript/ - I highly recommend the series of tutorials provided by CoderDojo Athenry. Developer Chris Cacciatore has created some interesting tools using Scriptcraft... @@ -24,3 +20,7 @@ Developer Chris Cacciatore has created some interesting tools using Scriptcraft. [wb]: https://github.com/cacciatc/wolfbot [ls]: https://github.com/cacciatc/scriptcraft-lsystems [ypgpm]: YoungPersonsGuideToProgrammingMinecraft.md +[cd]: http://coderdojo.com/ +[scr]: http://scratch.mit.edu/ +[cda]: http://cdathenry.wordpress.com/category/modderdojo/ +[sj]: http://cdathenry.wordpress.com/2013/10/12/modderdojo-week-2-moving-from-scratch-to-javascript/ From a215c609039b0de5dd3a6f2314f0c32dd0805d1f Mon Sep 17 00:00:00 2001 From: Walter Higgins Date: Thu, 19 Dec 2013 10:43:37 +0000 Subject: [PATCH 017/456] Update README.md --- README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3b0a5dd..0f87968 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,13 @@ ScriptCraft =========== A Minecraft mod that lets you create mods using Javascript. +# Let's begin... + +I created ScriptCraft to make it easier for children (and anyone curious about programming) to create their own Minecraft Mods. ScriptCraft makes it easier for new programmers to create Minecraft mods. Mods are written using the Javascript programming language and once the ScriptCraft mod is installed, you can add your own new Mods by adding Javascript (.js) files in a directory. + + * If you're new to programming and want to start modding Minecraft, then [Start Here][ypgpm]. + * If you've already used [Scratch][scr], have attended a few [CoderDojo][cd] sessions, or have already dabbled with Javascript, then [Start Here][cda]. + Description =========== ScriptCraft is a plugin for Minecraft Servers which lets @@ -118,8 +125,12 @@ You can find more information about [ScriptCraft on my blog][blog]. [blog]: http://walterhiggins.net/blog/cat-index-scriptcraft.html [buk]: https://github.com/walterhiggins/ScriptCraft/blob/master/bukkit.md -[yp]: http://walterhiggins.net/blog/YoungPersonProgrammingMinecraft +[yp]: YoungPersonsGuideToProgrammingMinecraft.md [mm]: http://walterhiggins.net/blog/ScriptCraft-1-Month-later [api]: https://github.com/walterhiggins/ScriptCraft/blob/master/docs/api.md [website]: http://scriptcraftjs.org/ +[ypgpm]: YoungPersonsGuideToProgrammingMinecraft.md +[cd]: http://coderdojo.com/ +[scr]: http://scratch.mit.edu/ +[cda]: http://cdathenry.wordpress.com/category/modderdojo/ From 4481ff7d33470e6e539a16f50b0e27e8a0449d35 Mon Sep 17 00:00:00 2001 From: Walter Higgins Date: Thu, 19 Dec 2013 11:06:17 +0000 Subject: [PATCH 018/456] Update README.md --- README.md | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 0f87968..d281781 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,13 @@ -ScriptCraft -=========== -A Minecraft mod that lets you create mods using Javascript. - # Let's begin... -I created ScriptCraft to make it easier for children (and anyone curious about programming) to create their own Minecraft Mods. ScriptCraft makes it easier for new programmers to create Minecraft mods. Mods are written using the Javascript programming language and once the ScriptCraft mod is installed, you can add your own new Mods by adding Javascript (.js) files in a directory. +I created ScriptCraft to make it easier for younger programmers to create their own Minecraft Mods. ScriptCraft makes it easier for new programmers to create Minecraft mods. Mods are written using the Javascript programming language and once the ScriptCraft mod is installed, you can add your own new Mods by adding Javascript (.js) files in a directory. * If you're new to programming and want to start modding Minecraft, then [Start Here][ypgpm]. * If you've already used [Scratch][scr], have attended a few [CoderDojo][cd] sessions, or have already dabbled with Javascript, then [Start Here][cda]. + * Watch some [demos][ytpl] of what you can do with ScriptCraft. + +# Description -Description -=========== ScriptCraft is a plugin for Minecraft Servers which lets operators, administrators and plug-in authors customize the game using Javascript. ScriptCraft makes it easier to create your own mods. Mods @@ -133,4 +130,4 @@ You can find more information about [ScriptCraft on my blog][blog]. [cd]: http://coderdojo.com/ [scr]: http://scratch.mit.edu/ [cda]: http://cdathenry.wordpress.com/category/modderdojo/ - +[ytpl]: http://www.youtube.com/watch?v=DDp20SKm43Y&list=PL4Tw0AgXQZH5BiFHqD2hXyXQi0-qFbGp_ From df0e09a7741f50f69a2e6757ef556c6195b8a6c8 Mon Sep 17 00:00:00 2001 From: Walter Higgins Date: Thu, 19 Dec 2013 11:10:08 +0000 Subject: [PATCH 019/456] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d281781..73e9563 100644 --- a/README.md +++ b/README.md @@ -122,11 +122,11 @@ You can find more information about [ScriptCraft on my blog][blog]. [blog]: http://walterhiggins.net/blog/cat-index-scriptcraft.html [buk]: https://github.com/walterhiggins/ScriptCraft/blob/master/bukkit.md -[yp]: YoungPersonsGuideToProgrammingMinecraft.md +[yp]: docs/YoungPersonsGuideToProgrammingMinecraft.md [mm]: http://walterhiggins.net/blog/ScriptCraft-1-Month-later [api]: https://github.com/walterhiggins/ScriptCraft/blob/master/docs/api.md [website]: http://scriptcraftjs.org/ -[ypgpm]: YoungPersonsGuideToProgrammingMinecraft.md +[ypgpm]: docs/YoungPersonsGuideToProgrammingMinecraft.md [cd]: http://coderdojo.com/ [scr]: http://scratch.mit.edu/ [cda]: http://cdathenry.wordpress.com/category/modderdojo/ From 88c5402e63344ee8206d7580a274b8001fc6d8b7 Mon Sep 17 00:00:00 2001 From: Walter Higgins Date: Thu, 19 Dec 2013 11:13:25 +0000 Subject: [PATCH 020/456] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 73e9563..b3f8b53 100644 --- a/README.md +++ b/README.md @@ -114,9 +114,9 @@ Further Reading ScriptCraft has [its own website][website] with further information. - * To get started using ScriptCraft to Learn Javascript I recommend [reading this][yp]. + * To get started using ScriptCraft to Learn Javascript, read [The Young Person's Guide to Programming in Minecraft][yp]. * The ScriptCraft [API documentation][api]. - * To delve deeper into creating your own minecraft mod, I recommend [reading this][mm]. + * To delve deeper into creating your own minecraft mod for use by others, read [Creating a complete Minecraft Mod in Javascript][mm]. You can find more information about [ScriptCraft on my blog][blog]. From db88a51cbfc2040340d02537c5c31298f8cbf152 Mon Sep 17 00:00:00 2001 From: Walter Higgins Date: Thu, 19 Dec 2013 11:21:55 +0000 Subject: [PATCH 021/456] Update YoungPersonsGuideToProgrammingMinecraft.md --- ...YoungPersonsGuideToProgrammingMinecraft.md | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/docs/YoungPersonsGuideToProgrammingMinecraft.md b/docs/YoungPersonsGuideToProgrammingMinecraft.md index 301eefa..11dfbbf 100644 --- a/docs/YoungPersonsGuideToProgrammingMinecraft.md +++ b/docs/YoungPersonsGuideToProgrammingMinecraft.md @@ -1,7 +1,6 @@ # The Young Person's Guide to Programming in Minecraft -## 2013/01/08 17:26 -### Introduction +## Introduction Minecraft is an open-ended 3D game where you can build and craft anything you like. Minecraft can be extended and enhanced using 'Mods' @@ -20,7 +19,7 @@ players connect to a Minecraft Server on the internet or locally ![Cottages created using ScriptCraft in MineCraft][img_cr] -### Installation +## Installation CraftBukkit is a version of the Minecraft server software which allows easy addition of 'Mods' and extensions to Minecraft. ScriptCraft is a @@ -61,7 +60,7 @@ Minecraft your way using Javascript. Javascript is easier to learn than Java but it's also more flexible and powerful and is used for creating interactive web sites and many other applications. -### Learning Javascript +## Learning Javascript To begin creating cool stuff in Minecraft using ScriptCraft, you don't *have* to know much JavaScript. ScriptCraft comes with lots of functions @@ -73,7 +72,7 @@ your friends. If you want to get started learning JavaScript, check out this [fun Javascript Tutorial][ce]. If you want to dive right in to ScriptCraft, read on... -### First Steps +## First Steps If you don't already know Javascript, don't worry, you'll learn a little about Programming and Javascript along the way. You've set up a @@ -93,7 +92,7 @@ press enter: `js 1 + 1` The number 2 should be displayed. ... Well Done! You've just confirmed you can run Javascript code from within the Minecraft Console. -### Variables +## Variables A variable is how you name something for the computer (and you the programmer) to remember. You create a new variable in Javascript using @@ -147,7 +146,7 @@ are handy "free" variables created for you by ScriptCraft. One such variable is ... for me but the message displayed will be different for every player. -### Functions +## Functions ScriptCraft comes with a couple of extra functions not normally found in Javascript. These functions will help you build new structures and @@ -235,7 +234,7 @@ in-game console. You'll see the number displayed is different each time. Think of Math.random() as a Dice with many many sides. You can rely on it to never return the same value twice. -### Building stuff in Minecraft +## Building stuff in Minecraft Now we get to the fun stuff - creating structures and buildings in Minecraft. Building by hand is fun but tedious when you want to build @@ -269,7 +268,7 @@ will cycle through all of the block materials. Alternatively, you can see many more current materials and the numbers Minecraft uses for them by visiting the [Minecraft Data Values][mcdv] site. -### Common Block Materials +## Common Block Materials In Minecraft Programming, Materials aren't known by their name, instead numbers (sometimes 2 numbers) are used to indicate which @@ -301,7 +300,7 @@ a chart of all of the blocks (not items) in the Minecraft world... ![Minecraft Data Values][img_dv] -### Dimensions +## Dimensions `box()` can do more than just create single blocks - it can create cubes and cuboids of any @@ -316,7 +315,7 @@ away you want something to extend. ![Width, Height and Depth][img_whd] -### More shapes +## More shapes * `box0( block, width, height, depth )` - creates an empty box (with the insides hollowed out - perfect for dwellings. `box0` will remove both @@ -329,7 +328,7 @@ away you want something to extend. * `prism( block, width, depth )` - creates a Prism - good for roofs. * `prism0( block, width, depth )` - creates an empty prism. -### The Drone Object +## The Drone Object ScriptCraft is a Minecraft Mod that lets you execute Javascript code in the game. It also lets you write your own Mod in Javacript. One @@ -359,7 +358,7 @@ Drone is easy... make the Drone turn twice so that it is facing in the opposite direction. -#### Chaining - combining bulding and movement. +### Chaining - combining bulding and movement. You can make a Drone move around before and after building by *daisy-chaining* the building and movement functions together. In the @@ -418,7 +417,7 @@ Both `chkpt()` and `mark()` are useful for when you want to build complex things that require your Drone to move about a lot ( for example, Castles, mansions, palaces, etc). -### Saving your work +## Saving your work You can build cool things using the in-game command-prompt and the `/js` command but sooner or later you'll probably want to build @@ -433,7 +432,7 @@ Notepad because it understands Javascript. If you prefer coding on a Macintosh, then [TextWrangler][twl] is a good programming editor which also understands Javascript code. -### Your First Minecraft Mod! +## Your First Minecraft Mod! So, You've learnt a little bit about Javascript and what the Drone() object can do, let's use that knowledge to create a Minecraft Mod! @@ -469,7 +468,7 @@ Mods is as simple as writing a new javascript function and saving it in a file in the js-plugins directory. This function will now be avaible every time you launch minecraft. -#### Parameters +### Parameters If you want to change the `greet()` function so that it displays a greeting other than "Hi " you can change the code in the `greet()` function, or better still, you can use *Parameters*. Parameters are @@ -546,7 +545,7 @@ things... ... try comparing some more numbers yourself - say for example, compare the ages of your friends or siblings to your own age. -#### More fun with `true` or `false` +### More fun with `true` or `false` You can find out if you can Fly in minecraft by typing the following statement... /js self.allowFlight @@ -688,7 +687,7 @@ loop and arrays. Arrays and `for` loops are used heavily in all types of software, in fact there probably isn't any software that doesn't use `for` loops and Arrays to get things done. -#### While Loops +### While Loops Another way to repeat things over and over is to use a `while` loop. The following `while` loop counts to 100... @@ -731,7 +730,7 @@ a matter of personal taste, `for` loops are more commonly used with Arrays but as you see from the example above, `while` loops can also loop over Arrays. -#### `utils.foreach()` - Yet another way to process Arrays +### `utils.foreach()` - Yet another way to process Arrays Both the `for` statement and `while` statement are standard commonly used javascript statements used for looping. ScriptCraft also comes @@ -793,7 +792,7 @@ provides `for` and `while` statements for looping and many javascript libraries also provide their own custom looping functions. You should use what you feel most comfortable with. -#### Putting `for` loops to use - Building a Skyscraper +### Putting `for` loops to use - Building a Skyscraper For loops can be used to build enormous structures. In this next exercise I'm going to use a for loop to build a skyscraper. This @@ -862,7 +861,6 @@ that out, creating an entire city of blocks of skyscrapers is the next logical step. Of course, Minecraft doesn't have the same constraints as real-world densely populated areas so let your imagination go wild. - ### Making Decisions All the programs we have seen so far have been fairly predictable - they went From 852a755819ed7c41088c7a0a65bf1ef3fcf40fb3 Mon Sep 17 00:00:00 2001 From: Ivan Kondratyev Date: Fri, 20 Dec 2013 10:11:34 -0600 Subject: [PATCH 022/456] Use Bukkit's ChatColor class for more reliable... ...output. Also, cleans up String.prototype[method] by removing the non-used parameter "method" --- src/main/javascript/utils/text.js | 111 ++++++++++++++++-------------- 1 file changed, 60 insertions(+), 51 deletions(-) diff --git a/src/main/javascript/utils/text.js b/src/main/javascript/utils/text.js index 32b66c8..4d9b947 100644 --- a/src/main/javascript/utils/text.js +++ b/src/main/javascript/utils/text.js @@ -3,30 +3,34 @@ String class extensions ----------------------- The following chat-formatting methods are added to the javascript String class.. - * black() - * darkblue() - * blue() - * darkgreen() - * darkaqua() - * darkred() - * purple() - * gold() - * gray() - * darkgray() - * indigo() - * brightgreen() - * green() - * aqua() - * red() - * pink() - * yellow() - * white() - * bold() - * random() - * strike() - * underline() - * italic() - * reset() + * aqua() + * black() + * blue() + * bold() + * brightgreen() + * darkaqua() + * darkblue() + * darkgray() + * darkgreen() + * purple() + * darkpurple() + * darkred() + * gold() + * gray() + * green() + * italic() + * lightpurple() + * indigo() + * green() + * red() + * pink() + * yellow() + * white() + * strike() + * random() + * magic() + * underline() + * reset() Example ------- @@ -38,35 +42,40 @@ Example ***/ (function(){ + var c = org.bukkit.ChatColor; var formattingCodes = { - black: 0, - darkblue: 1, - blue: 1, - darkgreen: 2, - darkaqua: 3, - darkred: 4, - purple: 5, - gold: 6, - gray: 7, - darkgray: 8, - indigo: 9, - brightgreen: 'a', - green: 'a', - aqua: 'b', - red: 'c', - pink: 'd', - yellow: 'e', - white: 'f', - bold: 'l', - random:'k', - strike: 'm', - underline: 'n', - italic: 'o', - reset: 'r' + aqua: c.AQUA, + black: c.BLACK, + blue: c.BLUE, + bold: c.BOLD, + brightgreen: c.GREEN, + darkaqua: c.DARK_AQUA, + darkblue: c.DARK_BLUE, + darkgray: c.DARK_GRAY, + darkgreen: c.DARK_GREEN, + purple: c.LIGHT_PURPLE, + darkpurple: c.DARK_PURPLE, + darkred: c.DARK_RED, + gold: c.GOLD, + gray: c.GRAY, + green: c.GREEN, + italic: c.ITALIC, + lightpurple: c.LIGHT_PURPLE, + indigo: c.BLUE, + green: c.GREEN, + red: c.RED, + pink: c.LIGHT_PURPLE, + yellow: c.YELLOW, + white: c.WHITE, + strike: c.STRIKETHROUGH, + random: c.MAGIC, + magic: c.MAGIC, + underline: c.UNDERLINE, + reset: c.RESET }; for (var method in formattingCodes){ - String.prototype[method] = function(m,c){ - return function(){ return "§"+c + this;}; - }(method,formattingCodes[method]); + String.prototype[method] = function(c){ + return function(){return c+this;}; + }(formattingCodes[method]); } }()); From f35a729ceba7b8781a704dba685466b53f5c60f6 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 21 Dec 2013 08:58:40 +0000 Subject: [PATCH 023/456] Adding support for 'require()' and node.js-style modules (not node modules per se - just support for the same module semantics) --- docs/API-Reference.md | 99 +++++++++++++----- src/main/javascript/core/_require.js | 123 +++++++++++++++++++++++ src/main/javascript/core/_scriptcraft.js | 54 +--------- 3 files changed, 203 insertions(+), 73 deletions(-) create mode 100644 src/main/javascript/core/_require.js diff --git a/docs/API-Reference.md b/docs/API-Reference.md index e407e9d..047aa10 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -272,6 +272,53 @@ See [issue #69][issue69] for more information. [issue69]: https://github.com/walterhiggins/ScriptCraft/issues/69 +# Require - Node.js-style module loading in ScriptCraft + +### (Experimental as of 2013-12-21) + +Node.js is a server-side javascript environment with an excellent +module loading system based on CommonJS. Modules in Node.js are really +simple. Each module is in its own javascript file and all variables +and functions within the file are private to that file/module only. There is a very concise explanation of CommonJS modules at +http://wiki.commonjs.org/wiki/Modules/1.1.1. + +If you want to export a variable or function you use the module.export +property. + +For example imagine you have 3 files program.js, inc.js and math.js ... + +## math.js + + exports.add = function(a,b){ + return a + b; + } + +## inc.js + + var math = require('./math'); + exports.increment = function(n){ + return math.add(n, 1); + } + +## program.js + + var inc = require('./inc').increment; + var a = 7; + a = inc(a); + print(a); + +You can see from the above sample code that programs can use modules +and modules themeselves can use other modules. Modules have full +control over what functions and properties they want to provide to +others. + +## Important + +Although ScriptCraft now supports Node.js style modules, it does not +support node modules. Node.js and Rhino are two very different +Javascript environments. ScriptCraft uses Rhino Javascript, not +Node.js. + Drone Module ============ The Drone is a convenience class for building. It can be used for... @@ -1451,30 +1498,34 @@ String class extensions ----------------------- The following chat-formatting methods are added to the javascript String class.. - * black() - * darkblue() - * blue() - * darkgreen() - * darkaqua() - * darkred() - * purple() - * gold() - * gray() - * darkgray() - * indigo() - * brightgreen() - * green() - * aqua() - * red() - * pink() - * yellow() - * white() - * bold() - * random() - * strike() - * underline() - * italic() - * reset() + * aqua() + * black() + * blue() + * bold() + * brightgreen() + * darkaqua() + * darkblue() + * darkgray() + * darkgreen() + * purple() + * darkpurple() + * darkred() + * gold() + * gray() + * green() + * italic() + * lightpurple() + * indigo() + * green() + * red() + * pink() + * yellow() + * white() + * strike() + * random() + * magic() + * underline() + * reset() Example ------- diff --git a/src/main/javascript/core/_require.js b/src/main/javascript/core/_require.js new file mode 100644 index 0000000..11501c0 --- /dev/null +++ b/src/main/javascript/core/_require.js @@ -0,0 +1,123 @@ +/************************************************************************* +# Require - Node.js-style module loading in ScriptCraft + +### (Experimental as of 2013-12-21) + +Node.js is a server-side javascript environment with an excellent +module loading system based on CommonJS. Modules in Node.js are really +simple. Each module is in its own javascript file and all variables +and functions within the file are private to that file/module only. There is a very concise explanation of CommonJS modules at +http://wiki.commonjs.org/wiki/Modules/1.1.1. + +If you want to export a variable or function you use the module.export +property. + +For example imagine you have 3 files program.js, inc.js and math.js ... + +## math.js + + exports.add = function(a,b){ + return a + b; + } + +## inc.js + + var math = require('./math'); + exports.increment = function(n){ + return math.add(n, 1); + } + +## program.js + + var inc = require('./inc').increment; + var a = 7; + a = inc(a); + print(a); + +You can see from the above sample code that programs can use modules +and modules themeselves can use other modules. Modules have full +control over what functions and properties they want to provide to +others. + +## Important + +Although ScriptCraft now supports Node.js style modules, it does not +support node modules. Node.js and Rhino are two very different +Javascript environments. ScriptCraft uses Rhino Javascript, not +Node.js. + +***/ +(function(__plugin, __engine, verbose){ + /* + wph 20131215 Experimental + */ + var _loadedModules = {}; + + var _require = function(parentFile, path) + { + var _canonize = function(file){ + return "" + file.canonicalPath.replaceAll("\\\\","/"); + }; + + var file = new java.io.File(parentFile, path); + if (!file.exists()) + { + if (path.match(/\.js$/i)){ + __plugin.logger.warning('require("' + path + '") failed. File [' + file.canonicalPath + '] not found'); + return; + }else{ + path = path + '.js'; + file = new java.io.File(parentFile, path); + if (!file.exists()){ + __plugin.logger.warning('require("' + path + '") failed. File [' + file.canonicalPath + '] not found'); + return; + } + } + } + if (file.isDirectory()){ + __plugin.logger.warning('require("' + path + '") directories not yet supported. ' + file.canonicalPath); + return; + } + var canonizedFilename = _canonize(file); + + var moduleInfo = _loadedModules[canonizedFilename]; + if (moduleInfo){ + return moduleInfo; + } + if (verbose){ + print("loading module " + canonizedFilename); + } + var reader = new java.io.FileReader(file); + var br = new java.io.BufferedReader(reader); + var code = ""; + while ((r = br.readLine()) !== null) code += r + "\n"; + + var head = "(function(exports,module,require,__filename,__dirname){ "; + + moduleInfo = { + loaded: false, + id: canonizedFilename, + exports: {} + }; + var tail = "})"; + code = head + code + tail; + + _loadedModules[canonizedFilename] = moduleInfo; + moduleInfo.main = __engine.eval(code); + moduleInfo.main(moduleInfo.exports, + moduleInfo, + _requireClosure(file.parentFile), + canonizedFilename, + "" + parentFile?parentFile.canonicalPath:""); + moduleInfo.loaded = true; + return moduleInfo; + }; + + var _requireClosure = function(parent){ + return function(path){ + var module = _require(parent, path); + return module.exports; + }; + }; + return _requireClosure(new java.io.File("./")); +}) diff --git a/src/main/javascript/core/_scriptcraft.js b/src/main/javascript/core/_scriptcraft.js index e7aecdd..b874a44 100644 --- a/src/main/javascript/core/_scriptcraft.js +++ b/src/main/javascript/core/_scriptcraft.js @@ -240,59 +240,15 @@ var server = org.bukkit.Bukkit.server; if (typeof load == "function") return ; - var _canonize = function(file){ return file.getCanonicalPath().replaceAll("\\\\","/"); }; + var _canonize = function(file){ + return "" + file.getCanonicalPath().replaceAll("\\\\","/"); + }; var _originalScript = __script; var parentFileObj = new java.io.File(__script).getParentFile(); var jsPluginsRootDir = parentFileObj.getParentFile(); var jsPluginsRootDirName = _canonize(jsPluginsRootDir); - /* - wph 20131215 Experimental - */ - var _loadedModules = {}; - var _require = function(path) - { - var file = new java.io.File(path); - if (!file.exists()){ - if (path.match(/\.js$/i)){ - __plugin.logger.warning('require("' + path + '") failed. File not found'); - return; - }else{ - path = path + '.js'; - file = new java.io.File(path); - if (!file.exists()){ - __plugin.logger.warning('require("' + path + '") failed. File not found'); - return; - } - } - } - if (file.isDirectory()){ - __plugin.logger.warning('require("' + path + '") directories not yet supported.'); - return; - } - var canonizedFilename = _canonize(file); - if (_loadedModules[canonizedFilename]){ - return _loadedModules[canonizedFilename]; - } - if (verbose){ - print("loading module " + canonizedFilename); - } - var reader = new java.io.FileReader(file); - var br = new java.io.BufferedReader(reader); - var code = ""; - var module = {id: canonizedFilename}; - while ((r = br.readLine()) !== null) code += r + "\n"; - - var head = "var result = {};(function(exports,module){ "; - var tail = "; return exports;}(result," + JSON.stringify(module) + "))"; - code = head + code + tail; - - _loadedModules[canonizedFilename] = __engine.eval(code); - - return _loadedModules[canonizedFilename]; - }; - global.require = _require; var _loaded = {}; @@ -331,7 +287,6 @@ var server = org.bukkit.Bukkit.server; } else { while ((r = br.readLine()) !== null) code += r + "\n"; } - result = __engine.eval(code); _loaded[canonizedFilename] = result || true; }catch (e){ @@ -829,7 +784,8 @@ See [issue #69][issue69] for more information. global._onTabComplete = __onTabCompleteJS; global.addUnloadHandler = _addUnloadHandler; - + var fnRequire = load(jsPluginsRootDirName + '/core/_require.js',true); + global.require = fnRequire(__plugin, __engine, verbose); // // assumes this was loaded from js-plugins/core/ // load all of the plugins. From f25f4ac7f3b72107640da0ed340d4357f12d5d42 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 21 Dec 2013 09:09:11 +0000 Subject: [PATCH 024/456] Updated doc for require --- src/main/javascript/core/_require.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/main/javascript/core/_require.js b/src/main/javascript/core/_require.js index 11501c0..a96beb8 100644 --- a/src/main/javascript/core/_require.js +++ b/src/main/javascript/core/_require.js @@ -1,33 +1,35 @@ /************************************************************************* -# Require - Node.js-style module loading in ScriptCraft +## Require - Node.js-style module loading in ScriptCraft -### (Experimental as of 2013-12-21) +#### (Experimental as of 2013-12-21) Node.js is a server-side javascript environment with an excellent module loading system based on CommonJS. Modules in Node.js are really simple. Each module is in its own javascript file and all variables -and functions within the file are private to that file/module only. There is a very concise explanation of CommonJS modules at -http://wiki.commonjs.org/wiki/Modules/1.1.1. +and functions within the file are private to that file/module only. +There is a very concise explanation of CommonJS modules at... + +[http://wiki.commonjs.org/wiki/Modules/1.1.1.][cjsmodules] If you want to export a variable or function you use the module.export property. For example imagine you have 3 files program.js, inc.js and math.js ... -## math.js +### math.js exports.add = function(a,b){ return a + b; } -## inc.js +### inc.js var math = require('./math'); exports.increment = function(n){ return math.add(n, 1); } -## program.js +### program.js var inc = require('./inc').increment; var a = 7; @@ -46,6 +48,12 @@ support node modules. Node.js and Rhino are two very different Javascript environments. ScriptCraft uses Rhino Javascript, not Node.js. +Right now, the base directory is for relative modules is 'js-plugins'. +Modules can be loaded using relative or absolute paths. Per the CommonJS +module specification, the '.js' suffix is optional. + +[cjsmodules]: http://wiki.commonjs.org/wiki/Modules/1.1.1. + ***/ (function(__plugin, __engine, verbose){ /* From 4ffc5f6850ebbeecf46cb5bc2e1700a40ed045a5 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 21 Dec 2013 09:10:59 +0000 Subject: [PATCH 025/456] updated require docs --- docs/API-Reference.md | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 047aa10..8bb5283 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -272,35 +272,37 @@ See [issue #69][issue69] for more information. [issue69]: https://github.com/walterhiggins/ScriptCraft/issues/69 -# Require - Node.js-style module loading in ScriptCraft +## Require - Node.js-style module loading in ScriptCraft -### (Experimental as of 2013-12-21) +#### (Experimental as of 2013-12-21) Node.js is a server-side javascript environment with an excellent module loading system based on CommonJS. Modules in Node.js are really simple. Each module is in its own javascript file and all variables -and functions within the file are private to that file/module only. There is a very concise explanation of CommonJS modules at -http://wiki.commonjs.org/wiki/Modules/1.1.1. +and functions within the file are private to that file/module only. +There is a very concise explanation of CommonJS modules at... + +[http://wiki.commonjs.org/wiki/Modules/1.1.1.][cjsmodules] If you want to export a variable or function you use the module.export property. For example imagine you have 3 files program.js, inc.js and math.js ... -## math.js +### math.js exports.add = function(a,b){ return a + b; } -## inc.js +### inc.js var math = require('./math'); exports.increment = function(n){ return math.add(n, 1); } -## program.js +### program.js var inc = require('./inc').increment; var a = 7; @@ -319,6 +321,12 @@ support node modules. Node.js and Rhino are two very different Javascript environments. ScriptCraft uses Rhino Javascript, not Node.js. +Right now, the base directory is for relative modules is 'js-plugins'. +Modules can be loaded using relative or absolute paths. Per the CommonJS +module specification, the '.js' suffix is optional. + +[cjsmodules]: http://wiki.commonjs.org/wiki/Modules/1.1.1. + Drone Module ============ The Drone is a convenience class for building. It can be used for... From a7dcb503aa8ccfc908cfcfbe1a895b335588dfae Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 24 Dec 2013 00:09:49 +0000 Subject: [PATCH 026/456] Major overhaul of plugin and module loading system and scriptcraft directory layout --- .../javascript/drone/{ => contrib}/partial.js | 0 .../drone/{ => contrib}/rboxcall.js | 0 src/main/javascript/ext/json2.js | 486 ----- .../javascript/{core => lib}/_primitives.js | 0 src/main/javascript/{events => lib}/events.js | 0 src/main/javascript/lib/plugin.js | 208 ++ .../{core/_require.js => lib/require.js} | 0 .../_scriptcraft.js => lib/scriptcraft.js} | 0 .../{ => modules}/fireworks/fireworks.js | 0 .../javascript/modules/fireworks/package.json | 4 + src/main/javascript/modules/http/package.json | 4 + .../javascript/{ => modules}/http/request.js | 0 src/main/javascript/modules/partial.js | 14 + .../javascript/{ => modules}/signs/menu.js | 0 .../javascript/modules/signs/package.json | 4 + .../modules/underscore/package.json | 4 + .../modules/underscore/underscore.js | 1314 +++++++++++++ .../javascript/modules/utils/package.json | 4 + .../text.js => modules/utils/string-exts.js} | 0 .../javascript/{ => modules}/utils/utils.js | 0 .../javascript/{ => plugins}/alias/alias.js | 0 .../javascript/{arrows => plugins}/arrows.js | 0 .../javascript/{ => plugins}/chat/color.js | 0 .../{ => plugins}/classroom/classroom.js | 0 src/main/javascript/plugins/drone/blocks.js | 275 +++ .../javascript/plugins/drone/blocktype.js | 376 ++++ .../plugins/drone/contrib/castle.js | 51 + .../plugins/drone/contrib/chessboard.js | 37 + .../plugins/drone/contrib/cottage.js | 79 + .../plugins/drone/contrib/dancefloor.js | 38 + .../javascript/plugins/drone/contrib/fort.js | 68 + .../javascript/plugins/drone/contrib/logo.js | 219 +++ .../plugins/drone/contrib/rainbow.js | 44 + .../plugins/drone/contrib/rboxcall.js | 34 + .../plugins/drone/contrib/redstonewire.js | 108 + .../drone/contrib/skyscraper-example.js | 19 + .../plugins/drone/contrib/spiral_stairs.js | 48 + .../plugins/drone/contrib/streamer.js | 32 + .../plugins/drone/contrib/temple.js | 25 + .../javascript/plugins/drone/drone-exts.js | 25 + .../plugins/drone/drone-firework.js | 6 + src/main/javascript/plugins/drone/drone.js | 1740 +++++++++++++++++ src/main/javascript/plugins/drone/sphere.js | 268 +++ src/main/javascript/plugins/drone/test.js | 23 + src/main/javascript/plugins/example-1.js | 12 + .../javascript/{ => plugins}/homes/homes.js | 0 .../{ => plugins}/minigames/NumberGuess.js | 0 .../{ => plugins}/minigames/SnowBallFight.js | 0 src/main/javascript/plugins/signs/examples.js | 32 + 49 files changed, 5115 insertions(+), 486 deletions(-) rename src/main/javascript/drone/{ => contrib}/partial.js (100%) rename src/main/javascript/drone/{ => contrib}/rboxcall.js (100%) delete mode 100644 src/main/javascript/ext/json2.js rename src/main/javascript/{core => lib}/_primitives.js (100%) rename src/main/javascript/{events => lib}/events.js (100%) create mode 100644 src/main/javascript/lib/plugin.js rename src/main/javascript/{core/_require.js => lib/require.js} (100%) rename src/main/javascript/{core/_scriptcraft.js => lib/scriptcraft.js} (100%) rename src/main/javascript/{ => modules}/fireworks/fireworks.js (100%) create mode 100644 src/main/javascript/modules/fireworks/package.json create mode 100644 src/main/javascript/modules/http/package.json rename src/main/javascript/{ => modules}/http/request.js (100%) create mode 100644 src/main/javascript/modules/partial.js rename src/main/javascript/{ => modules}/signs/menu.js (100%) create mode 100644 src/main/javascript/modules/signs/package.json create mode 100644 src/main/javascript/modules/underscore/package.json create mode 100644 src/main/javascript/modules/underscore/underscore.js create mode 100644 src/main/javascript/modules/utils/package.json rename src/main/javascript/{utils/text.js => modules/utils/string-exts.js} (100%) rename src/main/javascript/{ => modules}/utils/utils.js (100%) rename src/main/javascript/{ => plugins}/alias/alias.js (100%) rename src/main/javascript/{arrows => plugins}/arrows.js (100%) rename src/main/javascript/{ => plugins}/chat/color.js (100%) rename src/main/javascript/{ => plugins}/classroom/classroom.js (100%) create mode 100644 src/main/javascript/plugins/drone/blocks.js create mode 100644 src/main/javascript/plugins/drone/blocktype.js create mode 100644 src/main/javascript/plugins/drone/contrib/castle.js create mode 100644 src/main/javascript/plugins/drone/contrib/chessboard.js create mode 100644 src/main/javascript/plugins/drone/contrib/cottage.js create mode 100644 src/main/javascript/plugins/drone/contrib/dancefloor.js create mode 100644 src/main/javascript/plugins/drone/contrib/fort.js create mode 100644 src/main/javascript/plugins/drone/contrib/logo.js create mode 100644 src/main/javascript/plugins/drone/contrib/rainbow.js create mode 100644 src/main/javascript/plugins/drone/contrib/rboxcall.js create mode 100644 src/main/javascript/plugins/drone/contrib/redstonewire.js create mode 100644 src/main/javascript/plugins/drone/contrib/skyscraper-example.js create mode 100644 src/main/javascript/plugins/drone/contrib/spiral_stairs.js create mode 100644 src/main/javascript/plugins/drone/contrib/streamer.js create mode 100644 src/main/javascript/plugins/drone/contrib/temple.js create mode 100644 src/main/javascript/plugins/drone/drone-exts.js create mode 100644 src/main/javascript/plugins/drone/drone-firework.js create mode 100644 src/main/javascript/plugins/drone/drone.js create mode 100644 src/main/javascript/plugins/drone/sphere.js create mode 100644 src/main/javascript/plugins/drone/test.js create mode 100644 src/main/javascript/plugins/example-1.js rename src/main/javascript/{ => plugins}/homes/homes.js (100%) rename src/main/javascript/{ => plugins}/minigames/NumberGuess.js (100%) rename src/main/javascript/{ => plugins}/minigames/SnowBallFight.js (100%) create mode 100644 src/main/javascript/plugins/signs/examples.js diff --git a/src/main/javascript/drone/partial.js b/src/main/javascript/drone/contrib/partial.js similarity index 100% rename from src/main/javascript/drone/partial.js rename to src/main/javascript/drone/contrib/partial.js diff --git a/src/main/javascript/drone/rboxcall.js b/src/main/javascript/drone/contrib/rboxcall.js similarity index 100% rename from src/main/javascript/drone/rboxcall.js rename to src/main/javascript/drone/contrib/rboxcall.js diff --git a/src/main/javascript/ext/json2.js b/src/main/javascript/ext/json2.js deleted file mode 100644 index c7745df..0000000 --- a/src/main/javascript/ext/json2.js +++ /dev/null @@ -1,486 +0,0 @@ -/* - json2.js - 2012-10-08 - - Public Domain. - - NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. - - See http://www.JSON.org/js.html - - - This code should be minified before deployment. - See http://javascript.crockford.com/jsmin.html - - USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO - NOT CONTROL. - - - This file creates a global JSON object containing two methods: stringify - and parse. - - JSON.stringify(value, replacer, space) - value any JavaScript value, usually an object or array. - - replacer an optional parameter that determines how object - values are stringified for objects. It can be a - function or an array of strings. - - space an optional parameter that specifies the indentation - of nested structures. If it is omitted, the text will - be packed without extra whitespace. If it is a number, - it will specify the number of spaces to indent at each - level. If it is a string (such as '\t' or ' '), - it contains the characters used to indent at each level. - - This method produces a JSON text from a JavaScript value. - - When an object value is found, if the object contains a toJSON - method, its toJSON method will be called and the result will be - stringified. A toJSON method does not serialize: it returns the - value represented by the name/value pair that should be serialized, - or undefined if nothing should be serialized. The toJSON method - will be passed the key associated with the value, and this will be - bound to the value - - For example, this would serialize Dates as ISO strings. - - Date.prototype.toJSON = function (key) { - function f(n) { - // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; - } - - return this.getUTCFullYear() + '-' + - f(this.getUTCMonth() + 1) + '-' + - f(this.getUTCDate()) + 'T' + - f(this.getUTCHours()) + ':' + - f(this.getUTCMinutes()) + ':' + - f(this.getUTCSeconds()) + 'Z'; - }; - - You can provide an optional replacer method. It will be passed the - key and value of each member, with this bound to the containing - object. The value that is returned from your method will be - serialized. If your method returns undefined, then the member will - be excluded from the serialization. - - If the replacer parameter is an array of strings, then it will be - used to select the members to be serialized. It filters the results - such that only members with keys listed in the replacer array are - stringified. - - Values that do not have JSON representations, such as undefined or - functions, will not be serialized. Such values in objects will be - dropped; in arrays they will be replaced with null. You can use - a replacer function to replace those with JSON values. - JSON.stringify(undefined) returns undefined. - - The optional space parameter produces a stringification of the - value that is filled with line breaks and indentation to make it - easier to read. - - If the space parameter is a non-empty string, then that string will - be used for indentation. If the space parameter is a number, then - the indentation will be that many spaces. - - Example: - - text = JSON.stringify(['e', {pluribus: 'unum'}]); - // text is '["e",{"pluribus":"unum"}]' - - - text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); - // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' - - text = JSON.stringify([new Date()], function (key, value) { - return this[key] instanceof Date ? - 'Date(' + this[key] + ')' : value; - }); - // text is '["Date(---current time---)"]' - - - JSON.parse(text, reviver) - This method parses a JSON text to produce an object or array. - It can throw a SyntaxError exception. - - The optional reviver parameter is a function that can filter and - transform the results. It receives each of the keys and values, - and its return value is used instead of the original value. - If it returns what it received, then the structure is not modified. - If it returns undefined then the member is deleted. - - Example: - - // Parse the text. Values that look like ISO date strings will - // be converted to Date objects. - - myData = JSON.parse(text, function (key, value) { - var a; - if (typeof value === 'string') { - a = -/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); - if (a) { - return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], - +a[5], +a[6])); - } - } - return value; - }); - - myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { - var d; - if (typeof value === 'string' && - value.slice(0, 5) === 'Date(' && - value.slice(-1) === ')') { - d = new Date(value.slice(5, -1)); - if (d) { - return d; - } - } - return value; - }); - - - This is a reference implementation. You are free to copy, modify, or - redistribute. -*/ - -/*jslint evil: true, regexp: true */ - -/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, - call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, - getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, - lastIndex, length, parse, prototype, push, replace, slice, stringify, - test, toJSON, toString, valueOf -*/ - - -// Create a JSON object only if one does not already exist. We create the -// methods in a closure to avoid creating global variables. - -if (typeof JSON !== 'object') { - JSON = {}; -} - -(function () { - 'use strict'; - - function f(n) { - // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; - } - - if (typeof Date.prototype.toJSON !== 'function') { - - Date.prototype.toJSON = function (key) { - - return isFinite(this.valueOf()) - ? this.getUTCFullYear() + '-' + - f(this.getUTCMonth() + 1) + '-' + - f(this.getUTCDate()) + 'T' + - f(this.getUTCHours()) + ':' + - f(this.getUTCMinutes()) + ':' + - f(this.getUTCSeconds()) + 'Z' - : null; - }; - - String.prototype.toJSON = - Number.prototype.toJSON = - Boolean.prototype.toJSON = function (key) { - return this.valueOf(); - }; - } - - var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, - escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, - gap, - indent, - meta = { // table of character substitutions - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"' : '\\"', - '\\': '\\\\' - }, - rep; - - - function quote(string) { - -// If the string contains no control characters, no quote characters, and no -// backslash characters, then we can safely slap some quotes around it. -// Otherwise we must also replace the offending characters with safe escape -// sequences. - - escapable.lastIndex = 0; - return escapable.test(string) ? '"' + string.replace(escapable, function (a) { - var c = meta[a]; - return typeof c === 'string' - ? c - : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }) + '"' : '"' + string + '"'; - } - - - function str(key, holder) { - -// Produce a string from holder[key]. - - var i, // The loop counter. - k, // The member key. - v, // The member value. - length, - mind = gap, - partial, - value = holder[key]; - -// If the value has a toJSON method, call it to obtain a replacement value. - - if (value && typeof value === 'object' && - typeof value.toJSON === 'function') { - value = value.toJSON(key); - } - -// If we were called with a replacer function, then call the replacer to -// obtain a replacement value. - - if (typeof rep === 'function') { - value = rep.call(holder, key, value); - } - -// What happens next depends on the value's type. - - switch (typeof value) { - case 'string': - return quote(value); - - case 'number': - -// JSON numbers must be finite. Encode non-finite numbers as null. - - return isFinite(value) ? String(value) : 'null'; - - case 'boolean': - case 'null': - -// If the value is a boolean or null, convert it to a string. Note: -// typeof null does not produce 'null'. The case is included here in -// the remote chance that this gets fixed someday. - - return String(value); - -// If the type is 'object', we might be dealing with an object or an array or -// null. - - case 'object': - -// Due to a specification blunder in ECMAScript, typeof null is 'object', -// so watch out for that case. - - if (!value) { - return 'null'; - } - -// Make an array to hold the partial results of stringifying this object value. - - gap += indent; - partial = []; - -// Is the value an array? - - if (Object.prototype.toString.apply(value) === '[object Array]') { - -// The value is an array. Stringify every element. Use null as a placeholder -// for non-JSON values. - - length = value.length; - for (i = 0; i < length; i += 1) { - partial[i] = str(i, value) || 'null'; - } - -// Join all of the elements together, separated with commas, and wrap them in -// brackets. - - v = partial.length === 0 - ? '[]' - : gap - ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' - : '[' + partial.join(',') + ']'; - gap = mind; - return v; - } - -// If the replacer is an array, use it to select the members to be stringified. - - if (rep && typeof rep === 'object') { - length = rep.length; - for (i = 0; i < length; i += 1) { - if (typeof rep[i] === 'string') { - k = rep[i]; - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } - } - } - } else { - -// Otherwise, iterate through all of the keys in the object. - - for (k in value) { - if (Object.prototype.hasOwnProperty.call(value, k)) { - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } - } - } - } - -// Join all of the member texts together, separated with commas, -// and wrap them in braces. - - v = partial.length === 0 - ? '{}' - : gap - ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' - : '{' + partial.join(',') + '}'; - gap = mind; - return v; - } - } - -// If the JSON object does not yet have a stringify method, give it one. - - if (typeof JSON.stringify !== 'function') { - JSON.stringify = function (value, replacer, space) { - -// The stringify method takes a value and an optional replacer, and an optional -// space parameter, and returns a JSON text. The replacer can be a function -// that can replace values, or an array of strings that will select the keys. -// A default replacer method can be provided. Use of the space parameter can -// produce text that is more easily readable. - - var i; - gap = ''; - indent = ''; - -// If the space parameter is a number, make an indent string containing that -// many spaces. - - if (typeof space === 'number') { - for (i = 0; i < space; i += 1) { - indent += ' '; - } - -// If the space parameter is a string, it will be used as the indent string. - - } else if (typeof space === 'string') { - indent = space; - } - -// If there is a replacer, it must be a function or an array. -// Otherwise, throw an error. - - rep = replacer; - if (replacer && typeof replacer !== 'function' && - (typeof replacer !== 'object' || - typeof replacer.length !== 'number')) { - throw new Error('JSON.stringify'); - } - -// Make a fake root object containing our value under the key of ''. -// Return the result of stringifying the value. - - return str('', {'': value}); - }; - } - - -// If the JSON object does not yet have a parse method, give it one. - - if (typeof JSON.parse !== 'function') { - JSON.parse = function (text, reviver) { - -// The parse method takes a text and an optional reviver function, and returns -// a JavaScript value if the text is a valid JSON text. - - var j; - - function walk(holder, key) { - -// The walk method is used to recursively walk the resulting structure so -// that modifications can be made. - - var k, v, value = holder[key]; - if (value && typeof value === 'object') { - for (k in value) { - if (Object.prototype.hasOwnProperty.call(value, k)) { - v = walk(value, k); - if (v !== undefined) { - value[k] = v; - } else { - delete value[k]; - } - } - } - } - return reviver.call(holder, key, value); - } - - -// Parsing happens in four stages. In the first stage, we replace certain -// Unicode characters with escape sequences. JavaScript handles many characters -// incorrectly, either silently deleting them, or treating them as line endings. - - text = String(text); - cx.lastIndex = 0; - if (cx.test(text)) { - text = text.replace(cx, function (a) { - return '\\u' + - ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }); - } - -// In the second stage, we run the text against regular expressions that look -// for non-JSON patterns. We are especially concerned with '()' and 'new' -// because they can cause invocation, and '=' because it can cause mutation. -// But just to be safe, we want to reject all unexpected forms. - -// We split the second stage into 4 regexp operations in order to work around -// crippling inefficiencies in IE's and Safari's regexp engines. First we -// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we -// replace all simple value tokens with ']' characters. Third, we delete all -// open brackets that follow a colon or comma or that begin the text. Finally, -// we look to see that the remaining characters are only whitespace or ']' or -// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. - - if (/^[\],:{}\s]*$/ - .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') - .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') - .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { - -// In the third stage we use the eval function to compile the text into a -// JavaScript structure. The '{' operator is subject to a syntactic ambiguity -// in JavaScript: it can begin a block or an object literal. We wrap the text -// in parens to eliminate the ambiguity. - - j = eval('(' + text + ')'); - -// In the optional fourth stage, we recursively walk the new structure, passing -// each name/value pair to a reviver function for possible transformation. - - return typeof reviver === 'function' - ? walk({'': j}, '') - : j; - } - -// If the text is not JSON parseable, then a SyntaxError is thrown. - - throw new SyntaxError('JSON.parse'); - }; - } -}()); diff --git a/src/main/javascript/core/_primitives.js b/src/main/javascript/lib/_primitives.js similarity index 100% rename from src/main/javascript/core/_primitives.js rename to src/main/javascript/lib/_primitives.js diff --git a/src/main/javascript/events/events.js b/src/main/javascript/lib/events.js similarity index 100% rename from src/main/javascript/events/events.js rename to src/main/javascript/lib/events.js diff --git a/src/main/javascript/lib/plugin.js b/src/main/javascript/lib/plugin.js new file mode 100644 index 0000000..1cea02d --- /dev/null +++ b/src/main/javascript/lib/plugin.js @@ -0,0 +1,208 @@ +var File = java.io.File; +var FileWriter = java.io.FileWriter; +var PrintWriter = java.io.PrintWriter; + +/* + Save a javascript object to a file (saves using JSON notation) +*/ +var _save = function(object, filename){ + var objectToStr = null; + try{ + objectToStr = JSON.stringify(object); + }catch(e){ + print("ERROR: " + e.getMessage() + " while saving " + filename); + return; + } + var f = (filename instanceof File) ? filename : new File(filename); + var out = new PrintWriter(new FileWriter(f)); + out.println( objectToStr ); + out.close(); +}; +/* + plugin management +*/ +var _plugins = {}; + +var _plugin = function(/* String */ moduleName, /* Object */ moduleObject, isPersistent) +{ + // + // don't load plugin more than once + // + if (typeof _plugins[moduleName] != "undefined") + return _plugins[moduleName].module; + + var pluginData = {persistent: isPersistent, module: moduleObject}; + moduleObject.store = moduleObject.store || {}; + _plugins[moduleName] = pluginData; + + if (isPersistent){ + var loadedStore = load(dataDir.canonicalPath + "/" + moduleName + "-store.json"); + if (loadedStore){ + moduleObject.store = loadedStore; + }else{ + if (!moduleObject.store){ + moduleObject.store = {}; + } + } + } + return moduleObject; +}; +/* + allow for deferred execution (once all modules have loaded) +*/ +var _deferred = []; +var _ready = function( func ){ + _deferred.push(func); +}; +var _cmdInterceptors = []; +/* + command management - allow for non-ops to execute approved javascript code. +*/ +var _commands = {}; +exports.commands = _commands; +var _command = function(name,func,options,intercepts) +{ + if (typeof name == "undefined"){ + // it's an invocation from the Java Plugin! + if (__cmdArgs.length === 0) + throw new Error("Usage: jsp command-name command-parameters"); + var name = __cmdArgs[0]; + var cmd = _commands[name]; + if (typeof cmd === "undefined"){ + // it's not a global command - pass it on to interceptors + var intercepted = false; + for (var i = 0;i < _cmdInterceptors.length;i++){ + if (_cmdInterceptors[i](__cmdArgs)) + intercepted = true; + } + if (!intercepted) + self.sendMessage("Command '" + name + "' is not recognised"); + }else{ + func = cmd.callback; + var params = []; + for (var i =1; i < __cmdArgs.length;i++){ + params.push("" + __cmdArgs[i]); + } + return func(params); + } + }else{ + if (typeof options == "undefined") + options = []; + _commands[name] = {callback: func, options: options}; + if (intercepts) + _cmdInterceptors.push(func); + return func; + } +}; + +exports.plugin = _plugin; +exports.command = _command; +exports.save = _save; + +var scriptCraftDir = null; +var pluginDir = null; +var dataDir = null; + +exports.autoload = function(dir) { + + scriptCraftDir = dir; + pluginDir = new File(dir, "plugins"); + dataDir = new File(dir, "data"); + + var _canonize = function(file){ + return "" + file.getCanonicalPath().replaceAll("\\\\","/"); + }; + /* + recursively walk the given directory and return a list of all .js files + */ + var _listSourceFiles = function(store,dir) + { + var files = dir.listFiles(); + for (var i = 0;i < files.length; i++) { + var file = files[i]; + if (file.isDirectory()){ + _listSourceFiles(store,file); + }else{ + if ((file.getCanonicalPath().endsWith(".js") + || file.getCanonicalPath().endsWith(".coffee")) + ) { + store.push(file); + } + } + } + }; + /* + sort so that .js files with same name as parent directory appear before + other files in the same directory + */ + var sortByModule = function(a,b){ + a = _canonize(a); + b = _canonize(b); + var aparts = (""+a).split(/\//); + var bparts = (""+b).split(/\//); + //var adir = aparts[aparts.length-2]; + var adir = aparts.slice(0,aparts.length-1).join("/"); + var afile = aparts[aparts.length-1]; + //var bdir = bparts[bparts.length-2]; + var bdir = bparts.slice(0,bparts.length-1).join("/"); + var bfile = bparts[bparts.length-1]; + + if(adirbdir) return 1; + + afile = afile.match(/[a-zA-Z0-9\-_]+/)[0]; + + if (adir.match(new RegExp(afile + "$"))) + return -1; + else + return 1; + }; + /* + Reload all of the .js files in the given directory + */ + var _reload = function(pluginDir) + { + _loaded = []; + var sourceFiles = []; + _listSourceFiles(sourceFiles,pluginDir); + + //sourceFiles.sort(sortByModule); + + // + // script files whose name begins with _ (underscore) + // will not be loaded automatically at startup. + // These files are assumed to be dependencies/private to plugins + // + // E.g. If you have a plugin called myMiniGame.js in the myMiniGame directory + // and which in addition to myMiniGame.js also includes _myMiniGame_currency.js _myMiniGame_events.js etc. + // then it's assumed that _myMiniGame_currency.js and _myMiniGame_events.js will be loaded + // as dependencies by myMiniGame.js and do not need to be loaded via js reload + // + var len = sourceFiles.length; + if (config.verbose) + logger.info(len + " scriptcraft plugins found."); + for (var i = 0;i < len; i++){ + var pluginPath = _canonize(sourceFiles[i]); + if (config.verbose) + logger.info("Loading plugin: " + pluginPath); + var module = require(pluginPath); + for (var property in module){ + /* + all exports in plugins become global + */ + global[property] = module[property]; + } + } + }; + _reload(pluginDir); +}; +addUnloadHandler(function(){ + // + // save all plugins which have persistent data + // + for (var moduleName in _plugins){ + var pluginData = _plugins[moduleName]; + if (pluginData.persistent) + _save(pluginData.module.store, dataDir.canonicalPath + "/" + moduleName + "-store.json"); + } +}); diff --git a/src/main/javascript/core/_require.js b/src/main/javascript/lib/require.js similarity index 100% rename from src/main/javascript/core/_require.js rename to src/main/javascript/lib/require.js diff --git a/src/main/javascript/core/_scriptcraft.js b/src/main/javascript/lib/scriptcraft.js similarity index 100% rename from src/main/javascript/core/_scriptcraft.js rename to src/main/javascript/lib/scriptcraft.js diff --git a/src/main/javascript/fireworks/fireworks.js b/src/main/javascript/modules/fireworks/fireworks.js similarity index 100% rename from src/main/javascript/fireworks/fireworks.js rename to src/main/javascript/modules/fireworks/fireworks.js diff --git a/src/main/javascript/modules/fireworks/package.json b/src/main/javascript/modules/fireworks/package.json new file mode 100644 index 0000000..99595d8 --- /dev/null +++ b/src/main/javascript/modules/fireworks/package.json @@ -0,0 +1,4 @@ +{ + name: 'fireworks', + main: './fireworks.js' +} diff --git a/src/main/javascript/modules/http/package.json b/src/main/javascript/modules/http/package.json new file mode 100644 index 0000000..a143df2 --- /dev/null +++ b/src/main/javascript/modules/http/package.json @@ -0,0 +1,4 @@ +{ + "name": "http", + "main": "./request.js" +} diff --git a/src/main/javascript/http/request.js b/src/main/javascript/modules/http/request.js similarity index 100% rename from src/main/javascript/http/request.js rename to src/main/javascript/modules/http/request.js diff --git a/src/main/javascript/modules/partial.js b/src/main/javascript/modules/partial.js new file mode 100644 index 0000000..f9c40f7 --- /dev/null +++ b/src/main/javascript/modules/partial.js @@ -0,0 +1,14 @@ +/** +* Create a partial function +* +* Parameters: +* func - base function +* [remaining arguments] - arguments bound to the partial function +*/ +module.exports = function (func /*, 0..n args */) { + var args = Array.prototype.slice.call(arguments, 1); + return function() { + var allArguments = args.concat(Array.prototype.slice.call(arguments)); + return func.apply(this, allArguments); + }; +} diff --git a/src/main/javascript/signs/menu.js b/src/main/javascript/modules/signs/menu.js similarity index 100% rename from src/main/javascript/signs/menu.js rename to src/main/javascript/modules/signs/menu.js diff --git a/src/main/javascript/modules/signs/package.json b/src/main/javascript/modules/signs/package.json new file mode 100644 index 0000000..e59258e --- /dev/null +++ b/src/main/javascript/modules/signs/package.json @@ -0,0 +1,4 @@ +{ + name: 'signs', + main: './menu.js' +} diff --git a/src/main/javascript/modules/underscore/package.json b/src/main/javascript/modules/underscore/package.json new file mode 100644 index 0000000..8c4e2b2 --- /dev/null +++ b/src/main/javascript/modules/underscore/package.json @@ -0,0 +1,4 @@ +{ + name: 'underscore', + main: './underscore.js' +} diff --git a/src/main/javascript/modules/underscore/underscore.js b/src/main/javascript/modules/underscore/underscore.js new file mode 100644 index 0000000..6440a36 --- /dev/null +++ b/src/main/javascript/modules/underscore/underscore.js @@ -0,0 +1,1314 @@ +// Underscore.js 1.5.2 +// http://underscorejs.org +// (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors +// Underscore may be freely distributed under the MIT license. + +(function() { + + // Baseline setup + // -------------- + + // Establish the root object, `window` in the browser, or `exports` on the server. + var root = this; + + // Save the previous value of the `_` variable. + var previousUnderscore = root._; + + // Establish the object that gets returned to break out of a loop iteration. + var breaker = {}; + + // Save bytes in the minified (but not gzipped) version: + var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; + + //use the faster Date.now if available. + var getTime = (Date.now || function() { + return new Date().getTime(); + }); + + // Create quick reference variables for speed access to core prototypes. + var + push = ArrayProto.push, + slice = ArrayProto.slice, + concat = ArrayProto.concat, + toString = ObjProto.toString, + hasOwnProperty = ObjProto.hasOwnProperty; + + // All **ECMAScript 5** native function implementations that we hope to use + // are declared here. + var + nativeForEach = ArrayProto.forEach, + nativeMap = ArrayProto.map, + nativeReduce = ArrayProto.reduce, + nativeReduceRight = ArrayProto.reduceRight, + nativeFilter = ArrayProto.filter, + nativeEvery = ArrayProto.every, + nativeSome = ArrayProto.some, + nativeIndexOf = ArrayProto.indexOf, + nativeLastIndexOf = ArrayProto.lastIndexOf, + nativeIsArray = Array.isArray, + nativeKeys = Object.keys, + nativeBind = FuncProto.bind; + + // Create a safe reference to the Underscore object for use below. + var _ = function(obj) { + if (obj instanceof _) return obj; + if (!(this instanceof _)) return new _(obj); + this._wrapped = obj; + }; + + // Export the Underscore object for **Node.js**, with + // backwards-compatibility for the old `require()` API. If we're in + // the browser, add `_` as a global object via a string identifier, + // for Closure Compiler "advanced" mode. + if (typeof exports !== 'undefined') { + if (typeof module !== 'undefined' && module.exports) { + exports = module.exports = _; + } + exports._ = _; + } else { + root._ = _; + } + + // Current version. + _.VERSION = '1.5.2'; + + // Collection Functions + // -------------------- + + // The cornerstone, an `each` implementation, aka `forEach`. + // Handles objects with the built-in `forEach`, arrays, and raw objects. + // Delegates to **ECMAScript 5**'s native `forEach` if available. + var each = _.each = _.forEach = function(obj, iterator, context) { + if (obj == null) return; + if (nativeForEach && obj.forEach === nativeForEach) { + obj.forEach(iterator, context); + } else if (obj.length === +obj.length) { + for (var i = 0, length = obj.length; i < length; i++) { + if (iterator.call(context, obj[i], i, obj) === breaker) return; + } + } else { + var keys = _.keys(obj); + for (var i = 0, length = keys.length; i < length; i++) { + if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return; + } + } + }; + + // Return the results of applying the iterator to each element. + // Delegates to **ECMAScript 5**'s native `map` if available. + _.map = _.collect = function(obj, iterator, context) { + var results = []; + if (obj == null) return results; + if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); + each(obj, function(value, index, list) { + results.push(iterator.call(context, value, index, list)); + }); + return results; + }; + + var reduceError = 'Reduce of empty array with no initial value'; + + // **Reduce** builds up a single result from a list of values, aka `inject`, + // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. + _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { + var initial = arguments.length > 2; + if (obj == null) obj = []; + if (nativeReduce && obj.reduce === nativeReduce) { + if (context) iterator = _.bind(iterator, context); + return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); + } + each(obj, function(value, index, list) { + if (!initial) { + memo = value; + initial = true; + } else { + memo = iterator.call(context, memo, value, index, list); + } + }); + if (!initial) throw new TypeError(reduceError); + return memo; + }; + + // The right-associative version of reduce, also known as `foldr`. + // Delegates to **ECMAScript 5**'s native `reduceRight` if available. + _.reduceRight = _.foldr = function(obj, iterator, memo, context) { + var initial = arguments.length > 2; + if (obj == null) obj = []; + if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { + if (context) iterator = _.bind(iterator, context); + return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); + } + var length = obj.length; + if (length !== +length) { + var keys = _.keys(obj); + length = keys.length; + } + each(obj, function(value, index, list) { + index = keys ? keys[--length] : --length; + if (!initial) { + memo = obj[index]; + initial = true; + } else { + memo = iterator.call(context, memo, obj[index], index, list); + } + }); + if (!initial) throw new TypeError(reduceError); + return memo; + }; + + // Return the first value which passes a truth test. Aliased as `detect`. + _.find = _.detect = function(obj, iterator, context) { + var result; + any(obj, function(value, index, list) { + if (iterator.call(context, value, index, list)) { + result = value; + return true; + } + }); + return result; + }; + + // Return all the elements that pass a truth test. + // Delegates to **ECMAScript 5**'s native `filter` if available. + // Aliased as `select`. + _.filter = _.select = function(obj, iterator, context) { + var results = []; + if (obj == null) return results; + if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); + each(obj, function(value, index, list) { + if (iterator.call(context, value, index, list)) results.push(value); + }); + return results; + }; + + // Return all the elements for which a truth test fails. + _.reject = function(obj, iterator, context) { + return _.filter(obj, function(value, index, list) { + return !iterator.call(context, value, index, list); + }, context); + }; + + // Determine whether all of the elements match a truth test. + // Delegates to **ECMAScript 5**'s native `every` if available. + // Aliased as `all`. + _.every = _.all = function(obj, iterator, context) { + iterator || (iterator = _.identity); + var result = true; + if (obj == null) return result; + if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); + each(obj, function(value, index, list) { + if (!(result = result && iterator.call(context, value, index, list))) return breaker; + }); + return !!result; + }; + + // Determine if at least one element in the object matches a truth test. + // Delegates to **ECMAScript 5**'s native `some` if available. + // Aliased as `any`. + var any = _.some = _.any = function(obj, iterator, context) { + iterator || (iterator = _.identity); + var result = false; + if (obj == null) return result; + if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); + each(obj, function(value, index, list) { + if (result || (result = iterator.call(context, value, index, list))) return breaker; + }); + return !!result; + }; + + // Determine if the array or object contains a given value (using `===`). + // Aliased as `include`. + _.contains = _.include = function(obj, target) { + if (obj == null) return false; + if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; + return any(obj, function(value) { + return value === target; + }); + }; + + // Invoke a method (with arguments) on every item in a collection. + _.invoke = function(obj, method) { + var args = slice.call(arguments, 2); + var isFunc = _.isFunction(method); + return _.map(obj, function(value) { + return (isFunc ? method : value[method]).apply(value, args); + }); + }; + + // Convenience version of a common use case of `map`: fetching a property. + _.pluck = function(obj, key) { + return _.map(obj, _.property(key)); + }; + + // Convenience version of a common use case of `filter`: selecting only objects + // containing specific `key:value` pairs. + _.where = function(obj, attrs, first) { + if (_.isEmpty(attrs)) return first ? void 0 : []; + return _[first ? 'find' : 'filter'](obj, function(value) { + for (var key in attrs) { + if (attrs[key] !== value[key]) return false; + } + return true; + }); + }; + + // Convenience version of a common use case of `find`: getting the first object + // containing specific `key:value` pairs. + _.findWhere = function(obj, attrs) { + return _.where(obj, attrs, true); + }; + + // Return the maximum element or (element-based computation). + // Can't optimize arrays of integers longer than 65,535 elements. + // See [WebKit Bug 80797](https://bugs.webkit.org/show_bug.cgi?id=80797) + _.max = function(obj, iterator, context) { + if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { + return Math.max.apply(Math, obj); + } + if (!iterator && _.isEmpty(obj)) return -Infinity; + var result = {computed : -Infinity, value: -Infinity}; + each(obj, function(value, index, list) { + var computed = iterator ? iterator.call(context, value, index, list) : value; + computed > result.computed && (result = {value : value, computed : computed}); + }); + return result.value; + }; + + // Return the minimum element (or element-based computation). + _.min = function(obj, iterator, context) { + if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { + return Math.min.apply(Math, obj); + } + if (!iterator && _.isEmpty(obj)) return Infinity; + var result = {computed : Infinity, value: Infinity}; + each(obj, function(value, index, list) { + var computed = iterator ? iterator.call(context, value, index, list) : value; + computed < result.computed && (result = {value : value, computed : computed}); + }); + return result.value; + }; + + // Shuffle an array, using the modern version of the + // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle). + _.shuffle = function(obj) { + var rand; + var index = 0; + var shuffled = []; + each(obj, function(value) { + rand = _.random(index++); + shuffled[index - 1] = shuffled[rand]; + shuffled[rand] = value; + }); + return shuffled; + }; + + // Sample **n** random values from a collection. + // If **n** is not specified, returns a single random element. + // The internal `guard` argument allows it to work with `map`. + _.sample = function(obj, n, guard) { + if (n == null || guard) { + if (obj.length !== +obj.length) obj = _.values(obj); + return obj[_.random(obj.length - 1)]; + } + return _.shuffle(obj).slice(0, Math.max(0, n)); + }; + + // An internal function to generate lookup iterators. + var lookupIterator = function(value) { + if (value == null) return _.identity; + if (_.isFunction(value)) return value; + return _.property(value); + }; + + // Sort the object's values by a criterion produced by an iterator. + _.sortBy = function(obj, iterator, context) { + iterator = lookupIterator(iterator); + return _.pluck(_.map(obj, function(value, index, list) { + return { + value: value, + index: index, + criteria: iterator.call(context, value, index, list) + }; + }).sort(function(left, right) { + var a = left.criteria; + var b = right.criteria; + if (a !== b) { + if (a > b || a === void 0) return 1; + if (a < b || b === void 0) return -1; + } + return left.index - right.index; + }), 'value'); + }; + + // An internal function used for aggregate "group by" operations. + var group = function(behavior) { + return function(obj, iterator, context) { + var result = {}; + iterator = lookupIterator(iterator); + each(obj, function(value, index) { + var key = iterator.call(context, value, index, obj); + behavior(result, key, value); + }); + return result; + }; + }; + + // Groups the object's values by a criterion. Pass either a string attribute + // to group by, or a function that returns the criterion. + _.groupBy = group(function(result, key, value) { + (_.has(result, key) ? result[key] : (result[key] = [])).push(value); + }); + + // Indexes the object's values by a criterion, similar to `groupBy`, but for + // when you know that your index values will be unique. + _.indexBy = group(function(result, key, value) { + result[key] = value; + }); + + // Counts instances of an object that group by a certain criterion. Pass + // either a string attribute to count by, or a function that returns the + // criterion. + _.countBy = group(function(result, key) { + _.has(result, key) ? result[key]++ : result[key] = 1; + }); + + // Use a comparator function to figure out the smallest index at which + // an object should be inserted so as to maintain order. Uses binary search. + _.sortedIndex = function(array, obj, iterator, context) { + iterator = lookupIterator(iterator); + var value = iterator.call(context, obj); + var low = 0, high = array.length; + while (low < high) { + var mid = (low + high) >>> 1; + iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid; + } + return low; + }; + + // Safely create a real, live array from anything iterable. + _.toArray = function(obj) { + if (!obj) return []; + if (_.isArray(obj)) return slice.call(obj); + if (obj.length === +obj.length) return _.map(obj, _.identity); + return _.values(obj); + }; + + // Return the number of elements in an object. + _.size = function(obj) { + if (obj == null) return 0; + return (obj.length === +obj.length) ? obj.length : _.keys(obj).length; + }; + + // Array Functions + // --------------- + + // Get the first element of an array. Passing **n** will return the first N + // values in the array. Aliased as `head` and `take`. The **guard** check + // allows it to work with `_.map`. + _.first = _.head = _.take = function(array, n, guard) { + if (array == null) return void 0; + if ((n == null) || guard) return array[0]; + if (n < 0) return []; + return slice.call(array, 0, n); + }; + + // Returns everything but the last entry of the array. Especially useful on + // the arguments object. Passing **n** will return all the values in + // the array, excluding the last N. The **guard** check allows it to work with + // `_.map`. + _.initial = function(array, n, guard) { + return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); + }; + + // Get the last element of an array. Passing **n** will return the last N + // values in the array. The **guard** check allows it to work with `_.map`. + _.last = function(array, n, guard) { + if (array == null) return void 0; + if ((n == null) || guard) return array[array.length - 1]; + return slice.call(array, Math.max(array.length - n, 0)); + }; + + // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. + // Especially useful on the arguments object. Passing an **n** will return + // the rest N values in the array. The **guard** + // check allows it to work with `_.map`. + _.rest = _.tail = _.drop = function(array, n, guard) { + return slice.call(array, (n == null) || guard ? 1 : n); + }; + + // Trim out all falsy values from an array. + _.compact = function(array) { + return _.filter(array, _.identity); + }; + + // Internal implementation of a recursive `flatten` function. + var flatten = function(input, shallow, output) { + if (shallow && _.every(input, _.isArray)) { + return concat.apply(output, input); + } + each(input, function(value) { + if (_.isArray(value) || _.isArguments(value)) { + shallow ? push.apply(output, value) : flatten(value, shallow, output); + } else { + output.push(value); + } + }); + return output; + }; + + // Flatten out an array, either recursively (by default), or just one level. + _.flatten = function(array, shallow) { + return flatten(array, shallow, []); + }; + + // Return a version of the array that does not contain the specified value(s). + _.without = function(array) { + return _.difference(array, slice.call(arguments, 1)); + }; + + // Produce a duplicate-free version of the array. If the array has already + // been sorted, you have the option of using a faster algorithm. + // Aliased as `unique`. + _.uniq = _.unique = function(array, isSorted, iterator, context) { + if (_.isFunction(isSorted)) { + context = iterator; + iterator = isSorted; + isSorted = false; + } + var initial = iterator ? _.map(array, iterator, context) : array; + var results = []; + var seen = []; + each(initial, function(value, index) { + if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) { + seen.push(value); + results.push(array[index]); + } + }); + return results; + }; + + // Produce an array that contains the union: each distinct element from all of + // the passed-in arrays. + _.union = function() { + return _.uniq(_.flatten(arguments, true)); + }; + + // Produce an array that contains every item shared between all the + // passed-in arrays. + _.intersection = function(array) { + var rest = slice.call(arguments, 1); + return _.filter(_.uniq(array), function(item) { + return _.every(rest, function(other) { + return _.indexOf(other, item) >= 0; + }); + }); + }; + + // Take the difference between one array and a number of other arrays. + // Only the elements present in just the first array will remain. + _.difference = function(array) { + var rest = concat.apply(ArrayProto, slice.call(arguments, 1)); + return _.filter(array, function(value){ return !_.contains(rest, value); }); + }; + + // Zip together multiple lists into a single array -- elements that share + // an index go together. + _.zip = function() { + var length = _.max(_.pluck(arguments, "length").concat(0)); + var results = new Array(length); + for (var i = 0; i < length; i++) { + results[i] = _.pluck(arguments, '' + i); + } + return results; + }; + + // Converts lists into objects. Pass either a single array of `[key, value]` + // pairs, or two parallel arrays of the same length -- one of keys, and one of + // the corresponding values. + _.object = function(list, values) { + if (list == null) return {}; + var result = {}; + for (var i = 0, length = list.length; i < length; i++) { + if (values) { + result[list[i]] = values[i]; + } else { + result[list[i][0]] = list[i][1]; + } + } + return result; + }; + + // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), + // we need this function. Return the position of the first occurrence of an + // item in an array, or -1 if the item is not included in the array. + // Delegates to **ECMAScript 5**'s native `indexOf` if available. + // If the array is large and already in sort order, pass `true` + // for **isSorted** to use binary search. + _.indexOf = function(array, item, isSorted) { + if (array == null) return -1; + var i = 0, length = array.length; + if (isSorted) { + if (typeof isSorted == 'number') { + i = (isSorted < 0 ? Math.max(0, length + isSorted) : isSorted); + } else { + i = _.sortedIndex(array, item); + return array[i] === item ? i : -1; + } + } + if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted); + for (; i < length; i++) if (array[i] === item) return i; + return -1; + }; + + // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. + _.lastIndexOf = function(array, item, from) { + if (array == null) return -1; + var hasIndex = from != null; + if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) { + return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item); + } + var i = (hasIndex ? from : array.length); + while (i--) if (array[i] === item) return i; + return -1; + }; + + // Generate an integer Array containing an arithmetic progression. A port of + // the native Python `range()` function. See + // [the Python documentation](http://docs.python.org/library/functions.html#range). + _.range = function(start, stop, step) { + if (arguments.length <= 1) { + stop = start || 0; + start = 0; + } + step = arguments[2] || 1; + + var length = Math.max(Math.ceil((stop - start) / step), 0); + var idx = 0; + var range = new Array(length); + + while(idx < length) { + range[idx++] = start; + start += step; + } + + return range; + }; + + // Function (ahem) Functions + // ------------------ + + // Reusable constructor function for prototype setting. + var ctor = function(){}; + + // Create a function bound to a given object (assigning `this`, and arguments, + // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if + // available. + _.bind = function(func, context) { + var args, bound; + if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); + if (!_.isFunction(func)) throw new TypeError; + args = slice.call(arguments, 2); + return bound = function() { + if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); + ctor.prototype = func.prototype; + var self = new ctor; + ctor.prototype = null; + var result = func.apply(self, args.concat(slice.call(arguments))); + if (Object(result) === result) return result; + return self; + }; + }; + + // Partially apply a function by creating a version that has had some of its + // arguments pre-filled, without changing its dynamic `this` context. + _.partial = function(func) { + var args = slice.call(arguments, 1); + return function() { + return func.apply(this, args.concat(slice.call(arguments))); + }; + }; + + // Bind a number of an object's methods to that object. Remaining arguments + // are the method names to be bound. Useful for ensuring that all callbacks + // defined on an object belong to it. + _.bindAll = function(obj) { + var funcs = slice.call(arguments, 1); + if (funcs.length === 0) throw new Error("bindAll must be passed function names"); + each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); + return obj; + }; + + // Memoize an expensive function by storing its results. + _.memoize = function(func, hasher) { + var memo = {}; + hasher || (hasher = _.identity); + return function() { + var key = hasher.apply(this, arguments); + return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); + }; + }; + + // Delays a function for the given number of milliseconds, and then calls + // it with the arguments supplied. + _.delay = function(func, wait) { + var args = slice.call(arguments, 2); + return setTimeout(function(){ return func.apply(null, args); }, wait); + }; + + // Defers a function, scheduling it to run after the current call stack has + // cleared. + _.defer = function(func) { + return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); + }; + + // Returns a function, that, when invoked, will only be triggered at most once + // during a given window of time. Normally, the throttled function will run + // as much as it can, without ever going more than once per `wait` duration; + // but if you'd like to disable the execution on the leading edge, pass + // `{leading: false}`. To disable execution on the trailing edge, ditto. + _.throttle = function(func, wait, options) { + var context, args, result; + var timeout = null; + var previous = 0; + options || (options = {}); + var later = function() { + previous = options.leading === false ? 0 : getTime(); + timeout = null; + result = func.apply(context, args); + context = args = null; + }; + return function() { + var now = getTime(); + if (!previous && options.leading === false) previous = now; + var remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0) { + clearTimeout(timeout); + timeout = null; + previous = now; + result = func.apply(context, args); + context = args = null; + } else if (!timeout && options.trailing !== false) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }; + + // Returns a function, that, as long as it continues to be invoked, will not + // be triggered. The function will be called after it stops being called for + // N milliseconds. If `immediate` is passed, trigger the function on the + // leading edge, instead of the trailing. + _.debounce = function(func, wait, immediate) { + var timeout, args, context, timestamp, result; + return function() { + context = this; + args = arguments; + timestamp = getTime(); + var later = function() { + var last = getTime() - timestamp; + if (last < wait) { + timeout = setTimeout(later, wait - last); + } else { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + context = args = null; + } + } + }; + var callNow = immediate && !timeout; + if (!timeout) { + timeout = setTimeout(later, wait); + } + if (callNow) { + result = func.apply(context, args); + context = args = null; + } + + return result; + }; + }; + + // Returns a function that will be executed at most one time, no matter how + // often you call it. Useful for lazy initialization. + _.once = function(func) { + var ran = false, memo; + return function() { + if (ran) return memo; + ran = true; + memo = func.apply(this, arguments); + func = null; + return memo; + }; + }; + + // Returns the first function passed as an argument to the second, + // allowing you to adjust arguments, run code before and after, and + // conditionally execute the original function. + _.wrap = function(func, wrapper) { + return _.partial(wrapper, func); + }; + + // Returns a function that is the composition of a list of functions, each + // consuming the return value of the function that follows. + _.compose = function() { + var funcs = arguments; + return function() { + var args = arguments; + for (var i = funcs.length - 1; i >= 0; i--) { + args = [funcs[i].apply(this, args)]; + } + return args[0]; + }; + }; + + // Returns a function that will only be executed after being called N times. + _.after = function(times, func) { + return function() { + if (--times < 1) { + return func.apply(this, arguments); + } + }; + }; + + // Object Functions + // ---------------- + + // Retrieve the names of an object's properties. + // Delegates to **ECMAScript 5**'s native `Object.keys` + _.keys = nativeKeys || function(obj) { + if (obj !== Object(obj)) throw new TypeError('Invalid object'); + var keys = []; + for (var key in obj) if (_.has(obj, key)) keys.push(key); + return keys; + }; + + // Retrieve the values of an object's properties. + _.values = function(obj) { + var keys = _.keys(obj); + var length = keys.length; + var values = new Array(length); + for (var i = 0; i < length; i++) { + values[i] = obj[keys[i]]; + } + return values; + }; + + // Convert an object into a list of `[key, value]` pairs. + _.pairs = function(obj) { + var keys = _.keys(obj); + var length = keys.length; + var pairs = new Array(length); + for (var i = 0; i < length; i++) { + pairs[i] = [keys[i], obj[keys[i]]]; + } + return pairs; + }; + + // Invert the keys and values of an object. The values must be serializable. + _.invert = function(obj) { + var result = {}; + var keys = _.keys(obj); + for (var i = 0, length = keys.length; i < length; i++) { + result[obj[keys[i]]] = keys[i]; + } + return result; + }; + + // Return a sorted list of the function names available on the object. + // Aliased as `methods` + _.functions = _.methods = function(obj) { + var names = []; + for (var key in obj) { + if (_.isFunction(obj[key])) names.push(key); + } + return names.sort(); + }; + + // Extend a given object with all the properties in passed-in object(s). + _.extend = function(obj) { + each(slice.call(arguments, 1), function(source) { + if (source) { + for (var prop in source) { + obj[prop] = source[prop]; + } + } + }); + return obj; + }; + + // Return a copy of the object only containing the whitelisted properties. + _.pick = function(obj) { + var copy = {}; + var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); + each(keys, function(key) { + if (key in obj) copy[key] = obj[key]; + }); + return copy; + }; + + // Return a copy of the object without the blacklisted properties. + _.omit = function(obj) { + var copy = {}; + var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); + for (var key in obj) { + if (!_.contains(keys, key)) copy[key] = obj[key]; + } + return copy; + }; + + // Fill in a given object with default properties. + _.defaults = function(obj) { + each(slice.call(arguments, 1), function(source) { + if (source) { + for (var prop in source) { + if (obj[prop] === void 0) obj[prop] = source[prop]; + } + } + }); + return obj; + }; + + // Create a (shallow-cloned) duplicate of an object. + _.clone = function(obj) { + if (!_.isObject(obj)) return obj; + return _.isArray(obj) ? obj.slice() : _.extend({}, obj); + }; + + // Invokes interceptor with the obj, and then returns obj. + // The primary purpose of this method is to "tap into" a method chain, in + // order to perform operations on intermediate results within the chain. + _.tap = function(obj, interceptor) { + interceptor(obj); + return obj; + }; + + // Internal recursive comparison function for `isEqual`. + var eq = function(a, b, aStack, bStack) { + // Identical objects are equal. `0 === -0`, but they aren't identical. + // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). + if (a === b) return a !== 0 || 1 / a == 1 / b; + // A strict comparison is necessary because `null == undefined`. + if (a == null || b == null) return a === b; + // Unwrap any wrapped objects. + if (a instanceof _) a = a._wrapped; + if (b instanceof _) b = b._wrapped; + // Compare `[[Class]]` names. + var className = toString.call(a); + if (className != toString.call(b)) return false; + switch (className) { + // Strings, numbers, dates, and booleans are compared by value. + case '[object String]': + // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is + // equivalent to `new String("5")`. + return a == String(b); + case '[object Number]': + // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for + // other numeric values. + return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); + case '[object Date]': + case '[object Boolean]': + // Coerce dates and booleans to numeric primitive values. Dates are compared by their + // millisecond representations. Note that invalid dates with millisecond representations + // of `NaN` are not equivalent. + return +a == +b; + // RegExps are compared by their source patterns and flags. + case '[object RegExp]': + return a.source == b.source && + a.global == b.global && + a.multiline == b.multiline && + a.ignoreCase == b.ignoreCase; + } + if (typeof a != 'object' || typeof b != 'object') return false; + // Assume equality for cyclic structures. The algorithm for detecting cyclic + // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. + var length = aStack.length; + while (length--) { + // Linear search. Performance is inversely proportional to the number of + // unique nested structures. + if (aStack[length] == a) return bStack[length] == b; + } + // Objects with different constructors are not equivalent, but `Object`s + // from different frames are. + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) && + _.isFunction(bCtor) && (bCtor instanceof bCtor)) + && ('constructor' in a && 'constructor' in b)) { + return false; + } + // Add the first object to the stack of traversed objects. + aStack.push(a); + bStack.push(b); + var size = 0, result = true; + // Recursively compare objects and arrays. + if (className == '[object Array]') { + // Compare array lengths to determine if a deep comparison is necessary. + size = a.length; + result = size == b.length; + if (result) { + // Deep compare the contents, ignoring non-numeric properties. + while (size--) { + if (!(result = eq(a[size], b[size], aStack, bStack))) break; + } + } + } else { + // Deep compare objects. + for (var key in a) { + if (_.has(a, key)) { + // Count the expected number of properties. + size++; + // Deep compare each member. + if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break; + } + } + // Ensure that both objects contain the same number of properties. + if (result) { + for (key in b) { + if (_.has(b, key) && !(size--)) break; + } + result = !size; + } + } + // Remove the first object from the stack of traversed objects. + aStack.pop(); + bStack.pop(); + return result; + }; + + // Perform a deep comparison to check if two objects are equal. + _.isEqual = function(a, b) { + return eq(a, b, [], []); + }; + + // Is a given array, string, or object empty? + // An "empty" object has no enumerable own-properties. + _.isEmpty = function(obj) { + if (obj == null) return true; + if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; + for (var key in obj) if (_.has(obj, key)) return false; + return true; + }; + + // Is a given value a DOM element? + _.isElement = function(obj) { + return !!(obj && obj.nodeType === 1); + }; + + // Is a given value an array? + // Delegates to ECMA5's native Array.isArray + _.isArray = nativeIsArray || function(obj) { + return toString.call(obj) == '[object Array]'; + }; + + // Is a given variable an object? + _.isObject = function(obj) { + return obj === Object(obj); + }; + + // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. + each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) { + _['is' + name] = function(obj) { + return toString.call(obj) == '[object ' + name + ']'; + }; + }); + + // Define a fallback version of the method in browsers (ahem, IE), where + // there isn't any inspectable "Arguments" type. + if (!_.isArguments(arguments)) { + _.isArguments = function(obj) { + return !!(obj && _.has(obj, 'callee')); + }; + } + + // Optimize `isFunction` if appropriate. + if (typeof (/./) !== 'function') { + _.isFunction = function(obj) { + return typeof obj === 'function'; + }; + } + + // Is a given object a finite number? + _.isFinite = function(obj) { + return isFinite(obj) && !isNaN(parseFloat(obj)); + }; + + // Is the given value `NaN`? (NaN is the only number which does not equal itself). + _.isNaN = function(obj) { + return _.isNumber(obj) && obj != +obj; + }; + + // Is a given value a boolean? + _.isBoolean = function(obj) { + return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; + }; + + // Is a given value equal to null? + _.isNull = function(obj) { + return obj === null; + }; + + // Is a given variable undefined? + _.isUndefined = function(obj) { + return obj === void 0; + }; + + // Shortcut function for checking if an object has a given property directly + // on itself (in other words, not on a prototype). + _.has = function(obj, key) { + return hasOwnProperty.call(obj, key); + }; + + // Utility Functions + // ----------------- + + // Run Underscore.js in *noConflict* mode, returning the `_` variable to its + // previous owner. Returns a reference to the Underscore object. + _.noConflict = function() { + root._ = previousUnderscore; + return this; + }; + + // Keep the identity function around for default iterators. + _.identity = function(value) { + return value; + }; + + _.constant = function(value) { + return function () { + return value; + }; + }; + + _.property = function(key) { + return function(obj) { + return obj[key]; + }; + }; + + // Run a function **n** times. + _.times = function(n, iterator, context) { + var accum = Array(Math.max(0, n)); + for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i); + return accum; + }; + + // Return a random integer between min and max (inclusive). + _.random = function(min, max) { + if (max == null) { + max = min; + min = 0; + } + return min + Math.floor(Math.random() * (max - min + 1)); + }; + + // List of HTML entities for escaping. + var entityMap = { + escape: { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + } + }; + entityMap.unescape = _.invert(entityMap.escape); + + // Regexes containing the keys and values listed immediately above. + var entityRegexes = { + escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'), + unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g') + }; + + // Functions for escaping and unescaping strings to/from HTML interpolation. + _.each(['escape', 'unescape'], function(method) { + _[method] = function(string) { + if (string == null) return ''; + return ('' + string).replace(entityRegexes[method], function(match) { + return entityMap[method][match]; + }); + }; + }); + + // If the value of the named `property` is a function then invoke it with the + // `object` as context; otherwise, return it. + _.result = function(object, property) { + if (object == null) return void 0; + var value = object[property]; + return _.isFunction(value) ? value.call(object) : value; + }; + + // Add your own custom functions to the Underscore object. + _.mixin = function(obj) { + each(_.functions(obj), function(name) { + var func = _[name] = obj[name]; + _.prototype[name] = function() { + var args = [this._wrapped]; + push.apply(args, arguments); + return result.call(this, func.apply(_, args)); + }; + }); + }; + + // Generate a unique integer id (unique within the entire client session). + // Useful for temporary DOM ids. + var idCounter = 0; + _.uniqueId = function(prefix) { + var id = ++idCounter + ''; + return prefix ? prefix + id : id; + }; + + // By default, Underscore uses ERB-style template delimiters, change the + // following template settings to use alternative delimiters. + _.templateSettings = { + evaluate : /<%([\s\S]+?)%>/g, + interpolate : /<%=([\s\S]+?)%>/g, + escape : /<%-([\s\S]+?)%>/g + }; + + // When customizing `templateSettings`, if you don't want to define an + // interpolation, evaluation or escaping regex, we need one that is + // guaranteed not to match. + var noMatch = /(.)^/; + + // Certain characters need to be escaped so that they can be put into a + // string literal. + var escapes = { + "'": "'", + '\\': '\\', + '\r': 'r', + '\n': 'n', + '\t': 't', + '\u2028': 'u2028', + '\u2029': 'u2029' + }; + + var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; + + // JavaScript micro-templating, similar to John Resig's implementation. + // Underscore templating handles arbitrary delimiters, preserves whitespace, + // and correctly escapes quotes within interpolated code. + _.template = function(text, data, settings) { + var render; + settings = _.defaults({}, settings, _.templateSettings); + + // Combine delimiters into one regular expression via alternation. + var matcher = new RegExp([ + (settings.escape || noMatch).source, + (settings.interpolate || noMatch).source, + (settings.evaluate || noMatch).source + ].join('|') + '|$', 'g'); + + // Compile the template source, escaping string literals appropriately. + var index = 0; + var source = "__p+='"; + text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { + source += text.slice(index, offset) + .replace(escaper, function(match) { return '\\' + escapes[match]; }); + + if (escape) { + source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; + } + if (interpolate) { + source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; + } + if (evaluate) { + source += "';\n" + evaluate + "\n__p+='"; + } + index = offset + match.length; + return match; + }); + source += "';\n"; + + // If a variable is not specified, place data values in local scope. + if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; + + source = "var __t,__p='',__j=Array.prototype.join," + + "print=function(){__p+=__j.call(arguments,'');};\n" + + source + "return __p;\n"; + + try { + render = new Function(settings.variable || 'obj', '_', source); + } catch (e) { + e.source = source; + throw e; + } + + if (data) return render(data, _); + var template = function(data) { + return render.call(this, data, _); + }; + + // Provide the compiled function source as a convenience for precompilation. + template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; + + return template; + }; + + // Add a "chain" function, which will delegate to the wrapper. + _.chain = function(obj) { + return _(obj).chain(); + }; + + // OOP + // --------------- + // If Underscore is called as a function, it returns a wrapped object that + // can be used OO-style. This wrapper holds altered versions of all the + // underscore functions. Wrapped objects may be chained. + + // Helper function to continue chaining intermediate results. + var result = function(obj) { + return this._chain ? _(obj).chain() : obj; + }; + + // Add all of the Underscore functions to the wrapper object. + _.mixin(_); + + // Add all mutator Array functions to the wrapper. + each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { + var method = ArrayProto[name]; + _.prototype[name] = function() { + var obj = this._wrapped; + method.apply(obj, arguments); + if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0]; + return result.call(this, obj); + }; + }); + + // Add all accessor Array functions to the wrapper. + each(['concat', 'join', 'slice'], function(name) { + var method = ArrayProto[name]; + _.prototype[name] = function() { + return result.call(this, method.apply(this._wrapped, arguments)); + }; + }); + + _.extend(_.prototype, { + + // Start chaining a wrapped Underscore object. + chain: function() { + this._chain = true; + return this; + }, + + // Extracts the result from a wrapped and chained object. + value: function() { + return this._wrapped; + } + + }); + + // AMD registration happens at the end for compatibility with AMD loaders + // that may not enforce next-turn semantics on modules. Even though general + // practice for AMD registration is to be anonymous, underscore registers + // as a named module because, like jQuery, it is a base library that is + // popular enough to be bundled in a third party lib, but not be part of + // an AMD load request. Those cases could generate an error when an + // anonymous define() is called outside of a loader request. + if (typeof define === 'function' && define.amd) { + define('underscore', [], function() { + return _; + }); + } +}).call(this); diff --git a/src/main/javascript/modules/utils/package.json b/src/main/javascript/modules/utils/package.json new file mode 100644 index 0000000..d80809d --- /dev/null +++ b/src/main/javascript/modules/utils/package.json @@ -0,0 +1,4 @@ +{ + "name": 'utils', + "main": './utils.js' +} diff --git a/src/main/javascript/utils/text.js b/src/main/javascript/modules/utils/string-exts.js similarity index 100% rename from src/main/javascript/utils/text.js rename to src/main/javascript/modules/utils/string-exts.js diff --git a/src/main/javascript/utils/utils.js b/src/main/javascript/modules/utils/utils.js similarity index 100% rename from src/main/javascript/utils/utils.js rename to src/main/javascript/modules/utils/utils.js diff --git a/src/main/javascript/alias/alias.js b/src/main/javascript/plugins/alias/alias.js similarity index 100% rename from src/main/javascript/alias/alias.js rename to src/main/javascript/plugins/alias/alias.js diff --git a/src/main/javascript/arrows/arrows.js b/src/main/javascript/plugins/arrows.js similarity index 100% rename from src/main/javascript/arrows/arrows.js rename to src/main/javascript/plugins/arrows.js diff --git a/src/main/javascript/chat/color.js b/src/main/javascript/plugins/chat/color.js similarity index 100% rename from src/main/javascript/chat/color.js rename to src/main/javascript/plugins/chat/color.js diff --git a/src/main/javascript/classroom/classroom.js b/src/main/javascript/plugins/classroom/classroom.js similarity index 100% rename from src/main/javascript/classroom/classroom.js rename to src/main/javascript/plugins/classroom/classroom.js diff --git a/src/main/javascript/plugins/drone/blocks.js b/src/main/javascript/plugins/drone/blocks.js new file mode 100644 index 0000000..19e0c0c --- /dev/null +++ b/src/main/javascript/plugins/drone/blocks.js @@ -0,0 +1,275 @@ +/************************************************************************ +Blocks Module +============= +You hate having to lookup [Data Values][dv] when you use ScriptCraft's Drone() functions. So do I. +So I created this blocks object which is a helper object for use in construction. + +Examples +-------- + + box( blocks.oak ); // creates a single oak wood block + box( blocks.sand, 3, 2, 1 ); // creates a block of sand 3 wide x 2 high x 1 long + box( blocks.wool.green, 2 ); // creates a block of green wool 2 blocks wide + +Color aliased properties that were a direct descendant of the blocks object are no longer used to avoid confusion with carpet and stained clay blocks. In addition, there's a convenience array `blocks.rainbow` which is an array of the 7 colors of the rainbow (or closest approximations). + +***/ +var blocks = { + air: 0, + stone: 1, + grass: 2, + dirt: 3, + cobblestone: 4, + oak: 5, + spruce: '5:1', + birch: '5:2', + jungle: '5:3', + sapling: { + oak: 6, + spruce: '6:1', + birch: '62:2', + jungle: '6:3' + }, + bedrock: 7, + water: 8, + water_still: 9, + lava: 10, + lava_still: 11, + sand: 12, + gravel: 13, + gold_ore: 14, + iron_ore: 15, + coal_ore: 16, + wood: 17, + leaves: 18, + sponge: 19, + glass: 20, + lapis_lazuli_ore: 21, + lapis_lazuli_block: 22, + dispenser: 23, + sandstone: 24, + note: 25, + bed: 26, + powered_rail: 27, + detector_rail: 28, + sticky_piston: 29, + cobweb: 30, + grass_tall: 31, + dead_bush: 32, + piston: 33, + piston_extn: 34, + wool: { + white: 35 // All other colors added below + }, + dandelion: 37, + flower_yellow: 37, + rose: 38, + flower_red: 38, + mushroom_brown: 39, + mushroom_red: 40, + gold: 41, + iron: 42, + tnt: 46, + bookshelf: 47, + moss_stone: 48, + obsidian: 49, + torch: 50, + fire: 51, + monster_spawner: 52, + stairs: { + oak: 53, + cobblestone: 67, + brick: 108, + stone: 109, + nether: 114, + sandstone: 128, + spruce: 134, + birch: 135, + jungle: 136, + quartz: 156 + }, + chest: 54, + redstone_wire: 55, + diamond_ore: 56, + diamond: 57, + crafting_table: 58, + wheat_seeds: 59, + farmland: 60, + furnace: 61, + furnace_burning: 62, + sign_post: 63, + door_wood: 64, + ladder: 65, + rail: 66, + sign: 68, + lever: 69, + pressure_plate_stone: 70, + door_iron: 71, + pressure_plate_wood: 72, + redstone_ore: 73, + redstone_ore_glowing: 74, + torch_redstone: 75, + torch_redstone_active: 76, + stone_button: 77, + ice: 79, + snow: 80, + cactus: 81, + clay: 82, + sugar_cane: 83, + jukebox: 84, + fence: 85, + pumpkin: 86, + netherrack: 87, + soulsand: 88, + glowstone: 89, + netherportal: 90, + jackolantern: 91, + cake: 92, + redstone_repeater: 93, + redeston_repeater_active: 94, + chest_locked: 95, + trapdoor: 96, + monster_egg: 97, + brick: { + stone: 98, + mossy: '98:1', + cracked: '98:2', + chiseled: '98:3', + red: 45 + }, + mushroom_brown_huge: 99, + mushroom_red_huge: 100, + iron_bars: 101, + glass_pane: 102, + melon: 103, + pumpkin_stem: 104, + melon_stem: 105, + vines: 106, + fence_gate: 107, + mycelium: 110, + lily_pad: 111, + nether: 112, + nether_fence: 113, + netherwart: 115, + table_enchantment: 116, + brewing_stand: 117, + cauldron: 118, + endportal: 119, + endportal_frame: 120, + endstone: 121, + dragon_egg: 122, + redstone_lamp: 123, + redstone_lamp_active: 124, + slab: { + snow: 78, + stone: 44, + sandstone: '44:1', + wooden: '44:2', + cobblestone: '44:3', + brick: '44:4', + stonebrick: '44:5', + netherbrick:'44:6', + quartz: '44:7', + oak: 126, + spruce: '126:1', + birch: '126:2', + jungle: '126:3', + upper: { + stone: '44:8', + sandstone: '44:9', + wooden: '44:10', + cobblestone: '44:11', + brick: '44:12', + stonebrick: '44:13', + netherbrick:'44:14', + quartz: '44:15', + oak: '126:8', + spruce: '126:9', + birch: '126:10', + jungle: '126:11', + } + }, + cocoa: 127, + emerald_ore: 129, + enderchest: 130, + tripwire_hook: 131, + tripwire: 132, + emerald: 133, + command: 137, + beacon: 138, + cobblestone_wall: 139, + flowerpot: 140, + carrots: 141, + potatoes: 142, + button_wood: 143, + mobhead: 144, + anvil: 145, + chest_trapped: 146, + pressure_plate_weighted_light: 147, + pressure_plate_weighted_heavy: 148, + redstone_comparator: 149, + redstone_comparator_active: 150, + daylight_sensor: 151, + redstone: 152, + netherquartzore: 153, + hopper: 154, + quartz: 155, + rail_activator: 157, + dropper: 158, + stained_clay: { + white: 159 // All other colors added below + }, + hay: 170, + carpet: { + white: 171 // All other colors added below + }, + hardened_clay: 172, + coal_block: 173 +}; + +// Add all available colors to colorized block collections + +var colors = { + orange: ':1', + magenta: ':2', + lightblue: ':3', + yellow: ':4', + lime: ':5', + pink: ':6', + gray: ':7', + lightgray: ':8', + cyan: ':9', + purple: ':10', + blue: ':11', + brown: ':12', + green: ':13', + red: ':14', + black: ':15' +}; +var colorized_blocks = ["wool", "stained_clay", "carpet"]; + +for (var i = 0, len = colorized_blocks.length; i < len; i++) { + var block = colorized_blocks[i], + data_value = blocks[block].white; + + for (var color in colors) { + blocks[block][color] = data_value + colors[color]; + } +}; + +/* + rainbow colors - a convenience + Color aliased properties that were a direct descendant of the blocks + object are no longer used to avoid confusion with carpet and stained + clay blocks. +*/ +blocks.rainbow = [blocks.wool.red, + blocks.wool.orange, + blocks.wool.yellow, + blocks.wool.lime, + blocks.wool.lightblue, + blocks.wool.blue, + blocks.wool.purple]; + + +module.exports = blocks; diff --git a/src/main/javascript/plugins/drone/blocktype.js b/src/main/javascript/plugins/drone/blocktype.js new file mode 100644 index 0000000..1752280 --- /dev/null +++ b/src/main/javascript/plugins/drone/blocktype.js @@ -0,0 +1,376 @@ +var Drone = require('./drone'); +var blocks = require('./blocks'); + +module.exports = Drone; +/************************************************************************ +Drone.blocktype() method +======================== +Creates the text out of blocks. Useful for large-scale in-game signs. + +Parameters +---------- + + * message - The message to create - (use `\n` for newlines) + * foregroundBlock (default: black wool) - The block to use for the foreground + * backgroundBlock (default: none) - The block to use for the background + +Example +------- +To create a 2-line high message using glowstone... + + blocktype("Hello\nWorld",blocks.glowstone); + +![blocktype example][imgbt1] + +[imgbt1]: img/blocktype1.png + +***/ + +var bitmaps = { + raw: { + '0':' ### '+ + ' # # '+ + ' # # '+ + ' # # '+ + ' ### ', + + '1':' # '+ + ' ## '+ + ' # '+ + ' # '+ + ' ### ', + + '2':' ### '+ + ' # '+ + ' ### '+ + ' # '+ + ' ### ', + + '3':' ### '+ + ' # '+ + ' ## '+ + ' # '+ + ' ### ', + + '4':' # '+ + ' ## '+ + ' # # '+ + ' ### '+ + ' # ', + + '5':' ### '+ + ' # '+ + ' ### '+ + ' # '+ + ' ### ', + + '6':' ### '+ + ' # '+ + ' ### '+ + ' # # '+ + ' ### ', + + '7':' ### '+ + ' # '+ + ' # '+ + ' # '+ + ' # ', + + '8':' ### '+ + ' # # '+ + ' ### '+ + ' # # '+ + ' ### ', + + '9':' ### '+ + ' # # '+ + ' ### '+ + ' # '+ + ' ### ', + + 'a':' ### '+ + ' # # '+ + ' ### '+ + ' # # '+ + ' # # ', + + 'b':' ## '+ + ' # # '+ + ' ## '+ + ' # # '+ + ' ## ', + + 'c':' ## '+ + ' # '+ + ' # '+ + ' # '+ + ' ## ', + + 'd':' ## '+ + ' # # '+ + ' # # '+ + ' # # '+ + ' ## ', + + 'e':' ### '+ + ' # '+ + ' ## '+ + ' # '+ + ' ### ', + + 'f':' ### '+ + ' # '+ + ' ## '+ + ' # '+ + ' # ', + + 'g':' ### '+ + ' # '+ + ' # '+ + ' # # '+ + ' ### ', + + 'h':' # # '+ + ' # # '+ + ' ### '+ + ' # # '+ + ' # # ', + + 'i':' ### '+ + ' # '+ + ' # '+ + ' # '+ + ' ### ', + + 'j':' ### '+ + ' # '+ + ' # '+ + ' # '+ + ' # ', + + 'k':' # '+ + ' # # '+ + ' ## '+ + ' # # '+ + ' # # ', + + 'l':' # '+ + ' # '+ + ' # '+ + ' # '+ + ' ### ', + + 'm':' # # '+ + ' ### '+ + ' # # '+ + ' # # '+ + ' # # ', + + 'n':' ## '+ + ' # # '+ + ' # # '+ + ' # # '+ + ' # # ', + + 'o':' # '+ + ' # # '+ + ' # # '+ + ' # # '+ + ' # ', + + 'p':' ### '+ + ' # # '+ + ' ### '+ + ' # '+ + ' # ', + + 'q':' ### '+ + ' # # '+ + ' # # '+ + ' ### '+ + ' # ', + + 'r':' ## '+ + ' # # '+ + ' ## '+ + ' # # '+ + ' # # ', + + 's':' ## '+ + ' # '+ + ' ### '+ + ' # '+ + ' ## ', + + 't':' ### '+ + ' # '+ + ' # '+ + ' # '+ + ' # ', + + 'u':' # # '+ + ' # # '+ + ' # # '+ + ' # # '+ + ' ### ', + + 'v':' # # '+ + ' # # '+ + ' # # '+ + ' # # '+ + ' # ', + + 'w':' # # '+ + ' # # '+ + ' # # '+ + ' ### '+ + ' # # ', + + 'x':' # # '+ + ' # # '+ + ' # '+ + ' # # '+ + ' # # ', + + 'y':' # # '+ + ' # # '+ + ' # # '+ + ' # '+ + ' # ', + + 'z':' ### '+ + ' # '+ + ' # '+ + ' # '+ + ' ### ', + + '!':' # '+ + ' # '+ + ' # '+ + ' '+ + ' # ', + + ':':' '+ + ' # '+ + ' '+ + ' # '+ + ' ', + + ';':' '+ + ' # '+ + ' '+ + ' # '+ + ' # ', + + ',':' '+ + ' '+ + ' '+ + ' # '+ + ' # ', + + '/':' # '+ + ' # '+ + ' # '+ + ' # '+ + ' # ', + + '+':' '+ + ' # '+ + ' ### '+ + ' # '+ + ' ', + + '-':' '+ + ' '+ + ' ### '+ + ' '+ + ' ', + + '.':' '+ + ' '+ + ' '+ + ' '+ + ' # ', + + "'":' # '+ + ' # '+ + ' '+ + ' '+ + ' ', + + ' ':' '+ + ' '+ + ' '+ + ' '+ + ' ' + }, + computed: {} +}; +/* + wph 20130121 compute the width, and x,y coords of pixels ahead of time +*/ +for (var c in bitmaps.raw){ + var bits = bitmaps.raw[c]; + var width = bits.length/5; + var bmInfo = {"width": width,"pixels":[]} + bitmaps.computed[c] = bmInfo; + for (var j = 0; j < bits.length; j++){ + if (bits.charAt(j) != ' '){ + bmInfo.pixels.push([j%width,Math.ceil(j/width)]); + } + } +} + + +// +// message +// string with text to be displayed +// fg +// foreground material. The material the text will be in. +// bg +// background material, optional. The negative space within the bounding box of the text. +// +Drone.extend('blocktype', function(message,fg,bg){ + + this.chkpt('blocktext'); + + if (typeof fg == "undefined") + fg = blocks.wool.black; + + var bmfg = this._getBlockIdAndMeta(fg); + var bmbg = null; + if (typeof bg != "undefined") + bmbg = this._getBlockIdAndMeta(bg); + var lines = message.split("\n"); + var lineCount = lines.length; + for (var h = 0;h < lineCount; h++) { + var line = lines[h]; + line = line.toLowerCase().replace(/[^0-9a-z \.\-\+\/\;\'\:\!]/g,""); + this.up(7*(lineCount-(h+1))); + + for (var i =0;i < line.length; i++) { + var ch = line.charAt(i) + var bits = bitmaps.computed[ch]; + if (typeof bits == "undefined"){ + bits = bitmaps.computed[' ']; + } + var charWidth = bits.width; + if (typeof bg != "undefined") + this.cuboidX(bmbg[0],bmbg[1],charWidth,7,1); + for (var j = 0;j < bits.pixels.length;j++){ + this.chkpt('btbl'); + var x = bits.pixels[j][0]; + var y = bits.pixels[j][1]; + this.up(6-y).right(x).cuboidX(bmfg[0],bmfg[1]); + this.move('btbl'); + } + this.right(charWidth-1); + } + this.move('blocktext'); + } + + return this.move('blocktext'); +}); + + + diff --git a/src/main/javascript/plugins/drone/contrib/castle.js b/src/main/javascript/plugins/drone/contrib/castle.js new file mode 100644 index 0000000..bd634c6 --- /dev/null +++ b/src/main/javascript/plugins/drone/contrib/castle.js @@ -0,0 +1,51 @@ +var Drone = require('../drone'); +module.exports = Drone; +// +// a castle is just a big wide fort with 4 taller forts at each corner +// +Drone.extend('castle', function(side, height) +{ + // + // use sensible default parameter values + // if no parameters are supplied + // + if (typeof side == "undefined") + side = 24; + if (typeof height == "undefined") + height = 10; + if (height < 8 || side < 20) + throw new java.lang.RuntimeException("Castles must be at least 20 wide X 8 tall"); + // + // remember where the drone is so it can return 'home' + // + this.chkpt('castle'); + // + // how big the towers at each corner will be... + // + var towerSide = 10; + var towerHeight = height+4; + + // + // the main castle building will be front and right of the first tower + // + this.fwd(towerSide/2).right(towerSide/2); + // + // the castle is really just a big fort with 4 smaller 'tower' forts at each corner + // + this.fort(side,height); + // + // move back to start position + // + this.move('castle'); + // + // now place 4 towers at each corner (each tower is another fort) + // + for (var corner = 0; corner < 4; corner++) + { + // construct a 'tower' fort + this.fort(towerSide,towerHeight); + // move forward the length of the castle then turn right + this.fwd(side+towerSide-1).turn(); + } + return this.move('castle'); +}); diff --git a/src/main/javascript/plugins/drone/contrib/chessboard.js b/src/main/javascript/plugins/drone/contrib/chessboard.js new file mode 100644 index 0000000..0987e01 --- /dev/null +++ b/src/main/javascript/plugins/drone/contrib/chessboard.js @@ -0,0 +1,37 @@ +var Drone = require('../drone'); +var blocks = require('../blocks'); + +module.exports = Drone; +/** +* Creates a tile pattern of given block types and size +* +* Paramters: +* whiteBlock - blockId used for the traditional white portion of the chessboard +* blackBlock - blockId used for the traditional black portion of the chessboard +* width - width of the chessboard +* height - height of the chessboard +*/ +Drone.extend("chessboard", function(whiteBlock, blackBlock, width, depth) { + this.chkpt('chessboard-start'); + if (typeof whiteBlock == "undefined") + whiteBlock = blocks.wool.white; + if (typeof blackBlock == "undefined") + blackBlock = blocks.wool.black; + if (typeof width == "undefined") + width = 8; + if (typeof depth == "undefined") + depth = width; + + for(var i = 0; i < width; ++i) { + for(var j = 0; j < depth; ++j) { + var block = blackBlock; + if((i+j)%2 == 1) { + block = whiteBlock; + } + this.box(block); + this.right(); + } + this.move('chessboard-start').fwd(i+1); + } + return this.move('chessboard-start'); +}); diff --git a/src/main/javascript/plugins/drone/contrib/cottage.js b/src/main/javascript/plugins/drone/contrib/cottage.js new file mode 100644 index 0000000..194f216 --- /dev/null +++ b/src/main/javascript/plugins/drone/contrib/cottage.js @@ -0,0 +1,79 @@ +var Drone = require('../drone'); +module.exports = Drone; +// +// usage: +// [1] to build a cottage at the player's current location or the cross-hairs location... +// +// /js cottage(); +// +// [2] to build a cottage using an existing drone... +// +// /js drone.cottage(); +// + +Drone.extend('cottage',function () +{ + this.chkpt('cottage') + .box0(48,7,2,6) // 4 walls + .right(3).door() // door front and center + .up(1).left(2).box(102) // windows to left and right + .right(4).box(102) + .left(5).up().prism0(53,7,6); + // + // put up a sign near door. + // + this.down().right(4).sign(["Home","Sweet","Home"],68); + + return this.move('cottage'); +}); +// +// a more complex script that builds an tree-lined avenue with +// cottages on both sides. +// +Drone.extend('cottage_road', function(numberCottages) +{ + if (typeof numberCottages == "undefined"){ + numberCottages = 6; + } + var i=0, distanceBetweenTrees = 11; + // + // step 1 build the road. + // + var cottagesPerSide = Math.floor(numberCottages/2); + this + .chkpt("cottage_road") // make sure the drone's state is saved. + .box(43,3,1,cottagesPerSide*(distanceBetweenTrees+1)) // build the road + .up().right() // now centered in middle of road + .chkpt("cr"); // will be returning to this position later + + // + // step 2 line the road with trees + // + for (; i < cottagesPerSide+1;i++){ + this + .left(5).oak() + .right(10).oak() + .left(5) // return to middle of road + .fwd(distanceBetweenTrees+1); // move forward. + } + this.move("cr").back(6); // move back 1/2 the distance between trees + + // this function builds a path leading to a cottage. + function pathAndCottage(d){ + return d.down().box(43,1,1,5).fwd(5).left(3).up().cottage(); + }; + // + // step 3 build cottages on each side + // + for (i = 0;i < cottagesPerSide; i++) + { + this.fwd(distanceBetweenTrees+1).chkpt("r"+i); + // build cottage on left + pathAndCottage(this.turn(3)).move("r"+i); + // build cottage on right + pathAndCottage(this.turn()).move("r"+i); + } + // return drone to where it was at start of function + return this.move("cottage_road"); +}); + diff --git a/src/main/javascript/plugins/drone/contrib/dancefloor.js b/src/main/javascript/plugins/drone/contrib/dancefloor.js new file mode 100644 index 0000000..6979186 --- /dev/null +++ b/src/main/javascript/plugins/drone/contrib/dancefloor.js @@ -0,0 +1,38 @@ +var Drone = require('../drone'); +module.exports = Drone; +// +// Create a floor of colored tiles some of which emit light. +// The tiles change color every second creating a strobe-lit dance-floor. +// +// See it in action here => http://www.youtube.com/watch?v=UEooBt6NTFo +// +Drone.extend('dancefloor',function(width,length) +{ + if (typeof width == "undefined") + width = 5; + if (typeof length == "undefined") + length = width; + // + // create a separate Drone object to lay down disco tiles + // + var disco = new Drone(this.x, this.y, this.z, this.dir, this.world); + // + // under-floor lighting + // + disco.down().box(89,width,1,length).up(); + var floorTiles = [35,35,'35:1','35:2','35:3','35:4','35:4','35:4','35:6',20,20]; + // + // strobe gets called in a java thread - disco only lasts 30 seconds. + // + var discoTicks = 30; + var task = null; + var strobe = function() { + disco.rand(floorTiles,width,1,length); + if (!discoTicks--) + task.cancel(); + }; + var now = 0; + var everySecond = 20; + task = server.scheduler.runTaskTimer(__plugin,strobe,now,everySecond); + return this; +}); diff --git a/src/main/javascript/plugins/drone/contrib/fort.js b/src/main/javascript/plugins/drone/contrib/fort.js new file mode 100644 index 0000000..0f43043 --- /dev/null +++ b/src/main/javascript/plugins/drone/contrib/fort.js @@ -0,0 +1,68 @@ +var Drone = require('../drone'); +module.exports = Drone; +// +// constructs a medieval fort +// +Drone.extend('fort', function(side, height) +{ + if (typeof side == "undefined") + side = 18; + if (typeof height == "undefined") + height = 6; + // make sure side is even + if (side%2) + side++; + if (height < 4 || side < 10) + throw new java.lang.RuntimeException("Forts must be at least 10 wide X 4 tall"); + var brick = 98; + // + // build walls. + // + this.chkpt('fort').box0(brick,side,height-1,side); + // + // build battlements + // + this.up(height-1); + for (i = 0;i <= 3;i++){ + var turret = []; + this.box(brick) // solid brick corners + .up().box('50:5').down() // light a torch on each corner + .fwd(); + turret.push('109:'+ Drone.PLAYER_STAIRS_FACING[this.dir]); + turret.push('109:'+ Drone.PLAYER_STAIRS_FACING[(this.dir+2)%4]); + try{ + this.boxa(turret,1,1,side-2).fwd(side-2).turn(); + }catch(e){ + self.sendMessage("ERROR: " + e.toString()); + } + } + // + // build battlement's floor + // + this.move('fort'); + this.up(height-2).fwd().right().box('126:0',side-2,1,side-2); + var battlementWidth = 3; + if (side <= 12) + battlementWidth = 2; + + this.fwd(battlementWidth).right(battlementWidth) + .box(0,side-((1+battlementWidth)*2),1,side-((1+battlementWidth)*2)); + // + // add door + // + var torch = '50:' + Drone.PLAYER_TORCH_FACING[this.dir]; + this.move('fort').right((side/2)-1).door2() // double doors + .back().left().up() + .box(torch) // left torch + .right(3) + .box(torch); // right torch + // + // add ladder up to battlements + // + var ladder = '65:' + Drone.PLAYER_SIGN_FACING[(this.dir+2)%4]; + this.move('fort').right((side/2)-3).fwd(1) // move inside fort + .box(ladder, 1,height-1,1); + return this.move('fort'); + +}); + diff --git a/src/main/javascript/plugins/drone/contrib/logo.js b/src/main/javascript/plugins/drone/contrib/logo.js new file mode 100644 index 0000000..d9507e6 --- /dev/null +++ b/src/main/javascript/plugins/drone/contrib/logo.js @@ -0,0 +1,219 @@ +var Drone = require('../drone'); +module.exports = Drone; +// +// Constructs the JS logo +// https://raw.github.com/voodootikigod/logo.js/master/js.png +// +// fg +// the material that the letters JS will be made of +// bg +// the material that the square will be made of +// +Drone.extend('logojs', function(fg, bg) { + + // foreground defaults to gray wool + if (typeof fg == "undefined") + fg = '35:7'; + // background defaults to gold blocks + if (typeof bg == "undefined") + bg = 41; + + // Draw the sqaure + this.chkpt('logojs-start') + .up() + .box(bg, 100, 100, 1); + + // Draw the J, starting with the hook + this.right(30).up(13) + .box(fg) + .right().down() + .box(fg, 1, 3, 1) + .right().down() + .box(fg, 1, 5, 1) + .right().down() + .box(fg, 1, 7, 1) + .right() + .box(fg, 1, 8, 1) + .right().down() + .box(fg, 1, 10, 1) + .right() + .box(fg, 1, 9, 1) + .right() + .box(fg, 1, 8, 1) + .right().down() + .box(fg, 2, 8, 1) + .right(2) + .box(fg, 4, 7, 1) + .right(4) + .box(fg, 1, 8, 1) + .right() + .box(fg, 1, 9, 1) + .right().up() + .box(fg, 3, 10, 1) + .right(3).up() + .box(fg, 2, 9, 1) + .right(2).up() + .box(fg, 2, 8, 1) + .right(2).up() + .box(fg, 1, 7, 1) + .right().up() + .box(fg, 1, 6, 1) + .right().up() + .box(fg, 1, 5, 1) + .right().up(2) + .box(fg, 1, 3, 1) + .left(9).up(3) + .box(fg, 10, 31, 1) + + // Draw the S + // It's drawn in three strokes from bottom to top. Look for when + // it switches from .right() to .left() then back again + + // move to starting point for S + .right(22).down(6) + // stroke 1 + .box(fg) + .right().down() + .box(fg, 1, 3, 1) + .right().down() + .box(fg, 1, 5, 1) + .right().down() + .box(fg, 1, 7, 1) + .right() + .box(fg, 1, 8, 1) + .right().down() + .box(fg, 1, 10, 1) + .right() + .box(fg, 1, 9, 1) + .right() + .box(fg, 1, 8, 1) + .right().down() + .box(fg, 2, 8, 1) + .right(2) + .box(fg, 4, 7, 1) + .right(4) + .box(fg, 2, 8, 1) + .right(2) + .box(fg, 1, 9, 1) + .right().up() + .box(fg, 1, 9, 1) + .right() + .box(fg, 1, 10, 1) + .right() + .box(fg, 1, 22, 1) + .right().up() + .box(fg, 2, 20, 1) + .right().up() + .box(fg, 1, 18, 1) + .right().up() + .box(fg, 1, 17, 1) + .right().up() + .box(fg, 1, 15, 1) + .right().up() + .box(fg, 1, 13, 1) + .right().up(2) + .box(fg, 1, 9, 1) + .right().up(2) + .box(fg, 1, 5, 1) + // stroke 2 + .left(8).up(4) + .box(fg, 1, 9, 1) + .left().up() + .box(fg, 1, 9, 1) + .left().up() + .box(fg, 1, 8, 1) + .left(2).up() + .box(fg, 2, 8, 1) + .left(2).up() + .box(fg, 2, 7, 1) + .left(2).up() + .box(fg, 2, 7, 1) + .left() + .box(fg, 1, 8, 1) + .left().up() + .box(fg, 1, 8, 1) + .left() + .box(fg, 1, 9, 1) + .left(2).up() + .box(fg, 2, 19, 1) + .left().up() + .box(fg, 1, 17, 1) + .left() + .box(fg, 1, 16, 1) + .left().up() + .box(fg, 1, 14, 1) + .left().up(2) + .box(fg, 1, 10, 1) + .left().up(2) + .box(fg, 1, 6, 1) + // stroke 3 + .right(7).up(6) + .box(fg, 1, 8, 1) + .right().up() + .box(fg, 1, 7, 1) + .right().up() + .box(fg, 2, 7, 1) + .right(2).up() + .box(fg, 4, 6, 1) + .right(4).down() + .box(fg, 2, 7, 1) + .right().down() + .box(fg, 1, 8, 1) + .right() + .box(fg, 1, 7, 1) + .right().down() + .box(fg, 1, 8, 1) + .right().down() + .box(fg, 1, 9, 1) + .right().down() + .box(fg, 1, 9, 1) + .right().up() + .box(fg, 1, 8, 1) + .right().up() + .box(fg, 1, 6, 1) + .right().up() + .box(fg, 1, 5, 1) + .right().up() + .box(fg, 1, 3, 1) + .right().up() + .box(fg); + + this.move('logojs-start'); + + return this; +}); +// +// Makes a cube of JS logos! +// This is a wrapper for logojs() so look at its docs +// +// Until the drone can rotate on its Z axis we can't +// use logojs() to create top/bottom sides of cube. +// +Drone.extend('logojscube', function(fg, bg) { + + this.chkpt('jscube-start') + .logojs(fg, bg); + + this.move('jscube-start') + .right(100) + .turn(3) + .logojs(fg, bg); + + this.move('jscube-start') + .right(100) + .turn(3) + .right(100) + .turn(3) + .logojs(fg, bg); + + this.move('jscube-start') + .right(100) + .turn(3) + .right(100) + .turn(3) + .right(100) + .turn(3) + .logojs(fg, bg); + + return this; +}); diff --git a/src/main/javascript/plugins/drone/contrib/rainbow.js b/src/main/javascript/plugins/drone/contrib/rainbow.js new file mode 100644 index 0000000..870cbc5 --- /dev/null +++ b/src/main/javascript/plugins/drone/contrib/rainbow.js @@ -0,0 +1,44 @@ +var Drone = require('../drone'); +var blocks = require('../blocks'); +module.exports = Drone; +/************************************************************************ +Drone.rainbow() method +====================== +Creates a Rainbow. + +Parameters +---------- + + * radius (optional - default:18) - The radius of the rainbow + +Example +------- + + var d = new Drone(); + d.rainbow(30); + +![rainbow example](img/rainbowex1.png) + +***/ +Drone.extend('rainbow', function(radius){ + if (typeof radius == "undefined") + radius = 18; + + this.chkpt('rainbow'); + this.down(radius); + // copy blocks.rainbow and add air at end (to compensate for strokewidth) + var colors = blocks.rainbow.slice(0); + colors.push(blocks.air); + for (var i = 0;i < colors.length; i++) { + var bm = this._getBlockIdAndMeta(colors[i]); + this.arc({ + blockType: bm[0], + meta: bm[1], + radius: radius-i, + strokeWidth: 2, + quadrants: {topright: true, + topleft: true}, + orientation: 'vertical'}).right().up(); + } + return this.move('rainbow'); +}); diff --git a/src/main/javascript/plugins/drone/contrib/rboxcall.js b/src/main/javascript/plugins/drone/contrib/rboxcall.js new file mode 100644 index 0000000..4546eec --- /dev/null +++ b/src/main/javascript/plugins/drone/contrib/rboxcall.js @@ -0,0 +1,34 @@ +var Drone = require('../drone'); +module.exports = Drone; +/** +* Iterates over each cube in a cubic region. For each cube has a chance to callback your +* function and provide a new drone to it. +* +* Parameters: +* callback - any function that accepts a drone as its first argument +* probability - chance to invoke your callback on each iteration +* width - width of the region +* height - (Optional) height of the region, defaults to width +* depth - (Optional) depth of the cube, defaults to width +*/ + +Drone.extend("rboxcall", function(callback, probability, width, height, depth) { + this.chkpt('rboxcall-start'); + + for(var i = 0; i < width; ++i) { + this.move('rboxcall-start').right(i); + for(var j = 0; j < depth; ++j) { + this.move('rboxcall-start').right(i).fwd(j); + for(var k = 0; k < height; ++k) { + if(Math.random()*100 < probability) { + callback.call(null, new Drone(this.x, this.y, this.z)); + } + this.up(); + } + } + } + + this.move('rboxcall-start'); + + return this; +}); diff --git a/src/main/javascript/plugins/drone/contrib/redstonewire.js b/src/main/javascript/plugins/drone/contrib/redstonewire.js new file mode 100644 index 0000000..97dc0f8 --- /dev/null +++ b/src/main/javascript/plugins/drone/contrib/redstonewire.js @@ -0,0 +1,108 @@ +var Drone = require('../drone'); +var blocks = require('../blocks'); +module.exports = Drone; +// +// usage: +// [1] to place a new block with redstone wire on it (block on bottom, redstone on top) +// /js wireblock(blocks.sandstone); +// +// [2] to drop wire on to an existing block +// /js wire() +// +// [3] to place a (redstone) torch on a new block +// /js torchblock(blocks.sandstone) +// +// [4] to place a repeater on a new block +// /js repeaterblock(blocks.sandstone) +// +// [5] To create a long redstone wire (with necessary repeaters, powererd by a single torch) +// /js wirestraight(blocks.sandstone, distance) +// +// [6] To create a 'road' with redstone torches and wire lining each side +// /js redstoneroad(blocks.stone, blocks.sandstone, 25) + +Drone.extend('wireblock',function(blockType) +{ + this.chkpt('wireblock') + .box(blockType,1,2,1) // 2 blocks tall, top block will be wire dropped on lower + .up(); + + this.world.getBlockAt(this.x,this.y,this.z).setTypeId(55); //apply wire + + return this.move('wireblock'); +}); + +Drone.extend('wire',function () +{ + this.chkpt('wire') + .up(); + + this.world.getBlockAt(this.x,this.y,this.z).setTypeId(55); // apply wire + + return this.move('wire'); +}); + +Drone.extend('torchblock', function(blockType) +{ + this.box(blockType,1,2,1) // 2 blocks tall + .up(); + + this.world.getBlockAt(this.x,this.y,this.z).setTypeId(76); // apply torch + + return this.down(); +}); + +Drone.extend('repeaterblock',function(blockType) +{ + this.chkpt('repeaterblock') + .box(blockType,1,2,1) + .up(); + + var block = this.world.getBlockAt(this.x,this.y,this.z); + block.setTypeId(94); // apply repeater + + // redstone repeater dirs: north=0,east=1,south=2,west=3 + var direction = [1,2,3,0][this.dir]; // convert drone dir to repeater dir. + block.setData(direction); + + return this.move('repeaterblock'); +}); + + +Drone.extend('wirestraight',function (blockType,distance) +{ + this.chkpt('wirestraight'); + + this.torchblock(blockType); + this.fwd(); + + for (var i = 1; i < distance; i++) { + if(i % 14 == 0) + { + this.repeaterblock(blockType); + } + else + { + this.wireblock(blockType); + } + + this.fwd(); + }; + + return this.move('wirestraight'); +}); + + +Drone.extend('redstoneroad', function (roadBlockType, redstoneunderBlockType, distance) +{ + return this.down() + .wirestraight(redstoneunderBlockType, distance) + .right() + .box(roadBlockType, 4,1,distance) + .right(4) + .wirestraight(redstoneunderBlockType, distance) + .up(); +}); + + + diff --git a/src/main/javascript/plugins/drone/contrib/skyscraper-example.js b/src/main/javascript/plugins/drone/contrib/skyscraper-example.js new file mode 100644 index 0000000..13190dc --- /dev/null +++ b/src/main/javascript/plugins/drone/contrib/skyscraper-example.js @@ -0,0 +1,19 @@ +var Drone = require('../drone'); +var blocks = require('../blocks'); + +module.exports = Drone; +Drone.extend('skyscraper',function(floors){ + + if (typeof floors == "undefined") + floors = 10; + this.chkpt('skyscraper'); + for (var i = 0;i < floors; i++) + { + this + .box(blocks.iron,20,1,20) + .up() + .box0(blocks.glass_pane,20,3,20) + .up(3); + } + return this.move('skyscraper'); +}); diff --git a/src/main/javascript/plugins/drone/contrib/spiral_stairs.js b/src/main/javascript/plugins/drone/contrib/spiral_stairs.js new file mode 100644 index 0000000..39b818f --- /dev/null +++ b/src/main/javascript/plugins/drone/contrib/spiral_stairs.js @@ -0,0 +1,48 @@ +var Drone = require('../drone'); +var blocks = require('../blocks'); + +module.exports = Drone; +/************************************************************************ +Drone.spiral_stairs() method +============================ +Constructs a spiral staircase with slabs at each corner. + +Parameters +---------- + + * stairBlock - The block to use for stairs, should be one of the following... + - 'oak' + - 'spruce' + - 'birch' + - 'jungle' + - 'cobblestone' + - 'brick' + - 'stone' + - 'nether' + - 'sandstone' + - 'quartz' + * flights - The number of flights of stairs to build. + +![Spiral Staircase](img/spiralstair1.png) + +Example +------- +To construct a spiral staircase 5 floors high made of oak... + + spiral_stairs('oak', 5); + +***/ +Drone.extend("spiral_stairs",function(stairBlock, flights){ + this.chkpt('spiral_stairs'); + + for (var i = 0; i < flights; i++){ + this + .box(blocks.stairs[stairBlock] + ':' + Drone.PLAYER_STAIRS_FACING[this.dir]) + .up().fwd() + .box(blocks.stairs[stairBlock] + ':' + Drone.PLAYER_STAIRS_FACING[this.dir]) + .up().fwd() + .box(blocks.slab[stairBlock]) + .turn().fwd(); + } + return this.move('spiral_stairs'); +}); diff --git a/src/main/javascript/plugins/drone/contrib/streamer.js b/src/main/javascript/plugins/drone/contrib/streamer.js new file mode 100644 index 0000000..9c2031e --- /dev/null +++ b/src/main/javascript/plugins/drone/contrib/streamer.js @@ -0,0 +1,32 @@ +var Drone = require('../drone'); +module.exports = Drone; +/** +* Creates a stream of blocks in a given direction until it hits something other than air +* +* Parameters: +* block - blockId +* dir - "up", "down", "left", "right", "fwd", "back +* maxIterations - (Optional) maximum number of cubes to generate, defaults to 1000 +*/ +Drone.extend("streamer", function(block, dir, maxIterations) { + if (typeof maxIterations == "undefined") + maxIterations = 1000; + + var usage = "Usage: streamer({block-type}, {direction: 'up', 'down', 'fwd', 'back', 'left', 'right'}, {maximum-iterations: default 1000})\nE.g.\n" + + "streamer(5, 'up', 200)"; + if (typeof dir == "undefined"){ + throw new Error(usage); + } + if (typeof block == "undefined") { + throw new Error(usage); + } + for ( var i = 0; i < maxIterations||1000; ++i ) { + this.box(block); + this[dir].call(this); + var block = this.world.getBlockAt(this.x, this.y, this.z); + if ( block.typeId != 0 && block.data != 0) { + break + } + } + return this; +}); diff --git a/src/main/javascript/plugins/drone/contrib/temple.js b/src/main/javascript/plugins/drone/contrib/temple.js new file mode 100644 index 0000000..c774a9b --- /dev/null +++ b/src/main/javascript/plugins/drone/contrib/temple.js @@ -0,0 +1,25 @@ +var Drone = require('../drone'); +module.exports = Drone; +// +// constructs a mayan temple +// +Drone.extend('temple', function(side) { + if (!side) { + side = 20; + } + var stone = '98:1'; + var stair = '109:' + Drone.PLAYER_STAIRS_FACING[this.dir]; + + this.chkpt('temple'); + + while (side > 4) { + var middle = Math.round((side-2)/2); + this.chkpt('corner') + .box(stone, side, 1, side) + .right(middle).box(stair).right().box(stair) + .move('corner').up().fwd().right(); + side = side - 2; + } + + return this.move('temple'); +}); diff --git a/src/main/javascript/plugins/drone/drone-exts.js b/src/main/javascript/plugins/drone/drone-exts.js new file mode 100644 index 0000000..08da5da --- /dev/null +++ b/src/main/javascript/plugins/drone/drone-exts.js @@ -0,0 +1,25 @@ +var Drone = require('./drone'); +var utils = require('../utils/utils'); + +var files = []; + +var filter = function(file,name){ + name = "" + name; + if (name.match(/drone\.js$/)) + return false; + if (name.match(/drone\-exts\.js$/)) + return false; + if (name.match(/\.js$/)) + return true; + if (file.isDirectory()) + return true; + return false; +}; + +var files = utils.find(__dirname, filter); + +utils.foreach(files, function (file){ + require(file); +}); + +module.exports = Drone; diff --git a/src/main/javascript/plugins/drone/drone-firework.js b/src/main/javascript/plugins/drone/drone-firework.js new file mode 100644 index 0000000..ac07f37 --- /dev/null +++ b/src/main/javascript/plugins/drone/drone-firework.js @@ -0,0 +1,6 @@ +var fireworks = require('fireworks'); +var Drone = require('./drone').Drone; +Drone.extend('firework',function() { + fireworks.firework(this.getLocation()); +}); + diff --git a/src/main/javascript/plugins/drone/drone.js b/src/main/javascript/plugins/drone/drone.js new file mode 100644 index 0000000..4686c9d --- /dev/null +++ b/src/main/javascript/plugins/drone/drone.js @@ -0,0 +1,1740 @@ +var _utils = require('../utils/utils'); +var blocks = require('./blocks'); + +/********************************************************************* +Drone Module +============ +The Drone is a convenience class for building. It can be used for... + + 1. Building + 2. Copying and Pasting + +It uses a fluent interface which means all of the Drone's methods return `this` and can +be chained together like so... + + var theDrone = new Drone(); + theDrone.up().left().box(blocks.oak).down().fwd(3).cylinder0(blocks.lava,8); + +TLDNR; (Just read this if you're impatient) +=========================================== +At the in-game command prompt type... + + /js box( blocks.oak ) + +... creates a single wooden block at the cross-hairs or player location + + /js box( blocks.oak ).right(2).box( blocks.wool.black, 4, 9, 1) + +... creates a single wooden block and a 2001 black obelisk that is 4 +wide x 9 tall x 1 long in size. If you want to see what else +ScriptCraft's Drone can do, read on... + +Constructing a Drone Object +=========================== + +Drones can be created in any of the following ways... + + 1. Calling any one of the methods listed below will return a Drone object. For example... + + var d = box( blocks.oak ) + + ... creates a 1x1x1 wooden block at the cross-hairs or player's location and returns a Drone + object. This might look odd (if you're familiar with Java's Object-dot-method syntax) but all + of the Drone class's methods are also global functions that return new Drone objects. + This is short-hand for creating drones and is useful for playing around with Drones at the in-game + command prompt. It's shorter than typing ... + + var d = new Drone().box( blocks.oak ) + + ... All of the Drone's methods return `this` (self) so you can chain operations together like this... + + var d = box( blocks.oak ) + .up() + .box( blocks.oak ,3,1,3) + .down() + .fwd(2) + .box( blocks.oak ) + .turn() + .fwd(2) + .box( blocks.oak ) + .turn() + .fwd(2) + .box( blocks.oak ); + + 2. Using the following form... + + d = new Drone() + + ...will create a new Drone. If the cross-hairs are pointing at a + block at the time then, that block's location becomes the drone's + starting point. If the cross-hairs are _not_ pointing at a block, + then the drone's starting location will be 2 blocks directly in + front of the player. TIP: Building always happens right and front + of the drone's position... + + Plan View: + + ^ + | + | + D----> + + For convenience you can use a _corner stone_ to begin building. + The corner stone should be located just above ground level. If + the cross-hair is point at or into ground level when you create a + new Drone(), then building begins at that point. You can get + around this by pointing at a 'corner stone' just above ground + level or alternatively use the following statement... + + d = new Drone().up(); + + ... which will move the drone up one block as soon as it's created. + + ![corner stone](img/cornerstone1.png) + + 3. Or by using the following form... + + d = new Drone(x,y,z,direction,world); + + This will create a new Drone at the location you specified using + x, y, z In minecraft, the X axis runs west to east and the Z axis runs + north to south. The direction parameter says what direction you want + the drone to face: 0 = east, 1 = south, 2 = west, 3 = north. If the + direction parameter is omitted, the player's direction is used + instead. + + Both the `direction` and `world` parameters are optional. + + 4. Create a new Drone based on a Bukkit Location object... + + d = new Drone(location); + + This is useful when you want to create a drone at a given + `org.bukkit.Location` . The `Location` class is used throughout + the bukkit API. For example, if you want to create a drone when a + block is broken at the block's location you would do so like + this... + + events.on('block.BlockBreakEvent',function(listener,event){ + var location = event.block.location; + var drone = new Drone(location); + // do more stuff with the drone here... + }); + +Parameters +---------- + * location (optional) : *NB* If an `org.bukkit.Location` object is provided as a parameter, then it should be the only parameter. + * x (optional) : The x coordinate of the Drone + * y (optional) : The y coordinate of the Drone + * z (optional) : The z coordinate of the Drone + * direction (optional) : The direction in which the Drone is + facing. Possible values are 0 (east), 1 (south), 2 (west) or 3 (north) + * world (optional) : The world in which the drone is created. + +Drone.box() method +================== +the box() method is a convenience method for building things. (For the more performance-oriented method - see cuboid) + +parameters +---------- + * b - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. + Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` + * w (optional - default 1) - the width of the structure + * h (optional - default 1) - the height of the structure + * d (optional - default 1) - the depth of the structure - NB this is + not how deep underground the structure lies - this is how far + away (depth of field) from the drone the structure will extend. + +Example +------- +To create a black structure 4 blocks wide, 9 blocks tall and 1 block long... + + box(blocks.wool.black, 4, 9, 1); + +... or the following code does the same but creates a variable that can be used for further methods... + + var drone = new Drone(); + drone.box(blocks.wool.black, 4, 9, 1); + +![box example 1](img/boxex1.png) + +Drone.box0() method +=================== +Another convenience method - this one creates 4 walls with no floor or ceiling. + +Parameters +---------- + * block - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. + Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` + * width (optional - default 1) - the width of the structure + * height (optional - default 1) - the height of the structure + * length (optional - default 1) - the length of the structure - how far + away (depth of field) from the drone the structure will extend. + +Example +------- +To create a stone building with the insided hollowed out 7 wide by 3 tall by 6 long... + + box0( blocks.stone, 7, 3, 6); + +![example box0](img/box0ex1.png) + +Drone.boxa() method +=================== +Construct a cuboid using an array of blocks. As the drone moves first along the width axis, +then the height (y axis) then the length, each block is picked from the array and placed. + +Parameters +---------- + * blocks - An array of blocks - each block in the array will be placed in turn. + * width + * height + * length + +Example +------- +Construct a rainbow-colored road 100 blocks long... + + var rainbowColors = [blocks.wool.red, blocks.wool.orange, blocks.wool.yellow, blocks.wool.lime, + blocks.wool.lightblue, blocks.wool.blue, blocks.wool.purple]; + + boxa(rainbowColors,7,1,30); + +![boxa example](img/boxaex1.png) + +Drone Movement +============== +Drones can move freely in minecraft's 3-D world. You control the +Drone's movement using any of the following methods.. + + * up() + * down() + * left() + * right() + * fwd() + * back() + * turn() + +... Each of these methods takes a single optional parameter +`numBlocks` - the number of blocks to move in the given direction. If +no parameter is given, the default is 1. + +to change direction use the `turn()` method which also takes a single +optional parameter (numTurns) - the number of 90 degree turns to make. +Turns are always clock-wise. If the drone is facing north, then +drone.turn() will make the turn face east. If the drone is facing east +then drone.turn(2) will make the drone turn twice so that it is facing +west. + +Drone Positional Info +===================== + + * getLocation() - Returns a Bukkit Location object for the drone + +Drone Markers +============= +Markers are useful when your Drone has to do a lot of work. You can +set a check-point and return to the check-point using the move() +method. If your drone is about to undertake a lot of work - +e.g. building a road, skyscraper or forest you should set a +check-point before doing so if you want your drone to return to its +current location. + +A 'start' checkpoint is automatically created when the Drone is first created. + +Markers are created and returned to using the followng two methods... + + * chkpt - Saves the drone's current location so it can be returned to later. + * move - moves the drone to a saved location. Alternatively you can provide an + org.bukkit.Location object or x,y,z and direction parameters. + +Parameters +---------- + * name - the name of the checkpoint to save or return to. + +Example +------- + + drone.chkpt('town-square'); + // + // the drone can now go off on a long excursion + // + for (i = 0; i< 100; i++){ + drone.fwd(12).box(6); + } + // + // return to the point before the excursion + // + drone.move('town-square'); + +Drone.prism() method +==================== +Creates a prism. This is useful for roofs on houses. + +Parameters +---------- + + * block - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. + Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` + * width - the width of the prism + * length - the length of the prism (will be 2 time its height) + +Example +------- + + prism(blocks.oak,3,12); + +![prism example](img/prismex1.png) + +Drone.prism0() method +===================== +A variation on `prism` which hollows out the inside of the prism. It uses the same parameters as `prism`. + +Drone.cylinder() method +======================= +A convenience method for building cylinders. Building begins radius blocks to the right and forward. + +Parameters +---------- + + * block - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. + Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` + * radius + * height + +Example +------- +To create a cylinder of Iron 7 blocks in radius and 1 block high... + + cylinder(blocks.iron, 7 , 1); + +![cylinder example](img/cylinderex1.png) + +Drone.cylinder0() method +======================== +A version of cylinder that hollows out the middle. + +Example +------- +To create a hollow cylinder of Iron 7 blocks in radius and 1 block high... + + cylinder0(blocks.iron, 7, 1); + +![cylinder0 example](img/cylinder0ex1.png) + +Drone.arc() method +================== +The arc() method can be used to create 1 or more 90 degree arcs in the horizontal or vertical planes. +This method is called by cylinder() and cylinder0() and the sphere() and sphere0() methods. + +Parameters +---------- +arc() takes a single parameter - an object with the following named properties... + + * radius - The radius of the arc. + * blockType - The type of block to use - this is the block Id only (no meta). See [Data Values][dv]. + * meta - The metadata value. See [Data Values][dv]. + * orientation (default: 'horizontal') - the orientation of the arc - can be 'vertical' or 'horizontal'. + * stack (default: 1) - the height or length of the arc (depending on + the orientation - if orientation is horizontal then this parameter + refers to the height, if vertical then it refers to the length). + * strokeWidth (default: 1) - the width of the stroke (how many + blocks) - if drawing nested arcs it's usually a good idea to set + strokeWidth to at least 2 so that there are no gaps between each + arc. The arc method uses a [bresenham algorithm][bres] to plot + points along the circumference. + * fill - If true (or present) then the arc will be filled in. + * quadrants (default: + `{topleft:true,topright:true,bottomleft:true,bottomright:true}` - An + object with 4 properties indicating which of the 4 quadrants of a + circle to draw. If the quadrants property is absent then all 4 + quadrants are drawn. + +Examples +-------- +To draw a 1/4 circle (top right quadrant only) with a radius of 10 and stroke width of 2 blocks ... + + arc({blockType: blocks.iron, + meta: 0, + radius: 10, + strokeWidth: 2, + quadrants: { topright: true }, + orientation: 'vertical', + stack: 1, + fill: false + }); + +![arc example 1](img/arcex1.png) + +[bres]: http://en.wikipedia.org/wiki/Midpoint_circle_algorithm +[dv]: http://www.minecraftwiki.net/wiki/Data_values + +Drone.door() method +=================== +create a door - if a parameter is supplied an Iron door is created otherwise a wooden door is created. + +Parameters +---------- + * doorType (optional - default wood) - If a parameter is provided then the door is Iron. + +Example +------- +To create a wooden door at the crosshairs/drone's location... + + var drone = new Drone(); + drone.door(); + +To create an iron door... + + drone.door( blocks.door_iron ); + +![iron door](img/doorex1.png) + +Drone.door2() method +==================== +Create double doors (left and right side) + +Parameters +---------- + * doorType (optional - default wood) - If a parameter is provided then the door is Iron. + +Example +------- +To create double-doors at the cross-hairs/drone's location... + + drone.door2(); + +![double doors](img/door2ex1.png) + +Drone.sign() method +=================== +Signs must use block 63 (stand-alone signs) or 68 (signs on walls) + +Parameters +---------- + * message - can be a string or an array of strings. + * block - can be 63 or 68 + +Example +------- +To create a free-standing sign... + + drone.sign(["Hello","World"],63); + +![ground sign](img/signex1.png) + +... to create a wall mounted sign... + + drone.sign(["Welcome","to","Scriptopia"], 68); + +![wall sign](img/signex2.png) + +Drone Trees methods +=================== + + * oak() + * spruce() + * birch() + * jungle() + +Example +------- +To create 4 trees in a row, point the cross-hairs at the ground then type `/js ` and ... + + up().oak().right(8).spruce().right(8).birch().right(8).jungle(); + +Trees won't always generate unless the conditions are right. You +should use the tree methods when the drone is directly above the +ground. Trees will usually grow if the drone's current location is +occupied by Air and is directly above an area of grass (That is why +the `up()` method is called first). + +![tree example](img/treeex1.png) + + +None of the tree methods require parameters. Tree methods will only be successful +if the tree is placed on grass in a setting where trees can grow. + +Drone.garden() method +===================== +places random flowers and long grass (similar to the effect of placing bonemeal on grass) + +Parameters +---------- + + * width - the width of the garden + * length - how far from the drone the garden extends + +Example +------- +To create a garden 10 blocks wide by 5 blocks long... + + garden(10,5); + +![garden example](img/gardenex1.png) + +Drone.rand() method +=================== +rand takes either an array (if each blockid has the same chance of occurring) +or an object where each property is a blockid and the value is it's weight (an integer) + +Example +------- +place random blocks stone, mossy stone and cracked stone (each block has the same chance of being picked) + + rand( [blocks.brick.stone, blocks.brick.mossy, blocks.brick.cracked ],w,d,h) + +to place random blocks stone has a 50% chance of being picked, + + rand({blocks.brick.stone: 5, blocks.brick.mossy: 3, blocks.brick.cracked: 2},w,d,h) + +regular stone has a 50% chance, mossy stone has a 30% chance and cracked stone has just a 20% chance of being picked. + +Copy & Paste using Drone +======================== +A drone can be used to copy and paste areas of the game world. + +Drone.copy() method +=================== +Copies an area so it can be pasted elsewhere. The name can be used for +pasting the copied area elsewhere... + +Parameters +---------- + + * name - the name to be given to the copied area (used by `paste`) + * width - the width of the area to copy + * height - the height of the area to copy + * length - the length of the area (extending away from the drone) to copy + +Example +------- + + drone.copy('somethingCool',10,5,10).right(12).paste('somethingCool'); + +Drone.paste() method +==================== +Pastes a copied area to the current location. + +Example +------- +To copy a 10x5x10 area (using the drone's coordinates as the starting +point) into memory. the copied area can be referenced using the name +'somethingCool'. The drone moves 12 blocks right then pastes the copy. + + drone.copy('somethingCool',10,5,10) + .right(12) + .paste('somethingCool'); + +Chaining +======== + +All of the Drone methods return a Drone object, which means methods +can be 'chained' together so instead of writing this... + + drone = new Drone(); + drone.fwd(3); + drone.left(2); + drone.box(2); // create a grass block + drone.up(); + drone.box(2); // create another grass block + drone.down(); + +...you could simply write ... + + var drone = new Drone().fwd(3).left(2).box(2).up().box(2).down(); + +... since each Drone method is also a global function that constructs +a drone if none is supplied, you can shorten even further to just... + + fwd(3).left(2).box(2).up().box(2).down() + +The Drone object uses a [Fluent Interface][fl] to make ScriptCraft +scripts more concise and easier to write and read. Minecraft's +in-game command prompt is limited to about 80 characters so chaining +drone commands together means more can be done before hitting the +command prompt limit. For complex building you should save your +commands in a new script file and load it using /js load() + +[fl]: http://en.wikipedia.org/wiki/Fluent_interface + +Drone Properties +================ + + * x - The Drone's position along the west-east axis (x increases as you move east) + * y - The Drone's position along the vertical axis (y increses as you move up) + * z - The Drone's position along the north-south axis (z increases as you move south) + * dir - The Drone's direction 0 is east, 1 is south , 2 is west and 3 is north. + +Extending Drone +=============== +The Drone object can be easily extended - new buidling recipes/blue-prints can be added and can +become part of a Drone's chain using the *static* method `Drone.extend`. + +Drone.extend() static method +============================ +Use this method to add new methods (which also become chainable global functions) to the Drone object. + +Parameters +---------- + * name - The name of the new method e.g. 'pyramid' + * function - The method body. + +Example +------- + + // submitted by [edonaldson][edonaldson] + Drone.extend('pyramid', function(block,height){ + this.chkpt('pyramid'); + for (var i = height; i > 0; i -= 2) { + this.box(block, i, 1, i).up().right().fwd(); + } + return this.move('pyramid'); + }); + +Once the method is defined (it can be defined in a new pyramid.js file) it can be used like so... + + var d = new Drone(); + d.pyramid(blocks.brick.stone, 12); + +... or simply ... + + pyramid(blocks.brick.stone, 12); + +[edonaldson]: https://github.com/edonaldson + +Drone Constants +=============== + +Drone.PLAYER_STAIRS_FACING +-------------------------- +An array which can be used when constructing stairs facing in the Drone's direction... + + var d = new Drone(); + d.box(blocks.stairs.oak + ':' + Drone.PLAYER_STAIRS_FACING[d.dir]); + +... will construct a single oak stair block facing the drone. + +Drone.PLAYER_SIGN_FACING +------------------------ +An array which can be used when placing signs so they face in a given direction. +This is used internally by the Drone.sign() method. It should also be used for placing +any of the following blocks... + + * chest + * ladder + * furnace + * dispenser + +To place a chest facing the Drone ... + + drone.box( blocks.chest + ':' + Drone.PLAYER_SIGN_FACING[drone.dir]); + +Drone.PLAYER_TORCH_FACING +------------------------- +Used when placing torches so that they face towards the drone. + + drone.box( blocks.torch + ':' + Drone.PLAYER_TORCH_FACING[drone.dir]); + +***/ + +// +// Implementation +// ============== +// +// There is no need to read any further unless you want to understand how the Drone object works. +// + +var putBlock = function(x,y,z,blockId,metadata,world){ + if (typeof metadata == "undefined") + metadata = 0; + var block = world.getBlockAt(x,y,z); + if (block.typeId != blockId || block.data != metadata) + block.setTypeIdAndData(blockId,metadata,false); +}; + +var putSign = function(texts, x, y, z, blockId, meta, world){ + if (blockId != 63 && blockId != 68) + throw new Error("Invalid Parameter: blockId must be 63 or 68"); + putBlock(x,y,z,blockId,meta,world); + var block = world.getBlockAt(x,y,z); + var state = block.state; + if (state instanceof org.bukkit.block.Sign){ + for (var i = 0;i < texts.length; i++) + state.setLine(i%4,texts[i]); + state.update(true); + } +}; + +Drone = function(x,y,z,dir,world) +{ + this.record = false; + var usePlayerCoords = false; + var playerPos = _utils.getPlayerPos(); + if (typeof x == "undefined") + { + var mp = _utils.getMousePos(); + if (mp){ + this.x = mp.x; + this.y = mp.y; + this.z = mp.z; + if (playerPos) + this.dir = _getDirFromRotation(playerPos.yaw); + this.world = mp.world; + }else{ + // base it on the player's current location + usePlayerCoords = true; + // + // it's possible that drone.js could be loaded by a non-playing op + // (from the server console) + // + if (!playerPos){ + return null; + } + this.x = playerPos.x; + this.y = playerPos.y; + this.z = playerPos.z; + this.dir = _getDirFromRotation(playerPos.yaw); + this.world = playerPos.world; + } + }else{ + if (arguments[0] instanceof org.bukkit.Location){ + this.x = arguments[0].x; + this.y = arguments[0].y; + this.z = arguments[0].z; + this.dir = _getDirFromRotation(arguments[0].yaw); + this.world = arguments[0].world; + }else{ + this.x = x; + this.y = y; + this.z = z; + if (typeof dir == "undefined"){ + this.dir = _getDirFromRotation(playerPos.yaw); + }else{ + this.dir = dir%4; + } + if (typeof world == "undefined"){ + this.world = _getWorld(); + }else{ + this.world = world; + } + } + } + + if (usePlayerCoords){ + this.fwd(3); + } + this.chkpt('start'); + this.record = true; + this.history = []; + // for debugging + // self.sendMessage("New Drone " + this.toString()); + return this; +}; + +module.exports = Drone; + +// +// add custom methods to the Drone object using this function +// +Drone.extend = function(name, func) +{ + Drone.prototype['_' + name] = func; + Drone.prototype[name] = function(){ + if (this.record) + this.history.push([name,arguments]); + var oldVal = this.record; + this.record = false; + this['_' + name].apply(this,arguments); + this.record = oldVal; + return this; + }; + + global[name] = function(){ + var result = new Drone(); + result[name].apply(result,arguments); + return result; + }; +}; + +/************************************************************************** +Drone.times() Method +==================== +The times() method makes building multiple copies of buildings easy. It's possible to create rows or grids of buildings without resorting to `for` or `while` loops. + +Parameters +---------- + * numTimes (optional - default 2) : The number of times you want to repeat the preceding statements. + +Example +------- +Say you want to do the same thing over and over. You have a couple of options... + + * You can use a for loop... + + d = new Drone(); for (var i =0;i < 4; i++){ d.cottage().right(8); } + +While this will fit on the in-game prompt, it's awkward. You need to +declare a new Drone object first, then write a for loop to create the +4 cottages. It's also error prone, even the `for` loop is too much +syntax for what should really be simple. + + * You can use a while loop... + + d = new Drone(); var i=4; while (i--){ d.cottage().right(8); } + +... which is slightly shorter but still too much syntax. Each of the +above statements is fine for creating a 1-dimensional array of +structures. But what if you want to create a 2-dimensional or +3-dimensional array of structures? Enter the `times()` method. + +The `times()` method lets you repeat commands in a chain any number of +times. So to create 4 cottages in a row you would use the following +statement... + + cottage().right(8).times(4); + +...which will build a cottage, then move right 8 blocks, then do it +again 4 times over so that at the end you will have 4 cottages in a +row. What's more the `times()` method can be called more than once in +a chain. So if you wanted to create a *grid* of 20 houses ( 4 x 5 ), +you would do so using the following statement... + + cottage().right(8).times(4).fwd(8).left(32).times(5); + +... breaking it down... + + 1. The first 3 calls in the chain ( `cottage()`, `right(8)`, + `times(4)` ) build a single row of 4 cottages. + + 2. The last 3 calls in the chain ( `fwd(8)`, `left(32)`, `times(5)` ) + move the drone forward 8 then left 32 blocks (4 x 8) to return to + the original x coordinate, then everything in the chain is + repeated again 5 times so that in the end, we have a grid of 20 + cottages, 4 x 5. Normally this would require a nested loop but + the `times()` method does away with the need for loops when + repeating builds. + +Another example: This statement creates a row of trees 2 by 3 ... + + oak().right(10).times(2).left(20).fwd(10).times(3) + +... You can see the results below. + +![times example 1](img/times-trees.png) + +***/ +Drone.prototype.times = function(numTimes,commands) { + if (typeof numTimes == "undefined") + numTimes = 2; + if (typeof commands == "undefined") + commands = this.history.concat(); + + this.history = [['times',[numTimes+1,commands]]]; + var oldVal = this.record; + this.record = false; + for (var j = 1; j < numTimes; j++) + { + for (var i = 0;i < commands.length; i++){ + var command = commands[i]; + var methodName = command[0]; + var args = command[1]; + print ("command=" + JSON.stringify(command) + ",methodName=" + methodName); + this[methodName].apply(this,args); + } + } + this.record = oldVal; + return this; +}; + +Drone.prototype._checkpoints = {}; + +Drone.extend('chkpt',function(name){ + this._checkpoints[name] = {x:this.x,y:this.y,z:this.z,dir:this.dir}; +}); + +Drone.extend('move', function() { + if (arguments[0] instanceof org.bukkit.Location){ + this.x = arguments[0].x; + this.y = arguments[0].y; + this.z = arguments[0].z; + this.dir = _getDirFromRotation(arguments[0].yaw); + this.world = arguments[0].world; + }else if (typeof arguments[0] === "string"){ + var coords = this._checkpoints[arguments[0]]; + if (coords){ + this.x = coords.x; + this.y = coords.y; + this.z = coords.z; + this.dir = coords.dir%4; + } + }else{ + // expect x,y,z,dir + switch(arguments.length){ + case 4: + this.dir = arguments[3]; + case 3: + this.z = arguments[2]; + case 2: + this.y = arguments[1]; + case 1:n + this.x = arguments[0]; + } + } +}); + +Drone.extend('turn',function(n){ + if (typeof n == "undefined") + n = 1; + this.dir += n; + this.dir %=4; +}); +Drone.extend('right',function(n){ + if (typeof n == "undefined") + n = 1; + _movements[this.dir].right(this,n); +}); +Drone.extend('left',function(n){ + if (typeof n == "undefined") + n = 1; + _movements[this.dir].left(this,n); +}); +Drone.extend('fwd',function(n){ + if (typeof n == "undefined") + n = 1; + _movements[this.dir].fwd(this,n); +}); +Drone.extend('back',function(n){ + if (typeof n == "undefined") + n = 1; + _movements[this.dir].back(this,n); +}); +Drone.extend('up',function(n){ + if (typeof n == "undefined") + n = 1; + this.y+= n; +}); +Drone.extend('down',function(n){ + if (typeof n == "undefined") + n = 1; + this.y-= n; +}); +// +// position +// +Drone.prototype.getLocation = function() { + return new org.bukkit.Location(this.world, this.x, this.y, this.z); +}; +// +// building +// +Drone.extend('sign',function(message,block){ + if (message.constructor == Array){ + }else{ + message = [message]; + } + var bm = this._getBlockIdAndMeta(block); + block = bm[0]; + var meta = bm[1]; + if (block != 63 && block != 68){ + print("ERROR: Invalid block id for use in signs"); + return; + } + if (block == 68){ + meta = Drone.PLAYER_SIGN_FACING[this.dir%4]; + this.back(); + } + if (block == 63){ + meta = (12 + ((this.dir+2)*4)) % 16; + } + putSign(message,this.x,this.y,this.z,block,meta, this.world); + if (block == 68){ + this.fwd(); + } +}); +Drone.prototype.cuboida = function(/* Array */ blocks,w,h,d){ + var properBlocks = []; + var len = blocks.length; + for (var i = 0;i < len;i++){ + var bm = this._getBlockIdAndMeta(blocks[i]); + properBlocks.push([bm[0],bm[1]]); + } + if (typeof h == "undefined") + h = 1; + if (typeof d == "undefined") + d = 1; + if (typeof w == "undefined") + w = 1; + var that = this; + var dir = this.dir; + var pl = org.bukkit.entity.Player; + var cs = org.bukkit.command.BlockCommandSender; + var bi = 0; + /* + + */ + _traverse[dir].depth(that,d,function(){ + _traverseHeight(that,h,function(){ + _traverse[dir].width(that,w,function(){ + var block = that.world.getBlockAt(that.x,that.y,that.z); + var properBlock = properBlocks[bi%len]; + block.setTypeIdAndData(properBlock[0],properBlock[1],false); + bi++; + }); + }); + }); + return this; + +}; +/* + faster cuboid because blockid, meta and world must be provided + use this method when you need to repeatedly place blocks +*/ +Drone.prototype.cuboidX = function(blockType, meta, w, h, d){ + + if (typeof h == "undefined") + h = 1; + if (typeof d == "undefined") + d = 1; + if (typeof w == "undefined") + w = 1; + var that = this; + var dir = this.dir; + + var depthFunc = function(){ + var block = that.world.getBlockAt(that.x,that.y,that.z); + block.setTypeIdAndData(blockType,meta,false); + // wph 20130210 - dont' know if this is a bug in bukkit but for chests, + // the metadata is ignored (defaults to 2 - south facing) + // only way to change data is to set it using property/bean. + block.data = meta; + }; + var heightFunc = function(){ + _traverse[dir].depth(that,d,depthFunc); + }; + var widthFunc = function(){ + _traverseHeight(that,h,heightFunc); + }; + + _traverse[dir].width(that,w,widthFunc); + return this; + +}; + +Drone.prototype.cuboid = function(block,w,h,d){ + var bm = this._getBlockIdAndMeta(block); + return this.cuboidX(bm[0],bm[1], w,h,d); +}; +Drone.prototype.cuboid0 = function(block,w,h,d){ + this.chkpt('start_point'); + + // Front wall + this.cuboid(block, w, h, 1); + // Left wall + this.cuboid(block, 1, h, d); + // Right wall + this.right(w-1).cuboid(block, 1, h, d).left(w-1); + // Back wall + this.fwd(d-1).cuboid(block, w, h, 1); + + return this.move('start_point'); +}; +Drone.extend('door',function(door){ + if (typeof door == "undefined"){ + door = 64; + }else{ + door = 71; + } + this.cuboid(door+':' + this.dir).up().cuboid(door+':8').down(); +}); +Drone.extend('door2',function(door){ + if (typeof door == "undefined"){ + door = 64; + }else{ + door = 71; + } + this + .box(door+':' + this.dir).up() + .box(door+':8').right() + .box(door+':9').down() + .box(door+':' + this.dir).left(); +}); +// player dirs: 0 = east, 1 = south, 2 = west, 3 = north +// block dirs: 0 = east, 1 = west, 2 = south , 3 = north +// sign dirs: 5 = east, 3 = south, 4 = west, 2 = north +Drone.PLAYER_STAIRS_FACING = [0,2,1,3]; +// for blocks 68 (wall signs) 65 (ladders) 61,62 (furnaces) 23 (dispenser) and 54 (chest) +Drone.PLAYER_SIGN_FACING = [4,2,5,3]; +Drone.PLAYER_TORCH_FACING = [2,4,1,3]; + +var _getWorld = function(){ + var pl = org.bukkit.entity.Player; + var cs = org.bukkit.command.BlockCommandSender; + var world = (self instanceof pl)?self.location.world:(self instanceof cs)?self.block.location.world:null; + return world; +}; + +var _STAIRBLOCKS = {53: '5:0' // oak wood + ,67: 4 // cobblestone + ,108: 45 // brick + ,109: 98 // stone brick + ,114: 112 // nether brick + ,128: 24 // sandstone + ,134: '5:1' // spruce wood + ,135: '5:2' // birch wood + ,136: '5:3' // jungle wood + }; +// +// prism private implementation +// +var _prism = function(block,w,d) { + var stairEquiv = _STAIRBLOCKS[block]; + if (stairEquiv){ + this.fwd().prism(stairEquiv,w,d-2).back(); + var d2 = 0; + var middle = Math.floor(d/2); + var uc = 0,dc = 0; + while (d2 < d) + { + var di = (d2 < middle?this.dir:(this.dir+2)%4); + var bd = block + ':' + Drone.PLAYER_STAIRS_FACING[di]; + var putStep = true; + if (d2 == middle){ + if (d % 2 == 1){ + putStep = false; + } + } + if (putStep) + this.cuboid(bd,w); + if (d2 < middle-1){ + this.up(); + uc++; + } + var modulo = d % 2; + if (modulo == 1){ + if (d2 > middle && d2= middle && d2= 1){ + this.cuboid(block,w,1,d2); + d2 -= 2; + this.fwd().up(); + c++; + } + this.down(c).back(c); + } + return this; +}; +// +// prism0 private implementation +// +var _prism0 = function(block,w,d){ + this.prism(block,w,d) + .fwd().right() + .prism(0,w-2,d-2) + .left().back(); + var se = _STAIRBLOCKS[block]; + if (d % 2 == 1 && se){ + // top of roof will be open - need repair + var f = Math.floor(d/2); + this.fwd(f).up(f).cuboid(se,w).down(f).back(f); + } +}; +Drone.extend('prism0',_prism0); +Drone.extend('prism',_prism); +Drone.extend('box',Drone.prototype.cuboid); +Drone.extend('box0',Drone.prototype.cuboid0); +Drone.extend('boxa',Drone.prototype.cuboida); +// +// show the Drone's position and direction +// +Drone.prototype.toString = function(){ + var dirs = ["east","south","west","north"]; + return "x: " + this.x + " y: "+this.y + " z: " + this.z + " dir: " + this.dir + " "+dirs[this.dir]; +}; +Drone.prototype.debug = function(){ + print(this.toString()); + return this; +}; +/* + do the bresenham thing +*/ +var _bresenham = function(x0,y0,radius, setPixel, quadrants){ + // + // credit: Following code is copied almost verbatim from + // http://en.wikipedia.org/wiki/Midpoint_circle_algorithm + // Bresenham's circle algorithm + // + var f = 1 - radius; + var ddF_x = 1; + var ddF_y = -2 * radius; + var x = 0; + var y = radius; + var defaultQuadrants = {topleft: true, topright: true, bottomleft: true, bottomright: true}; + quadrants = quadrants?quadrants:defaultQuadrants; + /* + II | I + ------------ + III | IV + */ + if (quadrants.topleft || quadrants.topright) + setPixel(x0, y0 + radius); // quadrant I/II topmost + if (quadrants.bottomleft || quadrants.bottomright) + setPixel(x0, y0 - radius); // quadrant III/IV bottommost + if (quadrants.topright || quadrants.bottomright) + setPixel(x0 + radius, y0); // quadrant I/IV rightmost + if (quadrants.topleft || quadrants.bottomleft) + setPixel(x0 - radius, y0); // quadrant II/III leftmost + + while(x < y) + { + // ddF_x == 2 * x + 1; + // ddF_y == -2 * y; + // f == x*x + y*y - radius*radius + 2*x - y + 1; + if(f >= 0) + { + y--; + ddF_y += 2; + f += ddF_y; + } + x++; + ddF_x += 2; + f += ddF_x; + if (quadrants.topright){ + setPixel(x0 + x, y0 + y); // quadrant I + setPixel(x0 + y, y0 + x); // quadrant I + } + if (quadrants.topleft){ + setPixel(x0 - x, y0 + y); // quadrant II + setPixel(x0 - y, y0 + x); // quadrant II + } + if (quadrants.bottomleft){ + setPixel(x0 - x, y0 - y); // quadrant III + setPixel(x0 - y, y0 - x); // quadrant III + } + if (quadrants.bottomright){ + setPixel(x0 + x, y0 - y); // quadrant IV + setPixel(x0 + y, y0 - x); // quadrant IV + } + } +}; +var _getStrokeDir = function(x,y){ + var absY = Math.abs(y); + var absX = Math.abs(x); + var strokeDir = 0; + if (y > 0 && absY >= absX) + strokeDir = 0 ; //down + else if (y < 0 && absY >= absX) + strokeDir = 1 ; // up + else if (x > 0 && absX >= absY) + strokeDir = 2 ; // left + else if (x < 0 && absX >= absY) + strokeDir = 3 ; // right + return strokeDir; +}; +/* + The daddy of all arc-related API calls - + if you're drawing anything that bends it ends up here. +*/ +var _arc2 = function( params ) { + + var drone = params.drone; + var orientation = params.orientation?params.orientation:"horizontal"; + var quadrants = params.quadrants?params.quadrants:{ + topright:1, + topleft:2, + bottomleft:3, + bottomright:4 + }; + var stack = params.stack?params.stack:1; + var radius = params.radius; + var strokeWidth = params.strokeWidth?params.strokeWidth:1; + drone.chkpt('arc2'); + var x0, y0, gotoxy,setPixel; + + if (orientation == "horizontal"){ + gotoxy = function(x,y){ return drone.right(x).fwd(y);}; + drone.right(radius).fwd(radius).chkpt('center'); + switch (drone.dir) { + case 0: // east + case 2: // west + x0 = drone.z; + y0 = drone.x; + break; + case 1: // south + case 3: // north + x0 = drone.x; + y0 = drone.z; + } + setPixel = function(x,y) { + x = (x-x0); + y = (y-y0); + if (params.fill){ + // wph 20130114 more efficient esp. for large cylinders/spheres + if (y < 0){ + drone + .fwd(y).right(x) + .cuboidX(params.blockType,params.meta,1,stack,Math.abs(y*2)+1) + .back(y).left(x); + } + }else{ + if (strokeWidth == 1){ + gotoxy(x,y) + .cuboidX(params.blockType, + params.meta, + 1, // width + stack, // height + strokeWidth // depth + ) + .move('center'); + } else { + var strokeDir = _getStrokeDir(x,y); + var width = 1, depth = 1; + switch (strokeDir){ + case 0: // down + y = y-(strokeWidth-1); + depth = strokeWidth; + break; + case 1: // up + depth = strokeWidth; + break; + case 2: // left + width = strokeWidth; + x = x-(strokeWidth-1); + break; + case 3: // right + width = strokeWidth; + break; + } + gotoxy(x,y) + .cuboidX(params.blockType, params.meta, width, stack, depth) + .move('center'); + + } + } + }; + }else{ + // vertical + gotoxy = function(x,y){ return drone.right(x).up(y);}; + drone.right(radius).up(radius).chkpt('center'); + switch (drone.dir) { + case 0: // east + case 2: // west + x0 = drone.z; + y0 = drone.y; + break; + case 1: // south + case 3: // north + x0 = drone.x; + y0 = drone.y; + } + setPixel = function(x,y) { + x = (x-x0); + y = (y-y0); + if (params.fill){ + // wph 20130114 more efficient esp. for large cylinders/spheres + if (y < 0){ + drone + .up(y).right(x) + .cuboidX(params.blockType,params.meta,1,Math.abs(y*2)+1,stack) + .down(y).left(x); + } + }else{ + if (strokeWidth == 1){ + gotoxy(x,y) + .cuboidX(params.blockType,params.meta,strokeWidth,1,stack) + .move('center'); + }else{ + var strokeDir = _getStrokeDir(x,y); + var width = 1, height = 1; + switch (strokeDir){ + case 0: // down + y = y-(strokeWidth-1); + height = strokeWidth; + break; + case 1: // up + height = strokeWidth; + break; + case 2: // left + width = strokeWidth; + x = x-(strokeWidth-1); + break; + case 3: // right + width = strokeWidth; + break; + } + gotoxy(x,y) + .cuboidX(params.blockType, params.meta, width, height, stack) + .move('center'); + + } + } + }; + } + /* + setPixel assumes a 2D plane - need to put a block along appropriate plane + */ + _bresenham(x0,y0,radius,setPixel,quadrants); + + params.drone.move('arc2'); +}; + + +Drone.extend('arc',function(params) { + params.drone = this; + _arc2(params); +}); + +var _cylinder0 = function(block,radius,height,exactParams){ + var arcParams = { + radius: radius, + fill: false, + orientation: 'horizontal', + stack: height, + }; + + if (exactParams){ + arcParams.blockType = exactParams.blockType; + arcParams.meta = exactParams.meta; + }else{ + var md = this._getBlockIdAndMeta(block); + arcParams.blockType = md[0]; + arcParams.meta = md[1]; + } + return this.arc(arcParams); +}; +var _cylinder1 = function(block,radius,height,exactParams){ + var arcParams = { + radius: radius, + fill: true, + orientation: 'horizontal', + stack: height, + }; + + if (exactParams){ + arcParams.blockType = exactParams.blockType; + arcParams.meta = exactParams.meta; + }else{ + var md = this._getBlockIdAndMeta(block); + arcParams.blockType = md[0]; + arcParams.meta = md[1]; + } + return this.arc(arcParams); +}; +var _paste = function(name) +{ + var ccContent = Drone.clipBoard[name]; + var srcBlocks = ccContent.blocks; + var srcDir = ccContent.dir; // direction player was facing when copied. + var dirOffset = (4 + (this.dir - srcDir)) %4; + var that = this; + + _traverse[this.dir].width(that,srcBlocks.length,function(ww){ + var h = srcBlocks[ww].length; + _traverseHeight(that,h,function(hh){ + var d = srcBlocks[ww][hh].length; + _traverse[that.dir].depth(that,d,function(dd){ + var b = srcBlocks[ww][hh][dd]; + var bm = that._getBlockIdAndMeta(b); + var cb = bm[0]; + var md = bm[1]; + // + // need to adjust blocks which face a direction + // + switch (cb) + { + // + // doors + // + case 64: // wood + case 71: // iron + // top half of door doesn't need to change + if (md < 8) { + md = (md + dirOffset) % 4; + } + break; + // + // stairs + // + case 53: // oak + case 67: // cobblestone + case 108: // red brick + case 109: // stone brick + case 114: // nether brick + case 128: // sandstone + case 134: // spruce + case 135: // birch + case 136: // junglewood + var dir = md & 0x3; + var a = Drone.PLAYER_STAIRS_FACING; + var len = a.length; + for (var c=0;c < len;c++){ + if (a[c] == dir){ + break; + } + } + c = (c + dirOffset) %4; + var newDir = a[c]; + md = (md >>2<<2) + newDir; + break; + // + // signs , ladders etc + // + case 23: // dispenser + case 54: // chest + case 61: // furnace + case 62: // burning furnace + case 65: // ladder + case 68: // wall sign + var a = Drone.PLAYER_SIGN_FACING; + var len = a.length; + for (var c=0;c < len;c++){ + if (a[c] == md){ + break; + } + } + c = (c + dirOffset) %4; + var newDir = a[c]; + md = newDir; + break; + } + putBlock(that.x,that.y,that.z,cb,md,that.world); + }); + }); + }); +}; +var _getDirFromRotation = function(r){ + // 0 = east, 1 = south, 2 = west, 3 = north + // 46 to 135 = west + // 136 to 225 = north + // 226 to 315 = east + // 316 to 45 = south + + r = (r + 360) % 360; // east could be 270 or -90 + + if (r > 45 && r <= 135) + return 2; // west + if (r > 135 && r <= 225) + return 3; // north + if (r > 225 && r <= 315) + return 0; // east + if (r > 315 || r < 45) + return 1; // south +}; +var _getBlockIdAndMeta = function(b){ + var defaultMeta = 0; + if (typeof b == 'string'){ + var bs = b; + var sp = bs.indexOf(':'); + if (sp == -1){ + b = parseInt(bs); + // wph 20130414 - use sensible defaults for certain blocks e.g. stairs + // should face the drone. + for (var i in blocks.stairs){ + if (blocks.stairs[i] === b){ + defaultMeta = Drone.PLAYER_STAIRS_FACING[this.dir]; + break; + } + } + return [b,defaultMeta]; + } + b = parseInt(bs.substring(0,sp)); + var md = parseInt(bs.substring(sp+1,bs.length)); + return [b,md]; + }else{ + // wph 20130414 - use sensible defaults for certain blocks e.g. stairs + // should face the drone. + for (var i in blocks.stairs){ + if (blocks.stairs[i] === b){ + defaultMeta = Drone.PLAYER_STAIRS_FACING[this.dir]; + break; + } + } + return [b,defaultMeta]; + } +}; +// +// movement +// +var _movements = [{},{},{},{}]; +// east +_movements[0].right = function(that,n){ that.z +=n; return that;}; +_movements[0].left = function(that,n){ that.z -=n; return that;}; +_movements[0].fwd = function(that,n){ that.x +=n; return that;}; +_movements[0].back = function(that,n){ that.x -= n; return that;}; +// south +_movements[1].right = _movements[0].back; +_movements[1].left = _movements[0].fwd; +_movements[1].fwd = _movements[0].right; +_movements[1].back = _movements[0].left; +// west +_movements[2].right = _movements[0].left; +_movements[2].left = _movements[0].right; +_movements[2].fwd = _movements[0].back; +_movements[2].back = _movements[0].fwd; +// north +_movements[3].right = _movements[0].fwd; +_movements[3].left = _movements[0].back; +_movements[3].fwd = _movements[0].left; +_movements[3].back = _movements[0].right; +var _traverse = [{},{},{},{}]; +// east +_traverse[0].width = function(that,n,callback){ + var s = that.z, e = s + n; + for (; that.z < e; that.z++){ + callback(that.z-s); + } + that.z = s; +}; +_traverse[0].depth = function(that,n,callback){ + var s = that.x, e = s+n; + for (;that.x < e;that.x++){ + callback(that.x-s); + } + that.x = s; +}; +// south +_traverse[1].width = function(that,n,callback){ + var s = that.x, e = s-n; + for (;that.x > e;that.x--){ + callback(s-that.x); + } + that.x = s; +}; +_traverse[1].depth = _traverse[0].width; +// west +_traverse[2].width = function(that,n,callback){ + var s = that.z, e = s-n; + for (;that.z > e;that.z--){ + callback(s-that.z); + } + that.z = s; +}; +_traverse[2].depth = _traverse[1].width; +// north +_traverse[3].width = _traverse[0].depth; +_traverse[3].depth = _traverse[2].width; +var _traverseHeight = function(that,n,callback){ + var s = that.y, e = s + n; + for (; that.y < e; that.y++){ + callback(that.y-s); + } + that.y = s; +}; +// +// standard fisher-yates shuffle algorithm +// +var _fisherYates = function( myArray ) { + var i = myArray.length; + if ( i == 0 ) return false; + while ( --i ) { + var j = Math.floor( Math.random() * ( i + 1 ) ); + var tempi = myArray[i]; + var tempj = myArray[j]; + myArray[i] = tempj; + myArray[j] = tempi; + } +}; +var _copy = function(name, w, h, d) { + var that = this; + var ccContent = []; + _traverse[this.dir].width(that,w,function(ww){ + ccContent.push([]); + _traverseHeight(that,h,function(hh){ + ccContent[ww].push([]); + _traverse[that.dir].depth(that,d,function(dd){ + var b = that.world.getBlockAt(that.x,that.y,that.z); + ccContent[ww][hh][dd] = b; + }); + }); + }); + Drone.clipBoard[name] = {dir: this.dir, blocks: ccContent}; +}; +var _garden = function(w,d) { + // make sure grass is present first + this.down().box(2,w,1,d).up(); + + // make flowers more common than long grass + var dist = {37: 3, // red flower + 38: 3, // yellow flower + '31:1': 2, // long grass + 0: 1 + }; + + return this.rand(dist,w,1,d); +}; + +var _rand = function(blockDistribution){ + if (!(blockDistribution.constructor == Array)){ + var a = []; + for (var p in blockDistribution){ + var n = blockDistribution[p]; + for (var i = 0;i < n;i++){ + a.push(p); + } + } + blockDistribution = a; + } + while (blockDistribution.length < 1000){ + // make array bigger so that it's more random + blockDistribution = blockDistribution.concat(blockDistribution); + } + _fisherYates(blockDistribution); + return blockDistribution; +}; +Drone.extend('rand',function(dist,w,h,d){ + var randomized = _rand(dist); + this.boxa(randomized,w,h,d); +}); +var _trees = { + oak: org.bukkit.TreeType.BIG_TREE , + birch: org.bukkit.TreeType.BIRCH , + jungle: org.bukkit.TreeType.JUNGLE, + spruce: org.bukkit.TreeType.REDWOOD +}; +for (var p in _trees) +{ + Drone.extend(p, function(v) { + return function() { + var block = this.world.getBlockAt(this.x,this.y,this.z); + if (block.typeId == 2){ + this.up(); + } + var treeLoc = new org.bukkit.Location(this.world,this.x,this.y,this.z); + var successful = treeLoc.world.generateTree(treeLoc,v); + if (block.typeId == 2){ + this.down(); + } + }; + }(_trees[p])); +} + +// +// Drone's clipboard +// +Drone.clipBoard = {}; +Drone.extend('garden',_garden); +Drone.extend('copy', _copy); +Drone.extend('paste',_paste); +Drone.extend('cylinder0',_cylinder0); +Drone.extend('cylinder', _cylinder1); +// +// wph 20130130 - make this a method - extensions can use it. +// +Drone.prototype._getBlockIdAndMeta = _getBlockIdAndMeta; + + diff --git a/src/main/javascript/plugins/drone/sphere.js b/src/main/javascript/plugins/drone/sphere.js new file mode 100644 index 0000000..58f9451 --- /dev/null +++ b/src/main/javascript/plugins/drone/sphere.js @@ -0,0 +1,268 @@ +var Drone = require('./drone'); +module.exports = Drone; + +/************************************************************************ +Drone.sphere() method +===================== +Creates a sphere. + +Parameters +---------- + + * block - The block the sphere will be made of. + * radius - The radius of the sphere. + +Example +------- +To create a sphere of Iron with a radius of 10 blocks... + + sphere( blocks.iron, 10); + +![sphere example](img/sphereex1.png) + +Spheres are time-consuming to make. You *can* make large spheres (250 radius) but expect the +server to be very busy for a couple of minutes while doing so. + +***/ +Drone.extend('sphere', function(block,radius) +{ + var lastRadius = radius; + var slices = [[radius,0]]; + var diameter = radius*2; + var bm = this._getBlockIdAndMeta(block); + + var r2 = radius*radius; + for (var i = 0; i <= radius;i++){ + var newRadius = Math.round(Math.sqrt(r2 - i*i)); + if (newRadius == lastRadius) + slices[slices.length-1][1]++; + else + slices.push([newRadius,1]); + lastRadius = newRadius; + } + this.chkpt('sphere'); + // + // mid section + // + this.up(radius - slices[0][1]) + .cylinder(block,radius,(slices[0][1]*2)-1,{blockType: bm[0],meta: bm[1]}) + .down(radius-slices[0][1]); + + var yOffset = -1; + for (var i = 1; i < slices.length;i++) + { + yOffset += slices[i-1][1]; + var sr = slices[i][0]; + var sh = slices[i][1]; + var v = radius + yOffset, h = radius-sr; + // northern hemisphere + this.up(v).fwd(h).right(h) + .cylinder(block,sr,sh,{blockType: bm[0],meta: bm[1]}) + .left(h).back(h).down(v); + + // southern hemisphere + v = radius - (yOffset+sh+1); + this.up(v).fwd(h).right(h) + .cylinder(block,sr,sh,{blockType: bm[0],meta: bm[1]}) + .left(h).back(h). down(v); + } + return this.move('sphere'); +}); +/************************************************************************ +Drone.sphere0() method +====================== +Creates an empty sphere. + +Parameters +---------- + + * block - The block the sphere will be made of. + * radius - The radius of the sphere. + +Example +------- +To create a sphere of Iron with a radius of 10 blocks... + + sphere0( blocks.iron, 10); + +Spheres are time-consuming to make. You *can* make large spheres (250 radius) but expect the +server to be very busy for a couple of minutes while doing so. + +***/ +Drone.extend('sphere0', function(block,radius) +{ +/* + this.sphere(block,radius) + .fwd().right().up() + .sphere(0,radius-1) + .back().left().down(); + +*/ + + var lastRadius = radius; + var slices = [[radius,0]]; + var diameter = radius*2; + var bm = this._getBlockIdAndMeta(block); + + var r2 = radius*radius; + for (var i = 0; i <= radius;i++){ + var newRadius = Math.round(Math.sqrt(r2 - i*i)); + if (newRadius == lastRadius) + slices[slices.length-1][1]++; + else + slices.push([newRadius,1]); + lastRadius = newRadius; + } + this.chkpt('sphere0'); + // + // mid section + // + //.cylinder(block,radius,(slices[0][1]*2)-1,{blockType: bm[0],meta: bm[1]}) + this.up(radius - slices[0][1]) + .arc({blockType: bm[0], + meta: bm[1], + radius: radius, + strokeWidth: 2, + stack: (slices[0][1]*2)-1, + fill: false + }) + .down(radius-slices[0][1]); + + var yOffset = -1; + var len = slices.length; + for (var i = 1; i < len;i++) + { + yOffset += slices[i-1][1]; + var sr = slices[i][0]; + var sh = slices[i][1]; + var v = radius + yOffset, h = radius-sr; + // northern hemisphere + // .cylinder(block,sr,sh,{blockType: bm[0],meta: bm[1]}) + this.up(v).fwd(h).right(h) + .arc({ + blockType: bm[0], + meta: bm[1], + radius: sr, + stack: sh, + fill: false, + strokeWidth: i Date: Tue, 24 Dec 2013 00:11:08 +0000 Subject: [PATCH 027/456] reorg --- build.xml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/build.xml b/build.xml index 530179e..3f64ef4 100644 --- a/build.xml +++ b/build.xml @@ -5,6 +5,7 @@ + @@ -73,13 +74,13 @@ Retrieving Coffeescript compiler - + + dest="${build}/coffeescript/lib/coffeescript.js"/> - + From 7f3b17a07cdca0b998be26f669f417c6d6508e38 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 24 Dec 2013 00:12:56 +0000 Subject: [PATCH 028/456] moved drone to plugins directory --- src/main/javascript/drone/blocks.js | 274 --- src/main/javascript/drone/blocktype.js | 373 ---- src/main/javascript/drone/contrib/castle.js | 49 - .../javascript/drone/contrib/chessboard.js | 33 - src/main/javascript/drone/contrib/cottage.js | 83 - .../javascript/drone/contrib/dancefloor.js | 36 - src/main/javascript/drone/contrib/fort.js | 66 - src/main/javascript/drone/contrib/logo.js | 217 -- src/main/javascript/drone/contrib/partial.js | 14 - src/main/javascript/drone/contrib/rainbow.js | 41 - src/main/javascript/drone/contrib/rboxcall.js | 32 - .../javascript/drone/contrib/redstonewire.js | 105 - .../drone/contrib/skyscraper-example.js | 15 - .../javascript/drone/contrib/spiral_stairs.js | 44 - src/main/javascript/drone/contrib/streamer.js | 19 - src/main/javascript/drone/contrib/temple.js | 23 - src/main/javascript/drone/drone.js | 1810 ----------------- src/main/javascript/drone/sphere.js | 265 --- src/main/javascript/drone/test.js | 21 - 19 files changed, 3520 deletions(-) delete mode 100644 src/main/javascript/drone/blocks.js delete mode 100644 src/main/javascript/drone/blocktype.js delete mode 100644 src/main/javascript/drone/contrib/castle.js delete mode 100644 src/main/javascript/drone/contrib/chessboard.js delete mode 100644 src/main/javascript/drone/contrib/cottage.js delete mode 100644 src/main/javascript/drone/contrib/dancefloor.js delete mode 100644 src/main/javascript/drone/contrib/fort.js delete mode 100644 src/main/javascript/drone/contrib/logo.js delete mode 100644 src/main/javascript/drone/contrib/partial.js delete mode 100644 src/main/javascript/drone/contrib/rainbow.js delete mode 100644 src/main/javascript/drone/contrib/rboxcall.js delete mode 100644 src/main/javascript/drone/contrib/redstonewire.js delete mode 100644 src/main/javascript/drone/contrib/skyscraper-example.js delete mode 100644 src/main/javascript/drone/contrib/spiral_stairs.js delete mode 100644 src/main/javascript/drone/contrib/streamer.js delete mode 100644 src/main/javascript/drone/contrib/temple.js delete mode 100644 src/main/javascript/drone/drone.js delete mode 100644 src/main/javascript/drone/sphere.js delete mode 100644 src/main/javascript/drone/test.js diff --git a/src/main/javascript/drone/blocks.js b/src/main/javascript/drone/blocks.js deleted file mode 100644 index c265d17..0000000 --- a/src/main/javascript/drone/blocks.js +++ /dev/null @@ -1,274 +0,0 @@ -/************************************************************************ -Blocks Module -============= -You hate having to lookup [Data Values][dv] when you use ScriptCraft's Drone() functions. So do I. -So I created this blocks object which is a helper object for use in construction. - -Examples --------- - - box( blocks.oak ); // creates a single oak wood block - box( blocks.sand, 3, 2, 1 ); // creates a block of sand 3 wide x 2 high x 1 long - box( blocks.wool.green, 2 ); // creates a block of green wool 2 blocks wide - -Color aliased properties that were a direct descendant of the blocks object are no longer used to avoid confusion with carpet and stained clay blocks. In addition, there's a convenience array `blocks.rainbow` which is an array of the 7 colors of the rainbow (or closest approximations). - -***/ -var blocks = { - air: 0, - stone: 1, - grass: 2, - dirt: 3, - cobblestone: 4, - oak: 5, - spruce: '5:1', - birch: '5:2', - jungle: '5:3', - sapling: { - oak: 6, - spruce: '6:1', - birch: '62:2', - jungle: '6:3' - }, - bedrock: 7, - water: 8, - water_still: 9, - lava: 10, - lava_still: 11, - sand: 12, - gravel: 13, - gold_ore: 14, - iron_ore: 15, - coal_ore: 16, - wood: 17, - leaves: 18, - sponge: 19, - glass: 20, - lapis_lazuli_ore: 21, - lapis_lazuli_block: 22, - dispenser: 23, - sandstone: 24, - note: 25, - bed: 26, - powered_rail: 27, - detector_rail: 28, - sticky_piston: 29, - cobweb: 30, - grass_tall: 31, - dead_bush: 32, - piston: 33, - piston_extn: 34, - wool: { - white: 35 // All other colors added below - }, - dandelion: 37, - flower_yellow: 37, - rose: 38, - flower_red: 38, - mushroom_brown: 39, - mushroom_red: 40, - gold: 41, - iron: 42, - tnt: 46, - bookshelf: 47, - moss_stone: 48, - obsidian: 49, - torch: 50, - fire: 51, - monster_spawner: 52, - stairs: { - oak: 53, - cobblestone: 67, - brick: 108, - stone: 109, - nether: 114, - sandstone: 128, - spruce: 134, - birch: 135, - jungle: 136, - quartz: 156 - }, - chest: 54, - redstone_wire: 55, - diamond_ore: 56, - diamond: 57, - crafting_table: 58, - wheat_seeds: 59, - farmland: 60, - furnace: 61, - furnace_burning: 62, - sign_post: 63, - door_wood: 64, - ladder: 65, - rail: 66, - sign: 68, - lever: 69, - pressure_plate_stone: 70, - door_iron: 71, - pressure_plate_wood: 72, - redstone_ore: 73, - redstone_ore_glowing: 74, - torch_redstone: 75, - torch_redstone_active: 76, - stone_button: 77, - ice: 79, - snow: 80, - cactus: 81, - clay: 82, - sugar_cane: 83, - jukebox: 84, - fence: 85, - pumpkin: 86, - netherrack: 87, - soulsand: 88, - glowstone: 89, - netherportal: 90, - jackolantern: 91, - cake: 92, - redstone_repeater: 93, - redeston_repeater_active: 94, - chest_locked: 95, - trapdoor: 96, - monster_egg: 97, - brick: { - stone: 98, - mossy: '98:1', - cracked: '98:2', - chiseled: '98:3', - red: 45 - }, - mushroom_brown_huge: 99, - mushroom_red_huge: 100, - iron_bars: 101, - glass_pane: 102, - melon: 103, - pumpkin_stem: 104, - melon_stem: 105, - vines: 106, - fence_gate: 107, - mycelium: 110, - lily_pad: 111, - nether: 112, - nether_fence: 113, - netherwart: 115, - table_enchantment: 116, - brewing_stand: 117, - cauldron: 118, - endportal: 119, - endportal_frame: 120, - endstone: 121, - dragon_egg: 122, - redstone_lamp: 123, - redstone_lamp_active: 124, - slab: { - snow: 78, - stone: 44, - sandstone: '44:1', - wooden: '44:2', - cobblestone: '44:3', - brick: '44:4', - stonebrick: '44:5', - netherbrick:'44:6', - quartz: '44:7', - oak: 126, - spruce: '126:1', - birch: '126:2', - jungle: '126:3', - upper: { - stone: '44:8', - sandstone: '44:9', - wooden: '44:10', - cobblestone: '44:11', - brick: '44:12', - stonebrick: '44:13', - netherbrick:'44:14', - quartz: '44:15', - oak: '126:8', - spruce: '126:9', - birch: '126:10', - jungle: '126:11', - } - }, - cocoa: 127, - emerald_ore: 129, - enderchest: 130, - tripwire_hook: 131, - tripwire: 132, - emerald: 133, - command: 137, - beacon: 138, - cobblestone_wall: 139, - flowerpot: 140, - carrots: 141, - potatoes: 142, - button_wood: 143, - mobhead: 144, - anvil: 145, - chest_trapped: 146, - pressure_plate_weighted_light: 147, - pressure_plate_weighted_heavy: 148, - redstone_comparator: 149, - redstone_comparator_active: 150, - daylight_sensor: 151, - redstone: 152, - netherquartzore: 153, - hopper: 154, - quartz: 155, - rail_activator: 157, - dropper: 158, - stained_clay: { - white: 159 // All other colors added below - }, - hay: 170, - carpet: { - white: 171 // All other colors added below - }, - hardened_clay: 172, - coal_block: 173 -}; - -(function() { - // Add all available colors to colorized block collections - - var colors = { - orange: ':1', - magenta: ':2', - lightblue: ':3', - yellow: ':4', - lime: ':5', - pink: ':6', - gray: ':7', - lightgray: ':8', - cyan: ':9', - purple: ':10', - blue: ':11', - brown: ':12', - green: ':13', - red: ':14', - black: ':15' - }; - var colorized_blocks = ["wool", "stained_clay", "carpet"]; - - for (var i = 0, len = colorized_blocks.length; i < len; i++) { - var block = colorized_blocks[i], - data_value = blocks[block].white; - - for (var color in colors) { - blocks[block][color] = data_value + colors[color]; - } - }; - - /* - rainbow colors - a convenience - Color aliased properties that were a direct descendant of the blocks - object are no longer used to avoid confusion with carpet and stained - clay blocks. - */ - blocks.rainbow = [blocks.wool.red, - blocks.wool.orange, - blocks.wool.yellow, - blocks.wool.lime, - blocks.wool.lightblue, - blocks.wool.blue, - blocks.wool.purple]; -})(); diff --git a/src/main/javascript/drone/blocktype.js b/src/main/javascript/drone/blocktype.js deleted file mode 100644 index c1ca783..0000000 --- a/src/main/javascript/drone/blocktype.js +++ /dev/null @@ -1,373 +0,0 @@ -/************************************************************************ -Drone.blocktype() method -======================== -Creates the text out of blocks. Useful for large-scale in-game signs. - -Parameters ----------- - - * message - The message to create - (use `\n` for newlines) - * foregroundBlock (default: black wool) - The block to use for the foreground - * backgroundBlock (default: none) - The block to use for the background - -Example -------- -To create a 2-line high message using glowstone... - - blocktype("Hello\nWorld",blocks.glowstone); - -![blocktype example][imgbt1] - -[imgbt1]: img/blocktype1.png - -***/ -(function(){ - - var bitmaps = { - raw: { - '0':' ### '+ - ' # # '+ - ' # # '+ - ' # # '+ - ' ### ', - - '1':' # '+ - ' ## '+ - ' # '+ - ' # '+ - ' ### ', - - '2':' ### '+ - ' # '+ - ' ### '+ - ' # '+ - ' ### ', - - '3':' ### '+ - ' # '+ - ' ## '+ - ' # '+ - ' ### ', - - '4':' # '+ - ' ## '+ - ' # # '+ - ' ### '+ - ' # ', - - '5':' ### '+ - ' # '+ - ' ### '+ - ' # '+ - ' ### ', - - '6':' ### '+ - ' # '+ - ' ### '+ - ' # # '+ - ' ### ', - - '7':' ### '+ - ' # '+ - ' # '+ - ' # '+ - ' # ', - - '8':' ### '+ - ' # # '+ - ' ### '+ - ' # # '+ - ' ### ', - - '9':' ### '+ - ' # # '+ - ' ### '+ - ' # '+ - ' ### ', - - 'a':' ### '+ - ' # # '+ - ' ### '+ - ' # # '+ - ' # # ', - - 'b':' ## '+ - ' # # '+ - ' ## '+ - ' # # '+ - ' ## ', - - 'c':' ## '+ - ' # '+ - ' # '+ - ' # '+ - ' ## ', - - 'd':' ## '+ - ' # # '+ - ' # # '+ - ' # # '+ - ' ## ', - - 'e':' ### '+ - ' # '+ - ' ## '+ - ' # '+ - ' ### ', - - 'f':' ### '+ - ' # '+ - ' ## '+ - ' # '+ - ' # ', - - 'g':' ### '+ - ' # '+ - ' # '+ - ' # # '+ - ' ### ', - - 'h':' # # '+ - ' # # '+ - ' ### '+ - ' # # '+ - ' # # ', - - 'i':' ### '+ - ' # '+ - ' # '+ - ' # '+ - ' ### ', - - 'j':' ### '+ - ' # '+ - ' # '+ - ' # '+ - ' # ', - - 'k':' # '+ - ' # # '+ - ' ## '+ - ' # # '+ - ' # # ', - - 'l':' # '+ - ' # '+ - ' # '+ - ' # '+ - ' ### ', - - 'm':' # # '+ - ' ### '+ - ' # # '+ - ' # # '+ - ' # # ', - - 'n':' ## '+ - ' # # '+ - ' # # '+ - ' # # '+ - ' # # ', - - 'o':' # '+ - ' # # '+ - ' # # '+ - ' # # '+ - ' # ', - - 'p':' ### '+ - ' # # '+ - ' ### '+ - ' # '+ - ' # ', - - 'q':' ### '+ - ' # # '+ - ' # # '+ - ' ### '+ - ' # ', - - 'r':' ## '+ - ' # # '+ - ' ## '+ - ' # # '+ - ' # # ', - - 's':' ## '+ - ' # '+ - ' ### '+ - ' # '+ - ' ## ', - - 't':' ### '+ - ' # '+ - ' # '+ - ' # '+ - ' # ', - - 'u':' # # '+ - ' # # '+ - ' # # '+ - ' # # '+ - ' ### ', - - 'v':' # # '+ - ' # # '+ - ' # # '+ - ' # # '+ - ' # ', - - 'w':' # # '+ - ' # # '+ - ' # # '+ - ' ### '+ - ' # # ', - - 'x':' # # '+ - ' # # '+ - ' # '+ - ' # # '+ - ' # # ', - - 'y':' # # '+ - ' # # '+ - ' # # '+ - ' # '+ - ' # ', - - 'z':' ### '+ - ' # '+ - ' # '+ - ' # '+ - ' ### ', - - '!':' # '+ - ' # '+ - ' # '+ - ' '+ - ' # ', - - ':':' '+ - ' # '+ - ' '+ - ' # '+ - ' ', - - ';':' '+ - ' # '+ - ' '+ - ' # '+ - ' # ', - - ',':' '+ - ' '+ - ' '+ - ' # '+ - ' # ', - - '/':' # '+ - ' # '+ - ' # '+ - ' # '+ - ' # ', - - '+':' '+ - ' # '+ - ' ### '+ - ' # '+ - ' ', - - '-':' '+ - ' '+ - ' ### '+ - ' '+ - ' ', - - '.':' '+ - ' '+ - ' '+ - ' '+ - ' # ', - - "'":' # '+ - ' # '+ - ' '+ - ' '+ - ' ', - - ' ':' '+ - ' '+ - ' '+ - ' '+ - ' ' - }, - computed: {} - }; - /* - wph 20130121 compute the width, and x,y coords of pixels ahead of time - */ - for (var c in bitmaps.raw){ - var bits = bitmaps.raw[c]; - var width = bits.length/5; - var bmInfo = {"width": width,"pixels":[]} - bitmaps.computed[c] = bmInfo; - for (var j = 0; j < bits.length; j++){ - if (bits.charAt(j) != ' '){ - bmInfo.pixels.push([j%width,Math.ceil(j/width)]); - } - } - } - - - // - // message - // string with text to be displayed - // fg - // foreground material. The material the text will be in. - // bg - // background material, optional. The negative space within the bounding box of the text. - // - Drone.extend('blocktype', function(message,fg,bg){ - - this.chkpt('blocktext'); - - if (typeof fg == "undefined") - fg = blocks.wool.black; - - var bmfg = this._getBlockIdAndMeta(fg); - var bmbg = null; - if (typeof bg != "undefined") - bmbg = this._getBlockIdAndMeta(bg); - var lines = message.split("\n"); - var lineCount = lines.length; - for (var h = 0;h < lineCount; h++) { - var line = lines[h]; - line = line.toLowerCase().replace(/[^0-9a-z \.\-\+\/\;\'\:\!]/g,""); - this.up(7*(lineCount-(h+1))); - - for (var i =0;i < line.length; i++) { - var ch = line.charAt(i) - var bits = bitmaps.computed[ch]; - if (typeof bits == "undefined"){ - bits = bitmaps.computed[' ']; - } - var charWidth = bits.width; - if (typeof bg != "undefined") - this.cuboidX(bmbg[0],bmbg[1],charWidth,7,1); - for (var j = 0;j < bits.pixels.length;j++){ - this.chkpt('btbl'); - var x = bits.pixels[j][0]; - var y = bits.pixels[j][1]; - this.up(6-y).right(x).cuboidX(bmfg[0],bmfg[1]); - this.move('btbl'); - } - this.right(charWidth-1); - } - this.move('blocktext'); - } - - return this.move('blocktext'); - }); -}()); - - diff --git a/src/main/javascript/drone/contrib/castle.js b/src/main/javascript/drone/contrib/castle.js deleted file mode 100644 index 9cb9d46..0000000 --- a/src/main/javascript/drone/contrib/castle.js +++ /dev/null @@ -1,49 +0,0 @@ -// -// a castle is just a big wide fort with 4 taller forts at each corner -// -Drone.extend('castle', function(side, height) -{ - // - // use sensible default parameter values - // if no parameters are supplied - // - if (typeof side == "undefined") - side = 24; - if (typeof height == "undefined") - height = 10; - if (height < 8 || side < 20) - throw new java.lang.RuntimeException("Castles must be at least 20 wide X 8 tall"); - // - // remember where the drone is so it can return 'home' - // - this.chkpt('castle'); - // - // how big the towers at each corner will be... - // - var towerSide = 10; - var towerHeight = height+4; - - // - // the main castle building will be front and right of the first tower - // - this.fwd(towerSide/2).right(towerSide/2); - // - // the castle is really just a big fort with 4 smaller 'tower' forts at each corner - // - this.fort(side,height); - // - // move back to start position - // - this.move('castle'); - // - // now place 4 towers at each corner (each tower is another fort) - // - for (var corner = 0; corner < 4; corner++) - { - // construct a 'tower' fort - this.fort(towerSide,towerHeight); - // move forward the length of the castle then turn right - this.fwd(side+towerSide-1).turn(); - } - return this.move('castle'); -}); diff --git a/src/main/javascript/drone/contrib/chessboard.js b/src/main/javascript/drone/contrib/chessboard.js deleted file mode 100644 index e05679d..0000000 --- a/src/main/javascript/drone/contrib/chessboard.js +++ /dev/null @@ -1,33 +0,0 @@ -/** -* Creates a tile pattern of given block types and size -* -* Paramters: -* whiteBlock - blockId used for the traditional white portion of the chessboard -* blackBlock - blockId used for the traditional black portion of the chessboard -* width - width of the chessboard -* height - height of the chessboard -*/ -Drone.extend("chessboard", function(whiteBlock, blackBlock, width, depth) { - this.chkpt('chessboard-start'); - if (typeof whiteBlock == "undefined") - whiteBlock = blocks.wool.white; - if (typeof blackBlock == "undefined") - blackBlock = blocks.wool.black; - if (typeof width == "undefined") - width = 8; - if (typeof depth == "undefined") - depth = width; - - for(var i = 0; i < width; ++i) { - for(var j = 0; j < depth; ++j) { - var block = blackBlock; - if((i+j)%2 == 1) { - block = whiteBlock; - } - this.box(block); - this.right(); - } - this.move('chessboard-start').fwd(i+1); - } - return this.move('chessboard-start'); -}); diff --git a/src/main/javascript/drone/contrib/cottage.js b/src/main/javascript/drone/contrib/cottage.js deleted file mode 100644 index e5a2321..0000000 --- a/src/main/javascript/drone/contrib/cottage.js +++ /dev/null @@ -1,83 +0,0 @@ -// -// need to use the drone module to create buildings easily -// it can be done using calls to putBlock(), putSign(), getPlayerPos() and getMousePos() -// but it's easier to use the Drone class -// __folder is a special javascript variable whose value is the directory where the -// current script resides. -// -// usage: -// [1] to build a cottage at the player's current location or the cross-hairs location... -// -// /js cottage(); -// -// [2] to build a cottage using an existing drone... -// -// /js drone.cottage(); -// - -Drone.extend('cottage',function () -{ - this.chkpt('cottage') - .box0(48,7,2,6) // 4 walls - .right(3).door() // door front and center - .up(1).left(2).box(102) // windows to left and right - .right(4).box(102) - .left(5).up().prism0(53,7,6); - // - // put up a sign near door. - // - this.down().right(4).sign(["Home","Sweet","Home"],68); - - return this.move('cottage'); -}); -// -// a more complex script that builds an tree-lined avenue with -// cottages on both sides. -// -Drone.extend('cottage_road', function(numberCottages) -{ - if (typeof numberCottages == "undefined"){ - numberCottages = 6; - } - var i=0, distanceBetweenTrees = 11; - // - // step 1 build the road. - // - var cottagesPerSide = Math.floor(numberCottages/2); - this - .chkpt("cottage_road") // make sure the drone's state is saved. - .box(43,3,1,cottagesPerSide*(distanceBetweenTrees+1)) // build the road - .up().right() // now centered in middle of road - .chkpt("cr"); // will be returning to this position later - - // - // step 2 line the road with trees - // - for (; i < cottagesPerSide+1;i++){ - this - .left(5).oak() - .right(10).oak() - .left(5) // return to middle of road - .fwd(distanceBetweenTrees+1); // move forward. - } - this.move("cr").back(6); // move back 1/2 the distance between trees - - // this function builds a path leading to a cottage. - function pathAndCottage(d){ - return d.down().box(43,1,1,5).fwd(5).left(3).up().cottage(); - }; - // - // step 3 build cottages on each side - // - for (i = 0;i < cottagesPerSide; i++) - { - this.fwd(distanceBetweenTrees+1).chkpt("r"+i); - // build cottage on left - pathAndCottage(this.turn(3)).move("r"+i); - // build cottage on right - pathAndCottage(this.turn()).move("r"+i); - } - // return drone to where it was at start of function - return this.move("cottage_road"); -}); - diff --git a/src/main/javascript/drone/contrib/dancefloor.js b/src/main/javascript/drone/contrib/dancefloor.js deleted file mode 100644 index b961a19..0000000 --- a/src/main/javascript/drone/contrib/dancefloor.js +++ /dev/null @@ -1,36 +0,0 @@ -// -// Create a floor of colored tiles some of which emit light. -// The tiles change color every second creating a strobe-lit dance-floor. -// -// See it in action here => http://www.youtube.com/watch?v=UEooBt6NTFo -// -Drone.extend('dancefloor',function(width,length) -{ - if (typeof width == "undefined") - width = 5; - if (typeof length == "undefined") - length = width; - // - // create a separate Drone object to lay down disco tiles - // - var disco = new Drone(this.x, this.y, this.z, this.dir, this.world); - // - // under-floor lighting - // - disco.down().box(89,width,1,length).up(); - var floorTiles = [35,35,'35:1','35:2','35:3','35:4','35:4','35:4','35:6',20,20]; - // - // strobe gets called in a java thread - disco only lasts 30 seconds. - // - var discoTicks = 30; - var task = null; - var strobe = function() { - disco.rand(floorTiles,width,1,length); - if (!discoTicks--) - task.cancel(); - }; - var now = 0; - var everySecond = 20; - task = server.scheduler.runTaskTimer(__plugin,strobe,now,everySecond); - return this; -}); diff --git a/src/main/javascript/drone/contrib/fort.js b/src/main/javascript/drone/contrib/fort.js deleted file mode 100644 index 49aec69..0000000 --- a/src/main/javascript/drone/contrib/fort.js +++ /dev/null @@ -1,66 +0,0 @@ -// -// constructs a medieval fort -// -Drone.extend('fort', function(side, height) -{ - if (typeof side == "undefined") - side = 18; - if (typeof height == "undefined") - height = 6; - // make sure side is even - if (side%2) - side++; - if (height < 4 || side < 10) - throw new java.lang.RuntimeException("Forts must be at least 10 wide X 4 tall"); - var brick = 98; - // - // build walls. - // - this.chkpt('fort').box0(brick,side,height-1,side); - // - // build battlements - // - this.up(height-1); - for (i = 0;i <= 3;i++){ - var turret = []; - this.box(brick) // solid brick corners - .up().box('50:5').down() // light a torch on each corner - .fwd(); - turret.push('109:'+ Drone.PLAYER_STAIRS_FACING[this.dir]); - turret.push('109:'+ Drone.PLAYER_STAIRS_FACING[(this.dir+2)%4]); - try{ - this.boxa(turret,1,1,side-2).fwd(side-2).turn(); - }catch(e){ - self.sendMessage("ERROR: " + e.toString()); - } - } - // - // build battlement's floor - // - this.move('fort'); - this.up(height-2).fwd().right().box('126:0',side-2,1,side-2); - var battlementWidth = 3; - if (side <= 12) - battlementWidth = 2; - - this.fwd(battlementWidth).right(battlementWidth) - .box(0,side-((1+battlementWidth)*2),1,side-((1+battlementWidth)*2)); - // - // add door - // - var torch = '50:' + Drone.PLAYER_TORCH_FACING[this.dir]; - this.move('fort').right((side/2)-1).door2() // double doors - .back().left().up() - .box(torch) // left torch - .right(3) - .box(torch); // right torch - // - // add ladder up to battlements - // - var ladder = '65:' + Drone.PLAYER_SIGN_FACING[(this.dir+2)%4]; - this.move('fort').right((side/2)-3).fwd(1) // move inside fort - .box(ladder, 1,height-1,1); - return this.move('fort'); - -}); - diff --git a/src/main/javascript/drone/contrib/logo.js b/src/main/javascript/drone/contrib/logo.js deleted file mode 100644 index 880a00e..0000000 --- a/src/main/javascript/drone/contrib/logo.js +++ /dev/null @@ -1,217 +0,0 @@ -// -// Constructs the JS logo -// https://raw.github.com/voodootikigod/logo.js/master/js.png -// -// fg -// the material that the letters JS will be made of -// bg -// the material that the square will be made of -// -Drone.extend('logojs', function(fg, bg) { - - // foreground defaults to gray wool - if (typeof fg == "undefined") - fg = '35:7'; - // background defaults to gold blocks - if (typeof bg == "undefined") - bg = 41; - - // Draw the sqaure - this.chkpt('logojs-start') - .up() - .box(bg, 100, 100, 1); - - // Draw the J, starting with the hook - this.right(30).up(13) - .box(fg) - .right().down() - .box(fg, 1, 3, 1) - .right().down() - .box(fg, 1, 5, 1) - .right().down() - .box(fg, 1, 7, 1) - .right() - .box(fg, 1, 8, 1) - .right().down() - .box(fg, 1, 10, 1) - .right() - .box(fg, 1, 9, 1) - .right() - .box(fg, 1, 8, 1) - .right().down() - .box(fg, 2, 8, 1) - .right(2) - .box(fg, 4, 7, 1) - .right(4) - .box(fg, 1, 8, 1) - .right() - .box(fg, 1, 9, 1) - .right().up() - .box(fg, 3, 10, 1) - .right(3).up() - .box(fg, 2, 9, 1) - .right(2).up() - .box(fg, 2, 8, 1) - .right(2).up() - .box(fg, 1, 7, 1) - .right().up() - .box(fg, 1, 6, 1) - .right().up() - .box(fg, 1, 5, 1) - .right().up(2) - .box(fg, 1, 3, 1) - .left(9).up(3) - .box(fg, 10, 31, 1) - - // Draw the S - // It's drawn in three strokes from bottom to top. Look for when - // it switches from .right() to .left() then back again - - // move to starting point for S - .right(22).down(6) - // stroke 1 - .box(fg) - .right().down() - .box(fg, 1, 3, 1) - .right().down() - .box(fg, 1, 5, 1) - .right().down() - .box(fg, 1, 7, 1) - .right() - .box(fg, 1, 8, 1) - .right().down() - .box(fg, 1, 10, 1) - .right() - .box(fg, 1, 9, 1) - .right() - .box(fg, 1, 8, 1) - .right().down() - .box(fg, 2, 8, 1) - .right(2) - .box(fg, 4, 7, 1) - .right(4) - .box(fg, 2, 8, 1) - .right(2) - .box(fg, 1, 9, 1) - .right().up() - .box(fg, 1, 9, 1) - .right() - .box(fg, 1, 10, 1) - .right() - .box(fg, 1, 22, 1) - .right().up() - .box(fg, 2, 20, 1) - .right().up() - .box(fg, 1, 18, 1) - .right().up() - .box(fg, 1, 17, 1) - .right().up() - .box(fg, 1, 15, 1) - .right().up() - .box(fg, 1, 13, 1) - .right().up(2) - .box(fg, 1, 9, 1) - .right().up(2) - .box(fg, 1, 5, 1) - // stroke 2 - .left(8).up(4) - .box(fg, 1, 9, 1) - .left().up() - .box(fg, 1, 9, 1) - .left().up() - .box(fg, 1, 8, 1) - .left(2).up() - .box(fg, 2, 8, 1) - .left(2).up() - .box(fg, 2, 7, 1) - .left(2).up() - .box(fg, 2, 7, 1) - .left() - .box(fg, 1, 8, 1) - .left().up() - .box(fg, 1, 8, 1) - .left() - .box(fg, 1, 9, 1) - .left(2).up() - .box(fg, 2, 19, 1) - .left().up() - .box(fg, 1, 17, 1) - .left() - .box(fg, 1, 16, 1) - .left().up() - .box(fg, 1, 14, 1) - .left().up(2) - .box(fg, 1, 10, 1) - .left().up(2) - .box(fg, 1, 6, 1) - // stroke 3 - .right(7).up(6) - .box(fg, 1, 8, 1) - .right().up() - .box(fg, 1, 7, 1) - .right().up() - .box(fg, 2, 7, 1) - .right(2).up() - .box(fg, 4, 6, 1) - .right(4).down() - .box(fg, 2, 7, 1) - .right().down() - .box(fg, 1, 8, 1) - .right() - .box(fg, 1, 7, 1) - .right().down() - .box(fg, 1, 8, 1) - .right().down() - .box(fg, 1, 9, 1) - .right().down() - .box(fg, 1, 9, 1) - .right().up() - .box(fg, 1, 8, 1) - .right().up() - .box(fg, 1, 6, 1) - .right().up() - .box(fg, 1, 5, 1) - .right().up() - .box(fg, 1, 3, 1) - .right().up() - .box(fg); - - this.move('logojs-start'); - - return this; -}); -// -// Makes a cube of JS logos! -// This is a wrapper for logojs() so look at its docs -// -// Until the drone can rotate on its Z axis we can't -// use logojs() to create top/bottom sides of cube. -// -Drone.extend('logojscube', function(fg, bg) { - - this.chkpt('jscube-start') - .logojs(fg, bg); - - this.move('jscube-start') - .right(100) - .turn(3) - .logojs(fg, bg); - - this.move('jscube-start') - .right(100) - .turn(3) - .right(100) - .turn(3) - .logojs(fg, bg); - - this.move('jscube-start') - .right(100) - .turn(3) - .right(100) - .turn(3) - .right(100) - .turn(3) - .logojs(fg, bg); - - return this; -}); diff --git a/src/main/javascript/drone/contrib/partial.js b/src/main/javascript/drone/contrib/partial.js deleted file mode 100644 index 8adbeac..0000000 --- a/src/main/javascript/drone/contrib/partial.js +++ /dev/null @@ -1,14 +0,0 @@ -/** -* Create a partial function -* -* Parameters: -* func - base function -* [remaining arguments] - arguments bound to the partial function -*/ -function partial(func /*, 0..n args */) { - var args = Array.prototype.slice.call(arguments, 1); - return function() { - var allArguments = args.concat(Array.prototype.slice.call(arguments)); - return func.apply(this, allArguments); - }; -} diff --git a/src/main/javascript/drone/contrib/rainbow.js b/src/main/javascript/drone/contrib/rainbow.js deleted file mode 100644 index a9da6e4..0000000 --- a/src/main/javascript/drone/contrib/rainbow.js +++ /dev/null @@ -1,41 +0,0 @@ -/************************************************************************ -Drone.rainbow() method -====================== -Creates a Rainbow. - -Parameters ----------- - - * radius (optional - default:18) - The radius of the rainbow - -Example -------- - - var d = new Drone(); - d.rainbow(30); - -![rainbow example](img/rainbowex1.png) - -***/ -Drone.extend('rainbow', function(radius){ - if (typeof radius == "undefined") - radius = 18; - - this.chkpt('rainbow'); - this.down(radius); - // copy blocks.rainbow and add air at end (to compensate for strokewidth) - var colors = blocks.rainbow.slice(0); - colors.push(blocks.air); - for (var i = 0;i < colors.length; i++) { - var bm = this._getBlockIdAndMeta(colors[i]); - this.arc({ - blockType: bm[0], - meta: bm[1], - radius: radius-i, - strokeWidth: 2, - quadrants: {topright: true, - topleft: true}, - orientation: 'vertical'}).right().up(); - } - return this.move('rainbow'); -}); diff --git a/src/main/javascript/drone/contrib/rboxcall.js b/src/main/javascript/drone/contrib/rboxcall.js deleted file mode 100644 index 15b05e0..0000000 --- a/src/main/javascript/drone/contrib/rboxcall.js +++ /dev/null @@ -1,32 +0,0 @@ -/** -* Iterates over each cube in a cubic region. For each cube has a chance to callback your -* function and provide a new drone to it. -* -* Parameters: -* callback - any function that accepts a drone as its first argument -* probability - chance to invoke your callback on each iteration -* width - width of the region -* height - (Optional) height of the region, defaults to width -* depth - (Optional) depth of the cube, defaults to width -*/ - -Drone.extend("rboxcall", function(callback, probability, width, height, depth) { - this.chkpt('rboxcall-start'); - - for(var i = 0; i < width; ++i) { - this.move('rboxcall-start').right(i); - for(var j = 0; j < depth; ++j) { - this.move('rboxcall-start').right(i).fwd(j); - for(var k = 0; k < height; ++k) { - if(Math.random()*100 < probability) { - callback.call(null, new Drone(this.x, this.y, this.z)); - } - this.up(); - } - } - } - - this.move('rboxcall-start'); - - return this; -}); diff --git a/src/main/javascript/drone/contrib/redstonewire.js b/src/main/javascript/drone/contrib/redstonewire.js deleted file mode 100644 index fc36992..0000000 --- a/src/main/javascript/drone/contrib/redstonewire.js +++ /dev/null @@ -1,105 +0,0 @@ -// -// usage: -// [1] to place a new block with redstone wire on it (block on bottom, redstone on top) -// /js wireblock(blocks.sandstone); -// -// [2] to drop wire on to an existing block -// /js wire() -// -// [3] to place a (redstone) torch on a new block -// /js torchblock(blocks.sandstone) -// -// [4] to place a repeater on a new block -// /js repeaterblock(blocks.sandstone) -// -// [5] To create a long redstone wire (with necessary repeaters, powererd by a single torch) -// /js wirestraight(blocks.sandstone, distance) -// -// [6] To create a 'road' with redstone torches and wire lining each side -// /js redstoneroad(blocks.stone, blocks.sandstone, 25) - -Drone.extend('wireblock',function(blockType) -{ - this.chkpt('wireblock') - .box(blockType,1,2,1) // 2 blocks tall, top block will be wire dropped on lower - .up(); - - this.world.getBlockAt(this.x,this.y,this.z).setTypeId(55); //apply wire - - return this.move('wireblock'); -}); - -Drone.extend('wire',function () -{ - this.chkpt('wire') - .up(); - - this.world.getBlockAt(this.x,this.y,this.z).setTypeId(55); // apply wire - - return this.move('wire'); -}); - -Drone.extend('torchblock', function(blockType) -{ - this.box(blockType,1,2,1) // 2 blocks tall - .up(); - - this.world.getBlockAt(this.x,this.y,this.z).setTypeId(76); // apply torch - - return this.down(); -}); - -Drone.extend('repeaterblock',function(blockType) -{ - this.chkpt('repeaterblock') - .box(blockType,1,2,1) - .up(); - - var block = this.world.getBlockAt(this.x,this.y,this.z); - block.setTypeId(94); // apply repeater - - // redstone repeater dirs: north=0,east=1,south=2,west=3 - var direction = [1,2,3,0][this.dir]; // convert drone dir to repeater dir. - block.setData(direction); - - return this.move('repeaterblock'); -}); - - -Drone.extend('wirestraight',function (blockType,distance) -{ - this.chkpt('wirestraight'); - - this.torchblock(blockType); - this.fwd(); - - for (var i = 1; i < distance; i++) { - if(i % 14 == 0) - { - this.repeaterblock(blockType); - } - else - { - this.wireblock(blockType); - } - - this.fwd(); - }; - - return this.move('wirestraight'); -}); - - -Drone.extend('redstoneroad', function (roadBlockType, redstoneunderBlockType, distance) -{ - return this.down() - .wirestraight(redstoneunderBlockType, distance) - .right() - .box(roadBlockType, 4,1,distance) - .right(4) - .wirestraight(redstoneunderBlockType, distance) - .up(); -}); - - - diff --git a/src/main/javascript/drone/contrib/skyscraper-example.js b/src/main/javascript/drone/contrib/skyscraper-example.js deleted file mode 100644 index 87c25d1..0000000 --- a/src/main/javascript/drone/contrib/skyscraper-example.js +++ /dev/null @@ -1,15 +0,0 @@ -Drone.extend('skyscraper',function(floors){ - - if (typeof floors == "undefined") - floors = 10; - this.chkpt('skyscraper'); - for (var i = 0;i < floors; i++) - { - this - .box(blocks.iron,20,1,20) - .up() - .box0(blocks.glass_pane,20,3,20) - .up(3); - } - return this.move('skyscraper'); -}); diff --git a/src/main/javascript/drone/contrib/spiral_stairs.js b/src/main/javascript/drone/contrib/spiral_stairs.js deleted file mode 100644 index c41bda6..0000000 --- a/src/main/javascript/drone/contrib/spiral_stairs.js +++ /dev/null @@ -1,44 +0,0 @@ -/************************************************************************ -Drone.spiral_stairs() method -============================ -Constructs a spiral staircase with slabs at each corner. - -Parameters ----------- - - * stairBlock - The block to use for stairs, should be one of the following... - - 'oak' - - 'spruce' - - 'birch' - - 'jungle' - - 'cobblestone' - - 'brick' - - 'stone' - - 'nether' - - 'sandstone' - - 'quartz' - * flights - The number of flights of stairs to build. - -![Spiral Staircase](img/spiralstair1.png) - -Example -------- -To construct a spiral staircase 5 floors high made of oak... - - spiral_stairs('oak', 5); - -***/ -Drone.extend("spiral_stairs",function(stairBlock, flights){ - this.chkpt('spiral_stairs'); - - for (var i = 0; i < flights; i++){ - this - .box(blocks.stairs[stairBlock] + ':' + Drone.PLAYER_STAIRS_FACING[this.dir]) - .up().fwd() - .box(blocks.stairs[stairBlock] + ':' + Drone.PLAYER_STAIRS_FACING[this.dir]) - .up().fwd() - .box(blocks.slab[stairBlock]) - .turn().fwd(); - } - return this.move('spiral_stairs'); -}); diff --git a/src/main/javascript/drone/contrib/streamer.js b/src/main/javascript/drone/contrib/streamer.js deleted file mode 100644 index ffc3a53..0000000 --- a/src/main/javascript/drone/contrib/streamer.js +++ /dev/null @@ -1,19 +0,0 @@ -/** -* Creates a stream of blocks in a given direction until it hits something other than air -* -* Parameters: -* block - blockId -* dir - "up", "down", "left", "right", "fwd", "back -* maxIterations - (Optional) maximum number of cubes to generate, defaults to 1000 -*/ -Drone.extend("streamer", function(block, dir, maxIterations) { - for(var i = 0; i < maxIterations||1000; ++i) { - this.box(block); - this[dir].call(this); - if(getBlock(this.x, this.y, this.z) !== "0:0") { - break; - } - } - - return this; -}); diff --git a/src/main/javascript/drone/contrib/temple.js b/src/main/javascript/drone/contrib/temple.js deleted file mode 100644 index 90ba27e..0000000 --- a/src/main/javascript/drone/contrib/temple.js +++ /dev/null @@ -1,23 +0,0 @@ -// -// constructs a mayan temple -// -Drone.extend('temple', function(side) { - if (!side) { - side = 20; - } - var stone = '98:1'; - var stair = '109:' + Drone.PLAYER_STAIRS_FACING[this.dir]; - - this.chkpt('temple'); - - while (side > 4) { - var middle = Math.round((side-2)/2); - this.chkpt('corner') - .box(stone, side, 1, side) - .right(middle).box(stair).right().box(stair) - .move('corner').up().fwd().right(); - side = side - 2; - } - - return this.move('temple'); -}); diff --git a/src/main/javascript/drone/drone.js b/src/main/javascript/drone/drone.js deleted file mode 100644 index 2d4ccee..0000000 --- a/src/main/javascript/drone/drone.js +++ /dev/null @@ -1,1810 +0,0 @@ -var global = this; -load(__folder + "../core/_primitives.js",true); - -/********************************************************************* -Drone Module -============ -The Drone is a convenience class for building. It can be used for... - - 1. Building - 2. Copying and Pasting - -It uses a fluent interface which means all of the Drone's methods return `this` and can -be chained together like so... - - var theDrone = new Drone(); - theDrone.up().left().box(blocks.oak).down().fwd(3).cylinder0(blocks.lava,8); - -TLDNR; (Just read this if you're impatient) -=========================================== -At the in-game command prompt type... - - /js box( blocks.oak ) - -... creates a single wooden block at the cross-hairs or player location - - /js box( blocks.oak ).right(2).box( blocks.wool.black, 4, 9, 1) - -... creates a single wooden block and a 2001 black obelisk that is 4 -wide x 9 tall x 1 long in size. If you want to see what else -ScriptCraft's Drone can do, read on... - -Constructing a Drone Object -=========================== - -Drones can be created in any of the following ways... - - 1. Calling any one of the methods listed below will return a Drone object. For example... - - var d = box( blocks.oak ) - - ... creates a 1x1x1 wooden block at the cross-hairs or player's location and returns a Drone - object. This might look odd (if you're familiar with Java's Object-dot-method syntax) but all - of the Drone class's methods are also global functions that return new Drone objects. - This is short-hand for creating drones and is useful for playing around with Drones at the in-game - command prompt. It's shorter than typing ... - - var d = new Drone().box( blocks.oak ) - - ... All of the Drone's methods return `this` (self) so you can chain operations together like this... - - var d = box( blocks.oak ) - .up() - .box( blocks.oak ,3,1,3) - .down() - .fwd(2) - .box( blocks.oak ) - .turn() - .fwd(2) - .box( blocks.oak ) - .turn() - .fwd(2) - .box( blocks.oak ); - - 2. Using the following form... - - d = new Drone() - - ...will create a new Drone. If the cross-hairs are pointing at a - block at the time then, that block's location becomes the drone's - starting point. If the cross-hairs are _not_ pointing at a block, - then the drone's starting location will be 2 blocks directly in - front of the player. TIP: Building always happens right and front - of the drone's position... - - Plan View: - - ^ - | - | - D----> - - For convenience you can use a _corner stone_ to begin building. - The corner stone should be located just above ground level. If - the cross-hair is point at or into ground level when you create a - new Drone(), then building begins at that point. You can get - around this by pointing at a 'corner stone' just above ground - level or alternatively use the following statement... - - d = new Drone().up(); - - ... which will move the drone up one block as soon as it's created. - - ![corner stone](img/cornerstone1.png) - - 3. Or by using the following form... - - d = new Drone(x,y,z,direction,world); - - This will create a new Drone at the location you specified using - x, y, z In minecraft, the X axis runs west to east and the Z axis runs - north to south. The direction parameter says what direction you want - the drone to face: 0 = east, 1 = south, 2 = west, 3 = north. If the - direction parameter is omitted, the player's direction is used - instead. - - Both the `direction` and `world` parameters are optional. - - 4. Create a new Drone based on a Bukkit Location object... - - d = new Drone(location); - - This is useful when you want to create a drone at a given - `org.bukkit.Location` . The `Location` class is used throughout - the bukkit API. For example, if you want to create a drone when a - block is broken at the block's location you would do so like - this... - - events.on('block.BlockBreakEvent',function(listener,event){ - var location = event.block.location; - var drone = new Drone(location); - // do more stuff with the drone here... - }); - -Parameters ----------- - * location (optional) : *NB* If an `org.bukkit.Location` object is provided as a parameter, then it should be the only parameter. - * x (optional) : The x coordinate of the Drone - * y (optional) : The y coordinate of the Drone - * z (optional) : The z coordinate of the Drone - * direction (optional) : The direction in which the Drone is - facing. Possible values are 0 (east), 1 (south), 2 (west) or 3 (north) - * world (optional) : The world in which the drone is created. - -***/ -var Drone = function(/* number */ x, /* number */ y, /* number */ z, /* number */ direction){}; -/************************************************************************ -Drone.box() method -================== -the box() method is a convenience method for building things. (For the more performance-oriented method - see cuboid) - -parameters ----------- - * b - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. - Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` - * w (optional - default 1) - the width of the structure - * h (optional - default 1) - the height of the structure - * d (optional - default 1) - the depth of the structure - NB this is - not how deep underground the structure lies - this is how far - away (depth of field) from the drone the structure will extend. - -Example -------- -To create a black structure 4 blocks wide, 9 blocks tall and 1 block long... - - box(blocks.wool.black, 4, 9, 1); - -... or the following code does the same but creates a variable that can be used for further methods... - - var drone = new Drone(); - drone.box(blocks.wool.black, 4, 9, 1); - -![box example 1](img/boxex1.png) - -Drone.box0() method -=================== -Another convenience method - this one creates 4 walls with no floor or ceiling. - -Parameters ----------- - * block - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. - Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` - * width (optional - default 1) - the width of the structure - * height (optional - default 1) - the height of the structure - * length (optional - default 1) - the length of the structure - how far - away (depth of field) from the drone the structure will extend. - -Example -------- -To create a stone building with the insided hollowed out 7 wide by 3 tall by 6 long... - - box0( blocks.stone, 7, 3, 6); - -![example box0](img/box0ex1.png) - -Drone.boxa() method -=================== -Construct a cuboid using an array of blocks. As the drone moves first along the width axis, -then the height (y axis) then the length, each block is picked from the array and placed. - -Parameters ----------- - * blocks - An array of blocks - each block in the array will be placed in turn. - * width - * height - * length - -Example -------- -Construct a rainbow-colored road 100 blocks long... - - var rainbowColors = [blocks.wool.red, blocks.wool.orange, blocks.wool.yellow, blocks.wool.lime, - blocks.wool.lightblue, blocks.wool.blue, blocks.wool.purple]; - - boxa(rainbowColors,7,1,30); - -![boxa example](img/boxaex1.png) - -***/ -Drone.prototype.box = function(block,width,height,depth){}; -Drone.prototype.box0 = function(block,width,height,length){}; -Drone.prototype.boxa = function(/* [string] */ width, height, length){}; - -/************************************************************************ -Drone Movement -============== -Drones can move freely in minecraft's 3-D world. You control the -Drone's movement using any of the following methods.. - - * up() - * down() - * left() - * right() - * fwd() - * back() - * turn() - -... Each of these methods takes a single optional parameter -`numBlocks` - the number of blocks to move in the given direction. If -no parameter is given, the default is 1. - -to change direction use the `turn()` method which also takes a single -optional parameter (numTurns) - the number of 90 degree turns to make. -Turns are always clock-wise. If the drone is facing north, then -drone.turn() will make the turn face east. If the drone is facing east -then drone.turn(2) will make the drone turn twice so that it is facing -west. - -***/ -Drone.prototype.up = function(numBlocks){}; -Drone.prototype.down = function(numBlocks){}; -Drone.prototype.left = function(numBlocks){}; -Drone.prototype.right = function(numBlocks){}; -Drone.prototype.fwd = function(numBlocks){}; -Drone.prototype.back = function(numBlocks){}; -Drone.prototype.turn = function(numTurns){}; - -/************************************************************************ -Drone Positional Info -===================== - - * getLocation() - Returns a Bukkit Location object for the drone - -***/ -Drone.prototype.getLocation = function(){}; - -/************************************************************************ -Drone Markers -============= -Markers are useful when your Drone has to do a lot of work. You can -set a check-point and return to the check-point using the move() -method. If your drone is about to undertake a lot of work - -e.g. building a road, skyscraper or forest you should set a -check-point before doing so if you want your drone to return to its -current location. - -A 'start' checkpoint is automatically created when the Drone is first created. - -Markers are created and returned to using the followng two methods... - - * chkpt - Saves the drone's current location so it can be returned to later. - * move - moves the drone to a saved location. Alternatively you can provide an - org.bukkit.Location object or x,y,z and direction parameters. - -Parameters ----------- - * name - the name of the checkpoint to save or return to. - -Example -------- - - drone.chkpt('town-square'); - // - // the drone can now go off on a long excursion - // - for (i = 0; i< 100; i++){ - drone.fwd(12).box(6); - } - // - // return to the point before the excursion - // - drone.move('town-square'); - -***/ -Drone.prototype.chkpt = function(checkpoint_name){}; -Drone.prototype.move = function(checkpoint_name){}; - -/************************************************************************ -Drone.prism() method -==================== -Creates a prism. This is useful for roofs on houses. - -Parameters ----------- - - * block - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. - Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` - * width - the width of the prism - * length - the length of the prism (will be 2 time its height) - -Example -------- - - prism(blocks.oak,3,12); - -![prism example](img/prismex1.png) - -Drone.prism0() method -===================== -A variation on `prism` which hollows out the inside of the prism. It uses the same parameters as `prism`. - -***/ -Drone.prototype.prism = function(block,width,depth){}; -Drone.prototype.prism0 = function(block,width,depth){}; -/************************************************************************ -Drone.cylinder() method -======================= -A convenience method for building cylinders. Building begins radius blocks to the right and forward. - -Parameters ----------- - - * block - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. - Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` - * radius - * height - -Example -------- -To create a cylinder of Iron 7 blocks in radius and 1 block high... - - cylinder(blocks.iron, 7 , 1); - -![cylinder example](img/cylinderex1.png) - -Drone.cylinder0() method -======================== -A version of cylinder that hollows out the middle. - -Example -------- -To create a hollow cylinder of Iron 7 blocks in radius and 1 block high... - - cylinder0(blocks.iron, 7, 1); - -![cylinder0 example](img/cylinder0ex1.png) - -***/ -Drone.prototype.cylinder = function(block,radius,height){}; -Drone.prototype.cylinder0 = function(block,radius,height){}; -/************************************************************************ -Drone.arc() method -================== -The arc() method can be used to create 1 or more 90 degree arcs in the horizontal or vertical planes. -This method is called by cylinder() and cylinder0() and the sphere() and sphere0() methods. - -Parameters ----------- -arc() takes a single parameter - an object with the following named properties... - - * radius - The radius of the arc. - * blockType - The type of block to use - this is the block Id only (no meta). See [Data Values][dv]. - * meta - The metadata value. See [Data Values][dv]. - * orientation (default: 'horizontal') - the orientation of the arc - can be 'vertical' or 'horizontal'. - * stack (default: 1) - the height or length of the arc (depending on - the orientation - if orientation is horizontal then this parameter - refers to the height, if vertical then it refers to the length). - * strokeWidth (default: 1) - the width of the stroke (how many - blocks) - if drawing nested arcs it's usually a good idea to set - strokeWidth to at least 2 so that there are no gaps between each - arc. The arc method uses a [bresenham algorithm][bres] to plot - points along the circumference. - * fill - If true (or present) then the arc will be filled in. - * quadrants (default: - `{topleft:true,topright:true,bottomleft:true,bottomright:true}` - An - object with 4 properties indicating which of the 4 quadrants of a - circle to draw. If the quadrants property is absent then all 4 - quadrants are drawn. - -Examples --------- -To draw a 1/4 circle (top right quadrant only) with a radius of 10 and stroke width of 2 blocks ... - - arc({blockType: blocks.iron, - meta: 0, - radius: 10, - strokeWidth: 2, - quadrants: { topright: true }, - orientation: 'vertical', - stack: 1, - fill: false - }); - -![arc example 1](img/arcex1.png) - -[bres]: http://en.wikipedia.org/wiki/Midpoint_circle_algorithm -[dv]: http://www.minecraftwiki.net/wiki/Data_values -***/ -Drone.prototype.arc = function(params) {}; - -/************************************************************************ -Drone.door() method -=================== -create a door - if a parameter is supplied an Iron door is created otherwise a wooden door is created. - -Parameters ----------- - * doorType (optional - default wood) - If a parameter is provided then the door is Iron. - -Example -------- -To create a wooden door at the crosshairs/drone's location... - - var drone = new Drone(); - drone.door(); - -To create an iron door... - - drone.door( blocks.door_iron ); - -![iron door](img/doorex1.png) - -Drone.door2() method -==================== -Create double doors (left and right side) - -Parameters ----------- - * doorType (optional - default wood) - If a parameter is provided then the door is Iron. - -Example -------- -To create double-doors at the cross-hairs/drone's location... - - drone.door2(); - -![double doors](img/door2ex1.png) - -***/ -Drone.prototype.door = function(b){}; -Drone.prototype.door2 = function(b){}; -/************************************************************************ -Drone.sign() method -=================== -Signs must use block 63 (stand-alone signs) or 68 (signs on walls) - -Parameters ----------- - * message - can be a string or an array of strings. - * block - can be 63 or 68 - -Example -------- -To create a free-standing sign... - - drone.sign(["Hello","World"],63); - -![ground sign](img/signex1.png) - -... to create a wall mounted sign... - - drone.sign(["Welcome","to","Scriptopia"], 68); - -![wall sign](img/signex2.png) - -***/ -Drone.prototype.sign = function(s,b){}; -/************************************************************************ -Drone Trees methods -=================== - - * oak() - * spruce() - * birch() - * jungle() - -Example -------- -To create 4 trees in a row, point the cross-hairs at the ground then type `/js ` and ... - - up().oak().right(8).spruce().right(8).birch().right(8).jungle(); - -Trees won't always generate unless the conditions are right. You -should use the tree methods when the drone is directly above the -ground. Trees will usually grow if the drone's current location is -occupied by Air and is directly above an area of grass (That is why -the `up()` method is called first). - -![tree example](img/treeex1.png) - - -None of the tree methods require parameters. Tree methods will only be successful -if the tree is placed on grass in a setting where trees can grow. -***/ -Drone.prototype.oak= function(){}; -Drone.prototype.spruce = function(){}; -Drone.prototype.birch = function(){}; -Drone.prototype.jungle = function(){}; -/************************************************************************ -Drone.garden() method -===================== -places random flowers and long grass (similar to the effect of placing bonemeal on grass) - -Parameters ----------- - - * width - the width of the garden - * length - how far from the drone the garden extends - -Example -------- -To create a garden 10 blocks wide by 5 blocks long... - - garden(10,5); - -![garden example](img/gardenex1.png) - -***/ -Drone.prototype.garden = function(w,d){}; -/************************************************************************ -Drone.rand() method -=================== -rand takes either an array (if each blockid has the same chance of occurring) -or an object where each property is a blockid and the value is it's weight (an integer) - -Example -------- -place random blocks stone, mossy stone and cracked stone (each block has the same chance of being picked) - - rand( [blocks.brick.stone, blocks.brick.mossy, blocks.brick.cracked ],w,d,h) - -to place random blocks stone has a 50% chance of being picked, - - rand({blocks.brick.stone: 5, blocks.brick.mossy: 3, blocks.brick.cracked: 2},w,d,h) - -regular stone has a 50% chance, mossy stone has a 30% chance and cracked stone has just a 20% chance of being picked. - -***/ -Drone.prototype.rand = function(distribution,w,h,d){}; -/************************************************************************ -Copy & Paste using Drone -======================== -A drone can be used to copy and paste areas of the game world. - -Drone.copy() method -=================== -Copies an area so it can be pasted elsewhere. The name can be used for -pasting the copied area elsewhere... - -Parameters ----------- - - * name - the name to be given to the copied area (used by `paste`) - * width - the width of the area to copy - * height - the height of the area to copy - * length - the length of the area (extending away from the drone) to copy - -Example -------- - - drone.copy('somethingCool',10,5,10).right(12).paste('somethingCool'); - -Drone.paste() method -==================== -Pastes a copied area to the current location. - -Example -------- -To copy a 10x5x10 area (using the drone's coordinates as the starting -point) into memory. the copied area can be referenced using the name -'somethingCool'. The drone moves 12 blocks right then pastes the copy. - - drone.copy('somethingCool',10,5,10) - .right(12) - .paste('somethingCool'); - -***/ -Drone.prototype.copy = function(name,w,h,d){}; -Drone.prototype.paste = function(name){}; -/************************************************************************ -Chaining -======== - -All of the Drone methods return a Drone object, which means methods -can be 'chained' together so instead of writing this... - - drone = new Drone(); - drone.fwd(3); - drone.left(2); - drone.box(2); // create a grass block - drone.up(); - drone.box(2); // create another grass block - drone.down(); - -...you could simply write ... - - var drone = new Drone().fwd(3).left(2).box(2).up().box(2).down(); - -... since each Drone method is also a global function that constructs -a drone if none is supplied, you can shorten even further to just... - - fwd(3).left(2).box(2).up().box(2).down() - -The Drone object uses a [Fluent Interface][fl] to make ScriptCraft -scripts more concise and easier to write and read. Minecraft's -in-game command prompt is limited to about 80 characters so chaining -drone commands together means more can be done before hitting the -command prompt limit. For complex building you should save your -commands in a new script file and load it using /js load() - -[fl]: http://en.wikipedia.org/wiki/Fluent_interface - -Drone Properties -================ - - * x - The Drone's position along the west-east axis (x increases as you move east) - * y - The Drone's position along the vertical axis (y increses as you move up) - * z - The Drone's position along the north-south axis (z increases as you move south) - * dir - The Drone's direction 0 is east, 1 is south , 2 is west and 3 is north. - -Extending Drone -=============== -The Drone object can be easily extended - new buidling recipes/blue-prints can be added and can -become part of a Drone's chain using the *static* method `Drone.extend`. - -Drone.extend() static method -============================ -Use this method to add new methods (which also become chainable global functions) to the Drone object. - -Parameters ----------- - * name - The name of the new method e.g. 'pyramid' - * function - The method body. - -Example -------- - - // submitted by [edonaldson][edonaldson] - Drone.extend('pyramid', function(block,height){ - this.chkpt('pyramid'); - for (var i = height; i > 0; i -= 2) { - this.box(block, i, 1, i).up().right().fwd(); - } - return this.move('pyramid'); - }); - -Once the method is defined (it can be defined in a new pyramid.js file) it can be used like so... - - var d = new Drone(); - d.pyramid(blocks.brick.stone, 12); - -... or simply ... - - pyramid(blocks.brick.stone, 12); - -[edonaldson]: https://github.com/edonaldson - -Drone Constants -=============== - -Drone.PLAYER_STAIRS_FACING --------------------------- -An array which can be used when constructing stairs facing in the Drone's direction... - - var d = new Drone(); - d.box(blocks.stairs.oak + ':' + Drone.PLAYER_STAIRS_FACING[d.dir]); - -... will construct a single oak stair block facing the drone. - -Drone.PLAYER_SIGN_FACING ------------------------- -An array which can be used when placing signs so they face in a given direction. -This is used internally by the Drone.sign() method. It should also be used for placing -any of the following blocks... - - * chest - * ladder - * furnace - * dispenser - -To place a chest facing the Drone ... - - drone.box( blocks.chest + ':' + Drone.PLAYER_SIGN_FACING[drone.dir]); - -Drone.PLAYER_TORCH_FACING -------------------------- -Used when placing torches so that they face towards the drone. - - drone.box( blocks.torch + ':' + Drone.PLAYER_TORCH_FACING[drone.dir]); - -***/ - -// -// Implementation -// ============== -// -// There is no need to read any further unless you want to understand how the Drone object works. -// -(function(){ - Drone = function(x,y,z,dir,world) - { - this.record = false; - var usePlayerCoords = false; - var playerPos = getPlayerPos(); - if (typeof x == "undefined") - { - var mp = getMousePos(); - if (mp){ - this.x = mp.x; - this.y = mp.y; - this.z = mp.z; - if (playerPos) - this.dir = _getDirFromRotation(playerPos.yaw); - this.world = mp.world; - }else{ - // base it on the player's current location - usePlayerCoords = true; - // - // it's possible that drone.js could be loaded by a non-playing op - // (from the server console) - // - if (!playerPos){ - return null; - } - this.x = playerPos.x; - this.y = playerPos.y; - this.z = playerPos.z; - this.dir = _getDirFromRotation(playerPos.yaw); - this.world = playerPos.world; - } - }else{ - if (arguments[0] instanceof org.bukkit.Location){ - this.x = arguments[0].x; - this.y = arguments[0].y; - this.z = arguments[0].z; - this.dir = _getDirFromRotation(arguments[0].yaw); - this.world = arguments[0].world; - }else{ - this.x = x; - this.y = y; - this.z = z; - if (typeof dir == "undefined"){ - this.dir = _getDirFromRotation(playerPos.yaw); - }else{ - this.dir = dir%4; - } - if (typeof world == "undefined"){ - this.world = _getWorld(); - }else{ - this.world = world; - } - } - } - - if (usePlayerCoords){ - this.fwd(3); - } - this.chkpt('start'); - this.record = true; - this.history = []; - // for debugging - // self.sendMessage("New Drone " + this.toString()); - return this; - }; - // - // add custom methods to the Drone object using this function - // - Drone.extend = function(name, func) - { - // Drone.prototype[name] = func; - Drone.prototype['_' + name] = func; - Drone.prototype[name] = function(){ - if (this.record) - this.history.push([name,arguments]); - var oldVal = this.record; - this.record = false; - this['_' + name].apply(this,arguments); - this.record = oldVal; - return this; - }; - - global[name] = function(){ - var result = new Drone(); - result[name].apply(result,arguments); - return result; - }; - }; - -/************************************************************************** -Drone.times() Method -==================== -The times() method makes building multiple copies of buildings easy. It's possible to create rows or grids of buildings without resorting to `for` or `while` loops. - -Parameters ----------- - * numTimes (optional - default 2) : The number of times you want to repeat the preceding statements. - -Example -------- -Say you want to do the same thing over and over. You have a couple of options... - - * You can use a for loop... - - d = new Drone(); for (var i =0;i < 4; i++){ d.cottage().right(8); } - -While this will fit on the in-game prompt, it's awkward. You need to -declare a new Drone object first, then write a for loop to create the -4 cottages. It's also error prone, even the `for` loop is too much -syntax for what should really be simple. - - * You can use a while loop... - - d = new Drone(); var i=4; while (i--){ d.cottage().right(8); } - -... which is slightly shorter but still too much syntax. Each of the -above statements is fine for creating a 1-dimensional array of -structures. But what if you want to create a 2-dimensional or -3-dimensional array of structures? Enter the `times()` method. - -The `times()` method lets you repeat commands in a chain any number of -times. So to create 4 cottages in a row you would use the following -statement... - - cottage().right(8).times(4); - -...which will build a cottage, then move right 8 blocks, then do it -again 4 times over so that at the end you will have 4 cottages in a -row. What's more the `times()` method can be called more than once in -a chain. So if you wanted to create a *grid* of 20 houses ( 4 x 5 ), -you would do so using the following statement... - - cottage().right(8).times(4).fwd(8).left(32).times(5); - -... breaking it down... - - 1. The first 3 calls in the chain ( `cottage()`, `right(8)`, - `times(4)` ) build a single row of 4 cottages. - - 2. The last 3 calls in the chain ( `fwd(8)`, `left(32)`, `times(5)` ) - move the drone forward 8 then left 32 blocks (4 x 8) to return to - the original x coordinate, then everything in the chain is - repeated again 5 times so that in the end, we have a grid of 20 - cottages, 4 x 5. Normally this would require a nested loop but - the `times()` method does away with the need for loops when - repeating builds. - -Another example: This statement creates a row of trees 2 by 3 ... - - oak().right(10).times(2).left(20).fwd(10).times(3) - -... You can see the results below. - -![times example 1](img/times-trees.png) - -***/ - Drone.prototype.times = function(numTimes,commands) - { - if (typeof numTimes == "undefined") - numTimes = 2; - if (typeof commands == "undefined") - commands = this.history.concat(); - - this.history = [['times',[numTimes+1,commands]]]; - var oldVal = this.record; - this.record = false; - for (var j = 1; j < numTimes; j++) - { - for (var i = 0;i < commands.length; i++){ - var command = commands[i]; - var methodName = command[0]; - var args = command[1]; - print ("command=" + JSON.stringify(command) + ",methodName=" + methodName); - this[methodName].apply(this,args); - } - } - this.record = oldVal; - return this; - }; - - Drone.prototype._checkpoints = {}; - - Drone.extend('chkpt',function(name){ - this._checkpoints[name] = {x:this.x,y:this.y,z:this.z,dir:this.dir}; - }); - - Drone.extend('move', function() { - if (arguments[0] instanceof org.bukkit.Location){ - this.x = arguments[0].x; - this.y = arguments[0].y; - this.z = arguments[0].z; - this.dir = _getDirFromRotation(arguments[0].yaw); - this.world = arguments[0].world; - }else if (typeof arguments[0] === "string"){ - var coords = this._checkpoints[arguments[0]]; - if (coords){ - this.x = coords.x; - this.y = coords.y; - this.z = coords.z; - this.dir = coords.dir%4; - } - }else{ - // expect x,y,z,dir - switch(arguments.length){ - case 4: - this.dir = arguments[3]; - case 3: - this.z = arguments[2]; - case 2: - this.y = arguments[1]; - case 1:n - this.x = arguments[0]; - } - } - }); - - Drone.extend('turn',function(n){ - if (typeof n == "undefined") - n = 1; - this.dir += n; - this.dir %=4; - }); - Drone.extend('right',function(n){ - if (typeof n == "undefined") - n = 1; - _movements[this.dir].right(this,n); - }); - Drone.extend('left',function(n){ - if (typeof n == "undefined") - n = 1; - _movements[this.dir].left(this,n); - }); - Drone.extend('fwd',function(n){ - if (typeof n == "undefined") - n = 1; - _movements[this.dir].fwd(this,n); - }); - Drone.extend('back',function(n){ - if (typeof n == "undefined") - n = 1; - _movements[this.dir].back(this,n); - }); - Drone.extend('up',function(n){ - if (typeof n == "undefined") - n = 1; - this.y+= n; - }); - Drone.extend('down',function(n){ - if (typeof n == "undefined") - n = 1; - this.y-= n; - }); - // - // position - // - Drone.prototype.getLocation = function() { - return new org.bukkit.Location(this.world, this.x, this.y, this.z); - }; - // - // building - // - Drone.extend('sign',function(message,block){ - if (message.constructor == Array){ - }else{ - message = [message]; - } - var bm = this._getBlockIdAndMeta(block); - block = bm[0]; - var meta = bm[1]; - if (block != 63 && block != 68){ - print("ERROR: Invalid block id for use in signs"); - return; - } - if (block == 68){ - meta = Drone.PLAYER_SIGN_FACING[this.dir%4]; - this.back(); - } - if (block == 63){ - meta = (12 + ((this.dir+2)*4)) % 16; - } - putSign(message,this.x,this.y,this.z,block,meta); - if (block == 68){ - this.fwd(); - } - }); - Drone.prototype.cuboida = function(/* Array */ blocks,w,h,d){ - var properBlocks = []; - var len = blocks.length; - for (var i = 0;i < len;i++){ - var bm = this._getBlockIdAndMeta(blocks[i]); - properBlocks.push([bm[0],bm[1]]); - } - if (typeof h == "undefined") - h = 1; - if (typeof d == "undefined") - d = 1; - if (typeof w == "undefined") - w = 1; - var that = this; - var dir = this.dir; - var pl = org.bukkit.entity.Player; - var cs = org.bukkit.command.BlockCommandSender; - var bi = 0; - /* - - */ - _traverse[dir].depth(that,d,function(){ - _traverseHeight(that,h,function(){ - _traverse[dir].width(that,w,function(){ - var block = that.world.getBlockAt(that.x,that.y,that.z); - var properBlock = properBlocks[bi%len]; - block.setTypeIdAndData(properBlock[0],properBlock[1],false); - bi++; - }); - }); - }); - return this; - - }; - /* - faster cuboid because blockid, meta and world must be provided - use this method when you need to repeatedly place blocks - */ - Drone.prototype.cuboidX = function(blockType, meta, w, h, d){ - - if (typeof h == "undefined") - h = 1; - if (typeof d == "undefined") - d = 1; - if (typeof w == "undefined") - w = 1; - var that = this; - var dir = this.dir; - - var depthFunc = function(){ - var block = that.world.getBlockAt(that.x,that.y,that.z); - block.setTypeIdAndData(blockType,meta,false); - // wph 20130210 - dont' know if this is a bug in bukkit but for chests, - // the metadata is ignored (defaults to 2 - south facing) - // only way to change data is to set it using property/bean. - block.data = meta; - }; - var heightFunc = function(){ - _traverse[dir].depth(that,d,depthFunc); - }; - var widthFunc = function(){ - _traverseHeight(that,h,heightFunc); - }; - - _traverse[dir].width(that,w,widthFunc); - return this; - - }; - - Drone.prototype.cuboid = function(block,w,h,d){ - var bm = this._getBlockIdAndMeta(block); - return this.cuboidX(bm[0],bm[1], w,h,d); - }; - Drone.prototype.cuboid0 = function(block,w,h,d){ - this.chkpt('start_point'); - - // Front wall - this.cuboid(block, w, h, 1); - // Left wall - this.cuboid(block, 1, h, d); - // Right wall - this.right(w-1).cuboid(block, 1, h, d).left(w-1); - // Back wall - this.fwd(d-1).cuboid(block, w, h, 1); - - return this.move('start_point'); - }; - Drone.extend('door',function(door){ - if (typeof door == "undefined"){ - door = 64; - }else{ - door = 71; - } - this.cuboid(door+':' + this.dir).up().cuboid(door+':8').down(); - }); - Drone.extend('door2',function(door){ - if (typeof door == "undefined"){ - door = 64; - }else{ - door = 71; - } - this - .box(door+':' + this.dir).up() - .box(door+':8').right() - .box(door+':9').down() - .box(door+':' + this.dir).left(); - }); - // player dirs: 0 = east, 1 = south, 2 = west, 3 = north - // block dirs: 0 = east, 1 = west, 2 = south , 3 = north - // sign dirs: 5 = east, 3 = south, 4 = west, 2 = north - Drone.PLAYER_STAIRS_FACING = [0,2,1,3]; - // for blocks 68 (wall signs) 65 (ladders) 61,62 (furnaces) 23 (dispenser) and 54 (chest) - Drone.PLAYER_SIGN_FACING = [4,2,5,3]; - Drone.PLAYER_TORCH_FACING = [2,4,1,3]; - - var _getWorld = function(){ - var pl = org.bukkit.entity.Player; - var cs = org.bukkit.command.BlockCommandSender; - var world = (self instanceof pl)?self.location.world:(self instanceof cs)?self.block.location.world:null; - return world; - }; - - var _STAIRBLOCKS = {53: '5:0' // oak wood - ,67: 4 // cobblestone - ,108: 45 // brick - ,109: 98 // stone brick - ,114: 112 // nether brick - ,128: 24 // sandstone - ,134: '5:1' // spruce wood - ,135: '5:2' // birch wood - ,136: '5:3' // jungle wood - }; - // - // prism private implementation - // - var _prism = function(block,w,d) - { - var stairEquiv = _STAIRBLOCKS[block]; - if (stairEquiv){ - this.fwd().prism(stairEquiv,w,d-2).back(); - var d2 = 0; - var middle = Math.floor(d/2); - var uc = 0,dc = 0; - while (d2 < d) - { - var di = (d2 < middle?this.dir:(this.dir+2)%4); - var bd = block + ':' + Drone.PLAYER_STAIRS_FACING[di]; - var putStep = true; - if (d2 == middle){ - if (d % 2 == 1){ - putStep = false; - } - } - if (putStep) - this.cuboid(bd,w); - if (d2 < middle-1){ - this.up(); - uc++; - } - var modulo = d % 2; - if (modulo == 1){ - if (d2 > middle && d2= middle && d2= 1){ - this.cuboid(block,w,1,d2); - d2 -= 2; - this.fwd().up(); - c++; - } - this.down(c).back(c); - } - return this; - }; - // - // prism0 private implementation - // - var _prism0 = function(block,w,d){ - this.prism(block,w,d) - .fwd().right() - .prism(0,w-2,d-2) - .left().back(); - var se = _STAIRBLOCKS[block]; - if (d % 2 == 1 && se){ - // top of roof will be open - need repair - var f = Math.floor(d/2); - this.fwd(f).up(f).cuboid(se,w).down(f).back(f); - } - }; - Drone.extend('prism0',_prism0); - Drone.extend('prism',_prism); - Drone.extend('box',Drone.prototype.cuboid); - Drone.extend('box0',Drone.prototype.cuboid0); - Drone.extend('boxa',Drone.prototype.cuboida); - // - // show the Drone's position and direction - // - Drone.prototype.toString = function(){ - var dirs = ["east","south","west","north"]; - return "x: " + this.x + " y: "+this.y + " z: " + this.z + " dir: " + this.dir + " "+dirs[this.dir]; - }; - Drone.prototype.debug = function(){ - print(this.toString()); - return this; - }; - /* - do the bresenham thing - */ - var _bresenham = function(x0,y0,radius, setPixel, quadrants){ - // - // credit: Following code is copied almost verbatim from - // http://en.wikipedia.org/wiki/Midpoint_circle_algorithm - // Bresenham's circle algorithm - // - var f = 1 - radius; - var ddF_x = 1; - var ddF_y = -2 * radius; - var x = 0; - var y = radius; - var defaultQuadrants = {topleft: true, topright: true, bottomleft: true, bottomright: true}; - quadrants = quadrants?quadrants:defaultQuadrants; - /* - II | I - ------------ - III | IV - */ - if (quadrants.topleft || quadrants.topright) - setPixel(x0, y0 + radius); // quadrant I/II topmost - if (quadrants.bottomleft || quadrants.bottomright) - setPixel(x0, y0 - radius); // quadrant III/IV bottommost - if (quadrants.topright || quadrants.bottomright) - setPixel(x0 + radius, y0); // quadrant I/IV rightmost - if (quadrants.topleft || quadrants.bottomleft) - setPixel(x0 - radius, y0); // quadrant II/III leftmost - - while(x < y) - { - // ddF_x == 2 * x + 1; - // ddF_y == -2 * y; - // f == x*x + y*y - radius*radius + 2*x - y + 1; - if(f >= 0) - { - y--; - ddF_y += 2; - f += ddF_y; - } - x++; - ddF_x += 2; - f += ddF_x; - if (quadrants.topright){ - setPixel(x0 + x, y0 + y); // quadrant I - setPixel(x0 + y, y0 + x); // quadrant I - } - if (quadrants.topleft){ - setPixel(x0 - x, y0 + y); // quadrant II - setPixel(x0 - y, y0 + x); // quadrant II - } - if (quadrants.bottomleft){ - setPixel(x0 - x, y0 - y); // quadrant III - setPixel(x0 - y, y0 - x); // quadrant III - } - if (quadrants.bottomright){ - setPixel(x0 + x, y0 - y); // quadrant IV - setPixel(x0 + y, y0 - x); // quadrant IV - } - } - }; - var _getStrokeDir = function(x,y){ - var absY = Math.abs(y); - var absX = Math.abs(x); - var strokeDir = 0; - if (y > 0 && absY >= absX) - strokeDir = 0 ; //down - else if (y < 0 && absY >= absX) - strokeDir = 1 ; // up - else if (x > 0 && absX >= absY) - strokeDir = 2 ; // left - else if (x < 0 && absX >= absY) - strokeDir = 3 ; // right - return strokeDir; - }; - /* - The daddy of all arc-related API calls - - if you're drawing anything that bends it ends up here. - */ - var _arc2 = function( params ) { - - var drone = params.drone; - var orientation = params.orientation?params.orientation:"horizontal"; - var quadrants = params.quadrants?params.quadrants:{ - topright:1, - topleft:2, - bottomleft:3, - bottomright:4 - }; - var stack = params.stack?params.stack:1; - var radius = params.radius; - var strokeWidth = params.strokeWidth?params.strokeWidth:1; - drone.chkpt('arc2'); - var x0, y0, gotoxy,setPixel; - - if (orientation == "horizontal"){ - gotoxy = function(x,y){ return drone.right(x).fwd(y);}; - drone.right(radius).fwd(radius).chkpt('center'); - switch (drone.dir) { - case 0: // east - case 2: // west - x0 = drone.z; - y0 = drone.x; - break; - case 1: // south - case 3: // north - x0 = drone.x; - y0 = drone.z; - } - setPixel = function(x,y) { - x = (x-x0); - y = (y-y0); - if (params.fill){ - // wph 20130114 more efficient esp. for large cylinders/spheres - if (y < 0){ - drone - .fwd(y).right(x) - .cuboidX(params.blockType,params.meta,1,stack,Math.abs(y*2)+1) - .back(y).left(x); - } - }else{ - if (strokeWidth == 1){ - gotoxy(x,y) - .cuboidX(params.blockType, - params.meta, - 1, // width - stack, // height - strokeWidth // depth - ) - .move('center'); - } else { - var strokeDir = _getStrokeDir(x,y); - var width = 1, depth = 1; - switch (strokeDir){ - case 0: // down - y = y-(strokeWidth-1); - depth = strokeWidth; - break; - case 1: // up - depth = strokeWidth; - break; - case 2: // left - width = strokeWidth; - x = x-(strokeWidth-1); - break; - case 3: // right - width = strokeWidth; - break; - } - gotoxy(x,y) - .cuboidX(params.blockType, params.meta, width, stack, depth) - .move('center'); - - } - } - }; - }else{ - // vertical - gotoxy = function(x,y){ return drone.right(x).up(y);}; - drone.right(radius).up(radius).chkpt('center'); - switch (drone.dir) { - case 0: // east - case 2: // west - x0 = drone.z; - y0 = drone.y; - break; - case 1: // south - case 3: // north - x0 = drone.x; - y0 = drone.y; - } - setPixel = function(x,y) { - x = (x-x0); - y = (y-y0); - if (params.fill){ - // wph 20130114 more efficient esp. for large cylinders/spheres - if (y < 0){ - drone - .up(y).right(x) - .cuboidX(params.blockType,params.meta,1,Math.abs(y*2)+1,stack) - .down(y).left(x); - } - }else{ - if (strokeWidth == 1){ - gotoxy(x,y) - .cuboidX(params.blockType,params.meta,strokeWidth,1,stack) - .move('center'); - }else{ - var strokeDir = _getStrokeDir(x,y); - var width = 1, height = 1; - switch (strokeDir){ - case 0: // down - y = y-(strokeWidth-1); - height = strokeWidth; - break; - case 1: // up - height = strokeWidth; - break; - case 2: // left - width = strokeWidth; - x = x-(strokeWidth-1); - break; - case 3: // right - width = strokeWidth; - break; - } - gotoxy(x,y) - .cuboidX(params.blockType, params.meta, width, height, stack) - .move('center'); - - } - } - }; - } - /* - setPixel assumes a 2D plane - need to put a block along appropriate plane - */ - _bresenham(x0,y0,radius,setPixel,quadrants); - - params.drone.move('arc2'); - }; - - - Drone.extend('arc',function(params) { - params.drone = this; - _arc2(params); - }); - // ======================================================================== - // Private variables and functions - // ======================================================================== - var _cylinder0 = function(block,radius,height,exactParams){ - var arcParams = { - radius: radius, - fill: false, - orientation: 'horizontal', - stack: height, - }; - - if (exactParams){ - arcParams.blockType = exactParams.blockType; - arcParams.meta = exactParams.meta; - }else{ - var md = this._getBlockIdAndMeta(block); - arcParams.blockType = md[0]; - arcParams.meta = md[1]; - } - return this.arc(arcParams); - }; - var _cylinder1 = function(block,radius,height,exactParams){ - var arcParams = { - radius: radius, - fill: true, - orientation: 'horizontal', - stack: height, - }; - - if (exactParams){ - arcParams.blockType = exactParams.blockType; - arcParams.meta = exactParams.meta; - }else{ - var md = this._getBlockIdAndMeta(block); - arcParams.blockType = md[0]; - arcParams.meta = md[1]; - } - return this.arc(arcParams); - }; - var _getDirFromRotation = function(r){ - // 0 = east, 1 = south, 2 = west, 3 = north - // 46 to 135 = west - // 136 to 225 = north - // 226 to 315 = east - // 316 to 45 = south - - r = (r + 360) % 360; // east could be 270 or -90 - - if (r > 45 && r <= 135) - return 2; // west - if (r > 135 && r <= 225) - return 3; // north - if (r > 225 && r <= 315) - return 0; // east - if (r > 315 || r < 45) - return 1; // south - }; - var _getBlockIdAndMeta = function(b){ - var defaultMeta = 0; - if (typeof b == 'string'){ - var bs = b; - var sp = bs.indexOf(':'); - if (sp == -1){ - b = parseInt(bs); - // wph 20130414 - use sensible defaults for certain blocks e.g. stairs - // should face the drone. - for (var i in blocks.stairs){ - if (blocks.stairs[i] === b){ - defaultMeta = Drone.PLAYER_STAIRS_FACING[this.dir]; - break; - } - } - return [b,defaultMeta]; - } - b = parseInt(bs.substring(0,sp)); - var md = parseInt(bs.substring(sp+1,bs.length)); - return [b,md]; - }else{ - // wph 20130414 - use sensible defaults for certain blocks e.g. stairs - // should face the drone. - for (var i in blocks.stairs){ - if (blocks.stairs[i] === b){ - defaultMeta = Drone.PLAYER_STAIRS_FACING[this.dir]; - break; - } - } - return [b,defaultMeta]; - } - }; - // - // movement - // - var _movements = [{},{},{},{}]; - // east - _movements[0].right = function(that,n){ that.z +=n; return that;}; - _movements[0].left = function(that,n){ that.z -=n; return that;}; - _movements[0].fwd = function(that,n){ that.x +=n; return that;}; - _movements[0].back = function(that,n){ that.x -= n; return that;}; - // south - _movements[1].right = _movements[0].back; - _movements[1].left = _movements[0].fwd; - _movements[1].fwd = _movements[0].right; - _movements[1].back = _movements[0].left; - // west - _movements[2].right = _movements[0].left; - _movements[2].left = _movements[0].right; - _movements[2].fwd = _movements[0].back; - _movements[2].back = _movements[0].fwd; - // north - _movements[3].right = _movements[0].fwd; - _movements[3].left = _movements[0].back; - _movements[3].fwd = _movements[0].left; - _movements[3].back = _movements[0].right; - var _traverse = [{},{},{},{}]; - // east - _traverse[0].width = function(that,n,callback){ - var s = that.z, e = s + n; - for (; that.z < e; that.z++){ - callback(that.z-s); - } - that.z = s; - }; - _traverse[0].depth = function(that,n,callback){ - var s = that.x, e = s+n; - for (;that.x < e;that.x++){ - callback(that.x-s); - } - that.x = s; - }; - // south - _traverse[1].width = function(that,n,callback){ - var s = that.x, e = s-n; - for (;that.x > e;that.x--){ - callback(s-that.x); - } - that.x = s; - }; - _traverse[1].depth = _traverse[0].width; - // west - _traverse[2].width = function(that,n,callback){ - var s = that.z, e = s-n; - for (;that.z > e;that.z--){ - callback(s-that.z); - } - that.z = s; - }; - _traverse[2].depth = _traverse[1].width; - // north - _traverse[3].width = _traverse[0].depth; - _traverse[3].depth = _traverse[2].width; - var _traverseHeight = function(that,n,callback){ - var s = that.y, e = s + n; - for (; that.y < e; that.y++){ - callback(that.y-s); - } - that.y = s; - }; - // - // standard fisher-yates shuffle algorithm - // - var _fisherYates = function( myArray ) { - var i = myArray.length; - if ( i == 0 ) return false; - while ( --i ) { - var j = Math.floor( Math.random() * ( i + 1 ) ); - var tempi = myArray[i]; - var tempj = myArray[j]; - myArray[i] = tempj; - myArray[j] = tempi; - } - }; - - var _rand = function(blockDistribution){ - if (!(blockDistribution.constructor == Array)){ - var a = []; - for (var p in blockDistribution){ - var n = blockDistribution[p]; - for (var i = 0;i < n;i++){ - a.push(p); - } - } - blockDistribution = a; - } - while (blockDistribution.length < 1000){ - // make array bigger so that it's more random - blockDistribution = blockDistribution.concat(blockDistribution); - } - _fisherYates(blockDistribution); - return blockDistribution; - }; - Drone.extend('rand',function(dist,w,h,d){ - var randomized = _rand(dist); - this.boxa(randomized,w,h,d); - }); - var _trees = { - oak: org.bukkit.TreeType.BIG_TREE , - birch: org.bukkit.TreeType.BIRCH , - jungle: org.bukkit.TreeType.JUNGLE, - spruce: org.bukkit.TreeType.REDWOOD - }; - for (var p in _trees) - { - Drone.extend(p, function(v) { - return function() { - var block = this.world.getBlockAt(this.x,this.y,this.z); - if (block.typeId == 2){ - this.up(); - } - var treeLoc = new org.bukkit.Location(this.world,this.x,this.y,this.z); - var successful = treeLoc.world.generateTree(treeLoc,v); - if (block.typeId == 2){ - this.down(); - } - }; - }(_trees[p])); - } - - Drone.extend('garden',function(w,d) - { - // make sure grass is present first - this.down().box(2,w,1,d).up(); - - // make flowers more common than long grass - var dist = {37: 3, // red flower - 38: 3, // yellow flower - '31:1': 2, // long grass - 0: 1 - }; - - return this.rand(dist,w,1,d); - }); - // - // Drone's clipboard - // - Drone.clipBoard = {}; - Drone.extend('copy', function(name, w, h, d) - { - var that = this; - var ccContent = []; - _traverse[this.dir].width(that,w,function(ww){ - ccContent.push([]); - _traverseHeight(that,h,function(hh){ - ccContent[ww].push([]); - _traverse[that.dir].depth(that,d,function(dd){ - var b = getBlock(that.x,that.y,that.z); - ccContent[ww][hh][dd] = b; - }); - }); - }); - Drone.clipBoard[name] = {dir: this.dir, blocks: ccContent}; - }); - - Drone.extend('paste',function(name) - { - var ccContent = Drone.clipBoard[name]; - var srcBlocks = ccContent.blocks; - var srcDir = ccContent.dir; // direction player was facing when copied. - var dirOffset = (4 + (this.dir - srcDir)) %4; - var that = this; - _traverse[this.dir].width(that,srcBlocks.length,function(ww){ - var h = srcBlocks[ww].length; - _traverseHeight(that,h,function(hh){ - var d = srcBlocks[ww][hh].length; - _traverse[that.dir].depth(that,d,function(dd){ - var b = srcBlocks[ww][hh][dd]; - var bm = that._getBlockIdAndMeta(b); - var cb = bm[0]; - var md = bm[1]; - // - // need to adjust blocks which face a direction - // - switch (cb) - { - // - // doors - // - case 64: // wood - case 71: // iron - // top half of door doesn't need to change - if (md < 8) { - md = (md + dirOffset) % 4; - } - break; - // - // stairs - // - case 53: // oak - case 67: // cobblestone - case 108: // red brick - case 109: // stone brick - case 114: // nether brick - case 128: // sandstone - case 134: // spruce - case 135: // birch - case 136: // junglewood - var dir = md & 0x3; - var a = Drone.PLAYER_STAIRS_FACING; - var len = a.length; - for (var c=0;c < len;c++){ - if (a[c] == dir){ - break; - } - } - c = (c + dirOffset) %4; - var newDir = a[c]; - md = (md >>2<<2) + newDir; - break; - // - // signs , ladders etc - // - case 23: // dispenser - case 54: // chest - case 61: // furnace - case 62: // burning furnace - case 65: // ladder - case 68: // wall sign - var a = Drone.PLAYER_SIGN_FACING; - var len = a.length; - for (var c=0;c < len;c++){ - if (a[c] == md){ - break; - } - } - c = (c + dirOffset) %4; - var newDir = a[c]; - md = newDir; - break; - } - putBlock(that.x,that.y,that.z,cb,md); - }); - }); - }); - }); - Drone.extend('cylinder0',_cylinder0); - Drone.extend('cylinder', _cylinder1); - - // - // make all Drone's methods available also as standalone functions - // which return a drone object - // this way drones can be created and used as follows... - // - // /js box( blocks.oak, 7, 3, 4) - // - // ... which is a short-hand way to create a wooden building 7x3x4 - // -/* - var ops = ['up','down','left','right','fwd','back','turn', - 'chkpt','move', - 'box','box0','boxa','prism','prism0','cylinder','cylinder0','arc', - 'door','door2','sign','oak','spruce','birch','jungle', - 'rand','garden', - 'copy','paste' - ]; - - for (var i = 0;i < ops.length; i++){ - global[ops[i]] = function(op){ - return function(){ - var result = new Drone(); - result[op].apply(result,arguments); - return result; - }; - }(ops[i]); - } -*/ - // - // wph 20130130 - make this a method - extensions can use it. - // - Drone.prototype._getBlockIdAndMeta = _getBlockIdAndMeta; - -}()); diff --git a/src/main/javascript/drone/sphere.js b/src/main/javascript/drone/sphere.js deleted file mode 100644 index eaf6b8f..0000000 --- a/src/main/javascript/drone/sphere.js +++ /dev/null @@ -1,265 +0,0 @@ -/************************************************************************ -Drone.sphere() method -===================== -Creates a sphere. - -Parameters ----------- - - * block - The block the sphere will be made of. - * radius - The radius of the sphere. - -Example -------- -To create a sphere of Iron with a radius of 10 blocks... - - sphere( blocks.iron, 10); - -![sphere example](img/sphereex1.png) - -Spheres are time-consuming to make. You *can* make large spheres (250 radius) but expect the -server to be very busy for a couple of minutes while doing so. - -***/ -Drone.extend('sphere', function(block,radius) -{ - var lastRadius = radius; - var slices = [[radius,0]]; - var diameter = radius*2; - var bm = this._getBlockIdAndMeta(block); - - var r2 = radius*radius; - for (var i = 0; i <= radius;i++){ - var newRadius = Math.round(Math.sqrt(r2 - i*i)); - if (newRadius == lastRadius) - slices[slices.length-1][1]++; - else - slices.push([newRadius,1]); - lastRadius = newRadius; - } - this.chkpt('sphere'); - // - // mid section - // - this.up(radius - slices[0][1]) - .cylinder(block,radius,(slices[0][1]*2)-1,{blockType: bm[0],meta: bm[1]}) - .down(radius-slices[0][1]); - - var yOffset = -1; - for (var i = 1; i < slices.length;i++) - { - yOffset += slices[i-1][1]; - var sr = slices[i][0]; - var sh = slices[i][1]; - var v = radius + yOffset, h = radius-sr; - // northern hemisphere - this.up(v).fwd(h).right(h) - .cylinder(block,sr,sh,{blockType: bm[0],meta: bm[1]}) - .left(h).back(h).down(v); - - // southern hemisphere - v = radius - (yOffset+sh+1); - this.up(v).fwd(h).right(h) - .cylinder(block,sr,sh,{blockType: bm[0],meta: bm[1]}) - .left(h).back(h). down(v); - } - return this.move('sphere'); -}); -/************************************************************************ -Drone.sphere0() method -====================== -Creates an empty sphere. - -Parameters ----------- - - * block - The block the sphere will be made of. - * radius - The radius of the sphere. - -Example -------- -To create a sphere of Iron with a radius of 10 blocks... - - sphere0( blocks.iron, 10); - -Spheres are time-consuming to make. You *can* make large spheres (250 radius) but expect the -server to be very busy for a couple of minutes while doing so. - -***/ -Drone.extend('sphere0', function(block,radius) -{ -/* - this.sphere(block,radius) - .fwd().right().up() - .sphere(0,radius-1) - .back().left().down(); - -*/ - - var lastRadius = radius; - var slices = [[radius,0]]; - var diameter = radius*2; - var bm = this._getBlockIdAndMeta(block); - - var r2 = radius*radius; - for (var i = 0; i <= radius;i++){ - var newRadius = Math.round(Math.sqrt(r2 - i*i)); - if (newRadius == lastRadius) - slices[slices.length-1][1]++; - else - slices.push([newRadius,1]); - lastRadius = newRadius; - } - this.chkpt('sphere0'); - // - // mid section - // - //.cylinder(block,radius,(slices[0][1]*2)-1,{blockType: bm[0],meta: bm[1]}) - this.up(radius - slices[0][1]) - .arc({blockType: bm[0], - meta: bm[1], - radius: radius, - strokeWidth: 2, - stack: (slices[0][1]*2)-1, - fill: false - }) - .down(radius-slices[0][1]); - - var yOffset = -1; - var len = slices.length; - for (var i = 1; i < len;i++) - { - yOffset += slices[i-1][1]; - var sr = slices[i][0]; - var sh = slices[i][1]; - var v = radius + yOffset, h = radius-sr; - // northern hemisphere - // .cylinder(block,sr,sh,{blockType: bm[0],meta: bm[1]}) - this.up(v).fwd(h).right(h) - .arc({ - blockType: bm[0], - meta: bm[1], - radius: sr, - stack: sh, - fill: false, - strokeWidth: i Date: Tue, 24 Dec 2013 00:15:17 +0000 Subject: [PATCH 029/456] reorg --- docs/API-Reference.md | 877 ++++++++++-------- .../scriptcraft/ScriptCraftPlugin.java | 6 +- 2 files changed, 484 insertions(+), 399 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 8bb5283..ed41dc1 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -1,332 +1,3 @@ -# ScriptCraft API Reference - -Walter Higgins - -[walter.higgins@gmail.com][email] - -[email]: mailto:walter.higgins@gmail.com?subject=ScriptCraft_API_Reference - -## Module Loading - -At server startup the ScriptCraft Java plugin is loaded and once -loaded the Java plugin will in turn begin loading all of the -javascript (.js) files it finds in the js-plugins directory (in the -current working directory). If this is the first time the ScriptCraft -plugin is loaded, then the js-plugins directory will not yet exist, it -will be created and all of the bundled javascript files will be -unzipped into it from a bundled resource within the Java plugin. The -very first javascript file to load will always be -js-plugins/core/_scriptcraft.js. Then all other javascript files are -loaded except for filenames which begin with `_` (underscore), such files -are considered to be private modules and will not be automatically -loaded at startup. - -### Directory structure - -The js-plugins directory is loosely organised into subdirectories - -one for each module. Each subdirectory in turn can contain one or more -javascript files. Within each directory, a javascript file with the -same filename as the directory will always be loaded before all other -files in the same directory. So for example, drone/drone.js will -always load before any other files in the drone/ directory. Similarly -utils/utils.js will always load before any other files in the utils/ -directory. - -### Directories - -As of February 10 2013, the js-plugins directory has the following sub-directories... - - * core - Contains javascript files containing Core functionality crucial to ScriptCraft and modules which use it. - * drone - Contains the drone module and drone extensions. Drone was the first scriptcraft module. - * ext - Contains external 3rd party javascript libraries (e.g. json2.js - the JSON lib) - * mini-games - Contains mini-games - * arrows - The arrows module - * signs - The signs module - * chat - The chat plugin/module - * alias - The alias plugin/module - -## Core Module - -This module defines commonly used functions by all plugins... - - * load (filename,warnOnFileNotFound) - loads and evaluates a javascript file, returning the evaluated object. - - * save (object, filename) - saves an object to a file. - - * plugin (name, interface, isPersistent) - defines a new plugin. If - isPersistent is true then the plugin doesn't have to worry about - loading and saving state - that will be done by the framework. Just - make sure that anything you want to save (and restore) is in the plugin's - 'store' property - this will be created automatically if not - already defined. (its type is object {} ) . More on plugins below. - - * ready (function) - specifies code to be executed only when all the plugins have loaded. - - * command (name, function) - defines a command that can be used by - non-operators. The `command` function provides a way for plugin - developers to provide new commands for use by players. - -### load() function - -The load() function is used by ScriptCraft at startup to load all of -the javascript modules and data. You normally wouldn't need to call -this function directly. If you put a javascript file anywhere in the -craftbukkit/js-plugins directory tree it will be loaded automatically -when craftbukkit starts up. The exception is files whose name begins -with an underscore `_` character. These files will not be -automatically loaded at startup as they are assumed to be files -managed / loaded by plugins. - -#### Parameters - - * filename - The name of the file to load. - * warnOnFileNotFound (optional - default: false) - warn if the file was not found. - -#### Return - -load() will return the result of the last statement evaluated in the file. - -#### Example - - load(__folder + "myFile.js"); // loads a javascript file and evaluates it. - - var myData = load("myData.json"); // loads a javascript file and evaluates it - eval'd contents are returned. - -myData.json contents... - - __data = { - players: { - walterh: { - h: ["jsp home {1}"], - sunny:["time set 0", - "weather clear"] - } - } - } - -### save() function - -The save() function saves an in-memory javascript object to a -specified file. Under the hood, save() uses JSON (specifically -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 `save()` function can later be -restored using the `load()` function. - -#### Parameters - - * objectToSave : The object you want to save. - * filename : The name of the file you want to save it to. - -#### Example - - var myObject = { name: 'John Doe', - aliases: ['John Ray', 'John Mee'], - date_of_birth: '1982/01/31' }; - save(myObject, 'johndoe.json'); - -johndoe.json contents... - - var __data = { "name": "John Doe", - "aliases": ["John Ray", "John Mee"], - "date_of_birth": "1982/01/31" }; - -### plugin() function - -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 -managed by Scriptcraft is `state` - this special member will be -automatically saved at shutdown and loaded at startup by -ScriptCraft. This makes it easier to write plugins which need to -persist data. - -#### Parameters - - * 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. - -#### Example - -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]: http://walterhiggins.net/blog/ScriptCraft-1-Month-later - -### command() function - -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. - -#### Parameters - - * 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. - * 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. - -#### Example - -See chat/colors.js or alias/alias.js or homes/homes.js for examples of how to use the `command()` function. - -### ready() function - -The `ready()` function provides a way for plugins to do additional -setup once all of the other plugins/modules have loaded. For example, -event listener registration can only be done after the -events/events.js module has loaded. A plugin author could load the -file explicilty like this... - - load(__folder + "../events/events.js"); - - // event listener registration goes here - -... or better still, just do event regristration using the `ready()` -handler knowing that by the time the `ready()` callback is invoked, -all of the scriptcraft modules have been loaded... - - ready(function(){ - // event listener registration goes here - // code that depends on other plugins/modules also goes here - }); - -The execution of the function object passed to the `ready()` function -is *deferred* until all of the plugins/modules have loaded. That way -you are guaranteed that when the function is invoked, all of the -plugins/modules have been loaded and evaluated and are ready to use. - -Core Module - Special Variables -=============================== -There are a couple of special javascript variables available in ScriptCraft... - - * __folder - The current working directory - this variable is only to be used within the main body of a .js file. - * __plugin - The ScriptCraft JavaPlugin object. - * server - The Minecraft Server object. - * self - the current player. (Note - this value should not be used in multi-threaded scripts - it's not thread-safe) - -## Miscellaneous Core Functions - -### setTimeout() function - -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. - -If Node.js supports setTimeout() then it's probably good for ScriptCraft to support it too. - -[btdoc]: http://jd.bukkit.org/beta/apidocs/org/bukkit/scheduler/BukkitTask.html - -#### Example - - // - // 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 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 - -## Require - Node.js-style module loading in ScriptCraft - -#### (Experimental as of 2013-12-21) - -Node.js is a server-side javascript environment with an excellent -module loading system based on CommonJS. Modules in Node.js are really -simple. Each module is in its own javascript file and all variables -and functions within the file are private to that file/module only. -There is a very concise explanation of CommonJS modules at... - -[http://wiki.commonjs.org/wiki/Modules/1.1.1.][cjsmodules] - -If you want to export a variable or function you use the module.export -property. - -For example imagine you have 3 files program.js, inc.js and math.js ... - -### math.js - - exports.add = function(a,b){ - return a + b; - } - -### inc.js - - var math = require('./math'); - exports.increment = function(n){ - return math.add(n, 1); - } - -### program.js - - var inc = require('./inc').increment; - var a = 7; - a = inc(a); - print(a); - -You can see from the above sample code that programs can use modules -and modules themeselves can use other modules. Modules have full -control over what functions and properties they want to provide to -others. - -## Important - -Although ScriptCraft now supports Node.js style modules, it does not -support node modules. Node.js and Rhino are two very different -Javascript environments. ScriptCraft uses Rhino Javascript, not -Node.js. - -Right now, the base directory is for relative modules is 'js-plugins'. -Modules can be loaded using relative or absolute paths. Per the CommonJS -module specification, the '.js' suffix is optional. - -[cjsmodules]: http://wiki.commonjs.org/wiki/Modules/1.1.1. - Drone Module ============ The Drone is a convenience class for building. It can be used for... @@ -693,6 +364,7 @@ To draw a 1/4 circle (top right quadrant only) with a radius of 10 and stroke wi [bres]: http://en.wikipedia.org/wiki/Midpoint_circle_algorithm [dv]: http://www.minecraftwiki.net/wiki/Data_values + Drone.door() method =================== create a door - if a parameter is supplied an Iron door is created otherwise a wooden door is created. @@ -778,6 +450,7 @@ the `up()` method is called first). None of the tree methods require parameters. Tree methods will only be successful if the tree is placed on grass in a setting where trees can grow. + Drone.garden() method ===================== places random flowers and long grass (similar to the effect of placing bonemeal on grass) @@ -1182,46 +855,369 @@ To construct a spiral staircase 5 floors high made of oak... spiral_stairs('oak', 5); -Classroom Module -================ -The `classroom` object contains a couple of utility functions for use -in a classroom setting. The goal of these functions is to make it -easier for tutors to facilitate ScriptCraft for use by students in a -classroom environment. Although granting ScriptCraft access to -students on a shared server is potentially risky (Students can -potentially abuse it), it is slighlty less risky than granting -operator privileges to each student. (Enterprising students will -quickly realise how to grant themselves and others operator privileges -once they have access to ScriptCraft). +## Require - Node.js-style module loading in ScriptCraft -The goal of this module is not so much to enforce restrictions -(security or otherwise) but to make it easier for tutors to setup a shared server -so students can learn Javascript. +#### (Experimental as of 2013-12-21) -classroom.allowScripting() function -=================================== -Allow or disallow anyone who connects to the server (or is already -connected) to use ScriptCraft. This function is preferable to granting 'ops' privileges -to every student in a Minecraft classroom environment. +Node.js is a server-side javascript environment with an excellent +module loading system based on CommonJS. Modules in Node.js are really +simple. Each module is in its own javascript file and all variables +and functions within the file are private to that file/module only. +There is a very concise explanation of CommonJS modules at... -Parameters ----------- +[http://wiki.commonjs.org/wiki/Modules/1.1.1.][cjsmodules] - * canScript : true or false +If you want to export a variable or function you use the module.export +property. -Example -------- -To allow all players (and any players who connect to the server) to -use the `js` and `jsp` commands... +For example imagine you have 3 files program.js, inc.js and math.js ... - /js classroom.allowScripting(true) +### math.js -To disallow scripting (and prevent players who join the server from using the commands)... + exports.add = function(a,b){ + return a + b; + } - /js classroom.allowScripting(false) +### inc.js -Only ops users can run the classroom.allowScripting() function - this is so that students -don't try to bar themselves and each other from scripting. + var math = require('./math'); + exports.increment = function(n){ + return math.add(n, 1); + } + +### program.js + + var inc = require('./inc').increment; + var a = 7; + a = inc(a); + print(a); + +You can see from the above sample code that programs can use modules +and modules themeselves can use other modules. Modules have full +control over what functions and properties they want to provide to +others. + +## Important + +Although ScriptCraft now supports Node.js style modules, it does not +support node modules. Node.js and Rhino are two very different +Javascript environments. ScriptCraft uses Rhino Javascript, not +Node.js. + +Right now, the base directory is for relative modules is 'js-plugins'. +Modules can be loaded using relative or absolute paths. Per the CommonJS +module specification, the '.js' suffix is optional. + +[cjsmodules]: http://wiki.commonjs.org/wiki/Modules/1.1.1. + +## When resolving module names to file paths, ScriptCraft uses the following rules... + + 1. if the module does not begin with './' or '/' then ... + + 1.1 Look in the 'scriptcraft/lib' directory. If it's not there then... + 1.2 Look in the 'scriptcraft/modules' directory. If it's not there then + Throw an Error. + + 2. If the module begins with './' or '/' then ... + + 2.1 if the module begins with './' then it's treated as a file path. File paths are + always relative to the module from which the require() call is being made. + + 2.2 If the module begins with '/' then it's treated as an absolute path. + + If the module does not have a '.js' suffix, and a file with the same name and a .js sufix exists, + then the file will be loaded. + + 3. If the module name resolves to a directory then... + + 3.1 look for a package.json file in the directory and load the `main` property e.g. + + // package.json located in './some-library/' + { + "main": './some-lib.js', + "name": 'some-library' + } + + 3.2 if no package.json file exists then look for an index.js file in the directory + +# ScriptCraft API Reference + +Walter Higgins + +[walter.higgins@gmail.com][email] + +[email]: mailto:walter.higgins@gmail.com?subject=ScriptCraft_API_Reference + +## Module Loading + +At server startup the ScriptCraft Java plugin is loaded and once +loaded the Java plugin will in turn begin loading all of the +javascript (.js) files it finds in the js-plugins directory (in the +current working directory). If this is the first time the ScriptCraft +plugin is loaded, then the js-plugins directory will not yet exist, it +will be created and all of the bundled javascript files will be +unzipped into it from a bundled resource within the Java plugin. The +very first javascript file to load will always be +js-plugins/core/_scriptcraft.js. Then all other javascript files are +loaded except for filenames which begin with `_` (underscore), such files +are considered to be private modules and will not be automatically +loaded at startup. + +### Directory structure + +The js-plugins directory is loosely organised into subdirectories - +one for each module. Each subdirectory in turn can contain one or more +javascript files. Within each directory, a javascript file with the +same filename as the directory will always be loaded before all other +files in the same directory. So for example, drone/drone.js will +always load before any other files in the drone/ directory. Similarly +utils/utils.js will always load before any other files in the utils/ +directory. + +### Directories + +As of February 10 2013, the js-plugins directory has the following sub-directories... + + * core - Contains javascript files containing Core functionality crucial to ScriptCraft and modules which use it. + * drone - Contains the drone module and drone extensions. Drone was the first scriptcraft module. + * ext - Contains external 3rd party javascript libraries (e.g. json2.js - the JSON lib) + * mini-games - Contains mini-games + * arrows - The arrows module + * signs - The signs module + * chat - The chat plugin/module + * alias - The alias plugin/module + +## Core Module + +This module defines commonly used functions by all plugins... + + * echo (message) - Displays a message on the screen. + For example: `/js echo('Hello World')` will print Hello World on the in-game chat window. + 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')` + + * load (filename,warnOnFileNotFound) - loads and evaluates a javascript file, returning the evaluated object. + + * save (object, filename) - saves an object to a file. + + * plugin (name, interface, isPersistent) - defines a new plugin. If + isPersistent is true then the plugin doesn't have to worry about + loading and saving state - that will be done by the framework. Just + make sure that anything you want to save (and restore) is in the plugin's + 'store' property - this will be created automatically if not + already defined. (its type is object {} ) . More on plugins below. + + * ready (function) - specifies code to be executed only when all the plugins have loaded. + + * command (name, function) - defines a command that can be used by + non-operators. The `command` function provides a way for plugin + developers to provide new commands for use by players. + +### load() function + +The load() function is used by ScriptCraft at startup to load all of +the javascript modules and data. You normally wouldn't need to call +this function directly. If you put a javascript file anywhere in the +craftbukkit/js-plugins directory tree it will be loaded automatically +when craftbukkit starts up. The exception is files whose name begins +with an underscore `_` character. These files will not be +automatically loaded at startup as they are assumed to be files +managed / loaded by plugins. + +#### Parameters + + * filename - The name of the file to load. + * warnOnFileNotFound (optional - default: false) - warn if the file was not found. + +#### Return + +load() will return the result of the last statement evaluated in the file. + +#### Example + + load(__folder + "myFile.js"); // loads a javascript file and evaluates it. + + var myData = load("myData.json"); // loads a javascript file and evaluates it - eval'd contents are returned. + +myData.json contents... + + __data = { + players: { + walterh: { + h: ["jsp home {1}"], + sunny:["time set 0", + "weather clear"] + } + } + } + +### save() function + +The save() function saves an in-memory javascript object to a +specified file. Under the hood, save() uses JSON (specifically +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 `save()` function can later be +restored using the `load()` function. + +#### Parameters + + * objectToSave : The object you want to save. + * filename : The name of the file you want to save it to. + +#### Example + + var myObject = { name: 'John Doe', + aliases: ['John Ray', 'John Mee'], + date_of_birth: '1982/01/31' }; + save(myObject, 'johndoe.json'); + +johndoe.json contents... + + var __data = { "name": "John Doe", + "aliases": ["John Ray", "John Mee"], + "date_of_birth": "1982/01/31" }; + +### plugin() function + +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 +managed by Scriptcraft is `store` - this special member will be +automatically saved at shutdown and loaded at startup by +ScriptCraft. This makes it easier to write plugins which need to +persist data. + +#### Parameters + + * 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. + +#### Example + +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]: http://walterhiggins.net/blog/ScriptCraft-1-Month-later + +### command() function + +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. + +#### Parameters + + * 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. + * 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. + +#### Example + +See chat/colors.js or alias/alias.js or homes/homes.js for examples of how to use the `command()` function. + +### ready() function + +The `ready()` function provides a way for plugins to do additional +setup once all of the other plugins/modules have loaded. For example, +event listener registration can only be done after the +events/events.js module has loaded. A plugin author could load the +file explicilty like this... + + load(__folder + "../events/events.js"); + + // event listener registration goes here + +... or better still, just do event regristration using the `ready()` +handler knowing that by the time the `ready()` callback is invoked, +all of the scriptcraft modules have been loaded... + + ready(function(){ + // event listener registration goes here + // code that depends on other plugins/modules also goes here + }); + +The execution of the function object passed to the `ready()` function +is *deferred* until all of the plugins/modules have loaded. That way +you are guaranteed that when the function is invoked, all of the +plugins/modules have been loaded and evaluated and are ready to use. + +Core Module - Special Variables +=============================== +There are a couple of special javascript variables available in ScriptCraft... + + * __folder - The current working directory - this variable is only to be used within the main body of a .js file. + * __plugin - The ScriptCraft JavaPlugin object. + * server - The Minecraft Server object. + * self - the current player. (Note - this value should not be used in multi-threaded scripts - it's not thread-safe) + +## Miscellaneous Core Functions + +### setTimeout() function + +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. + +If Node.js supports setTimeout() then it's probably good for ScriptCraft to support it too. + +[btdoc]: http://jd.bukkit.org/beta/apidocs/org/bukkit/scheduler/BukkitTask.html + +#### Example + + // + // 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 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 events Module ============= @@ -1269,12 +1265,16 @@ Example: ------ The following code will print a message on screen every time a block is broken in the game + var events = require('./events/events'); + events.on("block.BlockBreakEvent", function(listener, evt){ echo (evt.player.name + " broke a block!"); }); To handle an event only once and unregister from further events... + var events = require('./events/events'); + events.on("block.BlockBreakEvent", function(listener, evt){ print (evt.player.name + " broke a block!"); evt.handlers.unregister(listener); @@ -1282,6 +1282,8 @@ To handle an event only once and unregister from further events... To unregister a listener *outside* of the listener function... + var events = require('./events/events'); + var myBlockBreakListener = events.on("block.BlockBreakEvent",function(l,e){ ... }); ... var handlers = org.bukkit.event.block.BlockBreakEvent.getHandlerList(); @@ -1306,7 +1308,7 @@ Example.... ... creates a single firework, while .... - /js firework.fwd(3).times(5) + /js firework().fwd(3).times(5) ... creates 5 fireworks in a row. Fireworks have also been added as a possible option for the `arrow` module. To have a firework launch @@ -1317,6 +1319,7 @@ where an arrow strikes... To call the fireworks.firework() function directly, you must provide a location. For example... + /js var fireworks = require('fireworks'); /js fireworks.firework(self.location); ![firework example](img/firework.png) @@ -1347,12 +1350,14 @@ Example The following example illustrates how to use http.request to make a request to a JSON web service and evaluate its response... var jsResponse; + var http = require('./http/request'); http.request("http://scriptcraftjs.org/sample.json",function(responseCode, responseBody){ jsResponse = eval("(" + responseBody + ")"); }); ... The following example illustrates a more complex use-case POSTing parameters to a CGI process on a server... + var http = require('./http/request'); http.request({ url: "http://pixenate.com/pixenate/pxn8.pl", method: "POST", params: {script: "[]"} @@ -1360,6 +1365,47 @@ The following example illustrates how to use http.request to make a request to a var jsObj = eval("(" + responseBody + ")"); }); +String class extensions +----------------------- +The following chat-formatting methods are added to the javascript String class.. + + * aqua() + * black() + * blue() + * bold() + * brightgreen() + * darkaqua() + * darkblue() + * darkgray() + * darkgreen() + * purple() + * darkpurple() + * darkred() + * gold() + * gray() + * green() + * italic() + * lightpurple() + * indigo() + * green() + * red() + * pink() + * yellow() + * white() + * strike() + * random() + * magic() + * underline() + * reset() + +Example +------- + + var boldGoldText = "Hello World".bold().gold(); + self.sendMessage(boldGoldText); + +

Hello World

+ Utilities Module ================ Miscellaneous utility functions and classes to help with programming. @@ -1369,7 +1415,13 @@ Miscellaneous utility functions and classes to help with programming. * getPlayerObject(playerName) - returns the Player object for a named player or `self` if no name is provided. -utils.foreach() function + * getPlayerPos(playerName) - returns the player's x,y,z and yaw (direction) for a named player + or player or `self` if no parameter is provided. + + * getMousePos(playerName) - returns the x,y,z of the current block being targeted by the named player + or player or `self` if no paramter is provided. + +foreach() function ======================== The utils.foreach() function is a utility function for iterating over an array of objects (or a java.util.Collection of objects) and processing each object in turn. Where @@ -1417,6 +1469,7 @@ Example ------- The following example illustrates how to use foreach for immediate processing of an array... + var utils = require('./utils/_utils'); var players = ["moe", "larry", "curly"]; utils.foreach (players, function(item){ server.getPlayer(item).sendMessage("Hi " + item); @@ -1436,6 +1489,7 @@ without hogging CPU usage... // build a structure 200 wide x 200 tall x 200 long // (That's 8 Million Blocks - enough to tax any machine!) + var utils = require('./utils/_utils'); var a = []; a.length = 200; @@ -1494,6 +1548,8 @@ Example To warn players when night is approaching... + var utils = require('./utils/_utils'); + utils.at( "19:00", function() { utils.foreach( server.onlinePlayers, function(player){ @@ -1502,44 +1558,73 @@ To warn players when night is approaching... }, self.world); -String class extensions ------------------------ -The following chat-formatting methods are added to the javascript String class.. + +## The arrows mod adds fancy arrows to the game. + +### Usage: - * aqua() - * black() - * blue() - * bold() - * brightgreen() - * darkaqua() - * darkblue() - * darkgray() - * darkgreen() - * purple() - * darkpurple() - * darkred() - * gold() - * gray() - * green() - * italic() - * lightpurple() - * indigo() - * green() - * red() - * pink() - * yellow() - * white() - * strike() - * random() - * magic() - * underline() - * reset() + /js var arrows = require('./arrows/arrows') + + * `/js arrows.sign()` turns a targeted sign into a Arrows menu + * `/js arrows.normal()` sets arrow type to normal. + * `/js arrows.explosive()` - makes arrows explode. + * `/js arrows.teleport()` - makes player teleport to where arrow has landed. + * `/js arrows.flourish()` - makes a tree grow where the arrow lands. + * `/js arrows.lightning()` - lightning strikes where the arrow lands. + * `/js arrows.firework()` - A firework launches where the the arrow lands. + + All of the above functions can take an optional player object or name as + a parameter. E.g. + + `/js arrows.explosive('player23')` makes player23's arrows explosive. + +Classroom Module +================ +The `classroom` object contains a couple of utility functions for use +in a classroom setting. The goal of these functions is to make it +easier for tutors to facilitate ScriptCraft for use by students in a +classroom environment. Although granting ScriptCraft access to +students on a shared server is potentially risky (Students can +potentially abuse it), it is slighlty less risky than granting +operator privileges to each student. (Enterprising students will +quickly realise how to grant themselves and others operator privileges +once they have access to ScriptCraft). + +The goal of this module is not so much to enforce restrictions +(security or otherwise) but to make it easier for tutors to setup a shared server +so students can learn Javascript. + +classroom.allowScripting() function +=================================== +Allow or disallow anyone who connects to the server (or is already +connected) to use ScriptCraft. This function is preferable to granting 'ops' privileges +to every student in a Minecraft classroom environment. + +Parameters +---------- + + * canScript : true or false Example ------- +To allow all players (and any players who connect to the server) to +use the `js` and `jsp` commands... - var boldGoldText = "Hello World".bold().gold(); - self.sendMessage(boldGoldText); + /js classroom.allowScripting(true) -

Hello World

+To disallow scripting (and prevent players who join the server from using the commands)... + /js classroom.allowScripting(false) + +Only ops users can run the classroom.allowScripting() function - this is so that students +don't try to bar themselves and each other from scripting. + +## Minigame: Guess the number + +### Example + + /js Game_NumberGuess.start() + +... Begins a number-guessing game where you must guess the number (between 1 and 10) chosen by the computer. + + A basic number-guessing game that uses the Bukkit Conversation API. diff --git a/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java b/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java index 2df8324..4b7d3c4 100644 --- a/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java +++ b/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java @@ -23,7 +23,7 @@ public class ScriptCraftPlugin extends JavaPlugin implements Listener // need to look at possibly having context/scope per operator //protected Map playerContexts = new HashMap(); protected ScriptEngine engine = null; - private static final String JS_PLUGINS_DIR = "js-plugins"; + private static final String JS_PLUGINS_DIR = "scriptcraft"; /** * Unzips bundled javascript code. @@ -92,7 +92,7 @@ public class ScriptCraftPlugin extends JavaPlugin implements Listener FileReader reader = null; try{ ScriptEngineManager factory = new ScriptEngineManager(); - File boot = new File(JS_PLUGINS_DIR + "/core/_scriptcraft.js"); + File boot = new File(JS_PLUGINS_DIR + "/lib/scriptcraft.js"); this.engine = factory.getEngineByName("JavaScript"); this.engine.put("__engine",engine); this.engine.put("__plugin",this); @@ -103,7 +103,7 @@ public class ScriptCraftPlugin extends JavaPlugin implements Listener wph 20130811 Need to disable coffeescript support until issues loading and evaluating it are resolved. See issue #92 // Load the CoffeeScript compiler - File coffeescript = new File(JS_PLUGINS_DIR + "/core/_coffeescript.js"); + File coffeescript = new File(JS_PLUGINS_DIR + "/lib/coffeescript.js"); this.engine.eval(new FileReader(coffeescript)); */ From 4d807373da1d98f37fc4a4e88e6f8bd9bd5c5be9 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 24 Dec 2013 00:16:07 +0000 Subject: [PATCH 030/456] reorg --- src/main/javascript/lib/events.js | 117 ++++++++-------- src/main/javascript/lib/require.js | 207 +++++++++++++++++++++++++---- 2 files changed, 234 insertions(+), 90 deletions(-) diff --git a/src/main/javascript/lib/events.js b/src/main/javascript/lib/events.js index 7ead348..ea292e3 100644 --- a/src/main/javascript/lib/events.js +++ b/src/main/javascript/lib/events.js @@ -45,12 +45,16 @@ Example: ------ The following code will print a message on screen every time a block is broken in the game + var events = require('./events/events'); + events.on("block.BlockBreakEvent", function(listener, evt){ echo (evt.player.name + " broke a block!"); }); To handle an event only once and unregister from further events... + var events = require('./events/events'); + events.on("block.BlockBreakEvent", function(listener, evt){ print (evt.player.name + " broke a block!"); evt.handlers.unregister(listener); @@ -58,6 +62,8 @@ To handle an event only once and unregister from further events... To unregister a listener *outside* of the listener function... + var events = require('./events/events'); + var myBlockBreakListener = events.on("block.BlockBreakEvent",function(l,e){ ... }); ... var handlers = org.bukkit.event.block.BlockBreakEvent.getHandlerList(); @@ -67,67 +73,56 @@ To unregister a listener *outside* of the listener function... [buk]: http://jd.bukkit.org/dev/apidocs/index.html?org/bukkit/event/Event.html ***/ - -var events = events || { - // - // handle events in Minecraft - // -------------------------- - // eventType can be a string (assumed to be a sub package of org.bukkit.event - e.g. - // if the string "block.BlockBreakEvent" is supplied then it's converted to the - // org.bukkit.event.block.BlockBreakEvent class . For custom event classes, just - // supply the custom event class e.g. - // events.on(net.yourdomain.events.YourCustomEvent,function(l,e){ ... }); - // - on: function( - /* String or java Class */ eventType, - /* function( registeredListener, event) */ handler, - /* (optional) String (HIGH, HIGHEST, LOW, LOWEST, NORMAL, MONITOR), */ priority - ){} -}; -// -// private implementation from here on in... -// -(function(events){ - if (events._eventsLoaded){ - return; - } - var bkEvent = org.bukkit.event; - var bkEvtExecutor = org.bukkit.plugin.EventExecutor; - var bkRegListener = org.bukkit.plugin.RegisteredListener; - var _on = function(eventType, handler, priority) - { - if (typeof priority == "undefined"){ - priority = bkEvent.EventPriority.HIGHEST; - }else{ - priority = bkEvent.EventPriority[priority]; +// +// handle events in Minecraft +// -------------------------- +// eventType can be a string (assumed to be a sub package of org.bukkit.event - e.g. +// if the string "block.BlockBreakEvent" is supplied then it's converted to the +// org.bukkit.event.block.BlockBreakEvent class . For custom event classes, just +// supply the custom event class e.g. +// events.on(net.yourdomain.events.YourCustomEvent,function(l,e){ ... }); +// +var bkEvent = org.bukkit.event; +var bkEvtExecutor = org.bukkit.plugin.EventExecutor; +var bkRegListener = org.bukkit.plugin.RegisteredListener; + +exports.on = function( + /* String or java Class */ + eventType, + /* function( registeredListener, event) */ + handler, + /* (optional) String (HIGH, HIGHEST, LOW, LOWEST, NORMAL, MONITOR), */ + priority ) { + + if (typeof priority == "undefined"){ + priority = bkEvent.EventPriority.HIGHEST; + }else{ + priority = bkEvent.EventPriority[priority]; + } + if (typeof eventType == "string"){ + var subPkgs = eventType.split('.'); + eventType = bkEvent[subPkgs[0]]; + for (var i = 1;i < subPkgs.length; i++){ + eventType = eventType[subPkgs[i]]; } - if (typeof eventType == "string"){ - var subPkgs = eventType.split('.'); - eventType = bkEvent[subPkgs[0]]; - for (var i = 1;i < subPkgs.length; i++){ - eventType = eventType[subPkgs[i]]; - } - } - var handlerList = eventType.getHandlerList(); - var listener = {}; - var eventExecutor = new bkEvtExecutor(){ - execute: function(l,e){ - handler(listener.reg,e); - } - }; - /* - wph 20130222 issue #64 bad interaction with Essentials plugin - if another plugin tries to unregister a Listener (not a Plugin or a RegisteredListener) - then BOOM! the other plugin will throw an error because Rhino can't coerce an - equals() method from an Interface. - The workaround is to make the ScriptCraftPlugin java class a Listener. - Should only unregister() registered plugins in ScriptCraft js code. - */ - listener.reg = new bkRegListener( __plugin, eventExecutor, priority, __plugin, true); - handlerList.register(listener.reg); - return listener.reg; + } + var handlerList = eventType.getHandlerList(); + var listener = {}; + var eventExecutor = new bkEvtExecutor(){ + execute: function(l,e){ + handler(listener.reg,e); + } }; - events.on = _on; - events._eventsLoaded = true; -}(events)); + /* + wph 20130222 issue #64 bad interaction with Essentials plugin + if another plugin tries to unregister a Listener (not a Plugin or a RegisteredListener) + then BOOM! the other plugin will throw an error because Rhino can't coerce an + equals() method from an Interface. + The workaround is to make the ScriptCraftPlugin java class a Listener. + Should only unregister() registered plugins in ScriptCraft js code. + */ + listener.reg = new bkRegListener( __plugin, eventExecutor, priority, __plugin, true); + handlerList.register(listener.reg); + return listener.reg; +}; diff --git a/src/main/javascript/lib/require.js b/src/main/javascript/lib/require.js index a96beb8..ef95233 100644 --- a/src/main/javascript/lib/require.js +++ b/src/main/javascript/lib/require.js @@ -55,7 +55,153 @@ module specification, the '.js' suffix is optional. [cjsmodules]: http://wiki.commonjs.org/wiki/Modules/1.1.1. ***/ -(function(__plugin, __engine, verbose){ +( function (logger, evaluator, verbose, rootDir) { + + if (verbose) + logger.info("Setting up 'require' module system. Root Directory: " + rootDir); + + var File = java.io.File; + + var readModuleFromDirectory = function(dir){ + + // look for a package.json file + var pkgJsonFile = new File(dir, './package.json'); + if (pkgJsonFile.exists()){ + var pkg = load(pkgJsonFile); + var mainFile = new File(dir, pkg.main); + if (mainFile.exists()){ + return mainFile; + } else { + return null; + } + }else{ + // look for an index.js file + var indexJsFile = new File(dir + './index.js'); + if (indexJsFile.exists()){ + return indexJsFile; + } else { + return null; + } + } + }; + + var LIB_DIR = rootDir + '/lib/'; + var MODULE_DIR = rootDir + '/modules/'; + + var resolveModuleToFile = function(moduleName, parentDir) { +/********************************************************************** +## When resolving module names to file paths, ScriptCraft uses the following rules... + + 1. if the module does not begin with './' or '/' then ... + + 1.1 Look in the 'scriptcraft/lib' directory. If it's not there then... + 1.2 Look in the 'scriptcraft/modules' directory. If it's not there then + Throw an Error. + + 2. If the module begins with './' or '/' then ... + + 2.1 if the module begins with './' then it's treated as a file path. File paths are + always relative to the module from which the require() call is being made. + + 2.2 If the module begins with '/' then it's treated as an absolute path. + + If the module does not have a '.js' suffix, and a file with the same name and a .js sufix exists, + then the file will be loaded. + + 3. If the module name resolves to a directory then... + + 3.1 look for a package.json file in the directory and load the `main` property e.g. + + // package.json located in './some-library/' + { + "main": './some-lib.js', + "name": 'some-library' + } + + 3.2 if no package.json file exists then look for an index.js file in the directory + +***/ + var file = new File(moduleName); + + var fileExists = function(file) { + + if (file.isDirectory()){ + return readModuleFromDirectory(file); + }else { + return file; + } + }; + + if (file.exists()){ + return fileExists(file); + } + if (moduleName.match(/^[^\.\/]/)){ + // it's a module named like so ... 'events' , 'net/http' + // + + var resolvedFile = new File(LIB_DIR + moduleName); + if (resolvedFile.exists()){ + + return fileExists(resolvedFile); + } else{ + + // try appending a .js to the end + resolvedFile = new File(LIB_DIR + moduleName + '.js'); + if (resolvedFile.exists()){ + + return resolvedFile; + }else{ + + if (verbose){ + logger.info("File not found in " + LIB_DIR + ': ' + resolvedFile.canonicalPath); + } + + resolvedFile = new File(MODULE_DIR + moduleName); + if (resolvedFile.exists()){ + return fileExists(resolvedFile); + }else { + if (verbose){ + logger.info("File not found in " + MODULE_DIR + ': ' + resolvedFile.canonicalPath); + } + resolvedFile = new File(MODULE_DIR + moduleName + '.js'); + if (resolvedFile.exists()) + return resolvedFile; + else{ + + if (verbose){ + logger.info("File not found in " + MODULE_DIR + ': ' + resolvedFile.canonicalPath); + } + } + } + } + } + } else { + // it's of the form ./path + file = new File(parentDir, moduleName); + if (file.exists()){ + if (file.isDirectory()){ + return readModuleFromDirectory(file); + }else { + return file; + } + }else { + + // try appending a .js to the end + var pathWithJSExt = file.canonicalPath + '.js'; + file = new File( parentDir, pathWithJSExt); + if (file.exists()) + return file; + else{ + + file = new File(pathWithJSExt); + if (file.exists()) + return file; + } + + } + } + return null; + }; /* wph 20131215 Experimental */ @@ -66,25 +212,10 @@ module specification, the '.js' suffix is optional. var _canonize = function(file){ return "" + file.canonicalPath.replaceAll("\\\\","/"); }; - - var file = new java.io.File(parentFile, path); - if (!file.exists()) - { - if (path.match(/\.js$/i)){ - __plugin.logger.warning('require("' + path + '") failed. File [' + file.canonicalPath + '] not found'); - return; - }else{ - path = path + '.js'; - file = new java.io.File(parentFile, path); - if (!file.exists()){ - __plugin.logger.warning('require("' + path + '") failed. File [' + file.canonicalPath + '] not found'); - return; - } - } - } - if (file.isDirectory()){ - __plugin.logger.warning('require("' + path + '") directories not yet supported. ' + file.canonicalPath); - return; + + var file = resolveModuleToFile(path, parentFile); + if (!file){ + throw new Error("require('" + path + "'," + parentFile.canonicalPath + ") failed"); } var canonizedFilename = _canonize(file); @@ -93,12 +224,14 @@ module specification, the '.js' suffix is optional. return moduleInfo; } if (verbose){ - print("loading module " + canonizedFilename); + logger.info("loading module " + canonizedFilename); } var reader = new java.io.FileReader(file); var br = new java.io.BufferedReader(reader); var code = ""; - while ((r = br.readLine()) !== null) code += r + "\n"; + var r = null; + while ((r = br.readLine()) !== null) + code += r + "\n"; var head = "(function(exports,module,require,__filename,__dirname){ "; @@ -111,12 +244,28 @@ module specification, the '.js' suffix is optional. code = head + code + tail; _loadedModules[canonizedFilename] = moduleInfo; - moduleInfo.main = __engine.eval(code); - moduleInfo.main(moduleInfo.exports, - moduleInfo, - _requireClosure(file.parentFile), - canonizedFilename, - "" + parentFile?parentFile.canonicalPath:""); + var compiledWrapper = null; + try { + compiledWrapper = evaluator.eval(code); + }catch (e){ + logger.severe("Error:" + e + " while evaluating module " + canonizedFilename); + throw e; + } + var __dirname = file.parentFile.canonicalPath; + try { + compiledWrapper.apply(moduleInfo.exports, + [moduleInfo.exports, + moduleInfo, + _requireClosure(file.parentFile), + canonizedFilename, + "" + __dirname]); + } catch (e){ + logger.severe("Error:" + e + " while executing module " + canonizedFilename); + throw e; + } + if (verbose) + logger.info("loaded module " + canonizedFilename); + moduleInfo.loaded = true; return moduleInfo; }; @@ -127,5 +276,5 @@ module specification, the '.js' suffix is optional. return module.exports; }; }; - return _requireClosure(new java.io.File("./")); + return _requireClosure(new java.io.File(rootDir)); }) From aa93491a6cccad61275540a354631f904d282980 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 24 Dec 2013 00:17:07 +0000 Subject: [PATCH 031/456] reorg --- src/main/javascript/lib/scriptcraft.js | 257 ++++++------------------- 1 file changed, 55 insertions(+), 202 deletions(-) diff --git a/src/main/javascript/lib/scriptcraft.js b/src/main/javascript/lib/scriptcraft.js index b874a44..c270f14 100644 --- a/src/main/javascript/lib/scriptcraft.js +++ b/src/main/javascript/lib/scriptcraft.js @@ -49,7 +49,12 @@ As of February 10 2013, the js-plugins directory has the following sub-directori ## Core Module This module defines commonly used functions by all plugins... - + + * echo (message) - Displays a message on the screen. + For example: `/js echo('Hello World')` will print Hello World on the in-game chat window. + 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')` + * load (filename,warnOnFileNotFound) - loads and evaluates a javascript file, returning the evaluated object. * save (object, filename) - saves an object to a file. @@ -140,7 +145,7 @@ 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 -managed by Scriptcraft is `state` - this special member will be +managed by Scriptcraft is `store` - this special member will be automatically saved at shutdown and loaded at startup by ScriptCraft. This makes it easier to write plugins which need to persist data. @@ -225,7 +230,6 @@ There are a couple of special javascript variables available in ScriptCraft... * self - the current player. (Note - this value should not be used in multi-threaded scripts - it's not thread-safe) ***/ -var verbose = verbose || false; /* wph 20130124 - make self, plugin and server public - these are far more useful now that tab-complete works. */ @@ -239,13 +243,14 @@ var server = org.bukkit.Bukkit.server; // if (typeof load == "function") return ; + var File = java.io.File; var _canonize = function(file){ return "" + file.getCanonicalPath().replaceAll("\\\\","/"); }; var _originalScript = __script; - var parentFileObj = new java.io.File(__script).getParentFile(); + var parentFileObj = new File(__script).getParentFile(); var jsPluginsRootDir = parentFileObj.getParentFile(); var jsPluginsRootDirName = _canonize(jsPluginsRootDir); @@ -257,9 +262,15 @@ var server = org.bukkit.Bukkit.server; */ var _load = function(filename,warnOnFileNotFound) { - var result = null; + var FileReader = java.io.FileReader + ,BufferedReader = java.io.BufferedReader + ,result = null + ,file = filename + ,r = undefined; - var file = new java.io.File(filename); + if (!(filename instanceof File)) + file = new File(filename); + var canonizedFilename = _canonize(file); // // wph 20130123 don't load the same file more than once. @@ -267,27 +278,24 @@ var server = org.bukkit.Bukkit.server; if (_loaded[canonizedFilename]) return _loaded[canonizedFilename]; - if (verbose) - print("loading " + canonizedFilename); - if (file.exists()) { var parent = file.getParentFile(); - var reader = new java.io.FileReader(file); - var br = new java.io.BufferedReader(reader); + var reader = new FileReader(file); + var br = new BufferedReader(reader); __engine.put("__script",canonizedFilename); __engine.put("__folder",(parent?_canonize(parent):"")+"/"); var code = ""; try{ if (file.getCanonicalPath().endsWith(".coffee")) { - var r = undefined; while ((r = br.readLine()) !== null) code += "\"" + r + "\" +\n"; code += "\"\""; var code = "load(__folder + \"../core/_coffeescript.js\"); var ___code = "+code+"; eval(CoffeeScript.compile(___code, {bare: true}))"; } else { - while ((r = br.readLine()) !== null) code += r + "\n"; + while ((r = br.readLine()) !== null) + code += r + "\n"; } - result = __engine.eval(code); + result = __engine.eval("(" + code + ")"); _loaded[canonizedFilename] = result || true; }catch (e){ __plugin.logger.severe("Error evaluating " + canonizedFilename + ", " + e ); @@ -306,168 +314,12 @@ var server = org.bukkit.Bukkit.server; return result; }; /* - recursively walk the given directory and return a list of all .js files + now that load is defined, use it to load a global config object */ - var _listSourceFiles = function(store,dir) - { - if (typeof dir == "undefined"){ - dir = new java.io.File(_originalScript).getParentFile().getParentFile(); - } - var files = dir.listFiles(); - for (var i = 0;i < files.length; i++){ - var file = files[i]; - if (file.isDirectory()){ - _listSourceFiles(store,file); - }else{ - if ((file.getCanonicalPath().endsWith(".js") || file.getCanonicalPath().endsWith(".coffee")) && - !(file.getName().startsWith("_")) && - file.exists()) - { - store.push(file); - } - } - } - }; - /* - sort so that .js files with same name as parent directory appear before - other files in the same directory - */ - var sortByModule = function(a,b){ - a = _canonize(a); - b = _canonize(b); - var aparts = (""+a).split(/\//); - var bparts = (""+b).split(/\//); - //var adir = aparts[aparts.length-2]; - var adir = aparts.slice(0,aparts.length-1).join("/"); - var afile = aparts[aparts.length-1]; - //var bdir = bparts[bparts.length-2]; - var bdir = bparts.slice(0,bparts.length-1).join("/"); - var bfile = bparts[bparts.length-1]; - - if(adirbdir) return 1; - - afile = afile.match(/[a-zA-Z0-9\-_]+/)[0]; - - if (adir.match(new RegExp(afile + "$"))) - return -1; - else - return 1; - }; - /* - Reload all of the .js files in the given directory - */ - var _reload = function(pluginDir) - { - _loaded = []; - var sourceFiles = []; - _listSourceFiles(sourceFiles,pluginDir); - - sourceFiles.sort(sortByModule); - - // - // script files whose name begins with _ (underscore) - // will not be loaded automatically at startup. - // These files are assumed to be dependencies/private to plugins - // - // E.g. If you have a plugin called myMiniGame.js in the myMiniGame directory - // and which in addition to myMiniGame.js also includes _myMiniGame_currency.js _myMiniGame_events.js etc. - // then it's assumed that _myMiniGame_currency.js and _myMiniGame_events.js will be loaded - // as dependencies by myMiniGame.js and do not need to be loaded via js reload - // - var len = sourceFiles.length; - for (var i = 0;i < len; i++){ - load(_canonize(sourceFiles[i]),true); - } - }; - - /* - Save a javascript object to a file (saves using JSON notation) - */ - var _save = function(object, filename){ - var objectToStr = null; - try{ - objectToStr = JSON.stringify(object); - }catch(e){ - print("ERROR: " + e.getMessage() + " while saving " + filename); - return; - } - var f = new java.io.File(filename); - var out = new java.io.PrintWriter(new java.io.FileWriter(f)); - out.println("__data = " + objectToStr); - out.close(); - }; - /* - plugin management - */ - var _plugins = {}; - var _plugin = function(/* String */ moduleName, /* Object */ moduleObject, isPersistent) - { - // - // don't load plugin more than once - // - if (typeof _plugins[moduleName] != "undefined") - return _plugins[moduleName].module; - - var pluginData = {persistent: isPersistent, module: moduleObject}; - moduleObject.store = moduleObject.store || {}; - _plugins[moduleName] = pluginData; - - if (isPersistent) - moduleObject.store = load(jsPluginsRootDirName + "/" + moduleName + "-store.txt") || {}; - - global[moduleName] = moduleObject; - return moduleObject; - }; - /* - allow for deferred execution (once all modules have loaded) - */ - var _deferred = []; - var _ready = function( func ){ - _deferred.push(func); - }; - var _cmdInterceptors = []; - /* - command management - allow for non-ops to execute approved javascript code. - */ - var _commands = {}; - var _command = function(name,func,options,intercepts) - { - if (typeof name == "undefined"){ - // it's an invocation from the Java Plugin! - if (__cmdArgs.length === 0) - throw new Error("Usage: jsp command-name command-parameters"); - var name = __cmdArgs[0]; - var cmd = _commands[name]; - if (typeof cmd === "undefined"){ - // it's not a global command - pass it on to interceptors - var intercepted = false; - for (var i = 0;i < _cmdInterceptors.length;i++){ - if (_cmdInterceptors[i](__cmdArgs)) - intercepted = true; - } - if (!intercepted) - self.sendMessage("Command '" + name + "' is not recognised"); - }else{ - func = cmd.callback; - var params = []; - for (var i =1; i < __cmdArgs.length;i++){ - params.push("" + __cmdArgs[i]); - } - return func(params); - } - }else{ - if (typeof options == "undefined") - options = []; - _commands[name] = {callback: func, options: options}; - if (intercepts) - _cmdInterceptors.push(func); - return func; - } - }; - var _rmCommand = function(name){ - delete _commands[name]; - }; + var config = _load(new File(jsPluginsRootDir, "data/global-config.json" )); + if (!config) + config = {verbose: false}; + global.config = config; /* Tab Completion of the /js and /jsp commands */ @@ -568,6 +420,7 @@ var server = org.bukkit.Bukkit.server; } return result; }; + var _commands; /* Tab completion for the /js command */ @@ -775,41 +628,41 @@ See [issue #69][issue69] for more information. __plugin.pluginLoader.enablePlugin(__plugin); }; + var _echo = function (msg) { + __plugin.logger.info( msg ); + if (typeof self == "undefined"){ + return; + } + self.sendMessage(msg); + }; + global.echo = _echo; + global.alert = _echo; global.load = _load; - global.save = _save; - global.plugin = _plugin; - global.ready = _ready; - global.command = _command; + global.logger = __plugin.logger; global._onTabComplete = __onTabCompleteJS; global.addUnloadHandler = _addUnloadHandler; - var fnRequire = load(jsPluginsRootDirName + '/core/_require.js',true); - global.require = fnRequire(__plugin, __engine, verbose); - // - // assumes this was loaded from js-plugins/core/ - // load all of the plugins. - // - _reload(jsPluginsRootDir); + var fnRequire = load(jsPluginsRootDirName + '/lib/require.js',true); + global.require = fnRequire(__plugin.logger, __engine, config.verbose, jsPluginsRootDirName); - // - // all modules have loaded - // - for (var i =0;i < _deferred.length;i++) - _deferred[i](); - - events.on("server.PluginDisableEvent",function(l,e){ - // - // save all plugins which have persistent data - // - for (var moduleName in _plugins){ - var pluginData = _plugins[moduleName]; - if (pluginData.persistent) - save(pluginData.module.store, jsPluginsRootDirName + "/" + moduleName + "-store.txt"); - } - _runUnloadHandlers(); + + var plugins = require('plugin'); + _commands = plugins.commands; + global.plugin = plugins.plugin; + global.command = plugins.command; + global.save = plugins.save; + plugins.autoload(jsPluginsRootDir); + + var events = require('events'); + events.on('server.PluginDisableEvent',function(l,e){ + // save config + plugins.save(global.config, new File(jsPluginsRootDir, "data/global-config.json" )); + + _runUnloadHandlers(); org.bukkit.event.HandlerList["unregisterAll(org.bukkit.plugin.Plugin)"](__plugin); }); + }()); From d0da034fb75c4033e16bc048042f5b0af5cf30cc Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 24 Dec 2013 00:17:33 +0000 Subject: [PATCH 032/456] reorg --- .../javascript/modules/fireworks/fireworks.js | 95 +++-- src/main/javascript/modules/http/request.js | 6 +- src/main/javascript/modules/signs/menu.js | 342 ++++++++---------- .../javascript/modules/utils/string-exts.js | 74 ++-- src/main/javascript/modules/utils/utils.js | 169 ++++++--- 5 files changed, 356 insertions(+), 330 deletions(-) diff --git a/src/main/javascript/modules/fireworks/fireworks.js b/src/main/javascript/modules/fireworks/fireworks.js index a26bd1c..3703649 100644 --- a/src/main/javascript/modules/fireworks/fireworks.js +++ b/src/main/javascript/modules/fireworks/fireworks.js @@ -15,7 +15,7 @@ Example.... ... creates a single firework, while .... - /js firework.fwd(3).times(5) + /js firework().fwd(3).times(5) ... creates 5 fireworks in a row. Fireworks have also been added as a possible option for the `arrow` module. To have a firework launch @@ -26,55 +26,54 @@ where an arrow strikes... To call the fireworks.firework() function directly, you must provide a location. For example... + /js var fireworks = require('fireworks'); /js fireworks.firework(self.location); ![firework example](img/firework.png) ***/ -plugin("fireworks", { - /* - create a firework at the given location - */ - firework: function(location){ - importPackage(org.bukkit.entity); - importPackage(org.bukkit); - - var randInt = function(n){ - return Math.floor(Math.random() * n); - }; - var getColor = function(i){ - var colors = [ - Color.AQUA, Color.BLACK, Color.BLUE, Color.FUCHSIA, Color.GRAY, - Color.GREEN, Color.LIME, Color.MAROON, Color.NAVY, Color.OLIVE, - Color.ORANGE, Color.PURPLE, Color.RED, Color.SILVER, Color.TEAL, - Color.WHITE, Color.YELLOW]; - return colors[i]; - }; - var fw = location.world.spawnEntity(location, EntityType.FIREWORK); - var fwm = fw.getFireworkMeta(); - var fwTypes = [FireworkEffect.Type.BALL, - FireworkEffect.Type.BALL_LARGE, - FireworkEffect.Type.BURST, - FireworkEffect.Type.CREEPER, - FireworkEffect.Type.STAR]; - var type = fwTypes[randInt(5)]; - - var r1i = randInt(17); - var r2i = randInt(17); - var c1 = getColor(r1i); - var c2 = getColor(r2i); - var effectBuilder = FireworkEffect.builder() - .flicker(Math.round(Math.random())==0) - .withColor(c1) - .withFade(c2).trail(Math.round(Math.random())==0); - effectBuilder['with'](type); - var effect = effectBuilder.build(); - fwm.addEffect(effect); - fwm.setPower(randInt(2)+1); - fw.setFireworkMeta(fwm); - } -}); -Drone.extend('firework',function() -{ - fireworks.firework(this.getLocation()); -}); + +/* + create a firework at the given location +*/ +var firework = function(location){ + importPackage(org.bukkit.entity); + importPackage(org.bukkit); + + var randInt = function(n){ + return Math.floor(Math.random() * n); + }; + var getColor = function(i){ + var colors = [ + Color.AQUA, Color.BLACK, Color.BLUE, Color.FUCHSIA, Color.GRAY, + Color.GREEN, Color.LIME, Color.MAROON, Color.NAVY, Color.OLIVE, + Color.ORANGE, Color.PURPLE, Color.RED, Color.SILVER, Color.TEAL, + Color.WHITE, Color.YELLOW]; + return colors[i]; + }; + var fw = location.world.spawnEntity(location, EntityType.FIREWORK); + var fwm = fw.getFireworkMeta(); + var fwTypes = [FireworkEffect.Type.BALL, + FireworkEffect.Type.BALL_LARGE, + FireworkEffect.Type.BURST, + FireworkEffect.Type.CREEPER, + FireworkEffect.Type.STAR]; + var type = fwTypes[randInt(5)]; + + var r1i = randInt(17); + var r2i = randInt(17); + var c1 = getColor(r1i); + var c2 = getColor(r2i); + var effectBuilder = FireworkEffect.builder() + .flicker(Math.round(Math.random())==0) + .withColor(c1) + .withFade(c2).trail(Math.round(Math.random())==0); + effectBuilder['with'](type); + var effect = effectBuilder.build(); + fwm.addEffect(effect); + fwm.setPower(randInt(2)+1); + fw.setFireworkMeta(fwm); +}; + +exports.firework = firework; + diff --git a/src/main/javascript/modules/http/request.js b/src/main/javascript/modules/http/request.js index 520a9df..eb47677 100644 --- a/src/main/javascript/modules/http/request.js +++ b/src/main/javascript/modules/http/request.js @@ -25,12 +25,14 @@ Example The following example illustrates how to use http.request to make a request to a JSON web service and evaluate its response... var jsResponse; + var http = require('./http/request'); http.request("http://scriptcraftjs.org/sample.json",function(responseCode, responseBody){ jsResponse = eval("(" + responseBody + ")"); }); ... The following example illustrates a more complex use-case POSTing parameters to a CGI process on a server... + var http = require('./http/request'); http.request({ url: "http://pixenate.com/pixenate/pxn8.pl", method: "POST", params: {script: "[]"} @@ -39,9 +41,7 @@ The following example illustrates how to use http.request to make a request to a }); ***/ -var http = http ? http : {}; - -http.request = function( request, callback) +exports.request = function( request, callback) { var paramsToString = function(params){ var result = ""; diff --git a/src/main/javascript/modules/signs/menu.js b/src/main/javascript/modules/signs/menu.js index eb33eb3..8f9bbce 100644 --- a/src/main/javascript/modules/signs/menu.js +++ b/src/main/javascript/modules/signs/menu.js @@ -1,218 +1,186 @@ +var _utils = require('utils'); +var stringExt = require('utils/string-exts'); +var events = require('events'); /* Define the signs module - signs are persistent - (that is - a menu sign will still be a menu after th + (that is - a menu sign will still be a menu after the server has shut down and started up) plugins now have persistent state - Yay! */ -var signs = signs || plugin("signs", { +var signs = plugin("signs", { /* construct an interactive menu which can then be attached to a Sign. - */ + */ menu: function( /* String */ label, /* Array */ options, /* Function */ onInteract, /* Number */ defaultSelection ){} - /* - more to come - clocks - */ },true); + +module.exports = signs; + /* - private implementation + redraw a menu sign */ -(function(){ +var _redrawMenuSign = function(p_sign,p_selectedIndex,p_displayOptions) +{ + var optLen = p_displayOptions.length; + // the offset is where the menu window begins + var offset = Math.max(0, Math.min(optLen-3, Math.floor(p_selectedIndex/3) * 3)); + for (var i = 0;i < 3; i++){ + var text = ""; + if (offset+i < optLen) + text = p_displayOptions[offset+i]; + if (offset+i == p_selectedIndex) + text = ("" + text).replace(/^ /,">"); + p_sign.setLine(i+1,text); + } + p_sign.update(true); +}; +var _updaters = {}; +var _store = {}; +signs.store = _store; +/* + construct an interactive menu to be subsequently attached to + one or more Signs. +*/ +signs.menu = function( + /* String */ label, + /* Array */ options, + /* Function */ callback, + /* Number */ selectedIndex) +{ + + if (typeof selectedIndex == "undefined") + selectedIndex = 0; + + // + // variables common to all instances of this menu can go here + // + var labelPadding = "---------------"; + var optionPadding = " "; + + var paddedLabel = (labelPadding+label+labelPadding).substr(((label.length+30)/2)-7,15); + var optLen = options.length; + var displayOptions = []; + for (var i =0;i < options.length;i++){ + displayOptions[i] = (" " + options[i] + optionPadding).substring(0,15); + } /* - redraw a menu sign + this function is returned by signs.menu and when it is invoked it will + attach menu behaviour to an existing sign in the world. + signs.menu is for use by Plugin Authors. + The function returned by signs.menu is for use by admins/ops. */ - var _redrawMenuSign = function(p_sign,p_selectedIndex,p_displayOptions) + var convertToMenuSign = function(/* Sign */ sign, save) { - var optLen = p_displayOptions.length; - // the offset is where the menu window begins - var offset = Math.max(0, Math.min(optLen-3, Math.floor(p_selectedIndex/3) * 3)); - for (var i = 0;i < 3; i++){ - var text = ""; - if (offset+i < optLen) - text = p_displayOptions[offset+i]; - if (offset+i == p_selectedIndex) - text = ("" + text).replace(/^ /,">"); - p_sign.setLine(i+1,text); - } - p_sign.update(true); - }; - signs._updaters = {}; + if (typeof save == "undefined") + save = true; - /* - construct an interactive menu to be subsequently attached to - one or more Signs. - */ - signs.menu = function( - /* String */ label, - /* Array */ options, - /* Function */ callback, - /* Number */ selectedIndex) - { - - if (typeof selectedIndex == "undefined") - selectedIndex = 0; - - // - // variables common to all instances of this menu can go here - // - var labelPadding = "---------------"; - var optionPadding = " "; - - var paddedLabel = (labelPadding+label+labelPadding).substr(((label.length+30)/2)-7,15); - var optLen = options.length; - var displayOptions = []; - for (var i =0;i < options.length;i++){ - displayOptions[i] = (" " + options[i] + optionPadding).substring(0,15); - } - - var theSigns = this; - - /* - this function is returned by signs.menu and when it is invoked it will - attach menu behaviour to an existing sign in the world. - signs.menu is for use by Plugin Authors. - The function returned by signs.menu is for use by admins/ops. - */ - var convertToMenuSign = function(/* Sign */ sign, save) - { - if (typeof save == "undefined") - save = true; - - if (typeof sign == "undefined"){ - var mouseLoc = getMousePos(); - if (mouseLoc){ - sign = mouseLoc.block.state; - }else{ - throw new Exception("You must provide a sign!"); - } + if (typeof sign == "undefined"){ + var mouseLoc = _utils.getMousePos(); + if (mouseLoc){ + sign = mouseLoc.block.state; + }else{ + throw new Exception("You must provide a sign!"); } - // - // per-sign variables go here - // - var cSelectedIndex = selectedIndex; - sign.setLine(0,paddedLabel.bold()); - var _updateSign = function(p_player,p_sign) { - cSelectedIndex = (cSelectedIndex+1) % optLen; - _redrawMenuSign(p_sign,cSelectedIndex,displayOptions); - var signSelectionEvent = {player: p_player, - sign: p_sign, - text: options[cSelectedIndex], - number:cSelectedIndex}; - - callback(signSelectionEvent); - }; - - /* - get a unique ID for this particular sign instance - */ - var signLoc = sign.block.location; - var menuSignSaveData = [""+signLoc.world.name, signLoc.x,signLoc.y,signLoc.z]; - var menuSignUID = JSON.stringify(menuSignSaveData); - /* - keep a reference to the update function for use by the event handler - */ - theSigns._updaters[menuSignUID] = _updateSign; - - // initialize the sign - _redrawMenuSign(sign,cSelectedIndex,displayOptions); - - /* - whenever a sign is placed somewhere in the world - (which is what this function does) - save its location for loading and initialization - when the server starts up again. - */ - if (save){ - if (typeof theSigns.store.menus == "undefined") - theSigns.store.menus = {}; - var signLocations = theSigns.store.menus[label]; - if (typeof signLocations == "undefined") - signLocations = theSigns.store.menus[label] = []; - signLocations.push(menuSignSaveData); - } - return sign; + } + // + // per-sign variables go here + // + var cSelectedIndex = selectedIndex; + sign.setLine(0,paddedLabel.bold()); + var _updateSign = function(p_player,p_sign) { + cSelectedIndex = (cSelectedIndex+1) % optLen; + _redrawMenuSign(p_sign,cSelectedIndex,displayOptions); + var signSelectionEvent = {player: p_player, + sign: p_sign, + text: options[cSelectedIndex], + number:cSelectedIndex}; + + callback(signSelectionEvent); }; + /* + get a unique ID for this particular sign instance + */ + var signLoc = sign.block.location; + var menuSignSaveData = [""+signLoc.world.name, signLoc.x,signLoc.y,signLoc.z]; + var menuSignUID = JSON.stringify(menuSignSaveData); /* - a new sign definition - need to store (in-memory only) - its behaviour and bring back to life other signs of the - same type in the world. Look for other static signs in the - world with this same label and make dynamic again. - */ + keep a reference to the update function for use by the event handler + */ + _updaters[menuSignUID] = _updateSign; - if (this.store.menus && this.store.menus[label]) - { - var signsOfSameLabel = this.store.menus[label]; - var defragged = []; - var len = signsOfSameLabel.length; - for (var i = 0; i < len ; i++) - { - var loc = signsOfSameLabel[i]; - var world = org.bukkit.Bukkit.getWorld(loc[0]); - if (!world) - continue; - var block = world.getBlockAt(loc[1],loc[2],loc[3]); - if (block.state instanceof org.bukkit.block.Sign){ - convertToMenuSign(block.state,false); - defragged.push(loc); - } - } - /* - remove data for signs which no longer exist. - */ - if (defragged.length != len){ - this.store.menus[label] = defragged; - } + // initialize the sign + _redrawMenuSign(sign,cSelectedIndex,displayOptions); + + /* + whenever a sign is placed somewhere in the world + (which is what this function does) + save its location for loading and initialization + when the server starts up again. + */ + if (save){ + if (typeof _store.menus == "undefined") + _store.menus = {}; + var signLocations = _store.menus[label]; + if (typeof signLocations == "undefined") + signLocations = _store.menus[label] = []; + signLocations.push(menuSignSaveData); } - return convertToMenuSign; + return sign; }; /* - All dependecies ( 'events' module ) have loaded - */ - ready(function(){ - // - // Usage: - // In game, create a sign , target it and type /js signs.testMenu() - // - signs.testMenu = signs.menu( - "Dinner", - ["Lamb","Pork","Chicken","Duck","Beef"], - function(event){ - event.player.sendMessage("You chose " + event.text); - }); - // - // This is an example sign that displays a menu of times of day - // interacting with the sign will change the time of day accordingly. - // - // In game, create a sign , target it and type /js signs.timeOfDay() - // - signs.timeOfDay = signs.menu( - "Time", - ["Dawn","Midday","Dusk","Midnight"], - function(event){ - event.player.location.world.setTime( event.number * 6000 ); - }); + a new sign definition - need to store (in-memory only) + its behaviour and bring back to life other signs of the + same type in the world. Look for other static signs in the + world with this same label and make dynamic again. + */ - // - // update it every time player interacts with it. - // - events.on("player.PlayerInteractEvent",function(listener, event) { - /* - look up our list of menu signs. If there's a matching location and there's - a sign, then update it. - */ + if (_store.menus && _store.menus[label]) + { + var signsOfSameLabel = _store.menus[label]; + var defragged = []; + var len = signsOfSameLabel.length; + for (var i = 0; i < len ; i++) + { + var loc = signsOfSameLabel[i]; + var world = org.bukkit.Bukkit.getWorld(loc[0]); + if (!world) + continue; + var block = world.getBlockAt(loc[1],loc[2],loc[3]); + if (block.state instanceof org.bukkit.block.Sign){ + convertToMenuSign(block.state,false); + defragged.push(loc); + } + } + /* + remove data for signs which no longer exist. + */ + if (defragged.length != len){ + _store.menus[label] = defragged; + } + } + return convertToMenuSign; +}; - if (! event.clickedBlock.state instanceof org.bukkit.block.Sign) - return; - var evtLocStr = utils.locationToString(event.clickedBlock.location); - var signUpdater = signs._updaters[evtLocStr] - if (signUpdater) - signUpdater(event.player, event.clickedBlock.state); - }); - }); -}()); +// +// update it every time player interacts with it. +// +events.on("player.PlayerInteractEvent",function(listener, event) { + /* + look up our list of menu signs. If there's a matching location and there's + a sign, then update it. + */ + + if (! event.clickedBlock.state instanceof org.bukkit.block.Sign) + return; + var evtLocStr = _utils.locationToString(event.clickedBlock.location); + var signUpdater = _updaters[evtLocStr] + if (signUpdater) + signUpdater(event.player, event.clickedBlock.state); +}); diff --git a/src/main/javascript/modules/utils/string-exts.js b/src/main/javascript/modules/utils/string-exts.js index 4d9b947..f10810e 100644 --- a/src/main/javascript/modules/utils/string-exts.js +++ b/src/main/javascript/modules/utils/string-exts.js @@ -41,41 +41,39 @@ Example

Hello World

***/ -(function(){ - var c = org.bukkit.ChatColor; - var formattingCodes = { - aqua: c.AQUA, - black: c.BLACK, - blue: c.BLUE, - bold: c.BOLD, - brightgreen: c.GREEN, - darkaqua: c.DARK_AQUA, - darkblue: c.DARK_BLUE, - darkgray: c.DARK_GRAY, - darkgreen: c.DARK_GREEN, - purple: c.LIGHT_PURPLE, - darkpurple: c.DARK_PURPLE, - darkred: c.DARK_RED, - gold: c.GOLD, - gray: c.GRAY, - green: c.GREEN, - italic: c.ITALIC, - lightpurple: c.LIGHT_PURPLE, - indigo: c.BLUE, - green: c.GREEN, - red: c.RED, - pink: c.LIGHT_PURPLE, - yellow: c.YELLOW, - white: c.WHITE, - strike: c.STRIKETHROUGH, - random: c.MAGIC, - magic: c.MAGIC, - underline: c.UNDERLINE, - reset: c.RESET - }; - for (var method in formattingCodes){ - String.prototype[method] = function(c){ - return function(){return c+this;}; - }(formattingCodes[method]); - } -}()); +var c = org.bukkit.ChatColor; +var formattingCodes = { + aqua: c.AQUA, + black: c.BLACK, + blue: c.BLUE, + bold: c.BOLD, + brightgreen: c.GREEN, + darkaqua: c.DARK_AQUA, + darkblue: c.DARK_BLUE, + darkgray: c.DARK_GRAY, + darkgreen: c.DARK_GREEN, + purple: c.LIGHT_PURPLE, + darkpurple: c.DARK_PURPLE, + darkred: c.DARK_RED, + gold: c.GOLD, + gray: c.GRAY, + green: c.GREEN, + italic: c.ITALIC, + lightpurple: c.LIGHT_PURPLE, + indigo: c.BLUE, + green: c.GREEN, + red: c.RED, + pink: c.LIGHT_PURPLE, + yellow: c.YELLOW, + white: c.WHITE, + strike: c.STRIKETHROUGH, + random: c.MAGIC, + magic: c.MAGIC, + underline: c.UNDERLINE, + reset: c.RESET +}; +for (var method in formattingCodes){ + String.prototype[method] = function(c){ + return function(){return c+this;}; + }(formattingCodes[method]); +} diff --git a/src/main/javascript/modules/utils/utils.js b/src/main/javascript/modules/utils/utils.js index 97a56ae..8ef848c 100644 --- a/src/main/javascript/modules/utils/utils.js +++ b/src/main/javascript/modules/utils/utils.js @@ -8,21 +8,55 @@ Miscellaneous utility functions and classes to help with programming. * getPlayerObject(playerName) - returns the Player object for a named player or `self` if no name is provided. -***/ -var utils = utils ? utils : { - locationToString: function(location){ - return JSON.stringify([""+location.world.name,location.x, location.y, location.z]); - }, + * getPlayerPos(playerName) - returns the player's x,y,z and yaw (direction) for a named player + or player or `self` if no parameter is provided. + + * getMousePos(playerName) - returns the x,y,z of the current block being targeted by the named player + or player or `self` if no paramter is provided. - getPlayerObject: function(playerName){ - if (typeof playerName == "undefined") +***/ +var _getPlayerObject = function ( playerName ) { + if (typeof playerName == "undefined"){ + if (typeof self == "undefined"){ + return null; + } else { return self; + } + } else { if (typeof playerName == "string") return org.bukkit.Bukkit.getPlayer(playerName); - return player; - }, + else + return playerName; // assumes it's a player object + } +}; + +exports.locationToString = function(location){ + return JSON.stringify([""+location.world.name,location.x, location.y, location.z]); +}; + +exports.getPlayerObject = _getPlayerObject; + +exports.getPlayerPos = function( player ) { + player = _getPlayerObject(player); + return player.location; +}; + +exports.getMousePos = function (player) { + + player = _getPlayerObject(player); + if (!player) + return null; + // player might be CONSOLE or a CommandBlock + if (!player.getTargetBlock) + return null; + var targetedBlock = player.getTargetBlock(null,5); + if (targetedBlock == null || targetedBlock.isEmpty()){ + return null; + } + return targetedBlock.location; +}; /************************************************************************ -utils.foreach() function +foreach() function ======================== The utils.foreach() function is a utility function for iterating over an array of objects (or a java.util.Collection of objects) and processing each object in turn. Where @@ -70,6 +104,7 @@ Example ------- The following example illustrates how to use foreach for immediate processing of an array... + var utils = require('./utils/_utils'); var players = ["moe", "larry", "curly"]; utils.foreach (players, function(item){ server.getPlayer(item).sendMessage("Hi " + item); @@ -89,6 +124,7 @@ without hogging CPU usage... // build a structure 200 wide x 200 tall x 200 long // (That's 8 Million Blocks - enough to tax any machine!) + var utils = require('./utils/_utils'); var a = []; a.length = 200; @@ -106,21 +142,22 @@ without hogging CPU usage... utils.foreach (a, processItem, null, 10, onDone); ***/ - foreach: function(array, callback, object, delay, onCompletion) { - if (array instanceof java.util.Collection) - array = array.toArray(); - var i = 0; - var len = array.length; - if (delay){ - var next = function(){ callback(array[i],i,object,array); i++;}; - var hasNext = function(){return i < len;}; - utils.nicely(next,hasNext,onCompletion,delay); - }else{ - for (;i < len; i++){ - callback(array[i],i,object,array); - } +var _foreach = function(array, callback, object, delay, onCompletion) { + if (array instanceof java.util.Collection) + array = array.toArray(); + var i = 0; + var len = array.length; + if (delay){ + var next = function(){ callback(array[i],i,object,array); i++;}; + var hasNext = function(){return i < len;}; + utils.nicely(next,hasNext,onCompletion,delay); + }else{ + for (;i < len; i++){ + callback(array[i],i,object,array); } - }, + } +}; +exports.foreach = _foreach; /************************************************************************ utils.nicely() function ======================= @@ -147,17 +184,17 @@ Example See the source code to utils.foreach for an example of how utils.nicely is used. ***/ - nicely: function(next, hasNext, onDone, delay){ - if (hasNext()){ - next(); - server.scheduler.runTaskLater(__plugin,function(){ - utils.nicely(next,hasNext,onDone,delay); - },delay); - }else{ - if (onDone) - onDone(); - } - }, +exports.nicely = function(next, hasNext, onDone, delay){ + if (hasNext()){ + next(); + server.scheduler.runTaskLater(__plugin,function(){ + utils.nicely(next,hasNext,onDone,delay); + },delay); + }else{ + if (onDone) + onDone(); + } +}; /************************************************************************ utils.at() function =================== @@ -177,6 +214,8 @@ Example To warn players when night is approaching... + var utils = require('./utils/_utils'); + utils.at( "19:00", function() { utils.foreach( server.onlinePlayers, function(player){ @@ -186,24 +225,46 @@ To warn players when night is approaching... }, self.world); ***/ - at: function(time24hr, callback, world){ - var forever = function(){ return true;}; - var timeParts = time24hr.split(":"); - var hrs = ((timeParts[0] * 1000) + 18000) % 24000; - var mins; - if (timeParts.length > 1) - mins = (timeParts[1] / 60) * 1000; - - var timeMc = hrs + mins; - if (typeof world == "undefined"){ - world = server.worlds.get(0); +exports.at = function(time24hr, callback, world) { + var forever = function(){ return true;}; + var timeParts = time24hr.split(":"); + var hrs = ((timeParts[0] * 1000) + 18000) % 24000; + var mins; + if (timeParts.length > 1) + mins = (timeParts[1] / 60) * 1000; + + var timeMc = hrs + mins; + if (typeof world == "undefined"){ + world = server.worlds.get(0); + } + utils.nicely(function(){ + var time = world.getTime(); + var diff = timeMc - time; + if (diff > 0 && diff < 100){ + callback(); } - utils.nicely(function(){ - var time = world.getTime(); - var diff = timeMc - time; - if (diff > 0 && diff < 100){ - callback(); - } - },forever, null, 100); - }, + },forever, null, 100); }; + +exports.find = function( dir , filter){ + var result = []; + var recurse = function(dir, store){ + var files, dirfile = new java.io.File(dir); + + if (typeof filter == "undefined") + files = dirfile.list(); + else + files = dirfile.list(filter); + + _foreach(files, function (file){ + file = new java.io.File(dir + '/' + file); + if (file.isDirectory()){ + recurse(file.canonicalPath, store); + }else{ + store.push(file.canonicalPath); + } + }); + } + recurse(dir,result); + return result; +} From b2761d29e3b923bc5c397044bdf9a9643ac914a5 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 24 Dec 2013 00:18:43 +0000 Subject: [PATCH 033/456] reorg --- src/main/javascript/plugins/alias/alias.js | 21 +- src/main/javascript/plugins/arrows.js | 191 ++++++------ src/main/javascript/plugins/chat/color.js | 87 +++--- .../javascript/plugins/classroom/classroom.js | 39 +-- src/main/javascript/plugins/drone/blocks.js | 2 +- .../javascript/plugins/drone/blocktype.js | 5 +- .../plugins/drone/contrib/castle.js | 4 +- .../plugins/drone/contrib/chessboard.js | 5 +- .../plugins/drone/contrib/cottage.js | 4 +- .../plugins/drone/contrib/dancefloor.js | 4 +- .../javascript/plugins/drone/contrib/fort.js | 4 +- .../javascript/plugins/drone/contrib/logo.js | 4 +- .../plugins/drone/contrib/rainbow.js | 6 +- .../plugins/drone/contrib/rboxcall.js | 4 +- .../plugins/drone/contrib/redstonewire.js | 6 +- .../drone/contrib/skyscraper-example.js | 5 +- .../plugins/drone/contrib/spiral_stairs.js | 5 +- .../plugins/drone/contrib/streamer.js | 3 +- .../plugins/drone/contrib/temple.js | 3 +- .../javascript/plugins/drone/drone-exts.js | 25 -- src/main/javascript/plugins/drone/drone.js | 6 +- src/main/javascript/plugins/drone/sphere.js | 3 +- src/main/javascript/plugins/drone/test.js | 2 +- src/main/javascript/plugins/homes/homes.js | 262 ++++++++-------- .../plugins/minigames/NumberGuess.js | 26 +- .../plugins/minigames/SnowBallFight.js | 292 +++++++++--------- src/main/javascript/plugins/signs/examples.js | 34 +- 27 files changed, 516 insertions(+), 536 deletions(-) diff --git a/src/main/javascript/plugins/alias/alias.js b/src/main/javascript/plugins/alias/alias.js index a8b2edf..87076f6 100644 --- a/src/main/javascript/plugins/alias/alias.js +++ b/src/main/javascript/plugins/alias/alias.js @@ -1,4 +1,7 @@ -plugin("alias", { + +var _store = {players: {}}; + +var alias = plugin("alias", { help: function(){ return [ "/jsp alias set : Set a shortcut/alias for one or more commands (separated by ';')\n" + @@ -10,30 +13,30 @@ plugin("alias", { ]; }, set: function(player, alias, commands){ - var aliases = this.store.players; + var aliases = _store.players; var name = player.name; if (typeof aliases[name] == "undefined") aliases[name] = {}; aliases[name][alias] = commands; }, remove: function(player, alias){ - var aliases = this.store.players; + var aliases = _store.players; if (aliases[player.name]) delete aliases[player.name][alias]; }, list: function(player){ var result = []; - var aliases = this.store.players[player.name]; + var aliases = _store.players[player.name]; for (var a in aliases) result.push(a + " = " + aliases[a].join(";")); return result; - } + }, + store: _store },true); -if (typeof alias.store.players == "undefined") - alias.store.players = {}; +exports.alias = alias; -command("alias",function(params){ +command("alias", function ( params ) { /* this function also intercepts command options for /jsp */ @@ -59,7 +62,7 @@ command("alias",function(params){ if (params.length == 0) return self.sendMessage(alias.help()); - var playerHasAliases = alias.store.players[self.name]; + var playerHasAliases = _store.players[self.name]; if (!playerHasAliases) return false; // is it an alias? diff --git a/src/main/javascript/plugins/arrows.js b/src/main/javascript/plugins/arrows.js index b00cdd0..534fa04 100644 --- a/src/main/javascript/plugins/arrows.js +++ b/src/main/javascript/plugins/arrows.js @@ -1,25 +1,33 @@ -/* +/************************************************************************* - The arrows mod adds fancy arrows to the game. +## The arrows mod adds fancy arrows to the game. - Usage: +### Usage: - /js arrows.sign() turns a targeted sign into a Arrows menu - /js arrows.normal() sets arrow type to normal. - /js arrows.explosive() - makes arrows explode. - /js arrows.teleport() - makes player teleport to where arrow has landed. - /js arrows.flourish() - makes a tree grow where the arrow lands. - /js arrows.lightning() - lightning strikes where the arrow lands. - /js arrows.firework() - A firework launches where the the arrow lands. + /js var arrows = require('./arrows/arrows') + + * `/js arrows.sign()` turns a targeted sign into a Arrows menu + * `/js arrows.normal()` sets arrow type to normal. + * `/js arrows.explosive()` - makes arrows explode. + * `/js arrows.teleport()` - makes player teleport to where arrow has landed. + * `/js arrows.flourish()` - makes a tree grow where the arrow lands. + * `/js arrows.lightning()` - lightning strikes where the arrow lands. + * `/js arrows.firework()` - A firework launches where the the arrow lands. All of the above functions can take an optional player object or name as a parameter. E.g. - /js arrows.explosive('player23') makes player23's arrows explosive. + `/js arrows.explosive('player23')` makes player23's arrows explosive. -*/ +***/ -var arrows = arrows || plugin("arrows",{ +var signs = require('signs'); +var events = require('events'); +var fireworks = require('fireworks'); + +var _store = {players: {}}; + +var arrows = plugin("arrows",{ /* turn a sign into a menu of arrow choices */ @@ -48,91 +56,88 @@ var arrows = arrows || plugin("arrows",{ /* launch a firework where the arrow lands */ - explosiveYield: 2.5 + explosiveYield: 2.5, + + store: _store },true); -/* - initialize data -*/ -arrows.store.players = arrows.store.players || {}; -/* - private implementation of normal, explosive, teleport, flourish and lightning functions -*/ -(function(){ - // - // setup functions for the arrow types - // - var _types = {normal: 0, explosive: 1, teleport: 2, flourish: 3, lightning: 4, firework: 5}; - for (var type in _types) - { - arrows[type] = (function(n){ - return function(player){ - if (typeof player == "undefined") - player = self; - var playerName = null; - if (typeof player == "string") - playerName = player; - else - playerName = player.name; - arrows.store.players[playerName] = n; - }; - })(_types[type]); - } -}()); -/* - Arrows depends on 2 other modules: 'signs' and 'events' so the following code - can't execute until all modules have loaded (ready). -*/ -ready(function() +exports.arrows = arrows; + +// +// setup functions for the arrow types +// +var _types = {normal: 0, explosive: 1, teleport: 2, flourish: 3, lightning: 4, firework: 5}; +for (var type in _types) { - /* - called when the player chooses an arrow option from a menu sign - */ - var _onMenuChoice = function(event){ - arrows.store.players[event.player.name] = event.number; - }; - arrows.sign = signs.menu("Arrow", - ["Normal","Explosive","Teleport","Flourish","Lightning","Firework"], - _onMenuChoice ); + arrows[type] = (function(n){ + return function(player){ + if (typeof player == "undefined") + player = self; + var playerName = null; + if (typeof player == "string") + playerName = player; + else + playerName = player.name; + arrows.store.players[playerName] = n; + }; + })(_types[type]); +} - /* - event handler called when a projectile hits something - */ - var _onArrowHit = function(listener,event) +/* + called when the player chooses an arrow option from a menu sign +*/ +var _onMenuChoice = function(event){ + arrows.store.players[event.player.name] = event.number; +}; +arrows.sign = signs.menu("Arrow", + ["Normal","Explosive","Teleport","Flourish","Lightning","Firework"], + _onMenuChoice ); + +/* + event handler called when a projectile hits something +*/ +var _onArrowHit = function(listener,event) +{ + var projectile = event.entity; + var world = projectile.world; + var shooter = projectile.shooter; + var fireworkCount = 5; + if (projectile instanceof org.bukkit.entity.Arrow && + shooter instanceof org.bukkit.entity.Player) { - var projectile = event.entity; - var world = projectile.world; - var shooter = projectile.shooter; - if (projectile instanceof org.bukkit.entity.Arrow && - shooter instanceof org.bukkit.entity.Player) - { - var arrowType = arrows.store.players[shooter.name]; - switch (arrowType){ - case 1: - projectile.remove(); - world.createExplosion(projectile.location,arrows.explosiveYield); - break; - case 2: - projectile.remove(); - var teleportCause =org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; - shooter.teleport(projectile.location, - teleportCause.PLUGIN); - break; - case 3: - projectile.remove(); - world.generateTree(projectile.location, org.bukkit.TreeType.BIG_TREE); - break; - case 4: - projectile.remove(); - world.strikeLightning(projectile.location); - break; - case 5: - projectile.remove(); + var arrowType = arrows.store.players[shooter.name]; + + switch (arrowType){ + case 1: + projectile.remove(); + world.createExplosion(projectile.location,arrows.explosiveYield); + break; + case 2: + projectile.remove(); + var teleportCause =org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; + shooter.teleport(projectile.location, + teleportCause.PLUGIN); + break; + case 3: + projectile.remove(); + world.generateTree(projectile.location, org.bukkit.TreeType.BIG_TREE); + break; + case 4: + projectile.remove(); + world.strikeLightning(projectile.location); + break; + case 5: + projectile.remove(); + var launch = function(){ fireworks.firework(projectile.location); - break; - } + if (--fireworkCount) + setTimeout(launch,2000); + }; + launch(); + break; } - }; - events.on("entity.ProjectileHitEvent",_onArrowHit); -}); + } +}; +events.on('entity.ProjectileHitEvent',_onArrowHit); + diff --git a/src/main/javascript/plugins/chat/color.js b/src/main/javascript/plugins/chat/color.js index 2e28bdc..76b5e3b 100644 --- a/src/main/javascript/plugins/chat/color.js +++ b/src/main/javascript/plugins/chat/color.js @@ -1,53 +1,56 @@ +/* + TODO: Document this module +*/ +var events = require('events'); + +var _store = {players: {}}; /* declare a new javascript plugin for changing chat text color */ -var chat = chat || plugin("chat", { +exports.chat = plugin("chat", { /* set the color of text for a given player */ setColor: function(player, color){ this.store.players[player.name] = color; - } -},true); -/* - initialize the store -*/ -chat.store.players = chat.store.players || {}; + }, -ready(function() -{ - var colors = [ - "black", "blue", "darkgreen", "darkaqua", "darkred", - "purple", "gold", "gray", "darkgray", "indigo", - "brightgreen", "aqua", "red", "pink", "yellow", "white" - ]; - var colorCodes = {}; - for (var i =0;i < colors.length;i++) { - var hexCode = i.toString(16); - colorCodes[colors[i]] = hexCode; + store: _store + +},true); + +var colors = [ + "black", "blue", "darkgreen", "darkaqua", "darkred", + "purple", "gold", "gray", "darkgray", "indigo", + "brightgreen", "aqua", "red", "pink", "yellow", "white" +]; +var colorCodes = {}; +for (var i =0;i < colors.length;i++) { + var hexCode = i.toString(16); + colorCodes[colors[i]] = hexCode; +} + +events.on("player.AsyncPlayerChatEvent",function(l,e){ + var player = e.player; + var playerChatColor = chat.store.players[player.name]; + if (playerChatColor){ + e.message = "§" + colorCodes[playerChatColor] + e.message; } - - events.on("player.AsyncPlayerChatEvent",function(l,e){ - var player = e.player; - var playerChatColor = chat.store.players[player.name]; - if (playerChatColor){ - e.message = "§" + colorCodes[playerChatColor] + e.message; - } - }); - var listColors = function(params){ - var colorNamesInColor = []; - for (var i = 0;i < colors.length;i++) - colorNamesInColor[i] = "§"+colorCodes[colors[i]] + colors[i]; - self.sendMessage("valid chat colors are " + colorNamesInColor.join(", ")); - }; - command("list_colors", listColors); - command("chat_color",function(params){ - var color = params[0]; - if (colorCodes[color]){ - chat.setColor(self,color); - }else{ - self.sendMessage(color + " is not a valid color"); - listColors(); - } - },colors); }); +var listColors = function(params){ + var colorNamesInColor = []; + for (var i = 0;i < colors.length;i++) + colorNamesInColor[i] = "§"+colorCodes[colors[i]] + colors[i]; + self.sendMessage("valid chat colors are " + colorNamesInColor.join(", ")); +}; +command("list_colors", listColors); +command("chat_color",function(params){ + var color = params[0]; + if (colorCodes[color]){ + chat.setColor(self,color); + }else{ + self.sendMessage(color + " is not a valid color"); + listColors(); + } +},colors); + diff --git a/src/main/javascript/plugins/classroom/classroom.js b/src/main/javascript/plugins/classroom/classroom.js index 57f9fbc..329251d 100644 --- a/src/main/javascript/plugins/classroom/classroom.js +++ b/src/main/javascript/plugins/classroom/classroom.js @@ -1,3 +1,6 @@ +var utils = require('utils'); +var events = require('events'); + /************************************************************************ Classroom Module ================ @@ -41,22 +44,19 @@ Only ops users can run the classroom.allowScripting() function - this is so that don't try to bar themselves and each other from scripting. ***/ -var classroom = { - allowScripting: function(/* boolean: true or false */ canScript){} -}; +var _canScript = false; -ready(function(){ - classroom.allowScripting = function(canScript) - { +exports.classroom = { + allowScripting: function (/* boolean: true or false */ canScript) { /* only operators should be allowed run this function - */ + */ if (!self.isOp()) return; if (canScript){ - utils.foreach( server.onlinePlayers, function (player) { - player.addAttachment(__plugin, "scriptcraft.*", true); - }); + utils.foreach( server.onlinePlayers, function (player) { + player.addAttachment(__plugin, "scriptcraft.*", true); + }); }else{ utils.foreach( server.onlinePlayers, function(player) { utils.foreach(player.getEffectivePermissions(), function(perm) { @@ -67,12 +67,13 @@ ready(function(){ }); }); } - classroom.canScript = canScript; - }; - events.on("player.PlayerLoginEvent", function(listener, event) { - var player = event.player; - if (classroom.canScript){ - player.addAttachment(__plugin, "scriptcraft.*", true); - } - }, "HIGHEST"); -}); + _canScript = canScript; + } +}; +events.on('player.PlayerLoginEvent', function(listener, event) { + var player = event.player; + if (classroom.canScript){ + player.addAttachment(__plugin, "scriptcraft.*", true); + } +}, "HIGHEST"); + diff --git a/src/main/javascript/plugins/drone/blocks.js b/src/main/javascript/plugins/drone/blocks.js index 19e0c0c..5a66766 100644 --- a/src/main/javascript/plugins/drone/blocks.js +++ b/src/main/javascript/plugins/drone/blocks.js @@ -272,4 +272,4 @@ blocks.rainbow = [blocks.wool.red, blocks.wool.purple]; -module.exports = blocks; +exports.blocks = blocks; diff --git a/src/main/javascript/plugins/drone/blocktype.js b/src/main/javascript/plugins/drone/blocktype.js index 1752280..4772de4 100644 --- a/src/main/javascript/plugins/drone/blocktype.js +++ b/src/main/javascript/plugins/drone/blocktype.js @@ -1,7 +1,6 @@ -var Drone = require('./drone'); -var blocks = require('./blocks'); +var Drone = require('./drone').Drone; +var blocks = require('./blocks').blocks; -module.exports = Drone; /************************************************************************ Drone.blocktype() method ======================== diff --git a/src/main/javascript/plugins/drone/contrib/castle.js b/src/main/javascript/plugins/drone/contrib/castle.js index bd634c6..f7e4d15 100644 --- a/src/main/javascript/plugins/drone/contrib/castle.js +++ b/src/main/javascript/plugins/drone/contrib/castle.js @@ -1,5 +1,5 @@ -var Drone = require('../drone'); -module.exports = Drone; +var Drone = require('../drone').Drone; + // // a castle is just a big wide fort with 4 taller forts at each corner // diff --git a/src/main/javascript/plugins/drone/contrib/chessboard.js b/src/main/javascript/plugins/drone/contrib/chessboard.js index 0987e01..0684659 100644 --- a/src/main/javascript/plugins/drone/contrib/chessboard.js +++ b/src/main/javascript/plugins/drone/contrib/chessboard.js @@ -1,7 +1,6 @@ -var Drone = require('../drone'); -var blocks = require('../blocks'); +var Drone = require('../drone').Drone; +var blocks = require('../blocks').blocks; -module.exports = Drone; /** * Creates a tile pattern of given block types and size * diff --git a/src/main/javascript/plugins/drone/contrib/cottage.js b/src/main/javascript/plugins/drone/contrib/cottage.js index 194f216..8aeb256 100644 --- a/src/main/javascript/plugins/drone/contrib/cottage.js +++ b/src/main/javascript/plugins/drone/contrib/cottage.js @@ -1,5 +1,5 @@ -var Drone = require('../drone'); -module.exports = Drone; +var Drone = require('../drone').Drone; + // // usage: // [1] to build a cottage at the player's current location or the cross-hairs location... diff --git a/src/main/javascript/plugins/drone/contrib/dancefloor.js b/src/main/javascript/plugins/drone/contrib/dancefloor.js index 6979186..a1b17d6 100644 --- a/src/main/javascript/plugins/drone/contrib/dancefloor.js +++ b/src/main/javascript/plugins/drone/contrib/dancefloor.js @@ -1,5 +1,5 @@ -var Drone = require('../drone'); -module.exports = Drone; +var Drone = require('../drone').Drone; + // // Create a floor of colored tiles some of which emit light. // The tiles change color every second creating a strobe-lit dance-floor. diff --git a/src/main/javascript/plugins/drone/contrib/fort.js b/src/main/javascript/plugins/drone/contrib/fort.js index 0f43043..d6477dd 100644 --- a/src/main/javascript/plugins/drone/contrib/fort.js +++ b/src/main/javascript/plugins/drone/contrib/fort.js @@ -1,5 +1,5 @@ -var Drone = require('../drone'); -module.exports = Drone; +var Drone = require('../drone').Drone; + // // constructs a medieval fort // diff --git a/src/main/javascript/plugins/drone/contrib/logo.js b/src/main/javascript/plugins/drone/contrib/logo.js index d9507e6..77631eb 100644 --- a/src/main/javascript/plugins/drone/contrib/logo.js +++ b/src/main/javascript/plugins/drone/contrib/logo.js @@ -1,5 +1,5 @@ -var Drone = require('../drone'); -module.exports = Drone; +var Drone = require('../drone').Drone; + // // Constructs the JS logo // https://raw.github.com/voodootikigod/logo.js/master/js.png diff --git a/src/main/javascript/plugins/drone/contrib/rainbow.js b/src/main/javascript/plugins/drone/contrib/rainbow.js index 870cbc5..e3d2992 100644 --- a/src/main/javascript/plugins/drone/contrib/rainbow.js +++ b/src/main/javascript/plugins/drone/contrib/rainbow.js @@ -1,6 +1,6 @@ -var Drone = require('../drone'); -var blocks = require('../blocks'); -module.exports = Drone; +var Drone = require('../drone').Drone; +var blocks = require('../blocks').blocks; + /************************************************************************ Drone.rainbow() method ====================== diff --git a/src/main/javascript/plugins/drone/contrib/rboxcall.js b/src/main/javascript/plugins/drone/contrib/rboxcall.js index 4546eec..bc709bb 100644 --- a/src/main/javascript/plugins/drone/contrib/rboxcall.js +++ b/src/main/javascript/plugins/drone/contrib/rboxcall.js @@ -1,5 +1,5 @@ -var Drone = require('../drone'); -module.exports = Drone; +var Drone = require('../drone').Drone; + /** * Iterates over each cube in a cubic region. For each cube has a chance to callback your * function and provide a new drone to it. diff --git a/src/main/javascript/plugins/drone/contrib/redstonewire.js b/src/main/javascript/plugins/drone/contrib/redstonewire.js index 97dc0f8..c6fa79f 100644 --- a/src/main/javascript/plugins/drone/contrib/redstonewire.js +++ b/src/main/javascript/plugins/drone/contrib/redstonewire.js @@ -1,6 +1,6 @@ -var Drone = require('../drone'); -var blocks = require('../blocks'); -module.exports = Drone; +var Drone = require('../drone').Drone; +var blocks = require('../blocks').blocks; + // // usage: // [1] to place a new block with redstone wire on it (block on bottom, redstone on top) diff --git a/src/main/javascript/plugins/drone/contrib/skyscraper-example.js b/src/main/javascript/plugins/drone/contrib/skyscraper-example.js index 13190dc..a96641a 100644 --- a/src/main/javascript/plugins/drone/contrib/skyscraper-example.js +++ b/src/main/javascript/plugins/drone/contrib/skyscraper-example.js @@ -1,7 +1,6 @@ -var Drone = require('../drone'); -var blocks = require('../blocks'); +var Drone = require('../drone').Drone; +var blocks = require('../blocks').blocks; -module.exports = Drone; Drone.extend('skyscraper',function(floors){ if (typeof floors == "undefined") diff --git a/src/main/javascript/plugins/drone/contrib/spiral_stairs.js b/src/main/javascript/plugins/drone/contrib/spiral_stairs.js index 39b818f..1e6399e 100644 --- a/src/main/javascript/plugins/drone/contrib/spiral_stairs.js +++ b/src/main/javascript/plugins/drone/contrib/spiral_stairs.js @@ -1,7 +1,6 @@ -var Drone = require('../drone'); -var blocks = require('../blocks'); +var Drone = require('../drone').Drone; +var blocks = require('../blocks').blocks; -module.exports = Drone; /************************************************************************ Drone.spiral_stairs() method ============================ diff --git a/src/main/javascript/plugins/drone/contrib/streamer.js b/src/main/javascript/plugins/drone/contrib/streamer.js index 9c2031e..f0b00d9 100644 --- a/src/main/javascript/plugins/drone/contrib/streamer.js +++ b/src/main/javascript/plugins/drone/contrib/streamer.js @@ -1,5 +1,4 @@ -var Drone = require('../drone'); -module.exports = Drone; +var Drone = require('../drone').Drone; /** * Creates a stream of blocks in a given direction until it hits something other than air * diff --git a/src/main/javascript/plugins/drone/contrib/temple.js b/src/main/javascript/plugins/drone/contrib/temple.js index c774a9b..8b9b98c 100644 --- a/src/main/javascript/plugins/drone/contrib/temple.js +++ b/src/main/javascript/plugins/drone/contrib/temple.js @@ -1,5 +1,4 @@ -var Drone = require('../drone'); -module.exports = Drone; +var Drone = require('../drone').Drone; // // constructs a mayan temple // diff --git a/src/main/javascript/plugins/drone/drone-exts.js b/src/main/javascript/plugins/drone/drone-exts.js index 08da5da..e69de29 100644 --- a/src/main/javascript/plugins/drone/drone-exts.js +++ b/src/main/javascript/plugins/drone/drone-exts.js @@ -1,25 +0,0 @@ -var Drone = require('./drone'); -var utils = require('../utils/utils'); - -var files = []; - -var filter = function(file,name){ - name = "" + name; - if (name.match(/drone\.js$/)) - return false; - if (name.match(/drone\-exts\.js$/)) - return false; - if (name.match(/\.js$/)) - return true; - if (file.isDirectory()) - return true; - return false; -}; - -var files = utils.find(__dirname, filter); - -utils.foreach(files, function (file){ - require(file); -}); - -module.exports = Drone; diff --git a/src/main/javascript/plugins/drone/drone.js b/src/main/javascript/plugins/drone/drone.js index 4686c9d..745c10a 100644 --- a/src/main/javascript/plugins/drone/drone.js +++ b/src/main/javascript/plugins/drone/drone.js @@ -1,5 +1,5 @@ -var _utils = require('../utils/utils'); -var blocks = require('./blocks'); +var _utils = require('utils'); +var blocks = require('./blocks').blocks; /********************************************************************* Drone Module @@ -732,7 +732,7 @@ Drone = function(x,y,z,dir,world) return this; }; -module.exports = Drone; +exports.Drone = Drone; // // add custom methods to the Drone object using this function diff --git a/src/main/javascript/plugins/drone/sphere.js b/src/main/javascript/plugins/drone/sphere.js index 58f9451..ab0234c 100644 --- a/src/main/javascript/plugins/drone/sphere.js +++ b/src/main/javascript/plugins/drone/sphere.js @@ -1,5 +1,4 @@ -var Drone = require('./drone'); -module.exports = Drone; +var Drone = require('./drone').Drone; /************************************************************************ Drone.sphere() method diff --git a/src/main/javascript/plugins/drone/test.js b/src/main/javascript/plugins/drone/test.js index 92f3ae4..5057151 100644 --- a/src/main/javascript/plugins/drone/test.js +++ b/src/main/javascript/plugins/drone/test.js @@ -1,4 +1,4 @@ -var Drone = require('./drone'); +var Drone = require('./drone').Drone; Drone.prototype.testHorizontalStrokeWidth = function(){ this.arc({ diff --git a/src/main/javascript/plugins/homes/homes.js b/src/main/javascript/plugins/homes/homes.js index bc6fe8a..913720d 100644 --- a/src/main/javascript/plugins/homes/homes.js +++ b/src/main/javascript/plugins/homes/homes.js @@ -1,8 +1,14 @@ +var utils = require('utils'); +var _store = { + houses: {}, + openHouses: {}, + invites: {} +}; /* The homes plugin lets players set a location as home and return to the location, invite other players to their home and also visit other player's homes. */ -plugin("homes", { +var homes = plugin("homes", { help: function(){ return [ /* basic functions */ @@ -33,7 +39,7 @@ plugin("homes", { host = guest; guest = utils.getPlayerObject(guest); host = utils.getPlayerObject(host); - var loc = this.store.houses[host.name]; + var loc = _store.houses[host.name]; if (!loc){ guest.sendMessage(host.name + " has no home"); return; @@ -53,9 +59,9 @@ plugin("homes", { _canVisit: function(guest, host){ if (guest == host) return true; - if (this.store.openHouses[host.name]) + if (_store.openHouses[host.name]) return true; - var invitations = this.store.invites[host.name]; + var invitations = _store.invites[host.name]; if (invitations) for (var i = 0;i < invitations.length;i++) if (invitations[i] == guest.name) @@ -65,16 +71,16 @@ plugin("homes", { set: function(player){ player = utils.getPlayerObject(player); var loc = player.location; - this.store.houses[player.name] = [""+loc.world.name - ,Math.floor(loc.x) - ,Math.floor(loc.y) - ,Math.floor(loc.z) - ,Math.floor(loc.yaw) - ,Math.floor(loc.pitch)]; + _store.houses[player.name] = [""+loc.world.name + ,Math.floor(loc.x) + ,Math.floor(loc.y) + ,Math.floor(loc.z) + ,Math.floor(loc.yaw) + ,Math.floor(loc.pitch)]; }, remove: function(player){ player = utils.getPlayerObject(player); - delete this.store.houses[player.name]; + delete _store.houses[player.name]; }, /* ======================================================================== social functions @@ -85,11 +91,11 @@ plugin("homes", { */ list: function(player){ var result = []; - for (var ohp in this.store.openHouses) + for (var ohp in _store.openHouses) result.push(ohp); player = utils.getPlayerObject(player); - for (var host in this.store.invites){ - var guests = this.store.invites[host]; + for (var host in _store.invites){ + var guests = _store.invites[host]; for (var i = 0;i < guests.length; i++) if (guests[i] == player.name) result.push(host); @@ -103,14 +109,14 @@ plugin("homes", { player = utils.getPlayerObject(player); var result = []; // if home is public - all players - if (this.store.openHouses[player.name]){ + if (_store.openHouses[player.name]){ var online = org.bukkit.Bukkit.getOnlinePlayers(); for (var i = 0;i < online.length; i++) if (online[i].name != player.name) result.push(online[i].name); }else{ - if (this.store.invites[player.name]) - result = this.store.invites[player.name]; + if (_store.invites[player.name]) + result = _store.invites[player.name]; else result = []; } @@ -123,10 +129,10 @@ plugin("homes", { host = utils.getPlayerObject(host); guest = utils.getPlayerObject(guest); var invitations = []; - if (this.store.invites[host.name]) - invitations = this.store.invites[host.name]; + if (_store.invites[host.name]) + invitations = _store.invites[host.name]; invitations.push(guest.name); - this.store.invites[host.name] = invitations; + _store.invites[host.name] = invitations; guest.sendMessage(host.name + " has invited you to their home."); guest.sendMessage("type '/jsp home " + host.name + "' to accept"); }, @@ -136,21 +142,21 @@ plugin("homes", { uninvite: function(host, guest){ host = utils.getPlayerObject(host); guest = utils.getPlayerObject(guest); - var invitations = this.store.invites[host.name]; + var invitations = _store.invites[host.name]; if (!invitations) return; var revisedInvites = []; for (var i =0;i < invitations.length; i++) if (invitations[i] != guest.name) revisedInvites.push(invitations[i]); - this.store.invites[host.name] = revisedInvites; + _store.invites[host.name] = revisedInvites; }, /* make the player's house public */ open: function(player, optionalMsg){ player = utils.getPlayerObject(player); - this.store.openHouses[player.name] = true; + _store.openHouses[player.name] = true; if (typeof optionalMsg != "undefined") __plugin.server.broadcastMessage(optionalMsg); }, @@ -159,132 +165,122 @@ plugin("homes", { */ close: function(player){ player = utils.getPlayerObject(player); - delete this.store.openHouses[player.name]; + delete _store.openHouses[player.name]; }, /* ======================================================================== admin functions ======================================================================== */ listall: function(){ var result = []; - for (var home in this.store.houses) + for (var home in _store.houses) result.push(home); return result; }, clear: function(player){ player = utils.getPlayerObject(player); - delete this.store.houses[player.name]; - delete this.store.openHouses[player.name]; - } - + delete _store.houses[player.name]; + delete _store.openHouses[player.name]; + }, + store: _store }, true); -/* - private implementation + +exports.homes = homes; + +/* + define a set of command options that can be used by players */ -(function(){ - /* - define a set of command options that can be used by players - */ - var options = { - 'set': function(){homes.set();}, - 'delete': function(){ homes.remove();}, - 'help': function(){ self.sendMessage(homes.help());}, - 'list': function(){ - var visitable = homes.list(); - if (visitable.length == 0){ - self.sendMessage("There are no homes to visit"); - return; - }else{ - self.sendMessage([ - "You can visit any of these " + visitable.length + " homes" - ,visitable.join(", ") - ]); - } - }, - 'ilist': function(){ - var potentialVisitors = homes.ilist(); - if (potentialVisitors.length == 0) - self.sendMessage("No one can visit your home"); - else - self.sendMessage([ - "These " + potentialVisitors.length + "players can visit your home", - potentialVisitors.join(", ")]); - }, - 'invite': function(params){ - if (params.length == 1){ - self.sendMessage("You must provide a player's name"); - return; - } - var playerName = params[1]; - var guest = utils.getPlayerObject(playerName); - if (!guest) - self.sendMessage(playerName + " is not here"); - else - homes.invite(self,guest); - }, - 'uninvite': function(params){ - if (params.length == 1){ - self.sendMessage("You must provide a player's name"); - return; - } - var playerName = params[1]; - var guest = utils.getPlayerObject(playerName); - if (!guest) - self.sendMessage(playerName + " is not here"); - else - homes.uninvite(self,guest); - }, - 'public': function(params){ - homes.open(self,params.slice(1).join(' ')); - self.sendMessage("Your home is open to the public"); - }, - 'private': function(){ - homes.close(); - self.sendMessage("Your home is closed to the public"); - }, - 'listall': function(){ - if (!self.isOp()) - self.sendMessage("Only operators can do this"); - else - self.sendMessage(homes.listall().join(", ")); - }, - 'clear': function(params){ - if (!self.isOp()) - self.sendMessage("Only operators can do this"); - else - homes.clear(params[1]); +var options = { + 'set': function(){homes.set();}, + 'delete': function(){ homes.remove();}, + 'help': function(){ self.sendMessage(homes.help());}, + 'list': function(){ + var visitable = homes.list(); + if (visitable.length == 0){ + self.sendMessage("There are no homes to visit"); + return; + }else{ + self.sendMessage([ + "You can visit any of these " + visitable.length + " homes" + ,visitable.join(", ") + ]); } - }; - var optionList = []; - for (var o in options) - optionList.push(o); - /* - Expose a set of commands that players can use at the in-game command prompt - */ - command("home", function(params){ - if (params.length == 0){ - homes.go(); + }, + 'ilist': function(){ + var potentialVisitors = homes.ilist(); + if (potentialVisitors.length == 0) + self.sendMessage("No one can visit your home"); + else + self.sendMessage([ + "These " + potentialVisitors.length + "players can visit your home", + potentialVisitors.join(", ")]); + }, + 'invite': function(params){ + if (params.length == 1){ + self.sendMessage("You must provide a player's name"); return; } - var option = options[params[0]]; - if (option) - option(params); - else{ - var host = utils.getPlayerObject(params[0]); - if (!host) - self.sendMessage(params[0] + " is not here"); - else - homes.go(self,host); + var playerName = params[1]; + var guest = utils.getPlayerObject(playerName); + if (!guest) + self.sendMessage(playerName + " is not here"); + else + homes.invite(self,guest); + }, + 'uninvite': function(params){ + if (params.length == 1){ + self.sendMessage("You must provide a player's name"); + return; } - },optionList); + var playerName = params[1]; + var guest = utils.getPlayerObject(playerName); + if (!guest) + self.sendMessage(playerName + " is not here"); + else + homes.uninvite(self,guest); + }, + 'public': function(params){ + homes.open(self,params.slice(1).join(' ')); + self.sendMessage("Your home is open to the public"); + }, + 'private': function(){ + homes.close(); + self.sendMessage("Your home is closed to the public"); + }, + 'listall': function(){ + if (!self.isOp()) + self.sendMessage("Only operators can do this"); + else + self.sendMessage(homes.listall().join(", ")); + }, + 'clear': function(params){ + if (!self.isOp()) + self.sendMessage("Only operators can do this"); + else + homes.clear(params[1]); + } +}; +var optionList = []; +for (var o in options) + optionList.push(o); +/* + Expose a set of commands that players can use at the in-game command prompt +*/ +command("home", function ( params ) { + if (params.length == 0){ + homes.go(); + return; + } + var option = options[params[0]]; + if (option) + option(params); + else{ + var host = utils.getPlayerObject(params[0]); + if (!host) + self.sendMessage(params[0] + " is not here"); + else + homes.go(self,host); + } +},optionList); + - /* - initialize the store - */ - if (typeof homes.store.houses == "undefined") - homes.store.houses = {}; - if (typeof homes.store.openHouses == "undefined") - homes.store.openHouses = {}; - if (typeof homes.store.invites == "undefined") - homes.store.invites = {}; -}()); diff --git a/src/main/javascript/plugins/minigames/NumberGuess.js b/src/main/javascript/plugins/minigames/NumberGuess.js index b70d931..542086c 100644 --- a/src/main/javascript/plugins/minigames/NumberGuess.js +++ b/src/main/javascript/plugins/minigames/NumberGuess.js @@ -1,14 +1,20 @@ -/* - A basic number-guessing game that uses the Bukkit Conversation API. - */ -ready(function(){ +/************************************************************************* +## Minigame: Guess the number + +### Example - global.GuessTheNumber = function() - { + /js Game_NumberGuess.start() + +... Begins a number-guessing game where you must guess the number (between 1 and 10) chosen by the computer. + + A basic number-guessing game that uses the Bukkit Conversation API. +***/ +exports.Game_NumberGuess = { + start: function() { importPackage(org.bukkit.conversations); - + var number = Math.ceil(Math.random() * 10); - + var prompt = new Prompt() { getPromptText: function(ctx){ @@ -44,5 +50,5 @@ ready(function(){ .withPrefix(new ConversationPrefix(){ getPrefix: function(ctx){ return "[1-10] ";} }) .buildConversation(self); conv.begin(); - }; -}); + } +}; diff --git a/src/main/javascript/plugins/minigames/SnowBallFight.js b/src/main/javascript/plugins/minigames/SnowBallFight.js index 0876b1a..0939ab9 100644 --- a/src/main/javascript/plugins/minigames/SnowBallFight.js +++ b/src/main/javascript/plugins/minigames/SnowBallFight.js @@ -1,4 +1,5 @@ -load(__folder + "../events/events.js"); +var events = require('events'); + /* OK - this is a rough and ready prototype of a simple multi-player shoot-em-up. Get a bunch of players in close proximity and issue the following commands... @@ -6,7 +7,7 @@ load(__folder + "../events/events.js"); /js var redTeam = ['','',...etc] /js var blueTeam = ['',',...etc] /js var greenTeam = ['',',...etc] - /js new SnowBallFight({red: redTeam,blue: blueTeam,green: greenTeam},60).start(); + /js new Game_SnowBallFight({red: redTeam,blue: blueTeam,green: greenTeam},60).start(); Alternatively you can just have all players play against each other... @@ -29,152 +30,147 @@ load(__folder + "../events/events.js"); */ -var SnowBallFight = function(teams, duration){}; -SnowBallFight.prototype.start = function(){}; - -(function(){ - - /* - setup game - */ - var _startGame = function(gameState){ - // don't let game start if already in progress (wait for game to finish) - if (gameState.inProgress){ - return; - } - gameState.inProgress = true; - // reset timer - gameState.duration = gameState.originalDuration; - // put all players in survival mode and give them each 200 snowballs - // 64 snowballs for every 30 seconds should be more than enough - for (var i = 10;i < gameState.duration;i+=10) - gameState.ammo.push(gameState.ammo[0]); - - for (var teamName in gameState.teams) - { - gameState.teamScores[teamName] = 0; - var team = gameState.teams[teamName]; - for (var i = 0;i < team.length;i++) { - var player = server.getPlayer(team[i]); - gameState.savedModes[player.name] = player.gameMode; - player.gameMode = org.bukkit.GameMode.SURVIVAL; - player.inventory.addItem(gameState.ammo); - } - } - }; - /* - end the game - */ - var _endGame = function(gameState){ - var scores = []; - - var leaderBoard = []; - for (var tn in gameState.teamScores){ - leaderBoard.push([tn,gameState.teamScores[tn]]); - } - leaderBoard.sort(function(a,b){ return b[1] - a[1];}); - - for (var i = 0;i < leaderBoard.length; i++){ - scores.push("Team " + leaderBoard[i][0] + " scored " + leaderBoard[i][1]); - } - - for (var teamName in gameState.teams) { - var team = gameState.teams[teamName]; - for (var i = 0;i < team.length;i++) { - // restore player's previous game mode and take back snowballs - var player = server.getPlayer(team[i]); - player.gameMode = gameState.savedModes[player.name]; - player.inventory.removeItem(gameState.ammo); - player.sendMessage("GAME OVER."); - player.sendMessage(scores); - } - } - var handlerList = org.bukkit.event.entity.EntityDamageByEntityEvent.getHandlerList(); - handlerList.unregister(gameState.listener); - gameState.inProgress = false; - }; - /* - get the team the player belongs to - */ - var _getTeam = function(player,pteams) { - for (var teamName in pteams) { - var team = pteams[teamName]; - for (var i = 0;i < team.length; i++) - if (team[i] == player.name) - return teamName; - } - return null; - }; - /* - construct a new game - */ - var _constructor = function(duration, teams) { - - var _snowBalls = new org.bukkit.inventory.ItemStack(org.bukkit.Material.SNOW_BALL, 64); - - var _gameState = { - teams: teams, - duration: duration, - originalDuration: duration, - inProgress: false, - teamScores: {}, - listener: null, - savedModes: {}, - ammo: [_snowBalls] - }; - if (typeof duration == "undefined"){ - duration = 60; - } - if (typeof teams == "undefined"){ - /* - wph 20130511 use all players - */ - teams = []; - var players = server.onlinePlayers; - for (var i = 0;i < players.length; i++){ - teams.push(players[i].name); - } - } - // - // allow for teams param to be either {red:['player1','player2'],blue:['player3']} or - // ['player1','player2','player3'] if all players are against each other (no teams) - // - if (teams instanceof Array){ - _gameState.teams = {}; - for (var i = 0;i < teams.length; i++) - _gameState.teams[teams[i]] = [teams[i]]; - } - /* - this function is called every time a player is damaged by another entity/player - */ - var _onSnowballHit = function(l,event){ - var snowball = event.damager; - if (!snowball || !(snowball instanceof org.bukkit.entity.Snowball)) - return; - var throwersTeam = _getTeam(snowball.shooter,_gameState.teams); - var damageeTeam = _getTeam(event.entity,_gameState.teams); - if (!throwersTeam || !damageeTeam) - return; // thrower/damagee wasn't in game - if (throwersTeam != damageeTeam) - _gameState.teamScores[throwersTeam]++; - else - _gameState.teamScores[throwersTeam]--; - }; - - return { - start: function() { - _startGame(_gameState); - _gameState.listener = events.on("entity.EntityDamageByEntityEvent",_onSnowballHit); - new java.lang.Thread(function(){ - while (_gameState.duration--) - java.lang.Thread.sleep(1000); // sleep 1,000 millisecs (1 second) - _endGame(_gameState); - }).start(); - } - }; - }; - SnowBallFight = _constructor; +/* + setup game +*/ +var _startGame = function(gameState){ + // don't let game start if already in progress (wait for game to finish) + if (gameState.inProgress){ + return; + } + gameState.inProgress = true; + // reset timer + gameState.duration = gameState.originalDuration; + // put all players in survival mode and give them each 200 snowballs + // 64 snowballs for every 30 seconds should be more than enough + for (var i = 10;i < gameState.duration;i+=10) + gameState.ammo.push(gameState.ammo[0]); -}()); + for (var teamName in gameState.teams) + { + gameState.teamScores[teamName] = 0; + var team = gameState.teams[teamName]; + for (var i = 0;i < team.length;i++) { + var player = server.getPlayer(team[i]); + gameState.savedModes[player.name] = player.gameMode; + player.gameMode = org.bukkit.GameMode.SURVIVAL; + player.inventory.addItem(gameState.ammo); + } + } +}; +/* + end the game +*/ +var _endGame = function(gameState){ + var scores = []; + + var leaderBoard = []; + for (var tn in gameState.teamScores){ + leaderBoard.push([tn,gameState.teamScores[tn]]); + } + leaderBoard.sort(function(a,b){ return b[1] - a[1];}); + + for (var i = 0;i < leaderBoard.length; i++){ + scores.push("Team " + leaderBoard[i][0] + " scored " + leaderBoard[i][1]); + } + + for (var teamName in gameState.teams) { + var team = gameState.teams[teamName]; + for (var i = 0;i < team.length;i++) { + // restore player's previous game mode and take back snowballs + var player = server.getPlayer(team[i]); + player.gameMode = gameState.savedModes[player.name]; + player.inventory.removeItem(gameState.ammo); + player.sendMessage("GAME OVER."); + player.sendMessage(scores); + } + } + var handlerList = org.bukkit.event.entity.EntityDamageByEntityEvent.getHandlerList(); + handlerList.unregister(gameState.listener); + gameState.inProgress = false; +}; +/* + get the team the player belongs to +*/ +var _getTeam = function(player,pteams) { + for (var teamName in pteams) { + var team = pteams[teamName]; + for (var i = 0;i < team.length; i++) + if (team[i] == player.name) + return teamName; + } + return null; +}; +/* + construct a new game +*/ +var _constructor = function(duration, teams) { + + var _snowBalls = new org.bukkit.inventory.ItemStack(org.bukkit.Material.SNOW_BALL, 64); + + var _gameState = { + teams: teams, + duration: duration, + originalDuration: duration, + inProgress: false, + teamScores: {}, + listener: null, + savedModes: {}, + ammo: [_snowBalls] + }; + if (typeof duration == "undefined"){ + duration = 60; + } + if (typeof teams == "undefined"){ + /* + wph 20130511 use all players + */ + teams = []; + var players = server.onlinePlayers; + for (var i = 0;i < players.length; i++){ + teams.push(players[i].name); + } + } + // + // allow for teams param to be either {red:['player1','player2'],blue:['player3']} or + // ['player1','player2','player3'] if all players are against each other (no teams) + // + if (teams instanceof Array){ + _gameState.teams = {}; + for (var i = 0;i < teams.length; i++) + _gameState.teams[teams[i]] = [teams[i]]; + } + /* + this function is called every time a player is damaged by another entity/player + */ + var _onSnowballHit = function(l,event){ + var snowball = event.damager; + if (!snowball || !(snowball instanceof org.bukkit.entity.Snowball)) + return; + var throwersTeam = _getTeam(snowball.shooter,_gameState.teams); + var damageeTeam = _getTeam(event.entity,_gameState.teams); + if (!throwersTeam || !damageeTeam) + return; // thrower/damagee wasn't in game + if (throwersTeam != damageeTeam) + _gameState.teamScores[throwersTeam]++; + else + _gameState.teamScores[throwersTeam]--; + }; + + return { + start: function() { + _startGame(_gameState); + _gameState.listener = events.on("entity.EntityDamageByEntityEvent",_onSnowballHit); + new java.lang.Thread(function(){ + while (_gameState.duration--) + java.lang.Thread.sleep(1000); // sleep 1,000 millisecs (1 second) + _endGame(_gameState); + }).start(); + } + }; +}; +var SnowBallFight = _constructor; + +exports.Game_SnowBallFight = SnowBallFight; diff --git a/src/main/javascript/plugins/signs/examples.js b/src/main/javascript/plugins/signs/examples.js index cd7fcc8..dd1cdd3 100644 --- a/src/main/javascript/plugins/signs/examples.js +++ b/src/main/javascript/plugins/signs/examples.js @@ -1,18 +1,21 @@ -var signs = require('./menu'); +var signs = require('signs'); // // Usage: // // In game, create a sign , target it and type ... // -// /js var signExamples = require('./signs/examples'); -// /js signExamples.testMenu() +// /js signs.menu_food(); // -exports.testMenu = signs - .menu("Dinner", - ["Lamb","Pork","Chicken","Duck","Beef"], - function(event){ - event.player.sendMessage("You chose " + event.text); - }); +// ... or ... +// +// /js signs.menu_time() +// +exports.signs = { + menu_food: signs.menu("Dinner", + ["Lamb","Pork","Chicken","Duck","Beef"], + function(event){ + event.player.sendMessage("You chose " + event.text); + }), // // This is an example sign that displays a menu of times of day // interacting with the sign will change the time of day accordingly. @@ -22,11 +25,10 @@ exports.testMenu = signs // /js var signExamples = require('./signs/examples'); // /js signExamples.timeOfDay() // -exports.timeOfDay = signs - .menu("Time", - ["Dawn","Midday","Dusk","Midnight"], - function(event){ - event.player.location.world.setTime( event.number * 6000 ); - }); - + menu_time: signs.menu("Time", + ["Dawn","Midday","Dusk","Midnight"], + function(event){ + event.player.location.world.setTime( event.number * 6000 ); + }) +} From 5953a55fb1392109043ea306386061391d716866 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 24 Dec 2013 00:27:26 +0000 Subject: [PATCH 034/456] removed _primitives.js --- src/main/javascript/lib/_primitives.js | 139 ------------------------- 1 file changed, 139 deletions(-) delete mode 100644 src/main/javascript/lib/_primitives.js diff --git a/src/main/javascript/lib/_primitives.js b/src/main/javascript/lib/_primitives.js deleted file mode 100644 index 27590a1..0000000 --- a/src/main/javascript/lib/_primitives.js +++ /dev/null @@ -1,139 +0,0 @@ -var global = this; -// -// Define these primitive methods used by drone.js (and potentially others) -// -// getPlayerPos -// returns the player's x,y,z and yaw (direction) -// -// getMousePos -// returns the x,y,z of the current block being targeted. -// -// putBlock(x,y,z,blockId,metadata) -// puts a block at a location in current world -// -// getBlock(x,y,z) -// gets the block and metadata (returned as a string in form '35:15') -// -// putSign(texts,x,y,z,blockId,metadata) -// puts a sign at the given location -// -// notifyAdministrators(msg) -// sends a message to all admins/ops. -// -// echo(msg) -// prints a message on screen to current user. -// -(function(){ - - // - // only execute once - // - if (typeof getPlayerPos != "undefined"){ - return; - } - - var _getPlayerPos = function(){ - if (typeof self == "undefined") - return; - return self.location; - }; - - var _getMousePos = function(){ - if (typeof self == "undefined") - return; - // self might be CONSOLE or a CommandBlock - if (!self.getTargetBlock) - return; - var targetedBlock = self.getTargetBlock(null,5); - if (targetedBlock == null || targetedBlock.isEmpty()){ - return null; - } - return targetedBlock.location; - }; - - var _putBlock = function(x,y,z,blockId,metadata){ - - if (typeof metadata == "undefined") - metadata = 0; - var pl = org.bukkit.entity.Player; - var cs = org.bukkit.command.BlockCommandSender; - var world = (self instanceof pl)?self.location.world:(self instanceof cs)?self.block.location.world:null; - var block = world.getBlockAt(x,y,z); - if (block.typeId != blockId || block.data != metadata) - block.setTypeIdAndData(blockId,metadata,false); - - }; - - var _putSign = function(texts, x, y, z, blockId, meta){ - if (blockId != 63 && blockId != 68) - throw new Error("Invalid Parameter: blockId must be 63 or 68"); - putBlock(x,y,z,blockId,meta); - var block = _getBlockObject(x,y,z); - var state = block.state; - if (state instanceof org.bukkit.block.Sign){ - for (var i = 0;i < texts.length; i++) - state.setLine(i%4,texts[i]); - state.update(true); - } - }; - - var _getBlock = function(x,y,z){ - var block = _getBlockObject(x,y,z); - if (block) - return "" + block.typeId + ":" + block.data; - - }; - - var _getBlockObject = function(x,y,z){ - var world = _getWorld(); - if (world){ - if (typeof z == "undefined"){ - var loc = _getMousePos() - if (loc) - return world.getBlockAt(loc); - }else{ - return world.getBlockAt(x,y,z); - } - } - }; - - var _getWorld = function(){ - if (self instanceof org.bukkit.entity.Player) - return self.location.world; - if (typeof self == "undefined") - return; - if (self instanceof org.bukkit.command.BlockCommandSender) - return self.block.location.world; - }; - - var _notifyAdministrators = function(msg){ - var ops = __plugin.server.operators.toArray(); - for (var i = 0; i < ops.length;i++){ - var op = ops[i]; - if (op.isOnline()) - op.chat(msg); - } - __plugin.logger.info(msg); - }; - var _echo = function(msg){ - __plugin.logger.info(msg); - if (typeof self == "undefined"){ - java.lang.System.out.println(msg); - return; - } - self.sendMessage(msg); - }; - - global.getPlayerPos = _getPlayerPos; - global.getMousePos = _getMousePos; - global.putBlock = _putBlock; - global.getBlock = _getBlock; - global.putSign = _putSign; - global.notifyAdministrators = _notifyAdministrators; - global.echo = _echo; - /* - wph 20131020 - add 'alert' - behaves just like echo. For programmers familiar with browser-based js - */ - global.alert = _echo; - -}()); From 7f9736223ea3b1a844d687dd307362b037a2c4e4 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 24 Dec 2013 10:02:34 +0000 Subject: [PATCH 035/456] updated docs for module loading --- src/main/javascript/lib/scriptcraft.js | 174 +++++++++++++++++++------ 1 file changed, 135 insertions(+), 39 deletions(-) diff --git a/src/main/javascript/lib/scriptcraft.js b/src/main/javascript/lib/scriptcraft.js index c270f14..b7e2e3d 100644 --- a/src/main/javascript/lib/scriptcraft.js +++ b/src/main/javascript/lib/scriptcraft.js @@ -7,55 +7,139 @@ Walter Higgins [email]: mailto:walter.higgins@gmail.com?subject=ScriptCraft_API_Reference +## 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: + + var circle = require('./circle.js'); + echo( 'The area of a circle of radius 4 is ' + + circle.area(4)); + +The contents of circle.js: + + var PI = Math.PI; + + exports.area = function (r) { + return PI * r * r; + }; + + exports.circumference = function (r) { + return 2 * PI * r; + }; + +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. + ## Module Loading -At server startup the ScriptCraft Java plugin is loaded and once -loaded the Java plugin will in turn begin loading all of the -javascript (.js) files it finds in the js-plugins directory (in the -current working directory). If this is the first time the ScriptCraft -plugin is loaded, then the js-plugins directory will not yet exist, it -will be created and all of the bundled javascript files will be -unzipped into it from a bundled resource within the Java plugin. The -very first javascript file to load will always be -js-plugins/core/_scriptcraft.js. Then all other javascript files are -loaded except for filenames which begin with `_` (underscore), such files -are considered to be private modules and will not be automatically -loaded at startup. +When the ScriptCraft Java plugin is first installed, a new +subdirectory is created in the craftbukkit directory. If your +craftbukkit directory is called 'craftbukkit' then the new +subdirectories will be ... -### Directory structure + * craftbukkit/scriptcraft/ + * craftbukkit/scriptcraft/plugins + * craftbukkit/scriptcraft/modules + * craftbukkit/scriptcraft/lib -The js-plugins directory is loosely organised into subdirectories - -one for each module. Each subdirectory in turn can contain one or more -javascript files. Within each directory, a javascript file with the -same filename as the directory will always be loaded before all other -files in the same directory. So for example, drone/drone.js will -always load before any other files in the drone/ directory. Similarly -utils/utils.js will always load before any other files in the utils/ -directory. +... The `plugins`, `modules` and `lib` directories each serve a different purpose. + +### The `plugins` directory + +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.... + + exports.greet = function() { + echo('Hello ' + self.name); + }; + +... then `greet` becomes a global function and can be used at the +in-game (or server) command prompt like so... + + /js greet() + +... 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. + +### The `modules` directory + +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. + +### The `lib` directory + +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. ### Directories -As of February 10 2013, the js-plugins directory has the following sub-directories... +As of December 24 2013, the `scriptcraft/plugins` directory has the following sub-directories... - * core - Contains javascript files containing Core functionality crucial to ScriptCraft and modules which use it. * drone - Contains the drone module and drone extensions. Drone was the first scriptcraft module. - * ext - Contains external 3rd party javascript libraries (e.g. json2.js - the JSON lib) * mini-games - Contains mini-games * arrows - The arrows module - * signs - The signs module + * signs - The signs module (includes example signs) * chat - The chat plugin/module * alias - The alias plugin/module + * home - The home module - for setting homes and visiting other homes. -## Core Module +## Free Variables -This module defines commonly used functions by all plugins... +ScripCraft provides some functions which can be used by all plugins/modules... * echo (message) - Displays a message on the screen. For example: `/js echo('Hello World')` will print Hello World on the in-game chat window. 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')` - * load (filename,warnOnFileNotFound) - loads and evaluates a javascript file, returning the evaluated object. + * require (modulename) - Will load modules. See [Node.js modules][njsmod] + + * load (filename,warnOnFileNotFound) - loads and evaluates a javascript file, returning the evaluated object. (Note: Prefer `require()` to `load()`) * save (object, filename) - saves an object to a file. @@ -66,22 +150,34 @@ This module defines commonly used functions by all plugins... 'store' property - this will be created automatically if not already defined. (its type is object {} ) . More on plugins below. - * ready (function) - specifies code to be executed only when all the plugins have loaded. - * command (name, function) - defines a command that can be used by non-operators. The `command` function provides a way for plugin developers to provide new commands for use by players. -### load() function +### require() function -The load() function is used by ScriptCraft at startup to load all of -the javascript modules and data. You normally wouldn't need to call -this function directly. If you put a javascript file anywhere in the -craftbukkit/js-plugins directory tree it will be loaded automatically -when craftbukkit starts up. The exception is files whose name begins -with an underscore `_` character. These files will not be -automatically loaded at startup as they are assumed to be files -managed / loaded by plugins. +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. + +#### 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. + +### load() function + +#### No longer recommended for use by Plugin/Module developers (deprecated) + +load() should only be used to load .json data. #### Parameters From 60de2ec28ad893568e491e3e9356d99c066e9fda Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 24 Dec 2013 22:46:35 +0000 Subject: [PATCH 036/456] Updating documentation for modules release --- src/main/javascript/lib/readme.md | 12 ++++++++++++ src/main/javascript/readme.md | 13 +++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 src/main/javascript/lib/readme.md create mode 100644 src/main/javascript/readme.md diff --git a/src/main/javascript/lib/readme.md b/src/main/javascript/lib/readme.md new file mode 100644 index 0000000..972a966 --- /dev/null +++ b/src/main/javascript/lib/readme.md @@ -0,0 +1,12 @@ +# lib directory + +This directory contains core scriptcraft files and modules. + + * plugin.js - A module which provides support for persistent plugins (plugins which need to save state) + * require.js - The require() function implementation. See [Node.js modules] documentation. + * scriptcraft.js - The core scriptcraft code. + * events.js - Event handling module for use by plugin/module developers. + +When `require('modulename')` is called, if a file in the current working directory called 'modulename' is not found then the `lib` folder is the first location that `require()` looks for modules. + +[njsmod]: http://nodejs.org/api/modules.html diff --git a/src/main/javascript/readme.md b/src/main/javascript/readme.md new file mode 100644 index 0000000..ba82601 --- /dev/null +++ b/src/main/javascript/readme.md @@ -0,0 +1,13 @@ +# scriptcraft root directory + +This directory contains the following subdirectories... + + * lib - contains core scriptcraft modules and code. + * modules - contains modules for use by others + * plugins - contains plugins (modules which are automatically loaded and globally-namespaced at startup) + +If you are a minecraft modder who wants to develop simple mods then the `plugins` location is where you should probably place your .js files. + +If you are a minecraft modder who wants to develop more complex mods or provide an API for other modders, then modules intended for use by plugins (your own or others) should probably be placed in the `modules` directory. + +The `lib` directory is reserved for use by ScriptCraft. If a module is considered essential for all, or adds significantly useful new functionality to ScriptCraft then it should be placed in the `lib` directory. From 82f1928628e8459e83cb53b13542703e8b9217ed Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 24 Dec 2013 22:47:57 +0000 Subject: [PATCH 037/456] updating docs for module release --- README.md | 48 +- docs/API-Reference.md | 1501 +++++++++-------- ...YoungPersonsGuideToProgrammingMinecraft.md | 60 +- src/docs/javascript/generateApiDocs.js | 8 +- src/main/javascript/lib/require.js | 5 +- src/main/javascript/lib/scriptcraft.js | 8 +- .../javascript/plugins/classroom/classroom.js | 17 +- 7 files changed, 887 insertions(+), 760 deletions(-) diff --git a/README.md b/README.md index b3f8b53..66b87e2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ # Let's begin... -I created ScriptCraft to make it easier for younger programmers to create their own Minecraft Mods. ScriptCraft makes it easier for new programmers to create Minecraft mods. Mods are written using the Javascript programming language and once the ScriptCraft mod is installed, you can add your own new Mods by adding Javascript (.js) files in a directory. +I created ScriptCraft to make it easier for younger programmers to +create their own Minecraft Mods. ScriptCraft makes it easier for new +programmers to create Minecraft mods. Mods are written using the +Javascript programming language and once the ScriptCraft mod is +installed, you can add your own new Mods by adding Javascript (.js) +files in a directory. * If you're new to programming and want to start modding Minecraft, then [Start Here][ypgpm]. * If you've already used [Scratch][scr], have attended a few [CoderDojo][cd] sessions, or have already dabbled with Javascript, then [Start Here][cda]. @@ -17,22 +22,11 @@ prompt. To bring up the in-game prompt press the `/` key then type `js ` followed by any javascript statement. E.g. `/js 1+1` will print 2. -ScriptCraft also includes many objects and functions to make building and modding easier using Javascript. - - * echo( message ) - displays a message on the player's screen. e.g. `/js echo( 1 + 3 )` or `/js echo ("Hello World")` - * getMousePos() - A function which returns the current position of the cross-hairs (if a block is targeted) - * getPlayerPos() - A function which returns the current position of the player. - * putBlock( x, y, z, blockId, metaData ) - A function which lets you place a block anywhere (if no coordinates are given the block the player is currently looking at is replaced). - * getBlock( x, y, z ) - returns the blockId and metadata at the given location (if no coordinates are given the cross-hair location is used) - * putSign( String[] texts, x, y, z, blockId, metaData ) - A function which lets you place a sign. - -The above primitives can be used to create buildings which would -otherwise be time-consuming to create manually. It is highly -recommended using the attached [drone][drone] javascript plugin which -provides a fluent API for building. The Javascript `Drone` class -provides a much richer API which can be used to construct -buildings. See the attached [cottage.js][cottage] file for an example -of you can use the sample Drone plugin to create new buildings in +ScriptCraft also includes many objects and functions to make building +and modding easier using Javascript. The Javascript `Drone` object +bundled with ScriptCraft provides an easy way to build at-scale in +Minecraft. See the attached [cottage.js][cottage] file for an example +of how you can use the sample Drone plugin to create new buildings in Minecraft. [drone]: https://github.com/walterhiggins/ScriptCraft/tree/master/src/main/javascript/drone/drone.js @@ -78,10 +72,10 @@ just creating new buildings, take a look at [./homes/homes.js][homes] and [./chat/color.js][chatcolor] for examples of how to create a javascript plugin for Minecraft. -[ho]: blob/master/src/main/javascript/homes/homes.js -[ch]: blob/master/src/main/javascript/chat/color.js -[ar]: blob/master/src/main/javascript/arrows/arrows.js -[si]: blob/master/src/main/javascript/signs/menu.js +[ho]: blob/master/src/main/javascript/plugins/homes/homes.js +[ch]: blob/master/src/main/javascript/plugins/chat/color.js +[ar]: blob/master/src/main/javascript/plugins/arrows/arrows.js +[si]: blob/master/src/main/javascript/modules/signs/menu.js A Javascript mod for minecraft is just a javascript source file (.js) located in the craftbukkit/js-plugins directory. All .js files in this @@ -99,8 +93,16 @@ addition to the functions provided in the MCP version of ScriptCraft, there are a couple of useful Java objects exposed via javascript in the Bukkit ScriptCraft plugin... - * `__plugin` - the ScriptCraft Plugin itself. This is a useful starting point for accessing other Bukkit objects. The `__plugin` object is of type [org.bukkit.plugin.java.JavaPlugin][api] and all of its properties and methods are accessible. For example... `js __plugin.server.motd` returns the server's message of the day (javascript is more concise than the equivalent java code: __plugin.getServer().getMotd() ). - * `self` - The player/command-block or server console operator who invoked the js command. Again, this is a good jumping off point for diving into the Bukkit API. + * `__plugin` - the ScriptCraft Plugin itself. This is a useful + starting point for accessing other Bukkit objects. The `__plugin` + object is of type [org.bukkit.plugin.java.JavaPlugin][api] and all + of its properties and methods are accessible. For example... `js + __plugin.server.motd` returns the server's message of the day + (javascript is more concise than the equivalent java code: + __plugin.getServer().getMotd() ). + * `self` - The player/command-block or server console operator who + invoked the js command. Again, this is a good jumping off point for + diving into the Bukkit API. * `server` - The top-level org.bukkit.Server object. See the [Bukkit API docs][bukapi] for reference. [dl]: http://scriptcraftjs.org/download diff --git a/docs/API-Reference.md b/docs/API-Reference.md index ed41dc1..8fdeaec 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -1,3 +1,801 @@ +# ScriptCraft API Reference + +Walter Higgins + +[walter.higgins@gmail.com][email] + +[email]: mailto:walter.higgins@gmail.com?subject=ScriptCraft_API_Reference + +## 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: + + var circle = require('./circle.js'); + echo( 'The area of a circle of radius 4 is ' + + circle.area(4)); + +The contents of circle.js: + + var PI = Math.PI; + + exports.area = function (r) { + return PI * r * r; + }; + + exports.circumference = function (r) { + return 2 * PI * r; + }; + +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. + +## Module Loading + +When the ScriptCraft Java plugin is first installed, a new +subdirectory is created in the craftbukkit directory. If your +craftbukkit directory is called 'craftbukkit' then the new +subdirectories will be ... + + * craftbukkit/scriptcraft/ + * craftbukkit/scriptcraft/plugins + * craftbukkit/scriptcraft/modules + * craftbukkit/scriptcraft/lib + +... The `plugins`, `modules` and `lib` directories each serve a different purpose. + +### The plugins directory + +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.... + + exports.greet = function() { + echo('Hello ' + self.name); + }; + +... then `greet` becomes a global function and can be used at the +in-game (or server) command prompt like so... + + /js greet() + +... 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. + +### The modules directory + +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. + +### The lib directory + +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. + +### Directories + +As of December 24 2013, the `scriptcraft/plugins` directory has the following sub-directories... + + * drone - Contains the drone module and drone extensions. Drone was the first scriptcraft module. + * mini-games - Contains mini-games + * arrows - The arrows module + * signs - The signs module (includes example signs) + * chat - The chat plugin/module + * alias - The alias plugin/module + * home - The home module - for setting homes and visiting other homes. + +## Core Module: functions + +ScripCraft provides some functions which can be used by all plugins/modules... + + * echo (message) - Displays a message on the screen. + For example: `/js echo('Hello World')` will print Hello World on the in-game chat window. + 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')` + + * require (modulename) - Will load modules. See [Node.js modules][njsmod] + + * load (filename,warnOnFileNotFound) - loads and evaluates a javascript file, returning the evaluated object. (Note: Prefer `require()` to `load()`) + + * save (object, filename) - saves an object to a file. + + * plugin (name, interface, isPersistent) - defines a new plugin. If + isPersistent is true then the plugin doesn't have to worry about + loading and saving state - that will be done by the framework. Just + make sure that anything you want to save (and restore) is in the plugin's + 'store' property - this will be created automatically if not + already defined. (its type is object {} ) . More on plugins below. + + * command (name, function) - defines a command that can be used by + non-operators. The `command` function provides a way for plugin + developers to provide new commands for use by players. + +### 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. + +#### 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. + +### load() function + +#### No longer recommended for use by Plugin/Module developers (deprecated) + +load() should only be used to load .json data. + +#### Parameters + + * filename - The name of the file to load. + * warnOnFileNotFound (optional - default: false) - warn if the file was not found. + +#### Return + +load() will return the result of the last statement evaluated in the file. + +#### Example + + load(__folder + "myFile.js"); // loads a javascript file and evaluates it. + + var myData = load("myData.json"); // loads a javascript file and evaluates it - eval'd contents are returned. + +myData.json contents... + + __data = { + players: { + walterh: { + h: ["jsp home {1}"], + sunny:["time set 0", + "weather clear"] + } + } + } + +### save() function + +The save() function saves an in-memory javascript object to a +specified file. Under the hood, save() uses JSON (specifically +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 `save()` function can later be +restored using the `load()` function. + +#### Parameters + + * objectToSave : The object you want to save. + * filename : The name of the file you want to save it to. + +#### Example + + var myObject = { name: 'John Doe', + aliases: ['John Ray', 'John Mee'], + date_of_birth: '1982/01/31' }; + save(myObject, 'johndoe.json'); + +johndoe.json contents... + + var __data = { "name": "John Doe", + "aliases": ["John Ray", "John Mee"], + "date_of_birth": "1982/01/31" }; + +### plugin() function + +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 +managed by Scriptcraft is `store` - this special member will be +automatically saved at shutdown and loaded at startup by +ScriptCraft. This makes it easier to write plugins which need to +persist data. + +#### Parameters + + * 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. + +#### Example + +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]: http://walterhiggins.net/blog/ScriptCraft-1-Month-later + +### command() function + +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. + +#### Parameters + + * 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. + * 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. + +#### Example + +See chat/colors.js or alias/alias.js or homes/homes.js for examples of how to use the `command()` function. + +### ready() function + +The `ready()` function provides a way for plugins to do additional +setup once all of the other plugins/modules have loaded. For example, +event listener registration can only be done after the +events/events.js module has loaded. A plugin author could load the +file explicilty like this... + + load(__folder + "../events/events.js"); + + // event listener registration goes here + +... or better still, just do event regristration using the `ready()` +handler knowing that by the time the `ready()` callback is invoked, +all of the scriptcraft modules have been loaded... + + ready(function(){ + // event listener registration goes here + // code that depends on other plugins/modules also goes here + }); + +The execution of the function object passed to the `ready()` function +is *deferred* until all of the plugins/modules have loaded. That way +you are guaranteed that when the function is invoked, all of the +plugins/modules have been loaded and evaluated and are ready to use. + +Core Module - Special Variables +=============================== +There are a couple of special javascript variables available in ScriptCraft... + + * __folder - The current working directory - this variable is only to be used within the main body of a .js file. + * __plugin - The ScriptCraft JavaPlugin object. + * server - The Minecraft Server object. + * self - the current player. (Note - this value should not be used in multi-threaded scripts - it's not thread-safe) + +## Miscellaneous Core Functions + +### setTimeout() function + +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. + +If Node.js supports setTimeout() then it's probably good for ScriptCraft to support it too. + +[btdoc]: http://jd.bukkit.org/beta/apidocs/org/bukkit/scheduler/BukkitTask.html + +#### Example + + // + // 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 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 + +## Require - Node.js-style module loading in ScriptCraft + +Node.js is a server-side javascript environment with an excellent +module loading system based on CommonJS. Modules in Node.js are really +simple. Each module is in its own javascript file and all variables +and functions within the file are private to that file/module only. +There is a very concise explanation of CommonJS modules at... + +[http://wiki.commonjs.org/wiki/Modules/1.1.1.][cjsmodules] + +Node.js also has good documentation on [Modules][njsmod]. + +If you want to export a variable or function you use the module.export +property. + +For example imagine you have 3 files program.js, inc.js and math.js ... + +### math.js + + exports.add = function(a,b){ + return a + b; + } + +### inc.js + + var math = require('./math'); + exports.increment = function(n){ + return math.add(n, 1); + } + +### program.js + + var inc = require('./inc').increment; + var a = 7; + a = inc(a); + print(a); + +You can see from the above sample code that programs can use modules +and modules themeselves can use other modules. Modules have full +control over what functions and properties they want to provide to +others. + +## Important + +Although ScriptCraft now supports Node.js style modules, it does not +support node modules. Node.js and Rhino are two very different +Javascript environments. ScriptCraft uses Rhino Javascript, not +Node.js. + +Modules can be loaded using relative or absolute paths. Per the CommonJS +module specification, the '.js' suffix is optional. + +[cjsmodules]: http://wiki.commonjs.org/wiki/Modules/1.1.1. + +## When resolving module names to file paths, ScriptCraft uses the following rules... + + 1. if the module does not begin with './' or '/' then ... + + 1.1 Look in the 'scriptcraft/lib' directory. If it's not there then... + 1.2 Look in the 'scriptcraft/modules' directory. If it's not there then + Throw an Error. + + 2. If the module begins with './' or '/' then ... + + 2.1 if the module begins with './' then it's treated as a file path. File paths are + always relative to the module from which the require() call is being made. + + 2.2 If the module begins with '/' then it's treated as an absolute path. + + If the module does not have a '.js' suffix, and a file with the same name and a .js sufix exists, + then the file will be loaded. + + 3. If the module name resolves to a directory then... + + 3.1 look for a package.json file in the directory and load the `main` property e.g. + + // package.json located in './some-library/' + { + "main": './some-lib.js', + "name": 'some-library' + } + + 3.2 if no package.json file exists then look for an index.js file in the directory + +events Module +============= +The Events module provides a thin wrapper around Bukkit's +Event-handling API. Bukkit's Events API makes use of Java Annotations +which are not available in Javascript, so this module provides a +simple way to listen to minecraft events in javascript. + +events.on() static method +========================= +This method is used to register event listeners. + +Parameters +---------- + + * eventName - A string or java class. If a string is supplied it must + be part of the Bukkit event class name. See [Bukkit API][buk] for + details of the many bukkit event types. When a string is supplied + there is no need to provide the full class name - you should omit + the 'org.bukkit.event' prefix. e.g. if the string + "block.BlockBreakEvent" is supplied then it's converted to the + org.bukkit.event.block.BlockBreakEvent class . + + If a java class is provided (say in the case where you've defined + your own custom event) then provide the full class name (without + enclosing quotes). + + * callback - A function which will be called whenever the event + fires. The callback should take 2 parameters, listener (the Bukkit + registered listener for this callback) and event (the event fired). + + * priority (optional - default: "HIGHEST") - The priority the + listener/callback takes over other listeners to the same + event. Possible values are "HIGH", "HIGHEST", "LOW", "LOWEST", + "NORMAL", "MONITOR". For an explanation of what the different + priorities mean refer to bukkit's [Event API Reference][buk2]. + +Returns +------- +An org.bukkit.plugin.RegisteredListener object which can be used to +unregister the listener. This same object is passed to the callback +function each time the event is fired. + +Example: +------ +The following code will print a message on screen every time a block is broken in the game + + var events = require('./events/events'); + + events.on("block.BlockBreakEvent", function(listener, evt){ + echo (evt.player.name + " broke a block!"); + }); + +To handle an event only once and unregister from further events... + + var events = require('./events/events'); + + events.on("block.BlockBreakEvent", function(listener, evt){ + print (evt.player.name + " broke a block!"); + evt.handlers.unregister(listener); + }); + +To unregister a listener *outside* of the listener function... + + var events = require('./events/events'); + + var myBlockBreakListener = events.on("block.BlockBreakEvent",function(l,e){ ... }); + ... + var handlers = org.bukkit.event.block.BlockBreakEvent.getHandlerList(); + handlers.unregister(myBlockBreakListener); + +[buk2]: http://wiki.bukkit.org/Event_API_Reference +[buk]: http://jd.bukkit.org/dev/apidocs/index.html?org/bukkit/event/Event.html + +http.request() function +==================== +The http.request() function will fetch a web address asynchronously (on a +separate thread)and pass the URL's response to a callback function +which will be executed synchronously (on the main thread). In this +way, http.request() can be used to fetch web content without blocking the +main thread of execution. + +Parameters +---------- + + * request: The request details either a plain URL e.g. "http://scriptcraft.js/sample.json" or an object with the following properties... + + - url: The URL of the request. + - method: Should be one of the standard HTTP methods, GET, POST, PUT, DELETE (defaults to GET). + - params: A Javascript object with name-value pairs. This is for supplying parameters to the server. + + * callback: The function to be called when the Web request has completed. This function takes the following parameters... + - responseCode: The numeric response code from the server. If the server did not respond with 200 OK then the response parameter will be undefined. + - response: A string (if the response is of type text) or object containing the HTTP response body. + +Example +------- +The following example illustrates how to use http.request to make a request to a JSON web service and evaluate its response... + + var jsResponse; + var http = require('./http/request'); + http.request("http://scriptcraftjs.org/sample.json",function(responseCode, responseBody){ + jsResponse = eval("(" + responseBody + ")"); + }); + +... The following example illustrates a more complex use-case POSTing parameters to a CGI process on a server... + + var http = require('./http/request'); + http.request({ url: "http://pixenate.com/pixenate/pxn8.pl", + method: "POST", + params: {script: "[]"} + }, function( responseCode, responseBody){ + var jsObj = eval("(" + responseBody + ")"); + }); + +Utilities Module +================ +Miscellaneous utility functions and classes to help with programming. + + * locationToString(Location) - returns a bukkit Location object in string form. + + * getPlayerObject(playerName) - returns the Player object for a named + player or `self` if no name is provided. + + * getPlayerPos(playerName) - returns the player's x,y,z and yaw (direction) for a named player + or player or `self` if no parameter is provided. + + * getMousePos(playerName) - returns the x,y,z of the current block being targeted by the named player + or player or `self` if no paramter is provided. + +foreach() function +======================== +The utils.foreach() function is a utility function for iterating over +an array of objects (or a java.util.Collection of objects) and processing each object in turn. Where +utils.foreach() differs from other similar functions found in +javascript libraries, is that utils.foreach can process the array +immediately or can process it *nicely* by processing one item at a +time then delaying processing of the next item for a given number of +server ticks (there are 20 ticks per second on the minecraft main +thread). This method relies on Bukkit's [org.bukkit.scheduler][sched] +package for scheduling processing of arrays. + +[sched]: http://jd.bukkit.org/beta/apidocs/org/bukkit/scheduler/package-summary.html + +Parameters +---------- + + * array : The array to be processed - It can be a javascript array, a java array or java.util.Collection + * callback : The function to be called to process each item in the + array. The callback function should have the following signature + `callback(item, index, object, array)`. That is the callback will + be called with the following parameters.... + + - item : The item in the array + - index : The index at which the item can be found in the array. + - object : Additional (optional) information passed into the foreach method. + - array : The entire array. + + * object (optional) : An object which may be used by the callback. + * delay (optional, numeric) : If a delay is specified (in ticks - 20 + ticks = 1 second), then the processing will be scheduled so that + each item will be processed in turn with a delay between the completion of each + item and the start of the next. This is recommended for big builds (say 200 x 200 x 200 + blocks) or any CPU-intensive process. + * onDone (optional, function) : A function to be executed when all processing + is complete. This parameter is only used when the processing is delayed. (It's optional even if a + delay parameter is supplied). + +If called with a delay parameter then foreach() will return +immediately after processing just the first item in the array (all +subsequent items are processed later). If your code relies on the +completion of the array processing, then provide an `onDone` parameter +and put the code there. + +Example +------- +The following example illustrates how to use foreach for immediate processing of an array... + + var utils = require('./utils/_utils'); + var players = ["moe", "larry", "curly"]; + utils.foreach (players, function(item){ + server.getPlayer(item).sendMessage("Hi " + item); + }); + +... The `utils.foreach()` function can work with Arrays or any Java-style collection. This is important +because many objects in the Bukkit API use Java-style collections... + + utils.foreach( server.onlinePlayers, function(player){ + player.chat("Hello!"); + }); + +... the above code sends a "Hello!" to every online player. + +The following example is a more complex use case - The need to build an enormous structure +without hogging CPU usage... + + // build a structure 200 wide x 200 tall x 200 long + // (That's 8 Million Blocks - enough to tax any machine!) + var utils = require('./utils/_utils'); + + var a = []; + a.length = 200; + var drone = new Drone(); + var processItem = function(item, index, object, array){ + // build a box 200 wide by 200 long then move up + drone.box(blocks.wood, 200, 1, 200).up(); + }; + // by the time the job's done 'self' might be someone else + // assume this code is within a function/closure + var player = self; + var onDone = function(){ + player.sendMessage("Job Done!"); + }; + utils.foreach (a, processItem, null, 10, onDone); + +utils.nicely() function +======================= +The utils.nicely() function is for performing processing using the +[org.bukkit.scheduler][sched] package/API. utils.nicely() lets you +process with a specified delay between the completion of each `next()` +function and the start of the next `next()` function. +`utils.nicely()` is a recursive function - that is - it calls itself +(schedules itself actually) repeatedly until `hasNext` returns false. + +Parameters +---------- + + * next : A function which will be called if processing is to be done. + * hasNext : A function which is called to determine if the `next` + callback should be invoked. This should return a boolean value - + true if the `next` function should be called (processing is not + complete), false otherwise. + * onDone : A function which is to be called when all processing is complete (hasNext returned false). + * delay : The delay (in server ticks - 20 per second) between each call. + +Example +------- +See the source code to utils.foreach for an example of how utils.nicely is used. + +utils.at() function +=================== +The utils.at() function will perform a given task at a given time every +(minecraft) day. + +Parameters +---------- + + * time24hr : The time in 24hr form - e.g. 9:30 in the morning is "09:30" while + 9:30 pm is "21:30", midnight is "00:00" and midday is "12:00" + * callback : A javascript function which will be invoked at the given time. + * world : (optional) Each world has its own clock. If no world is specified, the server's first world is used. + +Example +------- + +To warn players when night is approaching... + + var utils = require('./utils/_utils'); + + utils.at( "19:00", function() { + + utils.foreach( server.onlinePlayers, function(player){ + player.chat("The night is dark and full of terrors!"); + }); + + }, self.world); + +String class extensions +----------------------- +The following chat-formatting methods are added to the javascript String class.. + + * aqua() + * black() + * blue() + * bold() + * brightgreen() + * darkaqua() + * darkblue() + * darkgray() + * darkgreen() + * purple() + * darkpurple() + * darkred() + * gold() + * gray() + * green() + * italic() + * lightpurple() + * indigo() + * green() + * red() + * pink() + * yellow() + * white() + * strike() + * random() + * magic() + * underline() + * reset() + +Example +------- + + var boldGoldText = "Hello World".bold().gold(); + self.sendMessage(boldGoldText); + +

Hello World

+ +Fireworks Module +================ +The fireworks module makes it easy to create fireworks using +ScriptCraft. The module has a single function `firework` which takes +a `org.bukkit.Location` as its 1 and only parameter. + +Examples +-------- +The module also extends the `Drone` object adding a `firework` method +so that fireworks can be created as a part of a Drone chain. For +Example.... + + /js firework() + +... creates a single firework, while .... + + /js firework().fwd(3).times(5) + +... creates 5 fireworks in a row. Fireworks have also been added as a +possible option for the `arrow` module. To have a firework launch +where an arrow strikes... + + /js arrows.firework() + +To call the fireworks.firework() function directly, you must provide a +location. For example... + + /js var fireworks = require('fireworks'); + /js fireworks.firework(self.location); + +![firework example](img/firework.png) + Drone Module ============ The Drone is a convenience class for building. It can be used for... @@ -855,709 +1653,6 @@ To construct a spiral staircase 5 floors high made of oak... spiral_stairs('oak', 5); -## Require - Node.js-style module loading in ScriptCraft - -#### (Experimental as of 2013-12-21) - -Node.js is a server-side javascript environment with an excellent -module loading system based on CommonJS. Modules in Node.js are really -simple. Each module is in its own javascript file and all variables -and functions within the file are private to that file/module only. -There is a very concise explanation of CommonJS modules at... - -[http://wiki.commonjs.org/wiki/Modules/1.1.1.][cjsmodules] - -If you want to export a variable or function you use the module.export -property. - -For example imagine you have 3 files program.js, inc.js and math.js ... - -### math.js - - exports.add = function(a,b){ - return a + b; - } - -### inc.js - - var math = require('./math'); - exports.increment = function(n){ - return math.add(n, 1); - } - -### program.js - - var inc = require('./inc').increment; - var a = 7; - a = inc(a); - print(a); - -You can see from the above sample code that programs can use modules -and modules themeselves can use other modules. Modules have full -control over what functions and properties they want to provide to -others. - -## Important - -Although ScriptCraft now supports Node.js style modules, it does not -support node modules. Node.js and Rhino are two very different -Javascript environments. ScriptCraft uses Rhino Javascript, not -Node.js. - -Right now, the base directory is for relative modules is 'js-plugins'. -Modules can be loaded using relative or absolute paths. Per the CommonJS -module specification, the '.js' suffix is optional. - -[cjsmodules]: http://wiki.commonjs.org/wiki/Modules/1.1.1. - -## When resolving module names to file paths, ScriptCraft uses the following rules... - - 1. if the module does not begin with './' or '/' then ... - - 1.1 Look in the 'scriptcraft/lib' directory. If it's not there then... - 1.2 Look in the 'scriptcraft/modules' directory. If it's not there then - Throw an Error. - - 2. If the module begins with './' or '/' then ... - - 2.1 if the module begins with './' then it's treated as a file path. File paths are - always relative to the module from which the require() call is being made. - - 2.2 If the module begins with '/' then it's treated as an absolute path. - - If the module does not have a '.js' suffix, and a file with the same name and a .js sufix exists, - then the file will be loaded. - - 3. If the module name resolves to a directory then... - - 3.1 look for a package.json file in the directory and load the `main` property e.g. - - // package.json located in './some-library/' - { - "main": './some-lib.js', - "name": 'some-library' - } - - 3.2 if no package.json file exists then look for an index.js file in the directory - -# ScriptCraft API Reference - -Walter Higgins - -[walter.higgins@gmail.com][email] - -[email]: mailto:walter.higgins@gmail.com?subject=ScriptCraft_API_Reference - -## Module Loading - -At server startup the ScriptCraft Java plugin is loaded and once -loaded the Java plugin will in turn begin loading all of the -javascript (.js) files it finds in the js-plugins directory (in the -current working directory). If this is the first time the ScriptCraft -plugin is loaded, then the js-plugins directory will not yet exist, it -will be created and all of the bundled javascript files will be -unzipped into it from a bundled resource within the Java plugin. The -very first javascript file to load will always be -js-plugins/core/_scriptcraft.js. Then all other javascript files are -loaded except for filenames which begin with `_` (underscore), such files -are considered to be private modules and will not be automatically -loaded at startup. - -### Directory structure - -The js-plugins directory is loosely organised into subdirectories - -one for each module. Each subdirectory in turn can contain one or more -javascript files. Within each directory, a javascript file with the -same filename as the directory will always be loaded before all other -files in the same directory. So for example, drone/drone.js will -always load before any other files in the drone/ directory. Similarly -utils/utils.js will always load before any other files in the utils/ -directory. - -### Directories - -As of February 10 2013, the js-plugins directory has the following sub-directories... - - * core - Contains javascript files containing Core functionality crucial to ScriptCraft and modules which use it. - * drone - Contains the drone module and drone extensions. Drone was the first scriptcraft module. - * ext - Contains external 3rd party javascript libraries (e.g. json2.js - the JSON lib) - * mini-games - Contains mini-games - * arrows - The arrows module - * signs - The signs module - * chat - The chat plugin/module - * alias - The alias plugin/module - -## Core Module - -This module defines commonly used functions by all plugins... - - * echo (message) - Displays a message on the screen. - For example: `/js echo('Hello World')` will print Hello World on the in-game chat window. - 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')` - - * load (filename,warnOnFileNotFound) - loads and evaluates a javascript file, returning the evaluated object. - - * save (object, filename) - saves an object to a file. - - * plugin (name, interface, isPersistent) - defines a new plugin. If - isPersistent is true then the plugin doesn't have to worry about - loading and saving state - that will be done by the framework. Just - make sure that anything you want to save (and restore) is in the plugin's - 'store' property - this will be created automatically if not - already defined. (its type is object {} ) . More on plugins below. - - * ready (function) - specifies code to be executed only when all the plugins have loaded. - - * command (name, function) - defines a command that can be used by - non-operators. The `command` function provides a way for plugin - developers to provide new commands for use by players. - -### load() function - -The load() function is used by ScriptCraft at startup to load all of -the javascript modules and data. You normally wouldn't need to call -this function directly. If you put a javascript file anywhere in the -craftbukkit/js-plugins directory tree it will be loaded automatically -when craftbukkit starts up. The exception is files whose name begins -with an underscore `_` character. These files will not be -automatically loaded at startup as they are assumed to be files -managed / loaded by plugins. - -#### Parameters - - * filename - The name of the file to load. - * warnOnFileNotFound (optional - default: false) - warn if the file was not found. - -#### Return - -load() will return the result of the last statement evaluated in the file. - -#### Example - - load(__folder + "myFile.js"); // loads a javascript file and evaluates it. - - var myData = load("myData.json"); // loads a javascript file and evaluates it - eval'd contents are returned. - -myData.json contents... - - __data = { - players: { - walterh: { - h: ["jsp home {1}"], - sunny:["time set 0", - "weather clear"] - } - } - } - -### save() function - -The save() function saves an in-memory javascript object to a -specified file. Under the hood, save() uses JSON (specifically -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 `save()` function can later be -restored using the `load()` function. - -#### Parameters - - * objectToSave : The object you want to save. - * filename : The name of the file you want to save it to. - -#### Example - - var myObject = { name: 'John Doe', - aliases: ['John Ray', 'John Mee'], - date_of_birth: '1982/01/31' }; - save(myObject, 'johndoe.json'); - -johndoe.json contents... - - var __data = { "name": "John Doe", - "aliases": ["John Ray", "John Mee"], - "date_of_birth": "1982/01/31" }; - -### plugin() function - -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 -managed by Scriptcraft is `store` - this special member will be -automatically saved at shutdown and loaded at startup by -ScriptCraft. This makes it easier to write plugins which need to -persist data. - -#### Parameters - - * 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. - -#### Example - -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]: http://walterhiggins.net/blog/ScriptCraft-1-Month-later - -### command() function - -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. - -#### Parameters - - * 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. - * 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. - -#### Example - -See chat/colors.js or alias/alias.js or homes/homes.js for examples of how to use the `command()` function. - -### ready() function - -The `ready()` function provides a way for plugins to do additional -setup once all of the other plugins/modules have loaded. For example, -event listener registration can only be done after the -events/events.js module has loaded. A plugin author could load the -file explicilty like this... - - load(__folder + "../events/events.js"); - - // event listener registration goes here - -... or better still, just do event regristration using the `ready()` -handler knowing that by the time the `ready()` callback is invoked, -all of the scriptcraft modules have been loaded... - - ready(function(){ - // event listener registration goes here - // code that depends on other plugins/modules also goes here - }); - -The execution of the function object passed to the `ready()` function -is *deferred* until all of the plugins/modules have loaded. That way -you are guaranteed that when the function is invoked, all of the -plugins/modules have been loaded and evaluated and are ready to use. - -Core Module - Special Variables -=============================== -There are a couple of special javascript variables available in ScriptCraft... - - * __folder - The current working directory - this variable is only to be used within the main body of a .js file. - * __plugin - The ScriptCraft JavaPlugin object. - * server - The Minecraft Server object. - * self - the current player. (Note - this value should not be used in multi-threaded scripts - it's not thread-safe) - -## Miscellaneous Core Functions - -### setTimeout() function - -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. - -If Node.js supports setTimeout() then it's probably good for ScriptCraft to support it too. - -[btdoc]: http://jd.bukkit.org/beta/apidocs/org/bukkit/scheduler/BukkitTask.html - -#### Example - - // - // 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 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 - -events Module -============= -The Events module provides a thin wrapper around Bukkit's -Event-handling API. Bukkit's Events API makes use of Java Annotations -which are not available in Javascript, so this module provides a -simple way to listen to minecraft events in javascript. - -events.on() static method -========================= -This method is used to register event listeners. - -Parameters ----------- - - * eventName - A string or java class. If a string is supplied it must - be part of the Bukkit event class name. See [Bukkit API][buk] for - details of the many bukkit event types. When a string is supplied - there is no need to provide the full class name - you should omit - the 'org.bukkit.event' prefix. e.g. if the string - "block.BlockBreakEvent" is supplied then it's converted to the - org.bukkit.event.block.BlockBreakEvent class . - - If a java class is provided (say in the case where you've defined - your own custom event) then provide the full class name (without - enclosing quotes). - - * callback - A function which will be called whenever the event - fires. The callback should take 2 parameters, listener (the Bukkit - registered listener for this callback) and event (the event fired). - - * priority (optional - default: "HIGHEST") - The priority the - listener/callback takes over other listeners to the same - event. Possible values are "HIGH", "HIGHEST", "LOW", "LOWEST", - "NORMAL", "MONITOR". For an explanation of what the different - priorities mean refer to bukkit's [Event API Reference][buk2]. - -Returns -------- -An org.bukkit.plugin.RegisteredListener object which can be used to -unregister the listener. This same object is passed to the callback -function each time the event is fired. - -Example: ------- -The following code will print a message on screen every time a block is broken in the game - - var events = require('./events/events'); - - events.on("block.BlockBreakEvent", function(listener, evt){ - echo (evt.player.name + " broke a block!"); - }); - -To handle an event only once and unregister from further events... - - var events = require('./events/events'); - - events.on("block.BlockBreakEvent", function(listener, evt){ - print (evt.player.name + " broke a block!"); - evt.handlers.unregister(listener); - }); - -To unregister a listener *outside* of the listener function... - - var events = require('./events/events'); - - var myBlockBreakListener = events.on("block.BlockBreakEvent",function(l,e){ ... }); - ... - var handlers = org.bukkit.event.block.BlockBreakEvent.getHandlerList(); - handlers.unregister(myBlockBreakListener); - -[buk2]: http://wiki.bukkit.org/Event_API_Reference -[buk]: http://jd.bukkit.org/dev/apidocs/index.html?org/bukkit/event/Event.html - -Fireworks Module -================ -The fireworks module makes it easy to create fireworks using -ScriptCraft. The module has a single function `firework` which takes -a `org.bukkit.Location` as its 1 and only parameter. - -Examples --------- -The module also extends the `Drone` object adding a `firework` method -so that fireworks can be created as a part of a Drone chain. For -Example.... - - /js firework() - -... creates a single firework, while .... - - /js firework().fwd(3).times(5) - -... creates 5 fireworks in a row. Fireworks have also been added as a -possible option for the `arrow` module. To have a firework launch -where an arrow strikes... - - /js arrows.firework() - -To call the fireworks.firework() function directly, you must provide a -location. For example... - - /js var fireworks = require('fireworks'); - /js fireworks.firework(self.location); - -![firework example](img/firework.png) - -http.request() function -==================== -The http.request() function will fetch a web address asynchronously (on a -separate thread)and pass the URL's response to a callback function -which will be executed synchronously (on the main thread). In this -way, http.request() can be used to fetch web content without blocking the -main thread of execution. - -Parameters ----------- - - * request: The request details either a plain URL e.g. "http://scriptcraft.js/sample.json" or an object with the following properties... - - - url: The URL of the request. - - method: Should be one of the standard HTTP methods, GET, POST, PUT, DELETE (defaults to GET). - - params: A Javascript object with name-value pairs. This is for supplying parameters to the server. - - * callback: The function to be called when the Web request has completed. This function takes the following parameters... - - responseCode: The numeric response code from the server. If the server did not respond with 200 OK then the response parameter will be undefined. - - response: A string (if the response is of type text) or object containing the HTTP response body. - -Example -------- -The following example illustrates how to use http.request to make a request to a JSON web service and evaluate its response... - - var jsResponse; - var http = require('./http/request'); - http.request("http://scriptcraftjs.org/sample.json",function(responseCode, responseBody){ - jsResponse = eval("(" + responseBody + ")"); - }); - -... The following example illustrates a more complex use-case POSTing parameters to a CGI process on a server... - - var http = require('./http/request'); - http.request({ url: "http://pixenate.com/pixenate/pxn8.pl", - method: "POST", - params: {script: "[]"} - }, function( responseCode, responseBody){ - var jsObj = eval("(" + responseBody + ")"); - }); - -String class extensions ------------------------ -The following chat-formatting methods are added to the javascript String class.. - - * aqua() - * black() - * blue() - * bold() - * brightgreen() - * darkaqua() - * darkblue() - * darkgray() - * darkgreen() - * purple() - * darkpurple() - * darkred() - * gold() - * gray() - * green() - * italic() - * lightpurple() - * indigo() - * green() - * red() - * pink() - * yellow() - * white() - * strike() - * random() - * magic() - * underline() - * reset() - -Example -------- - - var boldGoldText = "Hello World".bold().gold(); - self.sendMessage(boldGoldText); - -

Hello World

- -Utilities Module -================ -Miscellaneous utility functions and classes to help with programming. - - * locationToString(Location) - returns a bukkit Location object in string form. - - * getPlayerObject(playerName) - returns the Player object for a named - player or `self` if no name is provided. - - * getPlayerPos(playerName) - returns the player's x,y,z and yaw (direction) for a named player - or player or `self` if no parameter is provided. - - * getMousePos(playerName) - returns the x,y,z of the current block being targeted by the named player - or player or `self` if no paramter is provided. - -foreach() function -======================== -The utils.foreach() function is a utility function for iterating over -an array of objects (or a java.util.Collection of objects) and processing each object in turn. Where -utils.foreach() differs from other similar functions found in -javascript libraries, is that utils.foreach can process the array -immediately or can process it *nicely* by processing one item at a -time then delaying processing of the next item for a given number of -server ticks (there are 20 ticks per second on the minecraft main -thread). This method relies on Bukkit's [org.bukkit.scheduler][sched] -package for scheduling processing of arrays. - -[sched]: http://jd.bukkit.org/beta/apidocs/org/bukkit/scheduler/package-summary.html - -Parameters ----------- - - * array : The array to be processed - It can be a javascript array, a java array or java.util.Collection - * callback : The function to be called to process each item in the - array. The callback function should have the following signature - `callback(item, index, object, array)`. That is the callback will - be called with the following parameters.... - - - item : The item in the array - - index : The index at which the item can be found in the array. - - object : Additional (optional) information passed into the foreach method. - - array : The entire array. - - * object (optional) : An object which may be used by the callback. - * delay (optional, numeric) : If a delay is specified (in ticks - 20 - ticks = 1 second), then the processing will be scheduled so that - each item will be processed in turn with a delay between the completion of each - item and the start of the next. This is recommended for big builds (say 200 x 200 x 200 - blocks) or any CPU-intensive process. - * onDone (optional, function) : A function to be executed when all processing - is complete. This parameter is only used when the processing is delayed. (It's optional even if a - delay parameter is supplied). - -If called with a delay parameter then foreach() will return -immediately after processing just the first item in the array (all -subsequent items are processed later). If your code relies on the -completion of the array processing, then provide an `onDone` parameter -and put the code there. - -Example -------- -The following example illustrates how to use foreach for immediate processing of an array... - - var utils = require('./utils/_utils'); - var players = ["moe", "larry", "curly"]; - utils.foreach (players, function(item){ - server.getPlayer(item).sendMessage("Hi " + item); - }); - -... The `utils.foreach()` function can work with Arrays or any Java-style collection. This is important -because many objects in the Bukkit API use Java-style collections... - - utils.foreach( server.onlinePlayers, function(player){ - player.chat("Hello!"); - }); - -... the above code sends a "Hello!" to every online player. - -The following example is a more complex use case - The need to build an enormous structure -without hogging CPU usage... - - // build a structure 200 wide x 200 tall x 200 long - // (That's 8 Million Blocks - enough to tax any machine!) - var utils = require('./utils/_utils'); - - var a = []; - a.length = 200; - var drone = new Drone(); - var processItem = function(item, index, object, array){ - // build a box 200 wide by 200 long then move up - drone.box(blocks.wood, 200, 1, 200).up(); - }; - // by the time the job's done 'self' might be someone else - // assume this code is within a function/closure - var player = self; - var onDone = function(){ - player.sendMessage("Job Done!"); - }; - utils.foreach (a, processItem, null, 10, onDone); - -utils.nicely() function -======================= -The utils.nicely() function is for performing processing using the -[org.bukkit.scheduler][sched] package/API. utils.nicely() lets you -process with a specified delay between the completion of each `next()` -function and the start of the next `next()` function. -`utils.nicely()` is a recursive function - that is - it calls itself -(schedules itself actually) repeatedly until `hasNext` returns false. - -Parameters ----------- - - * next : A function which will be called if processing is to be done. - * hasNext : A function which is called to determine if the `next` - callback should be invoked. This should return a boolean value - - true if the `next` function should be called (processing is not - complete), false otherwise. - * onDone : A function which is to be called when all processing is complete (hasNext returned false). - * delay : The delay (in server ticks - 20 per second) between each call. - -Example -------- -See the source code to utils.foreach for an example of how utils.nicely is used. - -utils.at() function -=================== -The utils.at() function will perform a given task at a given time every -(minecraft) day. - -Parameters ----------- - - * time24hr : The time in 24hr form - e.g. 9:30 in the morning is "09:30" while - 9:30 pm is "21:30", midnight is "00:00" and midday is "12:00" - * callback : A javascript function which will be invoked at the given time. - * world : (optional) Each world has its own clock. If no world is specified, the server's first world is used. - -Example -------- - -To warn players when night is approaching... - - var utils = require('./utils/_utils'); - - utils.at( "19:00", function() { - - utils.foreach( server.onlinePlayers, function(player){ - player.chat("The night is dark and full of terrors!"); - }); - - }, self.world); - ## The arrows mod adds fancy arrows to the game. diff --git a/docs/YoungPersonsGuideToProgrammingMinecraft.md b/docs/YoungPersonsGuideToProgrammingMinecraft.md index 11dfbbf..e93349f 100644 --- a/docs/YoungPersonsGuideToProgrammingMinecraft.md +++ b/docs/YoungPersonsGuideToProgrammingMinecraft.md @@ -439,18 +439,18 @@ object can do, let's use that knowledge to create a Minecraft Mod! Once you've installed Notepad++, Launch it, create a new file and type the following... - function greet(){ + exports.greet = function(){ echo("Hi " + self.name); } ... then save the file in a new directory -`craftbukkit/js-plugins/{your_name}` (replace {your_name} with your +`craftbukkit/scriptcraft/plugins/{your_name}` (replace {your_name} with your own name) and call the file `greet.js` (be sure to change the file-type option to '*.* All Files' when saving or NotePad++ will add a '.txt' extension to the filename. Now switch back to the Minecraft game and type... - /reload + /js refresh() ... to reload all of the server plugins. Your mod has just been loaded. Try it out by typing this command... @@ -465,8 +465,30 @@ loaded. Try it out by typing this command... minecraft username. Congratulations - You've just written your very first Minecraft Mod! With ScriptCraft installed, writing Minecraft Mods is as simple as writing a new javascript function and saving it -in a file in the js-plugins directory. This function will now be -avaible every time you launch minecraft. +in a file in the craftbukkit/scriptcraft/plugins directory. This +function will now be avaible every time you launch minecraft. This is +a deliberately trivial minecraft mod but the principles are the same +when creating more complex mods. + +The `exports` variable is a special variable you can use in your mod +to provide functions, objects and variables for others to use. If you +want to provide something for other programmers to use, you should +"export" it using the special `exports` variable. The syntax is +straightforward and you can use the same `exports` variable to export +one or more functions, objects or variables. For example... + +#### thrower.js + + exports.egg = function(){ + self.throwEgg(); + } + exports.snowball = function(){ + self.throwSnowball(); + } + +... is a plugin which provides 2 javascript functions called `egg()` +and `snowball()` which can be invoked from the in-game prompt like +this `/js egg()` or `/js snowball()`. ### Parameters If you want to change the `greet()` function so that it displays a @@ -479,11 +501,11 @@ differently each time it is called. Change the `greet()` function so that it looks like this... - function greet( greeting ) { + exports.greet = function ( greeting ) { echo( greeting + self.name ); } -... Save your greet.js file and issue the /reload command in +... Save your greet.js file and issue the `/js refresh()` command in minecraft. Now enter the following command in Minecraft... greet("Hello "); @@ -672,7 +694,7 @@ function. Open the `hi.js` file you created earlier (using NotePad++ , TextWrangler or your editor of choice) and add the following code at the bottom of the file... - function hiAll(){ + exports.hiAll = function (){ var players = server.onlinePlayers; for (var i = 0; i < players.length; i++) { var player = players[i]; @@ -760,6 +782,7 @@ utils.foreach() function... /* give every player the ability to fly. */ + var utils = require('utils'); utils.foreach( server.onlinePlayers, function (player) { player.setAllowFlight(true); @@ -771,6 +794,7 @@ utils.foreach() function... /* Play a Cat's Meow sound for each player. */ + var utils = require('utils'); utils.foreach( server.onlinePlayers, function (player) { player.playSound(player.location, @@ -811,26 +835,26 @@ pointing at the block, type the following into the in-game prompt... ... A skyscraper with just a single floor isn't much of a skyscraper so the next step is to repeat this over and over. This is where `for` loops come in. Open your favorite text editor and create a new file in -your js-plugins/{your-name} directory called `myskyscraper.js`, then +your scriptcraft/plugins/{your-name} directory, name the file `myskyscraper.js`, then type the following... - function skyscraper(floors) + exports.myskyscraper = function(floors) { floors = floors || 10; // default number of floors is 10 - this.chkpt('skyscraper'); // saves the drone position so it can return there later + this.chkpt('myskyscraper'); // saves the drone position so it can return there later for (var i = 0; i < floors; i++) { this.box(blocks.iron,20,1,20).up().box0(blocks.glass_pane,20,3,20).up(3); } - return this.move('skyscraper'); // return to where we started + return this.move('myskyscraper'); // return to where we started }; load("../drone/drone.js"); - Drone.extend('skyscraper',skyscraper); + Drone.extend('myskyscraper',myskyscraper); ... so this takes a little explaining. First I create a new function -called skyscraper that will take a single parameter `floors` so that -when you eventually call the `skyscraper()` function you can tell it +called myskyscraper that will take a single parameter `floors` so that +when you eventually call the `myskyscraper()` function you can tell it how many floors you want built. The first statement in the function `floors = floors || 10;` just sets floors to 10 if no parameter is supplied. The next statement `this.chkpt('myskyscraper')` just saves @@ -845,7 +869,7 @@ so that now it can build skyscrapers among other things. Once you've typed in the above code and saved the file, type `reload` in your in-game prompt, then type ... - /js skyscraper(2); + /js myskyscraper(2); ... A two-story skyscraper should appear. If you're feeling adventurous, try a 10 story skyscraper! Or a 20 story skyscraper! @@ -904,7 +928,7 @@ whatever follows it. What if you want to display a message in both cases - whether you're flying or not? This is where the `if - else` construct comes in handy. Open your favorite editor and type the following code into a new file -in your js-plugins directory... +in your scriptcraft/plugins directory... function flightStatus() { @@ -935,7 +959,7 @@ minecraft, I recommend reading the accompanying [ScriptCraft API reference][api] which covers all of the ScriptCraft functions, objects and methods. I also recommend reading the source code to some of the existing scriptcraft add-ons, the *chat* module ( -`js-plugins/chat/chat.js` ) is a good place to start, followed by +`scriptcraft/plugins/chat/color.js` ) is a good place to start, followed by [Anatomy of a ScriptCraft Plug-in][ap]. The online [Craftbukkit API Reference][cbapi] provides lots of valuable information about the different objects and methods available for use by ScriptCraft. diff --git a/src/docs/javascript/generateApiDocs.js b/src/docs/javascript/generateApiDocs.js index 81de2a3..8577523 100644 --- a/src/docs/javascript/generateApiDocs.js +++ b/src/docs/javascript/generateApiDocs.js @@ -86,8 +86,12 @@ var store = []; find(new File(dir),store,/\/[a-zA-Z0-9_\-]+\.js$/); store.sort(sorter([ - /_scriptcraft\.js$/, - /core/, + /scriptcraft\.js$/, + /require\.js$/, + /plugin\.js$/, + /events\.js$/, + /lib/, + /modules/, /drone\.js/, /drone/ ])); diff --git a/src/main/javascript/lib/require.js b/src/main/javascript/lib/require.js index ef95233..d7b0c59 100644 --- a/src/main/javascript/lib/require.js +++ b/src/main/javascript/lib/require.js @@ -1,8 +1,6 @@ /************************************************************************* ## Require - Node.js-style module loading in ScriptCraft -#### (Experimental as of 2013-12-21) - Node.js is a server-side javascript environment with an excellent module loading system based on CommonJS. Modules in Node.js are really simple. Each module is in its own javascript file and all variables @@ -11,6 +9,8 @@ There is a very concise explanation of CommonJS modules at... [http://wiki.commonjs.org/wiki/Modules/1.1.1.][cjsmodules] +Node.js also has good documentation on [Modules][njsmod]. + If you want to export a variable or function you use the module.export property. @@ -48,7 +48,6 @@ support node modules. Node.js and Rhino are two very different Javascript environments. ScriptCraft uses Rhino Javascript, not Node.js. -Right now, the base directory is for relative modules is 'js-plugins'. Modules can be loaded using relative or absolute paths. Per the CommonJS module specification, the '.js' suffix is optional. diff --git a/src/main/javascript/lib/scriptcraft.js b/src/main/javascript/lib/scriptcraft.js index b7e2e3d..50ec87e 100644 --- a/src/main/javascript/lib/scriptcraft.js +++ b/src/main/javascript/lib/scriptcraft.js @@ -61,7 +61,7 @@ subdirectories will be ... ... The `plugins`, `modules` and `lib` directories each serve a different purpose. -### The `plugins` directory +### The plugins directory At server startup the ScriptCraft Java plugin is loaded and begins automatically loading and executing all of the modules (javascript @@ -93,7 +93,7 @@ javascript modules in the `js-plugins` directory, then put them in the * Automatically loaded and run at server startup. * Anything exported by modules becomes a global variable. -### The `modules` directory +### The modules directory The module directory is where you should place your modules if you don't want to export globally. In javascript, it's considered best @@ -108,7 +108,7 @@ between modules in the `plugins` directory and modules in the automatically loaded and exported in to the global namespace at server startup, modules in the `modules` directory are not. -### The `lib` directory +### The lib directory Modules in the `lib` directory are for use by ScriptCraft and some core functions for use by module and plugin developers are also @@ -128,7 +128,7 @@ As of December 24 2013, the `scriptcraft/plugins` directory has the following su * alias - The alias plugin/module * home - The home module - for setting homes and visiting other homes. -## Free Variables +## Core Module: functions ScripCraft provides some functions which can be used by all plugins/modules... diff --git a/src/main/javascript/plugins/classroom/classroom.js b/src/main/javascript/plugins/classroom/classroom.js index 329251d..19feab4 100644 --- a/src/main/javascript/plugins/classroom/classroom.js +++ b/src/main/javascript/plugins/classroom/classroom.js @@ -44,9 +44,8 @@ Only ops users can run the classroom.allowScripting() function - this is so that don't try to bar themselves and each other from scripting. ***/ -var _canScript = false; - -exports.classroom = { +var _store = {enableScripting: false}; +var classroom = plugin("classroom", { allowScripting: function (/* boolean: true or false */ canScript) { /* only operators should be allowed run this function @@ -67,12 +66,16 @@ exports.classroom = { }); }); } - _canScript = canScript; - } -}; + _store.enableScripting = canScript; + }, + store: _store +}, true); + +exports.classroom = classroom; + events.on('player.PlayerLoginEvent', function(listener, event) { var player = event.player; - if (classroom.canScript){ + if (classroom.store.enableScripting){ player.addAttachment(__plugin, "scriptcraft.*", true); } }, "HIGHEST"); From 82c9b9c1255e40502f4fa428f09c39d08a607f7c Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 24 Dec 2013 23:07:20 +0000 Subject: [PATCH 038/456] release notes for 'modules' release --- docs/release-notes.md | 132 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 docs/release-notes.md diff --git a/docs/release-notes.md b/docs/release-notes.md new file mode 100644 index 0000000..11de0e6 --- /dev/null +++ b/docs/release-notes.md @@ -0,0 +1,132 @@ + +# 2013 12 24 + +## 'Modules' release + +### Modules in ScriptCraft + +ScriptCraft now has a simple module loading system. ScriptCraft now uses the [CommonJS module contract][cjsmod] - that is - the same module system used by Node.js. All of the javascript code which comes bundled with ScriptCraft has been modified so that it conforms to the CommonJS module system. + +### What this means for plugins you've developed using ScriptCraft + +If you have written plugins using a previous version of ScriptCraft then you have 2 options... + + 1. Continue using the previous version of ScriptCraft. + 2. Update your plugins to work with the ScriptCraft 'Modules' release. + +... Option 2 should be relatively straightforward if you follow these steps... + + 1. Copy your own custom plugins from the `js-plugins` directory to the new `scriptcraft/plugins` directory. + 2. In your javascript code any functions, objects or variables which you want to expose for use by others should be exposed using the special `exports` variable. All other code within your .js files will now be private by default. See below for details on how CommonJS/Node.js modules work. + +If you have any questions or concerns or need help converting your existing javascript plugin, contact please post questions on the [ScriptCraft forum][scforum] or open an issue on the [Github project][github] + +[github]: http://github.com/walterhiggins/ScriptCraft +[scforum]: https://groups.google.com/forum/?fromgroups=#!forum/scriptcraft---scripting-minecraft + +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 +[cjsmod]: http://wiki.commonjs.org/wiki/Modules/1.1.1 + +The contents of foo.js: + + var circle = require('./circle.js'); + echo( 'The area of a circle of radius 4 is ' + + circle.area(4)); + +The contents of circle.js: + + var PI = Math.PI; + + exports.area = function (r) { + return PI * r * r; + }; + + exports.circumference = function (r) { + return 2 * PI * r; + }; + +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. + +#### Module Loading + +When the ScriptCraft Java plugin is first installed, a new +subdirectory is created in the craftbukkit directory. If your +craftbukkit directory is called 'craftbukkit' then the new +subdirectories will be ... + + * craftbukkit/scriptcraft/ + * craftbukkit/scriptcraft/plugins + * craftbukkit/scriptcraft/modules + * craftbukkit/scriptcraft/lib + +... The `plugins`, `modules` and `lib` directories each serve a different purpose. + +##### The plugins directory + +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.... + + exports.greet = function() { + echo('Hello ' + self.name); + }; + +... then `greet` becomes a global function and can be used at the +in-game (or server) command prompt like so... + + /js greet() + +... 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. + +##### The modules directory + +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. + +##### The lib directory + +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. From c6b600ae9ff95f698e0935a36751e0dc3b8e3777 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 24 Dec 2013 23:09:25 +0000 Subject: [PATCH 039/456] fill-region --- docs/release-notes.md | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 11de0e6..34144da 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -5,7 +5,11 @@ ### Modules in ScriptCraft -ScriptCraft now has a simple module loading system. ScriptCraft now uses the [CommonJS module contract][cjsmod] - that is - the same module system used by Node.js. All of the javascript code which comes bundled with ScriptCraft has been modified so that it conforms to the CommonJS module system. +ScriptCraft now has a simple module loading system. ScriptCraft now +uses the [CommonJS module contract][cjsmod] - that is - the same +module system used by Node.js. All of the javascript code which comes +bundled with ScriptCraft has been modified so that it conforms to the +CommonJS module system. ### What this means for plugins you've developed using ScriptCraft @@ -17,16 +21,24 @@ If you have written plugins using a previous version of ScriptCraft then you hav ... Option 2 should be relatively straightforward if you follow these steps... 1. Copy your own custom plugins from the `js-plugins` directory to the new `scriptcraft/plugins` directory. - 2. In your javascript code any functions, objects or variables which you want to expose for use by others should be exposed using the special `exports` variable. All other code within your .js files will now be private by default. See below for details on how CommonJS/Node.js modules work. + 2. In your javascript code any functions, objects or variables which + you want to expose for use by others should be exposed using the + special `exports` variable. All other code within your .js files will + now be private by default. See below for details on how + CommonJS/Node.js modules work. -If you have any questions or concerns or need help converting your existing javascript plugin, contact please post questions on the [ScriptCraft forum][scforum] or open an issue on the [Github project][github] +If you have any questions or concerns or need help converting your +existing javascript plugin, contact please post questions on the +[ScriptCraft forum][scforum] or open an issue on the [Github +project][github] [github]: http://github.com/walterhiggins/ScriptCraft [scforum]: https://groups.google.com/forum/?fromgroups=#!forum/scriptcraft---scripting-minecraft -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.* +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 [cjsmod]: http://wiki.commonjs.org/wiki/Modules/1.1.1 From d44a43e598c1f4f3f9068e97934a0406f1c3be97 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Wed, 25 Dec 2013 07:48:10 +0000 Subject: [PATCH 040/456] Fixed a bug in plugin code where stores were being trashed at startup --- build.properties | 3 +- build.xml | 2 +- src/main/javascript/lib/plugin.js | 9 ++-- src/main/javascript/lib/require.js | 54 ++++++------------- src/main/javascript/lib/scriptcraft.js | 11 +++- .../javascript/plugins/classroom/classroom.js | 2 +- 6 files changed, 35 insertions(+), 46 deletions(-) diff --git a/build.properties b/build.properties index f7c14f9..4ef2b33 100644 --- a/build.properties +++ b/build.properties @@ -1 +1,2 @@ -bukkit-version=1.6.4 +bukkit-version=1.7.2 +scriptcraft-version=2.0 diff --git a/build.xml b/build.xml index 3f64ef4..b845ae4 100644 --- a/build.xml +++ b/build.xml @@ -90,7 +90,7 @@ - + [[version]] diff --git a/src/main/javascript/lib/plugin.js b/src/main/javascript/lib/plugin.js index 1cea02d..f9b1027 100644 --- a/src/main/javascript/lib/plugin.js +++ b/src/main/javascript/lib/plugin.js @@ -36,12 +36,13 @@ var _plugin = function(/* String */ moduleName, /* Object */ moduleObject, isPer _plugins[moduleName] = pluginData; if (isPersistent){ + if (!moduleObject.store){ + moduleObject.store = {}; + } var loadedStore = load(dataDir.canonicalPath + "/" + moduleName + "-store.json"); if (loadedStore){ - moduleObject.store = loadedStore; - }else{ - if (!moduleObject.store){ - moduleObject.store = {}; + for (var i in loadedStore){ + moduleObject.store[i] = loadedStore[i]; } } } diff --git a/src/main/javascript/lib/require.js b/src/main/javascript/lib/require.js index d7b0c59..fd08ee4 100644 --- a/src/main/javascript/lib/require.js +++ b/src/main/javascript/lib/require.js @@ -54,10 +54,12 @@ module specification, the '.js' suffix is optional. [cjsmodules]: http://wiki.commonjs.org/wiki/Modules/1.1.1. ***/ -( function (logger, evaluator, verbose, rootDir) { +( function (logger, evaluator, verbose, rootDir, modulePaths) { - if (verbose) + if (verbose){ logger.info("Setting up 'require' module system. Root Directory: " + rootDir); + logger.info("Module paths: " + JSON.stringify(modulePaths)); + } var File = java.io.File; @@ -84,9 +86,6 @@ module specification, the '.js' suffix is optional. } }; - var LIB_DIR = rootDir + '/lib/'; - var MODULE_DIR = rootDir + '/modules/'; - var resolveModuleToFile = function(moduleName, parentDir) { /********************************************************************** ## When resolving module names to file paths, ScriptCraft uses the following rules... @@ -120,6 +119,7 @@ module specification, the '.js' suffix is optional. 3.2 if no package.json file exists then look for an index.js file in the directory ***/ + var file = new File(moduleName); var fileExists = function(file) { @@ -137,41 +137,19 @@ module specification, the '.js' suffix is optional. if (moduleName.match(/^[^\.\/]/)){ // it's a module named like so ... 'events' , 'net/http' // - - var resolvedFile = new File(LIB_DIR + moduleName); - if (resolvedFile.exists()){ - - return fileExists(resolvedFile); - } else{ - - // try appending a .js to the end - resolvedFile = new File(LIB_DIR + moduleName + '.js'); + var resolvedFile; + for (var i = 0;i < modulePaths.length; i++){ + resolvedFile = new File(modulePaths[i] + moduleName); if (resolvedFile.exists()){ - - return resolvedFile; + return fileExists(resolvedFile); }else{ - - if (verbose){ - logger.info("File not found in " + LIB_DIR + ': ' + resolvedFile.canonicalPath); - } - - resolvedFile = new File(MODULE_DIR + moduleName); - if (resolvedFile.exists()){ - return fileExists(resolvedFile); - }else { - if (verbose){ - logger.info("File not found in " + MODULE_DIR + ': ' + resolvedFile.canonicalPath); - } - resolvedFile = new File(MODULE_DIR + moduleName + '.js'); - if (resolvedFile.exists()) - return resolvedFile; - else{ - - if (verbose){ - logger.info("File not found in " + MODULE_DIR + ': ' + resolvedFile.canonicalPath); - } - } - } + // try appending a .js to the end + resolvedFile = new File(modulePaths[i] + moduleName + '.js'); + if (resolvedFile.exists()) + return resolvedFile; + } + if (verbose){ + logger.info("Module " + moduleName + " not found in " + modulePaths[i]); } } } else { diff --git a/src/main/javascript/lib/scriptcraft.js b/src/main/javascript/lib/scriptcraft.js index 50ec87e..143fd0d 100644 --- a/src/main/javascript/lib/scriptcraft.js +++ b/src/main/javascript/lib/scriptcraft.js @@ -740,7 +740,16 @@ See [issue #69][issue69] for more information. global.addUnloadHandler = _addUnloadHandler; var fnRequire = load(jsPluginsRootDirName + '/lib/require.js',true); - global.require = fnRequire(__plugin.logger, __engine, config.verbose, jsPluginsRootDirName); + /* + setup paths to search for modules + */ + var modulePaths = [jsPluginsRootDirName + '/lib/', + jsPluginsRootDirName + '/modules/']; + global.require = fnRequire(__plugin.logger, + __engine, + config.verbose, + jsPluginsRootDirName, + modulePaths); var plugins = require('plugin'); diff --git a/src/main/javascript/plugins/classroom/classroom.js b/src/main/javascript/plugins/classroom/classroom.js index 19feab4..42ef205 100644 --- a/src/main/javascript/plugins/classroom/classroom.js +++ b/src/main/javascript/plugins/classroom/classroom.js @@ -75,7 +75,7 @@ exports.classroom = classroom; events.on('player.PlayerLoginEvent', function(listener, event) { var player = event.player; - if (classroom.store.enableScripting){ + if (_store.enableScripting){ player.addAttachment(__plugin, "scriptcraft.*", true); } }, "HIGHEST"); From b92e9e4a0760994be243ad4d30e8465c00d6896d Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Wed, 25 Dec 2013 13:39:04 +0000 Subject: [PATCH 041/456] made blocks a module rather than a plugin --- docs/API-Reference.md | 28 +- src/main/javascript/modules/blocks.js | 275 ++++++++++++++++++ .../javascript/plugins/drone/blocktype.js | 2 +- .../plugins/drone/contrib/chessboard.js | 2 +- .../plugins/drone/contrib/rainbow.js | 2 +- .../plugins/drone/contrib/redstonewire.js | 2 +- .../drone/contrib/skyscraper-example.js | 2 +- .../plugins/drone/contrib/spiral_stairs.js | 2 +- src/main/javascript/plugins/drone/drone.js | 8 +- 9 files changed, 302 insertions(+), 21 deletions(-) create mode 100644 src/main/javascript/modules/blocks.js diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 8fdeaec..da7ec19 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -764,6 +764,20 @@ Example

Hello World

+Blocks Module +============= +You hate having to lookup [Data Values][dv] when you use ScriptCraft's Drone() functions. So do I. +So I created this blocks object which is a helper object for use in construction. + +Examples +-------- + + box( blocks.oak ); // creates a single oak wood block + box( blocks.sand, 3, 2, 1 ); // creates a block of sand 3 wide x 2 high x 1 long + box( blocks.wool.green, 2 ); // creates a block of green wool 2 blocks wide + +Color aliased properties that were a direct descendant of the blocks object are no longer used to avoid confusion with carpet and stained clay blocks. In addition, there's a convenience array `blocks.rainbow` which is an array of the 7 colors of the rainbow (or closest approximations). + Fireworks Module ================ The fireworks module makes it easy to create fireworks using @@ -1516,20 +1530,6 @@ To create a 2-line high message using glowstone... [imgbt1]: img/blocktype1.png -Blocks Module -============= -You hate having to lookup [Data Values][dv] when you use ScriptCraft's Drone() functions. So do I. -So I created this blocks object which is a helper object for use in construction. - -Examples --------- - - box( blocks.oak ); // creates a single oak wood block - box( blocks.sand, 3, 2, 1 ); // creates a block of sand 3 wide x 2 high x 1 long - box( blocks.wool.green, 2 ); // creates a block of green wool 2 blocks wide - -Color aliased properties that were a direct descendant of the blocks object are no longer used to avoid confusion with carpet and stained clay blocks. In addition, there's a convenience array `blocks.rainbow` which is an array of the 7 colors of the rainbow (or closest approximations). - Drone.sphere() method ===================== Creates a sphere. diff --git a/src/main/javascript/modules/blocks.js b/src/main/javascript/modules/blocks.js new file mode 100644 index 0000000..19e0c0c --- /dev/null +++ b/src/main/javascript/modules/blocks.js @@ -0,0 +1,275 @@ +/************************************************************************ +Blocks Module +============= +You hate having to lookup [Data Values][dv] when you use ScriptCraft's Drone() functions. So do I. +So I created this blocks object which is a helper object for use in construction. + +Examples +-------- + + box( blocks.oak ); // creates a single oak wood block + box( blocks.sand, 3, 2, 1 ); // creates a block of sand 3 wide x 2 high x 1 long + box( blocks.wool.green, 2 ); // creates a block of green wool 2 blocks wide + +Color aliased properties that were a direct descendant of the blocks object are no longer used to avoid confusion with carpet and stained clay blocks. In addition, there's a convenience array `blocks.rainbow` which is an array of the 7 colors of the rainbow (or closest approximations). + +***/ +var blocks = { + air: 0, + stone: 1, + grass: 2, + dirt: 3, + cobblestone: 4, + oak: 5, + spruce: '5:1', + birch: '5:2', + jungle: '5:3', + sapling: { + oak: 6, + spruce: '6:1', + birch: '62:2', + jungle: '6:3' + }, + bedrock: 7, + water: 8, + water_still: 9, + lava: 10, + lava_still: 11, + sand: 12, + gravel: 13, + gold_ore: 14, + iron_ore: 15, + coal_ore: 16, + wood: 17, + leaves: 18, + sponge: 19, + glass: 20, + lapis_lazuli_ore: 21, + lapis_lazuli_block: 22, + dispenser: 23, + sandstone: 24, + note: 25, + bed: 26, + powered_rail: 27, + detector_rail: 28, + sticky_piston: 29, + cobweb: 30, + grass_tall: 31, + dead_bush: 32, + piston: 33, + piston_extn: 34, + wool: { + white: 35 // All other colors added below + }, + dandelion: 37, + flower_yellow: 37, + rose: 38, + flower_red: 38, + mushroom_brown: 39, + mushroom_red: 40, + gold: 41, + iron: 42, + tnt: 46, + bookshelf: 47, + moss_stone: 48, + obsidian: 49, + torch: 50, + fire: 51, + monster_spawner: 52, + stairs: { + oak: 53, + cobblestone: 67, + brick: 108, + stone: 109, + nether: 114, + sandstone: 128, + spruce: 134, + birch: 135, + jungle: 136, + quartz: 156 + }, + chest: 54, + redstone_wire: 55, + diamond_ore: 56, + diamond: 57, + crafting_table: 58, + wheat_seeds: 59, + farmland: 60, + furnace: 61, + furnace_burning: 62, + sign_post: 63, + door_wood: 64, + ladder: 65, + rail: 66, + sign: 68, + lever: 69, + pressure_plate_stone: 70, + door_iron: 71, + pressure_plate_wood: 72, + redstone_ore: 73, + redstone_ore_glowing: 74, + torch_redstone: 75, + torch_redstone_active: 76, + stone_button: 77, + ice: 79, + snow: 80, + cactus: 81, + clay: 82, + sugar_cane: 83, + jukebox: 84, + fence: 85, + pumpkin: 86, + netherrack: 87, + soulsand: 88, + glowstone: 89, + netherportal: 90, + jackolantern: 91, + cake: 92, + redstone_repeater: 93, + redeston_repeater_active: 94, + chest_locked: 95, + trapdoor: 96, + monster_egg: 97, + brick: { + stone: 98, + mossy: '98:1', + cracked: '98:2', + chiseled: '98:3', + red: 45 + }, + mushroom_brown_huge: 99, + mushroom_red_huge: 100, + iron_bars: 101, + glass_pane: 102, + melon: 103, + pumpkin_stem: 104, + melon_stem: 105, + vines: 106, + fence_gate: 107, + mycelium: 110, + lily_pad: 111, + nether: 112, + nether_fence: 113, + netherwart: 115, + table_enchantment: 116, + brewing_stand: 117, + cauldron: 118, + endportal: 119, + endportal_frame: 120, + endstone: 121, + dragon_egg: 122, + redstone_lamp: 123, + redstone_lamp_active: 124, + slab: { + snow: 78, + stone: 44, + sandstone: '44:1', + wooden: '44:2', + cobblestone: '44:3', + brick: '44:4', + stonebrick: '44:5', + netherbrick:'44:6', + quartz: '44:7', + oak: 126, + spruce: '126:1', + birch: '126:2', + jungle: '126:3', + upper: { + stone: '44:8', + sandstone: '44:9', + wooden: '44:10', + cobblestone: '44:11', + brick: '44:12', + stonebrick: '44:13', + netherbrick:'44:14', + quartz: '44:15', + oak: '126:8', + spruce: '126:9', + birch: '126:10', + jungle: '126:11', + } + }, + cocoa: 127, + emerald_ore: 129, + enderchest: 130, + tripwire_hook: 131, + tripwire: 132, + emerald: 133, + command: 137, + beacon: 138, + cobblestone_wall: 139, + flowerpot: 140, + carrots: 141, + potatoes: 142, + button_wood: 143, + mobhead: 144, + anvil: 145, + chest_trapped: 146, + pressure_plate_weighted_light: 147, + pressure_plate_weighted_heavy: 148, + redstone_comparator: 149, + redstone_comparator_active: 150, + daylight_sensor: 151, + redstone: 152, + netherquartzore: 153, + hopper: 154, + quartz: 155, + rail_activator: 157, + dropper: 158, + stained_clay: { + white: 159 // All other colors added below + }, + hay: 170, + carpet: { + white: 171 // All other colors added below + }, + hardened_clay: 172, + coal_block: 173 +}; + +// Add all available colors to colorized block collections + +var colors = { + orange: ':1', + magenta: ':2', + lightblue: ':3', + yellow: ':4', + lime: ':5', + pink: ':6', + gray: ':7', + lightgray: ':8', + cyan: ':9', + purple: ':10', + blue: ':11', + brown: ':12', + green: ':13', + red: ':14', + black: ':15' +}; +var colorized_blocks = ["wool", "stained_clay", "carpet"]; + +for (var i = 0, len = colorized_blocks.length; i < len; i++) { + var block = colorized_blocks[i], + data_value = blocks[block].white; + + for (var color in colors) { + blocks[block][color] = data_value + colors[color]; + } +}; + +/* + rainbow colors - a convenience + Color aliased properties that were a direct descendant of the blocks + object are no longer used to avoid confusion with carpet and stained + clay blocks. +*/ +blocks.rainbow = [blocks.wool.red, + blocks.wool.orange, + blocks.wool.yellow, + blocks.wool.lime, + blocks.wool.lightblue, + blocks.wool.blue, + blocks.wool.purple]; + + +module.exports = blocks; diff --git a/src/main/javascript/plugins/drone/blocktype.js b/src/main/javascript/plugins/drone/blocktype.js index 4772de4..7deced8 100644 --- a/src/main/javascript/plugins/drone/blocktype.js +++ b/src/main/javascript/plugins/drone/blocktype.js @@ -1,5 +1,5 @@ var Drone = require('./drone').Drone; -var blocks = require('./blocks').blocks; +var blocks = require('blocks'); /************************************************************************ Drone.blocktype() method diff --git a/src/main/javascript/plugins/drone/contrib/chessboard.js b/src/main/javascript/plugins/drone/contrib/chessboard.js index 0684659..f0c046b 100644 --- a/src/main/javascript/plugins/drone/contrib/chessboard.js +++ b/src/main/javascript/plugins/drone/contrib/chessboard.js @@ -1,5 +1,5 @@ var Drone = require('../drone').Drone; -var blocks = require('../blocks').blocks; +var blocks = require('blocks'); /** * Creates a tile pattern of given block types and size diff --git a/src/main/javascript/plugins/drone/contrib/rainbow.js b/src/main/javascript/plugins/drone/contrib/rainbow.js index e3d2992..f675b5f 100644 --- a/src/main/javascript/plugins/drone/contrib/rainbow.js +++ b/src/main/javascript/plugins/drone/contrib/rainbow.js @@ -1,5 +1,5 @@ var Drone = require('../drone').Drone; -var blocks = require('../blocks').blocks; +var blocks = require('blocks'); /************************************************************************ Drone.rainbow() method diff --git a/src/main/javascript/plugins/drone/contrib/redstonewire.js b/src/main/javascript/plugins/drone/contrib/redstonewire.js index c6fa79f..19d256a 100644 --- a/src/main/javascript/plugins/drone/contrib/redstonewire.js +++ b/src/main/javascript/plugins/drone/contrib/redstonewire.js @@ -1,5 +1,5 @@ var Drone = require('../drone').Drone; -var blocks = require('../blocks').blocks; +var blocks = require('blocks'); // // usage: diff --git a/src/main/javascript/plugins/drone/contrib/skyscraper-example.js b/src/main/javascript/plugins/drone/contrib/skyscraper-example.js index a96641a..2a4adb0 100644 --- a/src/main/javascript/plugins/drone/contrib/skyscraper-example.js +++ b/src/main/javascript/plugins/drone/contrib/skyscraper-example.js @@ -1,5 +1,5 @@ var Drone = require('../drone').Drone; -var blocks = require('../blocks').blocks; +var blocks = require('blocks'); Drone.extend('skyscraper',function(floors){ diff --git a/src/main/javascript/plugins/drone/contrib/spiral_stairs.js b/src/main/javascript/plugins/drone/contrib/spiral_stairs.js index 1e6399e..7385ffc 100644 --- a/src/main/javascript/plugins/drone/contrib/spiral_stairs.js +++ b/src/main/javascript/plugins/drone/contrib/spiral_stairs.js @@ -1,5 +1,5 @@ var Drone = require('../drone').Drone; -var blocks = require('../blocks').blocks; +var blocks = require('blocks'); /************************************************************************ Drone.spiral_stairs() method diff --git a/src/main/javascript/plugins/drone/drone.js b/src/main/javascript/plugins/drone/drone.js index 745c10a..014cf2a 100644 --- a/src/main/javascript/plugins/drone/drone.js +++ b/src/main/javascript/plugins/drone/drone.js @@ -1,5 +1,5 @@ var _utils = require('utils'); -var blocks = require('./blocks').blocks; +var blocks = require('blocks'); /********************************************************************* Drone Module @@ -733,6 +733,12 @@ Drone = function(x,y,z,dir,world) }; exports.Drone = Drone; +/* + because this is a plugin, any of its exports will be exported globally. + Since 'blocks' is a module not a plugin it is convenient to export it via + the Drone module. +*/ +exports.blocks = blocks; // // add custom methods to the Drone object using this function From de756f38c0995d2b358faa5f174533b029850333 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Wed, 25 Dec 2013 13:39:36 +0000 Subject: [PATCH 042/456] removed --- src/main/javascript/plugins/drone/blocks.js | 275 -------------------- 1 file changed, 275 deletions(-) delete mode 100644 src/main/javascript/plugins/drone/blocks.js diff --git a/src/main/javascript/plugins/drone/blocks.js b/src/main/javascript/plugins/drone/blocks.js deleted file mode 100644 index 5a66766..0000000 --- a/src/main/javascript/plugins/drone/blocks.js +++ /dev/null @@ -1,275 +0,0 @@ -/************************************************************************ -Blocks Module -============= -You hate having to lookup [Data Values][dv] when you use ScriptCraft's Drone() functions. So do I. -So I created this blocks object which is a helper object for use in construction. - -Examples --------- - - box( blocks.oak ); // creates a single oak wood block - box( blocks.sand, 3, 2, 1 ); // creates a block of sand 3 wide x 2 high x 1 long - box( blocks.wool.green, 2 ); // creates a block of green wool 2 blocks wide - -Color aliased properties that were a direct descendant of the blocks object are no longer used to avoid confusion with carpet and stained clay blocks. In addition, there's a convenience array `blocks.rainbow` which is an array of the 7 colors of the rainbow (or closest approximations). - -***/ -var blocks = { - air: 0, - stone: 1, - grass: 2, - dirt: 3, - cobblestone: 4, - oak: 5, - spruce: '5:1', - birch: '5:2', - jungle: '5:3', - sapling: { - oak: 6, - spruce: '6:1', - birch: '62:2', - jungle: '6:3' - }, - bedrock: 7, - water: 8, - water_still: 9, - lava: 10, - lava_still: 11, - sand: 12, - gravel: 13, - gold_ore: 14, - iron_ore: 15, - coal_ore: 16, - wood: 17, - leaves: 18, - sponge: 19, - glass: 20, - lapis_lazuli_ore: 21, - lapis_lazuli_block: 22, - dispenser: 23, - sandstone: 24, - note: 25, - bed: 26, - powered_rail: 27, - detector_rail: 28, - sticky_piston: 29, - cobweb: 30, - grass_tall: 31, - dead_bush: 32, - piston: 33, - piston_extn: 34, - wool: { - white: 35 // All other colors added below - }, - dandelion: 37, - flower_yellow: 37, - rose: 38, - flower_red: 38, - mushroom_brown: 39, - mushroom_red: 40, - gold: 41, - iron: 42, - tnt: 46, - bookshelf: 47, - moss_stone: 48, - obsidian: 49, - torch: 50, - fire: 51, - monster_spawner: 52, - stairs: { - oak: 53, - cobblestone: 67, - brick: 108, - stone: 109, - nether: 114, - sandstone: 128, - spruce: 134, - birch: 135, - jungle: 136, - quartz: 156 - }, - chest: 54, - redstone_wire: 55, - diamond_ore: 56, - diamond: 57, - crafting_table: 58, - wheat_seeds: 59, - farmland: 60, - furnace: 61, - furnace_burning: 62, - sign_post: 63, - door_wood: 64, - ladder: 65, - rail: 66, - sign: 68, - lever: 69, - pressure_plate_stone: 70, - door_iron: 71, - pressure_plate_wood: 72, - redstone_ore: 73, - redstone_ore_glowing: 74, - torch_redstone: 75, - torch_redstone_active: 76, - stone_button: 77, - ice: 79, - snow: 80, - cactus: 81, - clay: 82, - sugar_cane: 83, - jukebox: 84, - fence: 85, - pumpkin: 86, - netherrack: 87, - soulsand: 88, - glowstone: 89, - netherportal: 90, - jackolantern: 91, - cake: 92, - redstone_repeater: 93, - redeston_repeater_active: 94, - chest_locked: 95, - trapdoor: 96, - monster_egg: 97, - brick: { - stone: 98, - mossy: '98:1', - cracked: '98:2', - chiseled: '98:3', - red: 45 - }, - mushroom_brown_huge: 99, - mushroom_red_huge: 100, - iron_bars: 101, - glass_pane: 102, - melon: 103, - pumpkin_stem: 104, - melon_stem: 105, - vines: 106, - fence_gate: 107, - mycelium: 110, - lily_pad: 111, - nether: 112, - nether_fence: 113, - netherwart: 115, - table_enchantment: 116, - brewing_stand: 117, - cauldron: 118, - endportal: 119, - endportal_frame: 120, - endstone: 121, - dragon_egg: 122, - redstone_lamp: 123, - redstone_lamp_active: 124, - slab: { - snow: 78, - stone: 44, - sandstone: '44:1', - wooden: '44:2', - cobblestone: '44:3', - brick: '44:4', - stonebrick: '44:5', - netherbrick:'44:6', - quartz: '44:7', - oak: 126, - spruce: '126:1', - birch: '126:2', - jungle: '126:3', - upper: { - stone: '44:8', - sandstone: '44:9', - wooden: '44:10', - cobblestone: '44:11', - brick: '44:12', - stonebrick: '44:13', - netherbrick:'44:14', - quartz: '44:15', - oak: '126:8', - spruce: '126:9', - birch: '126:10', - jungle: '126:11', - } - }, - cocoa: 127, - emerald_ore: 129, - enderchest: 130, - tripwire_hook: 131, - tripwire: 132, - emerald: 133, - command: 137, - beacon: 138, - cobblestone_wall: 139, - flowerpot: 140, - carrots: 141, - potatoes: 142, - button_wood: 143, - mobhead: 144, - anvil: 145, - chest_trapped: 146, - pressure_plate_weighted_light: 147, - pressure_plate_weighted_heavy: 148, - redstone_comparator: 149, - redstone_comparator_active: 150, - daylight_sensor: 151, - redstone: 152, - netherquartzore: 153, - hopper: 154, - quartz: 155, - rail_activator: 157, - dropper: 158, - stained_clay: { - white: 159 // All other colors added below - }, - hay: 170, - carpet: { - white: 171 // All other colors added below - }, - hardened_clay: 172, - coal_block: 173 -}; - -// Add all available colors to colorized block collections - -var colors = { - orange: ':1', - magenta: ':2', - lightblue: ':3', - yellow: ':4', - lime: ':5', - pink: ':6', - gray: ':7', - lightgray: ':8', - cyan: ':9', - purple: ':10', - blue: ':11', - brown: ':12', - green: ':13', - red: ':14', - black: ':15' -}; -var colorized_blocks = ["wool", "stained_clay", "carpet"]; - -for (var i = 0, len = colorized_blocks.length; i < len; i++) { - var block = colorized_blocks[i], - data_value = blocks[block].white; - - for (var color in colors) { - blocks[block][color] = data_value + colors[color]; - } -}; - -/* - rainbow colors - a convenience - Color aliased properties that were a direct descendant of the blocks - object are no longer used to avoid confusion with carpet and stained - clay blocks. -*/ -blocks.rainbow = [blocks.wool.red, - blocks.wool.orange, - blocks.wool.yellow, - blocks.wool.lime, - blocks.wool.lightblue, - blocks.wool.blue, - blocks.wool.purple]; - - -exports.blocks = blocks; From 5d55e1ce73d83a9a5d63b4c8acf227ea5aeb7f2c Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Wed, 25 Dec 2013 13:39:53 +0000 Subject: [PATCH 043/456] removed --- src/main/javascript/plugins/drone/drone-exts.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/main/javascript/plugins/drone/drone-exts.js diff --git a/src/main/javascript/plugins/drone/drone-exts.js b/src/main/javascript/plugins/drone/drone-exts.js deleted file mode 100644 index e69de29..0000000 From ffbb97abf1f081f929ae3a06959d64061c07101f Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Wed, 25 Dec 2013 22:29:24 +0000 Subject: [PATCH 044/456] removing old mcp code --- mcp-deprecated/CommandScript.java | 43 ---- mcp-deprecated/IScriptCraft.java | 12 - mcp-deprecated/ScriptCraftEvaluator.java | 265 ---------------------- mcp-deprecated/ScriptCraftMCP.java | 109 --------- mcp-deprecated/ServerCommandManager.patch | 2 - mcp-deprecated/optional/GuiIngame.patch | 10 - 6 files changed, 441 deletions(-) delete mode 100644 mcp-deprecated/CommandScript.java delete mode 100644 mcp-deprecated/IScriptCraft.java delete mode 100644 mcp-deprecated/ScriptCraftEvaluator.java delete mode 100644 mcp-deprecated/ScriptCraftMCP.java delete mode 100644 mcp-deprecated/ServerCommandManager.patch delete mode 100644 mcp-deprecated/optional/GuiIngame.patch diff --git a/mcp-deprecated/CommandScript.java b/mcp-deprecated/CommandScript.java deleted file mode 100644 index 1775c0d..0000000 --- a/mcp-deprecated/CommandScript.java +++ /dev/null @@ -1,43 +0,0 @@ -package net.minecraft.src; -/** - * This mod lets you load and run javascript to build structures which - * would otherwise be tedious. Build road networks, rows of houses, - * factories and sky-scrapers. ScriptCraft takes building to a whole - * new level by making it easy to create javascript scripts that do - * the building for you. The following code creates a simple cottage - * at the crosshair location or the player's current location... - * - * load("./drone.js"); - * var drone = new Drone().chkpt('cornerstone'); - * drone.box0(48,7,2,6) // 4 walls - * .right(3).door() // a door front-center - * .left(2).box(102) // windows left and right of door - * .right(4).box(102) // - * .move('cornerstone').up(2).prism0(53,7,6); // a gable roof - * - */ - -public class CommandScript extends CommandBase -{ - ScriptCraftEvaluator evaluator = null; - - public String getCommandName() { return "js"; } - - public int getRequiredPermissionLevel() { return 0; } - - public void processCommand(ICommandSender par1ICommandSender, String[] args) - { - if (this.evaluator == null) - this.evaluator = new ScriptCraftEvaluator(new ScriptCraftMCP(this)); - - // Collect the arguments into a single string. - String s = ""; - for (int i=0; i < args.length; i++) { - s += args[i] + " "; - } - // Now evaluate the string we've colected. - this.evaluator.eval(s,par1ICommandSender); - - return; - } -} diff --git a/mcp-deprecated/IScriptCraft.java b/mcp-deprecated/IScriptCraft.java deleted file mode 100644 index 7d9de18..0000000 --- a/mcp-deprecated/IScriptCraft.java +++ /dev/null @@ -1,12 +0,0 @@ -package net.minecraft.src; - -public interface IScriptCraft -{ - public double[] getPlayerPos(); - public double[] getMousePos(); - public void putSign(String[] texts,int x, int y, int z, int block, int meta); - public void putBlock(int x, int y, int z, int blockId, int meta); - public String getBlock(int x, int y, int z); - public void notifyAdministrators(String message); - public void setInvoker(Object invoker); -} diff --git a/mcp-deprecated/ScriptCraftEvaluator.java b/mcp-deprecated/ScriptCraftEvaluator.java deleted file mode 100644 index cf810db..0000000 --- a/mcp-deprecated/ScriptCraftEvaluator.java +++ /dev/null @@ -1,265 +0,0 @@ -package net.minecraft.src; - -import org.mozilla.javascript.*; -import java.util.List; -import java.io.*; -import javax.swing.JFileChooser; - -public class ScriptCraftEvaluator -{ - protected static IScriptCraft sc = null; - protected Context ctx = null; - protected Scriptable scope = null; - - public static class MCScope extends ImporterTopLevel{ - public MCScope(Context ctx){ - super(ctx); - } - } - public ScriptCraftEvaluator(IScriptCraft scImpl) - { - ScriptCraftEvaluator.sc = scImpl; - this.ctx = Context.enter(); - ScriptableObject importer = new ScriptCraftEvaluator.MCScope(ctx); - this.scope = this.ctx.initStandardObjects(importer); - // - // for mcp debug only - //ctx.evaluateString(scope,"importPackage(net.minecraft.src)","",1,null); - // - String[] names = { - "print" - ,"load" - ,"help" - ,"getPlayerPos" - ,"getMousePos" - ,"putBlock" - ,"getBlock" - ,"putSign" - }; - importer.defineFunctionProperties(names, - ScriptCraftEvaluator.class, - ScriptableObject.DONTENUM); - } - /** - * So clients can add their own properties ... - * - * evaluator.getScope().defineProperty("jsVarName",javaObject); - */ - public ScriptableObject getScope() - { - return (ScriptableObject)this.scope; - } - - public Object eval(String javascript, Object invoker) - { - ScriptCraftEvaluator.sc.setInvoker(invoker); - ScriptCraftEvaluator.sc.notifyAdministrators("js> " + javascript); - Object result = null; - try - { - result = ctx.evaluateString(this.scope, javascript, "", 1, null); - } - catch(Exception e) - { - e.printStackTrace(System.err); - ScriptCraftEvaluator.sc.notifyAdministrators("Exception: " + e.getMessage()); - } - if (result != null) - { - ScriptCraftEvaluator.sc.notifyAdministrators(Context.toString(result)); - } - return result; - } - - /** - * Load a javascript source file and evaluate its contents. - */ - public static Object load(Context cx, Scriptable thisObj, Object[] args, Function funObj) - { - Object result = null; - - File scriptFile = null; - String filename = null; - - if (args.length == 0) - { - JFileChooser fc = new javax.swing.JFileChooser(); - int rc = fc.showOpenDialog(null); - if (rc ==JFileChooser.APPROVE_OPTION){ - scriptFile = fc.getSelectedFile(); - }else{ - return result; - } - }else{ - scriptFile = new File((String)args[0]); - } - - FileReader in = null; - try { - in = new FileReader(scriptFile); - } - catch (FileNotFoundException ex) - { - ex.printStackTrace(System.err); - ScriptCraftEvaluator.sc.notifyAdministrators( "Error - File not found " + args[0]); - Context.reportError("Couldn't open file \"" + scriptFile + "\"."); - return null; - } - filename = scriptFile.getAbsolutePath(); - System.out.println("ScripCraftEvaluator: filename=" + filename); - File parentFile = scriptFile.getParentFile(); - String filedir = null; - if (parentFile !=null){ - filedir = parentFile.getAbsolutePath(); - } - // - // setup the special script-context-only variables - // - ((ScriptableObject)thisObj).defineProperty("$SCRIPT",filename,ScriptableObject.DONTENUM); - ((ScriptableObject)thisObj).defineProperty("$SCRIPT_DIR",filedir==null?"":filedir,ScriptableObject.DONTENUM); - - try { - // Here we evalute the entire contents of the file as - // a script. Text is printed only if the print() function - // is called. - ScriptCraftEvaluator.sc.notifyAdministrators( "Loading " + filename); - result = cx.evaluateReader(thisObj, in, filename, 1, null); - ScriptCraftEvaluator.sc.notifyAdministrators( "Successfully loaded " + filename); - } - catch (WrappedException we) { - we.printStackTrace(System.err); - String wes = we.getWrappedException().toString(); - ScriptCraftEvaluator.sc.notifyAdministrators("WrappedException while loading " + filename + ": " + wes); - System.err.println(wes); - we.printStackTrace(); - } - catch (EvaluatorException ee) { - ee.printStackTrace(System.err); - System.err.println("js: " + ee.getMessage()); - ScriptCraftEvaluator.sc.notifyAdministrators( "EvaluatorException while loading " + filename + ": " + ee.getMessage()); - } - catch (JavaScriptException jse) { - jse.printStackTrace(System.err); - System.err.println("js: " + jse.getMessage()); - ScriptCraftEvaluator.sc.notifyAdministrators("JavascriptException while loading " + filename + ": " + jse.getMessage()); - } - catch (IOException ioe) { - ioe.printStackTrace(System.err); - System.err.println(ioe.toString()); - ScriptCraftEvaluator.sc.notifyAdministrators( "IOException while loading " + filename + ": " + ioe.getMessage()); - } - finally { - try { - in.close(); - } - catch (IOException ioe) { - System.err.println(ioe.toString()); - } - } - return result; - } - public static void help(Context cx, Scriptable thisObj, Object[] args, Function funObj) - { - String cwd = java.lang.System.getProperty("user.dir"); - String[] helpArgs = {"Current Working Directory: " + cwd, - "load('path-to-script.js')", - "load() (with no params) lets you choose a script file", - "getPlayerPos() returns player coords", - "getMousePos() returns mouse/crosshair coords", - "getBlock(x,y,z) returns the block and metadata e.g. '98' for a stone block or '98:2' for a mossy stone block", - "putBlock(x,y,z,blockId,meta) e.g. putBlock(100,2,50,44,2) puts a sandstone slab (44:2) at position 100,2,50. See http://www.minecraftinfo.com/idlist.htm for block ids" - }; - print(cx,thisObj,helpArgs,funObj); - } - - public static void print(Context cx, Scriptable thisObj,Object[] args, Function funObj) - { - for (int i=0; i < args.length; i++) { - if (i > 0){ - System.out.print(" "); - } - - // Convert the arbitrary JavaScript value into a string form. - String s = Context.toString(args[i]); - ScriptCraftEvaluator.sc.notifyAdministrators(s); - System.out.print(s); - } - System.out.println(); - } - public static double[] getPlayerPos(Context cx, Scriptable thisObj,Object[] args, Function funObj) - { - return ScriptCraftEvaluator.sc.getPlayerPos(); - } - public static double[] getMousePos(Context cx, Scriptable thisObj,Object[] args, Function funObj) - { - return ScriptCraftEvaluator.sc.getMousePos(); - } - public static void putBlock(Context cx, Scriptable thisObj,Object[] args, Function funObj) - { - int x; - int y; - int z; - int b; - int m; - - if (args.length == 2){ - double[] mousePos = ScriptCraftEvaluator.sc.getMousePos(); - if (mousePos != null){ - x = (int)mousePos[0]; - y = (int)mousePos[1]; - z = (int)mousePos[2]; - b = new Double(args[0].toString()).intValue(); - m = new Double(args[1].toString()).intValue(); - }else{ - return; - } - }else { - x = new Double(args[0].toString()).intValue(); - y = new Double(args[1].toString()).intValue(); - z = new Double(args[2].toString()).intValue(); - b = new Double(args[3].toString()).intValue(); - m = new Double(args[4].toString()).intValue(); - } - ScriptCraftEvaluator.sc.putBlock(x,y,z,b,m); - } - // - // gets the blockId and metadata at the given coords - // if no coords are provided then the mouse position is used instead. - // - public static String getBlock(Context cx, Scriptable thisObj,Object[] args, Function funObj){ - int x; - int y; - int z; - - if (args.length != 0){ - x = new Double(args[0].toString()).intValue(); - y = new Double(args[1].toString()).intValue(); - z = new Double(args[2].toString()).intValue(); - }else{ - double[] mousePos = ScriptCraftEvaluator.sc.getMousePos(); - if (mousePos != null){ - x = (int)mousePos[0]; - y = (int)mousePos[1]; - z = (int)mousePos[2]; - }else{ - return null; - } - } - return ScriptCraftEvaluator.sc.getBlock(x,y,z); - } - public static void putSign(Context cx, Scriptable thisObj,Object[] args, Function funObj){ - List jsArray = (List)args[0]; - - String[] texts = new String[4]; - for (int i = 0; i < jsArray.size() && i <= 3;i++){ - texts[i] = (String)jsArray.get(i); - } - int x = new Double(args[1].toString()).intValue(); - int y = new Double(args[2].toString()).intValue(); - int z = new Double(args[3].toString()).intValue(); - int b = new Double(args[4].toString()).intValue(); - int m = new Double(args[5].toString()).intValue(); - ScriptCraftEvaluator.sc.putSign(texts,x,y,z,b,m); - } - -} diff --git a/mcp-deprecated/ScriptCraftMCP.java b/mcp-deprecated/ScriptCraftMCP.java deleted file mode 100644 index 88b9429..0000000 --- a/mcp-deprecated/ScriptCraftMCP.java +++ /dev/null @@ -1,109 +0,0 @@ -package net.minecraft.src; -import net.minecraft.client.*; -/** - * An implementation of the IScriptCraft interface for - * Minecraft Coder Pack-style install - * - */ -public class ScriptCraftMCP implements IScriptCraft -{ - protected CommandBase command = null; - - public ScriptCraftMCP(CommandBase mcpCommand){ - this.command = mcpCommand; - } - - protected ICommandSender invoker; - // - // following code depends on MCP - // - public double[] getPlayerPos() - { - double[] result = new double[4]; - if (this.invoker instanceof EntityPlayer){ - EntityPlayer player = (EntityPlayer)this.invoker; - result[0] = player.posX; - result[1] = player.posY; - result[2] = player.posZ; - result[3] = player.rotationYaw; - return result; - } - return null; - } - public double[] getMousePos() - { - Minecraft mc = net.minecraft.client.Minecraft.getMinecraft(); - MovingObjectPosition omo = mc.objectMouseOver; - if (omo == null){ - return null; - } - double[] result = new double[4]; - result[0] = omo.blockX; - result[1] = omo.blockY; - result[2] = omo.blockZ; - return result; - } - public void putSign(String[] texts, int x, int y, int z, int block, int meta) - { - this.putBlock(x,y,z,block,meta); - EntityPlayer player = (EntityPlayer)this.invoker; - World world = player.worldObj; - TileEntitySign sign = (TileEntitySign)world.getBlockTileEntity(x,y,z); - for (int i=0 ; i < texts.length && i <= 3;i++){ - String text = texts[i]; - if (text != null){ - if (text.length() > 15){ - text = text.substring(0,15); - } - sign.signText[i] = text; - } - } - sign.onInventoryChanged(); - world.markBlockForUpdate(x,y,z); - } - - public void putBlock(int x, int y, int z, int blockId, int meta) - { - World world = null; - if (this.invoker instanceof EntityPlayer) - { - world = ((EntityPlayer)(this.invoker)).worldObj; - } - else if (this.invoker instanceof TileEntity) - { - world = ((TileEntity)(this.invoker)).getWorldObj(); - } - world.setBlockAndMetadata(x,y,z,blockId,meta); - - switch (blockId) - { - case 6: - ((BlockSapling)Block.sapling).growTree(world,x,y,z,world.rand); - break; - } - } - // - // returns the block id and metadata at a given location in the world - // e.g. returns "98" for a stone block or "98:2" for a mossy stone block. - // - public String getBlock(int x, int y, int z) - { - EntityPlayer player = (EntityPlayer)this.invoker; - World world = player.worldObj; - int blockId = world.getBlockId(x,y,z); - int metadata = world.getBlockMetadata(x,y,z); - if (metadata !=0){ - return "" + blockId + ":" + metadata; - }else{ - return "" + blockId; - } - } - public void notifyAdministrators(String message) - { - this.command.notifyAdmins(this.invoker,message,(Object[])null); - } - public void setInvoker(Object invoker) - { - this.invoker = (ICommandSender)invoker; - } -} diff --git a/mcp-deprecated/ServerCommandManager.patch b/mcp-deprecated/ServerCommandManager.patch deleted file mode 100644 index 231096f..0000000 --- a/mcp-deprecated/ServerCommandManager.patch +++ /dev/null @@ -1,2 +0,0 @@ -9a10 -> this.registerCommand(new CommandScript()); diff --git a/mcp-deprecated/optional/GuiIngame.patch b/mcp-deprecated/optional/GuiIngame.patch deleted file mode 100644 index 2ef70ac..0000000 --- a/mcp-deprecated/optional/GuiIngame.patch +++ /dev/null @@ -1,10 +0,0 @@ -466a467,475 -> // -> // wph 20121231 show the block id and metadata for in-focus block -> // -> MovingObjectPosition omo = this.mc.objectMouseOver; -> if (omo != null){ -> int bi = this.mc.theWorld.getBlockId(omo.blockX,omo.blockY,omo.blockZ); -> int md = this.mc.theWorld.getBlockMetadata(omo.blockX,omo.blockY,omo.blockZ); -> this.drawString(var8,"Mouse:" + omo.blockX + ", " + omo.blockY + ", " + omo.blockZ + " block data value: " + bi + ":" + md,2,112,14737632); -> } From c6d123f36a20a107a7035df49510768b09d7b481 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Wed, 25 Dec 2013 22:32:57 +0000 Subject: [PATCH 045/456] src/main/javascript/plugins/commando Fixing issue #83 --- docs/API-Reference.md | 77 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index da7ec19..f167618 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -1673,6 +1673,83 @@ To construct a spiral staircase 5 floors high made of oak... `/js arrows.explosive('player23')` makes player23's arrows explosive. +### Commando Plugin + +#### Description + +commando is a plugin which can be used to add completely new commands +to Minecraft. Normally ScriptCraft only allows for provision of new +commands as extensions to the jsp command. For example, to create a +new simple command for use by all players... + + /js command('hi', function(){ echo('Hi ' + self.name); }); + +... then players can use this command by typing... + + /jsp hi + +... A couple of ScriptCraft users have asked for the ability to take +this a step further and allow the global command namespace to be +populated so that when a developer creates a new command using the +'command' function, then the command is added to the global command +namespace so that players can use it simply like this... + + /hi + +... There are good reasons why ScriptCraft's core `command()` function +does not do this. Polluting the global namespace with commands would +make ScriptCraft a bad citizen in that Plugins should be able to work +together in the same server and - as much as possible - not step on +each others' toes. The CraftBukkit team have very good reasons for +forcing Plugins to declare their commands in the plugin.yml +configuration file. It makes approving plugins easier and ensures that +craftbukkit plugins behave well together. While it is possible to +override other plugins' commands, the CraftBukkit team do not +recommend this. However, as ScriptCraft users have suggested, it +should be at the discretion of server administrators and plugin +authors as to when overriding or adding new commands to the global +namespace is good. + +So this is where `commando()` comes in. It uses the exact same +signature as the core `command()` function but will also make the +command accessible without the `jsp` prefix so instead of having to +type `/jsp hi` for the above command example, players simply type +`/hi` . This functionality is provided as a plugin rather than as part +of the ScriptCraft core. + +#### Example hi-command.js + + var commando = require('../commando'); + commando('hi', function(){ + echo('Hi ' + self.name); + }); + +...Displays a greeting to any player who issues the `/hi` command. + +#### Example - timeofday-command.js + + var times = {Dawn: 0, Midday: 6000, Dusk: 12000, Midnight:18000}; + commando('timeofday', function(params){ + self.location.world.setTime(times[params[0]]); + } + ['Dawn','Midday','Dusk','Midnight']); + +... changes the time of day using a new `/timeofday` command (options are Dawn, Midday, Dusk, Midnight) + +#### Caveats + +Since commands registered using commando are really just appendages to +the `/jsp` command and are not actually registered globally (it just +looks like that to the player), you won't be able to avail of tab +completion for the command itself or its parameters (unless you go the +traditional route of adding the `jsp` prefix). This plugin uses the +[PlayerCommandPreprocessEvent][pcppevt] which allows plugins to +intercepts all commands and inject their own commands instead. If +anyone reading this knows of a better way to programmatically add new +global commands for a plugin, please let me know. + +[pcppevt]: http://jd.bukkit.org/dev/apidocs/org/bukkit/event/player/PlayerCommandPreprocessEvent.html + Classroom Module ================ The `classroom` object contains a couple of utility functions for use From c15b0c9ece002aae0f435eff47c306ff90e00ca8 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Wed, 25 Dec 2013 22:35:00 +0000 Subject: [PATCH 046/456] added commando module --- docs/release-notes.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 34144da..e229274 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,3 +1,6 @@ +# 2013 12 25 + +Added the 'commando' module. # 2013 12 24 From 6ab381420ddd8c34f65cabae0b2e4197b75b520a Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Thu, 26 Dec 2013 00:22:24 +0000 Subject: [PATCH 047/456] new commando plugin --- .../plugins/commando/commando-test.js | 14 +++ .../javascript/plugins/commando/commando.js | 94 +++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 src/main/javascript/plugins/commando/commando-test.js create mode 100644 src/main/javascript/plugins/commando/commando.js diff --git a/src/main/javascript/plugins/commando/commando-test.js b/src/main/javascript/plugins/commando/commando-test.js new file mode 100644 index 0000000..0f17784 --- /dev/null +++ b/src/main/javascript/plugins/commando/commando-test.js @@ -0,0 +1,14 @@ +/* + A test of the commando plugin. + Adds a new `/scriptcrafttimeofday` command with 4 possible options: Dawn, Midday, Dusk, Midnight +*/ +var commando = require('./commando').commando; +var times = { + Dawn: 0, + Midday: 6000, + Dusk: 12000, + Midnight: 18000 +}; +commando('scriptcrafttimeofday',function(params){ + self.location.world.setTime(times[params[0]]); +},['Dawn','Midday','Dusk','Midnight']); diff --git a/src/main/javascript/plugins/commando/commando.js b/src/main/javascript/plugins/commando/commando.js new file mode 100644 index 0000000..b9b43e8 --- /dev/null +++ b/src/main/javascript/plugins/commando/commando.js @@ -0,0 +1,94 @@ +/************************************************************************* +### Commando Plugin + +#### Description + +commando is a plugin which can be used to add completely new commands +to Minecraft. Normally ScriptCraft only allows for provision of new +commands as extensions to the jsp command. For example, to create a +new simple command for use by all players... + + /js command('hi', function(){ echo('Hi ' + self.name); }); + +... then players can use this command by typing... + + /jsp hi + +... A couple of ScriptCraft users have asked for the ability to take +this a step further and allow the global command namespace to be +populated so that when a developer creates a new command using the +'command' function, then the command is added to the global command +namespace so that players can use it simply like this... + + /hi + +... There are good reasons why ScriptCraft's core `command()` function +does not do this. Polluting the global namespace with commands would +make ScriptCraft a bad citizen in that Plugins should be able to work +together in the same server and - as much as possible - not step on +each others' toes. The CraftBukkit team have very good reasons for +forcing Plugins to declare their commands in the plugin.yml +configuration file. It makes approving plugins easier and ensures that +craftbukkit plugins behave well together. While it is possible to +override other plugins' commands, the CraftBukkit team do not +recommend this. However, as ScriptCraft users have suggested, it +should be at the discretion of server administrators and plugin +authors as to when overriding or adding new commands to the global +namespace is good. + +So this is where `commando()` comes in. It uses the exact same +signature as the core `command()` function but will also make the +command accessible without the `jsp` prefix so instead of having to +type `/jsp hi` for the above command example, players simply type +`/hi` . This functionality is provided as a plugin rather than as part +of the ScriptCraft core. + +#### Example hi-command.js + + var commando = require('../commando'); + commando('hi', function(){ + echo('Hi ' + self.name); + }); + +...Displays a greeting to any player who issues the `/hi` command. + +#### Example - timeofday-command.js + + var times = {Dawn: 0, Midday: 6000, Dusk: 12000, Midnight:18000}; + commando('timeofday', function(params){ + self.location.world.setTime(times[params[0]]); + } + ['Dawn','Midday','Dusk','Midnight']); + +... changes the time of day using a new `/timeofday` command (options are Dawn, Midday, Dusk, Midnight) + +#### Caveats + +Since commands registered using commando are really just appendages to +the `/jsp` command and are not actually registered globally (it just +looks like that to the player), you won't be able to avail of tab +completion for the command itself or its parameters (unless you go the +traditional route of adding the `jsp` prefix). This plugin uses the +[PlayerCommandPreprocessEvent][pcppevt] which allows plugins to +intercepts all commands and inject their own commands instead. If +anyone reading this knows of a better way to programmatically add new +global commands for a plugin, please let me know. + +[pcppevt]: http://jd.bukkit.org/dev/apidocs/org/bukkit/event/player/PlayerCommandPreprocessEvent.html + +***/ +var events = require('events'); +var commands = {}; +exports.commando = function(name, func, options, intercepts){ + var result = command(name, func, options, intercepts); + commands[name] = result; + return result; +}; + +events.on('player.PlayerCommandPreprocessEvent', function(l,e){ + var msg = "" + e.message; + var command = msg.match(/^\/([^\s]+)/)[1]; + if (commands[command]){ + e.message = "/jsp " + msg.substring(1); + } +}); From 509705487af008f2f3756631750f9fe54d44f8d6 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Thu, 26 Dec 2013 15:38:24 +0000 Subject: [PATCH 048/456] made 'events' global --- docs/API-Reference.md | 227 ++++++++++++++---- docs/release-notes.md | 7 + .../scriptcraft/ScriptCraftPlugin.java | 7 +- src/main/javascript/lib/require.js | 54 ++--- src/main/javascript/lib/scriptcraft.js | 6 +- src/main/javascript/modules/signs/menu.js | 10 +- src/main/javascript/plugins/arrows.js | 33 +-- src/main/javascript/plugins/chat/color.js | 2 - .../javascript/plugins/classroom/classroom.js | 1 - .../javascript/plugins/commando/commando.js | 2 +- .../plugins/minigames/NumberGuess.js | 16 +- .../plugins/minigames/SnowBallFight.js | 67 +++--- 12 files changed, 290 insertions(+), 142 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index f167618..de1a3be 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -1653,26 +1653,147 @@ To construct a spiral staircase 5 floors high made of oak... spiral_stairs('oak', 5); - -## The arrows mod adds fancy arrows to the game. - -### Usage: +# Arrows Module - /js var arrows = require('./arrows/arrows') +## Description +The arrows mod adds fancy arrows to the game. Arrows which... - * `/js arrows.sign()` turns a targeted sign into a Arrows menu - * `/js arrows.normal()` sets arrow type to normal. - * `/js arrows.explosive()` - makes arrows explode. + * Launch fireworks. + * Explode on impact. + * Force Lightning to strike where they land. + * Teleport the player to the landing spot. + * Spawn Trees at the landing spot. + +## Usage: + + * `/js arrows.firework()` - A firework launches where the the arrow lands. + * `/js arrows.lightning()` - lightning strikes where the arrow lands. * `/js arrows.teleport()` - makes player teleport to where arrow has landed. * `/js arrows.flourish()` - makes a tree grow where the arrow lands. - * `/js arrows.lightning()` - lightning strikes where the arrow lands. - * `/js arrows.firework()` - A firework launches where the the arrow lands. + * `/js arrows.explosive()` - makes arrows explode. + * `/js arrows.normal()` sets arrow type to normal. + * `/js arrows.sign()` turns a targeted sign into a Arrows menu - All of the above functions can take an optional player object or name as - a parameter. E.g. - - `/js arrows.explosive('player23')` makes player23's arrows explosive. +All of the above functions can take an optional player object or name as +a parameter. For example: `/js arrows.explosive('player23')` makes player23's arrows explosive. +# Commando Plugin + +## Description + +commando is a plugin which can be used to add completely new commands +to Minecraft. Normally ScriptCraft only allows for provision of new +commands as extensions to the jsp command. For example, to create a +new simple command for use by all players... + + /js command('hi', function(){ echo('Hi ' + self.name); }); + +... then players can use this command by typing... + + /jsp hi + +... A couple of ScriptCraft users have asked for the ability to take +this a step further and allow the global command namespace to be +populated so that when a developer creates a new command using the +'command' function, then the command is added to the global command +namespace so that players can use it simply like this... + + /hi + +... There are good reasons why ScriptCraft's core `command()` function +does not do this. Polluting the global namespace with commands would +make ScriptCraft a bad citizen in that Plugins should be able to work +together in the same server and - as much as possible - not step on +each others' toes. The CraftBukkit team have very good reasons for +forcing Plugins to declare their commands in the plugin.yml +configuration file. It makes approving plugins easier and ensures that +craftbukkit plugins behave well together. While it is possible to +override other plugins' commands, the CraftBukkit team do not +recommend this. However, as ScriptCraft users have suggested, it +should be at the discretion of server administrators as to when +overriding or adding new commands to the global namespace is good. + +So this is where `commando()` comes in. It uses the exact same +signature as the core `command()` function but will also make the +command accessible without the `jsp` prefix so instead of having to +type `/jsp hi` for the above command example, players simply type +`/hi` . This functionality is provided as a plugin rather than as part +of the ScriptCraft core. + +## Example hi-command.js + + var commando = require('../commando'); + commando('hi', function(){ + echo('Hi ' + self.name); + }); + +...Displays a greeting to any player who issues the `/hi` command. + +## Example - timeofday-command.js + + var times = {Dawn: 0, Midday: 6000, Dusk: 12000, Midnight:18000}; + commando('timeofday', function(params){ + self.location.world.setTime(times[params[0]]); + }, + ['Dawn','Midday','Dusk','Midnight']); + +... changes the time of day using a new `/timeofday` command (options are Dawn, Midday, Dusk, Midnight) + +## Caveats + +Since commands registered using commando are really just appendages to +the `/jsp` command and are not actually registered globally (it just +looks like that to the player), you won't be able to avail of tab +completion for the command itself or its parameters (unless you go the +traditional route of adding the `jsp` prefix). This plugin uses the +[PlayerCommandPreprocessEvent][pcppevt] which allows plugins to +intercepts all commands and inject their own commands instead. If +anyone reading this knows of a better way to programmatically add new +global commands for a plugin, please let me know. + +[pcppevt]: http://jd.bukkit.org/dev/apidocs/org/bukkit/event/player/PlayerCommandPreprocessEvent.html + +Classroom Module +================ +The `classroom` object contains a couple of utility functions for use +in a classroom setting. The goal of these functions is to make it +easier for tutors to facilitate ScriptCraft for use by students in a +classroom environment. Although granting ScriptCraft access to +students on a shared server is potentially risky (Students can +potentially abuse it), it is slighlty less risky than granting +operator privileges to each student. (Enterprising students will +quickly realise how to grant themselves and others operator privileges +once they have access to ScriptCraft). + +The goal of this module is not so much to enforce restrictions +(security or otherwise) but to make it easier for tutors to setup a shared server +so students can learn Javascript. + +classroom.allowScripting() function +=================================== +Allow or disallow anyone who connects to the server (or is already +connected) to use ScriptCraft. This function is preferable to granting 'ops' privileges +to every student in a Minecraft classroom environment. + +Parameters +---------- + + * canScript : true or false + +Example +------- +To allow all players (and any players who connect to the server) to +use the `js` and `jsp` commands... + + /js classroom.allowScripting(true) + +To disallow scripting (and prevent players who join the server from using the commands)... + + /js classroom.allowScripting(false) + +Only ops users can run the classroom.allowScripting() function - this is so that students +don't try to bar themselves and each other from scripting. + ### Commando Plugin #### Description @@ -1750,53 +1871,59 @@ global commands for a plugin, please let me know. [pcppevt]: http://jd.bukkit.org/dev/apidocs/org/bukkit/event/player/PlayerCommandPreprocessEvent.html -Classroom Module -================ -The `classroom` object contains a couple of utility functions for use -in a classroom setting. The goal of these functions is to make it -easier for tutors to facilitate ScriptCraft for use by students in a -classroom environment. Although granting ScriptCraft access to -students on a shared server is potentially risky (Students can -potentially abuse it), it is slighlty less risky than granting -operator privileges to each student. (Enterprising students will -quickly realise how to grant themselves and others operator privileges -once they have access to ScriptCraft). +# SnowballFight mini-game -The goal of this module is not so much to enforce restrictions -(security or otherwise) but to make it easier for tutors to setup a shared server -so students can learn Javascript. +## Description -classroom.allowScripting() function -=================================== -Allow or disallow anyone who connects to the server (or is already -connected) to use ScriptCraft. This function is preferable to granting 'ops' privileges -to every student in a Minecraft classroom environment. +This is a rough and ready prototype of a simple multi-player +shoot-em-up. To start a game with all players playing against one another... -Parameters ----------- + /js new Game_SnowballFight(60).start(); - * canScript : true or false +... this obviously works best if all of the players are in close +proximity within the same game world. Alternatively you can have team +matches... -Example -------- -To allow all players (and any players who connect to the server) to -use the `js` and `jsp` commands... - /js classroom.allowScripting(true) + /js var redTeam = ['','',...etc] + /js var blueTeam = ['',',...etc] + /js var greenTeam = ['',',...etc] + /js new Game_SnowballFight(60, {red: redTeam,blue: blueTeam,green: greenTeam}).start(); -To disallow scripting (and prevent players who join the server from using the commands)... +Or you can just have specific players play against each other... - /js classroom.allowScripting(false) + /js new Game_SnowballFight(60, ['player1','player2','player3']).start(); -Only ops users can run the classroom.allowScripting() function - this is so that students -don't try to bar themselves and each other from scripting. +(where 'player1' etc are the names of actual players) + +You specify the teams in the game as an object where each property's +name is a team name and each property's value is the list of players +on that team. You specify the duration of the game (in seconds) You +kick off the game with the start() method. I need to work on a +better in-game mechanism for players to choose teams and start the +game but this will do for now. -## Minigame: Guess the number +When the game starts, each player is put in survival mode and given +snowballs. The aim of the game is to hit players on opposing teams. If +you hit a player on your own team, you lose a point. + +At the end of the game the scores for each team are broadcast and each +player returns to their previous mode of play (creative or +survival). Create a small arena with a couple of small buildings for +cover to make the game more fun. + +# NumberGuess mini-game: -### Example +## Description +This is a very simple number guessing game. Minecraft will ask you to +guess a number between 1 and 10 and you will tell you if you're too +hight or too low when you guess wrong. The purpose of this mini-game +code is to demonstrate use of Bukkit's Conversation API. + +## Example /js Game_NumberGuess.start() -... Begins a number-guessing game where you must guess the number (between 1 and 10) chosen by the computer. - - A basic number-guessing game that uses the Bukkit Conversation API. +Once the game begins, guess a number by typing the `/` character +followed by a number between 1 and 10. + diff --git a/docs/release-notes.md b/docs/release-notes.md index e229274..b4286ec 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,3 +1,10 @@ +# 2013 12 26 + +Made the `events` variable global because it is use by modules and +plugins. This means there is no longer any need to explicitly +`require('events')` since `events` is now a free variable in the +global namespace. + # 2013 12 25 Added the 'commando' module. diff --git a/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java b/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java index 4b7d3c4..64bac6a 100644 --- a/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java +++ b/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java @@ -156,10 +156,10 @@ public class ScriptCraftPlugin extends JavaPlugin implements Listener this.engine.put("__cmdArgs",args); result = true; } else if (cmd.getName().equalsIgnoreCase("coffee")) { - for (int i = 0;i < args.length; i++) + for (int i = 0;i < args.length; i++) javascriptCode += args[i] + " "; - javascriptCode = "eval(CoffeeScript.compile(\""+javascriptCode+"\", {bare: true}))"; - result = true; + javascriptCode = "eval(CoffeeScript.compile(\""+javascriptCode+"\", {bare: true}))"; + result = true; } if (result){ @@ -181,5 +181,4 @@ public class ScriptCraftPlugin extends JavaPlugin implements Listener } return result; } - } diff --git a/src/main/javascript/lib/require.js b/src/main/javascript/lib/require.js index fd08ee4..74648b4 100644 --- a/src/main/javascript/lib/require.js +++ b/src/main/javascript/lib/require.js @@ -86,6 +86,18 @@ module specification, the '.js' suffix is optional. } }; + var fileExists = function(file) { + if (file.isDirectory()){ + return readModuleFromDirectory(file); + }else { + return file; + } + }; + + var _canonize = function(file){ + return "" + file.canonicalPath.replaceAll("\\\\","/"); + }; + var resolveModuleToFile = function(moduleName, parentDir) { /********************************************************************** ## When resolving module names to file paths, ScriptCraft uses the following rules... @@ -119,18 +131,8 @@ module specification, the '.js' suffix is optional. 3.2 if no package.json file exists then look for an index.js file in the directory ***/ - var file = new File(moduleName); - var fileExists = function(file) { - - if (file.isDirectory()){ - return readModuleFromDirectory(file); - }else { - return file; - } - }; - if (file.exists()){ return fileExists(file); } @@ -156,11 +158,7 @@ module specification, the '.js' suffix is optional. // it's of the form ./path file = new File(parentDir, moduleName); if (file.exists()){ - if (file.isDirectory()){ - return readModuleFromDirectory(file); - }else { - return file; - } + return fileExists(file); }else { // try appending a .js to the end @@ -169,7 +167,6 @@ module specification, the '.js' suffix is optional. if (file.exists()) return file; else{ - file = new File(pathWithJSExt); if (file.exists()) return file; @@ -186,10 +183,6 @@ module specification, the '.js' suffix is optional. var _require = function(parentFile, path) { - var _canonize = function(file){ - return "" + file.canonicalPath.replaceAll("\\\\","/"); - }; - var file = resolveModuleToFile(path, parentFile); if (!file){ throw new Error("require('" + path + "'," + parentFile.canonicalPath + ") failed"); @@ -215,7 +208,8 @@ module specification, the '.js' suffix is optional. moduleInfo = { loaded: false, id: canonizedFilename, - exports: {} + exports: {}, + require: _requireClosure(file.parentFile) }; var tail = "})"; code = head + code + tail; @@ -228,14 +222,18 @@ module specification, the '.js' suffix is optional. logger.severe("Error:" + e + " while evaluating module " + canonizedFilename); throw e; } - var __dirname = file.parentFile.canonicalPath; + var __dirname = "" + file.parentFile.canonicalPath; + var parameters = [ + moduleInfo.exports, /* exports */ + moduleInfo, /* module */ + moduleInfo.require, /* require */ + canonizedFilename, /* __filename */ + __dirname /* __dirname */ + ]; try { - compiledWrapper.apply(moduleInfo.exports, - [moduleInfo.exports, - moduleInfo, - _requireClosure(file.parentFile), - canonizedFilename, - "" + __dirname]); + compiledWrapper + .apply(moduleInfo.exports, /* this */ + parameters); } catch (e){ logger.severe("Error:" + e + " while executing module " + canonizedFilename); throw e; diff --git a/src/main/javascript/lib/scriptcraft.js b/src/main/javascript/lib/scriptcraft.js index 143fd0d..d006db6 100644 --- a/src/main/javascript/lib/scriptcraft.js +++ b/src/main/javascript/lib/scriptcraft.js @@ -757,7 +757,6 @@ See [issue #69][issue69] for more information. global.plugin = plugins.plugin; global.command = plugins.command; global.save = plugins.save; - plugins.autoload(jsPluginsRootDir); var events = require('events'); events.on('server.PluginDisableEvent',function(l,e){ @@ -767,6 +766,11 @@ See [issue #69][issue69] for more information. _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; + + plugins.autoload(jsPluginsRootDir); + }()); diff --git a/src/main/javascript/modules/signs/menu.js b/src/main/javascript/modules/signs/menu.js index 8f9bbce..0d08b86 100644 --- a/src/main/javascript/modules/signs/menu.js +++ b/src/main/javascript/modules/signs/menu.js @@ -1,6 +1,6 @@ var _utils = require('utils'); var stringExt = require('utils/string-exts'); -var events = require('events'); +var _store = {}; /* Define the signs module - signs are persistent (that is - a menu sign will still be a menu after the @@ -14,7 +14,8 @@ var signs = plugin("signs", { /* String */ label, /* Array */ options, /* Function */ onInteract, - /* Number */ defaultSelection ){} + /* Number */ defaultSelection ){}, + store: _store },true); module.exports = signs; @@ -37,9 +38,8 @@ var _redrawMenuSign = function(p_sign,p_selectedIndex,p_displayOptions) } p_sign.update(true); }; + var _updaters = {}; -var _store = {}; -signs.store = _store; /* construct an interactive menu to be subsequently attached to one or more Signs. @@ -169,7 +169,7 @@ signs.menu = function( // // update it every time player interacts with it. // -events.on("player.PlayerInteractEvent",function(listener, event) { +events.on('player.PlayerInteractEvent',function(listener, event) { /* look up our list of menu signs. If there's a matching location and there's a sign, then update it. diff --git a/src/main/javascript/plugins/arrows.js b/src/main/javascript/plugins/arrows.js index 534fa04..ef2ed6f 100644 --- a/src/main/javascript/plugins/arrows.js +++ b/src/main/javascript/plugins/arrows.js @@ -1,28 +1,31 @@ /************************************************************************* - -## The arrows mod adds fancy arrows to the game. - -### Usage: +# Arrows Module - /js var arrows = require('./arrows/arrows') +## Description +The arrows mod adds fancy arrows to the game. Arrows which... - * `/js arrows.sign()` turns a targeted sign into a Arrows menu - * `/js arrows.normal()` sets arrow type to normal. - * `/js arrows.explosive()` - makes arrows explode. + * Launch fireworks. + * Explode on impact. + * Force Lightning to strike where they land. + * Teleport the player to the landing spot. + * Spawn Trees at the landing spot. + +## Usage: + + * `/js arrows.firework()` - A firework launches where the the arrow lands. + * `/js arrows.lightning()` - lightning strikes where the arrow lands. * `/js arrows.teleport()` - makes player teleport to where arrow has landed. * `/js arrows.flourish()` - makes a tree grow where the arrow lands. - * `/js arrows.lightning()` - lightning strikes where the arrow lands. - * `/js arrows.firework()` - A firework launches where the the arrow lands. + * `/js arrows.explosive()` - makes arrows explode. + * `/js arrows.normal()` sets arrow type to normal. + * `/js arrows.sign()` turns a targeted sign into a Arrows menu - All of the above functions can take an optional player object or name as - a parameter. E.g. - - `/js arrows.explosive('player23')` makes player23's arrows explosive. +All of the above functions can take an optional player object or name as +a parameter. For example: `/js arrows.explosive('player23')` makes player23's arrows explosive. ***/ var signs = require('signs'); -var events = require('events'); var fireworks = require('fireworks'); var _store = {players: {}}; diff --git a/src/main/javascript/plugins/chat/color.js b/src/main/javascript/plugins/chat/color.js index 76b5e3b..62493e7 100644 --- a/src/main/javascript/plugins/chat/color.js +++ b/src/main/javascript/plugins/chat/color.js @@ -1,8 +1,6 @@ /* TODO: Document this module */ -var events = require('events'); - var _store = {players: {}}; /* declare a new javascript plugin for changing chat text color diff --git a/src/main/javascript/plugins/classroom/classroom.js b/src/main/javascript/plugins/classroom/classroom.js index 42ef205..13f6d1e 100644 --- a/src/main/javascript/plugins/classroom/classroom.js +++ b/src/main/javascript/plugins/classroom/classroom.js @@ -1,5 +1,4 @@ var utils = require('utils'); -var events = require('events'); /************************************************************************ Classroom Module diff --git a/src/main/javascript/plugins/commando/commando.js b/src/main/javascript/plugins/commando/commando.js index b9b43e8..e0f620c 100644 --- a/src/main/javascript/plugins/commando/commando.js +++ b/src/main/javascript/plugins/commando/commando.js @@ -77,7 +77,7 @@ global commands for a plugin, please let me know. [pcppevt]: http://jd.bukkit.org/dev/apidocs/org/bukkit/event/player/PlayerCommandPreprocessEvent.html ***/ -var events = require('events'); + var commands = {}; exports.commando = function(name, func, options, intercepts){ var result = command(name, func, options, intercepts); diff --git a/src/main/javascript/plugins/minigames/NumberGuess.js b/src/main/javascript/plugins/minigames/NumberGuess.js index 542086c..d2305cb 100644 --- a/src/main/javascript/plugins/minigames/NumberGuess.js +++ b/src/main/javascript/plugins/minigames/NumberGuess.js @@ -1,13 +1,19 @@ /************************************************************************* -## Minigame: Guess the number +# NumberGuess mini-game: -### Example +## Description +This is a very simple number guessing game. Minecraft will ask you to +guess a number between 1 and 10 and you will tell you if you're too +hight or too low when you guess wrong. The purpose of this mini-game +code is to demonstrate use of Bukkit's Conversation API. + +## Example /js Game_NumberGuess.start() -... Begins a number-guessing game where you must guess the number (between 1 and 10) chosen by the computer. - - A basic number-guessing game that uses the Bukkit Conversation API. +Once the game begins, guess a number by typing the `/` character +followed by a number between 1 and 10. + ***/ exports.Game_NumberGuess = { start: function() { diff --git a/src/main/javascript/plugins/minigames/SnowBallFight.js b/src/main/javascript/plugins/minigames/SnowBallFight.js index 0939ab9..9a95c2e 100644 --- a/src/main/javascript/plugins/minigames/SnowBallFight.js +++ b/src/main/javascript/plugins/minigames/SnowBallFight.js @@ -1,38 +1,47 @@ -var events = require('events'); +/************************************************************************* +# SnowballFight mini-game -/* - OK - this is a rough and ready prototype of a simple multi-player shoot-em-up. - Get a bunch of players in close proximity and issue the following commands... +## Description - /js var redTeam = ['','',...etc] - /js var blueTeam = ['',',...etc] - /js var greenTeam = ['',',...etc] - /js new Game_SnowBallFight({red: redTeam,blue: blueTeam,green: greenTeam},60).start(); +This is a rough and ready prototype of a simple multi-player +shoot-em-up. To start a game with all players playing against one another... - Alternatively you can just have all players play against each other... + /js new Game_SnowballFight(60).start(); - /js new SnowBallFight(['player1','player2','player3'],60).start(); +... this obviously works best if all of the players are in close +proximity within the same game world. Alternatively you can have team +matches... - (where etc are the names of actual players) + + /js var redTeam = ['','',...etc] + /js var blueTeam = ['',',...etc] + /js var greenTeam = ['',',...etc] + /js new Game_SnowballFight(60, {red: redTeam,blue: blueTeam,green: greenTeam}).start(); + +Or you can just have specific players play against each other... + + /js new Game_SnowballFight(60, ['player1','player2','player3']).start(); + +(where 'player1' etc are the names of actual players) - You specify the teams in the game as an object where each property's name is a team name and - each property's value is the list of players on that team. - You specify the duration of the game (in seconds) - You kick off the game with the start() method. - I need to work on a better in-game mechanism for players to choose teams and start the game - but this will do for now. +You specify the teams in the game as an object where each property's +name is a team name and each property's value is the list of players +on that team. You specify the duration of the game (in seconds) You +kick off the game with the start() method. I need to work on a +better in-game mechanism for players to choose teams and start the +game but this will do for now. - When the game starts, each player is put in survival mode and given snowballs. The aim of the - game is to hit players on opposing teams. If you hit a player on your own team, you lose a point. +When the game starts, each player is put in survival mode and given +snowballs. The aim of the game is to hit players on opposing teams. If +you hit a player on your own team, you lose a point. - At the end of the game the scores for each team are broadcast. Create a small arena - with a couple of small buildings for cover to make the game more fun :-) +At the end of the game the scores for each team are broadcast and each +player returns to their previous mode of play (creative or +survival). Create a small arena with a couple of small buildings for +cover to make the game more fun. -*/ +***/ -/* - setup game -*/ var _startGame = function(gameState){ // don't let game start if already in progress (wait for game to finish) if (gameState.inProgress){ @@ -104,7 +113,7 @@ var _getTeam = function(player,pteams) { /* construct a new game */ -var _constructor = function(duration, teams) { +var createGame = function(duration, teams) { var _snowBalls = new org.bukkit.inventory.ItemStack(org.bukkit.Material.SNOW_BALL, 64); @@ -160,7 +169,7 @@ var _constructor = function(duration, teams) { return { start: function() { _startGame(_gameState); - _gameState.listener = events.on("entity.EntityDamageByEntityEvent",_onSnowballHit); + _gameState.listener = events.on('entity.EntityDamageByEntityEvent',_onSnowballHit); new java.lang.Thread(function(){ while (_gameState.duration--) java.lang.Thread.sleep(1000); // sleep 1,000 millisecs (1 second) @@ -169,8 +178,6 @@ var _constructor = function(duration, teams) { } }; }; -var SnowBallFight = _constructor; - -exports.Game_SnowBallFight = SnowBallFight; +exports.Game_SnowballFight = createGame; From f9af3a5ce449994b46c573cb05a0aa8aafade2ea Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Thu, 26 Dec 2013 15:39:33 +0000 Subject: [PATCH 049/456] renamed SnowBall to Snowball --- .../plugins/minigames/SnowballFight.js | 183 ++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 src/main/javascript/plugins/minigames/SnowballFight.js diff --git a/src/main/javascript/plugins/minigames/SnowballFight.js b/src/main/javascript/plugins/minigames/SnowballFight.js new file mode 100644 index 0000000..9a95c2e --- /dev/null +++ b/src/main/javascript/plugins/minigames/SnowballFight.js @@ -0,0 +1,183 @@ +/************************************************************************* +# SnowballFight mini-game + +## Description + +This is a rough and ready prototype of a simple multi-player +shoot-em-up. To start a game with all players playing against one another... + + /js new Game_SnowballFight(60).start(); + +... this obviously works best if all of the players are in close +proximity within the same game world. Alternatively you can have team +matches... + + + /js var redTeam = ['','',...etc] + /js var blueTeam = ['',',...etc] + /js var greenTeam = ['',',...etc] + /js new Game_SnowballFight(60, {red: redTeam,blue: blueTeam,green: greenTeam}).start(); + +Or you can just have specific players play against each other... + + /js new Game_SnowballFight(60, ['player1','player2','player3']).start(); + +(where 'player1' etc are the names of actual players) + +You specify the teams in the game as an object where each property's +name is a team name and each property's value is the list of players +on that team. You specify the duration of the game (in seconds) You +kick off the game with the start() method. I need to work on a +better in-game mechanism for players to choose teams and start the +game but this will do for now. + +When the game starts, each player is put in survival mode and given +snowballs. The aim of the game is to hit players on opposing teams. If +you hit a player on your own team, you lose a point. + +At the end of the game the scores for each team are broadcast and each +player returns to their previous mode of play (creative or +survival). Create a small arena with a couple of small buildings for +cover to make the game more fun. + +***/ + +var _startGame = function(gameState){ + // don't let game start if already in progress (wait for game to finish) + if (gameState.inProgress){ + return; + } + gameState.inProgress = true; + // reset timer + gameState.duration = gameState.originalDuration; + // put all players in survival mode and give them each 200 snowballs + // 64 snowballs for every 30 seconds should be more than enough + for (var i = 10;i < gameState.duration;i+=10) + gameState.ammo.push(gameState.ammo[0]); + + for (var teamName in gameState.teams) + { + gameState.teamScores[teamName] = 0; + var team = gameState.teams[teamName]; + for (var i = 0;i < team.length;i++) { + var player = server.getPlayer(team[i]); + gameState.savedModes[player.name] = player.gameMode; + player.gameMode = org.bukkit.GameMode.SURVIVAL; + player.inventory.addItem(gameState.ammo); + } + } +}; +/* + end the game +*/ +var _endGame = function(gameState){ + var scores = []; + + var leaderBoard = []; + for (var tn in gameState.teamScores){ + leaderBoard.push([tn,gameState.teamScores[tn]]); + } + leaderBoard.sort(function(a,b){ return b[1] - a[1];}); + + for (var i = 0;i < leaderBoard.length; i++){ + scores.push("Team " + leaderBoard[i][0] + " scored " + leaderBoard[i][1]); + } + + for (var teamName in gameState.teams) { + var team = gameState.teams[teamName]; + for (var i = 0;i < team.length;i++) { + // restore player's previous game mode and take back snowballs + var player = server.getPlayer(team[i]); + player.gameMode = gameState.savedModes[player.name]; + player.inventory.removeItem(gameState.ammo); + player.sendMessage("GAME OVER."); + player.sendMessage(scores); + } + } + var handlerList = org.bukkit.event.entity.EntityDamageByEntityEvent.getHandlerList(); + handlerList.unregister(gameState.listener); + gameState.inProgress = false; +}; +/* + get the team the player belongs to +*/ +var _getTeam = function(player,pteams) { + for (var teamName in pteams) { + var team = pteams[teamName]; + for (var i = 0;i < team.length; i++) + if (team[i] == player.name) + return teamName; + } + return null; +}; +/* + construct a new game +*/ +var createGame = function(duration, teams) { + + var _snowBalls = new org.bukkit.inventory.ItemStack(org.bukkit.Material.SNOW_BALL, 64); + + var _gameState = { + teams: teams, + duration: duration, + originalDuration: duration, + inProgress: false, + teamScores: {}, + listener: null, + savedModes: {}, + ammo: [_snowBalls] + }; + if (typeof duration == "undefined"){ + duration = 60; + } + if (typeof teams == "undefined"){ + /* + wph 20130511 use all players + */ + teams = []; + var players = server.onlinePlayers; + for (var i = 0;i < players.length; i++){ + teams.push(players[i].name); + } + } + // + // allow for teams param to be either {red:['player1','player2'],blue:['player3']} or + // ['player1','player2','player3'] if all players are against each other (no teams) + // + if (teams instanceof Array){ + _gameState.teams = {}; + for (var i = 0;i < teams.length; i++) + _gameState.teams[teams[i]] = [teams[i]]; + } + /* + this function is called every time a player is damaged by another entity/player + */ + var _onSnowballHit = function(l,event){ + var snowball = event.damager; + if (!snowball || !(snowball instanceof org.bukkit.entity.Snowball)) + return; + var throwersTeam = _getTeam(snowball.shooter,_gameState.teams); + var damageeTeam = _getTeam(event.entity,_gameState.teams); + if (!throwersTeam || !damageeTeam) + return; // thrower/damagee wasn't in game + if (throwersTeam != damageeTeam) + _gameState.teamScores[throwersTeam]++; + else + _gameState.teamScores[throwersTeam]--; + }; + + return { + start: function() { + _startGame(_gameState); + _gameState.listener = events.on('entity.EntityDamageByEntityEvent',_onSnowballHit); + new java.lang.Thread(function(){ + while (_gameState.duration--) + java.lang.Thread.sleep(1000); // sleep 1,000 millisecs (1 second) + _endGame(_gameState); + }).start(); + } + }; +}; +exports.Game_SnowballFight = createGame; + + From d4fe3f9b1b42e7da4ef04d61706a9680e1cb917d Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Thu, 26 Dec 2013 15:51:31 +0000 Subject: [PATCH 050/456] updated link to API-Reference.md --- docs/API-Reference.md | 76 ------------------- ...YoungPersonsGuideToProgrammingMinecraft.md | 2 +- 2 files changed, 1 insertion(+), 77 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index de1a3be..57f3994 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -1677,82 +1677,6 @@ The arrows mod adds fancy arrows to the game. Arrows which... All of the above functions can take an optional player object or name as a parameter. For example: `/js arrows.explosive('player23')` makes player23's arrows explosive. -# Commando Plugin - -## Description - -commando is a plugin which can be used to add completely new commands -to Minecraft. Normally ScriptCraft only allows for provision of new -commands as extensions to the jsp command. For example, to create a -new simple command for use by all players... - - /js command('hi', function(){ echo('Hi ' + self.name); }); - -... then players can use this command by typing... - - /jsp hi - -... A couple of ScriptCraft users have asked for the ability to take -this a step further and allow the global command namespace to be -populated so that when a developer creates a new command using the -'command' function, then the command is added to the global command -namespace so that players can use it simply like this... - - /hi - -... There are good reasons why ScriptCraft's core `command()` function -does not do this. Polluting the global namespace with commands would -make ScriptCraft a bad citizen in that Plugins should be able to work -together in the same server and - as much as possible - not step on -each others' toes. The CraftBukkit team have very good reasons for -forcing Plugins to declare their commands in the plugin.yml -configuration file. It makes approving plugins easier and ensures that -craftbukkit plugins behave well together. While it is possible to -override other plugins' commands, the CraftBukkit team do not -recommend this. However, as ScriptCraft users have suggested, it -should be at the discretion of server administrators as to when -overriding or adding new commands to the global namespace is good. - -So this is where `commando()` comes in. It uses the exact same -signature as the core `command()` function but will also make the -command accessible without the `jsp` prefix so instead of having to -type `/jsp hi` for the above command example, players simply type -`/hi` . This functionality is provided as a plugin rather than as part -of the ScriptCraft core. - -## Example hi-command.js - - var commando = require('../commando'); - commando('hi', function(){ - echo('Hi ' + self.name); - }); - -...Displays a greeting to any player who issues the `/hi` command. - -## Example - timeofday-command.js - - var times = {Dawn: 0, Midday: 6000, Dusk: 12000, Midnight:18000}; - commando('timeofday', function(params){ - self.location.world.setTime(times[params[0]]); - }, - ['Dawn','Midday','Dusk','Midnight']); - -... changes the time of day using a new `/timeofday` command (options are Dawn, Midday, Dusk, Midnight) - -## Caveats - -Since commands registered using commando are really just appendages to -the `/jsp` command and are not actually registered globally (it just -looks like that to the player), you won't be able to avail of tab -completion for the command itself or its parameters (unless you go the -traditional route of adding the `jsp` prefix). This plugin uses the -[PlayerCommandPreprocessEvent][pcppevt] which allows plugins to -intercepts all commands and inject their own commands instead. If -anyone reading this knows of a better way to programmatically add new -global commands for a plugin, please let me know. - -[pcppevt]: http://jd.bukkit.org/dev/apidocs/org/bukkit/event/player/PlayerCommandPreprocessEvent.html - Classroom Module ================ The `classroom` object contains a couple of utility functions for use diff --git a/docs/YoungPersonsGuideToProgrammingMinecraft.md b/docs/YoungPersonsGuideToProgrammingMinecraft.md index e93349f..a402487 100644 --- a/docs/YoungPersonsGuideToProgrammingMinecraft.md +++ b/docs/YoungPersonsGuideToProgrammingMinecraft.md @@ -975,7 +975,7 @@ different objects and methods available for use by ScriptCraft. [boole]: http://en.wikipedia.org/wiki/George_Boole [soundapi]: http://jd.bukkit.org/beta/apidocs/org/bukkit/Sound.html [ap]: http://walterhiggins.net/blog/ScriptCraft-1-Month-later -[api]: api.md +[api]: API-Reference.md [twl]: http://www.barebones.com/products/textwrangler/ [img_echo_date]: img/ypgpm_echo_date.png From a7a4bf79a1250ffbc80894ce29842c892341ea50 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Fri, 27 Dec 2013 22:50:13 +0000 Subject: [PATCH 051/456] Updated alias to support aliases without 'jsp' prefix, added 'console' global variable --- src/main/javascript/lib/console.js | 57 ++++++ src/main/javascript/lib/tabcomplete-jsp.js | 43 ++++ src/main/javascript/lib/tabcomplete.js | 171 ++++++++++++++++ .../plugins/minigames/SnowBallFight.js | 183 ------------------ 4 files changed, 271 insertions(+), 183 deletions(-) create mode 100644 src/main/javascript/lib/console.js create mode 100644 src/main/javascript/lib/tabcomplete-jsp.js create mode 100644 src/main/javascript/lib/tabcomplete.js delete mode 100644 src/main/javascript/plugins/minigames/SnowBallFight.js diff --git a/src/main/javascript/lib/console.js b/src/main/javascript/lib/console.js new file mode 100644 index 0000000..77c27bd --- /dev/null +++ b/src/main/javascript/lib/console.js @@ -0,0 +1,57 @@ +/************************************************************************* +## console global variable + +ScriptCraft provides a `console` global variable with the followng methods... + + * log() + * info() + * warn() + * error() + +The ScriptCraft console methods work like the Web API implementation. + +### Example + + console.log('Hello %s', 'world'); + +Basic variable substitution is supported (ScriptCraft's implementation +of console uses the Bukkit Plugin [Logger][lgr] under the hood and +uses [java.lang.String.format()][strfmt] for variable +substitution. All output will be sent to the server console (not +in-game). + +[lgr]: http://jd.bukkit.org/beta/apidocs/org/bukkit/plugin/PluginLogger.html +[strfmt]: http://docs.oracle.com/javase/6/docs/api/java/lang/String.html#format(java.lang.String, java.lang.Object...) + +***/ +var argsToArray = function(args){ + var result = []; + for (var i =0;i < args.length; i++) + result.push(args[i]); + return result; +} +var log = function(level, restOfArgs){ + var args = argsToArray(restOfArgs); + if (args.length > 1){ + var msg = java.lang.String.format(args[0],args.slice(1)); + logger['log(java.util.logging.Level,java.lang.String)'](level,msg); + }else{ + logger['log(java.util.logging.Level,java.lang.String)'](level, args[0]); + } +}; + +var Level = java.util.logging.Level; + +exports.log = function(){ + log(Level.INFO, arguments); +}; + +exports.info = function(){ + log(Level.INFO, arguments); +} +exports.warn = function(){ + log(Level.WARNING, arguments); +}; +exports.error = function(){ + log(Level.SEVERE, arguments); +}; diff --git a/src/main/javascript/lib/tabcomplete-jsp.js b/src/main/javascript/lib/tabcomplete-jsp.js new file mode 100644 index 0000000..1d3764f --- /dev/null +++ b/src/main/javascript/lib/tabcomplete-jsp.js @@ -0,0 +1,43 @@ +var _commands = require('plugin').commands; +/* + Tab completion for the /jsp commmand +*/ +var __onTabCompleteJSP = function() { + var result = global.__onTC_result; + var args = global.__onTC_args; + var cmdInput = args[0]; + var cmd = _commands[cmdInput]; + if (cmd){ + var opts = cmd.options; + var len = opts.length; + if (args.length == 1){ + for (var i = 0;i < len; i++) + result.add(opts[i]); + }else{ + // partial e.g. /jsp chat_color dar + for (var i = 0;i < len; i++){ + if (opts[i].indexOf(args[1]) == 0){ + result.add(opts[i]); + } + } + } + }else{ + if (args.length == 0){ + for (var i in _commands) + result.add(i); + }else{ + // partial e.g. /jsp al + // should tabcomplete to alias + // + for (var c in _commands){ + if (c.indexOf(cmdInput) == 0){ + result.add(c); + } + } + } + } + return result; +}; +module.exports = __onTabCompleteJSP; + + diff --git a/src/main/javascript/lib/tabcomplete.js b/src/main/javascript/lib/tabcomplete.js new file mode 100644 index 0000000..37e0bbc --- /dev/null +++ b/src/main/javascript/lib/tabcomplete.js @@ -0,0 +1,171 @@ +var tabCompleteJSP = require('tabcomplete-jsp'); +/* + Tab Completion of the /js and /jsp commands +*/ +var _isJavaObject = function(o){ + var result = false; + try { + o.hasOwnProperty("testForJava"); + }catch (e){ + // java will throw an error when an attempt is made to access the + // hasOwnProperty method. (it won't exist for Java objects) + result = true; + } + return result; +}; +var _javaLangObjectMethods = [ + 'equals' + ,'getClass' + ,'class' + ,'getClass' + ,'hashCode' + ,'notify' + ,'notifyAll' + ,'toString' + ,'wait' + ,'clone' + ,'finalize' +]; + +var _getProperties = function(o) +{ + var result = []; + if (_isJavaObject(o)) + { + propertyLoop: + for (var i in o) + { + // + // don't include standard Object methods + // + var isObjectMethod = false; + for (var j = 0;j < _javaLangObjectMethods.length; j++) + if (_javaLangObjectMethods[j] == i) + continue propertyLoop; + var typeofProperty = null; + try { + typeofProperty = typeof o[i]; + }catch( e ){ + if (e.message == 'java.lang.IllegalStateException: Entity not leashed'){ + // wph 20131020 fail silently for Entity leashing in craftbukkit + }else{ + throw e; + } + } + if (typeofProperty == 'function' ) + result.push(i+'()'); + else + result.push(i); + } + }else{ + if (o.constructor == Array) + return result; + + for (var i in o){ + if (i.match(/^[^_]/)){ + if (typeof o[i] == 'function') + result.push(i+'()'); + else + result.push(i); + } + } + } + return result.sort(); +}; + +var onTabCompleteJS = function() { + if (__onTC_cmd.name == 'jsp') + return tabCompleteJSP() + var _globalSymbols = _getProperties(global) + var result = global.__onTC_result; + var args = global.__onTC_args; + var lastArg = args.length?args[args.length-1]+'':null; + var propsOfLastArg = []; + var statement = args.join(' '); + + statement = statement.replace(/^\s+/,'').replace(/\s+$/,''); + + + if (statement.length == 0) + propsOfLastArg = _globalSymbols; + else{ + var statementSyms = statement.split(/[^\$a-zA-Z0-9_\.]/); + var lastSymbol = statementSyms[statementSyms.length-1]; + //print('DEBUG: lastSymbol=[' + lastSymbol + ']'); + // + // try to complete the object ala java IDEs. + // + var parts = lastSymbol.split(/\./); + var name = parts[0]; + var symbol = global[name]; + var lastGoodSymbol = symbol; + if (typeof symbol != 'undefined') + { + for (var i = 1; i < parts.length;i++){ + name = parts[i]; + symbol = symbol[name]; + if (typeof symbol == 'undefined') + break; + lastGoodSymbol = symbol; + } + //print('debug:name['+name+']lastSymbol['+lastSymbol+']symbol['+symbol+']'); + if (typeof symbol == 'undefined'){ + // + // look up partial matches against last good symbol + // + var objectProps = _getProperties(lastGoodSymbol); + if (name == ''){ + // if the last symbol looks like this.. + // ScriptCraft. + // + + for (var i =0;i < objectProps.length;i++){ + var candidate = lastSymbol + objectProps[i]; + var re = new RegExp(lastSymbol + '$','g'); + propsOfLastArg.push(lastArg.replace(re,candidate)); + } + + }else{ + // it looks like this.. + // ScriptCraft.co + // + //print('debug:case Y: ScriptCraft.co'); + + var li = statement.lastIndexOf(name); + for (var i = 0; i < objectProps.length;i++){ + if (objectProps[i].indexOf(name) == 0) + { + var candidate = lastSymbol.substring(0,lastSymbol.lastIndexOf(name)); + candidate = candidate + objectProps[i]; + var re = new RegExp(lastSymbol+ '$','g'); + //print('DEBUG: re=' + re + ',lastSymbol='+lastSymbol+',lastArg=' + lastArg + ',candidate=' + candidate); + propsOfLastArg.push(lastArg.replace(re,candidate)); + } + } + + } + }else{ + //print('debug:case Z:ScriptCraft'); + var objectProps = _getProperties(symbol); + for (var i = 0; i < objectProps.length; i++){ + var re = new RegExp(lastSymbol+ '$','g'); + propsOfLastArg.push(lastArg.replace(re,lastSymbol + '.' + objectProps[i])); + } + } + }else{ + //print('debug:case AB:ScriptCr'); + // loop thru globalSymbols looking for a good match + for (var i = 0;i < _globalSymbols.length; i++){ + if (_globalSymbols[i].indexOf(lastSymbol) == 0){ + var possibleCompletion = _globalSymbols[i]; + var re = new RegExp(lastSymbol+ '$','g'); + propsOfLastArg.push(lastArg.replace(re,possibleCompletion)); + } + } + + } + } + for (var i = 0;i < propsOfLastArg.length; i++) + result.add(propsOfLastArg[i]); +}; +module.exports = onTabCompleteJS; diff --git a/src/main/javascript/plugins/minigames/SnowBallFight.js b/src/main/javascript/plugins/minigames/SnowBallFight.js deleted file mode 100644 index 9a95c2e..0000000 --- a/src/main/javascript/plugins/minigames/SnowBallFight.js +++ /dev/null @@ -1,183 +0,0 @@ -/************************************************************************* -# SnowballFight mini-game - -## Description - -This is a rough and ready prototype of a simple multi-player -shoot-em-up. To start a game with all players playing against one another... - - /js new Game_SnowballFight(60).start(); - -... this obviously works best if all of the players are in close -proximity within the same game world. Alternatively you can have team -matches... - - - /js var redTeam = ['','',...etc] - /js var blueTeam = ['',',...etc] - /js var greenTeam = ['',',...etc] - /js new Game_SnowballFight(60, {red: redTeam,blue: blueTeam,green: greenTeam}).start(); - -Or you can just have specific players play against each other... - - /js new Game_SnowballFight(60, ['player1','player2','player3']).start(); - -(where 'player1' etc are the names of actual players) - -You specify the teams in the game as an object where each property's -name is a team name and each property's value is the list of players -on that team. You specify the duration of the game (in seconds) You -kick off the game with the start() method. I need to work on a -better in-game mechanism for players to choose teams and start the -game but this will do for now. - -When the game starts, each player is put in survival mode and given -snowballs. The aim of the game is to hit players on opposing teams. If -you hit a player on your own team, you lose a point. - -At the end of the game the scores for each team are broadcast and each -player returns to their previous mode of play (creative or -survival). Create a small arena with a couple of small buildings for -cover to make the game more fun. - -***/ - -var _startGame = function(gameState){ - // don't let game start if already in progress (wait for game to finish) - if (gameState.inProgress){ - return; - } - gameState.inProgress = true; - // reset timer - gameState.duration = gameState.originalDuration; - // put all players in survival mode and give them each 200 snowballs - // 64 snowballs for every 30 seconds should be more than enough - for (var i = 10;i < gameState.duration;i+=10) - gameState.ammo.push(gameState.ammo[0]); - - for (var teamName in gameState.teams) - { - gameState.teamScores[teamName] = 0; - var team = gameState.teams[teamName]; - for (var i = 0;i < team.length;i++) { - var player = server.getPlayer(team[i]); - gameState.savedModes[player.name] = player.gameMode; - player.gameMode = org.bukkit.GameMode.SURVIVAL; - player.inventory.addItem(gameState.ammo); - } - } -}; -/* - end the game -*/ -var _endGame = function(gameState){ - var scores = []; - - var leaderBoard = []; - for (var tn in gameState.teamScores){ - leaderBoard.push([tn,gameState.teamScores[tn]]); - } - leaderBoard.sort(function(a,b){ return b[1] - a[1];}); - - for (var i = 0;i < leaderBoard.length; i++){ - scores.push("Team " + leaderBoard[i][0] + " scored " + leaderBoard[i][1]); - } - - for (var teamName in gameState.teams) { - var team = gameState.teams[teamName]; - for (var i = 0;i < team.length;i++) { - // restore player's previous game mode and take back snowballs - var player = server.getPlayer(team[i]); - player.gameMode = gameState.savedModes[player.name]; - player.inventory.removeItem(gameState.ammo); - player.sendMessage("GAME OVER."); - player.sendMessage(scores); - } - } - var handlerList = org.bukkit.event.entity.EntityDamageByEntityEvent.getHandlerList(); - handlerList.unregister(gameState.listener); - gameState.inProgress = false; -}; -/* - get the team the player belongs to -*/ -var _getTeam = function(player,pteams) { - for (var teamName in pteams) { - var team = pteams[teamName]; - for (var i = 0;i < team.length; i++) - if (team[i] == player.name) - return teamName; - } - return null; -}; -/* - construct a new game -*/ -var createGame = function(duration, teams) { - - var _snowBalls = new org.bukkit.inventory.ItemStack(org.bukkit.Material.SNOW_BALL, 64); - - var _gameState = { - teams: teams, - duration: duration, - originalDuration: duration, - inProgress: false, - teamScores: {}, - listener: null, - savedModes: {}, - ammo: [_snowBalls] - }; - if (typeof duration == "undefined"){ - duration = 60; - } - if (typeof teams == "undefined"){ - /* - wph 20130511 use all players - */ - teams = []; - var players = server.onlinePlayers; - for (var i = 0;i < players.length; i++){ - teams.push(players[i].name); - } - } - // - // allow for teams param to be either {red:['player1','player2'],blue:['player3']} or - // ['player1','player2','player3'] if all players are against each other (no teams) - // - if (teams instanceof Array){ - _gameState.teams = {}; - for (var i = 0;i < teams.length; i++) - _gameState.teams[teams[i]] = [teams[i]]; - } - /* - this function is called every time a player is damaged by another entity/player - */ - var _onSnowballHit = function(l,event){ - var snowball = event.damager; - if (!snowball || !(snowball instanceof org.bukkit.entity.Snowball)) - return; - var throwersTeam = _getTeam(snowball.shooter,_gameState.teams); - var damageeTeam = _getTeam(event.entity,_gameState.teams); - if (!throwersTeam || !damageeTeam) - return; // thrower/damagee wasn't in game - if (throwersTeam != damageeTeam) - _gameState.teamScores[throwersTeam]++; - else - _gameState.teamScores[throwersTeam]--; - }; - - return { - start: function() { - _startGame(_gameState); - _gameState.listener = events.on('entity.EntityDamageByEntityEvent',_onSnowballHit); - new java.lang.Thread(function(){ - while (_gameState.duration--) - java.lang.Thread.sleep(1000); // sleep 1,000 millisecs (1 second) - _endGame(_gameState); - }).start(); - } - }; -}; -exports.Game_SnowballFight = createGame; - - From e0f8f0dc0fe13278308910e33faf796aafb6db64 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Fri, 27 Dec 2013 22:52:16 +0000 Subject: [PATCH 052/456] Updated alias command to create aliases without 'jsp' prefix and added 'console' global variable --- docs/API-Reference.md | 91 +++++- docs/release-notes.md | 11 + src/main/javascript/lib/plugin.js | 9 +- src/main/javascript/lib/scriptcraft.js | 207 +------------ src/main/javascript/plugins/alias/alias.js | 288 +++++++++++++----- .../javascript/plugins/commando/commando.js | 26 +- 6 files changed, 341 insertions(+), 291 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 57f3994..ec7de2e 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -530,6 +530,30 @@ To unregister a listener *outside* of the listener function... [buk2]: http://wiki.bukkit.org/Event_API_Reference [buk]: http://jd.bukkit.org/dev/apidocs/index.html?org/bukkit/event/Event.html +## console global variable + +ScriptCraft provides a `console` global variable with the followng methods... + + * log() + * info() + * warn() + * error() + +The ScriptCraft console methods work like the Web API implementation. + +### Example + + console.log('Hello %s', 'world'); + +Basic variable substitution is supported (ScriptCraft's implementation +of console uses the Bukkit Plugin [Logger][lgr] under the hood and +uses [java.lang.String.format()][strfmt] for variable +substitution. All output will be sent to the server console (not +in-game). + +[lgr]: http://jd.bukkit.org/beta/apidocs/org/bukkit/plugin/PluginLogger.html +[strfmt]: http://docs.oracle.com/javase/6/docs/api/java/lang/String.html#format(java.lang.String, java.lang.Object...) + http.request() function ==================== The http.request() function will fetch a web address asynchronously (on a @@ -1677,6 +1701,56 @@ The arrows mod adds fancy arrows to the game. Arrows which... All of the above functions can take an optional player object or name as a parameter. For example: `/js arrows.explosive('player23')` makes player23's arrows explosive. +## alias Module + +The alias module lets players and server admins create their own +per-player or global custom in-game command aliases. + +### Examples + +To set a command alias which is only visible to the current player +(per-player alias)... + + /jsp alias set cw = time set {1} ; weather {2} + +... Creates a new custom command only usable by the player who set +it called `cw` (short for set Clock and Weather) which when invoked... + + /cw 4000 sun + +... will perform the following commands... + + /time set 4000 + /weather sun + +Aliases can use paramters as above. On the right hand side of the `=`, the +`{1}` refers to the first parameter provided with the `cw` alias, `{2}` +refers to the second parameter and so on. So `cw 4000 sun` is converted to +`time set 4000` and `weather sun`. + +To set a global command alias usable by all (only operators can create +such an alias)... + + /jsp alias global stormy = time 18000; weather storm + +To delete an alias ... + + /jsp alias delete cw + +... deletes the 'cw' alias from the appropriate alias map. + +To get a list of aliases currently defined... + + /jsp alias list + +To get help on the `jsp alias` command: + + /jsp alias help + +Aliases can be used at the in-game prompt by players or in the server +console. Aliases will not be able to avail of command autocompletion +(pressing the TAB key will have no effect). + Classroom Module ================ The `classroom` object contains a couple of utility functions for use @@ -1718,9 +1792,9 @@ To disallow scripting (and prevent players who join the server from using the co Only ops users can run the classroom.allowScripting() function - this is so that students don't try to bar themselves and each other from scripting. -### Commando Plugin +# Commando Plugin -#### Description +## Description commando is a plugin which can be used to add completely new commands to Minecraft. Normally ScriptCraft only allows for provision of new @@ -1751,9 +1825,8 @@ configuration file. It makes approving plugins easier and ensures that craftbukkit plugins behave well together. While it is possible to override other plugins' commands, the CraftBukkit team do not recommend this. However, as ScriptCraft users have suggested, it -should be at the discretion of server administrators and plugin -authors as to when overriding or adding new commands to the global -namespace is good. +should be at the discretion of server administrators as to when +overriding or adding new commands to the global namespace is good. So this is where `commando()` comes in. It uses the exact same signature as the core `command()` function but will also make the @@ -1762,7 +1835,7 @@ type `/jsp hi` for the above command example, players simply type `/hi` . This functionality is provided as a plugin rather than as part of the ScriptCraft core. -#### Example hi-command.js +## Example hi-command.js var commando = require('../commando'); commando('hi', function(){ @@ -1771,17 +1844,17 @@ of the ScriptCraft core. ...Displays a greeting to any player who issues the `/hi` command. -#### Example - timeofday-command.js +## Example - timeofday-command.js var times = {Dawn: 0, Midday: 6000, Dusk: 12000, Midnight:18000}; commando('timeofday', function(params){ self.location.world.setTime(times[params[0]]); - } + }, ['Dawn','Midday','Dusk','Midnight']); ... changes the time of day using a new `/timeofday` command (options are Dawn, Midday, Dusk, Midnight) -#### Caveats +## Caveats Since commands registered using commando are really just appendages to the `/jsp` command and are not actually registered globally (it just diff --git a/docs/release-notes.md b/docs/release-notes.md index b4286ec..3816c45 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,3 +1,14 @@ +# 2013 12 27 + +## Updated 'jsp alias' command. + +The 'jsp alias' command now lets players define their own shortcuts which don't require the 'jsp ' prefix. + +## Added console global variable. + +ScriptCraft now has a `console` global variable which can be used for logging (to the server console). +The `console` variable uses the ScriptCraft plugin Logger object. + # 2013 12 26 Made the `events` variable global because it is use by modules and diff --git a/src/main/javascript/lib/plugin.js b/src/main/javascript/lib/plugin.js index f9b1027..331b88a 100644 --- a/src/main/javascript/lib/plugin.js +++ b/src/main/javascript/lib/plugin.js @@ -84,7 +84,14 @@ var _command = function(name,func,options,intercepts) for (var i =1; i < __cmdArgs.length;i++){ params.push("" + __cmdArgs[i]); } - return func(params); + var result = null; + try { + result = func(params); + }catch (e){ + logger.severe("Error while trying to execute command: " + JSON.stringify(params)); + throw e; + } + return result; } }else{ if (typeof options == "undefined") diff --git a/src/main/javascript/lib/scriptcraft.js b/src/main/javascript/lib/scriptcraft.js index d006db6..4b1ee83 100644 --- a/src/main/javascript/lib/scriptcraft.js +++ b/src/main/javascript/lib/scriptcraft.js @@ -416,207 +416,7 @@ var server = org.bukkit.Bukkit.server; if (!config) config = {verbose: false}; global.config = config; - /* - Tab Completion of the /js and /jsp commands - */ - var _isJavaObject = function(o){ - var result = false; - try { - o.hasOwnProperty("testForJava"); - }catch (e){ - // java will throw an error when an attempt is made to access the - // hasOwnProperty method. (it won't exist for Java objects) - result = true; - } - return result; - }; - var _javaLangObjectMethods = ["equals","getClass","class","getClass","hashCode","notify","notifyAll","toString","wait","clone","finalize"]; - - var _getProperties = function(o) - { - var result = []; - if (_isJavaObject(o)) - { - propertyLoop: - for (var i in o) - { - // - // don't include standard Object methods - // - var isObjectMethod = false; - for (var j = 0;j < _javaLangObjectMethods.length; j++) - if (_javaLangObjectMethods[j] == i) - continue propertyLoop; - var typeofProperty = null; - try { - typeofProperty = typeof o[i]; - }catch( e ){ - if (e.message == "java.lang.IllegalStateException: Entity not leashed"){ - // wph 20131020 fail silently for Entity leashing in craftbukkit - }else{ - throw e; - } - } - if (typeofProperty == "function" ) - result.push(i+"()"); - else - result.push(i); - } - }else{ - if (o.constructor == Array) - return result; - for (var i in o){ - if (i.match(/^[^_]/)){ - if (typeof o[i] == "function") - result.push(i+"()"); - else - result.push(i); - } - } - } - return result.sort(); - }; - /* - Tab completion for the /jsp commmand - */ - var __onTabCompleteJSP = function() { - var result = global.__onTC_result; - var args = global.__onTC_args; - var cmdInput = args[0]; - var cmd = _commands[cmdInput]; - if (cmd){ - var opts = cmd.options; - var len = opts.length; - if (args.length == 1){ - for (var i = 0;i < len; i++) - result.add(opts[i]); - }else{ - // partial e.g. /jsp chat_color dar - for (var i = 0;i < len; i++){ - if (opts[i].indexOf(args[1]) == 0){ - result.add(opts[i]); - } - } - } - }else{ - if (args.length == 0){ - for (var i in _commands) - result.add(i); - }else{ - // partial e.g. /jsp al - // should tabcomplete to alias - // - for (var c in _commands){ - if (c.indexOf(cmdInput) == 0){ - result.add(c); - } - } - } - } - return result; - }; - var _commands; - /* - Tab completion for the /js command - */ - var __onTabCompleteJS = function() - { - if (__onTC_cmd.name == "jsp") - return __onTabCompleteJSP() - - var _globalSymbols = _getProperties(global) - var result = global.__onTC_result; - var args = global.__onTC_args; - var lastArg = args.length?args[args.length-1]+"":null; - var propsOfLastArg = []; - var statement = args.join(" "); - - statement = statement.replace(/^\s+/,"").replace(/\s+$/,""); - - - if (statement.length == 0) - propsOfLastArg = _globalSymbols; - else{ - var statementSyms = statement.split(/[^\$a-zA-Z0-9_\.]/); - var lastSymbol = statementSyms[statementSyms.length-1]; - //print("DEBUG: lastSymbol=[" + lastSymbol + "]"); - // - // try to complete the object ala java IDEs. - // - var parts = lastSymbol.split(/\./); - var name = parts[0]; - var symbol = global[name]; - var lastGoodSymbol = symbol; - if (typeof symbol != "undefined") - { - for (var i = 1; i < parts.length;i++){ - name = parts[i]; - symbol = symbol[name]; - if (typeof symbol == "undefined") - break; - lastGoodSymbol = symbol; - } - //print("debug:name["+name+"]lastSymbol["+lastSymbol+"]symbol["+symbol+"]"); - if (typeof symbol == "undefined"){ - // - // look up partial matches against last good symbol - // - var objectProps = _getProperties(lastGoodSymbol); - if (name == ""){ - // if the last symbol looks like this.. - // ScriptCraft. - // - - for (var i =0;i < objectProps.length;i++){ - var candidate = lastSymbol + objectProps[i]; - var re = new RegExp(lastSymbol + "$","g"); - propsOfLastArg.push(lastArg.replace(re,candidate)); - } - - }else{ - // it looks like this.. - // ScriptCraft.co - // - //print("debug:case Y: ScriptCraft.co"); - - var li = statement.lastIndexOf(name); - for (var i = 0; i < objectProps.length;i++){ - if (objectProps[i].indexOf(name) == 0) - { - var candidate = lastSymbol.substring(0,lastSymbol.lastIndexOf(name)); - candidate = candidate + objectProps[i]; - var re = new RegExp(lastSymbol+ "$","g"); - //print("DEBUG: re=" + re + ",lastSymbol="+lastSymbol+",lastArg=" + lastArg + ",candidate=" + candidate); - propsOfLastArg.push(lastArg.replace(re,candidate)); - } - } - - } - }else{ - //print("debug:case Z:ScriptCraft"); - var objectProps = _getProperties(symbol); - for (var i = 0; i < objectProps.length; i++){ - var re = new RegExp(lastSymbol+ "$","g"); - propsOfLastArg.push(lastArg.replace(re,lastSymbol + "." + objectProps[i])); - } - } - }else{ - //print("debug:case AB:ScriptCr"); - // loop thru globalSymbols looking for a good match - for (var i = 0;i < _globalSymbols.length; i++){ - if (_globalSymbols[i].indexOf(lastSymbol) == 0){ - var possibleCompletion = _globalSymbols[i]; - var re = new RegExp(lastSymbol+ "$","g"); - propsOfLastArg.push(lastArg.replace(re,possibleCompletion)); - } - } - - } - } - for (var i = 0;i < propsOfLastArg.length; i++) - result.add(propsOfLastArg[i]); - }; /* Unload Handlers @@ -736,7 +536,7 @@ See [issue #69][issue69] for more information. global.alert = _echo; global.load = _load; global.logger = __plugin.logger; - global._onTabComplete = __onTabCompleteJS; + global.addUnloadHandler = _addUnloadHandler; var fnRequire = load(jsPluginsRootDirName + '/lib/require.js',true); @@ -751,12 +551,15 @@ See [issue #69][issue69] for more information. jsPluginsRootDirName, modulePaths); + var plugins = require('plugin'); - _commands = plugins.commands; + global._onTabComplete = require('tabcomplete'); + global.plugin = plugins.plugin; global.command = plugins.command; global.save = plugins.save; + global.console = require('console'); var events = require('events'); events.on('server.PluginDisableEvent',function(l,e){ diff --git a/src/main/javascript/plugins/alias/alias.js b/src/main/javascript/plugins/alias/alias.js index 87076f6..2e27312 100644 --- a/src/main/javascript/plugins/alias/alias.js +++ b/src/main/javascript/plugins/alias/alias.js @@ -1,80 +1,230 @@ +/************************************************************************* +## alias Module -var _store = {players: {}}; +The alias module lets players and server admins create their own +per-player or global custom in-game command aliases. -var alias = plugin("alias", { - help: function(){ - return [ - "/jsp alias set : Set a shortcut/alias for one or more commands (separated by ';')\n" + - "For example: '/jsp alias set sunny time set 4000; weather clear'\n" + - "/jsp sunny (is the same as..\n/time set 4000\n/weather clear", - "/jsp alias delete : Removes a shortcut/alias", - "/jsp alias list : shows a list of the player's command aliases", - "/jsp alias help : Shows this message" - ]; - }, - set: function(player, alias, commands){ - var aliases = _store.players; - var name = player.name; - if (typeof aliases[name] == "undefined") - aliases[name] = {}; - aliases[name][alias] = commands; - }, - remove: function(player, alias){ - var aliases = _store.players; - if (aliases[player.name]) - delete aliases[player.name][alias]; - }, - list: function(player){ - var result = []; - var aliases = _store.players[player.name]; - for (var a in aliases) - result.push(a + " = " + aliases[a].join(";")); - return result; - }, - store: _store -},true); +### Examples -exports.alias = alias; +To set a command alias which is only visible to the current player +(per-player alias)... -command("alias", function ( params ) { - /* - this function also intercepts command options for /jsp - */ - if (params[0] === "help"){ - self.sendMessage(alias.help()); + /jsp alias set cw = time set {1} ; weather {2} + +... Creates a new custom command only usable by the player who set +it called `cw` (short for set Clock and Weather) which when invoked... + + /cw 4000 sun + +... will perform the following commands... + + /time set 4000 + /weather sun + +Aliases can use paramters as above. On the right hand side of the `=`, the +`{1}` refers to the first parameter provided with the `cw` alias, `{2}` +refers to the second parameter and so on. So `cw 4000 sun` is converted to +`time set 4000` and `weather sun`. + +To set a global command alias usable by all (only operators can create +such an alias)... + + /jsp alias global stormy = time 18000; weather storm + +To delete an alias ... + + /jsp alias delete cw + +... deletes the 'cw' alias from the appropriate alias map. + +To get a list of aliases currently defined... + + /jsp alias list + +To get help on the `jsp alias` command: + + /jsp alias help + +Aliases can be used at the in-game prompt by players or in the server +console. Aliases will not be able to avail of command autocompletion +(pressing the TAB key will have no effect). + +***/ + +var _usage = "\ +/jsp alias set {alias} = {comand-1} ;{command-2}\n \ +/jsp alias global {alias} = {command-1} ; {command-2}\n \ +/jsp alias list\n \ +/jsp alias delete {alias}\n \ +Create a new alias : \n \ +/jsp alias set cw = time set {1} ; weather {2}\n \ +Execute the alias : \n \ +/cw 4000 sun \n \ +...is the same as '/time set 4000' and '/weather sun'"; +/* + persist aliases +*/ +var _store = { + players: {}, + global: {} +}; +/* + turns 'cw = time set {1} ; weather {2}' into {cmd: 'cw', aliases: ['time set {1}', 'weather {2}']} + _processParams is a private function which takes an array of parameters + used for the 'set' and 'global' options. +*/ +var _processParams = function(params){ + var paramStr = params.join(' '); + var eqPos = paramStr.indexOf('='); + var aliasCmd = paramStr.substring(0,eqPos).trim(); + var aliasValue = paramStr.substring(eqPos+1).trim(); + return { cmd: aliasCmd, aliases: aliasValue.split(/\s*;\s*/) }; +}; + +var _set = function(player, params){ + var playerAliases = _store.players[player.name]; + if (!playerAliases){ + playerAliases = {}; + } + var o = _processParams(params); + playerAliases[o.cmd] = o.aliases; + _store.players[player.name] = playerAliases; + player.sendMessage("Alias '" + o.cmd + "' created."); +}; + +var _delete = function(player, params){ + if (_store.players[player.name] && + _store.players[player.name][params[0]]){ + delete _store.players[player.name][params[0]]; + player.sendMessage("Alias '" + params[0] + "' deleted."); + } + else{ + player.sendMessage("Alias '" + params[0] + "' does not exist."); + } + if (player.op){ + if (_store.global[params[0]]) + delete _store.global[params[0]]; + } +}; + +var _global = function(player, params){ + if (!player.op){ + player.sendMessage("Only operators can set global aliases. " + + "You need to be an operator to perform this command."); return; } - if (params[0] === "set"){ - var aliasCmd = params[1]; - var cmdStr = params.slice(2).join(' '); - var cmds = cmdStr.split(';'); - alias.set(self,aliasCmd,cmds); + var o = _processParams(params); + _store.global[o.cmd] = o.aliases; + player.sendMessage("Global alias '" + o.cmd + "' created."); +}; + +var _list = function(player){ + try { + var alias = 0; + if (_store.players[player.name]){ + player.sendMessage("Your aliases:"); + for (alias in _store.players[player.name]){ + player.sendMessage(alias + " = " + + JSON.stringify(_store.players[player.name][alias])); + } + }else{ + player.sendMessage("You have no player-specific aliases."); + } + player.sendMessage("Global aliases:"); + for (alias in _store.global){ + player.sendMessage(alias + " = " + JSON.stringify(_store.global[alias]) ); + } + }catch(e){ + logger.severe("Error in list function: " + e.message); + throw e; + } +}; +var alias = plugin('alias', { + "store": _store, + "set": _set, + "global": _global, + "delete": _delete, + "list": _list, + "help": function(player){ player.sendMessage("Usage:\n" + _usage);} +}, true ); + + +var aliasCmd = command('alias', function(params){ + var operation = params[0]; + if (!operation){ + self.sendMessage("Usage:\n" + _usage); return; } - if (params[0] === "delete"){ - alias.remove(self,params[1]); - return ; - } - if (params[0] === "list"){ - self.sendMessage(alias.list(self)); - return; - } - if (params.length == 0) - return self.sendMessage(alias.help()); + if (alias[operation]) + alias[operation](self, params.slice(1)); + else + self.sendMessage("Usage:\n" + _usage); +}); + +var _intercept = function( msg, invoker, exec) +{ + var msgParts = msg.split(' '); + var command = msg.match(/^\/*([^\s]+)/)[1]; + + var template = [], isAlias = false, cmds = []; - var playerHasAliases = _store.players[self.name]; - if (!playerHasAliases) - return false; - // is it an alias? - var commands = playerHasAliases[params[0]]; - if (!commands) - return false; - for (var i = 0;i < commands.length; i++){ - // fill in template - var cmd = commands[i]; - cmd = cmd.replace(/{([0-9]*)}/g,function(dummy,index){ return params[index] ? params[index] : "";}) - self.performCommand(cmd); + if (_store.global[command]){ + template = _store.global[command]; + isAlias = true; + }else{ + if (config.verbose){ + logger.info("No global alias found for command: " + command); + } } - return true; + /* + allows player-specific aliases to override global aliases + */ + if (_store.players[invoker] && + _store.players[invoker][command]) + { + template = _store.players[invoker][command]; + isAlias = true; + }else{ + if (config.verbose){ + logger.info("No player alias found for command: " + command); + } + } + for (var i = 0;i < template.length; i++) + { + var filledinCommand = template[i].replace(/{([0-9]+)}/g, function (match,index){ + index = parseInt(index,10); + if (msgParts[index]) + return msgParts[index] + else + return match; + }); + cmds.push(filledinCommand); + } + + for (var i = 0; i< cmds.length; i++){ + exec(cmds[i]); + } + return isAlias; -},["help","set","delete","list"],true); +}; +/* + Intercept all command processing and replace with aliased commands if the + command about to be issued matches an alias. +*/ +events.on('player.PlayerCommandPreprocessEvent', function(listener,evt){ + var invoker = evt.player; + var exec = function(cmd){ invoker.performCommand(cmd);}; + var isAlias = _intercept(''+evt.message, ''+invoker.name, exec); + if (isAlias) + evt.cancelled = true; + +}); +/* define a 'void' command because ServerCommandEvent can't be canceled */ +command('void',function(){}); +events.on('server.ServerCommandEvent', function(listener,evt){ + var invoker = evt.sender; + var exec = function(cmd){ invoker.server.dispatchCommand(invoker, cmd); }; + var isAlias = _intercept(''+evt.command, ''+ invoker.name, exec); + if (isAlias) + evt.command = "jsp void"; +}); diff --git a/src/main/javascript/plugins/commando/commando.js b/src/main/javascript/plugins/commando/commando.js index e0f620c..871e62b 100644 --- a/src/main/javascript/plugins/commando/commando.js +++ b/src/main/javascript/plugins/commando/commando.js @@ -1,7 +1,7 @@ /************************************************************************* -### Commando Plugin +# Commando Plugin -#### Description +## Description commando is a plugin which can be used to add completely new commands to Minecraft. Normally ScriptCraft only allows for provision of new @@ -32,9 +32,8 @@ configuration file. It makes approving plugins easier and ensures that craftbukkit plugins behave well together. While it is possible to override other plugins' commands, the CraftBukkit team do not recommend this. However, as ScriptCraft users have suggested, it -should be at the discretion of server administrators and plugin -authors as to when overriding or adding new commands to the global -namespace is good. +should be at the discretion of server administrators as to when +overriding or adding new commands to the global namespace is good. So this is where `commando()` comes in. It uses the exact same signature as the core `command()` function but will also make the @@ -43,7 +42,7 @@ type `/jsp hi` for the above command example, players simply type `/hi` . This functionality is provided as a plugin rather than as part of the ScriptCraft core. -#### Example hi-command.js +## Example hi-command.js var commando = require('../commando'); commando('hi', function(){ @@ -52,17 +51,17 @@ of the ScriptCraft core. ...Displays a greeting to any player who issues the `/hi` command. -#### Example - timeofday-command.js +## Example - timeofday-command.js var times = {Dawn: 0, Midday: 6000, Dusk: 12000, Midnight:18000}; commando('timeofday', function(params){ self.location.world.setTime(times[params[0]]); - } + }, ['Dawn','Midday','Dusk','Midnight']); ... changes the time of day using a new `/timeofday` command (options are Dawn, Midday, Dusk, Midnight) -#### Caveats +## Caveats Since commands registered using commando are really just appendages to the `/jsp` command and are not actually registered globally (it just @@ -77,8 +76,8 @@ global commands for a plugin, please let me know. [pcppevt]: http://jd.bukkit.org/dev/apidocs/org/bukkit/event/player/PlayerCommandPreprocessEvent.html ***/ - var commands = {}; + exports.commando = function(name, func, options, intercepts){ var result = command(name, func, options, intercepts); commands[name] = result; @@ -92,3 +91,10 @@ events.on('player.PlayerCommandPreprocessEvent', function(l,e){ e.message = "/jsp " + msg.substring(1); } }); +events.on('server.ServerCommandEvent', function(l,e){ + var msg = "" + e.command; + var command = msg.match(/^\/*([^\s]+)/)[1]; + if (commands[command]){ + e.command = "/jsp " + msg.substring(1); + } +}); From 1562a3bf0f555d9802fcb92b20ea2fd165e3561b Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Fri, 27 Dec 2013 22:59:18 +0000 Subject: [PATCH 053/456] Updated release notes to reflect recent changes --- docs/release-notes.md | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 3816c45..84c5e14 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,12 +2,44 @@ ## Updated 'jsp alias' command. -The 'jsp alias' command now lets players define their own shortcuts which don't require the 'jsp ' prefix. +The 'jsp alias' command now lets players define their own shortcuts +which don't require the 'jsp ' prefix. +### Example + +At the in-game prompt use the following command to create a new alias +`cw` (short for change Clock & Weather) which will change the time and +weather using a single statement. + + /jsp alias set cw = time set {1} ; weather {2} + +This creates a new cw alias which takes 2 parameters, time and weather +and passes them to the 'time set' and 'weather' commands. You use the +alias like this... + + /cw 4000 sun + +... which in turn gets converted into these 2 commands... + + /time set 4000 + /weather sun + +Aliases can be set on a per-player basis or can be set globally (for +all players). Aliases are automatically saved and restore on server +shutdown/startup. + ## Added console global variable. -ScriptCraft now has a `console` global variable which can be used for logging (to the server console). -The `console` variable uses the ScriptCraft plugin Logger object. +ScriptCraft now has a `console` global variable which can be used for +logging (to the server console). The `console` variable uses the +ScriptCraft plugin Logger object. You use the console object in your +javascript modules like this... + + console.info('Hello %s, %s', 'World', 'Universe'); + +... or simply... + + console.warn('Hello World'); # 2013 12 26 From 76164254ba9c03f169de1fb299b1f0e10f0aeaf9 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 28 Dec 2013 08:44:40 +0000 Subject: [PATCH 054/456] Cleaning up markdown documentation. --- docs/API-Reference.md | 300 +++++++++--------- src/main/javascript/lib/events.js | 51 ++- src/main/javascript/lib/plugin.js | 38 --- src/main/javascript/lib/require.js | 10 +- src/main/javascript/lib/scriptcraft.js | 71 ++--- src/main/javascript/modules/blocks.js | 20 +- .../javascript/modules/fireworks/fireworks.js | 8 +- src/main/javascript/modules/http/request.js | 11 +- src/main/javascript/modules/utils/utils.js | 63 ++-- .../javascript/plugins/classroom/classroom.js | 17 +- .../javascript/plugins/drone/blocktype.js | 11 +- .../plugins/drone/contrib/rainbow.js | 10 +- .../plugins/drone/contrib/spiral_stairs.js | 11 +- src/main/javascript/plugins/drone/sphere.js | 44 ++- src/main/javascript/plugins/homes/homes.js | 3 + 15 files changed, 305 insertions(+), 363 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index ec7de2e..b815b92 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -115,26 +115,35 @@ provided. The `lib` directory is for internal use by ScriptCraft. Modules in this directory are not automatically loaded nor are they globally exported. -### Directories +### plugins sub-directories As of December 24 2013, the `scriptcraft/plugins` directory has the following sub-directories... * drone - Contains the drone module and drone extensions. Drone was the first scriptcraft module. * mini-games - Contains mini-games - * arrows - The arrows module - * signs - The signs module (includes example signs) - * chat - The chat plugin/module - * alias - The alias plugin/module + * 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. * home - The home module - for setting homes and visiting other homes. -## Core Module: functions +## Global functions -ScripCraft provides some functions which can be used by all plugins/modules... +ScripCraft provides some global functions which can be used by all plugins/modules... - * echo (message) - Displays a message on the screen. +### 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). + +### Example + + /js echo('Hello World') + + +* echo (message) - Displays a message on the screen. For example: `/js echo('Hello World')` will print Hello World on the in-game chat window. 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')` + `alert` works exactly the same as `echo` e.g. `alert('Hello World')`. * require (modulename) - Will load modules. See [Node.js modules][njsmod] @@ -183,7 +192,7 @@ load() should only be used to load .json data. * filename - The name of the file to load. * warnOnFileNotFound (optional - default: false) - warn if the file was not found. -#### Return +#### Returns load() will return the result of the last statement evaluated in the file. @@ -283,34 +292,8 @@ plugin author) safely expose javascript functions for use by players. See chat/colors.js or alias/alias.js or homes/homes.js for examples of how to use the `command()` function. -### ready() function +## global variables -The `ready()` function provides a way for plugins to do additional -setup once all of the other plugins/modules have loaded. For example, -event listener registration can only be done after the -events/events.js module has loaded. A plugin author could load the -file explicilty like this... - - load(__folder + "../events/events.js"); - - // event listener registration goes here - -... or better still, just do event regristration using the `ready()` -handler knowing that by the time the `ready()` callback is invoked, -all of the scriptcraft modules have been loaded... - - ready(function(){ - // event listener registration goes here - // code that depends on other plugins/modules also goes here - }); - -The execution of the function object passed to the `ready()` function -is *deferred* until all of the plugins/modules have loaded. That way -you are guaranteed that when the function is invoked, all of the -plugins/modules have been loaded and evaluated and are ready to use. - -Core Module - Special Variables -=============================== There are a couple of special javascript variables available in ScriptCraft... * __folder - The current working directory - this variable is only to be used within the main body of a .js file. @@ -373,7 +356,7 @@ See [issue #69][issue69] for more information. [issue69]: https://github.com/walterhiggins/ScriptCraft/issues/69 -## Require - Node.js-style module loading in ScriptCraft +## require - Node.js-style module loading in ScriptCraft Node.js is a server-side javascript environment with an excellent module loading system based on CommonJS. Modules in Node.js are really @@ -415,19 +398,21 @@ and modules themeselves can use other modules. Modules have full control over what functions and properties they want to provide to others. -## Important +### Important Although ScriptCraft now supports Node.js style modules, it does not support node modules. Node.js and Rhino are two very different Javascript environments. ScriptCraft uses Rhino Javascript, not -Node.js. +Node.js. Standard Node.js modules such as `'fs'` are not available in ScriptCraft. Modules can be loaded using relative or absolute paths. Per the CommonJS module specification, the '.js' suffix is optional. [cjsmodules]: http://wiki.commonjs.org/wiki/Modules/1.1.1. -## When resolving module names to file paths, ScriptCraft uses the following rules... +### module name resolution + +When resolving module names to file paths, ScriptCraft uses the following rules... 1. if the module does not begin with './' or '/' then ... @@ -457,19 +442,19 @@ module specification, the '.js' suffix is optional. 3.2 if no package.json file exists then look for an index.js file in the directory -events Module -============= +## events Module + The Events module provides a thin wrapper around Bukkit's Event-handling API. Bukkit's Events API makes use of Java Annotations which are not available in Javascript, so this module provides a simple way to listen to minecraft events in javascript. -events.on() static method -========================= +### events.on() static method + This method is used to register event listeners. -Parameters ----------- +#### Parameters + * eventName - A string or java class. If a string is supplied it must be part of the Bukkit event class name. See [Bukkit API][buk] for @@ -493,40 +478,40 @@ Parameters "NORMAL", "MONITOR". For an explanation of what the different priorities mean refer to bukkit's [Event API Reference][buk2]. -Returns -------- +#### Returns + An org.bukkit.plugin.RegisteredListener object which can be used to unregister the listener. This same object is passed to the callback function each time the event is fired. -Example: ------- +#### Example: + The following code will print a message on screen every time a block is broken in the game - var events = require('./events/events'); - - events.on("block.BlockBreakEvent", function(listener, evt){ - echo (evt.player.name + " broke a block!"); + events.on('block.BlockBreakEvent', function(listener, evt){ + evt.player.sendMessage( evt.player.name + ' broke a block!'); }); To handle an event only once and unregister from further events... - var events = require('./events/events'); - - events.on("block.BlockBreakEvent", function(listener, evt){ - print (evt.player.name + " broke a block!"); + events.on('block.BlockBreakEvent', function(listener, evt){ + evt.player.sendMessage( evt.player.name + ' broke a block!'); evt.handlers.unregister(listener); }); To unregister a listener *outside* of the listener function... - var events = require('./events/events'); - - var myBlockBreakListener = events.on("block.BlockBreakEvent",function(l,e){ ... }); + var myBlockBreakListener = events.on('block.BlockBreakEvent',function(l,e){ ... }); ... var handlers = org.bukkit.event.block.BlockBreakEvent.getHandlerList(); handlers.unregister(myBlockBreakListener); +To listen for events using a full class name as the `eventName` parameter... + + events.on(org.bukkit.event.block.BlockBreakEvent, function(listener, evt){ + evt.player.sendMessage( evt.player.name + ' broke a block!'); + }); + [buk2]: http://wiki.bukkit.org/Event_API_Reference [buk]: http://jd.bukkit.org/dev/apidocs/index.html?org/bukkit/event/Event.html @@ -554,16 +539,15 @@ in-game). [lgr]: http://jd.bukkit.org/beta/apidocs/org/bukkit/plugin/PluginLogger.html [strfmt]: http://docs.oracle.com/javase/6/docs/api/java/lang/String.html#format(java.lang.String, java.lang.Object...) -http.request() function -==================== +## http.request() function + The http.request() function will fetch a web address asynchronously (on a separate thread)and pass the URL's response to a callback function which will be executed synchronously (on the main thread). In this way, http.request() can be used to fetch web content without blocking the main thread of execution. -Parameters ----------- +### Parameters * request: The request details either a plain URL e.g. "http://scriptcraft.js/sample.json" or an object with the following properties... @@ -575,8 +559,8 @@ Parameters - responseCode: The numeric response code from the server. If the server did not respond with 200 OK then the response parameter will be undefined. - response: A string (if the response is of type text) or object containing the HTTP response body. -Example -------- +### Example + The following example illustrates how to use http.request to make a request to a JSON web service and evaluate its response... var jsResponse; @@ -595,8 +579,8 @@ The following example illustrates how to use http.request to make a request to a var jsObj = eval("(" + responseBody + ")"); }); -Utilities Module -================ +## Utilities Module + Miscellaneous utility functions and classes to help with programming. * locationToString(Location) - returns a bukkit Location object in string form. @@ -610,8 +594,8 @@ Miscellaneous utility functions and classes to help with programming. * getMousePos(playerName) - returns the x,y,z of the current block being targeted by the named player or player or `self` if no paramter is provided. -foreach() function -======================== +### foreach() function + The utils.foreach() function is a utility function for iterating over an array of objects (or a java.util.Collection of objects) and processing each object in turn. Where utils.foreach() differs from other similar functions found in @@ -624,8 +608,7 @@ package for scheduling processing of arrays. [sched]: http://jd.bukkit.org/beta/apidocs/org/bukkit/scheduler/package-summary.html -Parameters ----------- +#### Parameters * array : The array to be processed - It can be a javascript array, a java array or java.util.Collection * callback : The function to be called to process each item in the @@ -654,11 +637,11 @@ subsequent items are processed later). If your code relies on the completion of the array processing, then provide an `onDone` parameter and put the code there. -Example -------- +#### Example + The following example illustrates how to use foreach for immediate processing of an array... - var utils = require('./utils/_utils'); + var utils = require('utils'); var players = ["moe", "larry", "curly"]; utils.foreach (players, function(item){ server.getPlayer(item).sendMessage("Hi " + item); @@ -678,7 +661,7 @@ without hogging CPU usage... // build a structure 200 wide x 200 tall x 200 long // (That's 8 Million Blocks - enough to tax any machine!) - var utils = require('./utils/_utils'); + var utils = require('utils'); var a = []; a.length = 200; @@ -695,8 +678,8 @@ without hogging CPU usage... }; utils.foreach (a, processItem, null, 10, onDone); -utils.nicely() function -======================= +### utils.nicely() function + The utils.nicely() function is for performing processing using the [org.bukkit.scheduler][sched] package/API. utils.nicely() lets you process with a specified delay between the completion of each `next()` @@ -704,8 +687,7 @@ function and the start of the next `next()` function. `utils.nicely()` is a recursive function - that is - it calls itself (schedules itself actually) repeatedly until `hasNext` returns false. -Parameters ----------- +#### Parameters * next : A function which will be called if processing is to be done. * hasNext : A function which is called to determine if the `next` @@ -715,29 +697,27 @@ Parameters * onDone : A function which is to be called when all processing is complete (hasNext returned false). * delay : The delay (in server ticks - 20 per second) between each call. -Example -------- +#### Example + See the source code to utils.foreach for an example of how utils.nicely is used. -utils.at() function -=================== +### utils.at() function + The utils.at() function will perform a given task at a given time every (minecraft) day. -Parameters ----------- +#### Parameters * time24hr : The time in 24hr form - e.g. 9:30 in the morning is "09:30" while 9:30 pm is "21:30", midnight is "00:00" and midday is "12:00" * callback : A javascript function which will be invoked at the given time. * world : (optional) Each world has its own clock. If no world is specified, the server's first world is used. -Example -------- +#### Example To warn players when night is approaching... - var utils = require('./utils/_utils'); + var utils = require('utils'); utils.at( "19:00", function() { @@ -747,6 +727,25 @@ To warn players when night is approaching... }, self.world); +### utils.find() function + +The utils.find() function will return a list of all files starting at +a given directory and recursiving trawling all sub-directories. + +#### Parameters + + * dir : The starting path. Must be a string. + * filter : (optional) A [FilenameFilter][fnfltr] object to return only files matching a given pattern. + +[fnfltr]: http://docs.oracle.com/javase/6/docs/api/java/io/FilenameFilter.html + +#### Example + + var utils = require('utils'); + var jsFiles = utils.find('./', function(dir,name){ + return name.match(/\.js$/); + }); + String class extensions ----------------------- The following chat-formatting methods are added to the javascript String class.. @@ -788,28 +787,34 @@ Example

Hello World

-Blocks Module -============= -You hate having to lookup [Data Values][dv] when you use ScriptCraft's Drone() functions. So do I. -So I created this blocks object which is a helper object for use in construction. +## Blocks Module -Examples --------- +You hate having to lookup [Data Values][dv] when you use ScriptCraft's +Drone() functions. So do I. So I created this blocks object which is +a helper object for use in construction. + +### Examples box( blocks.oak ); // creates a single oak wood block box( blocks.sand, 3, 2, 1 ); // creates a block of sand 3 wide x 2 high x 1 long box( blocks.wool.green, 2 ); // creates a block of green wool 2 blocks wide -Color aliased properties that were a direct descendant of the blocks object are no longer used to avoid confusion with carpet and stained clay blocks. In addition, there's a convenience array `blocks.rainbow` which is an array of the 7 colors of the rainbow (or closest approximations). +Color aliased properties that were a direct descendant of the blocks +object are no longer used to avoid confusion with carpet and stained +clay blocks. In addition, there's a convenience array `blocks.rainbow` +which is an array of the 7 colors of the rainbow (or closest +approximations). + +The blocks module is globally exported by the Drone module. + +## Fireworks Module -Fireworks Module -================ The fireworks module makes it easy to create fireworks using ScriptCraft. The module has a single function `firework` which takes a `org.bukkit.Location` as its 1 and only parameter. -Examples --------- +### Examples + The module also extends the `Drone` object adding a `firework` method so that fireworks can be created as a part of a Drone chain. For Example.... @@ -1533,19 +1538,18 @@ Another example: This statement creates a row of trees 2 by 3 ... ![times example 1](img/times-trees.png) -Drone.blocktype() method -======================== +## Drone.blocktype() method + Creates the text out of blocks. Useful for large-scale in-game signs. -Parameters ----------- +### Parameters * message - The message to create - (use `\n` for newlines) * foregroundBlock (default: black wool) - The block to use for the foreground * backgroundBlock (default: none) - The block to use for the background -Example -------- +### Example + To create a 2-line high message using glowstone... blocktype("Hello\nWorld",blocks.glowstone); @@ -1554,18 +1558,17 @@ To create a 2-line high message using glowstone... [imgbt1]: img/blocktype1.png -Drone.sphere() method -===================== +## Drone.sphere() method + Creates a sphere. -Parameters ----------- +### Parameters * block - The block the sphere will be made of. * radius - The radius of the sphere. -Example -------- +### Example + To create a sphere of Iron with a radius of 10 blocks... sphere( blocks.iron, 10); @@ -1575,18 +1578,17 @@ To create a sphere of Iron with a radius of 10 blocks... Spheres are time-consuming to make. You *can* make large spheres (250 radius) but expect the server to be very busy for a couple of minutes while doing so. -Drone.sphere0() method -====================== +## Drone.sphere0() method + Creates an empty sphere. -Parameters ----------- +### Parameters * block - The block the sphere will be made of. * radius - The radius of the sphere. -Example -------- +### Example + To create a sphere of Iron with a radius of 10 blocks... sphere0( blocks.iron, 10); @@ -1594,67 +1596,62 @@ To create a sphere of Iron with a radius of 10 blocks... Spheres are time-consuming to make. You *can* make large spheres (250 radius) but expect the server to be very busy for a couple of minutes while doing so. -Drone.hemisphere() method -========================= +## Drone.hemisphere() method + Creates a hemisphere. Hemispheres can be either north or south. -Parameters ----------- +### Parameters * block - the block the hemisphere will be made of. * radius - the radius of the hemisphere * northSouth - whether the hemisphere is 'north' or 'south' -Example -------- +### Example + To create a wood 'north' hemisphere with a radius of 7 blocks... hemisphere(blocks.oak, 7, 'north'); ![hemisphere example](img/hemisphereex1.png) -Drone.hemisphere0() method -========================= +## Drone.hemisphere0() method + Creates a hollow hemisphere. Hemispheres can be either north or south. -Parameters ----------- +### Parameters * block - the block the hemisphere will be made of. * radius - the radius of the hemisphere * northSouth - whether the hemisphere is 'north' or 'south' -Example -------- +### Example + To create a glass 'north' hemisphere with a radius of 20 blocks... hemisphere0(blocks.glass, 20, 'north'); ![hemisphere example](img/hemisphereex2.png) -Drone.rainbow() method -====================== +## Drone.rainbow() method + Creates a Rainbow. -Parameters ----------- +### Parameters * radius (optional - default:18) - The radius of the rainbow -Example -------- +### Example var d = new Drone(); d.rainbow(30); ![rainbow example](img/rainbowex1.png) -Drone.spiral_stairs() method -============================ +## Drone.spiral_stairs() method + Constructs a spiral staircase with slabs at each corner. -Parameters ----------- +### Parameters * stairBlock - The block to use for stairs, should be one of the following... - 'oak' @@ -1671,8 +1668,8 @@ Parameters ![Spiral Staircase](img/spiralstair1.png) -Example -------- +### Example + To construct a spiral staircase 5 floors high made of oak... spiral_stairs('oak', 5); @@ -1751,8 +1748,8 @@ Aliases can be used at the in-game prompt by players or in the server console. Aliases will not be able to avail of command autocompletion (pressing the TAB key will have no effect). -Classroom Module -================ +## Classroom Module + The `classroom` object contains a couple of utility functions for use in a classroom setting. The goal of these functions is to make it easier for tutors to facilitate ScriptCraft for use by students in a @@ -1767,19 +1764,18 @@ The goal of this module is not so much to enforce restrictions (security or otherwise) but to make it easier for tutors to setup a shared server so students can learn Javascript. -classroom.allowScripting() function -=================================== +### classroom.allowScripting() function + Allow or disallow anyone who connects to the server (or is already connected) to use ScriptCraft. This function is preferable to granting 'ops' privileges to every student in a Minecraft classroom environment. -Parameters ----------- +#### Parameters * canScript : true or false -Example -------- +#### Example + To allow all players (and any players who connect to the server) to use the `js` and `jsp` commands... diff --git a/src/main/javascript/lib/events.js b/src/main/javascript/lib/events.js index ea292e3..fdb4357 100644 --- a/src/main/javascript/lib/events.js +++ b/src/main/javascript/lib/events.js @@ -1,17 +1,17 @@ /************************************************************************ -events Module -============= +## events Module + The Events module provides a thin wrapper around Bukkit's Event-handling API. Bukkit's Events API makes use of Java Annotations which are not available in Javascript, so this module provides a simple way to listen to minecraft events in javascript. -events.on() static method -========================= +### events.on() static method + This method is used to register event listeners. -Parameters ----------- +#### Parameters + * eventName - A string or java class. If a string is supplied it must be part of the Bukkit event class name. See [Bukkit API][buk] for @@ -35,54 +35,45 @@ Parameters "NORMAL", "MONITOR". For an explanation of what the different priorities mean refer to bukkit's [Event API Reference][buk2]. -Returns -------- +#### Returns + An org.bukkit.plugin.RegisteredListener object which can be used to unregister the listener. This same object is passed to the callback function each time the event is fired. -Example: ------- +#### Example: + The following code will print a message on screen every time a block is broken in the game - var events = require('./events/events'); - - events.on("block.BlockBreakEvent", function(listener, evt){ - echo (evt.player.name + " broke a block!"); + events.on('block.BlockBreakEvent', function(listener, evt){ + evt.player.sendMessage( evt.player.name + ' broke a block!'); }); To handle an event only once and unregister from further events... - var events = require('./events/events'); - - events.on("block.BlockBreakEvent", function(listener, evt){ - print (evt.player.name + " broke a block!"); + events.on('block.BlockBreakEvent', function(listener, evt){ + evt.player.sendMessage( evt.player.name + ' broke a block!'); evt.handlers.unregister(listener); }); To unregister a listener *outside* of the listener function... - var events = require('./events/events'); - - var myBlockBreakListener = events.on("block.BlockBreakEvent",function(l,e){ ... }); + var myBlockBreakListener = events.on('block.BlockBreakEvent',function(l,e){ ... }); ... var handlers = org.bukkit.event.block.BlockBreakEvent.getHandlerList(); handlers.unregister(myBlockBreakListener); +To listen for events using a full class name as the `eventName` parameter... + + events.on(org.bukkit.event.block.BlockBreakEvent, function(listener, evt){ + evt.player.sendMessage( evt.player.name + ' broke a block!'); + }); + [buk2]: http://wiki.bukkit.org/Event_API_Reference [buk]: http://jd.bukkit.org/dev/apidocs/index.html?org/bukkit/event/Event.html ***/ -// -// handle events in Minecraft -// -------------------------- -// eventType can be a string (assumed to be a sub package of org.bukkit.event - e.g. -// if the string "block.BlockBreakEvent" is supplied then it's converted to the -// org.bukkit.event.block.BlockBreakEvent class . For custom event classes, just -// supply the custom event class e.g. -// events.on(net.yourdomain.events.YourCustomEvent,function(l,e){ ... }); -// var bkEvent = org.bukkit.event; var bkEvtExecutor = org.bukkit.plugin.EventExecutor; var bkRegListener = org.bukkit.plugin.RegisteredListener; diff --git a/src/main/javascript/lib/plugin.js b/src/main/javascript/lib/plugin.js index 331b88a..77e4086 100644 --- a/src/main/javascript/lib/plugin.js +++ b/src/main/javascript/lib/plugin.js @@ -139,32 +139,6 @@ exports.autoload = function(dir) { } } }; - /* - sort so that .js files with same name as parent directory appear before - other files in the same directory - */ - var sortByModule = function(a,b){ - a = _canonize(a); - b = _canonize(b); - var aparts = (""+a).split(/\//); - var bparts = (""+b).split(/\//); - //var adir = aparts[aparts.length-2]; - var adir = aparts.slice(0,aparts.length-1).join("/"); - var afile = aparts[aparts.length-1]; - //var bdir = bparts[bparts.length-2]; - var bdir = bparts.slice(0,bparts.length-1).join("/"); - var bfile = bparts[bparts.length-1]; - - if(adirbdir) return 1; - - afile = afile.match(/[a-zA-Z0-9\-_]+/)[0]; - - if (adir.match(new RegExp(afile + "$"))) - return -1; - else - return 1; - }; /* Reload all of the .js files in the given directory */ @@ -174,18 +148,6 @@ exports.autoload = function(dir) { var sourceFiles = []; _listSourceFiles(sourceFiles,pluginDir); - //sourceFiles.sort(sortByModule); - - // - // script files whose name begins with _ (underscore) - // will not be loaded automatically at startup. - // These files are assumed to be dependencies/private to plugins - // - // E.g. If you have a plugin called myMiniGame.js in the myMiniGame directory - // and which in addition to myMiniGame.js also includes _myMiniGame_currency.js _myMiniGame_events.js etc. - // then it's assumed that _myMiniGame_currency.js and _myMiniGame_events.js will be loaded - // as dependencies by myMiniGame.js and do not need to be loaded via js reload - // var len = sourceFiles.length; if (config.verbose) logger.info(len + " scriptcraft plugins found."); diff --git a/src/main/javascript/lib/require.js b/src/main/javascript/lib/require.js index 74648b4..e6e5847 100644 --- a/src/main/javascript/lib/require.js +++ b/src/main/javascript/lib/require.js @@ -1,5 +1,5 @@ /************************************************************************* -## Require - Node.js-style module loading in ScriptCraft +## require - Node.js-style module loading in ScriptCraft Node.js is a server-side javascript environment with an excellent module loading system based on CommonJS. Modules in Node.js are really @@ -41,12 +41,12 @@ and modules themeselves can use other modules. Modules have full control over what functions and properties they want to provide to others. -## Important +### Important Although ScriptCraft now supports Node.js style modules, it does not support node modules. Node.js and Rhino are two very different Javascript environments. ScriptCraft uses Rhino Javascript, not -Node.js. +Node.js. Standard Node.js modules such as `'fs'` are not available in ScriptCraft. Modules can be loaded using relative or absolute paths. Per the CommonJS module specification, the '.js' suffix is optional. @@ -100,7 +100,9 @@ module specification, the '.js' suffix is optional. var resolveModuleToFile = function(moduleName, parentDir) { /********************************************************************** -## When resolving module names to file paths, ScriptCraft uses the following rules... +### module name resolution + +When resolving module names to file paths, ScriptCraft uses the following rules... 1. if the module does not begin with './' or '/' then ... diff --git a/src/main/javascript/lib/scriptcraft.js b/src/main/javascript/lib/scriptcraft.js index 4b1ee83..7a08ef5 100644 --- a/src/main/javascript/lib/scriptcraft.js +++ b/src/main/javascript/lib/scriptcraft.js @@ -1,3 +1,4 @@ +var global = this; /************************************************************************ # ScriptCraft API Reference @@ -116,26 +117,35 @@ provided. The `lib` directory is for internal use by ScriptCraft. Modules in this directory are not automatically loaded nor are they globally exported. -### Directories +### plugins sub-directories As of December 24 2013, the `scriptcraft/plugins` directory has the following sub-directories... * drone - Contains the drone module and drone extensions. Drone was the first scriptcraft module. * mini-games - Contains mini-games - * arrows - The arrows module - * signs - The signs module (includes example signs) - * chat - The chat plugin/module - * alias - The alias plugin/module + * 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. * home - The home module - for setting homes and visiting other homes. -## Core Module: functions +## Global functions -ScripCraft provides some functions which can be used by all plugins/modules... +ScripCraft provides some global functions which can be used by all plugins/modules... - * echo (message) - Displays a message on the screen. +### 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). + +### Example + + /js echo('Hello World') + + +* echo (message) - Displays a message on the screen. For example: `/js echo('Hello World')` will print Hello World on the in-game chat window. 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')` + `alert` works exactly the same as `echo` e.g. `alert('Hello World')`. * require (modulename) - Will load modules. See [Node.js modules][njsmod] @@ -184,7 +194,7 @@ load() should only be used to load .json data. * filename - The name of the file to load. * warnOnFileNotFound (optional - default: false) - warn if the file was not found. -#### Return +#### Returns load() will return the result of the last statement evaluated in the file. @@ -284,40 +294,13 @@ plugin author) safely expose javascript functions for use by players. See chat/colors.js or alias/alias.js or homes/homes.js for examples of how to use the `command()` function. -### ready() function - -The `ready()` function provides a way for plugins to do additional -setup once all of the other plugins/modules have loaded. For example, -event listener registration can only be done after the -events/events.js module has loaded. A plugin author could load the -file explicilty like this... - - load(__folder + "../events/events.js"); - - // event listener registration goes here - -... or better still, just do event regristration using the `ready()` -handler knowing that by the time the `ready()` callback is invoked, -all of the scriptcraft modules have been loaded... - - ready(function(){ - // event listener registration goes here - // code that depends on other plugins/modules also goes here - }); - -The execution of the function object passed to the `ready()` function -is *deferred* until all of the plugins/modules have loaded. That way -you are guaranteed that when the function is invoked, all of the -plugins/modules have been loaded and evaluated and are ready to use. - ***/ -var global = this; /************************************************************************* -Core Module - Special Variables -=============================== +## global variables + There are a couple of special javascript variables available in ScriptCraft... * __folder - The current working directory - this variable is only to be used within the main body of a .js file. @@ -545,13 +528,7 @@ See [issue #69][issue69] for more information. */ var modulePaths = [jsPluginsRootDirName + '/lib/', jsPluginsRootDirName + '/modules/']; - global.require = fnRequire(__plugin.logger, - __engine, - config.verbose, - jsPluginsRootDirName, - modulePaths); - - + global.require = fnRequire(__plugin.logger, __engine, config.verbose, jsPluginsRootDirName, modulePaths); var plugins = require('plugin'); global._onTabComplete = require('tabcomplete'); @@ -573,8 +550,6 @@ See [issue #69][issue69] for more information. global.events = events; plugins.autoload(jsPluginsRootDir); - - }()); diff --git a/src/main/javascript/modules/blocks.js b/src/main/javascript/modules/blocks.js index 19e0c0c..019a6bf 100644 --- a/src/main/javascript/modules/blocks.js +++ b/src/main/javascript/modules/blocks.js @@ -1,17 +1,23 @@ /************************************************************************ -Blocks Module -============= -You hate having to lookup [Data Values][dv] when you use ScriptCraft's Drone() functions. So do I. -So I created this blocks object which is a helper object for use in construction. +## Blocks Module -Examples --------- +You hate having to lookup [Data Values][dv] when you use ScriptCraft's +Drone() functions. So do I. So I created this blocks object which is +a helper object for use in construction. + +### Examples box( blocks.oak ); // creates a single oak wood block box( blocks.sand, 3, 2, 1 ); // creates a block of sand 3 wide x 2 high x 1 long box( blocks.wool.green, 2 ); // creates a block of green wool 2 blocks wide -Color aliased properties that were a direct descendant of the blocks object are no longer used to avoid confusion with carpet and stained clay blocks. In addition, there's a convenience array `blocks.rainbow` which is an array of the 7 colors of the rainbow (or closest approximations). +Color aliased properties that were a direct descendant of the blocks +object are no longer used to avoid confusion with carpet and stained +clay blocks. In addition, there's a convenience array `blocks.rainbow` +which is an array of the 7 colors of the rainbow (or closest +approximations). + +The blocks module is globally exported by the Drone module. ***/ var blocks = { diff --git a/src/main/javascript/modules/fireworks/fireworks.js b/src/main/javascript/modules/fireworks/fireworks.js index 3703649..e3e160a 100644 --- a/src/main/javascript/modules/fireworks/fireworks.js +++ b/src/main/javascript/modules/fireworks/fireworks.js @@ -1,12 +1,12 @@ /************************************************************************ -Fireworks Module -================ +## Fireworks Module + The fireworks module makes it easy to create fireworks using ScriptCraft. The module has a single function `firework` which takes a `org.bukkit.Location` as its 1 and only parameter. -Examples --------- +### Examples + The module also extends the `Drone` object adding a `firework` method so that fireworks can be created as a part of a Drone chain. For Example.... diff --git a/src/main/javascript/modules/http/request.js b/src/main/javascript/modules/http/request.js index eb47677..9be0b36 100644 --- a/src/main/javascript/modules/http/request.js +++ b/src/main/javascript/modules/http/request.js @@ -1,14 +1,13 @@ /************************************************************************* -http.request() function -==================== +## http.request() function + The http.request() function will fetch a web address asynchronously (on a separate thread)and pass the URL's response to a callback function which will be executed synchronously (on the main thread). In this way, http.request() can be used to fetch web content without blocking the main thread of execution. -Parameters ----------- +### Parameters * request: The request details either a plain URL e.g. "http://scriptcraft.js/sample.json" or an object with the following properties... @@ -20,8 +19,8 @@ Parameters - responseCode: The numeric response code from the server. If the server did not respond with 200 OK then the response parameter will be undefined. - response: A string (if the response is of type text) or object containing the HTTP response body. -Example -------- +### Example + The following example illustrates how to use http.request to make a request to a JSON web service and evaluate its response... var jsResponse; diff --git a/src/main/javascript/modules/utils/utils.js b/src/main/javascript/modules/utils/utils.js index 8ef848c..bc8362b 100644 --- a/src/main/javascript/modules/utils/utils.js +++ b/src/main/javascript/modules/utils/utils.js @@ -1,6 +1,6 @@ /************************************************************************ -Utilities Module -================ +## Utilities Module + Miscellaneous utility functions and classes to help with programming. * locationToString(Location) - returns a bukkit Location object in string form. @@ -56,8 +56,8 @@ exports.getMousePos = function (player) { return targetedBlock.location; }; /************************************************************************ -foreach() function -======================== +### foreach() function + The utils.foreach() function is a utility function for iterating over an array of objects (or a java.util.Collection of objects) and processing each object in turn. Where utils.foreach() differs from other similar functions found in @@ -70,8 +70,7 @@ package for scheduling processing of arrays. [sched]: http://jd.bukkit.org/beta/apidocs/org/bukkit/scheduler/package-summary.html -Parameters ----------- +#### Parameters * array : The array to be processed - It can be a javascript array, a java array or java.util.Collection * callback : The function to be called to process each item in the @@ -100,11 +99,11 @@ subsequent items are processed later). If your code relies on the completion of the array processing, then provide an `onDone` parameter and put the code there. -Example -------- +#### Example + The following example illustrates how to use foreach for immediate processing of an array... - var utils = require('./utils/_utils'); + var utils = require('utils'); var players = ["moe", "larry", "curly"]; utils.foreach (players, function(item){ server.getPlayer(item).sendMessage("Hi " + item); @@ -124,7 +123,7 @@ without hogging CPU usage... // build a structure 200 wide x 200 tall x 200 long // (That's 8 Million Blocks - enough to tax any machine!) - var utils = require('./utils/_utils'); + var utils = require('utils'); var a = []; a.length = 200; @@ -159,8 +158,8 @@ var _foreach = function(array, callback, object, delay, onCompletion) { }; exports.foreach = _foreach; /************************************************************************ -utils.nicely() function -======================= +### utils.nicely() function + The utils.nicely() function is for performing processing using the [org.bukkit.scheduler][sched] package/API. utils.nicely() lets you process with a specified delay between the completion of each `next()` @@ -168,8 +167,7 @@ function and the start of the next `next()` function. `utils.nicely()` is a recursive function - that is - it calls itself (schedules itself actually) repeatedly until `hasNext` returns false. -Parameters ----------- +#### Parameters * next : A function which will be called if processing is to be done. * hasNext : A function which is called to determine if the `next` @@ -179,8 +177,8 @@ Parameters * onDone : A function which is to be called when all processing is complete (hasNext returned false). * delay : The delay (in server ticks - 20 per second) between each call. -Example -------- +#### Example + See the source code to utils.foreach for an example of how utils.nicely is used. ***/ @@ -196,25 +194,23 @@ exports.nicely = function(next, hasNext, onDone, delay){ } }; /************************************************************************ -utils.at() function -=================== +### utils.at() function + The utils.at() function will perform a given task at a given time every (minecraft) day. -Parameters ----------- +#### Parameters * time24hr : The time in 24hr form - e.g. 9:30 in the morning is "09:30" while 9:30 pm is "21:30", midnight is "00:00" and midday is "12:00" * callback : A javascript function which will be invoked at the given time. * world : (optional) Each world has its own clock. If no world is specified, the server's first world is used. -Example -------- +#### Example To warn players when night is approaching... - var utils = require('./utils/_utils'); + var utils = require('utils'); utils.at( "19:00", function() { @@ -246,6 +242,27 @@ exports.at = function(time24hr, callback, world) { },forever, null, 100); }; +/************************************************************************ +### utils.find() function + +The utils.find() function will return a list of all files starting at +a given directory and recursiving trawling all sub-directories. + +#### Parameters + + * dir : The starting path. Must be a string. + * filter : (optional) A [FilenameFilter][fnfltr] object to return only files matching a given pattern. + +[fnfltr]: http://docs.oracle.com/javase/6/docs/api/java/io/FilenameFilter.html + +#### Example + + var utils = require('utils'); + var jsFiles = utils.find('./', function(dir,name){ + return name.match(/\.js$/); + }); + +***/ exports.find = function( dir , filter){ var result = []; var recurse = function(dir, store){ diff --git a/src/main/javascript/plugins/classroom/classroom.js b/src/main/javascript/plugins/classroom/classroom.js index 13f6d1e..a1f07fe 100644 --- a/src/main/javascript/plugins/classroom/classroom.js +++ b/src/main/javascript/plugins/classroom/classroom.js @@ -1,8 +1,8 @@ var utils = require('utils'); /************************************************************************ -Classroom Module -================ +## Classroom Module + The `classroom` object contains a couple of utility functions for use in a classroom setting. The goal of these functions is to make it easier for tutors to facilitate ScriptCraft for use by students in a @@ -17,19 +17,18 @@ The goal of this module is not so much to enforce restrictions (security or otherwise) but to make it easier for tutors to setup a shared server so students can learn Javascript. -classroom.allowScripting() function -=================================== +### classroom.allowScripting() function + Allow or disallow anyone who connects to the server (or is already connected) to use ScriptCraft. This function is preferable to granting 'ops' privileges to every student in a Minecraft classroom environment. -Parameters ----------- +#### Parameters * canScript : true or false -Example -------- +#### Example + To allow all players (and any players who connect to the server) to use the `js` and `jsp` commands... @@ -77,5 +76,5 @@ events.on('player.PlayerLoginEvent', function(listener, event) { if (_store.enableScripting){ player.addAttachment(__plugin, "scriptcraft.*", true); } -}, "HIGHEST"); +}, 'HIGHEST'); diff --git a/src/main/javascript/plugins/drone/blocktype.js b/src/main/javascript/plugins/drone/blocktype.js index 7deced8..f142e34 100644 --- a/src/main/javascript/plugins/drone/blocktype.js +++ b/src/main/javascript/plugins/drone/blocktype.js @@ -2,19 +2,18 @@ var Drone = require('./drone').Drone; var blocks = require('blocks'); /************************************************************************ -Drone.blocktype() method -======================== +## Drone.blocktype() method + Creates the text out of blocks. Useful for large-scale in-game signs. -Parameters ----------- +### Parameters * message - The message to create - (use `\n` for newlines) * foregroundBlock (default: black wool) - The block to use for the foreground * backgroundBlock (default: none) - The block to use for the background -Example -------- +### Example + To create a 2-line high message using glowstone... blocktype("Hello\nWorld",blocks.glowstone); diff --git a/src/main/javascript/plugins/drone/contrib/rainbow.js b/src/main/javascript/plugins/drone/contrib/rainbow.js index f675b5f..cf8cad3 100644 --- a/src/main/javascript/plugins/drone/contrib/rainbow.js +++ b/src/main/javascript/plugins/drone/contrib/rainbow.js @@ -2,17 +2,15 @@ var Drone = require('../drone').Drone; var blocks = require('blocks'); /************************************************************************ -Drone.rainbow() method -====================== +## Drone.rainbow() method + Creates a Rainbow. -Parameters ----------- +### Parameters * radius (optional - default:18) - The radius of the rainbow -Example -------- +### Example var d = new Drone(); d.rainbow(30); diff --git a/src/main/javascript/plugins/drone/contrib/spiral_stairs.js b/src/main/javascript/plugins/drone/contrib/spiral_stairs.js index 7385ffc..5f6bf35 100644 --- a/src/main/javascript/plugins/drone/contrib/spiral_stairs.js +++ b/src/main/javascript/plugins/drone/contrib/spiral_stairs.js @@ -2,12 +2,11 @@ var Drone = require('../drone').Drone; var blocks = require('blocks'); /************************************************************************ -Drone.spiral_stairs() method -============================ +## Drone.spiral_stairs() method + Constructs a spiral staircase with slabs at each corner. -Parameters ----------- +### Parameters * stairBlock - The block to use for stairs, should be one of the following... - 'oak' @@ -24,8 +23,8 @@ Parameters ![Spiral Staircase](img/spiralstair1.png) -Example -------- +### Example + To construct a spiral staircase 5 floors high made of oak... spiral_stairs('oak', 5); diff --git a/src/main/javascript/plugins/drone/sphere.js b/src/main/javascript/plugins/drone/sphere.js index ab0234c..2dbdf5c 100644 --- a/src/main/javascript/plugins/drone/sphere.js +++ b/src/main/javascript/plugins/drone/sphere.js @@ -1,18 +1,17 @@ var Drone = require('./drone').Drone; /************************************************************************ -Drone.sphere() method -===================== +## Drone.sphere() method + Creates a sphere. -Parameters ----------- +### Parameters * block - The block the sphere will be made of. * radius - The radius of the sphere. -Example -------- +### Example + To create a sphere of Iron with a radius of 10 blocks... sphere( blocks.iron, 10); @@ -68,18 +67,17 @@ Drone.extend('sphere', function(block,radius) return this.move('sphere'); }); /************************************************************************ -Drone.sphere0() method -====================== +## Drone.sphere0() method + Creates an empty sphere. -Parameters ----------- +### Parameters * block - The block the sphere will be made of. * radius - The radius of the sphere. -Example -------- +### Example + To create a sphere of Iron with a radius of 10 blocks... sphere0( blocks.iron, 10); @@ -168,19 +166,18 @@ Drone.extend('sphere0', function(block,radius) }); /************************************************************************ -Drone.hemisphere() method -========================= +## Drone.hemisphere() method + Creates a hemisphere. Hemispheres can be either north or south. -Parameters ----------- +### Parameters * block - the block the hemisphere will be made of. * radius - the radius of the hemisphere * northSouth - whether the hemisphere is 'north' or 'south' -Example -------- +### Example + To create a wood 'north' hemisphere with a radius of 7 blocks... hemisphere(blocks.oak, 7, 'north'); @@ -239,19 +236,18 @@ Drone.extend('hemisphere', function(block,radius, northSouth){ }); /************************************************************************ -Drone.hemisphere0() method -========================= +## Drone.hemisphere0() method + Creates a hollow hemisphere. Hemispheres can be either north or south. -Parameters ----------- +### Parameters * block - the block the hemisphere will be made of. * radius - the radius of the hemisphere * northSouth - whether the hemisphere is 'north' or 'south' -Example -------- +### Example + To create a glass 'north' hemisphere with a radius of 20 blocks... hemisphere0(blocks.glass, 20, 'north'); diff --git a/src/main/javascript/plugins/homes/homes.js b/src/main/javascript/plugins/homes/homes.js index 913720d..01a079c 100644 --- a/src/main/javascript/plugins/homes/homes.js +++ b/src/main/javascript/plugins/homes/homes.js @@ -1,4 +1,7 @@ var utils = require('utils'); +/* + TODO: Document this plugin! +*/ var _store = { houses: {}, openHouses: {}, From a0ad7a8ec6dd53f91ebd96a7cee83b73c1a33336 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 28 Dec 2013 12:12:45 +0000 Subject: [PATCH 055/456] Updated docs for 'console' module and changed error message for failed require() to be more informative. --- docs/API-Reference.md | 12 +++++++++++- src/main/javascript/lib/console.js | 12 +++++++++++- src/main/javascript/lib/require.js | 9 +++++++-- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index b815b92..e03d9b6 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -524,7 +524,7 @@ ScriptCraft provides a `console` global variable with the followng methods... * warn() * error() -The ScriptCraft console methods work like the Web API implementation. +The ScriptCraft console methods work like the [Web API implementation][webcons]. ### Example @@ -536,8 +536,18 @@ uses [java.lang.String.format()][strfmt] for variable substitution. All output will be sent to the server console (not in-game). +### Using string substitutions + +ScriptCraft uses Java's [String.format()][strfmt] so any string substitution identifiers supported by +`java.lang.String.format()` are supported (e.g. %s , %d etc). + + for (var i=0; i<5; i++) { + console.log("Hello, %s. You've called me %d times.", "Bob", i+1); + } + [lgr]: http://jd.bukkit.org/beta/apidocs/org/bukkit/plugin/PluginLogger.html [strfmt]: http://docs.oracle.com/javase/6/docs/api/java/lang/String.html#format(java.lang.String, java.lang.Object...) +[webcons]: https://developer.mozilla.org/en-US/docs/Web/API/console ## http.request() function diff --git a/src/main/javascript/lib/console.js b/src/main/javascript/lib/console.js index 77c27bd..e865221 100644 --- a/src/main/javascript/lib/console.js +++ b/src/main/javascript/lib/console.js @@ -8,7 +8,7 @@ ScriptCraft provides a `console` global variable with the followng methods... * warn() * error() -The ScriptCraft console methods work like the Web API implementation. +The ScriptCraft console methods work like the [Web API implementation][webcons]. ### Example @@ -20,8 +20,18 @@ uses [java.lang.String.format()][strfmt] for variable substitution. All output will be sent to the server console (not in-game). +### Using string substitutions + +ScriptCraft uses Java's [String.format()][strfmt] so any string substitution identifiers supported by +`java.lang.String.format()` are supported (e.g. %s , %d etc). + + for (var i=0; i<5; i++) { + console.log("Hello, %s. You've called me %d times.", "Bob", i+1); + } + [lgr]: http://jd.bukkit.org/beta/apidocs/org/bukkit/plugin/PluginLogger.html [strfmt]: http://docs.oracle.com/javase/6/docs/api/java/lang/String.html#format(java.lang.String, java.lang.Object...) +[webcons]: https://developer.mozilla.org/en-US/docs/Web/API/console ***/ var argsToArray = function(args){ diff --git a/src/main/javascript/lib/require.js b/src/main/javascript/lib/require.js index e6e5847..1cd1677 100644 --- a/src/main/javascript/lib/require.js +++ b/src/main/javascript/lib/require.js @@ -187,7 +187,12 @@ When resolving module names to file paths, ScriptCraft uses the following rules. { var file = resolveModuleToFile(path, parentFile); if (!file){ - throw new Error("require('" + path + "'," + parentFile.canonicalPath + ") failed"); + var errMsg = java.lang.String + .format("require() failed to find matching file for module '%s' " + + "while searching directory '%s' and paths %s.", + [path, parentFile.canonicalPath, JSON.stringify(modulePaths)]); + console.warn(errMsg); + throw new Error(errMsg); } var canonizedFilename = _canonize(file); @@ -237,7 +242,7 @@ When resolving module names to file paths, ScriptCraft uses the following rules. .apply(moduleInfo.exports, /* this */ parameters); } catch (e){ - logger.severe("Error:" + e + " while executing module " + canonizedFilename); + console.error("Error:" + e + " while executing module " + canonizedFilename); throw e; } if (verbose) From 7679a1208f5c5b82ce854f4478fdae8e414fc112 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 28 Dec 2013 22:49:13 +0000 Subject: [PATCH 056/456] Added documentation for the 'homes' module ( issue #105) --- docs/API-Reference.md | 175 +++++++++--- src/main/javascript/lib/console.js | 1 + src/main/javascript/lib/plugin.js | 24 +- src/main/javascript/lib/scriptcraft.js | 252 ++++++++++-------- src/main/javascript/plugins/alias/alias.js | 6 +- src/main/javascript/plugins/homes/homes.js | 66 ++++- .../plugins/minigames/NumberGuess.js | 6 +- .../plugins/minigames/SnowballFight.js | 4 +- 8 files changed, 362 insertions(+), 172 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index e03d9b6..a2f42ef 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -127,27 +127,67 @@ As of December 24 2013, the `scriptcraft/plugins` directory has the following su * alias - The alias plugin/module - for creating custom aliases for commonly used commands. * home - The home module - for setting homes and visiting other homes. +## 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) + +### 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. + ## 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). +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). ### Example /js echo('Hello World') +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')`. -* echo (message) - Displays a message on the screen. - For example: `/js echo('Hello World')` will print Hello World on the in-game chat window. - 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 + +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]: * require (modulename) - Will load modules. See [Node.js modules][njsmod] - * load (filename,warnOnFileNotFound) - loads and evaluates a javascript file, returning the evaluated object. (Note: Prefer `require()` to `load()`) + * load (filename,warnOnFileNotFound) - loads and evaluates a + javascript file, returning the evaluated object. (Note: Prefer + `require()` to `load()`) * save (object, filename) - saves an object to a file. @@ -164,8 +204,9 @@ The `echo()` function displays a message on the in-game screen. The message is d ### 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. +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. #### Parameters @@ -258,7 +299,8 @@ persist data. * 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. + * isPersistent (boolean - optional) : Specifies whether or not the + plugin/object state should be loaded and saved by ScriptCraft. #### Example @@ -290,25 +332,18 @@ plugin author) safely expose javascript functions for use by players. #### Example -See chat/colors.js or alias/alias.js or homes/homes.js for examples of how to use the `command()` function. - -## global variables - -There are a couple of special javascript variables available in ScriptCraft... - - * __folder - The current working directory - this variable is only to be used within the main body of a .js file. - * __plugin - The ScriptCraft JavaPlugin object. - * server - The Minecraft Server object. - * self - the current player. (Note - this value should not be used in multi-threaded scripts - it's not thread-safe) - -## Miscellaneous Core Functions +See chat/colors.js or alias/alias.js or homes/homes.js for examples of +how to use the `command()` function. ### setTimeout() function -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. +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. If Node.js supports setTimeout() then it's probably good for ScriptCraft to support it too. @@ -330,12 +365,16 @@ 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. +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. +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 @@ -356,6 +395,13 @@ 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). + ## require - Node.js-style module loading in ScriptCraft Node.js is a server-side javascript environment with an excellent @@ -1874,9 +1920,68 @@ global commands for a plugin, please let me know. [pcppevt]: http://jd.bukkit.org/dev/apidocs/org/bukkit/event/player/PlayerCommandPreprocessEvent.html -# SnowballFight mini-game +## homes Module -## Description +The homes plugin lets players set a location as home and return to the +location, invite other players to their home and also visit other +player's homes. + +This module is a good example of how to create a javascript-based +minecraft mod which provides... + + * A programmatic interface (API) and + * A command extension which uses that API to provide new functionality for players. + +The module uses the `plugin()` function to specify an object and +methods, and the `command()` function to expose functionality to +players through a new `jsp home` command. This module also +demonstrates how to enable autocompletion for custom commands (to see +this in action, at the in-game prompt or server console prompt type +`jsp home ` then press the TAB key - you should see a list of further +possible options). + +The `jsp home` command has the following options... + +### Basic options + + * `/jsp home set` Will set your current location as your + 'home' location to which you can return at any time using the ... + + * `/jsp home` ..command will return you to your home, if you have set one. + + * `/jsp home ` Will take you to the home of (where + is the name of the player whose home you wish to visit. + + * `/jsp home delete` Deletes your home location from the location + database. This does not actually remove the home from the world or + change the world in any way. This command is completely + non-destructive and cannot be used for griefing. No blocks will be + destroyed by this command. + +### Social options +The following options allow players to open their homes to all or some +players, invite players to their home and see a list of homes they can +visit. + + * `/jsp home list` Lists home which you can visit. + * `/jsp home ilist` Lists players who can visit your home. + * `/jsp home invite ` Invites the named player to your home. + * `/jsp home uninvite ` Uninvites (revokes invitation) the named player to your home. + * `/jsp home public` Opens your home to all players (all players can visit your home). + * `/jsp home private` Makes your home private (no longer visitable by all). + +### Administration options +The following administration options can only be used by server operators... + + * `/jsp home listall` List all of the homes + * `/jsp home clear ` Removes the player's home + location. Again, this command does not destroy any structures in + the world, it simply removes the location from the database. No + blocks are destroyed by this command. + +## SnowballFight mini-game + +### Description This is a rough and ready prototype of a simple multi-player shoot-em-up. To start a game with all players playing against one another... @@ -1915,15 +2020,15 @@ player returns to their previous mode of play (creative or survival). Create a small arena with a couple of small buildings for cover to make the game more fun. -# NumberGuess mini-game: +## NumberGuess mini-game: -## Description +### Description This is a very simple number guessing game. Minecraft will ask you to guess a number between 1 and 10 and you will tell you if you're too hight or too low when you guess wrong. The purpose of this mini-game code is to demonstrate use of Bukkit's Conversation API. -## Example +### Example /js Game_NumberGuess.start() diff --git a/src/main/javascript/lib/console.js b/src/main/javascript/lib/console.js index e865221..122af34 100644 --- a/src/main/javascript/lib/console.js +++ b/src/main/javascript/lib/console.js @@ -34,6 +34,7 @@ ScriptCraft uses Java's [String.format()][strfmt] so any string substitution ide [webcons]: https://developer.mozilla.org/en-US/docs/Web/API/console ***/ +var logger = __plugin.logger; var argsToArray = function(args){ var result = []; for (var i =0;i < args.length; i++) diff --git a/src/main/javascript/lib/plugin.js b/src/main/javascript/lib/plugin.js index 77e4086..26e5fa4 100644 --- a/src/main/javascript/lib/plugin.js +++ b/src/main/javascript/lib/plugin.js @@ -1,3 +1,4 @@ +var console = require('./console'); var File = java.io.File; var FileWriter = java.io.FileWriter; var PrintWriter = java.io.PrintWriter; @@ -88,7 +89,7 @@ var _command = function(name,func,options,intercepts) try { result = func(params); }catch (e){ - logger.severe("Error while trying to execute command: " + JSON.stringify(params)); + console.error("Error while trying to execute command: " + JSON.stringify(params)); throw e; } return result; @@ -150,17 +151,22 @@ exports.autoload = function(dir) { var len = sourceFiles.length; if (config.verbose) - logger.info(len + " scriptcraft plugins found."); + console.info(len + " scriptcraft plugins found."); for (var i = 0;i < len; i++){ var pluginPath = _canonize(sourceFiles[i]); if (config.verbose) - logger.info("Loading plugin: " + pluginPath); - var module = require(pluginPath); - for (var property in module){ - /* - all exports in plugins become global - */ - global[property] = module[property]; + console.info("Loading plugin: " + pluginPath); + var module = {}; + try { + module = require(pluginPath); + for (var property in module){ + /* + all exports in plugins become global + */ + global[property] = module[property]; + } + }catch (e){ + } } }; diff --git a/src/main/javascript/lib/scriptcraft.js b/src/main/javascript/lib/scriptcraft.js index 7a08ef5..513e9d3 100644 --- a/src/main/javascript/lib/scriptcraft.js +++ b/src/main/javascript/lib/scriptcraft.js @@ -1,4 +1,3 @@ -var global = this; /************************************************************************ # ScriptCraft API Reference @@ -129,27 +128,67 @@ As of December 24 2013, the `scriptcraft/plugins` directory has the following su * alias - The alias plugin/module - for creating custom aliases for commonly used commands. * home - The home module - for setting homes and visiting other homes. +## 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) + +### 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. + ## 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). +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). ### Example /js echo('Hello World') +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')`. -* echo (message) - Displays a message on the screen. - For example: `/js echo('Hello World')` will print Hello World on the in-game chat window. - 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 + +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]: * require (modulename) - Will load modules. See [Node.js modules][njsmod] - * load (filename,warnOnFileNotFound) - loads and evaluates a javascript file, returning the evaluated object. (Note: Prefer `require()` to `load()`) + * load (filename,warnOnFileNotFound) - loads and evaluates a + javascript file, returning the evaluated object. (Note: Prefer + `require()` to `load()`) * save (object, filename) - saves an object to a file. @@ -166,8 +205,9 @@ The `echo()` function displays a message on the in-game screen. The message is d ### 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. +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. #### Parameters @@ -260,7 +300,8 @@ persist data. * 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. + * isPersistent (boolean - optional) : Specifies whether or not the + plugin/object state should be loaded and saved by ScriptCraft. #### Example @@ -292,34 +333,90 @@ plugin author) safely expose javascript functions for use by players. #### Example -See chat/colors.js or alias/alias.js or homes/homes.js for examples of how to use the `command()` function. +See chat/colors.js or alias/alias.js or homes/homes.js for examples of +how to use the `command()` function. + +### setTimeout() function + +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. + +If Node.js supports setTimeout() then it's probably good for ScriptCraft to support it too. + +[btdoc]: http://jd.bukkit.org/beta/apidocs/org/bukkit/scheduler/BukkitTask.html + +#### Example + + // + // 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 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). ***/ - - -/************************************************************************* -## global variables - -There are a couple of special javascript variables available in ScriptCraft... - - * __folder - The current working directory - this variable is only to be used within the main body of a .js file. - * __plugin - The ScriptCraft JavaPlugin object. - * server - The Minecraft Server object. - * self - the current player. (Note - this value should not be used in multi-threaded scripts - it's not thread-safe) - -***/ /* wph 20130124 - make self, plugin and server public - these are far more useful now that tab-complete works. */ +var global = this; var server = org.bukkit.Bukkit.server; -// -// private implementation -// +/* + private implementation +*/ (function(){ - // - // don't execute this more than once - // + /* + don't execute this more than once + */ if (typeof load == "function") return ; var File = java.io.File; @@ -333,8 +430,6 @@ var server = org.bukkit.Bukkit.server; var jsPluginsRootDir = parentFileObj.getParentFile(); var jsPluginsRootDirName = _canonize(jsPluginsRootDir); - - var _loaded = {}; /* Load the contents of the file and evaluate as javascript @@ -351,9 +446,9 @@ var server = org.bukkit.Bukkit.server; file = new File(filename); var canonizedFilename = _canonize(file); - // - // wph 20130123 don't load the same file more than once. - // + /* + wph 20130123 don't load the same file more than once. + */ if (_loaded[canonizedFilename]) return _loaded[canonizedFilename]; @@ -399,8 +494,6 @@ var server = org.bukkit.Bukkit.server; if (!config) config = {verbose: false}; global.config = config; - - /* Unload Handlers */ @@ -414,99 +507,32 @@ var server = org.bukkit.Bukkit.server; } }; -/************************************************************************* -## Miscellaneous Core Functions - -### setTimeout() function - -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. - -If Node.js supports setTimeout() then it's probably good for ScriptCraft to support it too. - -[btdoc]: http://jd.bukkit.org/beta/apidocs/org/bukkit/scheduler/BukkitTask.html - -#### Example - - // - // start a storm in 5 seconds - // - setTimeout( function() { - var world = server.worlds.get(0); - world.setStorm(true); - }, 5000); - -***/ global.setTimeout = function( callback, delayInMillis){ - // - // javascript programmers familiar with setTimeout know that it expects - // a delay in milliseconds. However, bukkit's scheduler expects a delay in ticks - // (where 1 tick = 1/20th second) - // + /* + javascript programmers familiar with setTimeout know that it expects + a delay in milliseconds. However, bukkit's scheduler expects a delay in ticks + (where 1 tick = 1/20th second) + */ var bukkitTask = server.scheduler.runTaskLater(__plugin, callback, delayInMillis/50); return bukkitTask; }; - -/************************************************************************* -### clearTimeout() function - -A scriptcraft implementation of clearTimeout(). - -***/ global.clearTimeout = function(bukkitTask){ bukkitTask.cancel(); }; -/************************************************************************* -### 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 - -***/ global.setInterval = function(callback, intervalInMillis){ var delay = intervalInMillis/ 50; var bukkitTask = server.scheduler.runTaskTimer(__plugin, callback, delay, delay); return bukkitTask; }; -/************************************************************************* -### clearInterval() function - -A scriptcraft implementation of clearInterval(). - -***/ global.clearInterval = function(bukkitTask){ bukkitTask.cancel(); }; - -/************************************************************************* -### refresh() function - -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 - -***/ global.refresh = function(){ __plugin.pluginLoader.disablePlugin(__plugin); __plugin.pluginLoader.enablePlugin(__plugin); }; - + var _echo = function (msg) { __plugin.logger.info( msg ); if (typeof self == "undefined"){ @@ -518,7 +544,6 @@ See [issue #69][issue69] for more information. global.echo = _echo; global.alert = _echo; global.load = _load; - global.logger = __plugin.logger; global.addUnloadHandler = _addUnloadHandler; @@ -530,13 +555,13 @@ See [issue #69][issue69] for more information. jsPluginsRootDirName + '/modules/']; global.require = fnRequire(__plugin.logger, __engine, config.verbose, jsPluginsRootDirName, modulePaths); + global.console = require('console'); var plugins = require('plugin'); global._onTabComplete = require('tabcomplete'); global.plugin = plugins.plugin; global.command = plugins.command; global.save = plugins.save; - global.console = require('console'); var events = require('events'); events.on('server.PluginDisableEvent',function(l,e){ @@ -551,6 +576,3 @@ See [issue #69][issue69] for more information. plugins.autoload(jsPluginsRootDir); }()); - - - diff --git a/src/main/javascript/plugins/alias/alias.js b/src/main/javascript/plugins/alias/alias.js index 2e27312..e5a221f 100644 --- a/src/main/javascript/plugins/alias/alias.js +++ b/src/main/javascript/plugins/alias/alias.js @@ -135,7 +135,7 @@ var _list = function(player){ player.sendMessage(alias + " = " + JSON.stringify(_store.global[alias]) ); } }catch(e){ - logger.severe("Error in list function: " + e.message); + console.error("Error in list function: " + e.message); throw e; } }; @@ -173,7 +173,7 @@ var _intercept = function( msg, invoker, exec) isAlias = true; }else{ if (config.verbose){ - logger.info("No global alias found for command: " + command); + console.info("No global alias found for command: " + command); } } /* @@ -186,7 +186,7 @@ var _intercept = function( msg, invoker, exec) isAlias = true; }else{ if (config.verbose){ - logger.info("No player alias found for command: " + command); + console.info("No player alias found for command: " + command); } } for (var i = 0;i < template.length; i++) diff --git a/src/main/javascript/plugins/homes/homes.js b/src/main/javascript/plugins/homes/homes.js index 01a079c..b7af5d6 100644 --- a/src/main/javascript/plugins/homes/homes.js +++ b/src/main/javascript/plugins/homes/homes.js @@ -1,15 +1,71 @@ +/************************************************************************* +## homes Module + +The homes plugin lets players set a location as home and return to the +location, invite other players to their home and also visit other +player's homes. + +This module is a good example of how to create a javascript-based +minecraft mod which provides... + + * A programmatic interface (API) and + * A command extension which uses that API to provide new functionality for players. + +The module uses the `plugin()` function to specify an object and +methods, and the `command()` function to expose functionality to +players through a new `jsp home` command. This module also +demonstrates how to enable autocompletion for custom commands (to see +this in action, at the in-game prompt or server console prompt type +`jsp home ` then press the TAB key - you should see a list of further +possible options). + +The `jsp home` command has the following options... + +### Basic options + + * `/jsp home set` Will set your current location as your + 'home' location to which you can return at any time using the ... + + * `/jsp home` ..command will return you to your home, if you have set one. + + * `/jsp home ` Will take you to the home of (where + is the name of the player whose home you wish to visit. + + * `/jsp home delete` Deletes your home location from the location + database. This does not actually remove the home from the world or + change the world in any way. This command is completely + non-destructive and cannot be used for griefing. No blocks will be + destroyed by this command. + +### Social options +The following options allow players to open their homes to all or some +players, invite players to their home and see a list of homes they can +visit. + + * `/jsp home list` Lists home which you can visit. + * `/jsp home ilist` Lists players who can visit your home. + * `/jsp home invite ` Invites the named player to your home. + * `/jsp home uninvite ` Uninvites (revokes invitation) the named player to your home. + * `/jsp home public` Opens your home to all players (all players can visit your home). + * `/jsp home private` Makes your home private (no longer visitable by all). + +### Administration options +The following administration options can only be used by server operators... + + * `/jsp home listall` List all of the homes + * `/jsp home clear ` Removes the player's home + location. Again, this command does not destroy any structures in + the world, it simply removes the location from the database. No + blocks are destroyed by this command. + +***/ var utils = require('utils'); -/* - TODO: Document this plugin! -*/ var _store = { houses: {}, openHouses: {}, invites: {} }; /* - The homes plugin lets players set a location as home and return to the location, invite - other players to their home and also visit other player's homes. */ var homes = plugin("homes", { help: function(){ diff --git a/src/main/javascript/plugins/minigames/NumberGuess.js b/src/main/javascript/plugins/minigames/NumberGuess.js index d2305cb..d03d7f2 100644 --- a/src/main/javascript/plugins/minigames/NumberGuess.js +++ b/src/main/javascript/plugins/minigames/NumberGuess.js @@ -1,13 +1,13 @@ /************************************************************************* -# NumberGuess mini-game: +## NumberGuess mini-game: -## Description +### Description This is a very simple number guessing game. Minecraft will ask you to guess a number between 1 and 10 and you will tell you if you're too hight or too low when you guess wrong. The purpose of this mini-game code is to demonstrate use of Bukkit's Conversation API. -## Example +### Example /js Game_NumberGuess.start() diff --git a/src/main/javascript/plugins/minigames/SnowballFight.js b/src/main/javascript/plugins/minigames/SnowballFight.js index 9a95c2e..d7ea67a 100644 --- a/src/main/javascript/plugins/minigames/SnowballFight.js +++ b/src/main/javascript/plugins/minigames/SnowballFight.js @@ -1,7 +1,7 @@ /************************************************************************* -# SnowballFight mini-game +## SnowballFight mini-game -## Description +### Description This is a rough and ready prototype of a simple multi-player shoot-em-up. To start a game with all players playing against one another... From 1c05da186251c26c33c5806dc654ce5c735324b4 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 28 Dec 2013 23:02:50 +0000 Subject: [PATCH 057/456] Documentation tweaks. Changed heading levels for the arrows and commando modules. --- docs/API-Reference.md | 16 ++++++++-------- src/main/javascript/plugins/arrows.js | 6 +++--- src/main/javascript/plugins/commando/commando.js | 10 +++++----- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index a2f42ef..9d3dfda 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -1730,9 +1730,9 @@ To construct a spiral staircase 5 floors high made of oak... spiral_stairs('oak', 5); -# Arrows Module +## Arrows Module -## Description +### Description The arrows mod adds fancy arrows to the game. Arrows which... * Launch fireworks. @@ -1741,7 +1741,7 @@ The arrows mod adds fancy arrows to the game. Arrows which... * Teleport the player to the landing spot. * Spawn Trees at the landing spot. -## Usage: +### Usage: * `/js arrows.firework()` - A firework launches where the the arrow lands. * `/js arrows.lightning()` - lightning strikes where the arrow lands. @@ -1844,9 +1844,9 @@ To disallow scripting (and prevent players who join the server from using the co Only ops users can run the classroom.allowScripting() function - this is so that students don't try to bar themselves and each other from scripting. -# Commando Plugin +## Commando Plugin -## Description +### Description commando is a plugin which can be used to add completely new commands to Minecraft. Normally ScriptCraft only allows for provision of new @@ -1887,7 +1887,7 @@ type `/jsp hi` for the above command example, players simply type `/hi` . This functionality is provided as a plugin rather than as part of the ScriptCraft core. -## Example hi-command.js +### Example hi-command.js var commando = require('../commando'); commando('hi', function(){ @@ -1896,7 +1896,7 @@ of the ScriptCraft core. ...Displays a greeting to any player who issues the `/hi` command. -## Example - timeofday-command.js +### Example - timeofday-command.js var times = {Dawn: 0, Midday: 6000, Dusk: 12000, Midnight:18000}; commando('timeofday', function(params){ @@ -1906,7 +1906,7 @@ of the ScriptCraft core. ... changes the time of day using a new `/timeofday` command (options are Dawn, Midday, Dusk, Midnight) -## Caveats +### Caveats Since commands registered using commando are really just appendages to the `/jsp` command and are not actually registered globally (it just diff --git a/src/main/javascript/plugins/arrows.js b/src/main/javascript/plugins/arrows.js index ef2ed6f..5b87fab 100644 --- a/src/main/javascript/plugins/arrows.js +++ b/src/main/javascript/plugins/arrows.js @@ -1,7 +1,7 @@ /************************************************************************* -# Arrows Module +## Arrows Module -## Description +### Description The arrows mod adds fancy arrows to the game. Arrows which... * Launch fireworks. @@ -10,7 +10,7 @@ The arrows mod adds fancy arrows to the game. Arrows which... * Teleport the player to the landing spot. * Spawn Trees at the landing spot. -## Usage: +### Usage: * `/js arrows.firework()` - A firework launches where the the arrow lands. * `/js arrows.lightning()` - lightning strikes where the arrow lands. diff --git a/src/main/javascript/plugins/commando/commando.js b/src/main/javascript/plugins/commando/commando.js index 871e62b..403bee9 100644 --- a/src/main/javascript/plugins/commando/commando.js +++ b/src/main/javascript/plugins/commando/commando.js @@ -1,7 +1,7 @@ /************************************************************************* -# Commando Plugin +## Commando Plugin -## Description +### Description commando is a plugin which can be used to add completely new commands to Minecraft. Normally ScriptCraft only allows for provision of new @@ -42,7 +42,7 @@ type `/jsp hi` for the above command example, players simply type `/hi` . This functionality is provided as a plugin rather than as part of the ScriptCraft core. -## Example hi-command.js +### Example hi-command.js var commando = require('../commando'); commando('hi', function(){ @@ -51,7 +51,7 @@ of the ScriptCraft core. ...Displays a greeting to any player who issues the `/hi` command. -## Example - timeofday-command.js +### Example - timeofday-command.js var times = {Dawn: 0, Midday: 6000, Dusk: 12000, Midnight:18000}; commando('timeofday', function(params){ @@ -61,7 +61,7 @@ of the ScriptCraft core. ... changes the time of day using a new `/timeofday` command (options are Dawn, Midday, Dusk, Midnight) -## Caveats +### Caveats Since commands registered using commando are really just appendages to the `/jsp` command and are not actually registered globally (it just From 5d00be9df4062b2c258b9729428d136370de311f Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sun, 29 Dec 2013 12:58:20 +0000 Subject: [PATCH 058/456] Fix issue #103 on Mac --- docs/API-Reference.md | 6 +- docs/release-notes.md | 4 + src/main/javascript/lib/json2.js | 486 +++++++++++++++++++++++++ src/main/javascript/lib/scriptcraft.js | 19 +- src/main/javascript/plugins/arrows.js | 6 +- 5 files changed, 510 insertions(+), 11 deletions(-) create mode 100644 src/main/javascript/lib/json2.js diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 9d3dfda..ef1abd4 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -1732,7 +1732,6 @@ To construct a spiral staircase 5 floors high made of oak... ## Arrows Module -### Description The arrows mod adds fancy arrows to the game. Arrows which... * Launch fireworks. @@ -1751,8 +1750,9 @@ The arrows mod adds fancy arrows to the game. Arrows which... * `/js arrows.normal()` sets arrow type to normal. * `/js arrows.sign()` turns a targeted sign into a Arrows menu -All of the above functions can take an optional player object or name as -a parameter. For example: `/js arrows.explosive('player23')` makes player23's arrows explosive. +All of the above functions can take an optional player object or name +as a parameter. For example: `/js arrows.explosive('player23')` makes +player23's arrows explosive. ## alias Module diff --git a/docs/release-notes.md b/docs/release-notes.md index 84c5e14..4af60c6 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,3 +1,7 @@ +# 2013 12 28 + +Documented the 'homes' module other tweaks to documentation. + # 2013 12 27 ## Updated 'jsp alias' command. diff --git a/src/main/javascript/lib/json2.js b/src/main/javascript/lib/json2.js new file mode 100644 index 0000000..d89ecc7 --- /dev/null +++ b/src/main/javascript/lib/json2.js @@ -0,0 +1,486 @@ +/* + json2.js + 2013-05-26 + + Public Domain. + + NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + + See http://www.JSON.org/js.html + + + This code should be minified before deployment. + See http://javascript.crockford.com/jsmin.html + + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO + NOT CONTROL. + + + This file creates a global JSON object containing two methods: stringify + and parse. + + JSON.stringify(value, replacer, space) + value any JavaScript value, usually an object or array. + + replacer an optional parameter that determines how object + values are stringified for objects. It can be a + function or an array of strings. + + space an optional parameter that specifies the indentation + of nested structures. If it is omitted, the text will + be packed without extra whitespace. If it is a number, + it will specify the number of spaces to indent at each + level. If it is a string (such as '\t' or ' '), + it contains the characters used to indent at each level. + + This method produces a JSON text from a JavaScript value. + + When an object value is found, if the object contains a toJSON + method, its toJSON method will be called and the result will be + stringified. A toJSON method does not serialize: it returns the + value represented by the name/value pair that should be serialized, + or undefined if nothing should be serialized. The toJSON method + will be passed the key associated with the value, and this will be + bound to the value + + For example, this would serialize Dates as ISO strings. + + Date.prototype.toJSON = function (key) { + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + return this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z'; + }; + + You can provide an optional replacer method. It will be passed the + key and value of each member, with this bound to the containing + object. The value that is returned from your method will be + serialized. If your method returns undefined, then the member will + be excluded from the serialization. + + If the replacer parameter is an array of strings, then it will be + used to select the members to be serialized. It filters the results + such that only members with keys listed in the replacer array are + stringified. + + Values that do not have JSON representations, such as undefined or + functions, will not be serialized. Such values in objects will be + dropped; in arrays they will be replaced with null. You can use + a replacer function to replace those with JSON values. + JSON.stringify(undefined) returns undefined. + + The optional space parameter produces a stringification of the + value that is filled with line breaks and indentation to make it + easier to read. + + If the space parameter is a non-empty string, then that string will + be used for indentation. If the space parameter is a number, then + the indentation will be that many spaces. + + Example: + + text = JSON.stringify(['e', {pluribus: 'unum'}]); + // text is '["e",{"pluribus":"unum"}]' + + + text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); + // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' + + text = JSON.stringify([new Date()], function (key, value) { + return this[key] instanceof Date ? + 'Date(' + this[key] + ')' : value; + }); + // text is '["Date(---current time---)"]' + + + JSON.parse(text, reviver) + This method parses a JSON text to produce an object or array. + It can throw a SyntaxError exception. + + The optional reviver parameter is a function that can filter and + transform the results. It receives each of the keys and values, + and its return value is used instead of the original value. + If it returns what it received, then the structure is not modified. + If it returns undefined then the member is deleted. + + Example: + + // Parse the text. Values that look like ISO date strings will + // be converted to Date objects. + + myData = JSON.parse(text, function (key, value) { + var a; + if (typeof value === 'string') { + a = +/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); + if (a) { + return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], + +a[5], +a[6])); + } + } + return value; + }); + + myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { + var d; + if (typeof value === 'string' && + value.slice(0, 5) === 'Date(' && + value.slice(-1) === ')') { + d = new Date(value.slice(5, -1)); + if (d) { + return d; + } + } + return value; + }); + + + This is a reference implementation. You are free to copy, modify, or + redistribute. +*/ + +/*jslint evil: true, regexp: true */ + +/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, + call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, + getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, + lastIndex, length, parse, prototype, push, replace, slice, stringify, + test, toJSON, toString, valueOf +*/ + + +// Create a JSON object only if one does not already exist. We create the +// methods in a closure to avoid creating global variables. + +if (typeof JSON !== 'object') { + JSON = {}; +} + +(function () { + 'use strict'; + + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + if (typeof Date.prototype.toJSON !== 'function') { + + Date.prototype.toJSON = function () { + + return isFinite(this.valueOf()) + ? this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z' + : null; + }; + + String.prototype.toJSON = + Number.prototype.toJSON = + Boolean.prototype.toJSON = function () { + return this.valueOf(); + }; + } + + var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + gap, + indent, + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }, + rep; + + + function quote(string) { + +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can safely slap some quotes around it. +// Otherwise we must also replace the offending characters with safe escape +// sequences. + + escapable.lastIndex = 0; + return escapable.test(string) ? '"' + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' + ? c + : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : '"' + string + '"'; + } + + + function str(key, holder) { + +// Produce a string from holder[key]. + + var i, // The loop counter. + k, // The member key. + v, // The member value. + length, + mind = gap, + partial, + value = holder[key]; + +// If the value has a toJSON method, call it to obtain a replacement value. + + if (value && typeof value === 'object' && + typeof value.toJSON === 'function') { + value = value.toJSON(key); + } + +// If we were called with a replacer function, then call the replacer to +// obtain a replacement value. + + if (typeof rep === 'function') { + value = rep.call(holder, key, value); + } + +// What happens next depends on the value's type. + + switch (typeof value) { + case 'string': + return quote(value); + + case 'number': + +// JSON numbers must be finite. Encode non-finite numbers as null. + + return isFinite(value) ? String(value) : 'null'; + + case 'boolean': + case 'null': + +// If the value is a boolean or null, convert it to a string. Note: +// typeof null does not produce 'null'. The case is included here in +// the remote chance that this gets fixed someday. + + return String(value); + +// If the type is 'object', we might be dealing with an object or an array or +// null. + + case 'object': + +// Due to a specification blunder in ECMAScript, typeof null is 'object', +// so watch out for that case. + + if (!value) { + return 'null'; + } + +// Make an array to hold the partial results of stringifying this object value. + + gap += indent; + partial = []; + +// Is the value an array? + + if (Object.prototype.toString.apply(value) === '[object Array]') { + +// The value is an array. Stringify every element. Use null as a placeholder +// for non-JSON values. + + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } + +// Join all of the elements together, separated with commas, and wrap them in +// brackets. + + v = partial.length === 0 + ? '[]' + : gap + ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' + : '[' + partial.join(',') + ']'; + gap = mind; + return v; + } + +// If the replacer is an array, use it to select the members to be stringified. + + if (rep && typeof rep === 'object') { + length = rep.length; + for (i = 0; i < length; i += 1) { + if (typeof rep[i] === 'string') { + k = rep[i]; + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } else { + +// Otherwise, iterate through all of the keys in the object. + + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } + +// Join all of the member texts together, separated with commas, +// and wrap them in braces. + + v = partial.length === 0 + ? '{}' + : gap + ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' + : '{' + partial.join(',') + '}'; + gap = mind; + return v; + } + } + +// If the JSON object does not yet have a stringify method, give it one. + + if (typeof JSON.stringify !== 'function') { + JSON.stringify = function (value, replacer, space) { + +// The stringify method takes a value and an optional replacer, and an optional +// space parameter, and returns a JSON text. The replacer can be a function +// that can replace values, or an array of strings that will select the keys. +// A default replacer method can be provided. Use of the space parameter can +// produce text that is more easily readable. + + var i; + gap = ''; + indent = ''; + +// If the space parameter is a number, make an indent string containing that +// many spaces. + + if (typeof space === 'number') { + for (i = 0; i < space; i += 1) { + indent += ' '; + } + +// If the space parameter is a string, it will be used as the indent string. + + } else if (typeof space === 'string') { + indent = space; + } + +// If there is a replacer, it must be a function or an array. +// Otherwise, throw an error. + + rep = replacer; + if (replacer && typeof replacer !== 'function' && + (typeof replacer !== 'object' || + typeof replacer.length !== 'number')) { + throw new Error('JSON.stringify'); + } + +// Make a fake root object containing our value under the key of ''. +// Return the result of stringifying the value. + + return str('', {'': value}); + }; + } + + +// If the JSON object does not yet have a parse method, give it one. + + if (typeof JSON.parse !== 'function') { + JSON.parse = function (text, reviver) { + +// The parse method takes a text and an optional reviver function, and returns +// a JavaScript value if the text is a valid JSON text. + + var j; + + function walk(holder, key) { + +// The walk method is used to recursively walk the resulting structure so +// that modifications can be made. + + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } + } + } + } + return reviver.call(holder, key, value); + } + + +// Parsing happens in four stages. In the first stage, we replace certain +// Unicode characters with escape sequences. JavaScript handles many characters +// incorrectly, either silently deleting them, or treating them as line endings. + + text = String(text); + cx.lastIndex = 0; + if (cx.test(text)) { + text = text.replace(cx, function (a) { + return '\\u' + + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } + +// In the second stage, we run the text against regular expressions that look +// for non-JSON patterns. We are especially concerned with '()' and 'new' +// because they can cause invocation, and '=' because it can cause mutation. +// But just to be safe, we want to reject all unexpected forms. + +// We split the second stage into 4 regexp operations in order to work around +// crippling inefficiencies in IE's and Safari's regexp engines. First we +// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we +// replace all simple value tokens with ']' characters. Third, we delete all +// open brackets that follow a colon or comma or that begin the text. Finally, +// we look to see that the remaining characters are only whitespace or ']' or +// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. + + if (/^[\],:{}\s]*$/ + .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') + .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') + .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { + +// In the third stage we use the eval function to compile the text into a +// JavaScript structure. The '{' operator is subject to a syntactic ambiguity +// in JavaScript: it can begin a block or an object literal. We wrap the text +// in parens to eliminate the ambiguity. + + j = eval('(' + text + ')'); + +// In the optional fourth stage, we recursively walk the new structure, passing +// each name/value pair to a reviver function for possible transformation. + + return typeof reviver === 'function' + ? walk({'': j}, '') + : j; + } + +// If the text is not JSON parseable, then a SyntaxError is thrown. + + throw new SyntaxError('JSON.parse'); + }; + } +}()); diff --git a/src/main/javascript/lib/scriptcraft.js b/src/main/javascript/lib/scriptcraft.js index 513e9d3..1325390 100644 --- a/src/main/javascript/lib/scriptcraft.js +++ b/src/main/javascript/lib/scriptcraft.js @@ -414,12 +414,15 @@ var server = org.bukkit.Bukkit.server; private implementation */ (function(){ + /* don't execute this more than once */ if (typeof load == "function") return ; - var File = java.io.File; + var File = java.io.File + ,FileReader = java.io.FileReader + ,BufferedReader = java.io.BufferedReader; var _canonize = function(file){ return "" + file.getCanonicalPath().replaceAll("\\\\","/"); @@ -436,9 +439,7 @@ var server = org.bukkit.Bukkit.server; */ var _load = function(filename,warnOnFileNotFound) { - var FileReader = java.io.FileReader - ,BufferedReader = java.io.BufferedReader - ,result = null + var result = null ,file = filename ,r = undefined; @@ -470,7 +471,10 @@ var server = org.bukkit.Bukkit.server; code += r + "\n"; } result = __engine.eval("(" + code + ")"); - _loaded[canonizedFilename] = result || true; + // issue #103 avoid side-effects of || operator on Mac Rhino + _loaded[canonizedFilename] = result ; + if (!_loaded[canonizedFilename]) + _loaded[canonizedFilename]= true; }catch (e){ __plugin.logger.severe("Error evaluating " + canonizedFilename + ", " + e ); } @@ -494,6 +498,11 @@ var server = org.bukkit.Bukkit.server; if (!config) config = {verbose: false}; global.config = config; + /* + wph 20131229 Issue #103 JSON is not bundled with javax.scripting / Rhino on Mac. + */ + var jsonLoaded = __engine["eval(java.io.Reader)"](new FileReader(new File(jsPluginsRootDirName + '/lib/json2.js'))); + /* Unload Handlers */ diff --git a/src/main/javascript/plugins/arrows.js b/src/main/javascript/plugins/arrows.js index 5b87fab..fb0d73d 100644 --- a/src/main/javascript/plugins/arrows.js +++ b/src/main/javascript/plugins/arrows.js @@ -1,7 +1,6 @@ /************************************************************************* ## Arrows Module -### Description The arrows mod adds fancy arrows to the game. Arrows which... * Launch fireworks. @@ -20,8 +19,9 @@ The arrows mod adds fancy arrows to the game. Arrows which... * `/js arrows.normal()` sets arrow type to normal. * `/js arrows.sign()` turns a targeted sign into a Arrows menu -All of the above functions can take an optional player object or name as -a parameter. For example: `/js arrows.explosive('player23')` makes player23's arrows explosive. +All of the above functions can take an optional player object or name +as a parameter. For example: `/js arrows.explosive('player23')` makes +player23's arrows explosive. ***/ From 4b85500257dfe18ddc347a06d88594e733c6935a Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sun, 29 Dec 2013 13:06:43 +0000 Subject: [PATCH 059/456] release note for issue #103 --- docs/release-notes.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 4af60c6..f2719ef 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,3 +1,9 @@ +# 2013 12 29 + +Bug Fix: [Can't get Scriptcraft core libraries working][bug103]. + +[bug103]: https://github.com/walterhiggins/ScriptCraft/issues/103 + # 2013 12 28 Documented the 'homes' module other tweaks to documentation. From dbf6adfadcdcd2dd945df2ef0157aabe41c0b582 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sun, 29 Dec 2013 13:26:21 +0000 Subject: [PATCH 060/456] Fix bug when using commando from server console --- src/main/javascript/plugins/commando/commando.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/javascript/plugins/commando/commando.js b/src/main/javascript/plugins/commando/commando.js index 403bee9..06db221 100644 --- a/src/main/javascript/plugins/commando/commando.js +++ b/src/main/javascript/plugins/commando/commando.js @@ -88,13 +88,13 @@ events.on('player.PlayerCommandPreprocessEvent', function(l,e){ var msg = "" + e.message; var command = msg.match(/^\/([^\s]+)/)[1]; if (commands[command]){ - e.message = "/jsp " + msg.substring(1); + e.message = "/jsp " + msg.replace(/^\//,""); } }); events.on('server.ServerCommandEvent', function(l,e){ var msg = "" + e.command; var command = msg.match(/^\/*([^\s]+)/)[1]; if (commands[command]){ - e.command = "/jsp " + msg.substring(1); + e.command = "jsp " + msg.replace(/^\//,""); } }); From fc440654b82a3ffddfb42dbfdd7a3c39633592f2 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Mon, 30 Dec 2013 01:07:41 +0000 Subject: [PATCH 061/456] Fixed issue with stack trace in console when empty commands entered. Added more example modules --- docs/API-Reference.md | 8 +-- docs/release-notes.md | 4 ++ src/main/javascript/lib/command.js | 59 +++++++++++++++++++ src/main/javascript/lib/plugin.js | 56 +----------------- src/main/javascript/lib/scriptcraft.js | 4 +- src/main/javascript/lib/tabcomplete-jsp.js | 2 +- .../javascript/modules/utils/string-exts.js | 4 +- src/main/javascript/modules/utils/utils.js | 31 +++++----- src/main/javascript/plugins/alias/alias.js | 10 ++-- .../javascript/plugins/commando/commando.js | 14 ++++- src/main/javascript/plugins/example-1.js | 12 ---- .../examples/example-1-hello-module.js | 20 +++++++ .../examples/example-2-hello-command.js | 24 ++++++++ .../examples/example-3-hello-ops-only.js | 30 ++++++++++ .../examples/example-4-hello-parameters.js | 29 +++++++++ .../examples/example-5-hello-using-module.js | 29 +++++++++ 16 files changed, 240 insertions(+), 96 deletions(-) create mode 100644 src/main/javascript/lib/command.js delete mode 100644 src/main/javascript/plugins/example-1.js create mode 100644 src/main/javascript/plugins/examples/example-1-hello-module.js create mode 100644 src/main/javascript/plugins/examples/example-2-hello-command.js create mode 100644 src/main/javascript/plugins/examples/example-3-hello-ops-only.js create mode 100644 src/main/javascript/plugins/examples/example-4-hello-parameters.js create mode 100644 src/main/javascript/plugins/examples/example-5-hello-using-module.js diff --git a/docs/API-Reference.md b/docs/API-Reference.md index ef1abd4..47068b8 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -767,7 +767,7 @@ The utils.at() function will perform a given task at a given time every * time24hr : The time in 24hr form - e.g. 9:30 in the morning is "09:30" while 9:30 pm is "21:30", midnight is "00:00" and midday is "12:00" * callback : A javascript function which will be invoked at the given time. - * world : (optional) Each world has its own clock. If no world is specified, the server's first world is used. + * worlds : (optional) An array of worlds. Each world has its own clock. If no array of worlds is specified, all the server's worlds are used. #### Example @@ -781,7 +781,7 @@ To warn players when night is approaching... player.chat("The night is dark and full of terrors!"); }); - }, self.world); + }); ### utils.find() function @@ -838,8 +838,8 @@ The following chat-formatting methods are added to the javascript String class.. Example ------- - var boldGoldText = "Hello World".bold().gold(); - self.sendMessage(boldGoldText); + /js var boldGoldText = "Hello World".bold().gold(); + /js self.sendMessage(boldGoldText);

Hello World

diff --git a/docs/release-notes.md b/docs/release-notes.md index f2719ef..afefd0f 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -4,6 +4,10 @@ Bug Fix: [Can't get Scriptcraft core libraries working][bug103]. [bug103]: https://github.com/walterhiggins/ScriptCraft/issues/103 +Bug Fix; Server console errors when empty commands submitted. + +Added more example modules. + # 2013 12 28 Documented the 'homes' module other tweaks to documentation. diff --git a/src/main/javascript/lib/command.js b/src/main/javascript/lib/command.js new file mode 100644 index 0000000..568bc12 --- /dev/null +++ b/src/main/javascript/lib/command.js @@ -0,0 +1,59 @@ +/* + command management - allow for non-ops to execute approved javascript code. +*/ +var _commands = {}; +var _cmdInterceptors = []; +/* + execute a JSP command. +*/ +var executeCmd = function(args, player){ + if (args.length === 0) + throw new Error("Usage: jsp command-name command-parameters"); + var name = args[0]; + var cmd = _commands[name]; + if (typeof cmd === "undefined"){ + // it's not a global command - pass it on to interceptors + var intercepted = false; + for (var i = 0;i < _cmdInterceptors.length;i++){ + if (_cmdInterceptors[i](args,player)) + intercepted = true; + } + if (!intercepted) + console.warn('Command %s is not recognised',name); + }else{ + func = cmd.callback; + var params = []; + for (var i =1; i < args.length;i++){ + params.push("" + args[i]); + } + var result = null; + try { + result = func(params,player); + }catch (e){ + console.error("Error while trying to execute command: " + JSON.stringify(params)); + throw e; + } + return result; + } +}; +/* + define a new JSP command. +*/ +var defineCmd = function(name, func, options, intercepts) { + if (typeof options == "undefined") + options = []; + _commands[name] = {callback: func, options: options}; + if (intercepts) + _cmdInterceptors.push(func); + return func; +}; +var _command = function(name, func, options, intercepts) { + if (typeof name == "undefined"){ + // it's an invocation from the Java Plugin! + return executeCmd(__cmdArgs, self); + }else{ + return defineCmd(name, func, options, intercepts); + } +}; +exports.command = _command; +exports.commands = _commands; diff --git a/src/main/javascript/lib/plugin.js b/src/main/javascript/lib/plugin.js index 26e5fa4..c43503c 100644 --- a/src/main/javascript/lib/plugin.js +++ b/src/main/javascript/lib/plugin.js @@ -49,63 +49,8 @@ var _plugin = function(/* String */ moduleName, /* Object */ moduleObject, isPer } return moduleObject; }; -/* - allow for deferred execution (once all modules have loaded) -*/ -var _deferred = []; -var _ready = function( func ){ - _deferred.push(func); -}; -var _cmdInterceptors = []; -/* - command management - allow for non-ops to execute approved javascript code. -*/ -var _commands = {}; -exports.commands = _commands; -var _command = function(name,func,options,intercepts) -{ - if (typeof name == "undefined"){ - // it's an invocation from the Java Plugin! - if (__cmdArgs.length === 0) - throw new Error("Usage: jsp command-name command-parameters"); - var name = __cmdArgs[0]; - var cmd = _commands[name]; - if (typeof cmd === "undefined"){ - // it's not a global command - pass it on to interceptors - var intercepted = false; - for (var i = 0;i < _cmdInterceptors.length;i++){ - if (_cmdInterceptors[i](__cmdArgs)) - intercepted = true; - } - if (!intercepted) - self.sendMessage("Command '" + name + "' is not recognised"); - }else{ - func = cmd.callback; - var params = []; - for (var i =1; i < __cmdArgs.length;i++){ - params.push("" + __cmdArgs[i]); - } - var result = null; - try { - result = func(params); - }catch (e){ - console.error("Error while trying to execute command: " + JSON.stringify(params)); - throw e; - } - return result; - } - }else{ - if (typeof options == "undefined") - options = []; - _commands[name] = {callback: func, options: options}; - if (intercepts) - _cmdInterceptors.push(func); - return func; - } -}; exports.plugin = _plugin; -exports.command = _command; exports.save = _save; var scriptCraftDir = null; @@ -172,6 +117,7 @@ exports.autoload = function(dir) { }; _reload(pluginDir); }; + addUnloadHandler(function(){ // // save all plugins which have persistent data diff --git a/src/main/javascript/lib/scriptcraft.js b/src/main/javascript/lib/scriptcraft.js index 1325390..7eecf94 100644 --- a/src/main/javascript/lib/scriptcraft.js +++ b/src/main/javascript/lib/scriptcraft.js @@ -565,11 +565,11 @@ var server = org.bukkit.Bukkit.server; global.require = fnRequire(__plugin.logger, __engine, config.verbose, jsPluginsRootDirName, modulePaths); global.console = require('console'); + global.command = require('command').command; var plugins = require('plugin'); global._onTabComplete = require('tabcomplete'); - + global.plugin = plugins.plugin; - global.command = plugins.command; global.save = plugins.save; var events = require('events'); diff --git a/src/main/javascript/lib/tabcomplete-jsp.js b/src/main/javascript/lib/tabcomplete-jsp.js index 1d3764f..b75e883 100644 --- a/src/main/javascript/lib/tabcomplete-jsp.js +++ b/src/main/javascript/lib/tabcomplete-jsp.js @@ -1,4 +1,4 @@ -var _commands = require('plugin').commands; +var _commands = require('command').commands; /* Tab completion for the /jsp commmand */ diff --git a/src/main/javascript/modules/utils/string-exts.js b/src/main/javascript/modules/utils/string-exts.js index f10810e..8be2180 100644 --- a/src/main/javascript/modules/utils/string-exts.js +++ b/src/main/javascript/modules/utils/string-exts.js @@ -35,8 +35,8 @@ The following chat-formatting methods are added to the javascript String class.. Example ------- - var boldGoldText = "Hello World".bold().gold(); - self.sendMessage(boldGoldText); + /js var boldGoldText = "Hello World".bold().gold(); + /js self.sendMessage(boldGoldText);

Hello World

diff --git a/src/main/javascript/modules/utils/utils.js b/src/main/javascript/modules/utils/utils.js index bc8362b..840c45e 100644 --- a/src/main/javascript/modules/utils/utils.js +++ b/src/main/javascript/modules/utils/utils.js @@ -149,7 +149,7 @@ var _foreach = function(array, callback, object, delay, onCompletion) { if (delay){ var next = function(){ callback(array[i],i,object,array); i++;}; var hasNext = function(){return i < len;}; - utils.nicely(next,hasNext,onCompletion,delay); + _nicely(next,hasNext,onCompletion,delay); }else{ for (;i < len; i++){ callback(array[i],i,object,array); @@ -182,17 +182,18 @@ function and the start of the next `next()` function. See the source code to utils.foreach for an example of how utils.nicely is used. ***/ -exports.nicely = function(next, hasNext, onDone, delay){ +var _nicely = function(next, hasNext, onDone, delay){ if (hasNext()){ next(); server.scheduler.runTaskLater(__plugin,function(){ - utils.nicely(next,hasNext,onDone,delay); + _nicely(next,hasNext,onDone,delay); },delay); }else{ if (onDone) onDone(); } }; +exports.nicely = _nicely; /************************************************************************ ### utils.at() function @@ -204,7 +205,7 @@ The utils.at() function will perform a given task at a given time every * time24hr : The time in 24hr form - e.g. 9:30 in the morning is "09:30" while 9:30 pm is "21:30", midnight is "00:00" and midday is "12:00" * callback : A javascript function which will be invoked at the given time. - * world : (optional) Each world has its own clock. If no world is specified, the server's first world is used. + * worlds : (optional) An array of worlds. Each world has its own clock. If no array of worlds is specified, all the server's worlds are used. #### Example @@ -218,10 +219,10 @@ To warn players when night is approaching... player.chat("The night is dark and full of terrors!"); }); - }, self.world); + }); ***/ -exports.at = function(time24hr, callback, world) { +exports.at = function(time24hr, callback, worlds) { var forever = function(){ return true;}; var timeParts = time24hr.split(":"); var hrs = ((timeParts[0] * 1000) + 18000) % 24000; @@ -230,15 +231,17 @@ exports.at = function(time24hr, callback, world) { mins = (timeParts[1] / 60) * 1000; var timeMc = hrs + mins; - if (typeof world == "undefined"){ - world = server.worlds.get(0); + if (typeof worlds == "undefined"){ + worlds = server.worlds; } - utils.nicely(function(){ - var time = world.getTime(); - var diff = timeMc - time; - if (diff > 0 && diff < 100){ - callback(); - } + _nicely(function(){ + _foreach (worlds, function (world){ + var time = world.getTime(); + var diff = timeMc - time; + if (diff > 0 && diff < 100){ + callback(); + } + }); },forever, null, 100); }; diff --git a/src/main/javascript/plugins/alias/alias.js b/src/main/javascript/plugins/alias/alias.js index e5a221f..1d325e1 100644 --- a/src/main/javascript/plugins/alias/alias.js +++ b/src/main/javascript/plugins/alias/alias.js @@ -149,20 +149,22 @@ var alias = plugin('alias', { }, true ); -var aliasCmd = command('alias', function(params){ +var aliasCmd = command('alias', function(params,invoker){ var operation = params[0]; if (!operation){ - self.sendMessage("Usage:\n" + _usage); + invoker.sendMessage("Usage:\n" + _usage); return; } if (alias[operation]) - alias[operation](self, params.slice(1)); + alias[operation](invoker, params.slice(1)); else - self.sendMessage("Usage:\n" + _usage); + invoker.sendMessage("Usage:\n" + _usage); }); var _intercept = function( msg, invoker, exec) { + if (msg.trim().length == 0) + return false; var msgParts = msg.split(' '); var command = msg.match(/^\/*([^\s]+)/)[1]; diff --git a/src/main/javascript/plugins/commando/commando.js b/src/main/javascript/plugins/commando/commando.js index 06db221..2943a22 100644 --- a/src/main/javascript/plugins/commando/commando.js +++ b/src/main/javascript/plugins/commando/commando.js @@ -86,14 +86,24 @@ exports.commando = function(name, func, options, intercepts){ events.on('player.PlayerCommandPreprocessEvent', function(l,e){ var msg = "" + e.message; - var command = msg.match(/^\/([^\s]+)/)[1]; + var parts = msg.match(/^\/([^\s]+)/); + if (!parts) + return; + if (parts.length < 2) + return; + var command = parts[1]; if (commands[command]){ e.message = "/jsp " + msg.replace(/^\//,""); } }); events.on('server.ServerCommandEvent', function(l,e){ var msg = "" + e.command; - var command = msg.match(/^\/*([^\s]+)/)[1]; + var parts = msg.match(/^\/*([^\s]+)/); + if (!parts) + return; + if (parts.length < 2) + return; + var command = parts[1]; if (commands[command]){ e.command = "jsp " + msg.replace(/^\//,""); } diff --git a/src/main/javascript/plugins/example-1.js b/src/main/javascript/plugins/example-1.js deleted file mode 100644 index 54a9603..0000000 --- a/src/main/javascript/plugins/example-1.js +++ /dev/null @@ -1,12 +0,0 @@ -/* - A simple minecraft plugin. - Usage: At the in-game prompt type ... - - /js hello() - - ... and a message `Hello {player-name}` will appear (where {player-name} is - replaced by your own name). -*/ -exports.hello = function(){ - echo("Hello " + self.name); -}; diff --git a/src/main/javascript/plugins/examples/example-1-hello-module.js b/src/main/javascript/plugins/examples/example-1-hello-module.js new file mode 100644 index 0000000..af60e75 --- /dev/null +++ b/src/main/javascript/plugins/examples/example-1-hello-module.js @@ -0,0 +1,20 @@ +/* + A simple minecraft plugin. + Usage: At the in-game prompt type ... + + /js hello(self) + + ... and a message `Hello {player-name}` will appear (where {player-name} is + replaced by your own name). + + This example demonstrates the basics of adding new functionality which is only + usable by server operators or users with the scriptcraft.evaluate permission. + By default, only ops are granted this permission. + + The `hello` function below is only usable by players with the scriptcraft.evaluate + permission since it relies on the `/js` command to execute. + +*/ +exports.hello = function(player){ + player.sendMessage('Hello ' + player.name); +}; diff --git a/src/main/javascript/plugins/examples/example-2-hello-command.js b/src/main/javascript/plugins/examples/example-2-hello-command.js new file mode 100644 index 0000000..4ead4ba --- /dev/null +++ b/src/main/javascript/plugins/examples/example-2-hello-command.js @@ -0,0 +1,24 @@ +/* + A simple minecraft plugin. + Usage: At the in-game prompt type ... + + /jsp hello + + ... and a message `Hello {player-name}` will appear (where {player-name} is + replaced by your own name). + + This example demonstrates the basics of adding new functionality + which is usable all players or those with the scriptcraft.proxy + permission. By default, all players are granted this permission. + + This differs from example 1 in that a new 'jsp ' command extension + is defined. Since all players can use the `jsp` command, all players + can use the new extension. Unlike the previous example, the `jsp + hello` command does not evaluate javascript code so this command is + much more secure. + +*/ + +command('hello', function (parameters, player) { + player.sendMessage('Hello ' + player.name); +}); diff --git a/src/main/javascript/plugins/examples/example-3-hello-ops-only.js b/src/main/javascript/plugins/examples/example-3-hello-ops-only.js new file mode 100644 index 0000000..10f0674 --- /dev/null +++ b/src/main/javascript/plugins/examples/example-3-hello-ops-only.js @@ -0,0 +1,30 @@ +/* + A simple minecraft plugin. + Usage: At the in-game prompt type ... + + /jsp op-hello + + ... and a message `Hello {player-name}` will appear (where {player-name} is + replaced by your own name). + + This example demonstrates the basics of adding new functionality + which is usable all players or those with the scriptcraft.proxy + permission. By default, all players are granted this permission. In + this command though, the function checks to see if the player is an + operator and if they aren't will return immediately. + + This differs from example 2 in that the function will only print a + message for operators. + +*/ + +command('op-hello', function (parameters, player) { + /* + this is how you limit based on player privileges + */ + if (!player.op){ + player.sendMessage('Only operators can do this.'); + return; + } + player.sendMessage('Hello ' + player.name); +}); diff --git a/src/main/javascript/plugins/examples/example-4-hello-parameters.js b/src/main/javascript/plugins/examples/example-4-hello-parameters.js new file mode 100644 index 0000000..14c6ec7 --- /dev/null +++ b/src/main/javascript/plugins/examples/example-4-hello-parameters.js @@ -0,0 +1,29 @@ +/* + A simple minecraft plugin. + Usage: At the in-game prompt type ... + + /jsp hello-params Hi + /jsp hello-params Saludos + /jsp hello-params Greetings + + ... and a message `Hi {player-name}` or `Saludos {player-name}` etc + will appear (where {player-name} is replaced by your own name). + + This example demonstrates adding and using parameters in commands. + + This differs from example 3 in that the greeting can be changed from + a fixed 'Hello ' to anything you like by passing a parameter. +*/ + +command('hello-params', function (parameters, player) { + /* + parameters is an array (or list) of strings. parameters[0] + refers to the first element in the list. Arrays in Javascript + are 0-based. That is, the 1st element is parameters[0], the 2nd + element is parameters[1], the 3rd element is parameters[2] and + so on. In this example, parameters[1] refers to the first word + which appears after `jsp hello-params `. + */ + var salutation = parameters[0] ; + player.sendMessage( salutation + ' ' + player.name); +}); diff --git a/src/main/javascript/plugins/examples/example-5-hello-using-module.js b/src/main/javascript/plugins/examples/example-5-hello-using-module.js new file mode 100644 index 0000000..b09d5eb --- /dev/null +++ b/src/main/javascript/plugins/examples/example-5-hello-using-module.js @@ -0,0 +1,29 @@ +/* + A simple minecraft plugin. + Usage: At the in-game prompt type ... + + /jsp hello-module + + ... and a message `Hello {player-name}` will appear (where {player-name} is + replaced by your own name). + + This example demonstrates the use of modules. In + example-1-hello-module.js we created a new javascript module. In + this example, we use that module... + + * We load the module using the `require()` function. Because this + module and the module we require are n the same directory, we + specify `'./example-1-hello-module'` as the path (when loading a + module from the same directory, `./` at the start of the path + indicates that the file should be searched for in the same + directory. + + * We assign the loaded module to a variable (`greetings`) and then + use the module's `hello` method to display the message. + +*/ +var greetings = require('./example-1-hello-module'); + +command('hello-module', function( parameters, player ){ + greetings.hello(player); +}); From f88d1f0428ece3990f2a4e9aa37c4724cad33887 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Mon, 30 Dec 2013 21:33:12 +0000 Subject: [PATCH 062/456] 'Buddha' Release : The annihilation of 'self' variable. use of 'self' should be limited only to in-game or console commands. It should not be used in the context of a module. --- docs/API-Reference.md | 53 +++++++----- ...YoungPersonsGuideToProgrammingMinecraft.md | 48 +++++------ .../scriptcraft/ScriptCraftPlugin.java | 67 ++++----------- src/main/javascript/lib/command.js | 16 +--- src/main/javascript/lib/scriptcraft.js | 83 +++++++++++++------ src/main/javascript/lib/tabcomplete-jsp.js | 6 +- src/main/javascript/lib/tabcomplete.js | 9 +- src/main/javascript/plugins/alias/alias.js | 8 +- src/main/javascript/plugins/arrows.js | 25 +++--- src/main/javascript/plugins/chat/color.js | 10 +-- .../javascript/plugins/classroom/classroom.js | 23 +++-- .../plugins/commando/commando-test.js | 11 ++- .../javascript/plugins/commando/commando.js | 20 +++-- .../javascript/plugins/drone/contrib/fort.js | 2 +- src/main/javascript/plugins/drone/drone.js | 4 +- src/main/javascript/plugins/homes/homes.js | 68 +++++++-------- .../plugins/minigames/NumberGuess.js | 6 +- 17 files changed, 239 insertions(+), 220 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 47068b8..352f8f5 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -18,7 +18,7 @@ loads the module circle.js in the same directory. The contents of foo.js: var circle = require('./circle.js'); - echo( 'The area of a circle of radius 4 is ' + console.log( 'The area of a circle of radius 4 is ' + circle.area(4)); The contents of circle.js: @@ -71,14 +71,14 @@ module in the `plugins` directory exports becomes a global variable. For example, if you have a module greeting.js in the plugins directory.... - exports.greet = function() { - echo('Hello ' + self.name); + exports.greet = function(player) { + player.sendMessage('Hello ' + player.name); }; ... then `greet` becomes a global function and can be used at the in-game (or server) command prompt like so... - /js greet() + /js greet(self) ... This differs from how modules (in NodeJS and commonJS environments) normally work. If you want your module to be exported @@ -138,7 +138,16 @@ The ScriptCraft JavaPlugin object. 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) +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. @@ -239,7 +248,7 @@ load() will return the result of the last statement evaluated in the file. #### Example - load(__folder + "myFile.js"); // loads a javascript file and evaluates it. + load("myFile.js"); // loads a javascript file and evaluates it. var myData = load("myData.json"); // loads a javascript file and evaluates it - eval'd contents are returned. @@ -939,7 +948,7 @@ Drones can be created in any of the following ways... var d = new Drone().box( blocks.oak ) - ... All of the Drone's methods return `this` (self) so you can chain operations together like this... + ... All of the Drone's methods return `this` so you can chain operations together like this... var d = box( blocks.oak ) .up() @@ -1742,13 +1751,13 @@ The arrows mod adds fancy arrows to the game. Arrows which... ### Usage: - * `/js arrows.firework()` - A firework launches where the the arrow lands. - * `/js arrows.lightning()` - lightning strikes where the arrow lands. - * `/js arrows.teleport()` - makes player teleport to where arrow has landed. - * `/js arrows.flourish()` - makes a tree grow where the arrow lands. - * `/js arrows.explosive()` - makes arrows explode. - * `/js arrows.normal()` sets arrow type to normal. - * `/js arrows.sign()` turns a targeted sign into a Arrows menu + * `/js arrows.firework(self)` - A firework launches where the the arrow lands. + * `/js arrows.lightning(self)` - lightning strikes where the arrow lands. + * `/js arrows.teleport(self)` - makes player teleport to where arrow has landed. + * `/js arrows.flourish(self)` - makes a tree grow where the arrow lands. + * `/js arrows.explosive(self)` - makes arrows explode. + * `/js arrows.normal(self)` sets arrow type to normal. + * `/js arrows.sign(self)` turns a targeted sign into a Arrows menu All of the above functions can take an optional player object or name as a parameter. For example: `/js arrows.explosive('player23')` makes @@ -1835,11 +1844,11 @@ to every student in a Minecraft classroom environment. To allow all players (and any players who connect to the server) to use the `js` and `jsp` commands... - /js classroom.allowScripting(true) + /js classroom.allowScripting(true,self) To disallow scripting (and prevent players who join the server from using the commands)... - /js classroom.allowScripting(false) + /js classroom.allowScripting(false,self) Only ops users can run the classroom.allowScripting() function - this is so that students don't try to bar themselves and each other from scripting. @@ -1853,7 +1862,7 @@ to Minecraft. Normally ScriptCraft only allows for provision of new commands as extensions to the jsp command. For example, to create a new simple command for use by all players... - /js command('hi', function(){ echo('Hi ' + self.name); }); + /js command('hi', function(args,player){ player.sendMessage('Hi ' + player.name); }); ... then players can use this command by typing... @@ -1890,8 +1899,8 @@ of the ScriptCraft core. ### Example hi-command.js var commando = require('../commando'); - commando('hi', function(){ - echo('Hi ' + self.name); + commando('hi', function(args,player){ + player.sendMessage('Hi ' + player.name); }); ...Displays a greeting to any player who issues the `/hi` command. @@ -1899,8 +1908,8 @@ of the ScriptCraft core. ### Example - timeofday-command.js var times = {Dawn: 0, Midday: 6000, Dusk: 12000, Midnight:18000}; - commando('timeofday', function(params){ - self.location.world.setTime(times[params[0]]); + commando('timeofday', function(params,player){ + player.location.world.setTime(times[params[0]]); }, ['Dawn','Midday','Dusk','Midnight']); @@ -2030,7 +2039,7 @@ code is to demonstrate use of Bukkit's Conversation API. ### Example - /js Game_NumberGuess.start() + /js Game_NumberGuess.start(self) Once the game begins, guess a number by typing the `/` character followed by a number between 1 and 10. diff --git a/docs/YoungPersonsGuideToProgrammingMinecraft.md b/docs/YoungPersonsGuideToProgrammingMinecraft.md index a402487..4772adf 100644 --- a/docs/YoungPersonsGuideToProgrammingMinecraft.md +++ b/docs/YoungPersonsGuideToProgrammingMinecraft.md @@ -439,8 +439,8 @@ object can do, let's use that knowledge to create a Minecraft Mod! Once you've installed Notepad++, Launch it, create a new file and type the following... - exports.greet = function(){ - echo("Hi " + self.name); + exports.greet = function(player){ + player.sendMessage("Hi " + player.name); } ... then save the file in a new directory @@ -479,16 +479,16 @@ one or more functions, objects or variables. For example... #### thrower.js - exports.egg = function(){ - self.throwEgg(); + exports.egg = function(player){ + player.throwEgg(); } - exports.snowball = function(){ - self.throwSnowball(); + exports.snowball = function(player){ + player.throwSnowball(); } ... is a plugin which provides 2 javascript functions called `egg()` and `snowball()` which can be invoked from the in-game prompt like -this `/js egg()` or `/js snowball()`. +this `/js egg(self)` or `/js snowball(self)`. ### Parameters If you want to change the `greet()` function so that it displays a @@ -501,18 +501,18 @@ differently each time it is called. Change the `greet()` function so that it looks like this... - exports.greet = function ( greeting ) { - echo( greeting + self.name ); + exports.greet = function ( greeting , player) { + player.sendMessage( greeting + player.name ); } ... Save your greet.js file and issue the `/js refresh()` command in minecraft. Now enter the following command in Minecraft... - greet("Hello "); + greet("Hello ",self); ... Now try ... - greet("Dia Dhuit "); + greet("Dia Dhuit ",self); ... you should see the following messages in your chat window... @@ -716,7 +716,7 @@ loop. The following `while` loop counts to 100... var i = 1; while (i <= 100){ - echo( i ); + console.log( i ); i = i + 1; } @@ -743,7 +743,7 @@ the server... var players = server.onlinePlayers; var i = 0; while ( i < players.length ) { - echo( players[i] ); + console.log( players[i] ); i = i + 1; } @@ -765,17 +765,17 @@ loops. utils.foreach() takes two parameters... 2. A function which will be called for each item in the array. ...that's right, you can pass functions as parameters in javascript! -Let's see it in action, the following code will `echo()` (print) the -name of each online player... +Let's see it in action, the following code will `console.log()` (print) the +name of each online player in the server console window... - utils.foreach( server.onlinePlayers, echo ); + utils.foreach( server.onlinePlayers, console.log ); ... in the above example, the list of online players is processed one -at a time and each item (player) is passed to the `echo` -function. Note here that I used `echo` not `echo()`. The round braces +at a time and each item (player) is passed to the `console.log` +function. Note here that I used `console.log` not `console.log()`. The round braces () are used to call the function. If I want to pass the function as a parameter, I just use the function name without the round braces. The -above example uses a named function which already exists ( `echo` ), +above example uses a named function which already exists ( `console.log` ), you can also create new functions on-the-fly and pass them to the utils.foreach() function... @@ -930,20 +930,20 @@ flying or not? This is where the `if - else` construct comes in handy. Open your favorite editor and type the following code into a new file in your scriptcraft/plugins directory... - function flightStatus() + function flightStatus(player) { - if ( self.flying ) + if ( player.flying ) { - echo( "Hey, You are flying!" ); + player.sendMessage( 'Hey, You are flying!' ); } else { - echo( "You are not flying." ); + player.sendMessage( 'You are not flying.' ); } } ... now type `/reload` at the in-game prompt then type `/js -flightStatus()` and an appropriate message will appear based on +flightStatus(self)` and an appropriate message will appear based on whether or not you're currently flying. Type the `/js flightStatus()` command while on the ground and while flying. The message displayed in each case should be different. diff --git a/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java b/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java index 64bac6a..4cbe96a 100644 --- a/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java +++ b/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java @@ -92,20 +92,12 @@ public class ScriptCraftPlugin extends JavaPlugin implements Listener FileReader reader = null; try{ ScriptEngineManager factory = new ScriptEngineManager(); - File boot = new File(JS_PLUGINS_DIR + "/lib/scriptcraft.js"); + File bootScript = new File(JS_PLUGINS_DIR + "/lib/scriptcraft.js"); this.engine = factory.getEngineByName("JavaScript"); - this.engine.put("__engine",engine); - this.engine.put("__plugin",this); - this.engine.put("__script",boot.getCanonicalPath().replaceAll("\\\\","/")); - reader = new FileReader(boot); + reader = new FileReader(bootScript); this.engine.eval(reader); - /* - wph 20130811 Need to disable coffeescript support until issues loading and evaluating it are resolved. - See issue #92 - // Load the CoffeeScript compiler - File coffeescript = new File(JS_PLUGINS_DIR + "/lib/coffeescript.js"); - this.engine.eval(new FileReader(coffeescript)); - */ + Invocable inv = (Invocable)this.engine; + inv.invokeFunction("__onEnable", engine, this, bootScript); }catch(Exception e){ e.printStackTrace(); @@ -128,12 +120,8 @@ public class ScriptCraftPlugin extends JavaPlugin implements Listener // List result = new ArrayList(); try { - this.engine.put("__onTC_result",result); - this.engine.put("__onTC_sender",sender); - this.engine.put("__onTC_cmd",cmd); - this.engine.put("__onTC_alias",alias); - this.engine.put("__onTC_args",args); - this.engine.eval("_onTabComplete()"); + Invocable inv = (Invocable)this.engine; + inv.invokeFunction("__onTabComplete", result, sender, cmd, alias, args); }catch (Exception e){ sender.sendMessage(e.getMessage()); e.printStackTrace(); @@ -145,40 +133,17 @@ public class ScriptCraftPlugin extends JavaPlugin implements Listener { boolean result = false; String javascriptCode = ""; - - if(cmd.getName().equalsIgnoreCase("js")){ - for (int i = 0;i < args.length; i++){ - javascriptCode = javascriptCode + args[i] + " "; - } - result = true; - } else if (cmd.getName().equalsIgnoreCase("jsp")){ - javascriptCode = "command()"; - this.engine.put("__cmdArgs",args); - result = true; - } else if (cmd.getName().equalsIgnoreCase("coffee")) { - for (int i = 0;i < args.length; i++) - javascriptCode += args[i] + " "; - javascriptCode = "eval(CoffeeScript.compile(\""+javascriptCode+"\", {bare: true}))"; - result = true; + Object jsResult = null; + try { + jsResult = ((Invocable)this.engine).invokeFunction("__onCommand", sender, cmd, label, args); + }catch (Exception se){ + this.getLogger().severe(se.toString()); + se.printStackTrace(); + sender.sendMessage(se.getMessage()); } - - if (result){ - this.engine.put("self",sender); - try{ - Object resultObj = this.engine.eval(javascriptCode); - if (resultObj != null){ - if (resultObj instanceof java.util.Collection){ - java.util.Collection collection = (java.util.Collection)resultObj; - sender.sendMessage(Arrays.toString(collection.toArray())); - }else{ - sender.sendMessage(resultObj.toString()); - } - } - }catch (Exception e){ - sender.sendMessage(e.getMessage()); - e.printStackTrace(); - } + if (jsResult != null){ + return ((Boolean)jsResult).booleanValue(); } - return result; + return result; } } diff --git a/src/main/javascript/lib/command.js b/src/main/javascript/lib/command.js index 568bc12..67b6ace 100644 --- a/src/main/javascript/lib/command.js +++ b/src/main/javascript/lib/command.js @@ -22,15 +22,11 @@ var executeCmd = function(args, player){ console.warn('Command %s is not recognised',name); }else{ func = cmd.callback; - var params = []; - for (var i =1; i < args.length;i++){ - params.push("" + args[i]); - } var result = null; try { - result = func(params,player); + result = func(args.slice(1),player); }catch (e){ - console.error("Error while trying to execute command: " + JSON.stringify(params)); + console.error("Error while trying to execute command: " + JSON.stringify(args)); throw e; } return result; @@ -48,12 +44,8 @@ var defineCmd = function(name, func, options, intercepts) { return func; }; var _command = function(name, func, options, intercepts) { - if (typeof name == "undefined"){ - // it's an invocation from the Java Plugin! - return executeCmd(__cmdArgs, self); - }else{ - return defineCmd(name, func, options, intercepts); - } + return defineCmd(name, func, options, intercepts); }; +_command.exec = executeCmd; exports.command = _command; exports.commands = _commands; diff --git a/src/main/javascript/lib/scriptcraft.js b/src/main/javascript/lib/scriptcraft.js index 7eecf94..ec34e01 100644 --- a/src/main/javascript/lib/scriptcraft.js +++ b/src/main/javascript/lib/scriptcraft.js @@ -19,7 +19,7 @@ loads the module circle.js in the same directory. The contents of foo.js: var circle = require('./circle.js'); - echo( 'The area of a circle of radius 4 is ' + console.log( 'The area of a circle of radius 4 is ' + circle.area(4)); The contents of circle.js: @@ -72,14 +72,14 @@ module in the `plugins` directory exports becomes a global variable. For example, if you have a module greeting.js in the plugins directory.... - exports.greet = function() { - echo('Hello ' + self.name); + exports.greet = function(player) { + player.sendMessage('Hello ' + player.name); }; ... then `greet` becomes a global function and can be used at the in-game (or server) command prompt like so... - /js greet() + /js greet(self) ... This differs from how modules (in NodeJS and commonJS environments) normally work. If you want your module to be exported @@ -139,7 +139,16 @@ The ScriptCraft JavaPlugin object. 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) +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. @@ -240,7 +249,7 @@ load() will return the result of the last statement evaluated in the file. #### Example - load(__folder + "myFile.js"); // loads a javascript file and evaluates it. + load("myFile.js"); // loads a javascript file and evaluates it. var myData = load("myData.json"); // loads a javascript file and evaluates it - eval'd contents are returned. @@ -413,8 +422,8 @@ var server = org.bukkit.Bukkit.server; /* private implementation */ -(function(){ - +function __onEnable (__engine, __plugin, __script) +{ /* don't execute this more than once */ @@ -428,9 +437,8 @@ var server = org.bukkit.Bukkit.server; return "" + file.getCanonicalPath().replaceAll("\\\\","/"); }; - var _originalScript = __script; - var parentFileObj = new File(__script).getParentFile(); - var jsPluginsRootDir = parentFileObj.getParentFile(); + var parentFileObj = __script.parentFile; + var jsPluginsRootDir = parentFileObj.parentFile; var jsPluginsRootDirName = _canonize(jsPluginsRootDir); var _loaded = {}; @@ -457,19 +465,11 @@ var server = org.bukkit.Bukkit.server; var parent = file.getParentFile(); var reader = new FileReader(file); var br = new BufferedReader(reader); - __engine.put("__script",canonizedFilename); - __engine.put("__folder",(parent?_canonize(parent):"")+"/"); - var code = ""; try{ - if (file.getCanonicalPath().endsWith(".coffee")) { - while ((r = br.readLine()) !== null) code += "\"" + r + "\" +\n"; - code += "\"\""; - var code = "load(__folder + \"../core/_coffeescript.js\"); var ___code = "+code+"; eval(CoffeeScript.compile(___code, {bare: true}))"; - } else { - while ((r = br.readLine()) !== null) - code += r + "\n"; - } + while ((r = br.readLine()) !== null) + code += r + "\n"; + result = __engine.eval("(" + code + ")"); // issue #103 avoid side-effects of || operator on Mac Rhino _loaded[canonizedFilename] = result ; @@ -498,6 +498,7 @@ var server = org.bukkit.Bukkit.server; 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. */ @@ -567,7 +568,8 @@ var server = org.bukkit.Bukkit.server; global.console = require('console'); global.command = require('command').command; var plugins = require('plugin'); - global._onTabComplete = require('tabcomplete'); + + global.__onTabComplete = require('tabcomplete'); global.plugin = plugins.plugin; global.save = plugins.save; @@ -584,4 +586,37 @@ var server = org.bukkit.Bukkit.server; global.events = events; plugins.autoload(jsPluginsRootDir); -}()); + + global.__onCommand = function( sender, cmd, label, args) { + var jsArgs = []; + var i = 0; + for (;i < args.length; i++) jsArgs.push('' + args[i]); + + var result = false; + var cmdName = ('' + cmd.name).toLowerCase(); + if (cmdName == 'js') + { + result = true; + var fnBody = jsArgs.join(' '); + global.self = sender; + global.__engine = __engine; + try { + //var jsResult = __engine["eval(java.lang.String,javax.script.Bindings)"]( fnBody, bindings ); + var jsResult = __engine.eval(fnBody); + if (jsResult) + sender.sendMessage(jsResult); + }catch (e){ + __plugin.logger.severe("Error while trying to evaluate javascript: " + fnBody + ", Error: "+ e); + throw e; + }finally{ + delete global.self; + delete global.__engine; + } + } + if (cmdName == 'jsp'){ + command.exec(jsArgs, sender); + result = true; + } + return result; + }; +} diff --git a/src/main/javascript/lib/tabcomplete-jsp.js b/src/main/javascript/lib/tabcomplete-jsp.js index b75e883..8a0c129 100644 --- a/src/main/javascript/lib/tabcomplete-jsp.js +++ b/src/main/javascript/lib/tabcomplete-jsp.js @@ -2,9 +2,9 @@ var _commands = require('command').commands; /* Tab completion for the /jsp commmand */ -var __onTabCompleteJSP = function() { - var result = global.__onTC_result; - var args = global.__onTC_args; +var __onTabCompleteJSP = function( __onTC_result, __onTC_sender, __onTC_cmd, __onTC_alias, __onTC_args) { + var result = __onTC_result; + var args = __onTC_args; var cmdInput = args[0]; var cmd = _commands[cmdInput]; if (cmd){ diff --git a/src/main/javascript/lib/tabcomplete.js b/src/main/javascript/lib/tabcomplete.js index 37e0bbc..885f6d0 100644 --- a/src/main/javascript/lib/tabcomplete.js +++ b/src/main/javascript/lib/tabcomplete.js @@ -73,12 +73,13 @@ var _getProperties = function(o) return result.sort(); }; -var onTabCompleteJS = function() { +var onTabCompleteJS = function( __onTC_result, __onTC_sender, __onTC_cmd, __onTC_alias, __onTC_args) { if (__onTC_cmd.name == 'jsp') - return tabCompleteJSP() + return tabCompleteJSP( __onTC_result, __onTC_sender, __onTC_cmd, __onTC_alias, __onTC_args ); + var _globalSymbols = _getProperties(global) - var result = global.__onTC_result; - var args = global.__onTC_args; + var result = __onTC_result; + var args = __onTC_args; var lastArg = args.length?args[args.length-1]+'':null; var propsOfLastArg = []; var statement = args.join(' '); diff --git a/src/main/javascript/plugins/alias/alias.js b/src/main/javascript/plugins/alias/alias.js index 1d325e1..ca7eb2e 100644 --- a/src/main/javascript/plugins/alias/alias.js +++ b/src/main/javascript/plugins/alias/alias.js @@ -175,7 +175,9 @@ var _intercept = function( msg, invoker, exec) isAlias = true; }else{ if (config.verbose){ - console.info("No global alias found for command: " + command); + var commandObj = server.commandMap.getCommand(command); + if (!commandObj) + console.info("No global alias found for command: " + command); } } /* @@ -188,7 +190,9 @@ var _intercept = function( msg, invoker, exec) isAlias = true; }else{ if (config.verbose){ - console.info("No player alias found for command: " + command); + var commandObj = server.commandMap.getCommand(command); + if (!commandObj) + console.info("No player alias found for command: " + command); } } for (var i = 0;i < template.length; i++) diff --git a/src/main/javascript/plugins/arrows.js b/src/main/javascript/plugins/arrows.js index fb0d73d..9cf7783 100644 --- a/src/main/javascript/plugins/arrows.js +++ b/src/main/javascript/plugins/arrows.js @@ -11,13 +11,13 @@ The arrows mod adds fancy arrows to the game. Arrows which... ### Usage: - * `/js arrows.firework()` - A firework launches where the the arrow lands. - * `/js arrows.lightning()` - lightning strikes where the arrow lands. - * `/js arrows.teleport()` - makes player teleport to where arrow has landed. - * `/js arrows.flourish()` - makes a tree grow where the arrow lands. - * `/js arrows.explosive()` - makes arrows explode. - * `/js arrows.normal()` sets arrow type to normal. - * `/js arrows.sign()` turns a targeted sign into a Arrows menu + * `/js arrows.firework(self)` - A firework launches where the the arrow lands. + * `/js arrows.lightning(self)` - lightning strikes where the arrow lands. + * `/js arrows.teleport(self)` - makes player teleport to where arrow has landed. + * `/js arrows.flourish(self)` - makes a tree grow where the arrow lands. + * `/js arrows.explosive(self)` - makes arrows explode. + * `/js arrows.normal(self)` sets arrow type to normal. + * `/js arrows.sign(self)` turns a targeted sign into a Arrows menu All of the above functions can take an optional player object or name as a parameter. For example: `/js arrows.explosive('player23')` makes @@ -27,6 +27,7 @@ player23's arrows explosive. var signs = require('signs'); var fireworks = require('fireworks'); +var utils = require('utils'); var _store = {players: {}}; @@ -75,14 +76,8 @@ for (var type in _types) { arrows[type] = (function(n){ return function(player){ - if (typeof player == "undefined") - player = self; - var playerName = null; - if (typeof player == "string") - playerName = player; - else - playerName = player.name; - arrows.store.players[playerName] = n; + player = utils.getPlayerObject(player); + arrows.store.players[player.name] = n; }; })(_types[type]); } diff --git a/src/main/javascript/plugins/chat/color.js b/src/main/javascript/plugins/chat/color.js index 62493e7..3d0000f 100644 --- a/src/main/javascript/plugins/chat/color.js +++ b/src/main/javascript/plugins/chat/color.js @@ -35,19 +35,19 @@ events.on("player.AsyncPlayerChatEvent",function(l,e){ e.message = "§" + colorCodes[playerChatColor] + e.message; } }); -var listColors = function(params){ +var listColors = function(params,sender){ var colorNamesInColor = []; for (var i = 0;i < colors.length;i++) colorNamesInColor[i] = "§"+colorCodes[colors[i]] + colors[i]; - self.sendMessage("valid chat colors are " + colorNamesInColor.join(", ")); + sender.sendMessage("valid chat colors are " + colorNamesInColor.join(", ")); }; command("list_colors", listColors); -command("chat_color",function(params){ +command("chat_color",function(params,sender){ var color = params[0]; if (colorCodes[color]){ - chat.setColor(self,color); + chat.setColor(sender,color); }else{ - self.sendMessage(color + " is not a valid color"); + sender.sendMessage(color + " is not a valid color"); listColors(); } },colors); diff --git a/src/main/javascript/plugins/classroom/classroom.js b/src/main/javascript/plugins/classroom/classroom.js index a1f07fe..04f8d62 100644 --- a/src/main/javascript/plugins/classroom/classroom.js +++ b/src/main/javascript/plugins/classroom/classroom.js @@ -32,11 +32,11 @@ to every student in a Minecraft classroom environment. To allow all players (and any players who connect to the server) to use the `js` and `jsp` commands... - /js classroom.allowScripting(true) + /js classroom.allowScripting(true,self) To disallow scripting (and prevent players who join the server from using the commands)... - /js classroom.allowScripting(false) + /js classroom.allowScripting(false,self) Only ops users can run the classroom.allowScripting() function - this is so that students don't try to bar themselves and each other from scripting. @@ -44,16 +44,25 @@ don't try to bar themselves and each other from scripting. ***/ var _store = {enableScripting: false}; var classroom = plugin("classroom", { - allowScripting: function (/* boolean: true or false */ canScript) { + allowScripting: function (/* boolean: true or false */ canScript, sender) { + if (typeof sender == 'undefined'){ + console.log("Attempt to set classroom scripting without credentials"); + return; + } + if (!sender.op){ + console.log("Attempt to set classroom scripting without credentials: " + sender.name); + return; + } + /* only operators should be allowed run this function */ - if (!self.isOp()) + if (!sender.isOp()) return; if (canScript){ - utils.foreach( server.onlinePlayers, function (player) { - player.addAttachment(__plugin, "scriptcraft.*", true); - }); + utils.foreach( server.onlinePlayers, function (player) { + player.addAttachment(__plugin, "scriptcraft.*", true); + }); }else{ utils.foreach( server.onlinePlayers, function(player) { utils.foreach(player.getEffectivePermissions(), function(perm) { diff --git a/src/main/javascript/plugins/commando/commando-test.js b/src/main/javascript/plugins/commando/commando-test.js index 0f17784..56900b4 100644 --- a/src/main/javascript/plugins/commando/commando-test.js +++ b/src/main/javascript/plugins/commando/commando-test.js @@ -9,6 +9,13 @@ var times = { Dusk: 12000, Midnight: 18000 }; -commando('scriptcrafttimeofday',function(params){ - self.location.world.setTime(times[params[0]]); +commando('scriptcrafttimeofday',function(params,sender){ + if (config.verbose){ + console.log('scriptcrafttimeofday.params=%s',JSON.stringify(params)); + } + if (sender.location) + sender.location.world.setTime(times[params[0]]); + else + sender.sendMessage('This command only works in-world'); + },['Dawn','Midday','Dusk','Midnight']); diff --git a/src/main/javascript/plugins/commando/commando.js b/src/main/javascript/plugins/commando/commando.js index 2943a22..6f2694a 100644 --- a/src/main/javascript/plugins/commando/commando.js +++ b/src/main/javascript/plugins/commando/commando.js @@ -8,7 +8,7 @@ to Minecraft. Normally ScriptCraft only allows for provision of new commands as extensions to the jsp command. For example, to create a new simple command for use by all players... - /js command('hi', function(){ echo('Hi ' + self.name); }); + /js command('hi', function(args,player){ player.sendMessage('Hi ' + player.name); }); ... then players can use this command by typing... @@ -45,8 +45,8 @@ of the ScriptCraft core. ### Example hi-command.js var commando = require('../commando'); - commando('hi', function(){ - echo('Hi ' + self.name); + commando('hi', function(args,player){ + player.sendMessage('Hi ' + player.name); }); ...Displays a greeting to any player who issues the `/hi` command. @@ -54,8 +54,8 @@ of the ScriptCraft core. ### Example - timeofday-command.js var times = {Dawn: 0, Midday: 6000, Dusk: 12000, Midnight:18000}; - commando('timeofday', function(params){ - self.location.world.setTime(times[params[0]]); + commando('timeofday', function(params,player){ + player.location.world.setTime(times[params[0]]); }, ['Dawn','Midday','Dusk','Midnight']); @@ -85,7 +85,7 @@ exports.commando = function(name, func, options, intercepts){ }; events.on('player.PlayerCommandPreprocessEvent', function(l,e){ - var msg = "" + e.message; + var msg = '' + e.message; var parts = msg.match(/^\/([^\s]+)/); if (!parts) return; @@ -97,7 +97,7 @@ events.on('player.PlayerCommandPreprocessEvent', function(l,e){ } }); events.on('server.ServerCommandEvent', function(l,e){ - var msg = "" + e.command; + var msg = '' + e.command; var parts = msg.match(/^\/*([^\s]+)/); if (!parts) return; @@ -105,6 +105,10 @@ events.on('server.ServerCommandEvent', function(l,e){ return; var command = parts[1]; if (commands[command]){ - e.command = "jsp " + msg.replace(/^\//,""); + var newCmd = "jsp " + msg.replace(/^\//,""); + if (config.verbose){ + console.log('Redirecting to : %s',newCmd); + } + e.command = newCmd; } }); diff --git a/src/main/javascript/plugins/drone/contrib/fort.js b/src/main/javascript/plugins/drone/contrib/fort.js index d6477dd..90686c7 100644 --- a/src/main/javascript/plugins/drone/contrib/fort.js +++ b/src/main/javascript/plugins/drone/contrib/fort.js @@ -33,7 +33,7 @@ Drone.extend('fort', function(side, height) try{ this.boxa(turret,1,1,side-2).fwd(side-2).turn(); }catch(e){ - self.sendMessage("ERROR: " + e.toString()); + console.log("ERROR: " + e.toString()); } } // diff --git a/src/main/javascript/plugins/drone/drone.js b/src/main/javascript/plugins/drone/drone.js index 014cf2a..497a8b3 100644 --- a/src/main/javascript/plugins/drone/drone.js +++ b/src/main/javascript/plugins/drone/drone.js @@ -46,7 +46,7 @@ Drones can be created in any of the following ways... var d = new Drone().box( blocks.oak ) - ... All of the Drone's methods return `this` (self) so you can chain operations together like this... + ... All of the Drone's methods return `this` so you can chain operations together like this... var d = box( blocks.oak ) .up() @@ -727,8 +727,6 @@ Drone = function(x,y,z,dir,world) this.chkpt('start'); this.record = true; this.history = []; - // for debugging - // self.sendMessage("New Drone " + this.toString()); return this; }; diff --git a/src/main/javascript/plugins/homes/homes.js b/src/main/javascript/plugins/homes/homes.js index b7af5d6..9c493cd 100644 --- a/src/main/javascript/plugins/homes/homes.js +++ b/src/main/javascript/plugins/homes/homes.js @@ -251,71 +251,71 @@ exports.homes = homes; var options = { 'set': function(){homes.set();}, 'delete': function(){ homes.remove();}, - 'help': function(){ self.sendMessage(homes.help());}, - 'list': function(){ + 'help': function(params, sender){ sender.sendMessage(homes.help());}, + 'list': function(params, sender){ var visitable = homes.list(); if (visitable.length == 0){ - self.sendMessage("There are no homes to visit"); + sender.sendMessage("There are no homes to visit"); return; }else{ - self.sendMessage([ + sender.sendMessage([ "You can visit any of these " + visitable.length + " homes" ,visitable.join(", ") ]); } }, - 'ilist': function(){ + 'ilist': function(params, sender){ var potentialVisitors = homes.ilist(); if (potentialVisitors.length == 0) - self.sendMessage("No one can visit your home"); + sender.sendMessage("No one can visit your home"); else - self.sendMessage([ + sender.sendMessage([ "These " + potentialVisitors.length + "players can visit your home", potentialVisitors.join(", ")]); }, - 'invite': function(params){ + 'invite': function(params,sender){ if (params.length == 1){ - self.sendMessage("You must provide a player's name"); + sender.sendMessage("You must provide a player's name"); return; } var playerName = params[1]; var guest = utils.getPlayerObject(playerName); if (!guest) - self.sendMessage(playerName + " is not here"); + sender.sendMessage(playerName + " is not here"); else - homes.invite(self,guest); + homes.invite(sender,guest); }, - 'uninvite': function(params){ + 'uninvite': function(params,sender){ if (params.length == 1){ - self.sendMessage("You must provide a player's name"); + sender.sendMessage("You must provide a player's name"); return; } var playerName = params[1]; var guest = utils.getPlayerObject(playerName); if (!guest) - self.sendMessage(playerName + " is not here"); + sender.sendMessage(playerName + " is not here"); else - homes.uninvite(self,guest); + homes.uninvite(sender,guest); }, - 'public': function(params){ - homes.open(self,params.slice(1).join(' ')); - self.sendMessage("Your home is open to the public"); + 'public': function(params,sender){ + homes.open(sender,params.slice(1).join(' ')); + sender.sendMessage("Your home is open to the public"); }, - 'private': function(){ - homes.close(); - self.sendMessage("Your home is closed to the public"); + 'private': function(params, sender){ + homes.close(sender); + sender.sendMessage("Your home is closed to the public"); }, - 'listall': function(){ - if (!self.isOp()) - self.sendMessage("Only operators can do this"); + 'listall': function(params, sender){ + if (!sender.isOp()) + sender.sendMessage("Only operators can do this"); else - self.sendMessage(homes.listall().join(", ")); + sender.sendMessage(homes.listall().join(", ")); }, - 'clear': function(params){ - if (!self.isOp()) - self.sendMessage("Only operators can do this"); + 'clear': function(params,sender){ + if (!sender.isOp()) + sender.sendMessage("Only operators can do this"); else - homes.clear(params[1]); + homes.clear(params[1], sender); } }; var optionList = []; @@ -324,20 +324,20 @@ for (var o in options) /* Expose a set of commands that players can use at the in-game command prompt */ -command("home", function ( params ) { +command("home", function ( params , sender) { if (params.length == 0){ - homes.go(); + homes.go(sender,sender); return; } var option = options[params[0]]; if (option) - option(params); + option(params,sender); else{ var host = utils.getPlayerObject(params[0]); if (!host) - self.sendMessage(params[0] + " is not here"); + sender.sendMessage(params[0] + " is not here"); else - homes.go(self,host); + homes.go(sender,host); } },optionList); diff --git a/src/main/javascript/plugins/minigames/NumberGuess.js b/src/main/javascript/plugins/minigames/NumberGuess.js index d03d7f2..01424a4 100644 --- a/src/main/javascript/plugins/minigames/NumberGuess.js +++ b/src/main/javascript/plugins/minigames/NumberGuess.js @@ -9,14 +9,14 @@ code is to demonstrate use of Bukkit's Conversation API. ### Example - /js Game_NumberGuess.start() + /js Game_NumberGuess.start(self) Once the game begins, guess a number by typing the `/` character followed by a number between 1 and 10. ***/ exports.Game_NumberGuess = { - start: function() { + start: function(sender) { importPackage(org.bukkit.conversations); var number = Math.ceil(Math.random() * 10); @@ -54,7 +54,7 @@ exports.Game_NumberGuess = { var conv = cf.withModality(true) .withFirstPrompt(prompt) .withPrefix(new ConversationPrefix(){ getPrefix: function(ctx){ return "[1-10] ";} }) - .buildConversation(self); + .buildConversation(sender); conv.begin(); } }; From ab2b26dacec806adce0e61a739c197fa51fc26d6 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Mon, 30 Dec 2013 21:52:58 +0000 Subject: [PATCH 063/456] removed coffeescript target from build.xml --- build.xml | 9 +-------- docs/release-notes.md | 9 +++++++++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/build.xml b/build.xml index b845ae4..31ff0d1 100644 --- a/build.xml +++ b/build.xml @@ -72,17 +72,10 @@
- - Retrieving Coffeescript compiler - - - - + - diff --git a/docs/release-notes.md b/docs/release-notes.md index afefd0f..db37ff2 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,3 +1,12 @@ +# 2013 12 30 + +Removing coffeescript support because coffeescript.js will not +compile/evaluate (generated bytecode exceeds 64K limit). + +Updated plugin to remove `self` variable once the `js` command +evaluation has completed (limits the scope of the `self` variable to +just command-prompt evaluation). + # 2013 12 29 Bug Fix: [Can't get Scriptcraft core libraries working][bug103]. From fba374d00ef20e932bc91a9053805b6d4e2b6908 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 31 Dec 2013 08:47:28 +0000 Subject: [PATCH 064/456] removed cofffee from plugin.yml and fixed link in README.md --- README.md | 25 ++++++++++++++----------- src/main/resources/plugin.yml | 5 ----- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 66b87e2..2ddface 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,15 @@ installed, you can add your own new Mods by adding Javascript (.js) files in a directory. * If you're new to programming and want to start modding Minecraft, then [Start Here][ypgpm]. - * If you've already used [Scratch][scr], have attended a few [CoderDojo][cd] sessions, or have already dabbled with Javascript, then [Start Here][cda]. + * If you've already used [Scratch][scr], have attended a few + [CoderDojo][cd] sessions, or have already dabbled with Javascript, + then [Start Here][cda]. * Watch some [demos][ytpl] of what you can do with ScriptCraft. # Description -ScriptCraft is a plugin for Minecraft Servers which lets -operators, administrators and plug-in authors customize the game using +ScriptCraft is a plugin for Minecraft Servers which lets operators, +administrators and plug-in authors customize the game using Javascript. ScriptCraft makes it easier to create your own mods. Mods can be written in Javscript and can use the full Bukkit API. The ScriptCraft mod also lets you enter javascript commands at the in-game @@ -88,10 +90,9 @@ simplest mod to get started with. Additional information ====================== Because the Bukkit API is open, all of the Bukkit API is accessible -via javascript once the ScriptCraft plugin is loaded. For example, in -addition to the functions provided in the MCP version of ScriptCraft, -there are a couple of useful Java objects exposed via javascript in -the Bukkit ScriptCraft plugin... +via javascript once the ScriptCraft plugin is loaded. There are a +couple of useful Java objects exposed via javascript in the Bukkit +ScriptCraft plugin... * `__plugin` - the ScriptCraft Plugin itself. This is a useful starting point for accessing other Bukkit objects. The `__plugin` @@ -100,11 +101,13 @@ the Bukkit ScriptCraft plugin... __plugin.server.motd` returns the server's message of the day (javascript is more concise than the equivalent java code: __plugin.getServer().getMotd() ). - * `self` - The player/command-block or server console operator who - invoked the js command. Again, this is a good jumping off point for - diving into the Bukkit API. + * `server` - The top-level org.bukkit.Server object. See the [Bukkit API docs][bukapi] for reference. + * `self` - The player/command-block or server console operator who + invoked the `/js` command. Again, this is a good jumping off point for + diving into the Bukkit API. + [dl]: http://scriptcraftjs.org/download [api]: http://jd.bukkit.org/apidocs/org/bukkit/plugin/java/JavaPlugin.html [ib]: http://wiki.bukkit.org/Setting_up_a_server @@ -126,7 +129,7 @@ You can find more information about [ScriptCraft on my blog][blog]. [buk]: https://github.com/walterhiggins/ScriptCraft/blob/master/bukkit.md [yp]: docs/YoungPersonsGuideToProgrammingMinecraft.md [mm]: http://walterhiggins.net/blog/ScriptCraft-1-Month-later -[api]: https://github.com/walterhiggins/ScriptCraft/blob/master/docs/api.md +[api]: https://github.com/walterhiggins/ScriptCraft/blob/master/docs/API-Reference.md [website]: http://scriptcraftjs.org/ [ypgpm]: docs/YoungPersonsGuideToProgrammingMinecraft.md [cd]: http://coderdojo.com/ diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 56f245e..c856198 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -12,11 +12,6 @@ commands: usage: / command-name command-parameters permission: scriptcraft.proxy permission-message: You don't have permission. - coffee: - description: Evaluate coffeescript. - usage: / Coffeescript code - permission: scriptcraft.evaluate - permission-message: You don't have permission. permissions: scriptcraft.*: From 0ec7dfc8ad86e7c14493b7bbd288d3faa069f834 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 31 Dec 2013 09:48:38 +0000 Subject: [PATCH 065/456] Updated README and added Anatomy doc --- README.md | 4 +- docs/Anatomy-of-a-Plugin.md | 141 ++++++++++++++++++++++ docs/img/scriptcraft-chat-color.png | Bin 0 -> 100465 bytes src/main/javascript/plugins/chat/color.js | 4 +- 4 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 docs/Anatomy-of-a-Plugin.md create mode 100644 docs/img/scriptcraft-chat-color.png diff --git a/README.md b/README.md index 2ddface..86b00fc 100644 --- a/README.md +++ b/README.md @@ -122,13 +122,14 @@ ScriptCraft has [its own website][website] with further information. * To get started using ScriptCraft to Learn Javascript, read [The Young Person's Guide to Programming in Minecraft][yp]. * The ScriptCraft [API documentation][api]. * To delve deeper into creating your own minecraft mod for use by others, read [Creating a complete Minecraft Mod in Javascript][mm]. + * Take a look at some [examples][ex] You can find more information about [ScriptCraft on my blog][blog]. [blog]: http://walterhiggins.net/blog/cat-index-scriptcraft.html [buk]: https://github.com/walterhiggins/ScriptCraft/blob/master/bukkit.md [yp]: docs/YoungPersonsGuideToProgrammingMinecraft.md -[mm]: http://walterhiggins.net/blog/ScriptCraft-1-Month-later +[mm]: docs/Anatomy-of-a-Plugin.md [api]: https://github.com/walterhiggins/ScriptCraft/blob/master/docs/API-Reference.md [website]: http://scriptcraftjs.org/ [ypgpm]: docs/YoungPersonsGuideToProgrammingMinecraft.md @@ -136,3 +137,4 @@ You can find more information about [ScriptCraft on my blog][blog]. [scr]: http://scratch.mit.edu/ [cda]: http://cdathenry.wordpress.com/category/modderdojo/ [ytpl]: http://www.youtube.com/watch?v=DDp20SKm43Y&list=PL4Tw0AgXQZH5BiFHqD2hXyXQi0-qFbGp_ +[ex]: ./src/main/javscript/plugins/examples/ diff --git a/docs/Anatomy-of-a-Plugin.md b/docs/Anatomy-of-a-Plugin.md new file mode 100644 index 0000000..c03a99c --- /dev/null +++ b/docs/Anatomy-of-a-Plugin.md @@ -0,0 +1,141 @@ +# Anatomy of a ScriptCraft Plugin + +Anything you can do using a java-based plugin, you can do it +faster and easier in Javascript with the ScriptCraft plugin. To +demonstrate this, I've recreated a commonly-used mod (homes) in +javascript. The `homes` javascript plugin lets players set their current +location as home and return to that location using in-game commands. +They can also visit other players' homes. It's a simple plugin that +demonstrates a couple of new features in ScriptCraft ... + + * Persistence + * Adding Player (non-operator) commands + +... First persistence. Persistence is the ability to retain state after +the server has shutdown and started up again. Persistence is something +you get for free if you create your javsacript plugin using the new +`plugin()` function provided with ScriptCraft - just keep any data you +want to save in a property called `store` and that data will be written +and read at shutdown and startup. The data is persisted in JSON form so +it's even somewhat human-readable. Declaring a new plugin is easy, you +give your plugin a name, specify an interface/object and whether the +plugin should be persistent. For this I'm going to create a new plugin +called "chat" that will let players change the default color of their messages +in the in-game chat window... + + var _store = {players: {}}; + exports.chat = plugin('chat', { + setColor: function(player,chatColor) { + _store.players[player.name] = chatColor; + }, + store: _store + }, true); + +The above code doesn't do a whole lot other than let operators set a +player's color choice ( `/js chat.setColor(self, 'green')` ). A little +bit more code has to be added so that the player's text color will +change when chatting with other players, but the above code will ensure +the player's color setting is at least saved. The following code just +ensures that when a player chats , the text will be displayed in their +chosen color... + + var colors = ['black', 'blue', 'darkgreen', 'darkaqua', 'darkred', + 'purple', 'gold', 'gray', 'darkgray', 'indigo', + 'brightgreen', 'aqua', 'red', 'pink', + 'yellow', 'white']; + var colorCodes = {}; + for (var i =0;i < colors.length;i++) colorCodes[colors[i]] = i.toString(16); + + events.on('player.AsyncPlayerChatEvent',function(l,e){ + var player = e.player; + var playerChatColor = _store.players[player.name]; + if (playerChatColor){ + e.message = '§' + colorCodes[playerChatColor] + e.message; + } + }); + +The next step is to declare a lookup table of colors / names and add an event +handler which intercepts and inserts color codes into player's text +messages. + +The other command in ScriptCraft is the `/jsp` command - this lets +operators expose plugins for use by regular players. To be clear, `/jsp` +does not do any javascript evaluation, it just accepts parameters which +are then passed on to the appropriate javascript plugin. So far in this +example plugin we haven't provided any way for regular players to - you +know - actually set their text color of choice - only operators can do +this for a player using the `js chat.setColor(...)` javascript +expression. Let's be clear - giving your players access to the whole API +via javascript isn't a good idea. So how do you safely let players +choose their text color? If you've written a javascript function and +want players to be able to use that function, you expose it using the +new `command()` function like so... + + command('chat_color',function(params,sender){ + var color = params[0]; + if (colorCodes[color]){ + chat.setColor(sender,color); + }else{ + sender.sendMessage(color + ' is not a valid color'); + sender.sendMessage('valid colors: ' + colors.join(', ')); + } + },colors); + +... The above code adds a new *subcommand* to the `/jsp` command and +also specifies autocomplete options (the last parameter - `colors`) for +that command when the player presses the `TAB` key. Now the player +themselves can change their chosen chat color like so... + + /jsp chat_color yellow + +... What I've done here is create a new plugin which lets players choose +a chat color and saves that preference when the server shuts down and +starts up. I've also added a new `jsp` sub-command - `chat_color` that +players use to change their chat color setting. The full plugin source +code is just a couple of lines of code but is a fully working plugin... + + // declare a new javascript plugin + var _store = {players: {}} // private variable + exports.chat = plugin('chat', { + setColor: function(player, color){ + _store.players[player.name] = color; + }, + store: _store + },true); + + var colors = ['black', 'blue', 'darkgreen', 'darkaqua', 'darkred', + 'purple', 'gold', 'gray', 'darkgray', 'indigo', + 'brightgreen', 'aqua', 'red', 'pink', + 'yellow', 'white']; + var colorCodes = {}; + for (var i =0;i < colors.length;i++) colorCodes[colors[i]] = i.toString(16); + + events.on('player.AsyncPlayerChatEvent',function(l,e){ + var player = e.player; + var playerChatColor = _store.players[player.name]; + if (playerChatColor){ + e.message = '§' + colorCodes[playerChatColor] + e.message; + } + }); + command('chat_color',function(params,sender){ + var color = params[0]; + if (colorCodes[color]){ + chat.setColor(sender,color); + }else{ + sender.sendMessage(color + ' is not a valid color'); + sender.sendMessage(colors.join(',')); + } + },colors); + + +![Chat Color plugin][1] + +... this is what I would call a minimum viable plugin and it +demonstrates some of the new features of ScriptCraft - persistence +(automatic) , event handling, and exposing new functionality to players +using the `/jsp` command. I hope this will give potential MineCraft +modders a feel for just how easy it can be to change the game to suit +their needs. + +[1]: img/scriptcraft-chat-color.png + diff --git a/docs/img/scriptcraft-chat-color.png b/docs/img/scriptcraft-chat-color.png new file mode 100644 index 0000000000000000000000000000000000000000..65208e8bd6f9b4a8e9bfad7cc88814dcb7edbeea GIT binary patch literal 100465 zcmV)HK)t_-P)Px&08mU+MF9c;A_E5i0I~`OWCI0;3lBCE5e*d%B@q!7EC(z-0X{Jd3jrSi8W#yG z5)>R478)2E1Roz+3IZP-3K|~+BpVQN1qK@(8Xp}Xi2?y49vlNJ149`XDIpjYDiI|l zCn_ZoBqbv(Bo-(nB2E=g3@t0H0stN(W-ufyWElZ8CJrnq3N_&JIYB!)JVrM*z#9N|E+ZgDJwZVgZ8Zx`JSspwK}bD5K0rP{KSxGDDMLR# zML#)EJ|_%MPE9{CF-jgXNHIe}MOQv3Q9&I?LNXvtLyt2KX*X+4Mle@IEl5U5OGZUa zMnG9bC_+G_Mnz{wNk&IVP)|)2OG!;$N;3puVoXg{i9R<}PBu?XP;E;TR8B`yPE9vj zDNj&NH&{kuPB2SWELl)L7-3maQCd?`RTyd#X-_lAI0I=>E;eExEMQcdIGn3KJzi8) zRaaD1R$g0HPgqu2T2@sjYFx!YI2UhhTU}ZUegRHpHE~)#UtM5gU0iuwC|zD>D{f$C zUsqpYUVB|TUt?KTSNm;bJ9A_$WMpkWn>e6dW~R2kYx~pXhnl* zK!0gbYi(+EZD(<9bAfM0adBdFZ*GHgERJtYgmgKNa6yi6NW)|&b9Hiub6?nF5&BhC ziFrnrc2gjq9gllJn0HQ#dro_NdYgDkLX|X)dt(5v0D*pV8Jd})dQprD<}oNJAy z#rSREsw z83v-)>QFZmNi+$;2+=edH04y}N=86QQ(To(YGoQ#EAo;wRmf>5RMr^XRR_wr&b5{` zE-bsx*ZUO_QfBp>yKi@(!X0_TRXQ$)T zjN|!wuEz=o+`{gwsvM=KXXj_9r@f1_vkSbrfCr~=kH*>A+3DFSY&yf=XY}0pIo`ni zv-94?Io!hs7x^qklHb;l&Gqfp<8WS8 zWjVqDCv2ofw`-low3~Lj-Q(lqZdvMW{DLimj)1R7;o#loleJ<0DSq8>GMNnA(TBmm zYoh}Lew)WfM@NG!%R~Q8BM3qmaEv@JaExITISjtnGW6DQ7Dn(N9wng-8IHt^xNYn$ z5#B1Duh&bW7apuvtM&WMW>c1Qis2-r-Ey@mD+0Eva5s3_YDpjyy=SNAXE*>}B>40k zIJ`JTrioj`^BkZ>Kzj@|lMNg3m9QfMeF2+K5Lm(uNeg>OGyXauqEABP7Fonw1or{M zOC#K;m%?uF6C%H15T5u*yAO|vc|6of&jWe`pUH&*;2s=mGA%?8nvk}DOWcQ5RT+dF znhaF!@NQ?JNH79uIfNxa+noHn!gSe(}4j!UWsqm??oa0{j1NT-- zcQdBRNQKqUbm8#oDGJZwm>&1>w+z!xVryg(_Q zm(fN{FB(-+2V`LpV6epMRHvhoM}>?d!>eVL<^bI}$_2Et$mA*fGp0oeV&wXI0YmQLZtnO3@M3#itS_#UdNE? zr|}g{!`rQ^h}2am4DVn#6rIq@E|}t@jx`8a8{j&#%4p_jO7p-E{1Dh3vvIWzuQtOP z(b$+zhSyLTX?P>WZWCQ%-?hAW^~Y0$jH1m&dXZiF4I-hMuQ%&WnZAF&Q6>f9MH^g^ zoraSN?4I>lyq=xS;aS|HOuO?9!N0yfB|s5QRKD;FR7LOf;+$+Q+{f)GH2^K76GSW6 zPu34;fqmlf#7E0|LUhs|1n$JXAmnn-DH0OV}N&*i$z6aIz?eoj`;jl8#oGlDe#5oXJg`*qGYbk*P z4KkQsw6;Ces6A4yL5N0^mBIMG9sSJd1S78_y8w(IO}5p;iK_I?LoT1 z^R#R@qzT)BO|2z?56sr4MOM#-+u(IM*6_*}=ebsR$%J;x@on(3BIyde1Tw=rw8TM8 z6c~7IX0;ve&&Kd_&;ygA0dFDj2E?v0}>5rbdBoWB7F~PcMfV#ZneBFyrOxlx(L|jG*C=7EiW@_W@)rz zw`*;|KZF(+G28;J1Mr|P)4JIN$80uh`Uot;n`bc@WmE@_M%@ao3okt+5lnYG8s2up zn`E_P5B69Bpo?e2&N5()J!1R-XJ#uDwxbPMViLt`P!jm0I_ue~s0JW$%;tnXfrp?( z=Bz~U*o)O>wOA~2kO^RX0b88k!Q>96ZRA6vW1ud8|Ih&YWH(u}hFY(ZL1h(u|GrCa zpQS<|-Zb$}u-PS{ERTH@4ZfIO7QJL(TUCQg?bc}>T3)f1?c@r)%v3|cI>_2`whi7c z&-}VJs$B<;!)rWa&yxkmpjg0|EyUG|9qyA{GMIJ2{Njbe7aUg3$$h(h!i zoS&A~weWbg24M`xWcS(<;a{y%5(($Z7Yy3BNA|c!l9^^#U|m@(uxl6e5W0VZ9yz># zU-U+R0e(=^4G$t_8~z8`fPRLaxOjUWsb}CH567wjjjx9HP_b)6%=e5vZXs8_3MXu( zOtRWcsx|*=oqjn+HEo5r>uhU>w=s6x+S$Ym1b92Vx!6k$5L_H1n!z>QvMumtYSiE* zEfB-qh~XVGySQVd$98yW0Guhha?AOOy$vf(FvAPyy4)<)U~j~nLmB{anu%~lcSEV+ z9iU<2Av zfZK1ti&MLvvb_i}PX2mi$4Ldlot{r_WHh_V@_IxrPTcG%+JS>;Lnz!+JIUsIX$@Xi z!@BTRHF)cDFZZ#A*H#D)yb9HDE4xOSg%*K&XL#Yk z5q>HGDH7iC7I;D3?3Jam0;A;;%rDNj=u%HwSLg-cN`M!1MTyp!!A2ab5<_#VboKuI z1?YCLqUQ{+D7WMFx)KwPzBhc$xGWP4Nu1LxuP18Zf;BT3Wb!4Tw-zOCk98}X(tw<` z04{nG$eGZ97fmfm-Y~$)na>kAcv*obzpo5XI6ARuW3*Mm`cwfad^t?;K>lofp z!J9WdTQ0o0hL^>HI%Z5lmORX8vwT+5yrUj*{n)XB_dQNQM`S&^$Lp2o57N`sd;zbes7CLM<7{WADu%(1 zwlh=)df?4T?AZEFa_B{``9xpn^4oPKp{L1YKsu7Wo`cA?!q^j|clv@sCgA+bB0Xw_RE zjainEnGjn(@?Y1e7Q9A3n21?y(AJxf-O#{0Xot5ZL=avS1#3nR@5*UUbu@zF5#*3a zmtsZP8^Q7c0bhZrAWsCwG}hN>iBmlj=BV<#l8ABzx};1op20!2Q*!@;B@WU507~NG zoD40^uX??Z;}q!i1(O?)2_@&P*yozN;Q__eP9(DdZLRorIpF~xhCOy2A7XBU@66W@ z4!2In4(h&!rt!U(HsG~z+yQS>zt$`-vAP4Px-0smSgC=W^}r=56z}Cc9#I(Pf+&)nK@7QP38=EDhFbY*yy{iwVY%!-$f1R%Xg3 z(YU3+tHtn|CUP5cP^QPDCHdk?ey$?cM?E!J1BlPZ^aqa7k4zGnCRPDiP*%J>aWL>y z&KDBNCO@cUH=yWc*KUu39X$>h2Rv`%h#MZ(_dN-*f#N+l6o;9^=7Ek&)PA*@W4LqA zwY)Xe+Jv{;Dqsz~x0+WymK4yhG_j@JE!*JLxj9F7gIBp0Sg2|<8mlQ*zQyu7*Z6Jh zGQ5$7m$M-1xNnD-rRa*B3iFcb-2lo;j$=K4jfg*I2??5RI3~wMI;scZo2iEj!XCh8b_bPS`b-Lg~@FP%$f^2+W4uZFA*UUtmpCZ-U#O|NzuUZWa1l$4VdIyQG3yaR?8V+ica z0u#DqF-wLxE%7@&R|Es0umidaXeV|DR}_cC#GZ`=m02ajgb$ChNnyrTnTQ6T*wKjd z(DIUU2wBFjkumxs$P}jT;yIKZx6})xTAPS&W7r>7`R%Hg9J$PoW;iahj zl)%Najeo+*C)*0I3#<)u!$lB_XEx&ibKrL{YLPq6fscWgj;q<_q?}vXrN2V(Di@Z- zBz;|E8g4CqBK z@A>KJc$D&g_!+J$m1?=dV8%2N_ZUfF622#5h(Qih4#P?BH~;m&e)lhw-S8KImy>@3 zjKYyH4po}j3CUelB0Wsn2|ORJRNg+q>w5=nd1bq@clQtO)g2EOyj>Ij(lWr-umCT` z*>0BxrFd>eoK7Nu8bfO=Z`n3Kw4iK(cVIgfcEFnpG6pGnWJ7L;*Rt;evAi7pQr@rC$~be=B_*bBI2^HFGr@3nv=nCTfPy$(uc|qJ2b6;X z%`*q)mT-_2`P68@?l3chDO;h>GML}Zd~W_Wyd-7y8gQuWgyU_LE=iIW4eN>j-~aFb z>o)|ixEmz90W;V+58dZLaS(_B$JdgV_|QM}PGH8y`RMzUo8#($)<=z;2Htw$xXNlG z)`oXCt2FU?h|yy;^-?<49tvJk4aZ_=`7bA~$!~#I_V<@-a9XRmvwcfJ8bD|ezC~d7+2;&bLViVaw$1n)v65$nJ#8QXmFfupa zEOm-cRV~?)r_ZP3)3eheLD4{Ge$*Z5KV9K5;1Bpc%D~~U0_p|IZRE^`fJ!b-p8fXg z2Nt~de?yr(fs_NWqXM18LNvRH(emPa4bvMy@$P$4Nuitz?E2c@G1_EsD!FtHyjxT( z4b3;78Xz%M`qkK18`=>X)~-F%qbh+N@LCRB3tr6P?QDV989U)cYZ;A;Vw^kI`MbeO zM~t*;ATMv|vfB=C61BifTuE{RrIcZ?I4wCsR*9DZowl?O&gYgep;LZgK)70QelJkV zb2&OzIht=UPk5Rxb6T!GTXj!rUq0(|zP3Y3{Ju2DtN}aQG&euEi z>N1Hql|wH?DKAFPM1@nq5>>`}gK3s*r_ye@#&<$fKKN@OITBU?2_|cdigJXydDZj1 zKxtpZ^5r8ZK>VAKOD}pXZ9`wuA~+w7=6w{m@ICzR@Zj*!#3>Hl8QMM3wXXBbQ4-GZ zTcMTs!EskbE=Q%U3aV1$+a=jhj<(y;I=m{!r?=Lzgtf!FQw2vZyrHSQwAck;2mKE2sLPb}*sDqoB9vvRgm1-Zj4KR2fEp<0YHd9)_5m!?nYQ8r3az#P`(G zHTx^>;s32w8rULMW2=4}!<*_V%boR@*`tuWtG{&ii*=zJdEI#3^J8Vwp`8id~Ug+=ti}c zx!77?=6EhQ!l}iQ;&N&>NX`xT9M__+C8Pnb?eg`G=h$eiK{xneNxALhOnbNjMY1Fp z^ky3N$a&WJ;n8myURDpkk!hJs^w?j<;Wd7NFP>u0KLN$y9|rrF7kNPOH6!zzP$U51v*g18Nd)L)?H#f(YM_jD}ce7lGWvxf})O*6X=Cpt@GF6rOL0TxHQwOQdV1 zO*pqFO#!n=g_hy-I@W1zb2Q%LvMfsY7HqE1?`wFGO`lv2s>Mh^={Z=99B{uK47K$R z{R0rq-T?-P_r1e=n264~Id@^!eyp@ZQd$}o6#>=zROL2qEqIj#&~=&H;dL;Emy9I1 zrYeSSfp=iw?e8+Ys3KY~_l{028t~=}Z`9d2LV>q$!JB2u@P@Imyd;L}<@c&Ue~%qu z^O8#<0N%~}&3oHalq;R;A;PyRW#)zOR`aD!H4_UwFXy9j!$N|;6q~sn*#Iew)ynYn zLJJZw)1#D=dpJh|bo6GvdVi9rD07ah>QY7={FUblUP|2!dn&L&5+Mj< z&Tyc_2U5fbs2DK2;Xq0-52gIw!L)8@wNXB(J038?reX)eN6pCTse>fSYD$57^e%!}l-9AJ2# znl~KO%HA1HJBz`A)+QS@uYAq6!s}Yno$xkWifn~w89M!`& zaV&{F7^z#%)dz+NJ@f^fuNEMGBR}Ngc$H2`Bzu@j#z8mE-RWV^5%18~&B`QH;R5+F zL(?YZY9bqKTiS$ImA0|wEvaKvYBGE80B^ZHnXO$82w(tjp=*C^cvYVgEgL#CyXvcH z7Mn|&Vr8OIL8LOgyNG2JiQOiU9|JFZ?~*|>zyeI2$$$nAvllmO3A3^7e7`|O!O>y+ z{E?$FRsHuW2`)&0qXJE9!fHBEa+zLjt~Q&StDCEl-n6Cw66X+dlz#}`qYlf4cFQ~HcMON2 zZ7;P>r7JzC)JVAp8irTcGZMfW!B#F^)u8vMZxP$*yin~W_pt6#IlHj&7X-@Y`kn}ssUaYv#T2!>M*27QhpCgKv$SBfdOf5i3V&^ zkZrS}PfOrUk5?RvM)6JXQm!OrK~VCU&k{G}CJ>a}fcfd5c`2RO=dMo&_YTqBV0jN5 zbeoYj^-w9^{dd6u#c@vMr$$ebDRd2Yy8}fOV#)f-s34+ z-cUJpv0Dkv-p~aR1*a!m5JaWLJx``=9jWZ8~5JygMX7 z{RK%^!8((K#8R+^mfPP%d0@F;KZ$x-kjsST4LlOO0YTm0AgIV3wlCpqeiv zashUeI#FNUOwjJmm|H5n9?Z}H)2}Pm!YL=b!<3GFAzW&|q(Y>M;~Z$?S8K4sbFSLQ z8l$o*9^4Np1Bdf)q+uD_TaPwU5k|ndW<;(hMQjvet9#0J!b-r}_oI=^mksY-EpKZB z>6X`ZtuEE23okxhatOns(cIYZqO={3|ce-Jr1t|ICrmw`eyJUPbJDK354jOC^exCbkp-yVus(N>ZQd1gqX9<%h29 z+->seW>y=M!Qe`a@s$uf7^tIQPd^YW75%vp!A$UCa(h{^q$U}`pLB`th7)dcChelA zVhH>Bh9`&QI-_xYO^li1Hq#Q_wUEl zc!UZjPCU}Q*jeuc^T&F=f*O>Ae-PlTjp7tOWtJhlwl%G{-aBxCHIp`)vMIz|9(N0Y zQGI8}it-*Ujd(41M;sZJ&D-GZH{l%!aBO5dNA<2|%1uz0s?LZCn>Y75kMJI4%D`Ii z<_g{x!<(6u9Xm3@84#!%9=q@!n}QxLPfshVahI-Tx@{!4$f3SZ=pZYl?-MBoN^5}G z#%L|&7UY~*=_m(JD+*K-Fhe7Idv(RLwkt5bJd4vxVm2AR$C5YfYQ2(N6;UIU*1+#( z4)BtQpom6UQs;@kfancRRH0Ddok-&cu)uNj2XdZ;)RPAT#HPQrr+SVkK5P16*F&{) z*Cz6g@LJ5Z!^>>e_cd*x+#R-C-cF~*wjOJEI}Larsym@GqW(Fm_5c7N07*naRIw#g zZKc`{Z|pXurWaGlfmT56%xb2)k-Us z)pETW;mR2*;{qdr6}EpUrRInNAtmLT{e?PA2jXdv#Q>-ZDDrwQ!O-1nrz^uD)$s7fte zSFdY{tsLG1o9}IwSK+8kX-hl)uG)E2ntJ11!@$eE8(eB-C^TEzoN|XKdC(^lI2a@x zQ_FLhq+zd7vYKNF2;>w_+X}B1sV4W)SNXm!s9WK!B~Zk*!CJde8N@OC5$Y0o{C>kR z2y}p%Z@pk*x|Wp^6P`;aUt1DO=Nzn9Q-T6>c<17xpc;r3`6ke_JJ1MoE4UkOSIGQM zKuk=QC3i%n-(`g^j=A)tQp>wk<&s;aVPv@Cx!m#rHXfl?sAy$R!PxrV>EVfg8o@@m z>|mB;MC%M;O5~vr=6B!oxrFVW4X7rTV-Pi)+hUZX8j#!JtzYF8iCwOOQ?_{f*jgWF z4~fNgTG%a`!zR(%g_iP>X;zjucTOzaA{)Lq^rarS25(DmhdR9GP>~TVEV=1VtNr#U zoqxSyzwL&9qhei-oC}yp3hMimR%`0}gn6~7cNM%i9Rqlml~oRhsn}>QK}An#&5PBV zLlc|LP~|b9vy9EKAPoEt*IYQn{too`q91o9(X`d*{d+0(;`DC#Gb0i=2(Qm(7k)*4 zK>pnD#EX&>FE|Cgdl>qWhf%l#Zd2xK)r(=a*7ng_8#F=}_Qt!xyVZ_rcv(pN8eDCw z8yqR;EZ&SCvl)H-xOFkVUGSfqY`g?g$(Z5G#1vJ!Vl_2LmbMF9B@?4tRP z8(lXX0Xnun;{|$Tv7Tc4N|a**FQ|ts0d^w+TKC&(RscwOw!H^5y)T-C5m7qqb( z*9>n+?@_;UkF9~U>{m$+=uMD^bfHd{@3|G9UXX1x8mY1t$&@T9FfZ*Q=MWC^)gWx*GHcXpTWmjPn^Pjw!Flf(sdzs1Brz6Qs*Zy8EDKW68Z) zM^tu1@bZd-m=zgDpc6vCIg9}0u!k8?K`27l$IcBD9HzuFinO&G-u)IIgR{@U{=KjP zq-{(aw$vi@*TDMl*v`cr4pc#!eER|3SV&b1pfunes>zha7VtknU#`_Z<?crM}JZ zVv-{Uef34&iK|7%`nIrp3%nl7YkizOE+{G0(!wic-BhtU{Z3VuFEwpiTeCYJMi8eG z48|rq+{b~|hsE5L2=ma4(F}-QZ*A5sD0}c4aP+ zdj8g8YwLdChdbd_M)e~ZwP@8>rQz0=(JIeoXu~_v%lQ?L$GLc!@_J#U+(j?&HhA5O zJ;PfEt_BRMY*+^nF^ZzE3EO z*D9+)80M%j_cpFpT0l^i!b&yfn=5}V!fSlHG=>9Wc(b`uF*ZuJ#yryw(dUOS1A5$Ln8dGFlW2^T*ZO)@R}a$Eta?8BGcazYgytVs>4g2NVU_86|8PCf({0NmY?G) z%&OAepD534scX3zViR%KWyrb!zEaVM;qYp6MV?-EGEA7~s~H7}Z!iO$D?w3er?jvp zt$s|U8eA+yK8+H=UWxN=#9ebvFcg6fGCu%d!NhuitbZDVkci=_Fa&$-`Cf2%;_YMa zkpSmwhg-UwHWs-OrrlthvAmAK+}`f#*lM=Rz*qVbrtTHk(PA(Yr(&~L^w1bvE@PKm z9GI>pkEO|Y8@%?k$uhhN>SYX_2;P-ACn|(Dq!y;TQ2tw$!w3cJ92KilDQ5^ z^YA!y;T;YXsAOVWnluT#x+nYUc%e}Zu>~)7$F*?mndOmTaqWCc@<1rM^I1v_F{f6{Xn#A&%y z9}>I7a3v2%^ipCD=mv6@&1OsLK!q{j70nwrGGXLo*^=|zu%;dq@v4%<_hp#^ypd|n zhn1Hnry;u>fL5%-pz+ws~Yt9RJ1&D9VJbgKmfuV81x+ql>h zjLufaQj(#Jbl}&8_pa<(hBwp!3K_Bv8!q>^Qj1Pi(w$5d^>E*(jn(&w31@im1h1uN zQom%$EveHRlCW2rVbm0(QeWr>SP!H%FG>f1_e!iWyM@d7M*qbMEc^%qD#~*4=T0kl zFIf(+$npld_$TywhnR+Q8ivuS7sb981*cdBbczjnee622PpM_%6%$Q+(3)9xV9nyJ zC)Fcf1g$$)t;n|6Qi4|!l^XD-jnM+H(F)rw?@_17?GHMU46VDuo3+5ZjX4)LqZ-gn z94V*7M;KoF6$V}@w6DW^+)a-uy33Go9y$kusW^>F+t#M4!kFkKm;7*Nl8S3_xQLA^ zCsITg;N_be_yoZC3{x>4@QKW>;CFGQ(#d9%VREC=&S=g!#~C1{;gEiTLt=bnO%cJB zbb^wPzakwoPVYZB;bKc*G&&5zGxRA?bM=VUvdo!C~*f_o?)K|G=Y!-oqNI zj{V(jlkbT+RlS|css&!`mqYN%HeLTPYNX)Y1>Od`y3FUOGj!mU^ulB(ym4q*-i~1B zP_Im#lpVa-} zIfuIXW2(+vWv)x@q{I?tHi@C%o zQm%_*MZCtjA~&j|PQlm_BD;TIbt*%p8$=#p?Bf~~Aavv4)a!w8I1OVz!X=3R=R;q$ ziliQ=H9zl1)eqAfG4QHJrHWsj{dj!LwQ5%Wn84dsZXxit+1G(sfjYd@%&BI1Ti~q? zP{Nce=?C1oG_>H|Z7>~pwU;+Q=Ny*Ghb^z%`Yv=$Tx_lfk!O;8Y~iv;S2|Q^_E{Xc z(+KYSv{AK`m72DJu@=+2Tq$1##U|J#hV#4|?F?fzKxvSJ>%}z;*ffQ)uw3mE%#Wn3 zGqk*vj+w%tr)qYH)fD(ep>lV$!y7KCr&^BDih4u=yxbN2MArZDq7z;|H z<7al?C%!|!3u5^C8i2k`ZY<-%P9XXC zS8MJfMIjVw3RQBl+8hgt*X;TL$vaQqV*q7MmRF^df%ZLvn*?NVd`gX;PNJT77WN{{ zhs3-G?6t>BxE{DOvxEAr_VFILs#kWnSL#Jg93Wfa)vcpbH~-O2htz(zfo*tq!^={l zBgqLuiKP#=!<%V%{Z@FlGpDcg@`x|D!9Iz~HrSv5HCd=IX;tj{W`Zl__= zPtAF?`aWsHtl?cNN1K}JExG6{y_w9c8JT430=aUZqGjj&CtU#AXLB+b)}AgUXTp^Kdh zeJC0=Eba_1e=)4oGQFRb*T5ld$KA09EFOzE+q$#eh3%0)#${Z6aC?gYX zEl(a-=sxBg3jC`mY?jOEU+(LaNbHZ(!*#Z!LpU!0Y0=4)c2KZ6IHRM+;!G ztE^e)Q{Uj)Vl5kNc&$ET7LTfIavQu!on=n$f%DMUJ^8njJJ3u02)wa@cblPONHx6h zQkvHTcl}$LQzNRwOQtexss;wrNs6$9h=I4ZjSX+!V3!hej%ChElVvPNqb3L?)wrX7 z71y%dtT$_|43_dqa54;L=p^Mzby!#JXi=xaGYVcQ`{78$;Cc$ymeZY>-#p3b@sxjE z&K6h6d-Sty#Q88$-F;-~$L`2t@fOQlv%=c)-W}cn7OEL|)peh3Zq^(YyorK0 zzZ<+Ze;P(b8#Xe_+?VSeoXlSv)MzWbVN*4rt8A2Zsb)2mEL;`?RO7z3irmRVC1Z_+ z?kjfXsxxisuI=?h)5(?2(zQ(QHR=W?7khL@U>Q1n`QUc()N_qbwsNvcwJkrg$5DmNzAL7QDlHQ#KO*b7yP zFVHxzSB*1|Vmck&n2!P^dP&Eu=#eVE@7UL7b#c!`xDs3$URH<)27y#B9-mr9c9 zp4mo+i#U_lz>9ZKHjKJJFPzI#XOn4owc)k;K9Q$P+DYQHY|VAPX1=$wd~Q;+Hc`1F z6AsLi=^eA$#cHYPqKL^9k00Y?4!19g;mt&K)gj`q=sJM4!uS_Q@3E-Jh)i5e0P}-J z0B?YG!$hcBX&YA-!s6ke=hK{x38uxfePelP70iQ{5SxM3#Nqa9`+(*h={99|hPQ3E zu4*|wA~$cp#m(Eot_v@w>u!bDM#q}PMS{=A!Rtb35)oj84R*twBXHpj>+rgUmo0C% z%a<|9ZqSXNMwn0_$gWh*1LbvrY#61RmC_lQHeN}l*&3`Ors=#Vsa#e%6N);s@Clv_ zKqmmS!<;%#VU=H=!36H{V>HI7dyDB}JlM=;)^u;!UsLkfyjo5%OH)ehF?(mNls1Z& z(+BslQDn$g7rWzxe&BJtUJvVl0&3lWiUQqbejt{YC9e~uX!sqrkAu6x%TusQ%LZd) zNA@?~>DqfMyzWz4@;cFBCnMKl&{hG89|JF0PF^>n6Luo&b(ZC|?`Ya6t)h-$bExCg z4R2^Rk{w6YAS=IUb_rfx&pI;jQjL3BZ79}Is`$fFC#WweO;Q!UOQ|n)r$%p9sZw(Q z`YS0}ySbt~91I2H35L@)P(GR>0fzJO4JZ181%8aRD|w>QeK|-s8?48&#y2@KR(ge( z+(IS!Rt2r@7CdMi3wW|s1L)D8%a%C;f-8)W8ueF7yP~=RN-gaQO8`m_LFMV z(ZCzFHHtNML+5BU)nFx*!JKGFhSz>I!+V_aLLLGqnOc)56743ivYy_3_(C%H?tZN4wf(LIX1nUw_hrM17C^4QLC ztv={rzlNm4W7lpSc^kY|o9b?h;M$(A0D&;}mp?ZrLpwjI1#5R&&cBQ(BpVcPS-n zT(gFeU*q*vA&E=8cXNZW3GIRD!$~H1XJhG3!inl6U8w6MMu*dNWt_@sC2JJIR|nt? zduq9DTpI{iU7~dbsnJu|^Kr!$FHrslPhrv|UvI>+?X>#GuD3*O9uw}mT6nVY_b)+$TeKR=aqEY(LEu-^qMYvBoQVc4x;Jx z_0?i=z0e^x0GApG4Ra;$C8E)i!+dSKA|L(mL3 zbVdLzD73D4X%}3Sgs!zQudb%T+_Hx{YNnVI$_!-brdrW4Nr?~ac!c+Q{*{go?*xfuV$Og)fFgZ&YHlFHQ_g!j${pAY)Ybf zs^t&vA7KYxT0QV2kpDtiWF_cH)&p8!FuejV{zfs-wt}m%!@Cf6WkW5c!G(7`c7txF zW>ZDs=wU(^#be_MYva~1Xj|TFm}mVh@FJl{*mxI{ccq68WudpQ(=v{H&%y5Ftfc^H zz}!Y>n5dw&@<_V_9&31UjLPnNyE~~2*Y`6jjbW}F^rX7eQ$%3%fb3jd!w?{ZFW3zrze^7kG) zc)7(tHgVv#{voePLH73~ly+q%K1`0@EpasqWWCg@sl3<%uiJUdSl(2z*$VIRv8q4b z1#jfSYaStucVH+8tmRJlC3oe}VfUlq1yx7~F+I*X&s-xaJ$iH>-_|i6@1e_!uD%q_ z9ii)=LT78WEG8`gs7U4uL=s)A1P4ME zm`xbx#bYVUoM`6f^Kw2Pk;9C8`)oEPEiX`-_eZ@OqL7*Oso5SaZWT(efBM119n4V2 zC`G{Q57T6=ecix=wsbQ^xPAh}jMJjQl)wv)svch8O}E2qHXdtuEgRctKGx|RyBqJq zP*9`y4todYUi;8J&+~jccJL$}vIZ~N*Fv?JB6fEi1mT5=u(F#awe**^463P}YW%cT zt#fcSz{*aJY_8gf@r`+@`ss{DInTK;v?f`Rli5UfA&RfBMK-YN1sOcOW)U%-NsauC ztYE(n7P-3r zUS<)7ElqO4)1}wm2cDsX-=c+GYrk9Ih1a61tYVAB=UKXoU=UU}elqDfAP1x;A9v*A zUn@@QovFmJtJE1IYDYAr*d++x*`%N-v3}FV6&c~_qL|J|_h#|6WI4g z)o4RW#MIM_TdYkP-X1Ti68JP>MrPIEd0Y@ml)_VqIs4q|l*NF;v_&tp2QNMX$h!~I zMz=ZK-XjyMU76Eu-?S?$iV4WtvGMMZM^bjc-DOiNy|@`&4Y`e+SY9I*a`#UAz(&!Z zQo4dgV;skAx$^dq{NYGKgEe^5?!%QTf!B?n)Vi$X9$T8{b)7qtjWs{Y^y+m2vFdU- zysnL_0ht5EFq;Q@bM&tsCs#B1>TyEP;7S6^yev_=*3E&bE=by={-B8SxDZHkB3qi~+}mc072N81|KAH(&|6cTR*GFU+Xlj`6ZVF>e;G4e zgT=s$zJal@TSx~p18-r&>+;_YuWou?wAW1NsI!yrOMwcmam(!R^lkN>HoOVLODU0h z5_Y^^SE@mS4z@`S1FzDysubH&g}LOQ#VEb9;JwnlaArd;l9*1BTPl~hx>{Ujm;8`% zrgoY+rC$uyzX@>8hGJ7@LrzQ^739^R%C#kz)G^8|98!MM_krHvL@i7~(1P{tapO~7 zPbHv5h{5pEQf##-MJ=}0FtoNW*c4#4iU5y5aKBe~0~KaZb;`aO*wsne4a}YD*KP0u zyTcZC1>Q*;yU3-QC#g%d0aV{A9+TMVVv6@kjNp^?^qy@);cDfh=_p%rM|kU&R~ugL zRd%cr`$7A5OYS~mmZ2~?IhgQ(k>5(1kH{{nuO!wAPiFO!HN5I8zpe6z;V%@nmy63O zb4`0#g>0Ct4re_^fA9o76D&1`T)M$a4f3*u6UWItJC)WkwZk z963gy!;sR-Jm2H$_X7zK-(j@Hknc5!td5UUNxQRntZMxncq_m=mwp2~;YITBwxi)~ zr1s)4u2HxuD;pY9_zSxxBrtXIxp{Fe@8VCAF!;9^n z0NAcx^K+CwT$=_B^OYXlMhEkE)@G&Z6`yY|6)r|xE9|szVt9v(DJDCzON>AUE08R> zmV)zmi}7RBx|fJDy2}{#*wDPVnoVcLjac`q8+-YIk=&FeS>y6P=9T%RY=KZ(G@H7R z1h^Vdk5*}8UoWmaY~sVRnw6w3w3*#)EFYk@Ff0ms0B?6WH_+Pfwpd<1nGI*8OcCsU zBX9>Suk<2mn@{xhr)@Qb#yka|yD(Ys)^(-LObfh}S)1LCoC~ixd`Cr)8Qw;ZRL8v1 z(BCrdS7lWl?eXN;)KqnkHN4AtWtxK^m720CSP+xdRHbDV!FH59>YQO1DsD#tSTT zD|q52G3Mb7eKqz2%>;2F*8L_T}x3Npa zf~z%Bn#{StIq>S#vRK~3E$}AlueHN#ONl*hH!mlff0k=%Z(2FJtW-G;V0vYRwz>3* zqn@L@%9TdrEX}eXNyd@of5=@8zpPt1`{h*UNkM3J(WhP3|Rz`fwaFC>+k6Jie4Y{kbo>HBm4%a)MN=Q>{UwNIDxqV$h+{d)t zpwYt2z-vsEeALunKaKozv$rkSqq2(@=cVf;;H~p=v?vy+{0Ag%EIysbQ=)mC z$HhWx;^}yi&u(;@53V(+mbSumD(2kjAEh|Ln}q-T`4^aajz8$=r6M-;G4K*?Tx)S? z*HRpC(;S7XDj9Z6v97*!i!jkH+enV2hNo)>&qv$gO-VJ}8Q%V&bJQqp8`j`eh8Llb zs!LoX2+sWQPVg%3=sTe1S(-T&Hg+I3m-M)HJZ=uG6Mj7dZ$}-e-s!&;UQ0zJdu5lJ zG>dCzN%=WTwdTZzTj!+j^@7+|C#V{|{2{anFiV8t7>qF#h;O|Z>(vy|8f2iw#(IH8 z_>EcV`ApB~rWB2vsrq@;4OmQ3ktc7(i*$06-r4>8pborGKKtZ^lgfGlm$ZePTt@B& z>1Ae5*_u&`9WIB4qxa+CwQRx1!8#oPv^ZjVXmqO)|pQn@)W{h`I&*K1j#&8~8X&FPw+UC}P@ zH)@H%&4vozB_C&K@-Qii#)T3Yza6<|BynGr`6uL~L4 zDoK1}c}2-y5o$Y8=J$USC;#DJzxa$=53|M`N2w@4Cu+im3<^YjBOxw zAH95oRcltT7z}Ep=4g-Qmc#6{@+}Wg!&5o|{g^*1%Jp%YEt>n~>HP<%@n@fW^3T0c z5_I?nWiJNWD7P2gXx^bpZn!l{ahvSDXAmVMdBR4Tw*jQPXKKUS*sBIFn_#iLTi~@) zar7shaQo7uUk@*JomPrjfa^4Qy9>Ouyaab@uSwq8v0QZRC1K#fUOJNHJ;)Mp$#tr7 z8L%iA>x50~9F?O(PovU=^DT|0X?O7hNNmD#`;N)kwOCAt+4bc@6;hxUz$*7>VFlr) zRQGwQDtMDtcyF`32Hx3rc`n`i~@HUs3(6z$Wp55bzqk2L7R(Lrby{#8XV}@bimE|VL)fH)z z__6RJbPiOz*^M1|&2;Uq@Ydd4R)ZJ2HdLzVNZL&~$6t{u9U5pccT@|;0IO#)YGD@3 z#^o(BCuc(S4>r8B1xJGy0Pi(-L?2^9IyuHL{hTpHuP?dERPeZknRGb~+_Osw}f=S(3-$g5dq+lYjc+pFjEMPrmr<_mrW-;vqCD0+a))2GjAWQA_bL z!=^Vie%>}9x4^rT-CqN5lC{Dc|5|wcUk$H1h-uzbTV3c5hS!ESBFig%_V{F`$I|I> zsl&hM(51!P3DCJUq_NH@1-m@2RLoepvSyfij*A3Z1(#@k^L%o>;Psq2ls3JjTA*nm zt)JK+$K&fMFP6*EwY=c!iZNFP4K=)2CC96{QleXxqV%3Vxc|v#_}>>`d2z6t%$ImE z{&?8^iArJNp~@@cCz-#(6%s6BQ@M2_5!orUQ&VmZrOk|C=NF&KYUZz6KY{>_D z)fGT?RISuIqV(Q5_Bs6x*xS!P`NL=b{27V|<=E}@e9*ui{hYx46T4{+I~xvNWzen{ zT8B%W6=4(C0-xgM;0O^_Pl-dq`dtT8>e!AtLpHoEt;z-lO!HAw({s4)$SglI@nP97 zl2o!fg`t7hq(jQ<^wITNDqC!_b}0zFS!M-FV*5zavyygB+Nj@pVQCK=>8eZ?-#%S{U;|< zAV@h4RNhAYrAhRch#@UpJS|ob|7qiL~ywo&D91en~YW$>%*|~mVq0q*Kf>s>m!dl_Vf%k@U(+OVgl2hc_ z^qS$Fj#b>kqO6nHGF0sU0B3ugm!eByc%A zy?_7rpMBE*BDyZx=*-VX0*7rbQ? zUU;qvuT)$9I(Un1@Mc@#?P%%}hdLYHrgI^DR>3dOSh8DK-mkZ z4KSBiU!8R><;e`jaoY5&_@^yZf(S{QeVEzMuWC|Abv3V=%d~ z{6}%)lNf6$4lT=Di&E^=42^n=JIDsz0`hLM0iF?LZOeR z83*Cbi^LxA3I=8nwGaDxpVU-?&KQm~aP1Q>S2Ym4apORVwXAS0YEe5JKkn>xbg2p_ z=5&vxbHHe1F*-^quS~#AQzt5bK|1@hFz3;2#wlG@`w=dGi19RfTUG)QW;zrx8rvje zD~!S7cskaJz_3HiUgPl?CW4qWVWkcaq>~x^oISYz+3!F3*Ux?r_ViLc%owz2Zwr!M%iQcXtaylRm`ffvnDb+x|7GIaRzjnDifFTF$XrpPcSLeApHd!oeP~baZni zp|koJ3~w-&fYM zE3hsTc3^@_qHemogVo*e*4v+M z8@I!o_h($P2N&UvY21X~G0tFE>v9FoX`uG!X*%27)a%@(z(Lgt8_KYV=W;f_rUg^R z-1uBGQIqTsO5*%qv9@KWddScG?Z ziR63#j%;TdzToNhMgYcK8Ffk%6wDs@^9a#?|8_OReh)N+>}N3P9kiuP&lMco8jCc*ZWTA_sU2U9=|k5_yzR;ZGsetMFeK;ME$F z)!Fe7;qrfgXozY*mJqY}&l0f<(vkb{S`{cS7PBe3Fw}fO_91wu6t}};WKl1Ovsf)b zj&mHO{ZqL>;Q9gxFNGrx>H9^CInh^vegIy&h~PU7$j_@p>%SYjb~CXW$j1uaR(|V! zNn7A;JhN@wrPuCV4)Re)Y5j^BF$}kRaYv?8z;FLZE%X6D4{6O`?#uw(E*7Ty2+iPB z076%-DHqvr02f=mxaC;N-KTjrTyTwqPD&qN=1I0F9>b=Hmfu1ok{}u9Bno&~y}2Vl zUKG~sZJMiZF9&bDYp%f<%UXnkBvdWvDYPDV$&_0g3H$J!z!^x0_8X_UG_MUkjz{C$ z0>iH@Z%kjXh>X2-4EOwn^eh?|{{FrsCsneti#=7a9#Q8!iI=rS8SD*5M{zdD$SIHw z1_?LMf!|y^u-hhE7~Fx1j~c(CGvv0zY(G%z*U;HxFBybGWq4r%U^2|Q-LdGla@+`_sIv*3f>aR{|Iv85yM=?wl#$vdo>L>`49CZbIJIG}Nh#N@`fOAdp z{$WnFw4_qx1wjDts*=$h;GI)BpDZr!v}drIHGnn6WeTKP%5)2H%THv$1ZynisTAL# zo}WY2Noc6@q*3ii>CX|($#UJzmYk8QuaYyA+pF1D-)@>qjrTwOw9!fTsJ64GfcrRj z@4#;D8e&nrm&8ZIzR)}DAD2osjMC%Hin^3qy>k@32(N0=Ff*mXSkwzYgQ<@7vbzD> zFf0}t-eM{VIq-iLQ*)8Wc|;Dh5b0rf!TgTJ^zsCQ@T3)lYP(M$9qNC+=5 z`&be!QPpO)vm0$@kX+LxUdVEGO?Ye&@ocYBJRZzx6Kcw?S?pYN#V_ef`CSTdV4V23X!CFp--6 zKxdDP(uY<92i^EdmO-;budZ|FTZ#%_7V6rp-viG5aTy9-v?kHDY)o#fKvqG(mX1{0 zrUNkgy#~njlxj8$u6kq3i!a01f`Gc@zdGT6Y>_$tT$t>J3sy101E>@~=Eyn50S^$y zM^xHgm>&3e2F+fHdFWEZjdx)adQYbLHTFy%(?X9d-{cCaH5oIzAP)^Wd4zE_-5|CB zXBgLG27PfoHE%1kZ^Zd1?<6AyGByT`>vZ)upm_ZFF+bcRufveyk9hJrLvxzD8`WN9 zS(QpV@WKz&zkxcFnQI-TGl&tnvU;x$6N>ucghN$hQzm}IVLz%*&8Oa{T2Ohhs@X>3 z)U|@1^rZPu%>Tm|Pl7uqv|LqqWsExL!$G2-ryG#7W6P%FVoy;>b+R#=Z`9V2hL^6> zMJBQ#k9nOj?U|!k=Ym?Y;?d$mYdIjZYT!Qv?@`BrH?uNYbDeJ(>)bIo0=iM1()QS? zrNK*E<7ITV@iLB;vDcRZ@8a^(0><1Wz#IaLscz0^E)2$M@k6@UWjM>g6XzZvvEj`) zUEO?8<3b(B>NlM;yq&Ng3?)v}>30U=@1cx`HOE?OEgD92kKncGwa>Z^S;HCXtFtt~ z&wYwt*~KL;V0AgY#8AK(|H%P^!tU09lv)Nl16LXhJ$vY2a$dl5zVn>ttB<2ke{#^b z;YBx#uS|czZxCp-jI#(7f4WdXiv->iM=1_-62`HDH?rX6w#seriU?EIKr(lSCK1H`g5uD_^X$NzV4hBv1(=>g4^H798l1wT>{K#(e9xWi`eKv| zrNf*BXYjX~m(k?_Y`)edftDcNH0O6f-#n2GnY#cfrpAJenC5&+KLXx|Lz6{5bm29R z*p$GhGRSicS)N%n5Gqiz+w!{b#$h|WsFfJrj9iG_Rcv%wo z?lOe6tQ*>cj_AHt{)EwRBN+tZmw?N zt9yM(YpG3RTI_PliRbt|!iiR%jaB=yF&Y$@CFO~h5niS%o+6}L`NH5+PHJ))LrA4p#*W)|rbUN_nV-m+I*(pg( zxs66ScGAriF$GV80p7(Jrec?4gNg8&KYjl690ukFj-NhvXK{RqZ&3~68l|@xgmPvD z?iP0ObKp5%jeINVjK^Luh3O4SRVrrsye*8rX1nQt0 zuk}N79xxo2oC$+nPRAUgn8LScA+9LwlY>+I0>cD-@`%B#2<~(05Ugw zPFKZ*F$z0%^yzaLPr0Wj&DN0O=~RLI{5j9;YFu06`BN2PVs9t& zZ9hPFAg)ZU%E|eNxwO`C;0+@V;Gn{yFaD%dy>sBk>MlafayDzdxC%_;EGGkoCT{dP ztyC;b&W6QQulA#p9PmySQP)TMXW$tnW+gxj8ls3LX#*eR%y1l{JIqIVV?s5+WCdZF zqjNzRYnBOh3vQZ?`JVm3;%6F=$w$4dSN#Y*Qn3ZAaM>EhxvW8w7-DcqF}jH6gn0Rp z@V0k&R7RLCgxH)ZqI6kJYU~aNUQGSUbx(#icxw%D4ZKv*8Kwg{xsJ^$UDzs-&O=Z`7yjH6`Ej z$&~9GT>?vXHc$t~Ia=Pe9kY83TO&lyQ76H)?3h%R?Ssh*5uf zVNc;m-d^B0M21*v_fXe+S&Y&mrkU>1gl4={(3)y^sqB*0KO3c^jqcwdYho*7i}2pK zwZgn2V4n1^O@{_44hDFs>+WMrktvv`JmK~!0I!emGK6GZBMVn!OX0b|&$MMl*#O@F zof925;YCzQRihXo0g}*`n0mnBheERRMuqNHt*|Z@hA&ZkE%Jqu94H41W|9$i6+w~1OpsuM+%(ua?^SuazY{(?)Z|Mqy*pNvF9ZYgs zqJZpyhG)P=$L3G&fY2ib13hIXGie7}be|0kv)qsKGspQncq_MbOTuybc zl01Y3&I-tt0)LwW-86saI9ue~{wtoOg=&^B2?kP*o#<=>eCS!mkpw_yiD0HVRQ_E0 z?9j}T;Fe1(zM&A|RXLh6lLL4O=qO#sKeVLdqp;U6uhOz&{e;j>CM_qe3kkZzr` zqMm!zW-jXq8hD*rVO8lqn+m4rWWSo|*u1d?)*W2V)6VfUPAl?X#mKbea)h-=^mC6lS)&9sA}joqyEAJcYOvBs%85)dYPanu>0Vv7b;TryPf zs`agE-AB^pdQNjTY>u$3YVckSCmbV|WW6E$SB#~!+3EjwEinsph9?Poz-e+HDEk~* zEDO(;i%RH4Q4yMCqZ8oTXS5k!WtrnZ`n7m86~m~AXcU4sjxBhxXdsVbJ!2!Wi`_bu zQ__Oh%h~bITb-6Sc3AkM;8g=XUubzXyP^?V;bnH=od+%K-U2Uqc|XPC?Hqj#@nvqo zYad3Xr{RbaZ3H#EX3}*Vyh`%a|1o!f*cyACP7G6}q@Qp*i@`u#y*OnfI|4?QTQpQt z>^`zwuOvGMlN`z+rj{1mgx4x`hr{5B?hhx0Ue9iZZA}Yq{J7vHtGIE)I0oAvF;%f& zU~G+@QW#z1${X00g_#{B@I*z}!C@amz;=2wJl`WFB9CyvR;@F{T>EjxagQieGTWDn z5xYES4hJt~pw$aWK`U7jv8N6fbFZ(Qt&oizc->0&t?-g;u%~94M5AT(1BN$r;AMw* z6J8Z+Gw|}MriDSU^@~Kz61!Z@hBpUzD?pGZ_o*;61k6k zi{T8?NoUhLDwn)^82_Z@YBj2+9ASfJiB!rj;mUS6>9Ta-0HrTLC z(|14=Haj4A(xv?rL4a=w{%pyfq>capAOJ~3K~&aJMj7G7KP^)><`s^TTeHm2Sl%YQ z$Ljv^@qj!DcHyKZ*m@*=OdIef7QFJ1AMiU5^UQ`ZVvod8P)TOr_7f3 zl4l$@RvA1O5zhu(q>Cnas# z_$he;xpFix%j%&QXLJhj-w0>x)T8E{i6r#F#-LsU_^Dst5;WjdII{)`IQux{5+!)2 zJL)Jy>pq_2{x)`Zz-w30s(t$%-I)Pp!yVw&t`-BYENqG0E5;k=SxBG&Ap z0-j1uj$Mj5Et0;}P{M;t)(%sSr%B^03OlfKCyp>cp(A2m!qYA_^>T|ZV^!(KghjE6 zMljm|ccy4g@cel*$`hSP{%>9k{jl@9{}}2d%0K^3YieE9<$0)Nzu6%~cIY?ZWfy@ZRYOIff!AtNq~MJSUidV!X`vIhbRi0TI0P?` z1SPKnFFLpkyDfCKY1)6x@FsgOiIyy_K5AE4uH;DNm1Dm#u&kKbaC2p#)r}ekgPSYy z?HZpi4v@ju3n{}i;}TOH7KN%qlZqg8cBn}_r87k8YRmyNx+nt4y4W9;gO>$%4=hgw zhC{Z?dQU|!Cs##O>_#eiOp#t(bVv#mkFv?jN8?PYz0ThKILLB8j$-SRrVGGU0a97Z zx6WB2xYsQt+9~>NDFHq{whP(mxQM6I6NK(|w4s)^B7N!tN&P&cD6*=qo5SSUg$yqm zY_MX5c-lnE$3x_cUS3B%?c`~GGu_WYJm-&Pg&y~v8mqkO>+QWKVN%MuF zJ6bE);fi-_aXRPNV};@on`7KNj5rlUD+DumQzY=p76mVIgZ|xw(i*`4!^q9cB6O57 zycmIq!lxp6(UV_?w}GxR%p2OhpX_bkpd1_(4akt<|DI%qu!| z2a_AC;|M<|nL5L3>CCTYW?U1NPjiVqO7Ia)o3A_r{S5d`JqeDZ4=M z2C;gGGSrV8!;Hye`b)sZ*&9-CPt&}<;~#zh?CamYdim<@I~Z?YKKtTT6podI(`^k0 zoue}?AD9O9z{DjKtAwQ4g8?r;Wj#C zc|X0WNdg0^f>(p96M8}PGQ3#YjxuUeaDHKuEhwbLQ^$9`ek7pjzbP5f%PNG6B?4dI^vl?syyk9^1 zn#LDjeEs#GzWDn4uft#nJCN6z2*tD1$DnY`eWWLaBqu@-gsw!dCtJo#55O zIH%xpy?*`f)w5Tx-@SeP;pK-n&)&U!`Qp!C{qA>v z{^Otj48OkmqgI#_qwr3&x0YfP`X9@sJX}~Tb(~QkmRU}Vf>#eX192xf`%l09?dexv z;`sdY&%gNMOOPK4my0H<>?VY(b0(Tn!He=TF!1VLkL?R*%XythojZ0oyc@hYCdC*p zLDQLJ>BXddl@meiWHW2tr#Pj8H}D&sYTzY4nB4;BYcLs8dGh4PKY#Oo{{7MGS0CQK0!%-=e*5z6yO)1| z`RwiM*Kgq0vp3(qcn!Bdym|KK(X-c&e)#3Zn;%{R)^A?_+v`U!zI*cIKmGZ;@1A_~ z=O^EN_g7k`9T!itXH21HS+aR_T^c;6OtHY``$IOml$sl11z3=O^XRv#rXm{r_J2u2 zKju0&4sjFheUUFJ$w4lyev(V`;#4xoy0il3R(N$dO)UxNN@32|$xVb(SJn``Q`Hn9 z&$*6&$kSj#OmQGz%$RbY8vgCNtxl*B=9K!qP*l^IsB$w+GQ1#pFT|+}lePSu_$}^c zc&+RImL1M8*^E-oFt1D#Y_i=sE`o_UaFDzoW24tjKlUfO2RFAcLx1{kaCCDuRaVt9 zujEvq3JS8dVmRn^6nB8nz4zreZ(jfX&9{Gl_UPTCN6+59dI>*oe}44#)!Pr>Kl||R z*@y36zkLf^-~Ro>%Wr>q^XA{4eEa(4n_s^E>BY-8Ujnti{P@ieFMj;?n-@R)mp4zo zeeu(qU%vhR<(ro;zkzAhi_f3Dc=F?u&wu=u7*-@pR1KALAc<;3H93V;ek4c7gvXcz zcVUo!_v(K?QKcsRzkM-4%PSnHGNCLvqG_Zqvttz!c%Yvhct^Z^tCjB{6mN%j&=}gE zZy|efat356|Ah;}741&3*Uv%Xi-B&++`~BOu z@4kQXZ~ylC_4oe<2KL8iK>81V`sJ55Z=Zbo%TGVO`4h1G(~sZ&1RCN`FJ8ZV{p8D^ ze){S4H{ZT|{nJl>dh*ljpI&_V!|NYke*5CZm(PCs@(t|#&F5|kj~W7RAa)pj4R9sc z4uz{YZ#VeQ&%XHL-P^ZsU%vd?SK;8**A~2bW|j?t7I znvayb}Va&xD+oJ!<)sH zYzQ^H7ubjBT;MHe#zrvdRtLMq>}{$cI)7RpO!1jJaMRwq-~I8AU;XZ@KmO4pB?Qxv z4!OQ*J{1ng5_kHl!pGKQx(K#E`0B6!2mJTdU%z|x-#&c!`R&`E-@W_!2c-4CJ^L9{ z?a$BNKKu6Fn^*5Z=01D(;ngd!xF6oV`~A0IiGTR^H6Zuw`&Y024yG06_;0>>@yjp2 zJp0q@H!t6O`{ge`{_yfo-~RH;-+%b>#ZNDPdh+FuuV4Q14G{d~8({?m(}eth}l z%O768{x4sC`O}XtUVi!Q>nE>Ye)+>MuYY;= zLr@J_1700+N;gOI=j#c%Hx2Hm4-|dk_9XPWuquHqU<;!%^>&<4p{-`&bbbDQQGIbpi z3A0|WM_hO5>uhvI59#&07lYYo$ zhtuKJ{*(LrPcr+F1bL17#zL>J!Gll6ld*KAXKg-xx+yV-spGoqIgYp^1&~XnQ(QX~ ziyC-SUZYzI@TL~veWaS#o8Irs2H(gsys-s%y(W0$0eK<)RO%LZ1MhvxAmM+u;@V#+ zM!4*+0m7OZ5cs@~K>%DBmGcHs1gmnO8cbyV6g_{3=Vfk^IPl>0t8Kmp?}EAY0zdOd z?(lYa#r{I_9(Ypt&S-Gu!dlLp9-rQ)Un&_7#jt8Te<&uXs>AO7y#pvr~G;!V0WHxZd zP!}$psHY@>>4ZJB1~U=|mF%xXs%P>)BH=u}Z>`6wNY0v!EUy4bM$J29octuGL2iZjRS;e&PWK5l+LvU*$((FhWLHOo zaPo@Ymw0P&B1fQ9=LWn& zq9;fA*TO%jSJiM-M7r3EoOMnd+iEzr?QFJfVz|-RvfKF9pevBR(3i*4zZuVC^1Go=|S$Rg5J_hYPKj>F`NSbBTB~?Bd*)IOxqz_2h(8tc+ zxw4*OF$L^#^ruY?o_b-WSPbeSuJD==dW>^jv9DO(?HfnHysCWg;Ps0m|1EgE_>=)C zWlG0xz&mfDjVNfny)0b5ZKb(RP2m;1G*g+AfC9n0i*bMFMEC(WTB_l#=KdGKyYnr> z>%+^(o!O*EfDylB>R{r??hWvr-t%ISLtk9w5Gcq&C=5wA6FQ?mj^nfmU}w{jFh5~y zDZE57Uu=-|9gW7lS)bUoW&|vGZaw0aK&IR1gEJz+Es`5k2gnoKHtCIN-^XsE3YrW+S=787=l#R* z?%e`!5KpOI`FG&;2j*}fto~Ja>#h3R0(385n}=6kD<`dG1aF;G!#Tmr{ywl}cz69w zlR*pUwO)^Z%f?n%Y6 zY@47Zh@FOQGrZPjL!_gfwdl5dqhT@28xK(4?AljX zwcA}6!o$75{lkm?sBd?t(A|ykls-yVS<<$y+rH?!m3E(`?zI0h)2!9bejd1!K0Y{o zQb1qmm+h0Ra`rRi;wDN@B9SMVOlBoebe2j6d@~Q;n{Ajuc<~*dh`0n9)4_Ftx4>J_ zsW`3Y;!t_L!zQ42JG?k)lx5qyf9L~oaCOw><=e};stGh(-h>y$4`hA`-qSL}%O`IV zzX8kpZOQuwRKp)PyoYxHY0q(c3@(m7a$Rt_d%gX#|Bor0>Lh)x6Xm0-S!}l8- z*?d-xMuW{SA#Bspu;^+wZ(DpVZ;L^u$28olE7x*K2KUC!5b_1sj%L%wVAdxm(jn74 z>pS$jjVAP|>LWx!(wJ0qkHDXh&LO!>3V?*bbb8f*f7hc=1}WTu+qSQ-ugR8n2iL=V zyG>`43-`L+y}qWOS#bv!?JJjFha|Z>CI@olICQ^TDfQi_^!YlK1BfFX*-Cg!AM{W&eJq-TwLKCr{QE_qP(ft;GeHUxoLp!G>IJ4qhGu z-SVRWLDf5_f4#^sf@`_D-~74=ZxRjfnA}G6a#{_a#$eA?ELK){4_jyrV#({lt5uNS zrt%NM`}>M_5#BDHa(j9(w|z00l>$17#i@{_nv~$6nCz1bIF;j!1TTSVZCcq4JvKMD zHa9jb#gtIC8w@d^##gY6E0s-tjN529Fo2CVY4FL^8IYaD`F)14o5gzAB}qVYf%3yxZ%N8HSJ8gWM+7 zYY%3y_XyXGCi(UinQ&yTC-iBdSM2wvq^#&?(a)yy(-r+y>7Q&kww>XxkkWJwmf!6S zuabeifC}&8_SR>Q=7(#h(f0a#@S3r_xg6Jy#jppVEM513TEiP?@@+a(Bvr51=O5$4 z8^<X`D zr3c$jD_`J1jx?=!$zdtFnVac#C$>nk0WjQ{`(v48s5ZXf*ABI7%(jFtiVdv=| z@%nQSt8$61?Z9taJrlle5!vgJ-L6L-uzK@6OJGnf*4rY;E05hLx2oY-kVfC>?FU z8{ONRgZCZaMKKvi%if)|P6%FS6YiNqUYo`E(yMH$#?5YRY;DpXEU@0#c-m0&Yy3Hz zZ!iLx?%6c(D$QFPHgX(vBAgBWw{N9ZAza4@*?}QUPV5^`3A`pLenDum9S(%)oV9C6S?D|jkp|L zyS~Q1T7G!lySOIpJsJ&=Z5iAK@1x-AVP5R~EqM1#j90w3+u)7ffY(2~uz!H(Z^N4t zf33s2FL*fPWiE9yJt>n1>nOh=c)2L&pmh*{*XWv?8;Ea&cE53WRSXNj-l`f7?*LhL zv|Rc_>eq5qKHzBAP#r1%%154mM*q%Lh+esd-ivCjLc*c#w25AlwE4U;3BlVC#js(i z9?srlh;b>Or~l#GNoax|FVCP$*^P!J8o80pdNh+5ZHy5QxyTPjaOZlx*VT1)PW+jfn)_lWu8@9`bH6r*3(dmfn>7+k| zOoibkCv-OH6P`r%xHqD|*9dyMeoOnX(ELcLro-z+OQ&>| zuxt{Rbg?d25O^2xOvxD2Sd0^Ff!WfW!2UYCsJFw~@syv94F<~6)lSE(ZG?2lU1F|u zt8?isa&Bp11NRRk13m7$2fOp|8g>H=|E^Z>4>J}ASzuW8+thCm-rrY#gMy?Ih@4q~CUObj$N6n}qQHLB84+$%M9TPW$SuiG{VdZGRm;IqCLFF(D8$(Ed1v-B%<7Ile)-T6NGO`DbF0#PV{H)))r}pet%#O zAUX&$QW^bz*G4*II?d8)pN`L#OU8Tn*+mY6{`UDRq&<3cCLwL``Exrz9bbR`Jj@PI zE<~;@3FyIf*S1mEIii=QSL!)7B=>a-B_DtNMiVjjUrM}KOGtcpd4v1H4tIV_}bgFoLfvgWw%afHzP$aVW6{ z6Nmm5yuYs+g76B!yWfz!q!=>FPS0|bfSW}ts%b0ovM2QmqNx-V`Be1tlntl zcv{ZhUYWj@{sszhkU~&;Ek)VhG_IK;R03%LvaRrX$EFov*T3X>dD$NV_cXErrbn!j zLFm95vB0%lPJB2vSGG;Nh@=s!w$-J-x=Wvc={5b`AzXVG^!IO!Ne7ScDH!#8bhG_= zg!5B&__i1Y8`A-KeTdQ6ofEU!QjZ7m_=7mU1X|xEel&iz(o|?iuCnfS2j5c64wInb>mK>`zL~!;1{DSIX-(82u@D za|`V1achD?9X4Au?g;VTt^^#i+11r2KhhCxsSSuqinz#gosN#o$m-XZJdmMPWq4Vz z6?sk>sypuAe-hcae;*KUYyi?cQ~{V}DJ+6mEOFLjkZh=(-`w2Fk`CGQJ_30wKW}tx zhCkczt+B12j=Vr#X;g6hfWMn<4alaBAQ@()F)nNy9#+5C?vfe48d{_s`Ur57ZtV{S zQx|obL;CJchtMRXKV~GNT?Zu^gs?-Og+59z$a^GNL6VqU#V$FFpIb+|AL*9GmTFU_ zzx`&%I|pxy9wM6+amL4(u40-1EEc{FugpuY7UoolJUoTxVn#C7Mbr0c ztDd?fukH0^hkIjrSj=UW7a^b+otr2E@Ji>fkuN*)6~y|CEd@{Oa&m#X0rntf#IrJ2dV&nDY{g8$Db+$3g z*-eBx4C)(MA{h5n$k`+OE-Dpwc+tHYkyrP5SaHcY>s}1o1A>sGfdd8H?;;~dyIF^*Lm9A4r11O*uzKhoi52GAeIoDTk?<;x&5e7T zNMdZ>+uGX7O7K_BCIqm?$n1cbwQyw%SAB}VvA8WN2Ov1MF(5shF#gn z(&La`_&g(haXtJz{(RLK&4y?kMi~Uz;HljhAP_fmk$efwl4UnUlxQT%9Zp{_Tm@y#jWh5batef=aJ)% zQZu8E9?|N&EfhFb)`2oez3s@Zq*DnuaG^WkEtKoQ4j><1Z`Q)^tJ*&auK--u4hj!m z>&KPQ%F0S;d1WaSHb*>)wp9EZg~QcUS?4ImUtj8HQpVq$baa^0>rR#Quiw47X9hrs z`%LhLqfvQbwMujjjY{0th}?jvwHqc4hi_SR_BQAIw0jFlkc}}x}Q@c z*{rCEzsYw!$o#|M>d$%&3tDzIM2W+6>LT1bLuh!)F$@HS9i;oF6NfV<=zAv6Avk)@ zWK=yWF2Flqs=#)xC9^#M03ZNKL_t(G2=8rcvkl2}@aj+=zyHh}xIOtbcvHH4AQodi z>QySFyy|uo(qjd*EC}h4PAEJ_)JztjykzRRQw=+R8s1dUAb*wJ zorUinyu|Iw(sF2dd3kBYgLf$vi}OT)WONPi@&=q#l&R%u{W46%tR%wf2{OI16Tpj9 zvIA1|&VzVU0B^vT=JR62xM&>zRXJe%JHTr~3PI-#W>efe$wZSCzGrym&<5Gqyl;+S zxDC8~*aq9Ye5I!d#Xng6LEml=$UPFhBT1A@ zX0z&%_xaT`&~5Mrm$wFpy#a4LDoZ@bOa$NX*aEx@xiFb=!CzuJXJiiE;46tX#h}jD z+RlkVd#nRH*uitooE~8H1FRPuGlJJd$qve5cz3-7^}oyRe8bT1!5h5Cz`L@v^uI6R z_-@U>8)9(zSQl{dyQiy{?=V2*wZ9wL4AN^=3+3m2Zqpk?WGu089goKMbRez3%K(Q$ z?7!S!CDHQz>MBxkTN|4d*2eifhkeP-V-9Twd$XbrTi%4ppr5lGE#8uk!M%+iHtrdH zoVC1+b6yW2m5%SX?0mM8&)RYjym{8hPw5L1zum#OVRN8jI70;|$>6J=>!M<)OJB(< zPbTmQCzH`+G_23TJAWS1f0zY$jfJLvl)Z(66oQ7YT4w(uf0u#;{wL1;-n0gN4|y$O z>;}AgRO#K5t$6f97+#K(ai=bN;L9mpae-xCsa>NQ2;MT!Y^Z)q@_zG3{ULZ44X+*t z2WY?~+3?+8zWZ+JyQSrDC=|kA(d=1ygmmcFCifWNMyS0xolK*kvn9$P!d) zEBDD;n|i*VH&mfJO9-)}1?=ZYKmIM0mre&#Rak1e?;GKU`k+){cR4miu z{yeaVTD#%P1+Qw-pCo_Va1nG=$IJrpxoAKV`;~LAbx{*Z-j4p5j?6tTAIRW7ffsE$ z+5vPxS9hU7cmFiJL3C5Q|Ayg}<4#N_$1R1!E5z?I4pI`KRCGNY#uThrIH@Ihsd74b z`Pz&nZRzMoNqVC=TDj$>fOz`d$;tD8(czX((!S|E1Fs1eg`;6+H-uwZj*vORp~&XO zre-k6B;WG_3@R-&Mj?J44DyC=W(iT7u#+`tb{F9du4}#VaDlfh8{bgZz!Ms7X=|5* zoD{uE-%@Me0D0jkmq|5r5VtC2H+r`}K!s)IIE`{M`59-geI|r{i zWw#jKufq#+$yr0oK(DGb*9q>MvO=|E=TSq#D0t+WlG-5Ky z@$_@)E;etZN`+|JXx?HvDoz}I1*K$wuX0Z`cbI{*Ef#&@NyyB+@ z;GKKm-r~~;##YN4<&pF1B9V^BNjqf_UNQi(w5IMYuMxZha`=Wo#(@=xuUY7N@ahYl z^Hxe(FpB5|&m=sp-hfxn_y0}9`?cdMs=>e;4lS>Ix3;pfybN|ibY@|i1gf=o93A5H z+oyB!x4Q1yEUZuSKch5He|jsyczzS2#pj;WUG#J=fm+mg_Hm6rNQfgYH-hD`+GEf9 z62oeWqx9g_@4x@vcy|a=JY~PE65!RA;yA?BDvD!v%QK;*8SwZdDLZ}q?wk~w!xZ1P&JP%m_Rc-a;2$P+t&w{Ur&@M0zODOq0b zriW^fvaqQZXa!2 zg0l(OEhi$;Xe6G9@@mkSZqsE)p>mt82C?a)Nz0+s+#q;E@QiTY>5&4=tbH z;-~$X-TM9qRTcN{{fGjiP3{H}T7^?l)|&j-*l6p^nZw=KwC#4gel!p3e5`i?-mfoj zHSnUMU~X3>9|3sH?RbKz*U@3*V0uwmsHP~(gQP!bt)9!(3uQ?T!}0FuZbx3CdV2o4 zTEN0d)1sa`JuEZ4WNsB+?-%=b17&mW+KypY>xBP+;oZH%x@dVNM5Z?sS_v^K3{gB8 zl`(TktSjLBcr=FfWtxurl7-*vg`I_+GYc!er{&Sq+o4 z$Yxb5Wb=Rfo*-rK%>>S1gjrZ@{Gjz2sz>Pa(Dnp`Z7)a)Q0zF4U9bB9&g}-|GtYu- z_{Qd89RXDqt;>g$F!%(Q{bU)dHig$GN!ozbR^NbE4|?-2iJ?xJCwUUUh4S0F)ab|v z|LW^hj^5&PZ`$e3K|$cfFEJ1AA7pmXVPq4W3dV4O?;nDfYmLdv4=pXRtH#`b9-zvL zbJ!7R)nlD%J&FY){s!1;>b2R-$|2z3n-!HDo6M+AYo=3dYg)#yN8_-_@o3a9a8<)A znq@iSqYQE*iNtDxFJv;Ah)Q$y?g(#0NusNM#8~95rOMdTs|4@&2449p=?YKrGV+Tl z5Y3|m^aFo3+ERW`N84d|j{@|0F4$M#HTT{mS892q(LG(7E%4@U!fP;%n_{!MTYY1l z3hbpQEW-ObmFkqMbx{k2!fSySV;TT&ImH3rPT7=72usnKRSfsa>9pqIRTvzA*m3Fgd+(u!?473c)EBw z6b{5FLd4ql-`_{fSY?IvR2XIVEDLZM-n^tO=nm^|l>oe^Qiy{bWXyxbZW})6pcj4r zZ9I$M^%txKTiDZqTW`YaA76vFTn@1M6?kO@Th6QHou8!d!RxhJaA`YEyKgUBr2ner zD(`3t!pl{Vg`~jyx@-#BljW-S)60Lt@GgA672bK(yYqE;!x*r*w6a9nLEU~K7_T0V z#Yy%?(b$}fW6n?68+MK&x@!9Jmkx_14xBwV?*`tcC`mh!Z&9hF+H2O1nzcQBf59w- zt`4KoFq;SlF}(}$M)V+x6M6wMf($YYahN4U*bB3Pe)^xRFg@%)*dX8gANTHUnG{{JYtn*H{K3!3ap8}#uu?!INf6VUC!YzU8uH}1p@~F7Woe#ScpJQ&nfW@r0<8~k z6+6XKh1)j12m3Ci%6EO;>&aAt{kq<{f-~;;NcFzf>F0i-+i}4!dF6MB6`DQd845e zGLZQ67t%blg5$f->m27Lg;&44lz{JZc&TbW_`Q-^>u402qxfF)`Ce_WSgHkOoveL} z#)HLFVL!$jg4zvBI4zVAy9@Ih2}L}TBdcVGA%^jXjByx1!?|OK4angT>*BkSRVaxc zNYZ}4vGpT|j`Nl#EeKjo#o3l0aPvcM99-c3q)iJ&Z6@%>5dWZPr#Kh$l^s=SZ4@pB z-MTq(F$XU+Z(V9|8PPd*gH9ZQl!oEOvaTEO&OJ7_c&x97U(?AGwu!9ibF#Ckw{?XV z11F&xlIMr#0&mCT^Olz7!o&ZL;Wh9cN|oBY<^Ae`^e`Xa;Q+!RWBI~Z))7ucv0M(Y-DHcuZAU={3&04KiEEO~QmeHRYd^HgZCcnld zL4J%EpCLU9kozVU7Z5Ya0GZ3+05yPRpufZUr_(L=CUTS=Cx|f_FY&T)cTj#qnPO z?;Nat%r_u_%Vms2T#v0wC@kgO6J&P*US$TVq0_p2E%(!pE9BnRB~4sEBtw2!FQe(F ze%?7{>EK(}8}R15F%N$RUZv60jQufo5p6)^dTE8U!`d1E=C^u{Ef(Mnr%5)%;V?uC zRg7pc4jNoE!24FpBe?*n?3d{6X}UrTN_;Q)Hq{8+Vu{~SV{n;KP%UYScsPI^f1DDC zjYZ^(fSX`5yc)>_?68p(bP4pX-X%$q$QWzB>`Qtv==V3i|Ka<)cacKgP-%>+2}IKZ z2?=fo4F=A9_pfb4uWh^8&A$cVougR%Q4ij}IL^zbm3JSTWLRZQ>kr60;&g!l0Rybm8f^rw~t z*fm$?;ayM-e-z%BorNYI0Eq#NehS=|(?yS>E<|*iTcS7}x~H#NbB6 z+=f&R3(9vB<}Ri@#1S^O#2NPP!d~)44*oGw^6l<#+`oT+i%l}Bg-H=}%EBlHleX{- zu-t;-R(6|PPTRI)qm#nKwC4b3U8FagK^Or&kxbtLZ#2FLZ|(-X;>{t>yN=lkRS(|z zVUXtVUMOV@FPs;$L}gtapL*S@dpT${s1*d>dR?W%sUOV!eaq`{8oT8PvKz=y{2T0Q zsBDcKHg?+RzweTg6GsGsP!`!4O~oP%yo^yr=_9{CaIJT`z1XRX^?+)(=UK0x>*at7#ZaMaZeib^VvCgrE!DvY>s1XZc3W1 zO>(1U${P8ji}HlM;w=T{e*f^|5jQQFY@!3x#R?0-FWAXK0NxZYU@goMdJ|qR<54!V zBde`C3BF6r$-#1toEACoFw<7+u$rr$^Jvp*9q^(N$1}cxh4VpnZ^C;kyTOOXw0_}0 zB=5K34LENGUS^k%r6sd>V{HW=GqF-Uq&Hqo!$0VB#1X~n0<1#K<*Gcd_m^La#iO?; zi|qmEsvy%_OP73eBXWU@sra2F%c~`laf$ItUX8y-L=LkJ4U_r2OVX9T++Bq!4zGq( z_sXBU39-v+phYVCkqU%}6LI>&5YrDMIqn%@HpI!7eaEt%y1mh;zbQEjlniG#$-N_| zOg%UMXxj`g__ZuFlo@_Iu)^^ZeSLxp!~iEQt8>ll5=;}1EdjY0AuDIBdP5tWft4WHiVx2~JX zr#K2n@?IHwrPkp{bVHicX>vY|bFWuX!VC!T%D6fDpFb?iQ7=s#4vFRE81Jfa#v&?X ziR6fHhga#rVu)2YkZe%rPL)N5ejKJeMiPl=A`uVs?^%*{bD9r%i7BnHv5xh0>P+_2 zada(lImBY5x53OJYwW}8o&FWxxmt7j)@o;NQ^p?3bMO}9!KK;hIe1e!?=tvUFW-c> zuy~}B2HvEu9Ofc2hBuX~l5CJXG4d0Dw~*|d>s7APImLgcKAt#i}RzVD9E;wt7c-oEORy9jNn;lRYrYPFlU(Wil|VtoM3np8D3HWcoX40J{=TwMon>#e!-Fz=XVxTVPshuwV3hP z_fpVvUt-1#v27aWL^{knUL-~0+UeuN+jO1TmoHe?@ueM2luC3Kke@*BVjyP7K}He; z@tYgpK{nk+uoY$NRxbHR;WaDZ#qwfyZ>oz@jz=w6wt zAD}lzn6Nf^_~#98EVdIslg#fob?#2!xU(MA4QzZ@mgG9Va90U-^{r{8D4%XHo#3}m zpCA-4>GbGbfZmgLChRWnndU#qo`fY+(JY3G67DS(GbTlWsD|V#Or+P#Pnb;GoR*CU z$qDtuKrU$s4oj^11rG>|8F=YI1aF9tjSIJN@Jn(x6o#{be0WO8i}W3g(E+ehX4WcFp? zIQ=hQP)T{c{{-tm_V>NAMezH8Pj+5k+8n%T9Y>Jl4Jve}2XESw2TARJ@DGgKRd$i> z#a1D*w6Y$$)5;-~ROXfDTmo^3ce{1&!&|Mx@SX?Vw%Ecj$#s7@ryuwIJ$7^RaQ-pF zOUGljyc{QE)4L?rS46|g+7kBitz#|U+B$dO@gmdQJF&RDS&(-UoAs_(DR^RH40~F8 zBLC7y#bS-XMTuFIa}{cxG++jJHN!SvkM4zfIsdSlNT|Z4Z_M**m|Zb0eMo4A;x6RG zDr(OZ-c_YOyqe(XLR_~4MFifWuQ}7v;n3Oa{{7eHvjRCR{iplY2lzdWERJfP8^4rR zw%vyy2FDv4Pxd@`CD9Oo7b2@=ubj&WPZ^Ns0l~d zz9#%YSvZm~)_B!i^EC0YD&ml5hjAe!@FJtks6%T9M=X&GclOfR{GVhUr_*cf$RK!| z+R~5Dns+`hxg5<#64+ZCn@@b%uqP|3f~nw~>tdSljFELQ0R>GMvoABpu~E?Oc5$;k znCUUF?ORarabS4$7mr0zca~gO7&RAjP{|-9tK?0ZHrndtWhaSpbM#*7x)@_HB4?C;B#cAKpUfAB1jbnM=g#Ua`cwp_caB*XSs53?NKM@#eIM zD2V0dh$Jdl-6k}-#z_jb!-dJTI{!avRex z%5>LpWWx=9cEdIp&zyDLC?3Su_<8GCA-Aqx*c`licsHejB%_u7wewthVb$J%b&KlWjs>Jvht+|4~oG@NE}PtONsrlW`_)3fHyAiB1o7@ zSDZm#Y-+FXINgbw;MvGkb3DObI=Kvzd)S{e;-$AUXksij;Hy0h%AQTI1>+1$d^1bx?#(q9J#QAvtexYK2<^eaot$e13>cuR(arbsyf9uOs=#kJ~x@ z1BUnU9pzRw+JM$2(F`%(Z@;o6aR>fbb4DjuNEUKQFFYnIv++mXH&PVf`AB$8r~Mec zR7{nmNnlUN4VR+vQcdDT8L86EK!|$B@_d4!B#GWtbGqg-hj7?r(^fr2fm9nX)+lcT z@n1n#gHxf5pC1lJq2kdDv6U)t@4>TbzjwdD@Xlp(>3k6v$IlOK#nVW0&%+y+RTF#Z zRAnnG5*Koy>zd1$CFOV=lV%vr)Q20p{2}#=l8L8ec&oCCqRwrL2i0=nfZ;uS zoJ<~S&ST9lF8CAde#=4o7Q2s4sLYt&6*j$`Wg~Fu)>ukiI$T8RJeABP*9^R70wN#p zbj6?s8*nq}25$f#-cM=!k&J1xS!*^E1f)XCu`4>so2KHZfmh>WssgxvHxVh7^jytl zVw`FdKE2#p#Kd>;{Vwb-?jos6FO&&oKr#LAeNLh}1m*suYXPzU>UUGA$!NduAihxJ z2)`{b4=-%Ke+}MZp+Zvj%S;fR+K$7d_IS$%3VZrS?>ZuR0X0@~sNkJiyo-@Hdd(UQ z9z;dQXySO*c{G~vcpn^3gkPUwpJ%nj9=xxs3U3M{BHzA!TVQx|ylA+JwLhn7dEvv6 z?mdvUOE_$#dC#%ZijS&kuKD-r#Zvb_3-A0ZX_^9|-jy{bc#Ws5@b45}^F-9zFFN1f z8%u^gt{|5O{d@D~;%a%1_EZXM%S8OFNe9c^lE!KiVtzUJ>H)PZ9+?&~M7a7~0%Cv< zMihSBU8!}90AVfr6+NuaOqePMj%|R%;b`QizVp*hF2UP>Ihy?E z%gOcw?ZNXy34k~34cV3Qq5!<|u(W}<<~rRk%fP1F&+;QF(I?v!;X4f>Ddje73~lD(8%<_*;QOW?NMWnW9tj8 z@2|NoyWYW!$a)9#a^j8$LRNWa6;^0Vmwi>TUsWD7V&FYL6r#!pmzS(2{2nnc;2XQa zYw%b{J;Tj%^WReqj~U+N+|nX0OZO5LLT?P7A=cNgb5t7bN2&E+7*TqM$nLg^N52JD zVtVsw0dM?7%X5l4Bd3_j)HuUN;MNG9NCuIs1PTri0!T!pu*@p}6NfAz@M_&T!<#Vf z7|DnXV=86mA&KuYty;pK;JmTK)J$qkpB`n`vQ#TZn@2T{kHjCgE$hd>{q0A`v3?@g zs?YGUyw$lO3zpY2ydmTX%@Ruu#J1}DH7pp=gS;~gZ&#f_D8g5pU}6+mQT54YoWJPK zc=IQA<2k&s2-{-FyR)HwXGRLkqM|FhpEPoVJV8<$1ozN_eV$;JCH<7j{e3yqBzuN9 z+^Sa@-rQU1E|5i5n9{+LKUqBm#2Vw}vV(&H;3XMxT1^%h*MqX6$Gd^%_|-$_I+V+| zX=LuUL^;Deyuw-Ko5F*q+KoaPS$F*=V!GVqf3|Z1vN5E5%LBJZekIv`##-%_x~;AiudaUl@yGUsYaq&D9&e{r z&%Lc8S5YU^%Y&VAQkb4R?|3sGbO!ovS$4b^c9Tg7J76`?F8(`^+0O@u23ooM{PH(F zE-$?S=P@rptF06GTmTr>pgPjp+;*;3U6|Nko)oQ(E z_OfvR03ZNKL_t(Y9=BT0e%ieM;IEok^HopOYyR*CLdRJ)c!*0-EuL(WDU=gDat{sL z!h~PZshpblqU^dVCRk9|TN?u-8u+QKaA9#>1S&?dsDMe9htbzDoI2c&NF06+UmB71 z&hrl)*fuuLSvF+HH6PP-uE1@V&vSK_4b``AJFTSFL{!T;lDyuyl4=WpRarFv-hv13 zxmmALKUXvjy>iVr#-DB&<;zpA{v6e0JKyMYM{_0spMy8HzM`cKOVZ0GSvILo=jt4y zmlVSqVU9FKa-B{p^}5vK$Trs_-sY-xtRB7Wxyal2;S5Jn)Ixw<1R5ZEHQbAPwcT~`zYONE(h^Lj*j0Feo8;PNG`Pn1;3NF;@2S^fSh%T^x` zT+_$O?DfcHY(%UPKQpbi+y)Jb|2p%Y2X#}#*(3sWGNWo6&Af~E9>jY^_3KId#M8bm&B0G zWWhwrP5^KE-7hBH#&I%p*2HNTM=HR$RPehzt55owu(<>k~0!P}oS zuynZ3F;{E<*-;7cgQFVe3rp>g$)cEGvC+KIR62!{8r_e5Mn=80U3UQpaM$lFH4`C^>9QNy#uu+HS; zS?c3q>5FaSF6_|h_iL#Frb877-VWy~-gaKs)%2FpcF^+c8qix9@cD#BPFgq*I==Bu|*=`4DOC* zBARuGdWndV)3csDv3RFqZ{ob%-sTS?$}wcspAM`F=7yQ(P&7^NHf9depJ*&vaTbI%CfOst0SrkpkFc<)VY7>e+1B(x zY>C0MhF!O-gpR~S%d^Dxj_9s#zmHwz*fRaFz*{I@S9!oG#`C_eis>a1lby~X&&}a# zUb445-XDnyf%nv4Nw6MBxS-51g5U*I1z*|g69D*p9^S)6?Hg3Q0m2Qu2%zY+?X`8D zK7wO~{v&&`u5~)FzbRBWpb0*ftR|C%)RC!bI60|e7x6FebiE))IM3dmeGswGJZf@W z40C#m933+Usf8#Bi6$UsAsVeov^OK|6oxZXd?N-n6XtwI$m;`IT@Bh{hSxX55o3rY zGRDqbX*DcGHRcpA-3&#Oh`5++)~lq(&*-QVY0kwnR3fL zeHGiW$1~l7izsZbKLKVV4EwcBz-z)w385_}!YG+!TQ!3i=;25`G>~juFM!xkl0`t< z@Yk5u-T+-ny#IW}aWm$c57E9h@A$%T{Sb3gtF7nKn22Bgb@eq@0#|nn$qrBvbQxZ# z27Tk9>-T+5WDrC&Q2r=thZQwv-RA1jXLH+w)cnZj-?UxqAdI4l=>?_3S8} zZmPwVUWR6^2|zT=ODdrx>55ELN+Bgvj7Znrg!EA`ob+fFwO$8>WQh|dn-TH#aDp4= zWjYBz+jAHF9I*h*#TF=!EL=O!wznTWc=T+$Xk|+TuSCcgUK2;)XTfB1xFRmK4bfJs zjEY!-*A`7X(+q-y48-_N2sC3{u>c&qlUczcwuPPYWEmSAIIu0>^BQptkCC$z*zBoj3&>$Xd8BvCeT?>Oc9pCaWb7~R55S*f=N!VJN=~^4FS>wuv=iVx zzy_6MGIwt5E56YM%~$Mr94;zf4_=?%n}%1V@7E14vHh6o-GSw0cr_;jdWBtb-J<5b zUCwd9S4uxp$yg5MpvfaYu1nOulV;(Qk00N^Ki>ZEq1imyJ3C^zz%2x1dI^pu=YkUv zQVUHo$|`$B>XMd}J0sc~8InXXxAREhn5S?>FVK_dDG{l5jF`%XRo@N^>t1-674a&k z^o;e-tg0(U@JiqyT_Kz%U9!Bj=HB+?Wk%rDJ(Z?m-S64=f(xj4c;1qCuvkHXBhZ3p zHgYAxrBc8ZQLBe?EtHSQBkKXH9xw4EKqcg5Ea-SD!dBcrk;44d;*bJkPLH=0-T}jF z*){~O7~8SUrk7uHH2oDLY6~5ijEd^ARyEZ*M|oLU;BBFvtgfS5 z72fk5)7I?Kd+yOI%$_b7-_zURg=xL190HGgtPzKuJ0{3$0@$IIHLV0+GAk<9LdY6o z*fv>U@muaa0npz1N(ZHC8W@J^sS4k^sI3R~39MRQBvpgnv#g*e! zG<~blIibYa3Dptze|qWsXOGY0KmR;=x!?BSjmB#{*PY=t!LoSTQy<}Yi^nvUOP1wM z*g{T5$d-|Mo|yCYkW0r#yq5(M7m+Z8!B|p|Z-vfJb@3eAcGX@Iq77VACvQ3%)7Ab+ ztvTIk0p5{XSdBJZxb8p#5f<;^lASO%?U9{^|A&OSUOZ&1QmPa$$GtT+TyYX z)bi>Ysog5U1$cL%Z0Q6qLY7&qF4F5uTtn-;j@a=UhUeIobi@L?eoXDL$yD4i*kT6y z8c*sCVc;pS`OY{ap)eNU#*@WZELB}c%nWrN1(pjk#FVoY1n!&TS8tAwUu++-(FNLP zP`jE%ruRHUw35ZGW#XA;$#`y(r6BJd<%&rAmLf!4Mm;Qv^PD4}# zpePb|{g{Lp;*96badlcLkuc>HxT)0SpdCq=QjwgpVmO-U(!1?AFMGY0FJF>Ut$I=+ z?(Z@(mNy*sCp3hj=v7ZWscdezdVU`%eFNNM8WMMU!iNd{cPkRi~E7X!)p+*|!=q<$N@1Bi_Jvmv>)q zpXKY95acGyF5e>VX|dY zf#cPnt8RdIW>f)JE_ia-7-7(xi74)3xJ%xMNp9jqfR26<5yId&-LW^f9wZVE9z1X? zhm>aOQB)=ZVql0_btJ}C*1;e+!lB=Iy`IaJgcXU?PJ0fg%ZO6ffrubC3yB6n3tE_N zcQ{@)n_^mziHgxVAA@=5B4Sd(zmtF}ow&iu8 z6nd7}^QmxN<1CvD$sxO2PZ7N5uhCFc?O;AN8{VpxLn=ZFudvG%zS@|1dhQMNIWULl z29MyNyQb407umf9-hj7u=;w&z3Ci%*b|kDs)hu?s|h!W>xMPshM)i|S23O8 zjN}|brb$@i2NDS5wA&oKj5WW?YQhKDROmqS3bq+3uVi>L@-Y$>obLR;R;7gu#db3J zU~@AnL0isEq}8kQ;APPpRlxyx)0oTW+tP8P!TSk8pRntG>A9}tD&T@~)WMI{_VpMc zw_rC$eG_zm@1`+c4)W5SZeN6~L~jRxcR1ul_9ogOB4TLUSa>*-t3CQm;Dzv1y0Mna zAx>7uuush4C|@38dmxe!JWU4hnl=he<&qpu2I2K$W~aW}wn(obc+UE2fMSP-q2EI1 zt#LBn@b26RW5g*JGC(WPm6c?24GUI)Z`e%PUys4^Mq{yP2;=fYP#5FI9EQAkk!B{D z3I|dMUT6UpT3#=*8aU_~B^d!F84R-QH3nDdh2wZNnf#4}@Y&f>z16ffA3frb7_4hp zM`3%i0kvd)nL{`li)6R5mFZYd^|P@hi!wFIMpn<&a4wnKzUC%0iVKHqsPPGUdJhpU z!IKb1rooJYTP9-54A zAQE;H-ov>#nGbK617x8k^3+iCh_bmA6}^9v21txcPZGIFvN6eUUD7Xfq~Cpd|6Xh@ zL3{j)FX7vZ7u$$5u!BxMTN-Ksz8iUQdo@$j5Kkc3kcR3^WSUatAO$w`J>icdi-a;_ zWSLJRkt5ntY1D7TB-es=I1u6GOos920tdtEgG*oIVS6(9>E+AGWHS8u=c~R`eU?5W zpDvxrq~j*Erm157$MXu(qsyyL8n!bXUtPdeYFxSQX4a90w2_|qdkSlRFzDGxM@XHH zx@V5$8KxKAZck0CB!5wqV0YE<&LmpvFpLAF0=o!O40?mX#l?ldn}-b^35Tgf8n;N+>%&UcNtEGqt3AMnHgcs_atJg8{UXGDr+#UyyS50A0c#l6Y znVQ<)c(k9 z({?74F9R)SP#?<#YPE22ih-#k4afGnyrspQ`c*jinFheyfc90luaR#&?e=@a&vX#G zpJBs?!!h8ct03K_+mR9e{25UTyQgUnI(|(SxNkzM(mUV`v(Qnu&9e@wht;y+tEP{) zX9wWryUu-4dssH`=>zHqrszT9Wnz5iqPaXR=U`4X6mBuR2Mh2nttD4hcr`f4)sBi~ zNwO_-ITbgdM`P>ZSah9qLp`M2KBT?c%<}Q3ogWpy&&WT8&ALW=tmsGYkv- zziDEt*OO{4mJDj}Fl>EeV{_m;aukS(T1!#)r7M?FlDo#eWl)^mcF5N)*(IzY2A zMxd8T2`HhW8lP{A1sugYv92v+YQ_o@a7%|;f1!r zTqjOaNYIQ0a0vbHGMgY4Uk@*{`dweg`&5uwn}uTX=+p5r3tjrwc?BQm#k1}0XV0E} z{P@e+TWH)Sf@bMGWZRkuoE`0z66rXG-6fDa*7_X4D~cgwT&@HlK`dZ15^2WgAN-VJtYzXCzwVPX(c;sb2cwMUuSn+Fn0TXBXaqR{BZc zrQfY))CImo^tP3qy8f_Ocw852%Q_Rr$=Rbu;gm>+RlWjM!1FNSoGEOpRbYZ-) zwV}EcjP@FSh!^l;0a34S%Enk8JCbQ&wA+maaAbCSy%81_0OAFBuTkFHfc9~v=7c~q%fu6STqYT3E;Uc?q9DF5C z<}2Qy;eC9En>1F!&b$Q9qKp9XBn z=sw$icKqV_)5njbeRam^QL|P|?|nd;;*5KUK?%VFn_FTlR%@!+(-;<6Paw_Mac^44 zbI*A4xd|1pnbnvjMp;WoWyq{VVnZU>L=3Zo73qhh={c(mbL%$ z&p$Wm9jU$i>DeQ(yul+L6(z0nCgY*d2AOFW5>;KU2Jj+ywIZBP-5dcM4P4i4V<)Fe zaOuOxdBhT=7?+(YtI?QBH6yMMcrjwn9SF}OyxN5;rDLdvcxpA=;ZQ;i^wq$NTLzy$ zGrS|ZXLN-h*|+KXK;shh{R*a+ghK}dCk;r&uiV_akYJY1SZw(SG^- zc}?I&1-kU6ud=8F%93GFSIsM8$as+lq%nkDzHPO}Hj!^aP%XsHoGCYB6_HW-t4~_h z%pTcP?WWUF(}nJco74ZR+pP>IFAK@P7F9Etpjw9DjmM>T2^5E){MfKA1}aD)84$Y~ z61);>C0EVn48sVuG6Za z$pvFy4dxdbqdOe-dc6T7OmsdSiq-wxcHPEPwYXUS3vg)NLT(H76ZBeCO*P{YQV>UEA?x!()b* zi^*b1P7||e2+P>AHE&uY)0>PE-#jHY1#v+(DitRujX>Szc#%iC(TG zJCmrx-dRmr-#?T}9D1nfA(zNqk~4ZOp#if}M)JLW6@vsARx_sehr<+x#Ej$~G*?{0 zMa`js z#5S_IK-W|<;NjB?^~hK?z_daz(|6XrXo55F($VYp_Kqmn zQ3cc?@U#c4a>;Q>p~~P@4FE~WJ zA(qrcTbR=o$Wwf`6j}*sPeYjZt0coI%=kx^0x7n5dj0(y2KNn;6)(1-7xZ}bDllL= zEi0bRK79CaRurWxf#hcN0j#c?Et-scBwSM2p+H7CibtQOqn7p4eR2gd4oQKY>)0uRH_e1+eA&S5M10(h zG#c%hmW)f(RpBCdk)It9d@NQ6l7-;=HBvR=B!X>!=JXin4ZyBtYQzBV1v0wo%+YH& z!Ta$V1aDj69K-x7oC6gSeS+6=%>yJDA@`3)aO(Q|0&l%lC3x!+R49{m|B#6&_2FfnCjkcc;BS}jdI1L&zG%r2`yxYl>&BnO=FoVk)T&WA~ZgdU4Nz>Aqv-lhSudTAdhX15B5>aV?REF8>EcrF*^;yerFc z`0@;zcQmq|BF!uJX)}Xuyf39u*`Pz}O+E4`*xXI>mEYK5<5*L=nMIK}XCiY>Gfhx_Gx$ILxal zqVZ%jmWsua@gjL_>vfD$+53bX+lym_dtW`(||a(~OQ-GO%F zaF7d0fM4lM^y#rkv(o5ACt-5CV1a) z(t>=p7caOQh*ZJ{l&06tlHZ7UU5T)^Mntr#Tt$Lt(2PXM zxC&BHHO^SZi>b*P6%$030-uP4HKb)u?u%-cuatIPlE*grpa1puzYhl^*V#Ef;v|LX zahDGGOf(Wrgznl7&tl?`7bL2n`Hz2^<6k)bKVx_iBLj*Y4r7M9Ta`wGlxM$dJM9aY z-$vfDhhxr<4F*!FAeBA_URU$TQl*Vhp}hIdRFgXkt5 zG0UZL)dJSHoD#P!sb;7XyPdi}C{=U;8sd|wQxrC!CCFsZObaGV=IO2U?wIlgLVeCC z@9f^@r1|s)3~z9917=s>k%F9+B@BDju2@-S;*UP0TbGt)hgmcn$7;c7u24>|V+3Zd zz=<_3%z5!*`^86G>bx+C3egIBH2F(2T{|;Xya`QGaCVpxO2BE$NSiR7@Iz*VNJJtJ zQgcI=wWNZRT)kD&8=Cg@YS1I4Kn56XZ0gOy$|mRu+C!m%U#2>ZE+lali`z&~STg0* zu||a=-O3|=!nLNUV<31F%aWJST&v^QJ!4h}*MQ=m|D@A+rLDde3tHPtH%R)zX>e*6 z$pg)U;Sp0sPt#>K3dW3z0D3+1ihy@$;<)@WLl0g^0!V|An-|Z_z{?*8+nsXk0b=7i zN${5Ig(ToTkR@z&qPNp2d-O89Wj4GiQ4M8nB|czBPT++V4bpoWq}MxwHwS#QIe5*L zuNmHl4E`EXVec3J&Oj)G7tafi@87{)D;kmk?4ngn1dMY$pjRwec2 zk0(KyZ$LyyaKe~b;*~!w%^J7Ig;%4IxXx$S3~^?A+j&y27cxYm)txv5Z%L@8hu~Lj zHt{bMUj7Rb5r)K`jqSt4#K_O;{s4SHgTM2iT(A(-!-3vNSk5kDvapK7&)1yWWdS=C z@_L3{n?te;^OW2-?x8fVIGbYG)=VP}tb4IRjxe2)wzUQ$z?;wWuQrr;!=NY2xagM` zqDNTBTCV3>r`7tQHwnFS-Z`y$@D>&x_#0c_HJwM^nntIP=Al4gBL1n=)7YnCv+RmPj+uRWM75;V5J z8p4yVs7{Zp&O39MG;Itup3AUj^an%yW;jJbqBmXQi0<}&->Fqo$@NyXu-~`-??Ry{ zRXf7Bh@N=j?p8j_tqP)dxqQoZRjhIj_@Do3FwCBSMJ3~SD$!bQha{|?x?}-|8tIzl z*(QqNH3~S-7wG8>UxV!Iz?0bYEpJ(Qdiq%GH`2ob?~pzWZF=e$@U|~p*Jab&7ay@b zqYsO?b%TBlvrP%!dhnglh?<>ID6kr8h(Y9Fs&eO^W8001BW zNkl*k!HBk0XE|ip$E=L`HsPEpCbeLyw!#jCiWbh`@1IPaQzwKiP5mHk+y53DWtYR z;iWqzQDGsc2E#rcj|YzHq86fY1v?LP@j4G-SyQdLHmvbFm1>LNJ>{X2y57};7u$lg zXX4;+5#HT^?nSsbSfH!HwLfHe4Za5^LE#^d1>S(Ml>}2*ub5$BCQyd+WHBvsIntr% z`g)pNwp=-P^oGM@$PSatJ%0Zlv0kG2_{FnrBN$jR5U<2QhP^ja)vNF#!-bSubHAyB zbV@9WO+ZXK)fl!+Ktc$x5rx+?!(KkY5Nz6nBm+%+C&bs&F{csCT5TTV*OV1$N#~_y z%_fsRarpCavryPBX&N|c9LxXn$Gnxl7zYAa1D3lS%+)BEZT;(Cj;Z~EieOoRjtbQg z?#5Gs_w!f+Sc6^<5m~M~_>7agsY}SaMAwBS!P#D(74B!P!NG-!>I%@xH>zwv43}xX z=7jLIusTGb3^cps6tdF5OZ`kPTebY!gI5MNvhh9cz0Db9Nc7g*P~a8 zZ~}P#F?i>eTiqfx9tylF3C!RkuJ+Sf6O^N)8FQJfvCIL9>a4lwNe&yXQdoF{qJ|fo zW5aQ*NeU?6lMf0mTi0T-1P89kgL!K#AjoBtLPJ3(Vj%4p>C0)isom;QUVASRt&G?%5d`q zQ03GZ8O@=>YxND4h%Z!F6qAircT7}rPn3t4VzRVP4ZMTlXgr01cSj~zh7XB^daH)s zLzcb=r>8mI1dx<8ahW$3o}PkMAKn84?}3pG9=)b|H^3+Nu0LMH_G|3!nof&3cpn>h zgJw5uN~BQBkPe5JmY4p5a>%gK8!DPH;!)JyAUv@iPoo|ODwjjP$H%BmhYNRnTsV>{ zbby;K?rD}nZ(?{enX{UNu|8<{SHKmXH5b9Wsl)}*H5RP38Uhf~+*2|c3KhVoKZK4T`*{}VA+s~ z-*-sM8g`Kb8=|tNq2B+D>%qvEbI#ADYj>*g-G*bby4nq4H+n1(dUOy(>{<2YM4y?KiJJ{)Wu36gEII9KU+Qp|i6O zY}L|)YRQY5`K1j^Pol<>IAezJX`+jRT*kn9MKl~H)8b!t^5_Wr4q8SGx%Y)I6$o1}S&;AcedcH+U`CogL#5QNaPt<6M||I!%@eWK6dcX#vIEZ5@B zG)*wJxgSz}H{3yfmc)N~0uzdFc%gE}c*2E#00Af z7l+*g_8ZMvxV&F~Gj-B~>&LufiZxiZn$qm$WA+qVAX{B5Kk;p$Ruc zbV~_+U_wiXB`;zG%l7u8M-N_3obKQMK0HGk!G&X0A9oU$D4mFJp^AJ47-SQkQa=kwH`&v-rB@;;Y_7RL2e)bLI_0$+%`r}ma z9agmhN-cw^WgNC1!ohy%%lax!`7 z!F%B4%|!A_BEduoc6LtB167a#(-~v*Zo->-{J3y%8@zbY0PGJ9yzzj^4TS;|3Anj1 zBolJ+#xcL4uw9Fi&<=qP;7hrJTbKg5pZ#aH)jAkH=`O-WEL5AR%r-9+@C*q8? z$OtV=)FvQu-Ae4TA<~dZ_z_&4!fUvzNp1sA*G$D~#xz@Sp3V;lCydeNP9CjR6IbsU zsFjn!_IB;j1M~oSn@$|Np5ot8{#g=+x$EHwox)^Jarz$_FuAe}0bngL6MxMz#Wegd z+_k|c_u*#|w7}N(6v1HZsaKAI`DF=45=iYMmxghcgDJhL@&G>aSl&M?azhlG%O5`8MeW+TMP) z{gKI)ZlBEA2a{;4iRd(UGSIb*2wilnajkG743aNQ4_?#u15;$c=$t>} zZQ7Q`%d}?)30*~3k_qI!*?v}x3~$3k4>(C+n`59y|Jr22aN_S7%w~Mc9K0~Id4X3S zgvV5Fz*|AY*WAM84)oFk?_)7-@_;giw^jG(6?k*JiUjbMlPMkoX^`Drgje@oi-h$y z4oZ<^VV=DOdLQ$~t;Y{{cW%=SJXud4ne=1>I())WNwI}Ox&T<&72Uw>!s^C3kfwKs z0`z`*gG#bvmJHHX@of9W@vBcCKYkFMz`Ec}b;Dk%RIJrNZc`jM0h)*WC)hMLOD0&B z;1TH{7D)(>kgfV>d{ZNCNT!$A-{0quu?EQ^zKAG!d16Twlc}yXYux?**9VU>4<0=t z)r&s_Z_U|6i|Kyl#~(2xB=&+sc+YCg?qEdrl2gE@jK-ZgWXc?iRBE?}$zx0xP#Qx&}WBUCwqZMcrlAx z;3Y}U@D7CX@%5NILsH5-rB{+46KoJ9dNHf1CGZlgxx%?A&`Ba5kxY@4ZJCm?s%Lsd zHE1TxgLn5J!0zekZfXJEAij?U-V_+U>9XZ?s_ESG5$842wxZ#+5QZ0pHBSMWO{|wA zYFt@{-FVzLdz4n$P>lY@;G*drjl+odzJK)*Et$u!yrKpo3$!*-^?g?Q#|O#1NdiN3 z0}9L#bI3%@BvkGq4oQ9&*um(+)~?E6kp$}ouSh&%y39a+L_@;CvPqt7mJnZ!VJ@lI zeMT1duMg0utTAm)MoXV&T$Zb zO)_3=Zg)MG%}*xy@EY4ImN%7r9B{W5=uM?Ocpu-GvuDOhb0Mz}ukW5oUd>FV7dc_J zS1*ToRTH-ZVP9Smn@e>NJ{l!HN$W88Uv9r3i_1;%MDK}#7arLetAP)1KD;3T%-kxv zJa&|4-Z4uFj{S0tI_8^71B8iwaWM`D%J@dr4P0LqfENYAY%N1ZE_1{!p#?iiyRO56 zt;g};uT1cx2QM7U8eLEO&kBW|i;-1*@Syo<`&p&J>QWtiz=C!jqGBA3sgfM1Sa_t> zl~G|LBc@sv$-%VBMt*DKN0ys?g_jMlJ7$nX6>#;K<4J@uX%)0g*jM;Cc<>^vSNT$`mj^B!h;YcIax#R$ zgI8>?_-hBI_A4+c@8E#b6bvt{_q^eK_|Ry9$A(?8vGcaqXO|BDxp&s0;b53o;q{z2 zV)rj0X(En>SV~|iX&57qcoiU5J;sZl5FR@|=1j%*@o(?V;HNj-4WtM5PoI7K$A|Y2 zy&r^KF4d7?=^s8cHQJD!B0bRH^9D}5fkJ?|_cB3|x6z~mRhVOlaZ zt1O*AqBCTZV5YSFXnVW)fX@Now>#@-=PyJk8R!F4^hlja&c(t{d zix&?T4DYTQU#XAe55U94BNbE)KBKoA-bHwW4jSN%%OIE#<{yGr%pF-?;`Fc=>P_4STaE8N(5omG)|hhZHCG7iXv1N_!1`3fLouIxlL<=kgpdB`&%<_u{JE?I zT-ES0yIE$pZ$b;br}@0a_2KQdR_3_4iG}b~oU>k!-K;hp-GQo!5pr^lM8p)tF<#yh znuoo?)z#H-oL6J4^25NZj-K9h@o)}aY<)+wT1&>;=>AB8w;(gi{0el1cbA7YdBx=( zdWQ^(l*S)})IZcg!f`oJr2*nRvpc!Tt^=?!aaOk6lg7>AaYVSf1`s2&WXgFRYg zd|`r%$J-PXNF+Ls8e z)y_l*Bs9HXifa@-2?z_DfY}TnlRHI3=oR`D(1&CncWP;bdP_nknE(&LDe!xI1Cecg2TGsBm zuFVadnEhEvV=V^ZDrEr0*iFF;p)Od5ucm9qJV^ zt^{wkOH6XQ4Jfzy<@3hG3cN_nRZJw9^BEVVhdCDdG8z*90LcSX?s8MG*v%A)gF->3 zlz9yl2U^O@^m?ix(1j-O@(VmZJ>QKj&`VM{_c(P6y(a8-JG+2)J?f{1St@X6yQz%S z^$p=r9KIV@IAH9YgnKbBF@3aG-P?Qf{?kXcw(m(iy#Mr`!F}`U|L5#|pqnhu|No*R zl%WlD)YQ%Q(|eSMFUBsdIm0j(~1%MPR2J&p<&usPVqzTSgkKUV?|y=9d9&_)U5 zl-JuV77tTT4@Px4xu%(;bSN~$M2>X>yW{u)Pv&H3MEHzlyn~$<&-Wyg$>+&o*4Fmt z51#$xvRRZd$3`9rzYwp@GC;xRwTgViv?!xgOzzkP++%hK^Piq7_-u%% zb2ABHa`Cn$lRcAEdW+{qV zn*_m8v*qP+2=B{`@z$+$0l*tac(-AY4gPP9x5}-lFw#r#wWK>*IwZX5OnAE{fVV>$ zUV`_{H=8oxjW>?5yd51Hz7xV*=`Yi#XIwW~OYf!fvwo4+Yv;WmIrpKON+eYyyR<{j zreBh8VIR)awj;c}vSNGt_U*05Q7#bW5?LRfXO(NVuo3gB!CZ!Ws*H4D`<64 zvvd#O@?eZ0@1*G$DPXzISWdlI0Q5Z1slc>SNDE*HH=1;+VR2x5Laii8GRcAA9u8Nu zwHEr_hV)xN7wwda@*3iU~_S|2KXZv z10BXZ-d9hO(QWVRJ9&89v2$c_57Ups2RS3MjfF042h>ddUvj3^HDgVZo#cVo3I9>8 zUXCeffi-lKtzFCY%fx&O;U$S6%&_npSJi1Uxk@aq6w5s8C_v3R8%;fV$eKYm^z^iY z{Ah38xjFoF4}6tx-An*J`N2_XCjSCo(WrV0Qvy_hBZt- zI11u+{WDm3Pt2a+2*0hjz9zrGd0O=vM~d0uCTD(d?K9ek<-<>KA7U291ieg2czG+D zPdO8N4Q=R?8oWo@Hv*L7lhOz4FTzBNx#g2bO&0oPOdrEI8ZKc=cQU+WVaeUafwq>G z)~>GBu5k^o7M~!74INFrt66r)cyW_S_)>Wj6TPOm@%ZdgiPQXA-g(!w8FX#6QBC+6 zZ_(I3qvHXwtww$~(JErp3RF@Y$vsTTI{Q0%bSjjW~Yf8X(3HqT9`7P~QY#*$pvZjdc(=ah>Y$QIBQiHR% zr3HBGY&FAcq*rq`7>hChcI0c&^2~ah%<#hJ#z<+$`1Bg3Mmj6s3Cds-TU?P)R;y>0 z(eCSFcObh*q)&Qk$=GhfS=?IQ77`wbihF(R?%jS83y>TpJAC}4SOz3kupK{q&aj@C zq8C^);rZjo+h`d)31G6iXd>(62AFath8ehh?c3X_n(Fp;o_kgGv`Kso$DD=3@eJtz z=8T@6W_>G8V7PBPg;+Su1`{{XywCpeZ#(00GEZE7@v{#<(839upOs;-zfd4-EBpM% zu>0i8eDw0mfBQs+(YC%tt8s{|zhJhE%qAz70&lE(R96Gi(fc=24Jl3X(gFx_Nr+|c zawhh&8D7qd(1H_s}5fn%HeXDsgoc9&8ddOI6iXjAOobk{bc=>@~v(c0OS=5rIU zOYrvUp<==fYf|gYs2Z5w__*vE=rtPNu5=b{;wU!=ksdNFOo3omR||Ya=C4o;wY=3O z#*1mc6skJHOWr~Ava6SNSJ`IB;{|xmfdoEy@R$q@qljq3$lEy-7Pu-Nb*(6+1Lw(4 z+e|q*n!;_XqpTlleaXI8t3_AqS_gQUWzw-zDEYx#5%U;<%$ie@mD7+Tw+6h zYhL-&W~@^tLk^;8+2G{x{Xla1!OL3eqR#Gz`m9a6f@b*a33!sfuRo&z!q%;yX>%*O znCgbS^Y^jvekPtn!26yr5C}Y@c@U0Dc(=Z%cd`4-s1Y*Zm2qSscy%0?1Mwm@9o}sS zFQ(@jh3qudPLk%W+~3B3FAhaovsDwf_F++it!pO;zb&25KIo+Mvyk62CtU3C;rrqw{0MbjpNX~MYn33l7tqhvMr)rpci z%_4pw$~m%#U~lmAb#hzn=Bj4Hk`0(A(%q=iCGne#wmrKcwL!~}Ws&%SG>I=iD}xKn z>9Z&q-ZMB5Ko;xle)yL^zxM3QbcE&i*(jou^~sMh8)2&+KOmKgX8ApK=#owr@cx-K zs)YA1{88~@O52mf_*i&9nFwAkh5BK}KaM2k#Wq(IeZukmvLsg5W)NUww)uV!t)2up12l+(^V5T^E;H?eo=^yj}(UKh>ykK#&l zc<@i?g}*-028vBvX(14%JfO<_@Wa>MdFM6qaD9vzzs&3+fFwuhQ}Z574L_r#uRp_$ zDboQX6#x9Y$dSOO+lXO6mcg2P;bm?52d`HKmkHhvwOBKP$~nEb$U}T=NWr7&MR)K2oNMuGeV2X z|Dzxz6(!l+-qs!sDs4U3g=eC)pL;1;MwzEr#r4M=R2W(mI(Kl{euslAjoC}#RoUSQ z;fd+LC>=lt{a+na>~@D;D>Ch|>jkIbHg>8r0?ma@;kziVU{o=+(ubc71p=W!3M=Q3 z$xFmS*q|@AakFd~4tO#ZzyL6fB1E=gi^0IiX6zCJ=hX+MHu6&k0zNu|R~C!KXsj1o3Lfzo7`l-L($mKh z`g7xcW?1(2>8HC|5MGh?$nG++627s8;N@-F(&(Ph7hdvamAx!^wH50EyNu1?ae=1J zZd|(0CRm6TCpLg|UQYfo8W_Jof3?^f-IE)ds!v2miO*VE`OX&z{btD>j#G zz~hRtN(E2`m_RsII?IqTgajT1vB9HckdK^Bpo|={Rlzc)N>QAEk;?^WI&kAlJK{>P z(x=Cc3qc(Yz}AYL4jVOkTAQN10-Ft=5B~S?=Z&sK?Co^5!|o7|Jf%30??Hd~9*TCK zQa8NWfmA9S4pWX}I1&zfebmCTr4rZz(B+PV&Ao0Mh?nuz*tOB?#umYr&{_=p0(cbB z&(J_Vk9{#7h|d!Wha>t^^GL5YkVpEkla5k!qm*__Z^o0+NO}notp#wObohgb4VXXP z)hl)Z67cpSyzy2Oyq#JSLKoK-BYKUCewn)!y$8(nVpWH@w7ZR3&dz@e-UAX|z|tL;M^BqrKFxC^p#? zojaRg5Ur;bKXyiX@p>znaED#kU0YB9OjpX!A$+Rfokn&ge}ttO-7qtW?D|4sQ;SG; z`AR_=Lp2V&J{k}+v+K)1Qc!Ms9){BZFCBR`cnwY76AZQXWCX8Q|Bz6t$wzt--WGtj zwG-gQR0oaTbev00bHiS&n`59?FA>tK7Z_vix^TM+Be`UK8@Vz>qaopJe0ojOwF6{% zQN@yJwSZi(3z1-*{{HAH_jG{wnufJiH00a5X7rGo?4eaRdrGwIv1>bODv?w)xiwb~ z`4lv|QRs-GF-CY0bkca}bD^RvZ5FI0$93;4~P($Jatf(v;g=PFEf@cOaFJ*bDd5p$#kiC+eMLP6ZWdem*>PDY7OI7O%<~W9L|S;Q{GaIIf5*xDbd&sA&{BulG%0Z>@smgc|aqK;N2Ji8RFBCVmlic z4?aUD@D}nhypC~@Od0eA$XRCJ2yezs4e-LCP{^%cuZtD|Z)a;`2f>?8?-h}XG}Rz` zvxTaV5WOuePm|#_1SGoj+G6Qmv*B&xy3=X7H-%Ut1cTz*bnQCy8VAJbDY9_Z<-B$& z+iuUkG-|hIU%Ccum4)Fwacy+;+M#UAD(t)jgpXdzhNBbNARA7sgrmoBcBh%WX!Nns zH>%3spwao!2S-mhuTi_=1QYu{9-RPso$wr2rd%7Xs0dbpO@=X}qocMR;*Q8JU4*(9 zB?OqsLwp;XczGM_cI=scVzdg`rE}Tx``-vJYXf2vRq)MLAtD@^@QRUO%k%AC(1!dr zr!*=_GPv31`63#-0HhlqMx@=vt9+sbjF`J=#-zieYfeW7JTcvz26UC|&VVl=;MU;9 zD>W|1!W+}Cr_T)wa9tA{hI$BIFudslxXK63C%eRYTg99W&19T5gleHuk-FgR(uy-- z>W)@mOv+R?-C}1Ixx5Vr<1}z}M!TNj?UnFWuU!jFf_Wu)uK-A+_15e|059}FJ7IU! zfxFpI+V;kNpt*HT}|P4anQ+bk6Bw`D&Oz z+6+@LJU|(2wTj7`4zI~tmw6F0_eL|jDJ0L$U)Q>x4Nx!123;eB-9C+81G{F-1TAB~ zwk2i(IU`F24Dh;5S4UVyyN%##$X^FuU7&c4cQwKb^ma15+a$dBcW0|e%+=w=Vh)Dc znsKvA(hWv<_TZIt0f z*)VhE%F%0C-zh0MF}lhElvuM%u8ab_IF&1bfp(8Rb&3*!SEf`{lw2BZh6!8A=(WO% zii#7XI~3?HDS=CqkBz>+qyllQs8H(AEtQS3p&omtc<_$!AJkTAgO^%~)&UgKvfr9I(BJHy*T@PgpQ<&~6s-r31JN^q38v4iXOc6J%* zO{aBuyLyf6t|oF1@JctW9?1bQe9Zca4CC8s>=(~Me%{K_D-|bBWFx%Uhb}?8HhS|3 zlnow$x1s_tIsu(mMz;VWS1Jk%4_zwUGJ55TuMjAAxUF`awv%KO@YbwZQ*&u_)sE3i z6>$BkHEZBzaKv|B1NxvQwBr(5US|}M2|hM@Y2}(jaL1aE@7k#S(kPC+GJQ0+uSG3{|jnlPQ8;C!Q>I&|ADwGrRO= z2HYCE3|#_|rrrR#WOa5yMuNNu;0-n!ybnHfQwf&H?jdNgJf;l#nHr)tkYQqD2E)tO zrV>VPft0+FG4Se7rdeKtr@`>?(dlQ5+444aQ9ZqFn7K=(NztLps+R+8@m8+)uk8`s zAziyHdG$O8HmV)M?y?gi&8v#vl;aO{jX^iQIW@9mODP%NmJS2F`gF~dP{xKgO^R1IBhn_r3#WQBR|l=;)yCua zc+QY z>hM-9SDXaz6>$@Uw-WulXnE_<=eq`aTzF{^J-A?E#n{L(Mg;^88>k^iC%fk;oc!^9bT3(;a`6 zMT2+CgAZ1r<&8IzOBlrQ7KFE6aT(#=va-exVo+?>>A-UsMrbMrQwF)9K0T% zu-754w8!Z6a)d$#cmo1nIEA+#3$MYm;WIp!q}NG#==#!jy)qf?(b{2zx3NQ44bt#R zcssNzA!(CnptG6qzS%LBU8c7wJ}$htaaDD;q(hetg&173+q2!HC#Gkw;m%dr051nC z&{%pH-su>qfGK~8J3k+twNu#JFa;@v30p-~G!B-xeu;RF6=m|fzhYHcSyfdqSQUgG zCvvX@i|~$9gc6wUvD3PDXLfdB329!Fs&P7DV1-+ZM*qdi3a6NOX=YZ@1bN2%P4`2T zIm=vjJ#?DTyj~k)F}Da4kF~qrfWak!%2%+PN@>9jVmFbAV@S7z451VWlG40HX7idr zhi7AZ>_u}Q%3xl=OByz$W9oA|#=@J5WvUeZ{lN4ByiaTJ3WBLR4zDHd980eeUat8> z31v+kf;izPGo0=^aKOlJCcUyXnx|{=v;khw4cV(s(5$Eap6PG!(aMVKLnjK|cDv(5 zeTCI0I!jJ$sb_fIaAfu5A3CwJ0v@?s`6o_Xfp!VHD<8wLhbn49m$t0D^#014VC93C z>g(f=@l95(amwddRY``oDyY11siGc6$Ae|>pIEudd4dNN(uA$lf8`3_yMAT9ymZTn z`g$im>q=ifk3LZdmsaGybGsZVDh3xuJ1&0iNu$0EGJCNZ{ z6TKD5fDAbNfP}kk2ryGMqDfgbd9%2jfqd;2+@SUdpg>8wSTR;Kgo-o1-K~ zb3ZP;7^rA$(Jb#aS(A(klx?F;*u-+mMnP|{7NRh#29~R6HpTMyUSWD=_M{nJ+_-Hl zytq1ZVtDjV5PHu zRh7S#2h>oSgP%?7rRnwcc6+{fdDBP461n6?7ONN*KZe2A6=5CBxt`_b}Mb5SmyA+i@QE z4e%PIux5~@@-YA6HTn7RbZ@I+mCrV9C!cNG@VlOBH)*D~rB%{vk_A*#7r*i28oZoy zror2l3GaaeO&O|TOqm^hFXAj(qh{rt28)MsZ8(ifW;wtiMj~Wh17)3ICuPgve{dS( z6t4V=dT2G32xFNOxZ!-9O6Y(Crz;7ExZE=tbVcLA%3v8LpJ84Oau$t8VJPMFl=oM3n?#B279T_5MMitDtJbVvFraF*{VPbYe=1y%|WoCLDcoYBk@EU`7x_$xEJ3ZcF zhL`gqNkNcMJ@B-NUTq}>tJ*GoYrrOAPwXrxGbX#b%=GH)YRlp$V%G?-2Mgv2PAwLX zow2lA{hRDI_(Azx&{4ZdjF1Ut(AsNd3b02Bg6%C_wQAMMo_!q53Q{Jo!kLi4sN%xB zTg8<;oQx_WIY;D3!r(^En8dU*?7`F=ERy5k1T%!U0`7eeCfA6seBDvN(=S6@O(_$8 zGLi2nI90@C?YmQg9E)>KE}|?P!-UGcb$)4D7a~va1FOt-SIpHCI_N?) zuxFP1sQe5eGo=sycwBg)n_i9ayIu3f$^@QJ1k;TF$MCYp!su0tnc+oR)57%18c#%9 zIs;xQ9D23-=v>U4>C@pwdJXV)nc>A8UrIWUPXKS+uoYnz6=0AFV^++UWs*;-)fP*? zDw5PK!>X25#&}z-7IoO7(Tg;JRh^zatb*PIa)UC1m;YdzEshyfu@8;1j&4#s;vVSp z@ScJ6htkkJ`5sOzbJ*FqveC3;iJ6!!E6Y3+(?S(l7MD3#QF_C-mUASd=!P5ZPF)Z(SWAQ47_d6>^73?7RLsb3ASHaU z;on?-n8q7Czli#g%aJf1n!XtHPnXZ3bNAnX*Wf{mpa+f4#RBp(%25tUurD8`Nepk- zwrzWPF_0{-hoH@G?>X72GQ2|st<(46zHW(O9>bd8^ zs6Y}k(vv}DKt(Zu@J53i+zQIUa-1s~l&QRwm*WTqNx||e4w{Y$9bn0_SUA{_l|_`g zGJ_F#g3PjFfOiV341jk^nS*m{2q_<0)r4-491Kis)!ALM1k%K=5C=QX?kX4T#yD43 zu&Yhh0+?E9HDbrjt=VI;#Nq$Eyl~EXsfIP+*8k@6oaHGwoUQT<|Zk_A$K7(ma2>q9<7fB2wpf^zn*h) z0V6n0g?DgwLW+hMKA>UfEQ5nU4ze3tk1ZiQ0_f#34Db5&H07fxaxAu<;l*w@ zx$EE-2rmwgz%l%B`$WKZeT>E@Sp1rU6yw4x+`Bk_JjXIUQJhv{c6Rl4cG4~tq!x5| z@xKECEJ3ff8AV#a3)WZ|-Z)n|O?OSuOqQ2&c2-Yhc+)0$v8e+H<$!8MN}>JIj_|G} z*@1)Juhkp;b6oKO)K0JodjNnmwP{3=$ zN2)EUjHtGLJOZh^kNVVocqs*i>7h010wHn{j?l6uv1`~OO(`OqMT&teFBxDK5aNh> zYzmzd0vX}G;f5P-^bow>B{xilujUQkx?w}C_y%n2q}_ERfvK}+&#oePcfirvmvX$` zvo9@$BM=e@@A@asK9Pv+K1*#4^x(POaEqmHuR~9C5aw5OkD+%pFC*?Xz`HH21>4dG4DdEddL_QS?Dr)tA>gHz^Ap0` zVPco?{qMqCTdNMBX6RQvcFV=Vp`oGEzqSy(7uD+OU-u6V4L&}sMuVgmvVQr$V8GDe zuNBa|#|8(7241yUt-r^s`j5HVhep()eqnZ_CkJro#be6$(|CU9^f4F0>*_m(kA_aZ zY8z2s?T4dd?aHy!)HmFYLIV@h9fM-buzC!~9B#KAJ4sJ{SghTV@PZ*mc;Pc);fE4q z8&eI!*QSNq>^FSt#&3P=TQ}a}M0njrH%#^ryn&+cV*j_kyCh6@*9$f{wtM$WFTJ#T zH^KW7w%vt+voAdX2fKC?yt{XudSKP=U3lu$+4=|0zOs`d+N^rJxUw$ioUv-?AT4kpQ!Z%iYMT{n?8=g=*YPz-*`N+43EuWv zJ57Ry)hv_^0B?Jr`unWtVK8{nD5>ErRn4+T zhno%H80g!lzAC0;$SP|GIs(l>&{|xg2SyXbG_%8r9!L`#Sl21;DL39w9=dt-O%TVwa?4d`u+vkOO70SDO;B>yfb)TG)URobK5`=Te+ zSAj{;4rqC!Ksg9ofOimj`r2Xm_GEP5X}rZCIVYlJpm&vFy5|tU-?u&LaA7Db2!@z7$E_7^QLJJevB2otu+lP6EV@rKEhf!h$ln_zg+@O}%>&KK`J ze$(Gn9@_B)!drd{+u22c>Y`b*LiG;-s;5rvcwpAd@0@+6<6az1)#Cf+Qv1~rH_&x&0>GQ;I~>^A^!?5XqEF3-6C^8I(uSswZEk7vAi`TUUw z=ga{!eT2@Phn_ieY94%0!0RS>Lp6Ni{c}tf)?i0DWg}B*GrV&4=p&CC=S33oRPVI2 zwH4UqxPuwqF5KHkBTKL=9S%eQE5ob2PK-=c|nHx?EAUKS`mAB*Lo%!OpVgg_{9J3c-v1 zV3@fjgZo%6qfc0IITS>ddxmjLQUP78igqmd&G3>&5aI#PF}yGr1kF2na`PPo@019_ zn-cJfHf6fVaE%bWi3IUh&WaV`oi%F~SU9q?iB+?{;{bTqBw`HjtXbu$a2Vhf4dCVW z(oK9bH^D2wS`YBnptxN)$NS=CY5?!ubLK2hB_e<-NLjGE056?7KjTpt7>)!?mN)bu zUwHo6X_PbV=DjGdgl*1Qmq0 zwz*Kin^+A$Hl`Tf$y2y}>xRuKa$+I8C5gnEv%79OdurNDlnn^)P5c$hvLVOo&Jhzf zf|rvWcAs5z6SY|{oxMpkg4bP$H#@sD6W)cFp90x{Od`DNQuNhvbg<#2lOlE36j{q;myEA*T^qHoap7IT{<02SfLXoC`uM=b ziv#=oN^;=h>HhwHVCvTc{ry>>f;Yk67cZVB*+9VwIDPT->C*#`dl+7{yaW9gp&Q0T zZ5Ib%-lkBQk>nusU+f{F_cy=6lP@B?N8HkJ%idzml9GFxTEM6q zLI$t1cI*uV$nqZDxthx}^a^_QDcd%J5F~G}s1l7CI!)--ZIZzWHoTmL+sjO5#D&Me z+tt~du{4O*SByt5t$gq8$f~XOtX*rTd6>T)4C|1P4SwtyfGJ#-5E9X#2w&LQRThk@ z!<8PpM847_QB|rpSLL^v_WiY zb#=1ZpN!gN55?LrV&SR9haS7XI_bh~+Ccbv{G0eH3YnqD0HOB}frdR}6&Lnq<#0Hmyl_J454PU8;!Pr`|Y??TR3xe^| zqRG@?O0#a=0x*$IxrqERg6mu#4+9ddGv83Oi)s&X#K_MwV1KG>k>?YHP8m zX;QI9qkt^(Q=6<7WtS`#1xw|*taeWl^U?g#TKHMZ)%7SFF^PF(6!ul9mx?^mX{uC4 zEOIqI7{EC?iq=DEJn%^C0gO>LP+SVEZ_yDm;o=ox8<{_xl{7Udz+9AA;)b1Z=f9DMR&rk#H?8ff>$Ix z@^GMZ6(6li5WL>oZaRhVicm4k$f&`zj1QJW4?H_2ihD)3Hi z@Ovz$gBj;_cuf+P;t~_WD<^arxHG)M^oorsB)hcQ9Sen!J~hMJ)G-!bERQN22)$!= zX3c0BhWE{k40RK}xa+&eqh&vedSxDu-PptlyH%8ov{+XcEhO;Pm9c|p_;&QeS|cNhA!l*Ns-f^s9wvIe`gm@J`D7T|<@_2sJd9CeiV(-VBE@EDU{*y$rYTU^na%_#ketF;wOI4J9_^sl z3(v8h&&8;JeQb`4V#eMSFb?=(HpP0c*D=QzGA_|Dz$-&+#%RRE@UrFoATCVr)0|1R zSFXeCZ0$gJ<2<=K&?xfD3_G(M*u{jhw1ed--2ea}07*naRCFNp_O_1MR@nTgS6@)f zhWE`0>5XU6Uhh^tdDm*YCD)b#FUf^la$N?Lwe%dy>ZNAOy+x~76O`tZ5*I|)7~*~I_aVF?YHInVCROz6QB0SdhBuww0n9a2H6!|xX0u-lUD{3lL6EUhoe#^_LMd}_|qQ$I47D4!@kHo(J zNE1CUh|BVjF~w&0Dg5X-jJJ=SIRke{ak$J&54b&i>R0e{xqNo2ImX1nW0%8iRIuc` zjhGU7+*HmXVQN18H_`nKSg}`5*|u@z9Jah&2ybgwOJiePY$_}^$BY}`Z8E@1%PzHr zmmM9Mw%7a+W^E?J%W}D6T;a==(S@3&3`g7Tw`Ox@q>)vR>-Jl1T8G7M;MXcj^}24k zPqA5Tl(1+>5z}+VqLj%?peB)EWO~K2dCRFjZc=3Qav46XCCMCf@=i3`9!;+P>Z>JA zvDCNwtAtZ$HDigI%!-sbznq8W83AYXkyTrt0AvegpbcGy?S-2EDGXV`XxLNiz9bj zJ(JKrRKnTC=9Rw*Oz<+mCU||!yD^JWys(hrgAj`yefQm?M+Ll{RD2}e+sW%NJ3Cui zx`5qYxg$0gA&EC-SYE@zOUZ7U3o%^R_8JxkrM04F;~HNsE^V>e#EJ=mcal{zy-X+= zXX-XtUL9<#_Kfh}K5d#)SV6YBSZ`Y9nJdB?yM$iAyhtSq@J@BQ6fuhOBuP<331iHj zgg@G3^H z_4HY+9$vAaeFDYvFM0)goT zB@FPUSvANV+Ty&O50$qu8eR#nUaj}~^cq&e>EYrr+YSnNp;59Ja64_3F0?l`UAX0DxSwfT&!sL|L#v zDS|$b32*`qmIR|k0B=01EP+Q0R&a51g`*s0Tzov@bhXpICULa9Gi##B<|m&F8{nlC z66Dv225m}`Lmqg7XNWdevK?Tz`pncyUug2fV54s8hmQS_;E4yUmS# z_y_;y3)j_n@f>~AuESDH0fwihY02az)P97|(+qDV!P^P&uHzG6#J`Vkah-u*2g3Wn z4wM5seic~rs~tPwemiz7S`~YM8(7|14`6pBu;W)(&+G=?%IU<8*kiwnM1Hm7S2W3j z5j%F!*UJNV1e)Rr{QHR=3wLw2z;URoI5H{Wd1 z;nnABvNR;KOPgj&cCW8{^}0L8TE0z9V?4eTVXJ17YMXK2efQ);SEX?Y>)P+ zjv8RuyxW#7E1w5_cLkR(+fxn)d+t&Y-W9>yuzT5p<&^1*S~prhH&Y(qmsw0r`)hAcMAz@VqB)LZlKgI+!2pK9B? z_m|-|pfvpW-u?Uc*Z3lB`y-M4d-o?&2(LZ-_CEVs`{ui%>D?OCnuiP@TN?d;%%Y%MKVbflLeVeAK6q0yTW zURzDeB+}_lBC@yR|BBu>4JneC{5}R=kALmjO`J}iduu^K?k%?vyal~Bpo(0R{Xf_@dp=>BC+B5H=*|WbrZ;!%d)UlZF+n>ME4ehJXXTlL4e*Vf5 zgm?Alc;fTV0p1l+k)^Bj@5B921_o{W2K!IL(dm=GHSV*r{o<>ZfdMSiUo-e?kDjg4 z)3Z)ddbXp9t-_gKTTi%oFLYKpdwP&*S5I4&wRBSrmQdZk-M+RD&XqzZTHYFvIh$&{ zecNpS!n(a4t5;5Ue)Res(_er64rq@&^8L^wk9-#nUPpNEm_iTW(RY_T^2kqVKsY2y z;lX_D>X%>sYS*qW&j6)gp4m-DSAo|rcf;|nt6$EXw)@Q0Q@hHa_;S}1cm|pN z@)TX*6{EjAlXK?O6L18N16RMq*xs2hcZdm{JJS!r%PBJ8C9WCX--+BZf|n}h;NDd& zt*COlDB{cT$_y~As;(BHV96`$nCW%%GHC4`PdONO0H9f1CcRl}*S4))%ZB%s`|Q?9 zfZ(l@a;z5Xq)F4r&fb@6o0fZD0ZiDk!01kz#61P~6=dIjD?FT(dtZ+IzFcM6wEJ#n z%gc>S?3AhSW6!dwQ-dzovUyXWYu@sy-(EKRF0`}C)OpL6AiOJ=Ny$B5z0L4~EiNemS%eY+*v2j>Z;GZXc2VZ2HmHy`%H{9@1osT zpU8nTUtTRIgN&CJfYB~2hodu-!2HfEFJE+Z_tL9h&RVo+(V43g!pj;;=JXnZ#s+vB ziQdjmQSJ#QU9>l73r^QW?J}KS9bPuPV#PbJCGPE*h~D&gy4RSS&Mt^C@K)EZW!V7m zg2J^4*>F3yTL9Hb&}p#|v>#cUmPv|d22R+@2VV%f>Qz8;@7+g-EYX4#lX^#Q#5ESBsVkecv=hX*Zv+X&vhE=x+zvWghq(lCew z69-q%0MoN7B7xmkcP|1ceJ0Ioq&H!r zmrd_cg7>qXtK*HG5?(gECV11mB8M@aNv{#!v?!k5F%iA#ap={vd$qX50W-c?v{|+W z?HUc4UQEwGD%>~E|I^X`hyZh)Xha|jFBfJl`#}VFFQg_}t z7uyf|e~rOsk_}N2mq@l>#0K!5zS!Q=({m8uJ-Ab@;R1L=p+NIKi?s&#)=GkaPyk+s zFO`VdlYPmxH4dx0v`2Bl?2O^H;d0D?y?NcH_QqtgFPPod=B!z_u5GU?d)+z)!d)id zjf6CK!vfw%9=YQ`zWd!rxH}ZV?(hEe`^*h#-tsjP-ttHyu?h$*KLcO-yLUf9-Ln>z zCvaco&sdz}&1Hqy@Dza48?{Vt7Lc@9rHjt|5W+hIDviMtE^=tC*;0 zf}0n9{P8Eh!{~#^N$=&_4No(?9cfBeko0DnUiNJ@vTBfp|2rn27w2+g=ruYUbl*25Tj zJJMQ$uOaAWw!GD~)fA+#<>auvy%lgYSl%2gM1nzzNz)2$&n0*{6gMrGN99@f?#wRy??|a_^ zcsD=!Ikuhmlsx&$SFbca`3gQl&HL5cC5sm`yo-O(n2h!tEpISN>5~L+b6Yamj_~$- zYEl%vEv;*R{PFEzZ*7UT$8pa~hF8qyPztv_j$Yh#`8L~jhL>HUzS75O$KxQ#j&-FV zPxiSieMw7*T)$>`JMUm#30`;U5}L5xG5JU2ZTOES>ZVF}jx%g3f3;&4!5b6s-sOGZ zf!(`zo8e9EICC}OaFoLtX?eL=?~Wb9^5#A803YFmZC0V%!SI$xWcZDCbTj1ag!Cdl z)EM4(c-P~#0$%j-cA4Q#lRp+K?@>05dCo?=sg0s|-(s6`P~ z9bR(4E?>5XPXfyTZ!#+2U5@a&f=P_0#pBI9VXJB+6TEs&Np$B|C*tjIu}LO)=aT82 zdv8NSnV7Ie6&X|$n%C)YN2(MhO7P~_B-oW-$I{he4fpi%kp^#|8Kv;1J~F4-7K;I1 zL``U3fH&o^SX=~ec8rQInc?01kP%+DTTIx(>?%u9H`zSL88)MdH3V-!hu7_s6Si69 zF@`r0J9QNWfLBh~B)qYhP`Y{ULO!AiTcMZXJ+(-O*WlHS84}VpcsZY!wsigQ6N0yu z;N`+2t?1>YYIn2&MK71=KOk4_2;o3Gq_l|8u-TxFXLv>IcwF0SGQ387vyk0t5h?Sy zY^(zSOe-JREYos4SXv)?rh&A`fw|hW9GS!;hVzULj}TrC%_6*FIpvDw`HZTveCiYz zCN&_uMY2H7T{wxmi$@^56i-|IIU~9xSW*>rx@OLtSyIv39$#Hj0^dwCv6Zmpy?5^1 zh6d6F|NcR)QjqKM&%M{{aOCH^M8T*00fKm-xYUX8$_N?y7%bTqD@>lUDbdIg#_1UW z>IHa1;p{R{1^f2xkA$+_VT&~-Yvbo-n7tGS&dYfEQ+8{1FN)I>ihYgcgOcI}-8U z1@I=;FI}{Y&+J$Vazn@l`0`)sIVYO5P%C4W)=>6_pks~oyIamV8fcEOSxB5W8Q z26AZ+S7LAymK33b9J_s5j$OoxbMSvS2fd{%9Bh+vc$5b;2mC#$;Ff}EP72sAm^XE5 zQkEnunmTpC)G1S)PSCuRkuz`J)S@8o{F{tU0eFKfdcQsI+f(5yJ{pa`@{hNneeSC% zQOXMDLo3D_WbvAc_GqQAnuV>crn4D1>kKJ@OE{gYUnsXhEmjo6s|0> zUJ};=HPOC+GAF0ESBJN2V$0h*240x9WrzmCw|DglhSx9hc`3QUZnJCKU|MWePbu%7 zjn&9JobO=6JRAoVq-m6GFvGPR4Ju|dP`-i-7g+?OckvFcm4I`R za+}R1i`}ArFv6MM!i=VBK6Wu4*gh647GJKn-Om=IR z>1hp=m4Lp1yqrbcv?@&EFNZg$T%1YBV%73V;j`tIwWSTEL}FmHh2<5g7>1BUMzx0o ztzkQQ^u71q6YzrRZE55hd%c~VSV$y&fJ;H9g~Ne^2x75s6Xu*DyjnH%-Yx^I8SwVX zg41ISFWTPTOnCA0&`awpvizRvwVOnejZ7w!sW<*oEkVa4^K|T4T}o%{CaVw#uDo$qu8O99u#y?cnfTwsK=TCFPl*LZ2IenNO)x$ui*trc;z{Q_g#)teEjli(66oVhv0p>v$K=n zJwPD`X?h8(H#@{?->xRwp_prvFq>m!ul7iza{NT~j`V>HcBxtq(wntu)23<)QiyG) z^;|KJ75C}2qnXwE?fz0G*&%mCwa98^qH?2+YW3Pb`i0ZuipmO+ib#N5ePK_K^CB^I z?7|+GFunAc=V?h^bJ32AfRa)F_un3ESPbyqi|8(HfMd^GV{2$=&=7``Frq>IA~*MD zEOFPP{w@LV?ldQIh0JducN%MWnUlrL2Gq!|J|sq431-4;*a=u%rx`ji!d$He>->eZ zgiX`a8SiRVs0p$0M8-OI39likO#2v_;l-9z65?%dTB%eY+dFyYaaDM0fWq zfbn9myaSCl-+XhP)$J{gHipls+oNHuq!$Q;4fZpj@3b!2O-Xh`czx9zjEo)l?H~l0pMK; zu>J!Zp!@be&^@;J%G*HgVzRgQb`SlZZW#UBFS@(c{>p)Yfi`bq=iu{ogG1`zKyjpa zU~q6?FeEn6@dn0YH^9l)UbQjbvD5X?a6fej)>2AKyp7`wqkVGQf*fcu6<3bhI$MY37d$`ICG= z$G{2+Z(P8uZJ}bK*CY@)=-fNL;bnc?o3Zl>(~Fk3cCCc>Yit;mm;`^jtY06Q^!2|c z!Koj9{amU2MLc~5o5HtWtJ*6YPrIZ6mFLVXu_4Tp^ob2A44Vaw5hBKF<{4lA z{{whAJ`sx-unNa)?8y6Wx0*;h+zYuC1It*zTIG<-mZm%jEXkA~I=%k?^qS|wf0 z08dnBPk7wMH@uSG48u$GvgNhPMV79+?_T)z_#2Q06jH#SK|mjkN{Di`dz zu%zh1o~aiuOkJ>G!MqC(A-oqZe5YtZD=A%QD@L!ZY;Lcl{RjWCxW0bnmC?nTZ0No+ zIv4v!uK~Oj-)m^N7e?bWZLWZ~67=pD>f`M|soNbNQe%HmkD+YnOGXFOjfuu&P#w-Q zpp@}m8V!(URLwfsZ7`175Jt%{w#u`iCW78WX$2@gkP*7}99|C-BPU zb;8eJ{Nxy3&4))d;qZxs7Y%O<<~863+L~@9H$jKokF{5(j7xZ%(gs)9#Ps3^U_5lC zZz99)Qo|~dGDu;y|L}*8K6*dFYn?Oa^1m3~<)t2k*P$%D|Iz!;f4$s>pyS?yr3kMj zC+CG1EI<2b>Czu8E%@jK+Y5;E3ooE-!0phm1iFIou0VK~Xz)IC;i1Y4%VsaTU@KY> zPezk8Ra*@>)_%v6(zbNH4!m8xTpx_{B%uUYPX4N7Hf? z8_EWo%VVSRBOq?4Or3hyLl{Mj zinj8E)Vu(1eSLRB1B{^^FdG2g`ds#)fdHn$X9cGb-hQPq86HyO$0WQdQP4*nsdRhg z?$?2UR%$e1`sUPOGqX$AnD_9B=!u46$(Ttko5iVYSROqgXHWE8I=u23`J}^S924e% zf~W?l!5cHdORr^ocpc-ytKC(?`|hWI#JnaK(Dxk-C2ne-+ zmp6~-1$cYMk-cNG(I+axr#Et$WmsN}uvy84@Z#m?9zAm8MS%Byu0q7{&LMagUOs>S zi$`9({Pq2JUw+O}>cN=t(vN;na3^{stUvqc2i&G1ynroOUYNhxT(o@s!UbIOv*)1& z5?%%2U2tL9yghr0R0|L414Zy%DZWA3rsj$Fa9nof^vJ}nB*~$9xQt74 zB`gMwA&K_+ywXIP?5B(wh?9anqZ!SB*Pt5chX$|yTCQ(-BN@6u{_gL7`tBd0eIor0 z@kY#yWg+Hf}$8C+hOfW!-q_kZn!SI=C6rJF#iV=Fay{k&NpG)!w`nZA0{U;J*W5BKuixL?(4oe2!C zj!Z}cNhdIq-3;L>jH~HNeNJUQDPJZpZ_oH>)aU^_ZLQ@uSYG2V3f-T6`l*55kJ0(n zN%Vq*phal{UgB0nD>C3+0b;nNiRm?MIV`gy$Jp6#g15uyZ9sTyd)k_%`|I-Mf5D&g z3td)^i{Z5^|3U%ruSf5;{*aIE26z`z(u05L3rh=tTxfN{U%Y_r7k0ag4qo`#G=SG( zvtc1UpcI$3FNpsLl0R=!fdp>KWe$~5E$mEODLvR`NpBQK`LCjh9cq0HEi=2 z!_oZ3xmV!F+$-4E-Ce(U?*B(;F9EzI-RLXJ&2{e-ds=K7J{?a)!u`Y1SBLijyy3wi zpCgY>4h;s_J{g-fb2Ik}`p#SM=hfam^Y7yrdmggTdW~wQ_*n4oWYVWzccPCie~$ zP|1aoa6%L!$tbedF+Qzh({jc%6TMmf>gKjOSsrJSm8ucmt&lg&h49)zEMV8&_R>5) zB6wlC26HxLT0vE*A53puRZt<-P~fmBRysg}{>`=H=nZILZdWQU<#jMAM4kP=?(# z7+xAQ&SoSU5}5=VXJkI%P0;o~GZ|hzuOX6gC-aAbXNMEP%O6rvb!381qUm!bFO94L zcFpkq@zYOxKsZoZZ=7<)(t@=uT+t#Kh zHQ1)(;zg0o@WUV8O)G$C6Ewh;3zsTC`QhEDc=FhA8z2{7Vh z#0;Q#%QE+JGVE?WA-fY?8ZkMveCF?F&Ko!6wVBuz=H7g%WY;kk-Vkpe?=!$F@-=Jt z`z0m@iCbS6UfvB>u$uudw4Gpkaf=Gh73&qMfjnGfQ%My_c%{cduK?9Mj^X`AdOOC$ z*JydG{r6OvNi~>viDmK)XH;y&Aq*fL(JN?*s^Kllpr~ zD6<=Iwga#BVb$(|DH^g1dZDzc$}yzEPcXiIyQ(@sI{2f>W@b?o4RhJ@InfNO^5ewK zrmE22F5+qA*`-3!&|TO3N;jg|a4&(`edrZ1u1qPwd#|v%JT=oB=JYlgQ(x4<%o$k> z!xHL%mRv9{e;W3DGUfA{EbmseKQ_*_ZR&BuwQ|BQh$E=-RJGVp3NI#i+KHHRxXeMa zb83%-S2NlP+^pLGFEpkSelx*iU`R!GnGUZs<=S*ErbW5Ohd1H&tUUk5Qme%80tDaOS zUZJB)6(Frcs=r!g#;VE9!{z*;h~uP|71R33}mP39rc2C78K~a>_Ujvip?{26kiWpwW*@o?NiH zK6RjGTy`_EUo=ILO2Pcdt(Axbcxx!1(R^*2daiWLH@~QF$9K*f87ZwXl)}@tt#|>ho1>Oif;1%{h<(Z>bJ%O*4KPCxkbVs2TU0Boq!9wg1AZ_7z?kq<8ZGje{AAOTL*1dcs<(-@K*b26G7Zg*j}q%Oo~#{Vn1S$ z+KanS`R#VxI>?X6)&acnT8G0<@CK9ZN);C~jRL)3nGHop30*YC7TP|Aic53V^eAns z?NUc7=P%BkD=qC}%F#vHOG*Kw5b&_On~pI1ktoh3qZ|$syWUuWN)D)l@mQ?5xR_Qd zgu^k+>h&SK@kFdR3}4#C*l5S6a5(JCyV>o&nKos|3nF|{NLnEsZq6%4c=39?FR%+k z;aV_aZR*8Fcc>VjIuR%?rgQ>)!aNd}bTl$jslyv6PQYgZUs)cR%RDb%8VIAwEEc~4 zz`H+*XY=6sLJ?D8CVGK+h;Ck9VIfLg_=Rf%?!23C4gkE-X8Cahc)c{ZFjQC=3Ka&} z%i_gUQM@-l41|O}7cf5z?Yi*(=}&LGvAV+mFHgfzJ9NmzMqw27TKrlCD2Zi8jfM$@ zL#NnxXdHUG7~ibgI?%1^Aa2=r+G<@(@LGm&tG;S5#a7z1&+oUxKTWNDXvE4Bw7RsplVi2GTMC>LR%E$Bpe zb4M^M3@;oR`J#JdB=?JvkzusJzs0^UKmgzS1zz~u?)U$TMybCAcIWe21RomR5%pjU z;oXR*hk-D4=w^J@Ja+`)-Kw4oMRwvjk{0LS$kMOkcR#W_G!l*!;|_(YF;@%~$B4R- z;H684;8Jy<7>1o=ctZ!(gFp~o+7Ez4etv~> zL(pA#97ZFq{43;vni;}R+BtQdF?#&)!`}oi()*`3nzV#6PKTsrJm~E;(rc(~lL>Ec zMh2N#_GTL13FxJI5C^hw%GIL|0#oWmk6prx9{tDRjGcDz@%z#8)_R83((1Zun40

Ok`*Pr|vn=HQ50nXFSs(3~Dqk0sl2n`^M;@Q`ZtQdTeS!5XA}eJEN0N_SYrfg?)e zVt{w)Vs$8Y@nW$hP`5fV_X|}WMpOJ-?D?Xh;ogQX@aW@L-v5M0{j6cWzKUjJ0(uT6 z7+%~%2!K?F^WgUw1nG$I?qqoJ_Bg&Do;q(pb-;H&!aJ<`BE|H(Zl&TPLLU06uxBU2 zdr)2qx*Gni`h5g%;tvS#5MDZfB88GD4pzB+Zuo@<3MIVQiicI;MIDJH^5CiA=FhAD z4_q3+fe2QA3==sw#t7b0H@%pL6B@g*P~u_!#n0uHK2I+uBH&Hn*5)S5>#m87@Lo#a z8CFB|tn2Utf<0~@!V4ewet=i2-}~^xRr-S>`9oqz9$e~E85&1XLU#6E= z=rGeew!+A`hbWRKrWZGgPs4<*wymuVekqSd8Bje0uiZ-fLaC}{K($-xdmV6APOF>j z$y$G%aEt*M!&aqC9rBRn1p)!r!|Ji9I)p1F`c+EN<)Y-r)ILSQT{1ym1aV^%b=U@P z+K??6RR?m_5!=7jR~l9ftGV+lD=XDOC;ZF*eQxDm^|!e|uupMaR2%M9zsU6~_J6B$ zf2JM|R#w88n>Doy1Qh%5Dv}NEGNm2fVtBfqyvmE}PJq{?s(E#dAR9^**GZy0FNj;n zC-O#_r7{YH?LQ3aH(<(FIClzH`J2;H&&ARK-1%zSm^3m876Ze6bq;n9e?^mpp9#?9-!BdP-@ zD^#KbXDc-|=lbEj1Um%72G!p?;B~ZB)f^mXYldI1{~Qd6>>Q+<9c+e)PE}n$e0l6s zPd5_0!MfspdSxS3>r!G`o;RXaHP`eH0KBosv`dHKwxtLAt9sNSzAg4N!xJ@Q5530H z-@~k{>R^8cy#2qgX>L>d?a*E7_8r83E4Z{qUfSGT1HZ@b8QwvDF{L$ft`~sYHrJGb zy$`Cxjym{t)#vL3yfw`|{Ub(r`_*O`Q6>JFYnqz}j@KpDiGP_=b;ty-fn5W|}dK?-%%Vs^kLwE;+$wq5G z8lsCyyFF`|zo5Vtw+DcPQM4i3-d{o4Ru;(=YSfoF7|r)pBdwdv6@EtFLN@g{@WP{z;kR*!fY;7V_^5; zRTAEJ{|npr>>|D$h_4wyX;j6WO_bAZjGO2k58iR37QNS@mq!4+WO)apcDt3{%Zo|> zFV$cA8*#RVa!tIF-%>U5dpSP$8fgJYg~#@R*v2na)H}2ze#v9h8$Hj4y*SNlyPb4hOw* zL`Z~FH7!0?E~yzxh&paH=@9D{>q@!3KJU@EqY3wqK^m%y(7w;}te^Xbfuu99-g8%o zy}kF^Yj1mh_Ork1S{XzU5c!_xo z8SzrxUTtht_lH6VmY{>di&oJG`ht2}Y*wxrj9w4G>+1(SXcW9+*97vywD!1>hwsrC zpl3I=KLAGj9N`$@))uJQ!8hans77uPTulu+yxncs;?~af?0j#K!yDz3(81$K_|XQ{ zZE1s-eu72mA`Ubv8{z9}p{RNG%Os6V(9OR+M&QAdLx-1}-u9M1{J~D|@Ab5R_?oGc z?lNY2`*>H(ZVfvTqI1nuPZJ+B6MX5?7WDE=C4er#yB-U7HWxNSvCaIAmnV(%P&^(* zIyMurp`oG8b*Qj`Nthn0Ef_DJZgV3VzY)*E1=(DS#W5kg z5jI&w+Q`I{i3pXLj6&HQ-dgnQBD|}cKlnj?PY2bIIlT4tJr5q-*SspZgZ(^sR6H&0 z;Iz5=F9QQc;a}jp1H2hyLbPu*ZctthZwu86HpbyAWQ|P7J;O>W4)5vHPVk!P{r&jp zxnG>q>EjNEbAsNW=trz^C$xp5hfKQ{E^R@t$W_us@UmOFB;+qyH(owavdN!*H~}%F zT?e{x;_T8*V^8aoS%D&1xU0c?Ce+&A2-m)PKR4PY z*+B3H$lI{L*C@1#XMX}Mx3>W*N}WY1pAvl?`v-A_z_(RnNV;pW>mKWgtFvvY+jWsp0lE*}xrH@bVUeSEpCBBs(=uIQYpN z>6P&I_xE>pr53Z*io;B6cjXj=mfb7CgP&aA)nQ(DUS)(pSj#hcxz z*nBK_Vo>ZlKN-XkEdsrRxyfK)Aa}MaH`6T|t7hgYS5sZhJS*0R$7rQKx`0vtKoia{ zW<(?;q-@@4@+If7wLf2Rz0=we%Ydr$PU^C+!51Ms36;i2!|;z9v9(tVe7D)ciL-ggZgApz#9nAQA6GQDb)g1S)1j3o4@Z2UPv~2NgQzD zaGqV1;IVfkz0|M5#;&KW$H@H$H%BOv!NVf10N?76fS0%4%j&l5SHq&X zM8u#RJcNC04Toqnop#JLgweeifZg+@Ir-+#u5mI%+bER z6#3wGRj{pK>AQj!cu$+*b!?_EYlq)S+|IKwvvVqL@q9Ov4aYdWV1fI#7^6XF*NI*; zyXQ-qYMc8nBDylAMqkE{0@vnRT5L*<);5L1oniRj43@YPv_lw2Ea_x{Hq5d{h+Q1B zNrvC}6KM(}i-c%8Y?=+;n~d+fu}s0aBS0{+OVJG9r*sgb7J54F-X*JyQN76V$b$zr z-=FTn@jW~&T5x{c-PJ`GyK!bdps`uuF|#WpaA`YSJ$doFy5#ik+Ws~D7I?MoAV(Fu zqGyiiK!FmbX{HUzSI2MZ>cZHDf%(Bef0JPM)6c*Ez!rX<)wJPjNZ!@%jG#u6PCx?Eo(%_H0C*0xu`GfY;*aeel2u-;Wf$m)PvW z=Y+HOJTtr+yhyJ;SZ`5wi`ZTr-m{%&FJ3&rY%W^d1G|ikUBqiM|A&9IO?Ae49S%g@ zNx)(P;oIEY%-Ka?BW!Teh@_bUe0}5-3q|3*c7pCk_zI0{ z2H4$oqqHukcY=T(qk^M3?DXwt)mgJk7PPmwIEO@dbEm@813Xn;isf&rKR3S_GN1 zP82=6jm7PR&wJif};DBh1FGC4AS)Zjq?= zUK#!?`c4E)0kL8Xhl|Eyr(n&)Mj6KpgCCs@%aKjcMXW`P1VIAndTk;sJ|cJ?{u`huC|lU3NO%m$I15o{&y3aF-3bS2gIZC{dtUo zsJE$pK-Vt)d<%G;l!M9gp1nA6R>%hGzGH0oKq_FsIoR9-P0d7bXA{C(TZ_UU$XS;x%> zBU}NqFv6ywFeU+jx0{zd0AEYoz%+rqnX-e^mfpJVjPGNvTC{&H!hE^djJQXe%@oO44I-o2s zRM;Q~$|g|rYgE_M19}PGHf!1nUc2da#uotA;RWo3yG+7+>>Lm6g}3u^oU>vBn--(j zYdW{V))PUE0Kq=L3 z^B9>%q2*%C8M$ z1K2g5>_%s+3oiP6OAGJgKHf|5HrUI7MSKAjY|E2sW3vY1hN{6vQ)AL=}BjeLn0|OuftHv<8C2&`l#x6XU(esN>ffm}eIuIcDt;+;& z8;0Q+Mvi0yZ}p<@LD7`+@;<}%4PeZ*4yWKEw?dl+ZwqfQt8u{dTJ0KC{Z-{x}j(=#^qw|LmDd_bKFx98+HhSwS2`ww(@6}^I8q*pr3IDXe0MmDv$ zFKksdv*o>b5oJT|%cF~HYiorpSzb1i&g3p*a}g&b$q#iJ>)vc(E!&*WoYd9 zI8#XhBtv$cmar)ewJyRdk{_&M+Q98=g?wcKcPV7Zdw%fB!zWMRqp^8#=^-wx{o7sC z3^%dzhL$%An^*64)EgV0!buczaM5#ZxdP_gFedtq^*kN5cYJNrSSwV_=jg1Bg2&j* z;h3hm(=9CbYN4%;EfsQs96OmD$xd09qg`_sT~tVJ)tGNH*4rp>@N9n|$97f2^a0a3 z$~f=&m>o4Y!*-o7NO&s{-nDslRDie6*kt8+0A4@rKhY(tbpUT$iyPprIGdvxU~==C z<>lWjcd^vi#F;dJ`!4o82~ad;8;ebvH&)>n`I>e;dl7$>)h4m)41G~R*T(L%!0RY| z@8200^dfe$FR>hYuTRj+O)qP1v(jrD2+Ha#)8L%|)yp))s!?~~5Ly7PKvBOe9}YL| zt@IkJyU?K4fkDlJY$)9{UT$sxgIw$P#}Hn_NcemOBL?ySyR5CzI3LI>^{wFEhe~R< zjDv0fJGp1o@c9mX&05$R@I#GaB2m^ z(`C$;dMo!p%`OKg+}*v8`j^S)si)L+sL}^k(b%kR!u`|TB5?3bxVN<$XOX8EOx9^K zuu2}+L&m0S!@$O@L&hqARZ@q$a(Gdupe?RxgfHCsDK*Y{ySp1!4S+Yh2c8aHL8;9F z-tKmUw>!bd4YvRQAOJ~3K~!tB)f}p10jV6`YK-{AZ&rX~G*pxVyqO^L&-D_#-Hq6j zq8sC@ODfnnzQH$^%U_tw<@E(bT!r{?y zGyE{A$xvtQ=B5|$BzQN-w;P3*0t*#j@J0yU5H(If{fyvEz)~d(%;|0fkwER1d1JEw z&VvzZk?k4)Yiq1GkKE+F)xh#bhQjrCjZ~Mh9u4^`>IN_p0gmb$7#PS=R7x})h6i}K z%Z0s^T-byaz@~mLoppQLh5k;1eGwxPWx)f6(TqVe?d>d3T#Ts4yJRZ9Dn)*6^C~y`z%N@?v^0F8LEJ6}R{V@Y<^=uN`*t=rp-;LpGDIM@47FA5AHNGo@5o;SDtcm>k~e$o&U53EogY)ZUEs26UPMy&VXzFC416OBdZA z4!J!o2rpRP?t#Hxf_K#)4r4P}Ctma}fY*PP43U?1g=$K_iqDLRB_Bn6wO4O$HuRqs zc2~&dRqy6zcYkm1rvlzqgtu1&5BTM{cK0B=j1kGBhoNJlq-73vj(oemoX_#p!H z6C_zoPt7Vj8BbHNjSY{-;XUK2tD%f6ZF+Th8@jL?5FP`5s}_W}y&SL%#6)L2djGK7`*NpbZlKf8sqfp-br3P)($zpoG_pii6mN>s_+z5PsqAMjo69@P2)$ zrLnqzMmtvuXHQX~GYZ}i&PvtZx?!**_@NHqt#AisFkp6_cfr5F3Y)JETHwu6FNsFT z$Qx2>#0`=)qFz=Y{#F2Q1H$WV!f<809`G}F-=Jgcm+*o?-pAo}Rg^M#L;FECbTx|8 z^93E@J$bSSy*j)me7B&N!+U>XR5!gq?>WwHcehq&lfB-^m6jGc$qC z+}hd{btNbm8{T|@^>@G@s{zpsBfQ@H>S_jr>?#H13(Jc>G0MHVff2rWEP^7wA~aQs zS6JRkpBGlAar2V`gEvt1r$6BVHxJD}SzUbs>C;1-#!r7rK8GG-6IT@qQM~A^@A|lr z@9rI6r9KTD-u(pcd|?%8^Z|C(3|=-?0XwtRwWh8DUu?^1@V51%YIMQ=tQ!TCIKaMM zx|?NRH{k2h)=m5#Gpy~cWtE34^agUg+*AcCV~xOS67W*riixiNc>!;L;LUf#Hy#Ig z+k`jz(*pf){z+cmYSYJs)j~DBJJ)UclQc-gRPC&n=H#Bv3uPv zgxBq=F*aaBwdau6&u+OcZX&s3%;R$La7C!FB@kW^l`Lom$5Rp^h-AyVy1PFF?cU0F zjfAzB!$b&6ZExZ6Hh2W?0b6y6NXHCCI&ydmehMc7gPcSmQW1bh1;UGK&>h#!hi9mE zp>_Y-@cUdm0;jrVKfY8Oo2~p9rFCcA`Z3xVz5CVaB|~m^DenRU8<=(Hr)Pur__wQiM4c%qDCw#X^qFV(}EG_SO)ox)HwPt#40qG_47cb7t9ANP`wYAM> z$i3CvJT%1O%);RiwFLvdthK?%yQT6#T_10k;08w&OIv*=Z@yp2#9 z;p%gdMw83912@LgdmTM+PKML!I( zL7WtZ*M%`gsEZ0Q!OQPEx7#0bHwDRA5emaDpo;quXE%@Mr8MIX;fYhYUa+mD9izh9 z2k_g|F)%j5qaB0BBEgekr`O-&4^iV>u&o_6Kkf=op#C%bTezLs-5Opiy;gW1{QiLh zy!Y>u@s*>ONqD8{ednEbRIdubt3}kB={;vo=g*Z)Ow9c2Jh026Z)%}RMhTPxUXXBS zC%n}1_?rlrS>Fa55P;gySR1hN>4p+)xPT~w7q=n6)X-MRG4}ex-~ayiJ39{^?98%? zaD9Ckqj&rJ&z>C|+1LblY3Q7K58XE*H-H%yZh`)0c7(M+f2g-DMD4AeUFdrq@U7F9 z4NqGLt|o&AM|;AXD;!29FsR7yZ}N2c$<5?n-2`|=gH{uL?+`l-*h$2=AAf|-PPZr| z4fej5P4&|f>XLr80SeLrIvQz*Q0L|vc93Yt3R|ARTY*Km>-_WqPNqMEZEk+Z$Qt1W z*w>n=@OKg2cq-U;GmP+taqz%!*iZ2C6UAc;$l>(ZuipjVjJ^x(6{5zqws2b@(8jj5 zw0NAzeF}J=j^6tdCTDLqpH{n1!TXK~Oy@9aBkMW9Ywtr`GJ^@hJy3A~)I(EeXH#=m zGlhRFMbsLo94vN<&=sorhi_SrK*}$lpROwl*6IJBMuYvo^E_wkS$G{5-Ax=8rf3w}2KSadz@XFb+1 zSNAWkW0cvk>z99oBRu&r67sF<*WYS>?fT^x*??oaf!R)XH^<`<8k zZL~qadhI9M!h_Mw(-4vMmPA_Mm4)a*InM&Gm0bnz1x!|Wt@JK9(ffOjuNB^N=Q+IB zu1R>03Dp4U(zR=v_tvawx23mae&*u8PSoOnV-=mDT12Rb8z`S1F@wVlni0Pp83&mZZ4H3#JkRCSEr zS1zyY-P_UAv2y)+8abNGE@%dRnP9b;(B?X1#P(D%)3dT=;#%9DvJxuAE=O0+@LQZ+ zc>~zkl?d66BZtwq!K*za*sW%cBg5NBY9#S}io;9Ic>2)(TfocmIHIfIy|6QQ@7;4U zz0CSrP4Bs%3wEzbc8>|w@XkBI^Vb~Yz)tBcxi~R1F<;v`xxU^R_NP*vAa}#*q2}hH z6r{<{2#)07k0dvYWE|*CLrMa+>4ZO#%p_>g5nnQ$j*-7?BOPZAZe)4iefO8Y{4v5i z^h->Kd<1V5!V9MNdyr`OXrdRGeEuMZ_j^D5&Ub!X;jZ}cj~jmlB}jPjLnCB-BcLWK zlQaaZz$h%_1q^^aXLgr!QJmfO04qo0?GXd;+Sm=)jw55#ZP+P2tL`*UjcNK{Svs@c znriS^3o(Pa4ZL{J7Dsd;Y3K@iw}kheBfMmNP4HsO&AAUayw`#P-eY~j@B+P00k4(b zk{N8IcsXx`YlEG2#%2@L)KW&Ov$Mebyr2g1IEoW_$Na3dbq~C6;#gxFl_9Xa=($}* zgY2&~HY-{1ZYX$pg{9A*_xI7l|MC{XTm2%z`{MU-)pb(8^}7#} zK+Ee}-$2Wo-o${ijY2w^-%J9=uo5$(WJu>XGR7Pyw|UP z4_00Nyq+ds-MyQ^`wCEc{rVfQ<=xNU{d)lS-CshUUlF_~KEM1cSiJ)9ic4Lg!p6mP z4st~ib_3L>(WG~2lGJt+zx<)D2Ipt&_G;|fdhRIdY=&2zi^cHDF!o}n+a7RG4IbVd zQw?bCrkT4nXM=>-Jgjw~w`A8^(&0K~N^I^sa zI?R%=D>k|@&AWH+^9b)spC1-i2;P5SvSH=(jyLx1y}ZIlI%n`6M0h`6`PR4h?)}cn zceG?e^=WKxNhXua%~uqRq2_Ss0eI}M8?}o-U^j35ibScxhH;I4-nhmhUn+5; z6fZUyd#QPtnk0-3FR#y?L3n?D=0^lCO9lqd)DO_dn zPVi>(>#Ke~=tvOZ?H;{E@Ls2MnZQJN557q7*1xO4d-?M9%kOe{-JYWi-Z$QWEBwaa z33%I?w*k?ta0jeH6$$X*sL4`6S|==#kMwrUoo|^4Qqs2-y8#jNC=aD3nzXX3Y_nWr z5e!3*5` z{Hz~wC_=KKlU!xjSHAr`ODikiMtHxCvf=sNFTN-(ZxvL=K7a9ZW}UzN?Z(D-2Jh>n zPy-y^=L2s_%d6QFVVU^+>vDogH9?w{T_p%A?Ci=sf&^Oyi5p%uU$30GM+K!~Owe$@>6TI!tmiP3yon1z+B=@lm-bb@$dL7~A_>P}Duf{dKcI~{Px6ev% z5tqUi^dh{uoZnwNI%?#bM^l^F^#CkyjQ5R*gd!NG8;Zp?*FsU=9U+gFmx)~7NWsGV z;uQZtgO0p|@Sf>Jcz<&8b4r(+fAW)+cVGL5U#|Sujyvztn)Gk@ zl)mxC3T`_32EofN@((Ys{ILCpmvQ|H!CTQHW+s)4$O|VfvTF-=W#%2Q>z0Di;(1cj zREsIooZwYMF(}cXhI?R1=2700b~!PngqI};c-1t=#o=vng4dB;9p1-0Z2>Q&-#O?8 zLGJ|b5+oyci^|@j4zc3&mdt?PQ%H`V1H&)*5=^vrsmiFZg1s**PaJ^`yy(I zy*<<#+*4om%HF*-HSTYf?QIXdxgWQ@5ooO0`#S&S8~+}vZjIk!rR=S+*&cgCl{;Yb zGswN{RiDO269;;pHrwv9KqVS_wat1m8P%c%H7Y-Fz_=RzqoC0$m7lqeidKVr)E=KQz`+VvZWqO@u@0nsA1t)q%yGF^AC%IfMIe|?T z@puwbJP}XE;z{@r!!-#=G2DWK9mf*!SS%KgMUx49PbT7V*ceYFW6=a`f)&yD^oK){ zL~6A2C;wYGG`Sf0Ni_Lk{3nq9;f=^>D)PqA(Byj~|M@?k8u{=4{r<@R{_p>O3er#8 z0&g6~Uw7$%#rc}Ce zg=gYP8a!DJ6n)~vFdkXv5ouT!BQ~iuEtX7I)c#{y(JYNw%fZuUX{?c-bij77F?BEj zq1ZM&Hhkp@U$UNcul+=*kdI_~|NSrYv|vu}zyJQ@$2*31_R*uqkA7DKUf!odnOiRBtYW!{+Am$!wue$iqSw2ieURV5yMJ z$}}jBHa9oN3Yz0-R-U0eBY7~`YB-$|U?Cnv;te`(7jf08AS$1`{W z(%_PWmnFF{tHG)Wa6)jATFHCS=*&od_4%jskWGXUab1B)KvR`_I>Inoqa~ZtbTa$_*XdF3@=*){3^Vr zIJ+mv$eGxkUE=rN+-*C&>Vze{tZXN-!_J(NN5V@*>s&g(YbTt;`&bjUR(LhtyB)k% zdKc6dyYXE(Z>9H~72dwS9n-51pHl+hWfs%9TskxVgeqW#0z(zV0;yZzGDGV#SZ~8D zX*9FAUz!-tqPWN=;_-MQ9-SPWgiJBC$H~NMAzMIWJ3To)ory<7)6*$HH!;1KyZDdC zf!*IqOQBsiu^@gJMSxv0JiI4CKS!`6+7W}cE2>iOUcfzf_LfZ;Wbs+tnk`RuUXpPGi|iM z`@R^9>{3yB`*uLD4c<%VN-`(}o_vumBr{KdHP8o1GP3AoKz)!YFp5z_FlC!56u7L& zqmsaSTsq0!Y?0B|U@j zL6>dGWTE608M7NLWy^%#Juzo0%FM20aM)bZ@|?CtlfVuZ&Bm^S1eK?tl)79cZxN}+ z8_cuh^m@%5vtSH(qBl^Jo>mY0M@aKISZsN;j>{`SZ57hyK4Ay@I+$^vD^DMg4gUS`)Wt?YUM)aJbM^)$yE~!R3@^I6G9VkCJOLw$^|cJT7xEdB2AJ>-YH(>J9FWAS8Z*1V zF0;((STY_8zNNC`opq`o-|ooB{lX8r0aGQ`ybAFv(TQR7srG4`DfB3%o1^RPpKr zC8TzECD`8(UK_pdo9SJ!g-tFjsCJ4;o9WeCiTgBo&HgeQy_YQT0+C+;yP0GXwCf_a zo}t6HfJzn}3dv+%wm)RjBzvhz0$pUVkT1kC*e4UJYiX#GrK6GN$k6CyBs?1Pr6SQ3 zx_ML6labhTAr_rX&4)s%#dPF`ykdj$QmSX=uq|nlK^ROcyO6eIR~2TLAF38L3nG@I zWHF?0ZzwG*&wkFvuJQvvv?*oPaHc?=lbXct=2q+uOO35s^UUxPzYg#Y3i&hlH-cBt zYlm0ESJLR{F6-+nMz7WKTH(#)o-C#psUDWkCo{ZemO=B2?P{{&jQ$8$ntv6WDl({b z0bURcaUdA@O(&Dl$yj(a5}r&%BBP5?Lqm~}0qdKJFQ(>aLb241u2H)Yln?MF^{{o~ zO=MS&wcDBxxcKFF@R8lKw_wcnQXLEp@0^)kJ!$nYyWw?U*VKw(g;$-v%~$xe@GdQh zJI@5~;NaNb0A8sZ_;+%ISI~Q3s)1iPuxp0*+|$4d9HQW*kOuODWzZFtXWO@V2S#L>f>8jS$L^ArF8 zAOJ~3K~%<)AI8T23;&6)C@~<}9ka1JX0f~YODRleHFgn5XX`6%<*@ybDxhIjR>P(o zV6?;QAR082D$TQ8#RcfSxqA+}cm-3X5Gp1_)`1>RwjRJUip5xien;e9k~ zN=4um_{u7qFui&!TXEC74ZCQ0zbHHbd&>M^P-TOv8G6sCw#I~R99z)xjAyLgk*5s# zMGqNj2DG?|3^ANa4n-o()8SvBddMzLMyJ!+bYwC$4fV9q>C|+3W?*0OuC*%7bY>3c zZh=?3IqdL`@mp_j&;l==#?!!S_LX6i@;30!7K4}9+_VNVONFi5hVM=lFT$H$TtpuN ziCPl7Of6*4$imu05(Gdj%c9MYTV{2E=|mcXBmrBnz8EnXOJ+bqe25jc(Nt#yWJ5l8 zaR_8@Dgw#@P&*j;428b}yCWnbge`1i<);buuuq6pfxpB%`5Nlzd~!47Ixfx+HT^=f)Dr z7&;p=)XJ95#FLZL@mORiG&&UNoS0rrO-=&4(eQL38=Dc02`9#uc_ibUwcd0^R}K!6 zVMpwm2vD@4ai@qG3%ii+{Zp?rybf+JdtrlHvFp^h=G1-+B6iSzlP*~Kol;cFb*4L^ zIJ{%ST&0=deZ27K@lN2C;lJC%3-m4&rFY?XKR@RPuj} z81|${pqfp_X;$o5I*qMuAbPQvMLGtF0vbp$qyb^v5uqLm4Rwx=4u!&?AM%SABT!WX zfV1-#MeNMj)G|3&7ZxE!dTA z*+GZenr2aDz5j}XsZ`|-iYnHi!`UyXgUWMrC{5;i=m0M|si)`&3SM*_Aer03`_)%Z z39kuX6TFW;QVxepnv>TV-aeVMsEr4LuYs~@7&U$s;~jO+^lX1?^l*&gI9N!Nq9AU%^izAQmXgdH8r7Y5tqYG z;ib+IXna|Bh;+Ju41?JPEWwzfmQ7=w4N?s7&E^wA-Nxfw^Cpt%1aGOJ|ItWvC>rtm zhDJxHQ;VRfr&H5$)kWtMK@m1=SzWEFqBCpjQiHke>=v=RJSmKpHFo)d)peJ)t#{Ue z3WbcN@?O;N@;G(P$Ll06bocF&wRZwfOWzz`c6>@-3A6jyDtgVXGJD$6)2q|F;7ISI z3C)4)#IDu!?ttA(CFuX+Sc;?t;@a5N zo4Hhlh(D+2oIS3+_O1$gUj+%)pdB`I5ZKb3?UrG5&CNL&;4u*fq%|Qw6TA-}KHe5y zCvcw*-US}$U=h8to0=96+4n(T5qfvRF35&LhKHNyspA9~;7Bx{NhBy-0nyE%C`d$+ zUU)%pk?jQQ44;fA60s;?ACHlQh)*X!eD(OLQ|E$UsQsN`kPY$dMc%i&F)$|}TC!iG zd$(1S!)?m9WHP>zT?fe^YJR3-Rap6$J$~$-5)IP9t2JObd3dKp1J>ZIgW+{DsA^wp zzX!ZMB)q-atpY{fGXpGI$xNsBH-fhqx=uB=E$C(D*X(aNBbt6SUa#FMYI=8Oc~wsd zayn$d&c;y4#>jb)<_*8->y1W3V1R+zOomz|$kHOd0CN&*ZD@U`KX~=kF>u&X(Y)y~!njmgPCvt+_o32ha$uiXoOMR<#&>r`WVbkFjA7A*9dKTCME>e>gl zKDf0*dYvsV#oCCT68SW*q9vggMm83mx1@im&K8Nr$mBA!3&^5>H;D_$4BFk;jsJF> zrB{!Cf7CxZG?_gs%&vzXddo%7y{B~_l+Sp(*#-WbG=tnqEoS$q?`_e#8YOu>mxqUU z#ICMT?d)m{ZQZ1xB`3(`4i4}RJHe~r=>)GuyKe>WmgqXcYju~I{R<0Ldbt z^k~!yZy%uh!L3_wJ+p8?R~ZY?%`+p5&1U%gkAaCr-$90?K^kC2yBmqZA9*6u0`oXy zHDVxc#wg*Tw09lfp3AiFoZZp`J z0C=g+mZ3F_S{$DY;l<`R>{NO6GhujNJ%07KJ%U}WmDB-Uon5;+)ir~X0;;ZNk_;j_ z)YY^Soia{2)zSEsU(!T_)e_i?7wFY^emdJ*!bfT%pKT__Swu@3i$;;?cqYl> z%+qnE8)DemlB_f}NsuZA4H2KeDiSGP9985rRL(sJZ?B9YaPl%}>hy|(*@O^cnCLw!a#@T8B0pymQE}NFM=@n&Q30+5JG9s$xre?eG%1 z=@@H`h$W&i(dH|?4G97p1I8jTsIZZ@4B17=kj@a;nH&E+bM>lD@A0dj4W6b3wZ_Ik zuN((NV^^`HA?YAbm0{KFu0}p*cZXO0f9k|%w5Pf2d7>rxY18=gpTF@@F8q}(uadr2 zcp1GC-`T!*G&k9~4}|3{>g?T)-V$mOjD6eIptfB6nGo`u<=j8Pw~N$>|SI zp|M49KcgBOEZpoB=&HJmLU%z1gWns|np_2@vs1&o@4F@$miVAJIDn_UoaK@b7^bzg z_0`o?dif8Sd@*DF&)cN;-aC}}CdbGm7`%_39$tGhg;hJe?+mYyzK_N=6We#|)-6fz z`C{;HO>YS^q97O&2^QYS+kE5EC{i5ft-g>x_{XcSMn|ck4RkMhCSZ>!u>KoT3P9{1 zKV$zt8@VFb)qJN*Hgk!i%qb7NTsJ4Bfl2O4OYSg+!}2F3zV$5gOtWuA`HVZjm;`3+6)BlP-O;ayOY+X8Pl zO-alR@2y*xcIa_9XMq=gyl4WuzQ=i!FI^3Q7ql!;&75WN>>1^-UgqBlKNT=qAAt-KQb3do-JlXS3M6ouFOtPR4h77GbUg zR9>?g++(_NtTP=rm2H zcebO~#;yi0(R)_+lW}-&(cHv46%HnPOUTTE8OU&RB(DpuNylOt@`*u(jA~)WkN@L8 z2cxJPIJ!4J{l7jVoq+8A1^K=D>Z#L$Gc{tP8tYr@J27=a;5e*I?3_bO+tQfMF8Z8i zO$Hp60p3iqOgWY+>grsjyUN&yvNAv=laGy-x%b;$P`g|e6|Q_H2JrT?O>Q^7l4q){ zoaL;*LEJJo>MAfZ-^LSLzL*Di(@9w-l(JvTO)0uoCR)!yT;W@FKcr+*hO7%t& z-uKu6MB1f!$_#?{Wo@flJB!lNrhGD<&m?!1ntg5MNIEf#FRGbo!#s%FHgzv^kEfrBLm9@6}z9vv3E6vIi~&e0;z?=%O-1S zmGhA7-+!3$lsADS$Yv6C6>@_}tU%8M!J8=E#R~R#a90n$LFp~~J=N)K0{-H_^nN_Y z{j6M824rZd26$1_R(ST~VGr}--Q^;+FnB8}aM&^^8%jT{O_{QPe;dL3;mf-`xD5}D zM;9f*InEHgf~+0G%RR$5VW73E+IIMw=N5Rx)j{{UTepQ>f4l_nfNwbyQ4>r9?52-X)t@P z)}I}rbo8)DufKle2xJlPLS{@yj<)iJBT(S$ufP5_fI*g*cK_K?*ykuM9zM)6jw8Iu zc&Up@JxYaA7M|hAd^VoU!a43ga#%}8SZ2%(vH{_B9cBfy^E?VCh<}jlsIdW(7~o~+ z#f~rY{_N=Cx8dG^`{(6drp+uft#~GQRkH?7*1DbbeY6F<_k`lTc1xOGzVs}@0b~PK z&9M5FOr)c%XAiHoA-d^w>Nmed9|HWn%HchJ^`Fl~0O}7v(tNwzMGo5u54snPwK*dIUp(d37ztCj@lN_NM- zbY}M<4~m>s#&k}@)DGv;(y$#}ht>=M@6n^Hd?a`HO4ubj|2Bfa;Ef8arnAeeqYm#8 z4hOxP;Ef}^3JpDtGufcSt0jat8zG#G56K zAD8q>TRL_{wW%p~g)LrQzOC3D)8xU!DdzPZydtA>lrYxV)#b1#9ApF3mYOcCvQb&F zS80}y@t%46Z2@ojVR50=bDKSRIlL~kmq%Lhdbcv;hMEzwDcJyH=(L5uUZR?qiRKl6};k*t*!Dbj{Xc@PNU{U*>Hru z7M=p$6-P8%t)WEtqrz zMe$x^qEKDEU)PZXnB)x z3OC5|@<)npgLiIuzj*w`7Y^_)+@C5*KFnjD;6C>ZddG0! zoWu-+^EjDaayC#4+Hp%_?EX|*$T1c7qInsXABvSrO7707xET>s^AMvVMbcbk4`l?H zwq4Z&8N6LL(&^QFHjymQ02GA+Ncm`_!2|HdGx@}%>##{O999*cd?udFCjnkn1!A?e z*BNJT=hJa0m{-_D7@+e|)@Do1f7p^Xk2*Bj^`uu*cw-VO)Yip}m!s&tQ z*?WC`J+0ckn_e(gRpHi}3Yp|Y?7iBAya8>kOJ)^Q{%R9M>oZ-e33qv^G{>+N+_PuTbERdW5YCufSz1bVHTD%n3Wm>k<%8%cB@4Md8 zGO<^gegd!#H~UIUOHE~q;vq{#&`3opEMNK5-zc; ze65mcH)prLr=!09pkU1H-iFf?=*1Db8?X@LbtE{;@VBTV9SHX)3@WmUFp}S|# z3uWaBUPb~oZ;7i;e-?PltnijItF{X>V51ejl`Ff8@K#m=JSCDxo=YY;zCYqK!z+$L z?$ZlsO`*FpctOr=ORojq_6p8x10*!O z3g3o?eJ>OEkYA-o`PLvWelf)l!kG1%RI8UaGvhARG|Uh zE#R%HI_Rc^lxgg0=c~x`NOoP?)8Ec6q#BPCyBfOM&*HAIz)OFXoZUU8WiAP?w3LLf zx~@Sk8=T2Ox)r?T!kp?H)&-U2<@DO&CD~wym)$MTS!IJ&;pE?+w{$5z7Yv&aTs7QVoNw}iJ`z+37Bud6t`W!mW!gZHFEa)9?_y92!2 z;@fJbw?(mfXx~0f^`a?m*s zknjo#uSQ{uBfCm6xD|!%j_@`rbe-#REhcuQ<*nR9^m@GtUbxyXqipc`IlLxv1PDSl z=%S~*RlI8UQo0SN!%c^?mPgdAU?WaIRJy)4SU7*Ps3%tZOSzcM2GjXme zY}z^MU$Y3jPVDXw-ks2UxH!CeosHl%n_eARORbII&4A|hc@?`2f8R)^6+uOSN0`E` z@3=YAQ>7(@x4vVfLbB`85OYMg(V5*AlPf~xb#240$L(ohwzu7_;4MXO1LIe~TMGY! zU2SN25nd;7iok2(+zDPi6Tyr3PkE6f;dQuwiojcDJ_i%L<=ew+CfEw^+wH~4eL8rz zruS%TtI7D*NqU9N-6z>CDtlkD!+Y*L2IlyIUEmi}L)}50P)#ZHFn1X(*E3&PeUFk1 z?nZ@}_CVLjtC2jqH4ho%+$tSBj&?Vovx~P2;@jS$!}~JUI{{ZA8%j$N-$FhQ*Hp>| z>l2$2!Rvon!mHE;XMw;tR90%YX&l}Ny6D(5+aw!Yd$AUAfB2)JX`3LlMhsx1x+3w5}Okj_me& z#BqULUMRrp_myIW&ASKrtz>2w{w33uu{^By(?i|C=qY#)y)5;l+349SZ01DpGR<4+ zkb$vG6*h85I2nIoc~SEgGyWEMZRfz?^%b$aR(RW6Y_@h=(`$!Ug;#71uZC}%&BeP< zvJ1(XUaJTuc(KBUl{7HD-b3|O_S8d(b4V9XzZF1vr7iO`sL{BYSGwj+fMI# z)RDzKA>c*93-J0X3EfJBH+^E7 zOn$PzD!k8a2XBK7UYT2i7k~4ohWGSV@D`<)!>j1!#Om-i6oFUpx;4CBZ*$#lXLd(M zZW6m4BlkyodY}f^gAEfkPU!Y3gQ^=-WlG)d?by{C;3^zlWKQf#c=w^&^^(V-lx%OL z01Hus7b|RnoO09i)CG&(KC_><)M}#{7zD42;pkF+S~I*BWO!oUhDt|xx#k5r{a$m! zg&E%1ce$U}PH!>Gs}Po&S2$n)%J3G&_i(EjUjgqvO*m-uzF;@KcK-vD4M~Exw!tUV z?rxpojuAljW=B2hgpmg~@7x?=6}N*29cyY*4w!U+N6FoQqr*VcyqcTAue4+ID$8mP5``s?r!Bpz<34W zWavS5N#fqQbMxkdJ0qkZSeqJikU5)2wbrA>Jvhi+bG5D*yQHAadRJ%H72w{6@;xt+ z<>fWD(n72RUe9uPxomKvD`<*sz+9!J8X+!CHc-==37b-zR4-OltD!M9aQaNH)rZV}UKigDQY60feA={yMoth zc}?EtQsHeVGnXuHp^sT3MTup#%v^!qI;-Z@&*SOfZF?4Y9r0~#HRD?+;Fa|5+o!>M z$SNEhoM!}YGM)(4)|tW;cGpt`4*NrjJ34xp$AP&V4m!3=II|mYjv>?KZjpd9tz?zA z-I?8*fCg_ly2`xVS4Pu*X8|My+xjfYG*(e2@N!1u6)-X_7WUTG_4dS9}9(&0^}2Rb_H>pLX6yU9D&BV1gyjQPY?cg?6nBDr0k&zy)twJWKzSXz| zye1l*M56c%_D>SfIG zVug*_G7c!Ul~LXUeg?081gTH(-G?oi1aE1n2|TgLTi8mtTw!NWs>MR&(-aa}4BpZ* zg4eHyL<)Gzb^xzU^xDuR(|gp)?+)NSYNq#a%RZasJ)|{M?7;LYcon+ToYtWNa7^s> zj3~vh19r9NyB7awqEXAO*XtbhuI4QZ=yo?C;PtZ@bd@4JCLP}QAiN-ZL)~&~(43hn zY<}xn+Mm7oync|qA?ra@QP{%fv{fdsytWK}xh6uIxA;h9Q147)3!5)uEkd2N&vXi6 zwK~Q&@LH0M+><9y^WOGP39nT*9M<7gi(EAz$1iL{uYmUyhL^uc394ot9j8tqsxQ7+ z#n=-r2pIduIoS!)h2jVQu8QabgoJWd_of$ z5I$)Vc8HXnEVW4?hKFx2J9L@ENlj!&O>J<7!P~0;rYW~7V@|slMA;?YwX8lqA`QQo z*2aybVI0%?sEpbob@b0t3r8u>12WjY=g zOVss!oAmL=FW^vHjssOvp&9eIkV#9{XsG1E`yyHUSlYS^I}Ahe=+5t0(t2XrldUu6 zy*u~JN$XO1O(ZpBFHTjl{WuOKyfZr+^M=pm!vt Date: Tue, 31 Dec 2013 09:51:57 +0000 Subject: [PATCH 066/456] fix examples link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 86b00fc..0ead6af 100644 --- a/README.md +++ b/README.md @@ -137,4 +137,4 @@ You can find more information about [ScriptCraft on my blog][blog]. [scr]: http://scratch.mit.edu/ [cda]: http://cdathenry.wordpress.com/category/modderdojo/ [ytpl]: http://www.youtube.com/watch?v=DDp20SKm43Y&list=PL4Tw0AgXQZH5BiFHqD2hXyXQi0-qFbGp_ -[ex]: ./src/main/javscript/plugins/examples/ +[ex]: ./tree/master/src/main/javascript/plugins/examples From 0afa5aea90506c682bbaba5b2bc752216ddcb534 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 31 Dec 2013 09:52:59 +0000 Subject: [PATCH 067/456] fix examples link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0ead6af..353a5ed 100644 --- a/README.md +++ b/README.md @@ -137,4 +137,4 @@ You can find more information about [ScriptCraft on my blog][blog]. [scr]: http://scratch.mit.edu/ [cda]: http://cdathenry.wordpress.com/category/modderdojo/ [ytpl]: http://www.youtube.com/watch?v=DDp20SKm43Y&list=PL4Tw0AgXQZH5BiFHqD2hXyXQi0-qFbGp_ -[ex]: ./tree/master/src/main/javascript/plugins/examples +[ex]: ../../tree/master/src/main/javascript/plugins/examples From 686285dcfb2e549294b3595431a980debe3662b2 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 31 Dec 2013 18:21:40 +0000 Subject: [PATCH 068/456] Added 2 new example plugins and changed drone so that public extensions (box, sphere, etc) pass 'self' as first param to Drone constructor (part of eventual phase-out of 'self' variable). --- build.xml | 1 + docs/API-Reference.md | 45 ++++++--- src/main/javascript/modules/utils/utils.js | 98 ++++++++++++++----- src/main/javascript/plugins/arrows.js | 7 +- src/main/javascript/plugins/drone/drone.js | 48 ++++----- .../examples/example-6-hello-player.js | 36 +++++++ .../examples/example-7-hello-events.js | 83 ++++++++++++++++ src/main/javascript/plugins/homes/homes.js | 46 ++++----- 8 files changed, 273 insertions(+), 91 deletions(-) create mode 100644 src/main/javascript/plugins/examples/example-6-hello-player.js create mode 100644 src/main/javascript/plugins/examples/example-7-hello-events.js diff --git a/build.xml b/build.xml index 31ff0d1..e63da8c 100644 --- a/build.xml +++ b/build.xml @@ -74,6 +74,7 @@ + diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 352f8f5..dd83f87 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -648,10 +648,9 @@ The following example illustrates how to use http.request to make a request to a Miscellaneous utility functions and classes to help with programming. - * locationToString(Location) - returns a bukkit Location object in string form. + * locationToString(Location) - returns a [bukkit Location][bkloc] object in string form. - * getPlayerObject(playerName) - returns the Player object for a named - player or `self` if no name is provided. + * player(playerName) - returns the Player object for a named player or `self` if no name is provided. * getPlayerPos(playerName) - returns the player's x,y,z and yaw (direction) for a named player or player or `self` if no parameter is provided. @@ -659,6 +658,28 @@ Miscellaneous utility functions and classes to help with programming. * getMousePos(playerName) - returns the x,y,z of the current block being targeted by the named player or player or `self` if no paramter is provided. +[bkloc]: http://jd.bukkit.org/dev/apidocs/org/bukkit/Location.html + +### player() function + +The utils.player() function will return a [bukkit Player][bkpl] object +with the given name. This function takes a single parameter +`playerName` which can be either a String or a [Player][bkpl] object - +if it's a Player object, then the same object is returned. If it's a +String, then it tries to find the player with that name. + +#### Parameters + + * playerName : A String or Player object. If no parameter is provided then player() will try to return the `self` variable . It is strongly recommended to provide a parameter. + +#### Example + + var utils = require('utils'); + var player = utils.player('walterh'); + player.sendMessage('Got you!'); + +[bkpl]: http://jd.bukkit.org/dev/apidocs/org/bukkit/entity/Player.html + ### foreach() function The utils.foreach() function is a utility function for iterating over @@ -707,19 +728,19 @@ and put the code there. The following example illustrates how to use foreach for immediate processing of an array... var utils = require('utils'); - var players = ["moe", "larry", "curly"]; + var players = ['moe', 'larry', 'curly']; utils.foreach (players, function(item){ - server.getPlayer(item).sendMessage("Hi " + item); + server.getPlayer(item).sendMessage('Hi ' + item); }); ... The `utils.foreach()` function can work with Arrays or any Java-style collection. This is important because many objects in the Bukkit API use Java-style collections... utils.foreach( server.onlinePlayers, function(player){ - player.chat("Hello!"); + player.chat('Hello!'); }); -... the above code sends a "Hello!" to every online player. +... the above code sends a 'Hello!' to every online player. The following example is a more complex use case - The need to build an enormous structure without hogging CPU usage... @@ -739,7 +760,7 @@ without hogging CPU usage... // assume this code is within a function/closure var player = self; var onDone = function(){ - player.sendMessage("Job Done!"); + player.sendMessage('Job Done!'); }; utils.foreach (a, processItem, null, 10, onDone); @@ -773,8 +794,8 @@ The utils.at() function will perform a given task at a given time every #### Parameters - * time24hr : The time in 24hr form - e.g. 9:30 in the morning is "09:30" while - 9:30 pm is "21:30", midnight is "00:00" and midday is "12:00" + * time24hr : The time in 24hr form - e.g. 9:30 in the morning is '09:30' while + 9:30 pm is '21:30', midnight is '00:00' and midday is '12:00' * callback : A javascript function which will be invoked at the given time. * worlds : (optional) An array of worlds. Each world has its own clock. If no array of worlds is specified, all the server's worlds are used. @@ -784,10 +805,10 @@ To warn players when night is approaching... var utils = require('utils'); - utils.at( "19:00", function() { + utils.at( '19:00', function() { utils.foreach( server.onlinePlayers, function(player){ - player.chat("The night is dark and full of terrors!"); + player.chat('The night is dark and full of terrors!'); }); }); diff --git a/src/main/javascript/modules/utils/utils.js b/src/main/javascript/modules/utils/utils.js index 840c45e..d5c76e8 100644 --- a/src/main/javascript/modules/utils/utils.js +++ b/src/main/javascript/modules/utils/utils.js @@ -3,10 +3,9 @@ Miscellaneous utility functions and classes to help with programming. - * locationToString(Location) - returns a bukkit Location object in string form. + * locationToString(Location) - returns a [bukkit Location][bkloc] object in string form. - * getPlayerObject(playerName) - returns the Player object for a named - player or `self` if no name is provided. + * player(playerName) - returns the Player object for a named player or `self` if no name is provided. * getPlayerPos(playerName) - returns the player's x,y,z and yaw (direction) for a named player or player or `self` if no parameter is provided. @@ -14,36 +13,87 @@ Miscellaneous utility functions and classes to help with programming. * getMousePos(playerName) - returns the x,y,z of the current block being targeted by the named player or player or `self` if no paramter is provided. +[bkloc]: http://jd.bukkit.org/dev/apidocs/org/bukkit/Location.html + ***/ -var _getPlayerObject = function ( playerName ) { - if (typeof playerName == "undefined"){ - if (typeof self == "undefined"){ +/************************************************************************ +### player() function + +The utils.player() function will return a [bukkit Player][bkpl] object +with the given name. This function takes a single parameter +`playerName` which can be either a String or a [Player][bkpl] object - +if it's a Player object, then the same object is returned. If it's a +String, then it tries to find the player with that name. + +#### Parameters + + * playerName : A String or Player object. If no parameter is provided then player() will try to return the `self` variable . It is strongly recommended to provide a parameter. + +#### Example + + var utils = require('utils'); + var player = utils.player('walterh'); + player.sendMessage('Got you!'); + +[bkpl]: http://jd.bukkit.org/dev/apidocs/org/bukkit/entity/Player.html + +***/ +var _player = function ( playerName ) { + if (typeof playerName == 'undefined'){ + if (typeof self == 'undefined'){ return null; } else { return self; } } else { - if (typeof playerName == "string") + if (typeof playerName == 'string') return org.bukkit.Bukkit.getPlayer(playerName); else return playerName; // assumes it's a player object } }; +var _locationToJSON = function(location){ + return { + world: ''+location.world.name, + x: location.x, + y: location.y, + z: location.z, + yaw: location.yaw, + pitch: location.pitch + }; +}; exports.locationToString = function(location){ - return JSON.stringify([""+location.world.name,location.x, location.y, location.z]); + return JSON.stringify(_locationToJSON(location)); +}; +exports.locationToJSON = _locationToJSON; + +exports.locationFromJSON = function(json){ + var world = org.bukkit.Bukkit.getWorld(json.world); + return new org.bukkit.Location(world, json.x, json.y , json.z, json.yaw, json.pitch); }; -exports.getPlayerObject = _getPlayerObject; +exports.player = _player; +exports.getPlayerObject = function(player){ + console.warn('utils.getPlayerObject() is deprecated. Use utils.player() instead.'); + return _player(player); +}; exports.getPlayerPos = function( player ) { - player = _getPlayerObject(player); - return player.location; + player = _player(player); + if (player){ + if (player instanceof org.bukkit.command.BlockCommandSender) + return player.block.location; + else + return player.location; + } + else + return null; }; exports.getMousePos = function (player) { - player = _getPlayerObject(player); + player = _player(player); if (!player) return null; // player might be CONSOLE or a CommandBlock @@ -104,19 +154,19 @@ and put the code there. The following example illustrates how to use foreach for immediate processing of an array... var utils = require('utils'); - var players = ["moe", "larry", "curly"]; + var players = ['moe', 'larry', 'curly']; utils.foreach (players, function(item){ - server.getPlayer(item).sendMessage("Hi " + item); + server.getPlayer(item).sendMessage('Hi ' + item); }); ... The `utils.foreach()` function can work with Arrays or any Java-style collection. This is important because many objects in the Bukkit API use Java-style collections... utils.foreach( server.onlinePlayers, function(player){ - player.chat("Hello!"); + player.chat('Hello!'); }); -... the above code sends a "Hello!" to every online player. +... the above code sends a 'Hello!' to every online player. The following example is a more complex use case - The need to build an enormous structure without hogging CPU usage... @@ -136,7 +186,7 @@ without hogging CPU usage... // assume this code is within a function/closure var player = self; var onDone = function(){ - player.sendMessage("Job Done!"); + player.sendMessage('Job Done!'); }; utils.foreach (a, processItem, null, 10, onDone); @@ -202,8 +252,8 @@ The utils.at() function will perform a given task at a given time every #### Parameters - * time24hr : The time in 24hr form - e.g. 9:30 in the morning is "09:30" while - 9:30 pm is "21:30", midnight is "00:00" and midday is "12:00" + * time24hr : The time in 24hr form - e.g. 9:30 in the morning is '09:30' while + 9:30 pm is '21:30', midnight is '00:00' and midday is '12:00' * callback : A javascript function which will be invoked at the given time. * worlds : (optional) An array of worlds. Each world has its own clock. If no array of worlds is specified, all the server's worlds are used. @@ -213,10 +263,10 @@ To warn players when night is approaching... var utils = require('utils'); - utils.at( "19:00", function() { + utils.at( '19:00', function() { utils.foreach( server.onlinePlayers, function(player){ - player.chat("The night is dark and full of terrors!"); + player.chat('The night is dark and full of terrors!'); }); }); @@ -224,14 +274,14 @@ To warn players when night is approaching... ***/ exports.at = function(time24hr, callback, worlds) { var forever = function(){ return true;}; - var timeParts = time24hr.split(":"); + var timeParts = time24hr.split(':'); var hrs = ((timeParts[0] * 1000) + 18000) % 24000; var mins; if (timeParts.length > 1) mins = (timeParts[1] / 60) * 1000; var timeMc = hrs + mins; - if (typeof worlds == "undefined"){ + if (typeof worlds == 'undefined'){ worlds = server.worlds; } _nicely(function(){ @@ -271,7 +321,7 @@ exports.find = function( dir , filter){ var recurse = function(dir, store){ var files, dirfile = new java.io.File(dir); - if (typeof filter == "undefined") + if (typeof filter == 'undefined') files = dirfile.list(); else files = dirfile.list(filter); diff --git a/src/main/javascript/plugins/arrows.js b/src/main/javascript/plugins/arrows.js index 9cf7783..a3d728f 100644 --- a/src/main/javascript/plugins/arrows.js +++ b/src/main/javascript/plugins/arrows.js @@ -76,8 +76,11 @@ for (var type in _types) { arrows[type] = (function(n){ return function(player){ - player = utils.getPlayerObject(player); - arrows.store.players[player.name] = n; + player = utils.player(player); + if (player) + arrows.store.players[player.name] = n; + else + console.warn('arrows.' + n + ' No player ' + player); }; })(_types[type]); } diff --git a/src/main/javascript/plugins/drone/drone.js b/src/main/javascript/plugins/drone/drone.js index 497a8b3..9eca9ca 100644 --- a/src/main/javascript/plugins/drone/drone.js +++ b/src/main/javascript/plugins/drone/drone.js @@ -1,4 +1,4 @@ -var _utils = require('utils'); +var utils = require('utils'); var blocks = require('blocks'); /********************************************************************* @@ -670,17 +670,26 @@ Drone = function(x,y,z,dir,world) { this.record = false; var usePlayerCoords = false; - var playerPos = _utils.getPlayerPos(); - if (typeof x == "undefined") + var player = self; + if (x instanceof org.bukkit.entity.Player){ + player = x; + } + var playerPos = utils.getPlayerPos(player); + var that = this; + var populateFromLocation = function(loc){ + that.x = loc.x; + that.y = loc.y; + that.z = loc.z; + that.dir = _getDirFromRotation(loc.yaw); + that.world = loc.world; + }; + var mp = utils.getMousePos(player); + if (typeof x == "undefined" || x instanceof org.bukkit.entity.Player) { - var mp = _utils.getMousePos(); if (mp){ - this.x = mp.x; - this.y = mp.y; - this.z = mp.z; + populateFromLocation(mp); if (playerPos) this.dir = _getDirFromRotation(playerPos.yaw); - this.world = mp.world; }else{ // base it on the player's current location usePlayerCoords = true; @@ -691,19 +700,11 @@ Drone = function(x,y,z,dir,world) if (!playerPos){ return null; } - this.x = playerPos.x; - this.y = playerPos.y; - this.z = playerPos.z; - this.dir = _getDirFromRotation(playerPos.yaw); - this.world = playerPos.world; + populateFromLocation(playerPos); } }else{ if (arguments[0] instanceof org.bukkit.Location){ - this.x = arguments[0].x; - this.y = arguments[0].y; - this.z = arguments[0].z; - this.dir = _getDirFromRotation(arguments[0].yaw); - this.world = arguments[0].world; + populateFromLocation(arguments[0]); }else{ this.x = x; this.y = y; @@ -714,7 +715,7 @@ Drone = function(x,y,z,dir,world) this.dir = dir%4; } if (typeof world == "undefined"){ - this.world = _getWorld(); + this.world = playerPos.world; }else{ this.world = world; } @@ -755,7 +756,7 @@ Drone.extend = function(name, func) }; global[name] = function(){ - var result = new Drone(); + var result = new Drone(self); result[name].apply(result,arguments); return result; }; @@ -1071,13 +1072,6 @@ Drone.PLAYER_STAIRS_FACING = [0,2,1,3]; Drone.PLAYER_SIGN_FACING = [4,2,5,3]; Drone.PLAYER_TORCH_FACING = [2,4,1,3]; -var _getWorld = function(){ - var pl = org.bukkit.entity.Player; - var cs = org.bukkit.command.BlockCommandSender; - var world = (self instanceof pl)?self.location.world:(self instanceof cs)?self.block.location.world:null; - return world; -}; - var _STAIRBLOCKS = {53: '5:0' // oak wood ,67: 4 // cobblestone ,108: 45 // brick diff --git a/src/main/javascript/plugins/examples/example-6-hello-player.js b/src/main/javascript/plugins/examples/example-6-hello-player.js new file mode 100644 index 0000000..c14734e --- /dev/null +++ b/src/main/javascript/plugins/examples/example-6-hello-player.js @@ -0,0 +1,36 @@ +/* + A simple minecraft plugin. + Usage: At the in-game prompt type ... + + /jsp hello-byname {player-name} + + ... substituting {player-name} with the name of a player currently + online and a message `Hello ...` will be sent to the named + player. + + This example builds on example-5 and also introduces a new concept - + use of shared modules. That is : modules which are not specific to + any one plugin or set of plugins but which can be used by all + plugins. Shared modules should be placed in the + `scriptcraft/modules` directory. + + * The utils module is used. Because the 'utils' module is + located in the modules folder we don't need to specify an exact + path, just 'utils' will do. + + * The `utils.player()` function is used to obtain a player object + matching the player name. Once a player object is obtained, a + message is sent to that player. +*/ + +var utils = require('utils'); +var greetings = require('./example-1-hello-module'); + +command('hello-byname', function( parameters, sender ) { + var playerName = parameters[0]; + var recipient = utils.player(playerName); + if (recipient) + greetings.hello(recipient); + else + sender.sendMessage('Player ' + playerName + ' not found.'); +}); diff --git a/src/main/javascript/plugins/examples/example-7-hello-events.js b/src/main/javascript/plugins/examples/example-7-hello-events.js new file mode 100644 index 0000000..bc8bf60 --- /dev/null +++ b/src/main/javascript/plugins/examples/example-7-hello-events.js @@ -0,0 +1,83 @@ +/* + A simple event-driven minecraft plugin. + + This example demonstrates event-driven programming. The code below + will display the version of ScriptCraft every time an operator joins + the game. This module is notable from previous modules for the + following reasons... + + 1. It does not export any functions or variables. That's fine. Not + all modules need export stuff. Code in this module will be + executed when the module is first loaded. Because it is in the + `/scriptcraft/plugins` directory, it will be loaded automatically + when the server starts up. + + 2. It uses ScriptCraft's `events.on()` function to add a new *Event + Handler*. An *Event Handler* is a just a function which gets + called whenever a particular *event* happens in the game. The + function defined below will only be executed whenever a player + joins the game. This style of program is sometimes refered to as + *Event-Driven Programming*. + +Adding new *Event Handlers* in ScriptCraft is relatively easy. Use the +`events.on()` function to add a new event handler. It takes 2 +parameters... + + 1. The Event Name, in this case `'player.PlayerJoinEvent'`. You can + browse [all possible Bukkit events][bkevts] (click the 'Next + Package' and 'Previous Package' links to browse). + + 2. The event handling function (also sometimes refered to as a + 'callback'). In ScriptCraft, this function takes 2 parameters, a + listener object and an event object. All of the information about + the event is in the event object. + +In the example below, if a player joins the server and is an operator, +then the ScriptCraft plugin information will be displayed to that +player. + +What's also notable about this example is how it uses the [Bukkit +API][bkapi]. The code... + + if (event.player.op) + +... is a succinct way of accessing object properties which in Java +would have to be written as ... + + if (event.getPlayer().isOp()) + +... ScriptCraft uses a special version of JavaScript which comes +bundled with Java (Minecraft is written in Java) and JavaScript in +Java can access properties of Java objects more succinctly than in +Java itself. What this means in practice is that when you're perusing +the [Bukkit API Reference][bkapi] and come across a method like +[Player.getAllowFlight()][bkgaf], you can write code like this... + + var allowFlight = player.getAllowFlight(); // java style + +... or the more succinct ... + + var allowFlight = player.allowFlight; // javascript style + +... Which style you choose is up to you but `player.allowFlight` is +cleaner and more readable. Similarly where you see a method like +[Player.setAllowFlight()][bksaf], you can write ... + + player.setAllowFlight(true); // java style + +... or the more readable... + + player.allowFlight = true; // javascript style + +... Which style you choose is up to you. + +[bkevts]: http://jd.bukkit.org/dev/apidocs/org/bukkit/event/package-summary.html +[bkgaf]: http://jd.bukkit.org/dev/apidocs/org/bukkit/entity/Player.html#getAllowFlight() +[bksaf]: http://jd.bukkit.org/dev/apidocs/org/bukkit/entity/Player.html#setAllowFlight() +[bkapi]: http://jd.bukkit.org/dev/apidocs/ +*/ +events.on('player.PlayerJoinEvent', function (listener, event){ + if (event.player.op) { + event.player.sendMessage('Welcome to ' + __plugin); + } +}); diff --git a/src/main/javascript/plugins/homes/homes.js b/src/main/javascript/plugins/homes/homes.js index 9c493cd..47d2b51 100644 --- a/src/main/javascript/plugins/homes/homes.js +++ b/src/main/javascript/plugins/homes/homes.js @@ -96,8 +96,8 @@ var homes = plugin("homes", { go: function(guest, host){ if (typeof host == "undefined") host = guest; - guest = utils.getPlayerObject(guest); - host = utils.getPlayerObject(host); + guest = utils.player(guest); + host = utils.player(host); var loc = _store.houses[host.name]; if (!loc){ guest.sendMessage(host.name + " has no home"); @@ -107,9 +107,8 @@ var homes = plugin("homes", { guest.sendMessage("You can't visit " + host.name + "'s home yet"); return; } - var worldName = loc[0], x = loc[1], y = loc[2], z=loc[3], yaw=loc[4]; var teleportCause = org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; - var homeLoc = new org.bukkit.Location(org.bukkit.Bukkit.getWorld(worldName),x,y,z,yaw,0); + var homeLoc = utils.locationFromJSON(loc); guest.teleport(homeLoc, teleportCause.PLUGIN); }, /* @@ -128,17 +127,12 @@ var homes = plugin("homes", { return false; }, set: function(player){ - player = utils.getPlayerObject(player); + player = utils.player(player); var loc = player.location; - _store.houses[player.name] = [""+loc.world.name - ,Math.floor(loc.x) - ,Math.floor(loc.y) - ,Math.floor(loc.z) - ,Math.floor(loc.yaw) - ,Math.floor(loc.pitch)]; + _store.houses[player.name] = utils.locationToJSON(loc); }, remove: function(player){ - player = utils.getPlayerObject(player); + player = utils.player(player); delete _store.houses[player.name]; }, /* ======================================================================== @@ -152,7 +146,7 @@ var homes = plugin("homes", { var result = []; for (var ohp in _store.openHouses) result.push(ohp); - player = utils.getPlayerObject(player); + player = utils.player(player); for (var host in _store.invites){ var guests = _store.invites[host]; for (var i = 0;i < guests.length; i++) @@ -165,7 +159,7 @@ var homes = plugin("homes", { list who can visit the player's home */ ilist: function(player){ - player = utils.getPlayerObject(player); + player = utils.player(player); var result = []; // if home is public - all players if (_store.openHouses[player.name]){ @@ -185,8 +179,8 @@ var homes = plugin("homes", { Invite a player to the home */ invite: function(host, guest){ - host = utils.getPlayerObject(host); - guest = utils.getPlayerObject(guest); + host = utils.player(host); + guest = utils.player(guest); var invitations = []; if (_store.invites[host.name]) invitations = _store.invites[host.name]; @@ -199,8 +193,8 @@ var homes = plugin("homes", { Uninvite someone to the home */ uninvite: function(host, guest){ - host = utils.getPlayerObject(host); - guest = utils.getPlayerObject(guest); + host = utils.player(host); + guest = utils.player(guest); var invitations = _store.invites[host.name]; if (!invitations) return; @@ -214,7 +208,7 @@ var homes = plugin("homes", { make the player's house public */ open: function(player, optionalMsg){ - player = utils.getPlayerObject(player); + player = utils.player(player); _store.openHouses[player.name] = true; if (typeof optionalMsg != "undefined") __plugin.server.broadcastMessage(optionalMsg); @@ -223,7 +217,7 @@ var homes = plugin("homes", { make the player's house private */ close: function(player){ - player = utils.getPlayerObject(player); + player = utils.player(player); delete _store.openHouses[player.name]; }, /* ======================================================================== @@ -236,7 +230,7 @@ var homes = plugin("homes", { return result; }, clear: function(player){ - player = utils.getPlayerObject(player); + player = utils.player(player); delete _store.houses[player.name]; delete _store.openHouses[player.name]; }, @@ -249,8 +243,8 @@ exports.homes = homes; define a set of command options that can be used by players */ var options = { - 'set': function(){homes.set();}, - 'delete': function(){ homes.remove();}, + 'set': function(params, sender){ homes.set(sender); }, + 'delete': function(params, sender ){ homes.remove(sender);}, 'help': function(params, sender){ sender.sendMessage(homes.help());}, 'list': function(params, sender){ var visitable = homes.list(); @@ -279,7 +273,7 @@ var options = { return; } var playerName = params[1]; - var guest = utils.getPlayerObject(playerName); + var guest = utils.player(playerName); if (!guest) sender.sendMessage(playerName + " is not here"); else @@ -291,7 +285,7 @@ var options = { return; } var playerName = params[1]; - var guest = utils.getPlayerObject(playerName); + var guest = utils.player(playerName); if (!guest) sender.sendMessage(playerName + " is not here"); else @@ -333,7 +327,7 @@ command("home", function ( params , sender) { if (option) option(params,sender); else{ - var host = utils.getPlayerObject(params[0]); + var host = utils.player(params[0]); if (!host) sender.sendMessage(params[0] + " is not here"); else From bb9433a6d342d533642f2ffcc95c1351823acddc Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 31 Dec 2013 20:12:57 +0000 Subject: [PATCH 069/456] Improved documentation of the utils module and fixed bug in signs module due to changes to serialization/deserialization of locations. --- docs/API-Reference.md | 119 +++++++++++++++--- src/main/javascript/modules/signs/menu.js | 8 +- src/main/javascript/modules/utils/utils.js | 138 +++++++++++++++++---- 3 files changed, 221 insertions(+), 44 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index dd83f87..fbbfee7 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -646,21 +646,11 @@ The following example illustrates how to use http.request to make a request to a ## Utilities Module -Miscellaneous utility functions and classes to help with programming. +The `utils` module is a storehouse for various useful utility +functions which can be used by plugin and module authors. It contains +miscellaneous utility functions and classes to help with programming. - * locationToString(Location) - returns a [bukkit Location][bkloc] object in string form. - - * player(playerName) - returns the Player object for a named player or `self` if no name is provided. - - * getPlayerPos(playerName) - returns the player's x,y,z and yaw (direction) for a named player - or player or `self` if no parameter is provided. - - * getMousePos(playerName) - returns the x,y,z of the current block being targeted by the named player - or player or `self` if no paramter is provided. - -[bkloc]: http://jd.bukkit.org/dev/apidocs/org/bukkit/Location.html - -### player() function +### utils.player() function The utils.player() function will return a [bukkit Player][bkpl] object with the given name. This function takes a single parameter @@ -670,17 +660,110 @@ String, then it tries to find the player with that name. #### Parameters - * playerName : A String or Player object. If no parameter is provided then player() will try to return the `self` variable . It is strongly recommended to provide a parameter. + * playerName : A String or Player object. If no parameter is provided + then player() will try to return the `self` variable . It is + strongly recommended to provide a parameter. #### Example var utils = require('utils'); - var player = utils.player('walterh'); - player.sendMessage('Got you!'); + var name = 'walterh'; + var player = utils.player(name); + if (player) { + player.sendMessage('Got ' + name); + }else{ + console.log('No player named ' + name); + } [bkpl]: http://jd.bukkit.org/dev/apidocs/org/bukkit/entity/Player.html +[bkloc]: http://jd.bukkit.org/dev/apidocs/org/bukkit/Location.html -### foreach() function +### utils.locationToJSON() function + +utils.locationToJSON() returns a [org.bukkit.Location][bkloc] object in JSON form... + + { world: 'world5', + x: 56.9324, + y: 103.9954, + z: 43.1323, + yaw: 0.0, + pitch: 0.0 + } + +This can be useful if you write a plugin that needs to store location data since bukkit's Location object is a Java object which cannot be serialized to JSON by default. + +#### Parameters + + * location: An object of type [org.bukkit.Location][bkloc] + +#### Returns + +A JSON object in the above form. + +### utils.locationToString() function + +The utils.locationToString() function returns a +[org.bukkit.Location][bkloc] object in string form... + + '{"world":"world5",x:56.9324,y:103.9954,z:43.1323,yaw:0.0,pitch:0.0}' + +... which can be useful if you write a plugin which uses Locations as +keys in a lookup table. + +#### Example + + var utils = require('utils'); + ... + var key = utils.locationToString(player.location); + lookupTable[key] = player.name; + +### utils.locationFromJSON() function + +This function reconstructs an [org.bukkit.Location][bkloc] object from +a JSON representation. This is the counterpart to the +`locationToJSON()` function. It takes a JSON object of the form +returned by locationToJSON() and reconstructs and returns a bukkit +Location object. + +### utils.getPlayerPos() function + +This function returns the player's [Location][bkloc] (x, y, z, pitch +and yaw) for a named player. If the "player" is in fact a +[org.bukkit.command.BlockCommandSender][bkbcs] then the attached +Block's location is returned. + +#### Parameters + + * player : A [org.bukkit.command.CommandSender][bkbcs] (Player or BlockCommandSender) or player name (String). + +#### Returns + +An [org.bukkit.Location][bkloc] object. + +[bkbcs]: http://jd.bukkit.org/dev/apidocs/org/bukkit/command/BlockCommandSender.html +[bksndr]: http://jd.bukkit.org/dev/apidocs/index.html?org/bukkit/command/CommandSender.html +### utils.getMousePos() function + +This function returns a [org.bukkit.Location][bkloc] object (the +x,y,z) of the current block being targeted by the named player. This +is the location of the block the player is looking at (targeting). + +#### Parameters + + * player : The player whose targeted location you wish to get. + +#### Example + +The following code will strike lightning at the location the player is looking at... + + var utils = require('utils'); + var playerName = 'walterh'; + var targetPos = utils.getMousePos(playerName); + if (targetPos){ + targetPos.world.strikeLightning(targetPos); + } + +### utils.foreach() function The utils.foreach() function is a utility function for iterating over an array of objects (or a java.util.Collection of objects) and processing each object in turn. Where diff --git a/src/main/javascript/modules/signs/menu.js b/src/main/javascript/modules/signs/menu.js index 0d08b86..397f25a 100644 --- a/src/main/javascript/modules/signs/menu.js +++ b/src/main/javascript/modules/signs/menu.js @@ -1,4 +1,4 @@ -var _utils = require('utils'); +var utils = require('utils'); var stringExt = require('utils/string-exts'); var _store = {}; /* @@ -78,7 +78,7 @@ signs.menu = function( save = true; if (typeof sign == "undefined"){ - var mouseLoc = _utils.getMousePos(); + var mouseLoc = utils.getMousePos(); if (mouseLoc){ sign = mouseLoc.block.state; }else{ @@ -105,7 +105,7 @@ signs.menu = function( get a unique ID for this particular sign instance */ var signLoc = sign.block.location; - var menuSignSaveData = [""+signLoc.world.name, signLoc.x,signLoc.y,signLoc.z]; + var menuSignSaveData = utils.locationToJSON(signLoc); var menuSignUID = JSON.stringify(menuSignSaveData); /* keep a reference to the update function for use by the event handler @@ -177,7 +177,7 @@ events.on('player.PlayerInteractEvent',function(listener, event) { if (! event.clickedBlock.state instanceof org.bukkit.block.Sign) return; - var evtLocStr = _utils.locationToString(event.clickedBlock.location); + var evtLocStr = utils.locationToString(event.clickedBlock.location); var signUpdater = _updaters[evtLocStr] if (signUpdater) signUpdater(event.player, event.clickedBlock.state); diff --git a/src/main/javascript/modules/utils/utils.js b/src/main/javascript/modules/utils/utils.js index d5c76e8..1e45a3b 100644 --- a/src/main/javascript/modules/utils/utils.js +++ b/src/main/javascript/modules/utils/utils.js @@ -1,23 +1,11 @@ /************************************************************************ ## Utilities Module -Miscellaneous utility functions and classes to help with programming. +The `utils` module is a storehouse for various useful utility +functions which can be used by plugin and module authors. It contains +miscellaneous utility functions and classes to help with programming. - * locationToString(Location) - returns a [bukkit Location][bkloc] object in string form. - - * player(playerName) - returns the Player object for a named player or `self` if no name is provided. - - * getPlayerPos(playerName) - returns the player's x,y,z and yaw (direction) for a named player - or player or `self` if no parameter is provided. - - * getMousePos(playerName) - returns the x,y,z of the current block being targeted by the named player - or player or `self` if no paramter is provided. - -[bkloc]: http://jd.bukkit.org/dev/apidocs/org/bukkit/Location.html - -***/ -/************************************************************************ -### player() function +### utils.player() function The utils.player() function will return a [bukkit Player][bkpl] object with the given name. This function takes a single parameter @@ -27,15 +15,23 @@ String, then it tries to find the player with that name. #### Parameters - * playerName : A String or Player object. If no parameter is provided then player() will try to return the `self` variable . It is strongly recommended to provide a parameter. + * playerName : A String or Player object. If no parameter is provided + then player() will try to return the `self` variable . It is + strongly recommended to provide a parameter. #### Example var utils = require('utils'); - var player = utils.player('walterh'); - player.sendMessage('Got you!'); + var name = 'walterh'; + var player = utils.player(name); + if (player) { + player.sendMessage('Got ' + name); + }else{ + console.log('No player named ' + name); + } [bkpl]: http://jd.bukkit.org/dev/apidocs/org/bukkit/entity/Player.html +[bkloc]: http://jd.bukkit.org/dev/apidocs/org/bukkit/Location.html ***/ var _player = function ( playerName ) { @@ -52,7 +48,30 @@ var _player = function ( playerName ) { return playerName; // assumes it's a player object } }; +/************************************************************************* +### utils.locationToJSON() function +utils.locationToJSON() returns a [org.bukkit.Location][bkloc] object in JSON form... + + { world: 'world5', + x: 56.9324, + y: 103.9954, + z: 43.1323, + yaw: 0.0, + pitch: 0.0 + } + +This can be useful if you write a plugin that needs to store location data since bukkit's Location object is a Java object which cannot be serialized to JSON by default. + +#### Parameters + + * location: An object of type [org.bukkit.Location][bkloc] + +#### Returns + +A JSON object in the above form. + +***/ var _locationToJSON = function(location){ return { world: ''+location.world.name, @@ -63,14 +82,49 @@ var _locationToJSON = function(location){ pitch: location.pitch }; }; +/************************************************************************* +### utils.locationToString() function + +The utils.locationToString() function returns a +[org.bukkit.Location][bkloc] object in string form... + + '{"world":"world5",x:56.9324,y:103.9954,z:43.1323,yaw:0.0,pitch:0.0}' + +... which can be useful if you write a plugin which uses Locations as +keys in a lookup table. + +#### Example + + var utils = require('utils'); + ... + var key = utils.locationToString(player.location); + lookupTable[key] = player.name; + +***/ exports.locationToString = function(location){ return JSON.stringify(_locationToJSON(location)); }; exports.locationToJSON = _locationToJSON; +/************************************************************************* +### utils.locationFromJSON() function + +This function reconstructs an [org.bukkit.Location][bkloc] object from +a JSON representation. This is the counterpart to the +`locationToJSON()` function. It takes a JSON object of the form +returned by locationToJSON() and reconstructs and returns a bukkit +Location object. + +***/ exports.locationFromJSON = function(json){ - var world = org.bukkit.Bukkit.getWorld(json.world); - return new org.bukkit.Location(world, json.x, json.y , json.z, json.yaw, json.pitch); + if (json.constuctor == Array){ + // for support of legacy format + var world = org.bukkit.Bukkit.getWorld(json[0]); + return new org.bukkit.Location(world, json[1], json[2] , json[3]); + }else{ + var world = org.bukkit.Bukkit.getWorld(json.world); + return new org.bukkit.Location(world, json.x, json.y , json.z, json.yaw, json.pitch); + } }; exports.player = _player; @@ -78,7 +132,25 @@ exports.getPlayerObject = function(player){ console.warn('utils.getPlayerObject() is deprecated. Use utils.player() instead.'); return _player(player); }; +/************************************************************************* +### utils.getPlayerPos() function +This function returns the player's [Location][bkloc] (x, y, z, pitch +and yaw) for a named player. If the "player" is in fact a +[org.bukkit.command.BlockCommandSender][bkbcs] then the attached +Block's location is returned. + +#### Parameters + + * player : A [org.bukkit.command.CommandSender][bkbcs] (Player or BlockCommandSender) or player name (String). + +#### Returns + +An [org.bukkit.Location][bkloc] object. + +[bkbcs]: http://jd.bukkit.org/dev/apidocs/org/bukkit/command/BlockCommandSender.html +[bksndr]: http://jd.bukkit.org/dev/apidocs/index.html?org/bukkit/command/CommandSender.html +***/ exports.getPlayerPos = function( player ) { player = _player(player); if (player){ @@ -90,7 +162,29 @@ exports.getPlayerPos = function( player ) { else return null; }; +/************************************************************************ +### utils.getMousePos() function +This function returns a [org.bukkit.Location][bkloc] object (the +x,y,z) of the current block being targeted by the named player. This +is the location of the block the player is looking at (targeting). + +#### Parameters + + * player : The player whose targeted location you wish to get. + +#### Example + +The following code will strike lightning at the location the player is looking at... + + var utils = require('utils'); + var playerName = 'walterh'; + var targetPos = utils.getMousePos(playerName); + if (targetPos){ + targetPos.world.strikeLightning(targetPos); + } + +***/ exports.getMousePos = function (player) { player = _player(player); @@ -106,7 +200,7 @@ exports.getMousePos = function (player) { return targetedBlock.location; }; /************************************************************************ -### foreach() function +### utils.foreach() function The utils.foreach() function is a utility function for iterating over an array of objects (or a java.util.Collection of objects) and processing each object in turn. Where From 6d9f2b43373a68184ab88e0a39ddce743ba2ec3b Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 31 Dec 2013 20:33:00 +0000 Subject: [PATCH 070/456] Updated example plugin docs to be included in the API reference. --- docs/API-Reference.md | 262 ++++++++++++++++++ .../examples/example-1-hello-module.js | 40 ++- .../examples/example-2-hello-command.js | 44 +-- .../examples/example-3-hello-ops-only.js | 46 +-- .../examples/example-4-hello-parameters.js | 36 ++- .../examples/example-5-hello-using-module.js | 33 ++- .../examples/example-6-hello-player.js | 45 ++- .../examples/example-7-hello-events.js | 24 +- 8 files changed, 435 insertions(+), 95 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index fbbfee7..16624ca 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -570,6 +570,92 @@ To listen for events using a full class name as the `eventName` parameter... [buk2]: http://wiki.bukkit.org/Event_API_Reference [buk]: http://jd.bukkit.org/dev/apidocs/index.html?org/bukkit/event/Event.html +## Example Plugin #7 + +A simple event-driven minecraft plugin. How to handle Events. + + +This example demonstrates event-driven programming. The code below +will display the version of ScriptCraft every time an operator joins +the game. This module is notable from previous modules for the +following reasons... + + 1. It does not export any functions or variables. That's fine. Not + all modules need export stuff. Code in this module will be + executed when the module is first loaded. Because it is in the + `/scriptcraft/plugins` directory, it will be loaded automatically + when the server starts up. + + 2. It uses ScriptCraft's `events.on()` function to add a new *Event + Handler*. An *Event Handler* is a just a function which gets + called whenever a particular *event* happens in the game. The + function defined below will only be executed whenever a player + joins the game. This style of program is sometimes refered to as + *Event-Driven Programming*. + +Adding new *Event Handlers* in ScriptCraft is relatively easy. Use the +`events.on()` function to add a new event handler. It takes 2 +parameters... + + 1. The Event Name, in this case `'player.PlayerJoinEvent'`. You can + browse [all possible Bukkit events][bkevts] (click the 'Next + Package' and 'Previous Package' links to browse). + + 2. The event handling function (also sometimes refered to as a + 'callback'). In ScriptCraft, this function takes 2 parameters, a + listener object and an event object. All of the information about + the event is in the event object. + +In the example below, if a player joins the server and is an operator, +then the ScriptCraft plugin information will be displayed to that +player. + +What's also notable about this example is how it uses the [Bukkit +API][bkapi]. The code... + + if (event.player.op) + +... is a succinct way of accessing object properties which in Java +would have to be written as ... + + if (event.getPlayer().isOp()) + +... ScriptCraft uses a special version of JavaScript which comes +bundled with Java (Minecraft is written in Java) and JavaScript in +Java can access properties of Java objects more succinctly than in +Java itself. What this means in practice is that when you're perusing +the [Bukkit API Reference][bkapi] and come across a method like +[Player.getAllowFlight()][bkgaf], you can write code like this... + + var allowFlight = player.getAllowFlight(); // java style + +... or the more succinct ... + + var allowFlight = player.allowFlight; // javascript style + +... Which style you choose is up to you but `player.allowFlight` is +cleaner and more readable. Similarly where you see a method like +[Player.setAllowFlight()][bksaf], you can write ... + + player.setAllowFlight(true); // java style + +... or the more readable... + + player.allowFlight = true; // javascript style + +... Which style you choose is up to you. + +[bkevts]: http://jd.bukkit.org/dev/apidocs/org/bukkit/event/package-summary.html +[bkgaf]: http://jd.bukkit.org/dev/apidocs/org/bukkit/entity/Player.html#getAllowFlight() +[bksaf]: http://jd.bukkit.org/dev/apidocs/org/bukkit/entity/Player.html#setAllowFlight() +[bkapi]: http://jd.bukkit.org/dev/apidocs/ + + events.on('player.PlayerJoinEvent', function (listener, event){ + if (event.player.op) { + event.player.sendMessage('Welcome to ' + __plugin); + } + }); + ## console global variable ScriptCraft provides a `console` global variable with the followng methods... @@ -2033,6 +2119,182 @@ global commands for a plugin, please let me know. [pcppevt]: http://jd.bukkit.org/dev/apidocs/org/bukkit/event/player/PlayerCommandPreprocessEvent.html +## Example Plugin #5 + +A simple minecraft plugin. Using Modules. + +### Usage: + +At the in-game prompt type ... + + /jsp hello-module + +... and a message `Hello {player-name}` will appear (where {player-name} is +replaced by your own name). + +This example demonstrates the use of modules. In +example-1-hello-module.js we created a new javascript module. In +this example, we use that module... + + * We load the module using the `require()` function. Because this + module and the module we require are n the same directory, we + specify `'./example-1-hello-module'` as the path (when loading a + module from the same directory, `./` at the start of the path + indicates that the file should be searched for in the same + directory. + + * We assign the loaded module to a variable (`greetings`) and then + use the module's `hello` method to display the message. + + + var greetings = require('./example-1-hello-module'); + command('hello-module', function( parameters, player ){ + greetings.hello(player); + }); + +## Example Plugin #3 + +A simple minecraft plugin. Commands for operators only. + +### Usage: + +At the in-game prompt type ... + + /jsp op-hello + +... and a message `Hello {player-name}` will appear (where {player-name} is +replaced by your own name). + +This example demonstrates the basics of adding new functionality +which is usable all players or those with the scriptcraft.proxy +permission. By default, all players are granted this permission. In +this command though, the function checks to see if the player is an +operator and if they aren't will return immediately. + +This differs from example 2 in that the function will only print a +message for operators. + + command('op-hello', function (parameters, player) { + if (!player.op){ + player.sendMessage('Only operators can do this.'); + return; + } + player.sendMessage('Hello ' + player.name); + }); +## Example Plugin #1 + +A simple minecraft plugin. The most basic module. + +### Usage: + +At the in-game prompt type ... + + /js hello(self) + +... and a message `Hello {player-name}` will appear (where + {player-name} is replaced by your own name). + +This example demonstrates the basics of adding new functionality which +is only usable by server operators or users with the +scriptcraft.evaluate permission. By default, only ops are granted this +permission. + +The `hello` function below is only usable by players with the scriptcraft.evaluate +permission since it relies on the `/js` command to execute. + + exports.hello = function(player){ + player.sendMessage('Hello ' + player.name); + }; + +## Example Plugin #6 + +A simple minecraft plugin. Finding players by name. + +### Usage: + +At the in-game prompt type ... + + /jsp hello-byname {player-name} + +... substituting {player-name} with the name of a player currently +online and a message `Hello ...` will be sent to the named +player. + +This example builds on example-5 and also introduces a new concept - +use of shared modules. That is : modules which are not specific to +any one plugin or set of plugins but which can be used by all +plugins. Shared modules should be placed in the +`scriptcraft/modules` directory. + + * The utils module is used. Because the 'utils' module is + located in the modules folder we don't need to specify an exact + path, just 'utils' will do. + + * The `utils.player()` function is used to obtain a player object + matching the player name. Once a player object is obtained, a + message is sent to that player. + + var utils = require('utils'); + var greetings = require('./example-1-hello-module'); + + command('hello-byname', function( parameters, sender ) { + var playerName = parameters[0]; + var recipient = utils.player(playerName); + if (recipient) + greetings.hello(recipient); + else + sender.sendMessage('Player ' + playerName + ' not found.'); + }); +## Example Plugin #2 + +A simple minecraft plugin. Commands for other players. + +### Usage: + +At the in-game prompt type ... + + /jsp hello + +... and a message `Hello {player-name}` will appear (where {player-name} is +replaced by your own name). + +This example demonstrates the basics of adding new functionality +which is usable all players or those with the scriptcraft.proxy +permission. By default, all players are granted this permission. + +This differs from example 1 in that a new 'jsp ' command extension +is defined. Since all players can use the `jsp` command, all players +can use the new extension. Unlike the previous example, the `jsp hello` +command does not evaluate javascript code so this command is much more secure. + + command('hello', function (parameters, player) { + player.sendMessage('Hello ' + player.name); + }); + +## Example Plugin #4 +A simple minecraft plugin. Handling parameters. + +### Usage: + +At the in-game prompt type ... + + /jsp hello-params Hi + /jsp hello-params Saludos + /jsp hello-params Greetings + +... and a message `Hi {player-name}` or `Saludos {player-name}` etc +will appear (where {player-name} is replaced by your own name). + +This example demonstrates adding and using parameters in commands. + +This differs from example 3 in that the greeting can be changed from +a fixed 'Hello ' to anything you like by passing a parameter. + + command('hello-params', function (parameters, player) { + var salutation = parameters[0] ; + player.sendMessage( salutation + ' ' + player.name); + }); + ## homes Module The homes plugin lets players set a location as home and return to the diff --git a/src/main/javascript/plugins/examples/example-1-hello-module.js b/src/main/javascript/plugins/examples/example-1-hello-module.js index af60e75..f4e36f7 100644 --- a/src/main/javascript/plugins/examples/example-1-hello-module.js +++ b/src/main/javascript/plugins/examples/example-1-hello-module.js @@ -1,20 +1,30 @@ -/* - A simple minecraft plugin. - Usage: At the in-game prompt type ... - - /js hello(self) +/************************************************************************* +## Example Plugin #1 - ... and a message `Hello {player-name}` will appear (where {player-name} is - replaced by your own name). - - This example demonstrates the basics of adding new functionality which is only - usable by server operators or users with the scriptcraft.evaluate permission. - By default, only ops are granted this permission. - - The `hello` function below is only usable by players with the scriptcraft.evaluate - permission since it relies on the `/js` command to execute. +A simple minecraft plugin. The most basic module. -*/ +### Usage: + +At the in-game prompt type ... + + /js hello(self) + +... and a message `Hello {player-name}` will appear (where + {player-name} is replaced by your own name). + +This example demonstrates the basics of adding new functionality which +is only usable by server operators or users with the +scriptcraft.evaluate permission. By default, only ops are granted this +permission. + +The `hello` function below is only usable by players with the scriptcraft.evaluate +permission since it relies on the `/js` command to execute. + + exports.hello = function(player){ + player.sendMessage('Hello ' + player.name); + }; + +***/ exports.hello = function(player){ player.sendMessage('Hello ' + player.name); }; diff --git a/src/main/javascript/plugins/examples/example-2-hello-command.js b/src/main/javascript/plugins/examples/example-2-hello-command.js index 4ead4ba..a41ea7e 100644 --- a/src/main/javascript/plugins/examples/example-2-hello-command.js +++ b/src/main/javascript/plugins/examples/example-2-hello-command.js @@ -1,23 +1,31 @@ -/* - A simple minecraft plugin. - Usage: At the in-game prompt type ... - - /jsp hello +/************************************************************************* +## Example Plugin #2 - ... and a message `Hello {player-name}` will appear (where {player-name} is - replaced by your own name). - - This example demonstrates the basics of adding new functionality - which is usable all players or those with the scriptcraft.proxy - permission. By default, all players are granted this permission. - - This differs from example 1 in that a new 'jsp ' command extension - is defined. Since all players can use the `jsp` command, all players - can use the new extension. Unlike the previous example, the `jsp - hello` command does not evaluate javascript code so this command is - much more secure. +A simple minecraft plugin. Commands for other players. -*/ +### Usage: + +At the in-game prompt type ... + + /jsp hello + +... and a message `Hello {player-name}` will appear (where {player-name} is +replaced by your own name). + +This example demonstrates the basics of adding new functionality +which is usable all players or those with the scriptcraft.proxy +permission. By default, all players are granted this permission. + +This differs from example 1 in that a new 'jsp ' command extension +is defined. Since all players can use the `jsp` command, all players +can use the new extension. Unlike the previous example, the `jsp hello` +command does not evaluate javascript code so this command is much more secure. + + command('hello', function (parameters, player) { + player.sendMessage('Hello ' + player.name); + }); + +***/ command('hello', function (parameters, player) { player.sendMessage('Hello ' + player.name); diff --git a/src/main/javascript/plugins/examples/example-3-hello-ops-only.js b/src/main/javascript/plugins/examples/example-3-hello-ops-only.js index 10f0674..4ec44ca 100644 --- a/src/main/javascript/plugins/examples/example-3-hello-ops-only.js +++ b/src/main/javascript/plugins/examples/example-3-hello-ops-only.js @@ -1,22 +1,34 @@ -/* - A simple minecraft plugin. - Usage: At the in-game prompt type ... - - /jsp op-hello +/************************************************************************* +## Example Plugin #3 - ... and a message `Hello {player-name}` will appear (where {player-name} is - replaced by your own name). - - This example demonstrates the basics of adding new functionality - which is usable all players or those with the scriptcraft.proxy - permission. By default, all players are granted this permission. In - this command though, the function checks to see if the player is an - operator and if they aren't will return immediately. - - This differs from example 2 in that the function will only print a - message for operators. +A simple minecraft plugin. Commands for operators only. -*/ +### Usage: + +At the in-game prompt type ... + + /jsp op-hello + +... and a message `Hello {player-name}` will appear (where {player-name} is +replaced by your own name). + +This example demonstrates the basics of adding new functionality +which is usable all players or those with the scriptcraft.proxy +permission. By default, all players are granted this permission. In +this command though, the function checks to see if the player is an +operator and if they aren't will return immediately. + +This differs from example 2 in that the function will only print a +message for operators. + + command('op-hello', function (parameters, player) { + if (!player.op){ + player.sendMessage('Only operators can do this.'); + return; + } + player.sendMessage('Hello ' + player.name); + }); +***/ command('op-hello', function (parameters, player) { /* diff --git a/src/main/javascript/plugins/examples/example-4-hello-parameters.js b/src/main/javascript/plugins/examples/example-4-hello-parameters.js index 14c6ec7..f2fc391 100644 --- a/src/main/javascript/plugins/examples/example-4-hello-parameters.js +++ b/src/main/javascript/plugins/examples/example-4-hello-parameters.js @@ -1,19 +1,29 @@ -/* - A simple minecraft plugin. - Usage: At the in-game prompt type ... - - /jsp hello-params Hi - /jsp hello-params Saludos - /jsp hello-params Greetings +/************************************************************************* +## Example Plugin #4 +A simple minecraft plugin. Handling parameters. - ... and a message `Hi {player-name}` or `Saludos {player-name}` etc - will appear (where {player-name} is replaced by your own name). +### Usage: + +At the in-game prompt type ... - This example demonstrates adding and using parameters in commands. + /jsp hello-params Hi + /jsp hello-params Saludos + /jsp hello-params Greetings + +... and a message `Hi {player-name}` or `Saludos {player-name}` etc +will appear (where {player-name} is replaced by your own name). - This differs from example 3 in that the greeting can be changed from - a fixed 'Hello ' to anything you like by passing a parameter. -*/ +This example demonstrates adding and using parameters in commands. + +This differs from example 3 in that the greeting can be changed from +a fixed 'Hello ' to anything you like by passing a parameter. + + command('hello-params', function (parameters, player) { + var salutation = parameters[0] ; + player.sendMessage( salutation + ' ' + player.name); + }); + +***/ command('hello-params', function (parameters, player) { /* diff --git a/src/main/javascript/plugins/examples/example-5-hello-using-module.js b/src/main/javascript/plugins/examples/example-5-hello-using-module.js index b09d5eb..a335033 100644 --- a/src/main/javascript/plugins/examples/example-5-hello-using-module.js +++ b/src/main/javascript/plugins/examples/example-5-hello-using-module.js @@ -1,15 +1,20 @@ -/* - A simple minecraft plugin. - Usage: At the in-game prompt type ... - - /jsp hello-module +/************************************************************************* +## Example Plugin #5 - ... and a message `Hello {player-name}` will appear (where {player-name} is - replaced by your own name). +A simple minecraft plugin. Using Modules. + +### Usage: + +At the in-game prompt type ... - This example demonstrates the use of modules. In - example-1-hello-module.js we created a new javascript module. In - this example, we use that module... + /jsp hello-module + +... and a message `Hello {player-name}` will appear (where {player-name} is +replaced by your own name). + +This example demonstrates the use of modules. In +example-1-hello-module.js we created a new javascript module. In +this example, we use that module... * We load the module using the `require()` function. Because this module and the module we require are n the same directory, we @@ -21,7 +26,13 @@ * We assign the loaded module to a variable (`greetings`) and then use the module's `hello` method to display the message. -*/ + + var greetings = require('./example-1-hello-module'); + command('hello-module', function( parameters, player ){ + greetings.hello(player); + }); + +***/ var greetings = require('./example-1-hello-module'); command('hello-module', function( parameters, player ){ diff --git a/src/main/javascript/plugins/examples/example-6-hello-player.js b/src/main/javascript/plugins/examples/example-6-hello-player.js index c14734e..924d92c 100644 --- a/src/main/javascript/plugins/examples/example-6-hello-player.js +++ b/src/main/javascript/plugins/examples/example-6-hello-player.js @@ -1,18 +1,23 @@ -/* - A simple minecraft plugin. - Usage: At the in-game prompt type ... - - /jsp hello-byname {player-name} +/************************************************************************* +## Example Plugin #6 - ... substituting {player-name} with the name of a player currently - online and a message `Hello ...` will be sent to the named - player. +A simple minecraft plugin. Finding players by name. + +### Usage: + +At the in-game prompt type ... - This example builds on example-5 and also introduces a new concept - - use of shared modules. That is : modules which are not specific to - any one plugin or set of plugins but which can be used by all - plugins. Shared modules should be placed in the - `scriptcraft/modules` directory. + /jsp hello-byname {player-name} + +... substituting {player-name} with the name of a player currently +online and a message `Hello ...` will be sent to the named +player. + +This example builds on example-5 and also introduces a new concept - +use of shared modules. That is : modules which are not specific to +any one plugin or set of plugins but which can be used by all +plugins. Shared modules should be placed in the +`scriptcraft/modules` directory. * The utils module is used. Because the 'utils' module is located in the modules folder we don't need to specify an exact @@ -21,7 +26,19 @@ * The `utils.player()` function is used to obtain a player object matching the player name. Once a player object is obtained, a message is sent to that player. -*/ + + var utils = require('utils'); + var greetings = require('./example-1-hello-module'); + + command('hello-byname', function( parameters, sender ) { + var playerName = parameters[0]; + var recipient = utils.player(playerName); + if (recipient) + greetings.hello(recipient); + else + sender.sendMessage('Player ' + playerName + ' not found.'); + }); +***/ var utils = require('utils'); var greetings = require('./example-1-hello-module'); diff --git a/src/main/javascript/plugins/examples/example-7-hello-events.js b/src/main/javascript/plugins/examples/example-7-hello-events.js index bc8bf60..92066b0 100644 --- a/src/main/javascript/plugins/examples/example-7-hello-events.js +++ b/src/main/javascript/plugins/examples/example-7-hello-events.js @@ -1,10 +1,13 @@ -/* - A simple event-driven minecraft plugin. +/************************************************************************* +## Example Plugin #7 - This example demonstrates event-driven programming. The code below - will display the version of ScriptCraft every time an operator joins - the game. This module is notable from previous modules for the - following reasons... +A simple event-driven minecraft plugin. How to handle Events. + + +This example demonstrates event-driven programming. The code below +will display the version of ScriptCraft every time an operator joins +the game. This module is notable from previous modules for the +following reasons... 1. It does not export any functions or variables. That's fine. Not all modules need export stuff. Code in this module will be @@ -75,7 +78,14 @@ cleaner and more readable. Similarly where you see a method like [bkgaf]: http://jd.bukkit.org/dev/apidocs/org/bukkit/entity/Player.html#getAllowFlight() [bksaf]: http://jd.bukkit.org/dev/apidocs/org/bukkit/entity/Player.html#setAllowFlight() [bkapi]: http://jd.bukkit.org/dev/apidocs/ -*/ + + events.on('player.PlayerJoinEvent', function (listener, event){ + if (event.player.op) { + event.player.sendMessage('Welcome to ' + __plugin); + } + }); + +***/ events.on('player.PlayerJoinEvent', function (listener, event){ if (event.player.op) { event.player.sendMessage('Welcome to ' + __plugin); From cdc8ad7d9b381a8f5e9809221085109629dd245e Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 31 Dec 2013 21:09:50 +0000 Subject: [PATCH 071/456] Tweaks to documentation (examples and all files in same directory sorted alphabetically except where precedence regexp present) --- docs/API-Reference.md | 774 +++++++++--------- src/docs/javascript/generateApiDocs.js | 59 +- .../examples/example-5-hello-using-module.js | 17 +- .../examples/example-6-hello-player.js | 15 +- 4 files changed, 447 insertions(+), 418 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 16624ca..70249b5 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -570,92 +570,6 @@ To listen for events using a full class name as the `eventName` parameter... [buk2]: http://wiki.bukkit.org/Event_API_Reference [buk]: http://jd.bukkit.org/dev/apidocs/index.html?org/bukkit/event/Event.html -## Example Plugin #7 - -A simple event-driven minecraft plugin. How to handle Events. - - -This example demonstrates event-driven programming. The code below -will display the version of ScriptCraft every time an operator joins -the game. This module is notable from previous modules for the -following reasons... - - 1. It does not export any functions or variables. That's fine. Not - all modules need export stuff. Code in this module will be - executed when the module is first loaded. Because it is in the - `/scriptcraft/plugins` directory, it will be loaded automatically - when the server starts up. - - 2. It uses ScriptCraft's `events.on()` function to add a new *Event - Handler*. An *Event Handler* is a just a function which gets - called whenever a particular *event* happens in the game. The - function defined below will only be executed whenever a player - joins the game. This style of program is sometimes refered to as - *Event-Driven Programming*. - -Adding new *Event Handlers* in ScriptCraft is relatively easy. Use the -`events.on()` function to add a new event handler. It takes 2 -parameters... - - 1. The Event Name, in this case `'player.PlayerJoinEvent'`. You can - browse [all possible Bukkit events][bkevts] (click the 'Next - Package' and 'Previous Package' links to browse). - - 2. The event handling function (also sometimes refered to as a - 'callback'). In ScriptCraft, this function takes 2 parameters, a - listener object and an event object. All of the information about - the event is in the event object. - -In the example below, if a player joins the server and is an operator, -then the ScriptCraft plugin information will be displayed to that -player. - -What's also notable about this example is how it uses the [Bukkit -API][bkapi]. The code... - - if (event.player.op) - -... is a succinct way of accessing object properties which in Java -would have to be written as ... - - if (event.getPlayer().isOp()) - -... ScriptCraft uses a special version of JavaScript which comes -bundled with Java (Minecraft is written in Java) and JavaScript in -Java can access properties of Java objects more succinctly than in -Java itself. What this means in practice is that when you're perusing -the [Bukkit API Reference][bkapi] and come across a method like -[Player.getAllowFlight()][bkgaf], you can write code like this... - - var allowFlight = player.getAllowFlight(); // java style - -... or the more succinct ... - - var allowFlight = player.allowFlight; // javascript style - -... Which style you choose is up to you but `player.allowFlight` is -cleaner and more readable. Similarly where you see a method like -[Player.setAllowFlight()][bksaf], you can write ... - - player.setAllowFlight(true); // java style - -... or the more readable... - - player.allowFlight = true; // javascript style - -... Which style you choose is up to you. - -[bkevts]: http://jd.bukkit.org/dev/apidocs/org/bukkit/event/package-summary.html -[bkgaf]: http://jd.bukkit.org/dev/apidocs/org/bukkit/entity/Player.html#getAllowFlight() -[bksaf]: http://jd.bukkit.org/dev/apidocs/org/bukkit/entity/Player.html#setAllowFlight() -[bkapi]: http://jd.bukkit.org/dev/apidocs/ - - events.on('player.PlayerJoinEvent', function (listener, event){ - if (event.player.op) { - event.player.sendMessage('Welcome to ' + __plugin); - } - }); - ## console global variable ScriptCraft provides a `console` global variable with the followng methods... @@ -690,6 +604,58 @@ ScriptCraft uses Java's [String.format()][strfmt] so any string substitution ide [strfmt]: http://docs.oracle.com/javase/6/docs/api/java/lang/String.html#format(java.lang.String, java.lang.Object...) [webcons]: https://developer.mozilla.org/en-US/docs/Web/API/console +## Blocks Module + +You hate having to lookup [Data Values][dv] when you use ScriptCraft's +Drone() functions. So do I. So I created this blocks object which is +a helper object for use in construction. + +### Examples + + box( blocks.oak ); // creates a single oak wood block + box( blocks.sand, 3, 2, 1 ); // creates a block of sand 3 wide x 2 high x 1 long + box( blocks.wool.green, 2 ); // creates a block of green wool 2 blocks wide + +Color aliased properties that were a direct descendant of the blocks +object are no longer used to avoid confusion with carpet and stained +clay blocks. In addition, there's a convenience array `blocks.rainbow` +which is an array of the 7 colors of the rainbow (or closest +approximations). + +The blocks module is globally exported by the Drone module. + +## Fireworks Module + +The fireworks module makes it easy to create fireworks using +ScriptCraft. The module has a single function `firework` which takes +a `org.bukkit.Location` as its 1 and only parameter. + +### Examples + +The module also extends the `Drone` object adding a `firework` method +so that fireworks can be created as a part of a Drone chain. For +Example.... + + /js firework() + +... creates a single firework, while .... + + /js firework().fwd(3).times(5) + +... creates 5 fireworks in a row. Fireworks have also been added as a +possible option for the `arrow` module. To have a firework launch +where an arrow strikes... + + /js arrows.firework() + +To call the fireworks.firework() function directly, you must provide a +location. For example... + + /js var fireworks = require('fireworks'); + /js fireworks.firework(self.location); + +![firework example](img/firework.png) + ## http.request() function The http.request() function will fetch a web address asynchronously (on a @@ -730,6 +696,47 @@ The following example illustrates how to use http.request to make a request to a var jsObj = eval("(" + responseBody + ")"); }); +String class extensions +----------------------- +The following chat-formatting methods are added to the javascript String class.. + + * aqua() + * black() + * blue() + * bold() + * brightgreen() + * darkaqua() + * darkblue() + * darkgray() + * darkgreen() + * purple() + * darkpurple() + * darkred() + * gold() + * gray() + * green() + * italic() + * lightpurple() + * indigo() + * green() + * red() + * pink() + * yellow() + * white() + * strike() + * random() + * magic() + * underline() + * reset() + +Example +------- + + /js var boldGoldText = "Hello World".bold().gold(); + /js self.sendMessage(boldGoldText); + +

Hello World

+ ## Utilities Module The `utils` module is a storehouse for various useful utility @@ -1001,99 +1008,6 @@ a given directory and recursiving trawling all sub-directories. return name.match(/\.js$/); }); -String class extensions ------------------------ -The following chat-formatting methods are added to the javascript String class.. - - * aqua() - * black() - * blue() - * bold() - * brightgreen() - * darkaqua() - * darkblue() - * darkgray() - * darkgreen() - * purple() - * darkpurple() - * darkred() - * gold() - * gray() - * green() - * italic() - * lightpurple() - * indigo() - * green() - * red() - * pink() - * yellow() - * white() - * strike() - * random() - * magic() - * underline() - * reset() - -Example -------- - - /js var boldGoldText = "Hello World".bold().gold(); - /js self.sendMessage(boldGoldText); - -

Hello World

- -## Blocks Module - -You hate having to lookup [Data Values][dv] when you use ScriptCraft's -Drone() functions. So do I. So I created this blocks object which is -a helper object for use in construction. - -### Examples - - box( blocks.oak ); // creates a single oak wood block - box( blocks.sand, 3, 2, 1 ); // creates a block of sand 3 wide x 2 high x 1 long - box( blocks.wool.green, 2 ); // creates a block of green wool 2 blocks wide - -Color aliased properties that were a direct descendant of the blocks -object are no longer used to avoid confusion with carpet and stained -clay blocks. In addition, there's a convenience array `blocks.rainbow` -which is an array of the 7 colors of the rainbow (or closest -approximations). - -The blocks module is globally exported by the Drone module. - -## Fireworks Module - -The fireworks module makes it easy to create fireworks using -ScriptCraft. The module has a single function `firework` which takes -a `org.bukkit.Location` as its 1 and only parameter. - -### Examples - -The module also extends the `Drone` object adding a `firework` method -so that fireworks can be created as a part of a Drone chain. For -Example.... - - /js firework() - -... creates a single firework, while .... - - /js firework().fwd(3).times(5) - -... creates 5 fireworks in a row. Fireworks have also been added as a -possible option for the `arrow` module. To have a firework launch -where an arrow strikes... - - /js arrows.firework() - -To call the fireworks.firework() function directly, you must provide a -location. For example... - - /js var fireworks = require('fireworks'); - /js fireworks.firework(self.location); - -![firework example](img/firework.png) - Drone Module ============ The Drone is a convenience class for building. It can be used for... @@ -1813,6 +1727,21 @@ To create a 2-line high message using glowstone... [imgbt1]: img/blocktype1.png +## Drone.rainbow() method + +Creates a Rainbow. + +### Parameters + + * radius (optional - default:18) - The radius of the rainbow + +### Example + + var d = new Drone(); + d.rainbow(30); + +![rainbow example](img/rainbowex1.png) + ## Drone.sphere() method Creates a sphere. @@ -1887,21 +1816,6 @@ To create a glass 'north' hemisphere with a radius of 20 blocks... ![hemisphere example](img/hemisphereex2.png) -## Drone.rainbow() method - -Creates a Rainbow. - -### Parameters - - * radius (optional - default:18) - The radius of the rainbow - -### Example - - var d = new Drone(); - d.rainbow(30); - -![rainbow example](img/rainbowex1.png) - ## Drone.spiral_stairs() method Constructs a spiral staircase with slabs at each corner. @@ -1929,6 +1843,272 @@ To construct a spiral staircase 5 floors high made of oak... spiral_stairs('oak', 5); +## Example Plugin #1 + +A simple minecraft plugin. The most basic module. + +### Usage: + +At the in-game prompt type ... + + /js hello(self) + +... and a message `Hello {player-name}` will appear (where + {player-name} is replaced by your own name). + +This example demonstrates the basics of adding new functionality which +is only usable by server operators or users with the +scriptcraft.evaluate permission. By default, only ops are granted this +permission. + +The `hello` function below is only usable by players with the scriptcraft.evaluate +permission since it relies on the `/js` command to execute. + + exports.hello = function(player){ + player.sendMessage('Hello ' + player.name); + }; + +## Example Plugin #2 + +A simple minecraft plugin. Commands for other players. + +### Usage: + +At the in-game prompt type ... + + /jsp hello + +... and a message `Hello {player-name}` will appear (where {player-name} is +replaced by your own name). + +This example demonstrates the basics of adding new functionality +which is usable all players or those with the scriptcraft.proxy +permission. By default, all players are granted this permission. + +This differs from example 1 in that a new 'jsp ' command extension +is defined. Since all players can use the `jsp` command, all players +can use the new extension. Unlike the previous example, the `jsp hello` +command does not evaluate javascript code so this command is much more secure. + + command('hello', function (parameters, player) { + player.sendMessage('Hello ' + player.name); + }); + +## Example Plugin #3 + +A simple minecraft plugin. Commands for operators only. + +### Usage: + +At the in-game prompt type ... + + /jsp op-hello + +... and a message `Hello {player-name}` will appear (where {player-name} is +replaced by your own name). + +This example demonstrates the basics of adding new functionality +which is usable all players or those with the scriptcraft.proxy +permission. By default, all players are granted this permission. In +this command though, the function checks to see if the player is an +operator and if they aren't will return immediately. + +This differs from example 2 in that the function will only print a +message for operators. + + command('op-hello', function (parameters, player) { + if (!player.op){ + player.sendMessage('Only operators can do this.'); + return; + } + player.sendMessage('Hello ' + player.name); + }); +## Example Plugin #4 +A simple minecraft plugin. Handling parameters. + +### Usage: + +At the in-game prompt type ... + + /jsp hello-params Hi + /jsp hello-params Saludos + /jsp hello-params Greetings + +... and a message `Hi {player-name}` or `Saludos {player-name}` etc +will appear (where {player-name} is replaced by your own name). + +This example demonstrates adding and using parameters in commands. + +This differs from example 3 in that the greeting can be changed from +a fixed 'Hello ' to anything you like by passing a parameter. + + command('hello-params', function (parameters, player) { + var salutation = parameters[0] ; + player.sendMessage( salutation + ' ' + player.name); + }); + +## Example Plugin #5 + +A simple minecraft plugin. Using Modules. + +### Usage: + +At the in-game prompt type ... + + /jsp hello-module + +... and a message `Hello {player-name}` will appear (where {player-name} is +replaced by your own name). + +This example demonstrates the use of modules. In +example-1-hello-module.js we created a new javascript module. In +this example, we use that module... + + * We load the module using the `require()` function. Because this + module and the module we require are n the same directory, we + specify `'./example-1-hello-module'` as the path (when loading a + module from the same directory, `./` at the start of the path + indicates that the file should be searched for in the same + directory. + + * We assign the loaded module to a variable (`greetings`) and then + use the module's `hello` method to display the message. + +Source Code... + + var greetings = require('./example-1-hello-module'); + command('hello-module', function( parameters, player ){ + greetings.hello(player); + }); + +## Example Plugin #6 + +A simple minecraft plugin. Finding players by name. + +### Usage: + +At the in-game prompt type ... + + /jsp hello-byname {player-name} + +... substituting {player-name} with the name of a player currently +online and a message `Hello ...` will be sent to the named +player. + +This example builds on example-5 and also introduces a new concept - +use of shared modules. That is : modules which are not specific to +any one plugin or set of plugins but which can be used by all +plugins. Shared modules should be placed in the +`scriptcraft/modules` directory. + + * The utils module is used. Because the 'utils' module is + located in the modules folder we don't need to specify an exact + path, just 'utils' will do. + + * The `utils.player()` function is used to obtain a player object + matching the player name. Once a player object is obtained, a + message is sent to that player. + +Source Code ... + + var utils = require('utils'); + var greetings = require('./example-1-hello-module'); + + command('hello-byname', function( parameters, sender ) { + var playerName = parameters[0]; + var recipient = utils.player(playerName); + if (recipient) + greetings.hello(recipient); + else + sender.sendMessage('Player ' + playerName + ' not found.'); + }); + +## Example Plugin #7 + +A simple event-driven minecraft plugin. How to handle Events. + + +This example demonstrates event-driven programming. The code below +will display the version of ScriptCraft every time an operator joins +the game. This module is notable from previous modules for the +following reasons... + + 1. It does not export any functions or variables. That's fine. Not + all modules need export stuff. Code in this module will be + executed when the module is first loaded. Because it is in the + `/scriptcraft/plugins` directory, it will be loaded automatically + when the server starts up. + + 2. It uses ScriptCraft's `events.on()` function to add a new *Event + Handler*. An *Event Handler* is a just a function which gets + called whenever a particular *event* happens in the game. The + function defined below will only be executed whenever a player + joins the game. This style of program is sometimes refered to as + *Event-Driven Programming*. + +Adding new *Event Handlers* in ScriptCraft is relatively easy. Use the +`events.on()` function to add a new event handler. It takes 2 +parameters... + + 1. The Event Name, in this case `'player.PlayerJoinEvent'`. You can + browse [all possible Bukkit events][bkevts] (click the 'Next + Package' and 'Previous Package' links to browse). + + 2. The event handling function (also sometimes refered to as a + 'callback'). In ScriptCraft, this function takes 2 parameters, a + listener object and an event object. All of the information about + the event is in the event object. + +In the example below, if a player joins the server and is an operator, +then the ScriptCraft plugin information will be displayed to that +player. + +What's also notable about this example is how it uses the [Bukkit +API][bkapi]. The code... + + if (event.player.op) + +... is a succinct way of accessing object properties which in Java +would have to be written as ... + + if (event.getPlayer().isOp()) + +... ScriptCraft uses a special version of JavaScript which comes +bundled with Java (Minecraft is written in Java) and JavaScript in +Java can access properties of Java objects more succinctly than in +Java itself. What this means in practice is that when you're perusing +the [Bukkit API Reference][bkapi] and come across a method like +[Player.getAllowFlight()][bkgaf], you can write code like this... + + var allowFlight = player.getAllowFlight(); // java style + +... or the more succinct ... + + var allowFlight = player.allowFlight; // javascript style + +... Which style you choose is up to you but `player.allowFlight` is +cleaner and more readable. Similarly where you see a method like +[Player.setAllowFlight()][bksaf], you can write ... + + player.setAllowFlight(true); // java style + +... or the more readable... + + player.allowFlight = true; // javascript style + +... Which style you choose is up to you. + +[bkevts]: http://jd.bukkit.org/dev/apidocs/org/bukkit/event/package-summary.html +[bkgaf]: http://jd.bukkit.org/dev/apidocs/org/bukkit/entity/Player.html#getAllowFlight() +[bksaf]: http://jd.bukkit.org/dev/apidocs/org/bukkit/entity/Player.html#setAllowFlight() +[bkapi]: http://jd.bukkit.org/dev/apidocs/ + + events.on('player.PlayerJoinEvent', function (listener, event){ + if (event.player.op) { + event.player.sendMessage('Welcome to ' + __plugin); + } + }); + ## Arrows Module The arrows mod adds fancy arrows to the game. Arrows which... @@ -2119,182 +2299,6 @@ global commands for a plugin, please let me know. [pcppevt]: http://jd.bukkit.org/dev/apidocs/org/bukkit/event/player/PlayerCommandPreprocessEvent.html -## Example Plugin #5 - -A simple minecraft plugin. Using Modules. - -### Usage: - -At the in-game prompt type ... - - /jsp hello-module - -... and a message `Hello {player-name}` will appear (where {player-name} is -replaced by your own name). - -This example demonstrates the use of modules. In -example-1-hello-module.js we created a new javascript module. In -this example, we use that module... - - * We load the module using the `require()` function. Because this - module and the module we require are n the same directory, we - specify `'./example-1-hello-module'` as the path (when loading a - module from the same directory, `./` at the start of the path - indicates that the file should be searched for in the same - directory. - - * We assign the loaded module to a variable (`greetings`) and then - use the module's `hello` method to display the message. - - - var greetings = require('./example-1-hello-module'); - command('hello-module', function( parameters, player ){ - greetings.hello(player); - }); - -## Example Plugin #3 - -A simple minecraft plugin. Commands for operators only. - -### Usage: - -At the in-game prompt type ... - - /jsp op-hello - -... and a message `Hello {player-name}` will appear (where {player-name} is -replaced by your own name). - -This example demonstrates the basics of adding new functionality -which is usable all players or those with the scriptcraft.proxy -permission. By default, all players are granted this permission. In -this command though, the function checks to see if the player is an -operator and if they aren't will return immediately. - -This differs from example 2 in that the function will only print a -message for operators. - - command('op-hello', function (parameters, player) { - if (!player.op){ - player.sendMessage('Only operators can do this.'); - return; - } - player.sendMessage('Hello ' + player.name); - }); -## Example Plugin #1 - -A simple minecraft plugin. The most basic module. - -### Usage: - -At the in-game prompt type ... - - /js hello(self) - -... and a message `Hello {player-name}` will appear (where - {player-name} is replaced by your own name). - -This example demonstrates the basics of adding new functionality which -is only usable by server operators or users with the -scriptcraft.evaluate permission. By default, only ops are granted this -permission. - -The `hello` function below is only usable by players with the scriptcraft.evaluate -permission since it relies on the `/js` command to execute. - - exports.hello = function(player){ - player.sendMessage('Hello ' + player.name); - }; - -## Example Plugin #6 - -A simple minecraft plugin. Finding players by name. - -### Usage: - -At the in-game prompt type ... - - /jsp hello-byname {player-name} - -... substituting {player-name} with the name of a player currently -online and a message `Hello ...` will be sent to the named -player. - -This example builds on example-5 and also introduces a new concept - -use of shared modules. That is : modules which are not specific to -any one plugin or set of plugins but which can be used by all -plugins. Shared modules should be placed in the -`scriptcraft/modules` directory. - - * The utils module is used. Because the 'utils' module is - located in the modules folder we don't need to specify an exact - path, just 'utils' will do. - - * The `utils.player()` function is used to obtain a player object - matching the player name. Once a player object is obtained, a - message is sent to that player. - - var utils = require('utils'); - var greetings = require('./example-1-hello-module'); - - command('hello-byname', function( parameters, sender ) { - var playerName = parameters[0]; - var recipient = utils.player(playerName); - if (recipient) - greetings.hello(recipient); - else - sender.sendMessage('Player ' + playerName + ' not found.'); - }); -## Example Plugin #2 - -A simple minecraft plugin. Commands for other players. - -### Usage: - -At the in-game prompt type ... - - /jsp hello - -... and a message `Hello {player-name}` will appear (where {player-name} is -replaced by your own name). - -This example demonstrates the basics of adding new functionality -which is usable all players or those with the scriptcraft.proxy -permission. By default, all players are granted this permission. - -This differs from example 1 in that a new 'jsp ' command extension -is defined. Since all players can use the `jsp` command, all players -can use the new extension. Unlike the previous example, the `jsp hello` -command does not evaluate javascript code so this command is much more secure. - - command('hello', function (parameters, player) { - player.sendMessage('Hello ' + player.name); - }); - -## Example Plugin #4 -A simple minecraft plugin. Handling parameters. - -### Usage: - -At the in-game prompt type ... - - /jsp hello-params Hi - /jsp hello-params Saludos - /jsp hello-params Greetings - -... and a message `Hi {player-name}` or `Saludos {player-name}` etc -will appear (where {player-name} is replaced by your own name). - -This example demonstrates adding and using parameters in commands. - -This differs from example 3 in that the greeting can be changed from -a fixed 'Hello ' to anything you like by passing a parameter. - - command('hello-params', function (parameters, player) { - var salutation = parameters[0] ; - player.sendMessage( salutation + ' ' + player.name); - }); - ## homes Module The homes plugin lets players set a location as home and return to the @@ -2354,6 +2358,21 @@ The following administration options can only be used by server operators... the world, it simply removes the location from the database. No blocks are destroyed by this command. +## NumberGuess mini-game: + +### Description +This is a very simple number guessing game. Minecraft will ask you to +guess a number between 1 and 10 and you will tell you if you're too +hight or too low when you guess wrong. The purpose of this mini-game +code is to demonstrate use of Bukkit's Conversation API. + +### Example + + /js Game_NumberGuess.start(self) + +Once the game begins, guess a number by typing the `/` character +followed by a number between 1 and 10. + ## SnowballFight mini-game ### Description @@ -2395,18 +2414,3 @@ player returns to their previous mode of play (creative or survival). Create a small arena with a couple of small buildings for cover to make the game more fun. -## NumberGuess mini-game: - -### Description -This is a very simple number guessing game. Minecraft will ask you to -guess a number between 1 and 10 and you will tell you if you're too -hight or too low when you guess wrong. The purpose of this mini-game -code is to demonstrate use of Bukkit's Conversation API. - -### Example - - /js Game_NumberGuess.start(self) - -Once the game begins, guess a number by typing the `/` character -followed by a number between 1 and 10. - diff --git a/src/docs/javascript/generateApiDocs.js b/src/docs/javascript/generateApiDocs.js index 8577523..c0ca1b9 100644 --- a/src/docs/javascript/generateApiDocs.js +++ b/src/docs/javascript/generateApiDocs.js @@ -1,6 +1,8 @@ /* This script is run at build time to generate api.md - a single Markdown document containing documentation for ScriptCraft's API */ +var err = java.lang.System.err; + args = args.slice(1); var dir = args[0]; var foreach = function(array, func){ @@ -38,17 +40,23 @@ var sorter = function( precedence ){ return function(a,b) { // convert from Java string to JS string - a = "" + a; - b = "" + b; + a = '' + a; + b = '' + b; var aparts = a.split(/\//); var bparts = b.split(/\//); - var adir = aparts.slice(3,aparts.length-1).join("/"); + var adir = aparts.slice(3,aparts.length-1).join('/'); var afile = aparts[aparts.length-1]; - var bdir = bparts.slice(3,bparts.length-1).join("/"); + var bdir = bparts.slice(3,bparts.length-1).join('/'); var bfile = bparts[bparts.length-1]; for (var i = 0;i < precedence.length; i++){ var re = precedence[i]; + if (a.match(re) && b.match(re)){ + if (afile < bfile) + return -1; + if (afile > bfile) + return 1; + } if (a.match(re)) return -1; if (b.match(re)) @@ -56,24 +64,35 @@ var sorter = function( precedence ){ } if(adirbdir) return 1; - afile = afile.replace(/\.js$/,""); - if (afile == adir) + afile = afile.replace(/\.js$/,''); + if (afile == adir){ return -1; - else - return 1; + } + else { + var result = 0; + if (afile < bfile){ + result = -1; + } + if (afile > bfile){ + result = 1; + } + //err.println("afile: " + afile + ", bfile:" + bfile + ",result=" + result); + + return result; + } }; }; var sortByModule = function(a,b) { - var aparts = (""+a).split(/\//); - var bparts = (""+b).split(/\//); + var aparts = (''+a).split(/\//); + var bparts = (''+b).split(/\//); var adir = aparts[aparts.length-2]; var afile = aparts[aparts.length-1]; var bdir = bparts[bparts.length-2]; var bfile = bparts[bparts.length-1]; - if (afile == "_scriptcraft.js") + if (afile == '_scriptcraft.js') return -1; - if (bfile == "_scriptcraft.js") + if (bfile == '_scriptcraft.js') return 1; if(adirbdir) return 1; @@ -86,15 +105,17 @@ var store = []; find(new File(dir),store,/\/[a-zA-Z0-9_\-]+\.js$/); store.sort(sorter([ - /scriptcraft\.js$/, - /require\.js$/, - /plugin\.js$/, - /events\.js$/, - /lib/, - /modules/, + /lib\/scriptcraft\.js$/, + /lib\/require\.js$/, + /lib\/plugin\.js$/, + /lib\/events\.js$/, + /lib\//, + /modules\//, /drone\.js/, - /drone/ + /drone\//, + /examples\// ])); +//err.println("store=" + JSON.stringify(store)); var contents = []; foreach(store, function(filename){ diff --git a/src/main/javascript/plugins/examples/example-5-hello-using-module.js b/src/main/javascript/plugins/examples/example-5-hello-using-module.js index a335033..80ae39f 100644 --- a/src/main/javascript/plugins/examples/example-5-hello-using-module.js +++ b/src/main/javascript/plugins/examples/example-5-hello-using-module.js @@ -16,16 +16,17 @@ This example demonstrates the use of modules. In example-1-hello-module.js we created a new javascript module. In this example, we use that module... - * We load the module using the `require()` function. Because this - module and the module we require are n the same directory, we - specify `'./example-1-hello-module'` as the path (when loading a - module from the same directory, `./` at the start of the path - indicates that the file should be searched for in the same - directory. + * We load the module using the `require()` function. Because this + module and the module we require are n the same directory, we + specify `'./example-1-hello-module'` as the path (when loading a + module from the same directory, `./` at the start of the path + indicates that the file should be searched for in the same + directory. - * We assign the loaded module to a variable (`greetings`) and then - use the module's `hello` method to display the message. + * We assign the loaded module to a variable (`greetings`) and then + use the module's `hello` method to display the message. +Source Code... var greetings = require('./example-1-hello-module'); command('hello-module', function( parameters, player ){ diff --git a/src/main/javascript/plugins/examples/example-6-hello-player.js b/src/main/javascript/plugins/examples/example-6-hello-player.js index 924d92c..0d1971b 100644 --- a/src/main/javascript/plugins/examples/example-6-hello-player.js +++ b/src/main/javascript/plugins/examples/example-6-hello-player.js @@ -19,13 +19,15 @@ any one plugin or set of plugins but which can be used by all plugins. Shared modules should be placed in the `scriptcraft/modules` directory. - * The utils module is used. Because the 'utils' module is - located in the modules folder we don't need to specify an exact - path, just 'utils' will do. + * The utils module is used. Because the 'utils' module is + located in the modules folder we don't need to specify an exact + path, just 'utils' will do. - * The `utils.player()` function is used to obtain a player object - matching the player name. Once a player object is obtained, a - message is sent to that player. + * The `utils.player()` function is used to obtain a player object + matching the player name. Once a player object is obtained, a + message is sent to that player. + +Source Code ... var utils = require('utils'); var greetings = require('./example-1-hello-module'); @@ -38,6 +40,7 @@ plugins. Shared modules should be placed in the else sender.sendMessage('Player ' + playerName + ' not found.'); }); + ***/ var utils = require('utils'); From e550ab7db227aa307ab4bee429dcaf930791653c Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Wed, 1 Jan 2014 15:30:12 +0000 Subject: [PATCH 072/456] Added section on event-driven programming and lookup tables to young persons' guide --- ...YoungPersonsGuideToProgrammingMinecraft.md | 305 +++++++++++++++--- 1 file changed, 258 insertions(+), 47 deletions(-) diff --git a/docs/YoungPersonsGuideToProgrammingMinecraft.md b/docs/YoungPersonsGuideToProgrammingMinecraft.md index 4772adf..35efac1 100644 --- a/docs/YoungPersonsGuideToProgrammingMinecraft.md +++ b/docs/YoungPersonsGuideToProgrammingMinecraft.md @@ -1,5 +1,40 @@ # The Young Person's Guide to Programming in Minecraft +## Table of Contents + + * [Introduction](#introduction) + * [Installation](#installation) + * [Learning Javascript](#learning-javascript) + * [First Steps](#first-steps) + * [Variables](#variables) + * [Functions](#functions) + * [Building stuff in Minecraft](#building-stuff-in-minecraft) + * [Common Block Materials](#common-block-materials) + * [Dimensions](#dimensions) + * [More Shapes](#more-shapes) + * [The Drone Object](#the-drone-object) + * [Movement](#movement) + * [Chaining - Combining Building and Movement](#chaining---combining-building-and-movement) + * [Exercise - Build a simple dwelling](#exercise---build-a-simple-dwelling) + * [Remembering where you started](#remembering-where-you-started) + * [Saving your work](#saving-your-work) + * [Your first Minecraft Mod!](#your-first-minecraft-mod) + * [Parameters](#parameters) + * [true or false](#true-or-false) + * [More fun with `true` or `false`](#more-fun-with-true-or-false) + * [...and Again, and Again, and Again,...](#and-again-and-again-and-again) + * [Counting to 100](#counting-to-100) + * [Saying "Hi!" to every player](#saying-hi-to-every-player) + * [While Loops](#while-loops) + * [`utils.foreach()` - Yet another way to process Arrays](#utilsforeach---yet-another-way-to-process-arrays) + * [Exercise](#exercise) + * [Putting `for` loops to use - Building a Skyscraper](#putting-for-loops-to-use---building-a-skyscraper) + * [Making Decisions](#making-decisions) + * [Event-Driven programming](#event-driven-programming) + * [Stop listening to events](#stop-listening-to-events) + * [Keeping Score - Lookup tables in Javascript](#keeping-score---lookup-tables-in-javascript) + * [Next Steps](#next-steps) + ## Introduction Minecraft is an open-ended 3D game where you can build and craft @@ -98,7 +133,7 @@ A variable is how you name something for the computer (and you the programmer) to remember. You create a new variable in Javascript using the `var` keyword... - /js var location = "Blackrock Castle" + /js var location = 'Blackrock Castle' ... creates a new variable called `location` and stores the text `Blackrock Castle` in it. Now the computer has a new item in its memory @@ -110,8 +145,8 @@ called `location`. We can use that name like this... Blackrock Castle -...You might be wondering where the `""` (called double-quotes) went. -When telling the computer to store some text, you have to put `"` +...You might be wondering where the `''` (called double-quotes) went. +When telling the computer to store some text, you have to put `'` (that's the double-quote character - press Shift+2) at the start and end of the text. The computer doesn't store these quote characters, only the text between them. The computer will store the variables while the @@ -121,7 +156,7 @@ pressing enter. You can repeat that statement as many times as you like and the computer will always display the same value. You can change the value like this... - /js location = "Mahon Point" + /js location = 'Mahon Point' ...notice this time I didn't use the `var` keyword. I didn't need to. The `var` keyword is only needed when you first create the variable. Now @@ -135,7 +170,7 @@ execute this command... Variables can be created and changed easily in Javascript. Along with the variables you'll create in your in-game commands and scripts, there -are handy "free" variables created for you by ScriptCraft. One such variable is +are handy *free* variables created for you by ScriptCraft. One such variable is `self`, it contains information about the current player (that's you)... /js echo ( self ) @@ -258,7 +293,7 @@ ground, then type the following and hit enter... ... This will change the targeted block to wood. What's happened here is the `box()` function has created a single new wooden -block. `blocks` is another one of those "free" variables you get in +block. `blocks` is another one of those *free* variables you get in ScriptCraft, you can see a list of block materials by typing ... /js blocks. @@ -274,21 +309,21 @@ In Minecraft Programming, Materials aren't known by their name, instead numbers (sometimes 2 numbers) are used to indicate which material should be used. For example the number 2 is grass, 1 is cobblestone etc, while 5 is wood (oak). There are different types of -wood so the text "5:1" means Spruce, "5:2" means Birch and "5:3" means +wood so the text '5:1' means Spruce, '5:2' means Birch and '5:3' means Jungle wood. There are many different materials in the Minecraft world, the most commonly used materials for building are: - * "4" - Cobblestone or `blocks.cobblestone` - * "5" - Wooden Planks or `blocks.oak` - * "5:2" - Birch wood Planks (light wood) or `blocks.birch` - * "98" - Stone bricks or `blocks.brick.stone` - * "45" - Red bricks or `blocks.brick.red` - * "68" - Sign or `blocks.sign` - * "102" - Glass panes (for windows) or `blocks.glass_pane` + * '4' - Cobblestone or `blocks.cobblestone` + * '5' - Wooden Planks or `blocks.oak` + * '5:2' - Birch wood Planks (light wood) or `blocks.birch` + * '98' - Stone bricks or `blocks.brick.stone` + * '45' - Red bricks or `blocks.brick.red` + * '68' - Sign or `blocks.sign` + * '102' - Glass panes (for windows) or `blocks.glass_pane` You can create a single wooden block using the numeric values or the `blocks` variable. For example... - /js box( "5" ) + /js box( '5' ) ... and ... @@ -375,7 +410,7 @@ A series of 2 boxes is created 3 blocks apart. OK. You know enough now about the `Drone` functions to be able to build a simple dwelling. The dwelling should be a hollow building with a sloped roof. *Don't worry about doors or windows for now*. The walls -should be made of Cobblestone ("4") and the roof made of wood ("5"). You can use +should be made of Cobblestone ('4') and the roof made of wood ('5'). You can use the following `Drone` functions to create a dwelling 7 blocks wide by 3 blocks high by 6 blocks long with a wooden sloped roof. It's up to you to figure out how. @@ -393,14 +428,14 @@ Your dwelling should end up looking something like this... Sometimes when you're building something big that requires lots of manoeuvering by your Drone, you need to leave breadcrumbs as you go so your `Drone` can return to where it started. Every new Drone has a -`"start"` checkpoint that it can return to by executing -`move("start")` ... +`'start'` checkpoint that it can return to by executing +`move('start')` ... - /js box("5").up(3).left(4).box("1").turn(3).fwd(5).right().box("1").move("start") + /js box('5').up(3).left(4).box('1').turn(3).fwd(5).right().box('1').move('start') ... A genius would have trouble figuring out how to get back to where they started. Fortunately, they don't have to - the -`move("start")` function will take the Drone back to its starting +`move('start')` function will take the Drone back to its starting point. * `chkpt( breadCrumb )` - Leaves a mark at your Drone's current @@ -440,7 +475,7 @@ object can do, let's use that knowledge to create a Minecraft Mod! Once you've installed Notepad++, Launch it, create a new file and type the following... exports.greet = function(player){ - player.sendMessage("Hi " + player.name); + player.sendMessage('Hi ' + player.name); } ... then save the file in a new directory @@ -455,7 +490,7 @@ type... ... to reload all of the server plugins. Your mod has just been loaded. Try it out by typing this command... - /js greet() + /js greet(self) ... it should display ... @@ -473,7 +508,7 @@ when creating more complex mods. The `exports` variable is a special variable you can use in your mod to provide functions, objects and variables for others to use. If you want to provide something for other programmers to use, you should -"export" it using the special `exports` variable. The syntax is +*export* it using the special `exports` variable. The syntax is straightforward and you can use the same `exports` variable to export one or more functions, objects or variables. For example... @@ -490,9 +525,9 @@ one or more functions, objects or variables. For example... and `snowball()` which can be invoked from the in-game prompt like this `/js egg(self)` or `/js snowball(self)`. -### Parameters +## Parameters If you want to change the `greet()` function so that it displays a -greeting other than "Hi " you can change the code in the `greet()` +greeting other than 'Hi ' you can change the code in the `greet()` function, or better still, you can use *Parameters*. Parameters are values you provide to a function so that the function behaves differently each time it is called. @@ -508,11 +543,11 @@ Change the `greet()` function so that it looks like this... ... Save your greet.js file and issue the `/js refresh()` command in minecraft. Now enter the following command in Minecraft... - greet("Hello ",self); + greet('Hello ',self); ... Now try ... - greet("Dia Dhuit ",self); + greet('Dia Dhuit ',self); ... you should see the following messages in your chat window... @@ -523,7 +558,7 @@ minecraft. Now enter the following command in Minecraft... they're called. As you'll see later, Parameters are very useful when changing the behaviour of MineCraft. -### true or false +## true or false Try entering each of the following statements and make a note of the answers given by minecraft... @@ -567,7 +602,7 @@ things... ... try comparing some more numbers yourself - say for example, compare the ages of your friends or siblings to your own age. -### More fun with `true` or `false` +## More fun with `true` or `false` You can find out if you can Fly in minecraft by typing the following statement... /js self.allowFlight @@ -616,14 +651,14 @@ called `API` calls - these are calls to functions and methods in Minecraft - you can read more about these on the [CraftBukkit API Reference][cbapi].) -### ...and Again, and Again, and Again,... +## ...and Again, and Again, and Again,... One of the things Computers are really good at is repetition. Computers don't get tired or bored of doing the same thing over and over again. Loops are handy, if you want to run the same code over and over again, each time with a different value. -#### Counting to 100 +### Counting to 100 At the in-game command prompt (hint: press 't') type the following then hit Enter... @@ -651,11 +686,11 @@ The `for` statement is useful when you want to repeat something over and over. I remember, an Array is just a list of things, for example - the players connnected to a server, the worlds of a server and so on. -#### Saying "Hi!" to every player +### Saying "Hi!" to every player At the in-game command prompt type the following then hit Enter... - /js for (var i = 0;i < server.onlinePlayers.length; i++){ server.onlinePlayers[i].sendMessage("Hi!"); } + /js for (var i = 0;i < server.onlinePlayers.length; i++){ server.onlinePlayers[i].sendMessage('Hi!'); } ... Lets look at these statements in more detail. We had to enter the statements on a single line at the in-game command prompt but the @@ -664,7 +699,7 @@ statements could be written like this... var players = server.onlinePlayers; for (var i = 0; i < players.length; i++) { var player = players[i]; - player.sendMessage("Hi!"); + player.sendMessage('Hi!'); } ... On the first line, a new variable `players` is created from the @@ -698,7 +733,7 @@ the bottom of the file... var players = server.onlinePlayers; for (var i = 0; i < players.length; i++) { var player = players[i]; - player.sendMessage("Hi!"); + player.sendMessage('Hi!'); } } @@ -709,7 +744,7 @@ loop and arrays. Arrays and `for` loops are used heavily in all types of software, in fact there probably isn't any software that doesn't use `for` loops and Arrays to get things done. -### While Loops +## While Loops Another way to repeat things over and over is to use a `while` loop. The following `while` loop counts to 100... @@ -752,7 +787,7 @@ a matter of personal taste, `for` loops are more commonly used with Arrays but as you see from the example above, `while` loops can also loop over Arrays. -### `utils.foreach()` - Yet another way to process Arrays +## `utils.foreach()` - Yet another way to process Arrays Both the `for` statement and `while` statement are standard commonly used javascript statements used for looping. ScriptCraft also comes @@ -805,7 +840,7 @@ utils.foreach() function... } ); -#### Exercise +### Exercise Try changing the above function so that different sounds are played instead of a Cat's Meow. You'll need to lookup the [CraftBukkit API's Sound class][soundapi] to see all of the possible sounds that can be @@ -816,7 +851,7 @@ provides `for` and `while` statements for looping and many javascript libraries also provide their own custom looping functions. You should use what you feel most comfortable with. -### Putting `for` loops to use - Building a Skyscraper +## Putting `for` loops to use - Building a Skyscraper For loops can be used to build enormous structures. In this next exercise I'm going to use a for loop to build a skyscraper. This @@ -849,7 +884,7 @@ type the following... return this.move('myskyscraper'); // return to where we started }; - load("../drone/drone.js"); + load('../drone/drone.js'); Drone.extend('myskyscraper',myskyscraper); ... so this takes a little explaining. First I create a new function @@ -885,7 +920,7 @@ that out, creating an entire city of blocks of skyscrapers is the next logical step. Of course, Minecraft doesn't have the same constraints as real-world densely populated areas so let your imagination go wild. -### Making Decisions +## Making Decisions All the programs we have seen so far have been fairly predictable - they went straight through the statements, and then went back to the beginning again. This is @@ -893,7 +928,7 @@ not very useful. In practice the computer would be expected to make decisions an act accordingly. The javascript statement used for making decisions is `if`. While standing on the ground in-game, type the following at the command prompt... - /js if ( self.flying ) { echo("Hey, You are flying!"); } + /js if ( self.flying ) { echo('Hey, You are flying!'); } ... No message should appear on screen. That is - `Hey, You are flying!` should *not* appear on screen. Now double-tap the `space` @@ -903,7 +938,7 @@ enter the same statement again (If you don't want to type the same statement again, just press `/` then press the `UP` cursor key on your keyboard, the statement you entered previously should reappear. - /js if ( self.flying ) { echo("Hey, You are flying!"); } + /js if ( self.flying ) { echo('Hey, You are flying!'); } ... this time the following message should have appeared on your screen... @@ -919,7 +954,7 @@ What if you wanted to display a message only if a condition is *not* true ? For example to only display a message if the player is *not* flying... - /js if ( ! self.flying ) { echo ("You are not flying."); } + /js if ( ! self.flying ) { echo ('You are not flying.'); } ... This code differs in that now there's a `!` (the exclamation mark) before `self.flying`. The `!` symbol negates (returns the opposite of) @@ -948,7 +983,183 @@ whether or not you're currently flying. Type the `/js flightStatus()` command while on the ground and while flying. The message displayed in each case should be different. -### Next Steps +## Event-Driven programming + +So far we've written code which executes when you invoke the `/js ` +command. What if - for example - you want to have some special +behaviour which occurs when a player joins the game? What if you +wanted to display a custom welcome message (in addition to the MotD - +message-of-the-day which is configurable in your server.properties +file) ? This is where *Event-Driven Programming* comes +in. Event-Driven Programming is just a fancy way of saying 'Do this +when that happens' where 'this' is a function you define, and 'that' +is some event which occurs. There are hundreds of events in the +minecraft game... + + * Every time someone joins the server - that's an event! + * Every time someone breaks a block - that's an event! + * Every time someone shoots an arrow - that's an event! and so on... + +You can write a function which will be called whenever a specific type +of event occurs, it's probably best to illustrate this by example. The +following code sends a message to any player who breaks a block in the +game... + + events.on('block.BlockBreakEvent', function (listener, event) { + var breaker = event.player; + breaker.sendMessage('You broke a block'); + }); + +The `events.on()` function is how you *register* a function which you +want to be called whenever a particular type of event occurs. In the +above code the first parameter `'block.BlockBreakEvent'` is the type +of event I want to listen for and the second parameter is the function +I want to be called when that event occurs. The function I want called +in turn takes 2 parameters. The `event` object has all the information +about the event which just occurred. I can tell who broke the block +and send a message to the player. The important thing to note is that +the function defined above will not be called until a player breaks a +block. Try it - save the above code in a new file in the +`scriptcraft/plugins` directory then type `/js refresh()` to reload +scriptcraft. Then break a block in the game and you should see the +message 'You broke a block'. + +There are many types of events you can listen for in Minecraft. You can +browse [all possible Bukkit events][bkevts] (click the 'Next +Package' and 'Previous Package' links to browse). + +It's important to note that when browsing the Bukkit API's +[org.bukkit.event][bkevts] package, if you see a class called +'org.bukkit.events.entity.EntityShootBowEvent', then when calling +`events.on()` you can listen to such an event using either the fully +qualified Class name... + + events.on(org.bukkit.events.entity.EntityShootBowEvent, function( listener, event) { + ... + }); + +or an abbreviated name in string form... + + events.on('entity.EntityShootBowEvent', function( listener, event) { + ... + }); + +If the `events.on()` function gets a String (text) as its first +parameter it automatically converts it to the appropriate Class by +prepending the 'org.bukkit.events' package. + +For custom events (events which aren't in the org.bukkit.event tree) +just specify the fully qualified class name instead. E.g. ... + + events.on ( net.yourdomain.events.YourEvent, function(listener, event ) { + ... + }); + +### Stop listening to events. + +If you want an event handler to only execute once, you can remove the handler like this... + + events.on('block.BlockBreakEvent', function(listener, evt) { + var breaker = evt.player; + breaker.sendMessage('You broke a block'); + evt.handlers.unregister( listener ); + }); + +The `evt.handlers.unregister( listener );` statement will remove this +function from the list of listeners for this event. + +## Keeping Score - Lookup tables in Javascript + +In the *Event-Driven Programming* section, I defined a function which +displayed a message to players every time they broke a block. Imagine +if I wanted to keep a count of how many blocks each player has broken? +This is where Javascript's Objecct literals come in handy. An object +literal in javascript is simply a way of creating a new Object +on-the-fly in your code. This is an example... + + var myNewObject = { name: 'walter', country: 'Ireland' }; + +... I created a new object with two properties 'name' and +'country'. The notation used to create this object is called JSON +which is short for JavaScript Object Notation. If I want to find out +the 'country' property of the myNewObject variable there are a few +ways I can do it... + + var playerCountry = myNewObject.country; + +... or ... + + var playerCountry = myNewObject['country'] + +... JavaScript lets you access any object property using either +dot-notation ( `object.property` ) or by index ( `object['property']` +). The result in both cases is the same - playerCountry will be +'Ireland'. When accessing the object by indexing, the property doesn't +even have to be a string literal - it can be a variable like this... + + var propertyName = 'country'; + var propertyValue = myNewObject[propertyName]; + +... in the above example, the propertyName variable is used when +indexing. What this means is that every object in JavaScript can act +like a lookup table? What's a lookup table? A table you 'look up' of +course. This is a table of names and scores... + + Name Score + -------- ----- + walter 5 + tom 6 + jane 8 + bart 7 + +... If I want to find Jane's score, I look *down* the list of names in +the name column until I find 'jane' then look *across* to get her +score. In Javascript, an object which stored such a table would look +like this... + + var scoreboard = { + walter: 5, + tom: 6, + jane: 8, + bart: 7 + }; + +... and if I wanted to write a function which took a player name as a +parameter and returned their score, I'd do it like this... + + function getScore(player){ + return scoreboard[ player ]; + } + +... I might call such a function like this... + + var janesScore = getScore('jane'); // returns 8 + +... putting it all together, a hypothetical scoreboard.js mdoule might +look something like this... + + var utils = require('utils'); + var scores = {}; + + exports.initialise = function(names){ + scores = {}; + utils.foreach(names, function(name){ + scores[name] = 0; + }); + }; + + /* changes score by diff e.g. to add 6 to the player's current score + updateScore('walter',6); // walter's new score = 5 + 6 = 11. + */ + exports.updateScore = function(name, diff){ + scores[name] += diff; + }; + + exports.getScore = function(name){ + return scores[name]; + }; + +## Next Steps This guide is meant as a gentle introduction to programming and modding Minecraft using the Javascript Programming Language. @@ -974,10 +1185,10 @@ different objects and methods available for use by ScriptCraft. [cbapi]: http://jd.bukkit.org/beta/apidocs/ [boole]: http://en.wikipedia.org/wiki/George_Boole [soundapi]: http://jd.bukkit.org/beta/apidocs/org/bukkit/Sound.html -[ap]: http://walterhiggins.net/blog/ScriptCraft-1-Month-later +[ap]: Anatomy-of-a-Plugin.md [api]: API-Reference.md [twl]: http://www.barebones.com/products/textwrangler/ - +[bkevts]: http://jd.bukkit.org/dev/apidocs/org/bukkit/event/package-summary.html [img_echo_date]: img/ypgpm_echo_date.png [img_3d_shapes]: img/ypgpm_3dshapes.jpg [img_whd]: img/ypgpm_whd.jpg From f87242413780be6564719d199d73d0f267dc0f61 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Wed, 1 Jan 2014 15:37:01 +0000 Subject: [PATCH 073/456] fixed typos in young persons guide --- docs/YoungPersonsGuideToProgrammingMinecraft.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/YoungPersonsGuideToProgrammingMinecraft.md b/docs/YoungPersonsGuideToProgrammingMinecraft.md index 35efac1..a396f77 100644 --- a/docs/YoungPersonsGuideToProgrammingMinecraft.md +++ b/docs/YoungPersonsGuideToProgrammingMinecraft.md @@ -393,7 +393,7 @@ Drone is easy... make the Drone turn twice so that it is facing in the opposite direction. -### Chaining - combining bulding and movement. +### Chaining - combining building and movement. You can make a Drone move around before and after building by *daisy-chaining* the building and movement functions together. In the @@ -405,7 +405,7 @@ A series of 2 boxes is created 3 blocks apart. ![Two Boxes 3 blocks apart][img_2boxes] -### Excercise - Build a simple dwelling +### Exercise - Build a simple dwelling OK. You know enough now about the `Drone` functions to be able to build a simple dwelling. The dwelling should be a hollow building with @@ -421,7 +421,7 @@ to you to figure out how. Your dwelling should end up looking something like this... -![Excercise Dwelling][img_ed] +![Exercise Dwelling][img_ed] ### Remembering where you started. From 220d246b42110e945eb24b8f200913ee74859ef8 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Wed, 1 Jan 2014 20:30:20 +0000 Subject: [PATCH 074/456] added subsection on keeping score for all players --- ...YoungPersonsGuideToProgrammingMinecraft.md | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/docs/YoungPersonsGuideToProgrammingMinecraft.md b/docs/YoungPersonsGuideToProgrammingMinecraft.md index a396f77..6f547be 100644 --- a/docs/YoungPersonsGuideToProgrammingMinecraft.md +++ b/docs/YoungPersonsGuideToProgrammingMinecraft.md @@ -33,6 +33,7 @@ * [Event-Driven programming](#event-driven-programming) * [Stop listening to events](#stop-listening-to-events) * [Keeping Score - Lookup tables in Javascript](#keeping-score---lookup-tables-in-javascript) + * [Counting block break events for each player](#counting-block-break-events-for-each-player) * [Next Steps](#next-steps) ## Introduction @@ -1102,7 +1103,7 @@ even have to be a string literal - it can be a variable like this... ... in the above example, the propertyName variable is used when indexing. What this means is that every object in JavaScript can act -like a lookup table? What's a lookup table? A table you 'look up' of +like a lookup table. What's a lookup table? A table you 'look up' of course. This is a table of names and scores... Name Score @@ -1159,6 +1160,32 @@ look something like this... return scores[name]; }; +## Counting block break events for each player + +I can use a Javascript lookup table (a plain old Javascript object) to +keep a count of how many blocks each player has broken ... + +#### block-break-counter.js + + var breaks = {}; + // every time a player joins the game reset their block-break-count to 0 + events.on('player.PlayerJoinEvent', function(listener, event){ + breaks[event.player] = 0; + }); + events.on('block.BlockBreakEvent', function(listener, event){ + var breaker = event.player; + var breakCount = breaks[breaker.name]; + breakCount++; // increment the count. + breaks[breaker.name] = breakCount; + + breaker.sendMessage('You broke ' + breakCount + ' blocks'); + + }); + +With a little more work, you could turn this into a game where players +compete against each other to break as many blocks as possible within +a given time period. + ## Next Steps This guide is meant as a gentle introduction to programming and From c774b5631f9d77d6632e3f9769e7a03474869057 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Wed, 1 Jan 2014 21:02:11 +0000 Subject: [PATCH 075/456] Moved scriptcraft dir from {craftbukkit-root}/scriptcraft to {craftbukkt-root}/plugins/scriptcraft in line with other plugins --- docs/release-notes.md | 14 ++++++++++++++ .../scriptcraft/ScriptCraftPlugin.java | 12 +++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index db37ff2..7187f10 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,3 +1,17 @@ +# 2014 01 01 + +'Buddha' Release - towards a total elimination of the 'self' variable. +The 'self' variable should only be used at the in-game or server +console command prompts and should not be used in modules or in +multi-threaded code. + +Moved scriptcraft directory from {craftbukkit-root}/scriptcraft to +{craftbukkit-root}/plugins/scriptcraft because this seems to be where +other plugins create plugin-specific directories on the filesystem. + +Documentation updates. Added new sections to the Young Persons Guide +to Modding Minecraft. + # 2013 12 30 Removing coffeescript support because coffeescript.js will not diff --git a/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java b/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java index 4cbe96a..bbdfae3 100644 --- a/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java +++ b/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java @@ -23,8 +23,9 @@ public class ScriptCraftPlugin extends JavaPlugin implements Listener // need to look at possibly having context/scope per operator //protected Map playerContexts = new HashMap(); protected ScriptEngine engine = null; - private static final String JS_PLUGINS_DIR = "scriptcraft"; - + private static final String JS_PLUGINS_DIR = "plugins/scriptcraft"; + private static final String JS_PLUGINS_ZIP = "scriptcraft.zip"; + /** * Unzips bundled javascript code. */ @@ -41,13 +42,14 @@ public class ScriptCraftPlugin extends JavaPlugin implements Listener jsPlugins.mkdir(); } - ZipInputStream zis = new ZipInputStream(getResource(JS_PLUGINS_DIR + ".zip")); + ZipInputStream zis = new ZipInputStream(getResource(JS_PLUGINS_ZIP)); ZipEntry entry; try { while ( ( entry = zis.getNextEntry() ) != null) { String filename = entry.getName(); - File newFile = new File(jsPlugins.getName() + File.separator + filename); + //File newFile = new File(jsPlugins.getName() + File.separator + filename); + File newFile = new File(jsPlugins, filename); //create all non exists folders //else you will hit FileNotFoundException for compressed folder @@ -67,7 +69,7 @@ public class ScriptCraftPlugin extends JavaPlugin implements Listener unzip = true; } if (unzip){ - getLogger().info("Unzipping " + filename); + getLogger().info("Unzipping " + newFile.getCanonicalPath()); FileOutputStream fout = new FileOutputStream(newFile); for (int c = zis.read(); c != -1; c = zis.read()) { fout.write(c); From 0682a5e69a390e19781186a14c344732bff1f4f1 Mon Sep 17 00:00:00 2001 From: Walter Higgins Date: Thu, 2 Jan 2014 10:37:34 +0000 Subject: [PATCH 076/456] Tweaked text in first paragraphs. --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 353a5ed..eea9584 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ # Let's begin... I created ScriptCraft to make it easier for younger programmers to -create their own Minecraft Mods. ScriptCraft makes it easier for new -programmers to create Minecraft mods. Mods are written using the -Javascript programming language and once the ScriptCraft mod is +create their own Minecraft Mods. Mods are written using the +Javascript programming language. Once the ScriptCraft mod is installed, you can add your own new Mods by adding Javascript (.js) files in a directory. @@ -18,7 +17,7 @@ files in a directory. ScriptCraft is a plugin for Minecraft Servers which lets operators, administrators and plug-in authors customize the game using Javascript. ScriptCraft makes it easier to create your own mods. Mods -can be written in Javscript and can use the full Bukkit API. The +can be written in Javscript and can use the full [Bukkit API][bukkit]. The ScriptCraft mod also lets you enter javascript commands at the in-game prompt. To bring up the in-game prompt press the `/` key then type `js ` followed by any javascript statement. E.g. `/js 1+1` will print @@ -33,6 +32,7 @@ Minecraft. [drone]: https://github.com/walterhiggins/ScriptCraft/tree/master/src/main/javascript/drone/drone.js [cottage]: https://github.com/walterhiggins/ScriptCraft/tree/master/src/main/javascript//drone/cottage.js +[bukkit]: http://dl.bukkit.org/ Prerequisites ============= From 1a247ecf1b140569d9880d52f85292e33f26a36a Mon Sep 17 00:00:00 2001 From: Walter Higgins Date: Thu, 2 Jan 2014 10:44:26 +0000 Subject: [PATCH 077/456] added simple plugin source --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index eea9584..39e7ff5 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,18 @@ files in a directory. then [Start Here][cda]. * Watch some [demos][ytpl] of what you can do with ScriptCraft. +This is a simple mod in a file called greet.js in the scriptcraft/plugins directory... + + exports.greet = function( player ) { + player.sendMessage('Hello ' + player.name ); + }; + +At the in-game prompt, type... + + /js greet(self) + +... to see the greeting. Anything you can do using CraftBukkit's API in Java, you can do using ScriptCraft in Javascript. + # Description ScriptCraft is a plugin for Minecraft Servers which lets operators, From 4803f3027a5c0892db007a90d76ce9e75fb9826c Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Thu, 2 Jan 2014 18:46:46 +0000 Subject: [PATCH 078/456] Added 'use strict' to lib modules. Added legacy directory check --- docs/API-Reference.md | 34 +++++++--- ...YoungPersonsGuideToProgrammingMinecraft.md | 18 +++--- src/main/javascript/lib/command.js | 1 + src/main/javascript/lib/console.js | 1 + src/main/javascript/lib/events.js | 1 + src/main/javascript/lib/plugin.js | 1 + src/main/javascript/lib/require.js | 3 +- src/main/javascript/lib/scriptcraft.js | 64 +++++++++++++++---- src/main/javascript/lib/tabcomplete-jsp.js | 1 + src/main/javascript/lib/tabcomplete.js | 1 + .../javascript/modules/utils/string-exts.js | 2 +- src/main/javascript/modules/utils/utils.js | 1 + 12 files changed, 95 insertions(+), 33 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 70249b5..ac8b153 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -49,14 +49,14 @@ module.exports instead of exports. ## Module Loading When the ScriptCraft Java plugin is first installed, a new -subdirectory is created in the craftbukkit directory. If your +subdirectory is created in the craftbukkit/plugins directory. If your craftbukkit directory is called 'craftbukkit' then the new subdirectories will be ... - * craftbukkit/scriptcraft/ - * craftbukkit/scriptcraft/plugins - * craftbukkit/scriptcraft/modules - * craftbukkit/scriptcraft/lib + * craftbukkit/plugins/scriptcraft/ + * craftbukkit/plugins/scriptcraft/plugins + * craftbukkit/plugins/scriptcraft/modules + * craftbukkit/plugins/scriptcraft/lib ... The `plugins`, `modules` and `lib` directories each serve a different purpose. @@ -317,7 +317,7 @@ 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]: http://walterhiggins.net/blog/ScriptCraft-1-Month-later +[anatomy]: ./Anatomy-of-a-Plugin.md ### command() function @@ -330,8 +330,18 @@ plugin author) safely expose javascript functions for use by players. #### Parameters - * 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. + * 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]). + * 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. @@ -393,7 +403,9 @@ A scriptcraft implementation of clearInterval(). ### refresh() function -The refresh() function will ... +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. @@ -411,6 +423,10 @@ parameter. The callback will be called when the ScriptCraft plugin is unloaded (usually as a result of a a `reload` command or server shutdown). +This function provides a way for ScriptCraft modules to do any +required cleanup/housekeeping just prior to the ScriptCraft Plugin +unloading. + ## require - Node.js-style module loading in ScriptCraft Node.js is a server-side javascript environment with an excellent diff --git a/docs/YoungPersonsGuideToProgrammingMinecraft.md b/docs/YoungPersonsGuideToProgrammingMinecraft.md index 6f547be..88a296f 100644 --- a/docs/YoungPersonsGuideToProgrammingMinecraft.md +++ b/docs/YoungPersonsGuideToProgrammingMinecraft.md @@ -480,11 +480,11 @@ Once you've installed Notepad++, Launch it, create a new file and type the follo } ... then save the file in a new directory -`craftbukkit/scriptcraft/plugins/{your_name}` (replace {your_name} with your -own name) and call the file `greet.js` (be sure to change the file-type -option to '*.* All Files' when saving or NotePad++ will add a '.txt' -extension to the filename. Now switch back to the Minecraft game and -type... +`craftbukkit/plugins/scriptcraft/plugins/{your_name}` (replace +{your_name} with your own name) and call the file `greet.js` (be sure +to change the file-type option to '*.* All Files' when saving or +NotePad++ will add a '.txt' extension to the filename. Now switch back +to the Minecraft game and type... /js refresh() @@ -501,10 +501,10 @@ loaded. Try it out by typing this command... minecraft username. Congratulations - You've just written your very first Minecraft Mod! With ScriptCraft installed, writing Minecraft Mods is as simple as writing a new javascript function and saving it -in a file in the craftbukkit/scriptcraft/plugins directory. This -function will now be avaible every time you launch minecraft. This is -a deliberately trivial minecraft mod but the principles are the same -when creating more complex mods. +in a file in the craftbukkit/plugins/scriptcraft/plugins +directory. This function will now be avaible every time you launch +minecraft. This is a deliberately trivial minecraft mod but the +principles are the same when creating more complex mods. The `exports` variable is a special variable you can use in your mod to provide functions, objects and variables for others to use. If you diff --git a/src/main/javascript/lib/command.js b/src/main/javascript/lib/command.js index 67b6ace..411aa17 100644 --- a/src/main/javascript/lib/command.js +++ b/src/main/javascript/lib/command.js @@ -1,3 +1,4 @@ +'use strict'; /* command management - allow for non-ops to execute approved javascript code. */ diff --git a/src/main/javascript/lib/console.js b/src/main/javascript/lib/console.js index 122af34..91c437b 100644 --- a/src/main/javascript/lib/console.js +++ b/src/main/javascript/lib/console.js @@ -1,3 +1,4 @@ +'use strict'; /************************************************************************* ## console global variable diff --git a/src/main/javascript/lib/events.js b/src/main/javascript/lib/events.js index fdb4357..4945955 100644 --- a/src/main/javascript/lib/events.js +++ b/src/main/javascript/lib/events.js @@ -1,3 +1,4 @@ +'use strict'; /************************************************************************ ## events Module diff --git a/src/main/javascript/lib/plugin.js b/src/main/javascript/lib/plugin.js index c43503c..87d7978 100644 --- a/src/main/javascript/lib/plugin.js +++ b/src/main/javascript/lib/plugin.js @@ -1,3 +1,4 @@ +'use strict'; var console = require('./console'); var File = java.io.File; var FileWriter = java.io.FileWriter; diff --git a/src/main/javascript/lib/require.js b/src/main/javascript/lib/require.js index 1cd1677..5bce81e 100644 --- a/src/main/javascript/lib/require.js +++ b/src/main/javascript/lib/require.js @@ -54,7 +54,7 @@ module specification, the '.js' suffix is optional. [cjsmodules]: http://wiki.commonjs.org/wiki/Modules/1.1.1. ***/ -( function (logger, evaluator, verbose, rootDir, modulePaths) { +(function (logger, evaluator, verbose, rootDir, modulePaths) { if (verbose){ logger.info("Setting up 'require' module system. Root Directory: " + rootDir); @@ -260,3 +260,4 @@ When resolving module names to file paths, ScriptCraft uses the following rules. }; return _requireClosure(new java.io.File(rootDir)); }) + diff --git a/src/main/javascript/lib/scriptcraft.js b/src/main/javascript/lib/scriptcraft.js index ec34e01..5cce36d 100644 --- a/src/main/javascript/lib/scriptcraft.js +++ b/src/main/javascript/lib/scriptcraft.js @@ -1,3 +1,4 @@ +'use strict'; /************************************************************************ # ScriptCraft API Reference @@ -50,14 +51,14 @@ module.exports instead of exports. ## Module Loading When the ScriptCraft Java plugin is first installed, a new -subdirectory is created in the craftbukkit directory. If your +subdirectory is created in the craftbukkit/plugins directory. If your craftbukkit directory is called 'craftbukkit' then the new subdirectories will be ... - * craftbukkit/scriptcraft/ - * craftbukkit/scriptcraft/plugins - * craftbukkit/scriptcraft/modules - * craftbukkit/scriptcraft/lib + * craftbukkit/plugins/scriptcraft/ + * craftbukkit/plugins/scriptcraft/plugins + * craftbukkit/plugins/scriptcraft/modules + * craftbukkit/plugins/scriptcraft/lib ... The `plugins`, `modules` and `lib` directories each serve a different purpose. @@ -318,7 +319,7 @@ 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]: http://walterhiggins.net/blog/ScriptCraft-1-Month-later +[anatomy]: ./Anatomy-of-a-Plugin.md ### command() function @@ -331,8 +332,18 @@ plugin author) safely expose javascript functions for use by players. #### Parameters - * 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. + * 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]). + * 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. @@ -394,7 +405,9 @@ A scriptcraft implementation of clearInterval(). ### refresh() function -The refresh() function will ... +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. @@ -412,6 +425,10 @@ parameter. The callback will be called when the ScriptCraft plugin is unloaded (usually as a result of a a `reload` command or server shutdown). +This function provides a way for ScriptCraft modules to do any +required cleanup/housekeeping just prior to the ScriptCraft Plugin +unloading. + ***/ /* @@ -437,8 +454,8 @@ function __onEnable (__engine, __plugin, __script) return "" + file.getCanonicalPath().replaceAll("\\\\","/"); }; - var parentFileObj = __script.parentFile; - var jsPluginsRootDir = parentFileObj.parentFile; + var libDir = __script.parentFile; // lib (assumes scriptcraft.js is in craftbukkit/plugins/scriptcraft/lib directory + var jsPluginsRootDir = libDir.parentFile; // scriptcraft var jsPluginsRootDirName = _canonize(jsPluginsRootDir); var _loaded = {}; @@ -466,11 +483,13 @@ function __onEnable (__engine, __plugin, __script) var reader = new FileReader(file); var br = new BufferedReader(reader); var code = ""; + var wrappedCode; try{ while ((r = br.readLine()) !== null) code += r + "\n"; - - result = __engine.eval("(" + code + ")"); + + wrappedCode = "(" + code + ")"; + result = __engine.eval(wrappedCode); // issue #103 avoid side-effects of || operator on Mac Rhino _loaded[canonizedFilename] = result ; if (!_loaded[canonizedFilename]) @@ -619,4 +638,23 @@ function __onEnable (__engine, __plugin, __script) } return result; }; + /* + wph 20140102 - warn if legacy 'craftbukkit/js-plugins' or 'craftbukkit/scriptcraft' directories are present + */ + var cbPluginsDir = jsPluginsRootDir.parentFile; + var cbDir = new File(cbPluginsDir.canonicalPath).parentFile; + var legacyDirs = [ + new File(cbDir, 'js-plugins'), + new File(cbDir, 'scriptcraft') + ]; + var legacyExists = false; + for (var i = 0; i < legacyDirs.length; i++){ + if (legacyDirs[i].exists() && legacyDirs[i].isDirectory()){ + legacyExists = true; + console.warn('Legacy ScriptCraft directory ' + legacyDirs[i].canonicalPath + ' was found. This directory is no longer used.'); + } + } + if (legacyExists){ + console.info('Please note that the working directory for ' + __plugin + ' is ' + jsPluginsRootDir.canonicalPath); + } } diff --git a/src/main/javascript/lib/tabcomplete-jsp.js b/src/main/javascript/lib/tabcomplete-jsp.js index 8a0c129..e2106ed 100644 --- a/src/main/javascript/lib/tabcomplete-jsp.js +++ b/src/main/javascript/lib/tabcomplete-jsp.js @@ -1,3 +1,4 @@ +'use strict'; var _commands = require('command').commands; /* Tab completion for the /jsp commmand diff --git a/src/main/javascript/lib/tabcomplete.js b/src/main/javascript/lib/tabcomplete.js index 885f6d0..9b85d3d 100644 --- a/src/main/javascript/lib/tabcomplete.js +++ b/src/main/javascript/lib/tabcomplete.js @@ -1,3 +1,4 @@ +'use strict'; var tabCompleteJSP = require('tabcomplete-jsp'); /* Tab Completion of the /js and /jsp commands diff --git a/src/main/javascript/modules/utils/string-exts.js b/src/main/javascript/modules/utils/string-exts.js index 8be2180..bb33e23 100644 --- a/src/main/javascript/modules/utils/string-exts.js +++ b/src/main/javascript/modules/utils/string-exts.js @@ -1,3 +1,4 @@ +'use strict'; /************************************************************************ String class extensions ----------------------- @@ -61,7 +62,6 @@ var formattingCodes = { italic: c.ITALIC, lightpurple: c.LIGHT_PURPLE, indigo: c.BLUE, - green: c.GREEN, red: c.RED, pink: c.LIGHT_PURPLE, yellow: c.YELLOW, diff --git a/src/main/javascript/modules/utils/utils.js b/src/main/javascript/modules/utils/utils.js index 1e45a3b..9ed6730 100644 --- a/src/main/javascript/modules/utils/utils.js +++ b/src/main/javascript/modules/utils/utils.js @@ -1,3 +1,4 @@ +'use strict'; /************************************************************************ ## Utilities Module From 8ff7020c76641a8e1427388ecb19d787eab9efdc Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Fri, 3 Jan 2014 09:18:43 +0000 Subject: [PATCH 079/456] fix bug: signs not being saved --- docs/release-notes.md | 8 +++++++- src/main/javascript/modules/signs/menu.js | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 7187f10..e081839 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,6 +1,12 @@ +# 2014 01 02 + +Added a warning in console at start-up if legacy directories are detected. +Added 'use strict' to core modules. +Bug Fix; Signs were not being saved. (introduced with recent change to JSONifying Location) + # 2014 01 01 -'Buddha' Release - towards a total elimination of the 'self' variable. +'Buddha' Release - towards a total annihilation of the 'self' variable. The 'self' variable should only be used at the in-game or server console command prompts and should not be used in modules or in multi-threaded code. diff --git a/src/main/javascript/modules/signs/menu.js b/src/main/javascript/modules/signs/menu.js index 397f25a..f07cb84 100644 --- a/src/main/javascript/modules/signs/menu.js +++ b/src/main/javascript/modules/signs/menu.js @@ -147,10 +147,10 @@ signs.menu = function( for (var i = 0; i < len ; i++) { var loc = signsOfSameLabel[i]; - var world = org.bukkit.Bukkit.getWorld(loc[0]); + var world = org.bukkit.Bukkit.getWorld(loc.world); if (!world) continue; - var block = world.getBlockAt(loc[1],loc[2],loc[3]); + var block = world.getBlockAt(loc.x,loc.y,loc.z); if (block.state instanceof org.bukkit.block.Sign){ convertToMenuSign(block.state,false); defragged.push(loc); From 6a73144c930c531c6d6b4727e4e21e0faea3bb0b Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 4 Jan 2014 16:57:43 +0000 Subject: [PATCH 080/456] generate table of contents for Young Persons guide --- build.xml | 23 +- ...YoungPersonsGuideToProgrammingMinecraft.md | 13 +- src/docs/javascript/generateTOC.js | 34 + src/docs/templates/ypgpm.mdt | 1192 +++++++++++++++++ 4 files changed, 1253 insertions(+), 9 deletions(-) create mode 100644 src/docs/javascript/generateTOC.js create mode 100644 src/docs/templates/ypgpm.mdt diff --git a/build.xml b/build.xml index e63da8c..159438f 100644 --- a/build.xml +++ b/build.xml @@ -61,7 +61,7 @@
- + @@ -72,6 +72,27 @@ + + + + + + + + + + + + + + +
# The Young Person's Guide to Programming in Minecraft + +
+ + +
+
diff --git a/docs/YoungPersonsGuideToProgrammingMinecraft.md b/docs/YoungPersonsGuideToProgrammingMinecraft.md index 88a296f..0cd95d2 100644 --- a/docs/YoungPersonsGuideToProgrammingMinecraft.md +++ b/docs/YoungPersonsGuideToProgrammingMinecraft.md @@ -1,7 +1,6 @@ # The Young Person's Guide to Programming in Minecraft ## Table of Contents - * [Introduction](#introduction) * [Installation](#installation) * [Learning Javascript](#learning-javascript) @@ -11,14 +10,14 @@ * [Building stuff in Minecraft](#building-stuff-in-minecraft) * [Common Block Materials](#common-block-materials) * [Dimensions](#dimensions) - * [More Shapes](#more-shapes) + * [More shapes](#more-shapes) * [The Drone Object](#the-drone-object) * [Movement](#movement) - * [Chaining - Combining Building and Movement](#chaining---combining-building-and-movement) + * [Chaining - combining building and movement.](#chaining---combining-building-and-movement) * [Exercise - Build a simple dwelling](#exercise---build-a-simple-dwelling) - * [Remembering where you started](#remembering-where-you-started) + * [Remembering where you started.](#remembering-where-you-started) * [Saving your work](#saving-your-work) - * [Your first Minecraft Mod!](#your-first-minecraft-mod) + * [Your First Minecraft Mod!](#your-first-minecraft-mod) * [Parameters](#parameters) * [true or false](#true-or-false) * [More fun with `true` or `false`](#more-fun-with-true-or-false) @@ -31,7 +30,7 @@ * [Putting `for` loops to use - Building a Skyscraper](#putting-for-loops-to-use---building-a-skyscraper) * [Making Decisions](#making-decisions) * [Event-Driven programming](#event-driven-programming) - * [Stop listening to events](#stop-listening-to-events) + * [Stop listening to events.](#stop-listening-to-events) * [Keeping Score - Lookup tables in Javascript](#keeping-score---lookup-tables-in-javascript) * [Counting block break events for each player](#counting-block-break-events-for-each-player) * [Next Steps](#next-steps) @@ -1227,5 +1226,3 @@ different objects and methods available for use by ScriptCraft. [img_ssf]: img/skyscraper_floor.png [img_ss]: img/skyscraper.png -## Categories -Minecraft, Programming, ScriptCraft diff --git a/src/docs/javascript/generateTOC.js b/src/docs/javascript/generateTOC.js new file mode 100644 index 0000000..555abf2 --- /dev/null +++ b/src/docs/javascript/generateTOC.js @@ -0,0 +1,34 @@ +args = args.slice(1); + +var template = args[0]; + +var BufferedReader = java.io.BufferedReader; +var FileReader = java.io.FileReader; + +var contents = [], line = undefined; +var br = new BufferedReader( new FileReader(template) ); + +while ( (line = br.readLine()) != null){ + contents.push(line); +} +br.close(); + +println('## Table of Contents'); + +for (var i = 0; i < contents.length; i++){ + line = contents[i]; + if (line.match(/^##\s+/)){ + var h2 = line.match(/^##\s+(.*)/)[1].trim(); + var link = h2.replace(/[^a-zA-Z0-9 \-]/g,''); + link = link.replace(/ /g,'-'); + link = link.toLowerCase(); + println (' * [' + h2 + '](#' + link + ')'); + } + if (line.match(/^###\s+/)){ + var h3 = line.match(/^###\s+(.*)/)[1].trim(); + var link = h3.replace(/[^a-zA-Z0-9 \-]/g,''); + var link = link.replace(/ /g,'-'); + link = link.toLowerCase(); + println (' * [' + h3 + '](#' + link + ')'); + } +} diff --git a/src/docs/templates/ypgpm.mdt b/src/docs/templates/ypgpm.mdt new file mode 100644 index 0000000..0d7ea54 --- /dev/null +++ b/src/docs/templates/ypgpm.mdt @@ -0,0 +1,1192 @@ + +## Introduction + +Minecraft is an open-ended 3D game where you can build and craft +anything you like. Minecraft can be extended and enhanced using 'Mods' +(short for 'modifications') - additional bits of code that are added +to the Game. ScriptCraft is one such Mod - it lets you program in +Javacript right within the game, making it possible to ... + + * Build using simple javascript statements. + * Extend the game in other interesting ways - add new Items, change + the game behaviour and create mini-games. + +Minecraft can be played in single-player or multi-player mode (with +friends). Most people play Minecraft in Multi-player mode where +players connect to a Minecraft Server on the internet or locally +(running on the player's computer). + +![Cottages created using ScriptCraft in MineCraft][img_cr] + +## Installation + +CraftBukkit is a version of the Minecraft server software which allows +easy addition of 'Mods' and extensions to Minecraft. ScriptCraft is a +'Mod' for use with CraftBukkit. Adding Mods to Minecraft can be +difficult but CraftBukkit makes it easy. Follow these steps to +Install ScriptCraft on your computer... + +1. [Download and install CraftBukkit][dlbuk]. Then follow the [Bukkit Installation Instructions][bii]. +[bii]: http://wiki.bukkit.org/Setting_up_a_server + +2. Start the CraftBukkit server, then once it has started up, stop it + by typing 'stop'. If you go to the craftbukkit folder (see step 1) you + should see some new files and subfolders. + +3. [Download the latest version of the ScriptCraft Mod][sc-plugin]. Then copy the ScriptCraft.jar file to the + `craftbukkit/plugins` folder (This folder won't be created until you run Bukkit for the first time (see previous step). + +4. In the CraftBukkit command window type `op {your_username}` and hit + enter, replacing {your_username} with your own minecraft + username. This will give you `operator` access meaning you can perform + more commands than are normally available in Minecraft. You should + make yourself a server operator (Server operators have full privileges + for the server) permanently by editing the craftbukkit/ops.txt file + and adding your username (one username per line). + +5. Start up the craftbukkit server again (see [instructions for starting the server][bii]). + +6. In the CraftBukkit command window type `js 1 + 1` and hit enter. You should see `> 2` . + +... Congratulations! You just installed your own Minecraft Server with +the ScriptCraft Mod and are now ready to begin programming in Minecraft. + +Normally, Minecraft Mods are written in Java. This makes writing your +own extension or game rules difficult because you must first learn Java. +Java is different enough from Javascript. With the ScriptCraft plug-in +installed, you don't have to learn Java, you can extend and customize +Minecraft your way using Javascript. Javascript is easier to learn than +Java but it's also more flexible and powerful and is used for creating +interactive web sites and many other applications. + +## Learning Javascript + +To begin creating cool stuff in Minecraft using ScriptCraft, you don't +*have* to know much JavaScript. ScriptCraft comes with lots of functions +to help you create buildings of any size, and lets you experiment while +you play. However, as you learn Javascript you will be able to create +cooler stuff in Minecraft - not just buildings, you'll be able to add +new rules and items to the game - even create mini-games for you and +your friends. If you want to get started learning JavaScript, check out +this [fun Javascript Tutorial][ce]. If you want to dive right in to +ScriptCraft, read on... + +## First Steps + +If you don't already know Javascript, don't worry, you'll learn a little +about Programming and Javascript along the way. You've set up a +Minecraft server and are ready to connect ... + +1. Launch Minecraft (keep the Bukkit Command window open). +2. Click 'Multi-Player' +3. Click 'Add Server' +4. Type any name you like in the name field then type `localhost` in the +address field. `localhost` is a special internet address that points to +your own computer. +5. Click 'Join Server' to join the craftbukkit server. +6. Once you've joined the game, press the `/` key located at the bottom +right of your keyboard. A prompt will appear. Type the following then +press enter: `js 1 + 1` The number 2 should be displayed. + +... Well Done! You've just confirmed you can run Javascript code from +within the Minecraft Console. + +## Variables + +A variable is how you name something for the computer (and you the +programmer) to remember. You create a new variable in Javascript using +the `var` keyword... + + /js var location = 'Blackrock Castle' + +... creates a new variable called `location` and stores the text +`Blackrock Castle` in it. Now the computer has a new item in its memory +called `location`. We can use that name like this... + + /js echo( location ) + +... and the following is displayed... + + Blackrock Castle + +...You might be wondering where the `''` (called double-quotes) went. +When telling the computer to store some text, you have to put `'` +(that's the double-quote character - press Shift+2) at the start and end +of the text. The computer doesn't store these quote characters, only the +text between them. The computer will store the variables while the +Minecraft Server is running. Repeat the last command you entered by +pressing the `/` key then the UP arrow key on your keyboard, then +pressing enter. You can repeat that statement as many times as you like +and the computer will always display the same value. You can change the +value like this... + + /js location = 'Mahon Point' + +...notice this time I didn't use the `var` keyword. I didn't need to. +The `var` keyword is only needed when you first create the variable. Now +execute this command... + + /js echo( location ) + +...and it displays... + + Mahon Point + +Variables can be created and changed easily in Javascript. Along with +the variables you'll create in your in-game commands and scripts, there +are handy *free* variables created for you by ScriptCraft. One such variable is +`self`, it contains information about the current player (that's you)... + + /js echo ( self ) + +... displays the following... + + CraftPlayer{name=walterh} + +... for me but the message displayed will be different for every player. + +## Functions + +ScriptCraft comes with a couple of extra functions not normally found in +Javascript. These functions will help you build new structures and +buildings which would otherwise take hours to build by hand. Before +looking at the building functions let's look at the `echo()` function. + +`echo()` - as its name implies - will echo back at you whatever you +tell it. For example, type ... + + /js echo('Hello') + +... and the game will display... + + Hello + +... type ... + + /js echo( 5 + 7 ) + +... and the game will display... + + 12 + +... While you can now use Minecraft to help with Maths homework - I +don't recommend it. Homework and Minecraft don't mix! The `echo()` +function will display anything you tell it to - Text, Numbers and other types... + + /js echo( new Date() ) + +... prints today's date. If the statement above looks confusing - don't +worry - `new Date()` creates a new date object - I'll talk about objects +later ... + + Tue Jan 08 2013 20:53:37 GMT-0000 (GMT) + +![Today's Date][img_echo_date] + +`echo()` is a very useful function but it is not part of the +Javascript Language. You can't use it outside of Minecraft. There are +many other functions in Javascript all of which you can also +use in Minecraft. For example... + + /js Math.max( 6, 11 ) + +... returns the larger of the 2 numbers you give it (max is short for +maximum). While... + + /js Math.min( 6, 11 ) + +... returns the smaller of the 2 numbers. That's another thing - +functions can `return` stuff. You can store the result of a function +(what it returns) in a variable like this... + + /js var biggest = Math.max( 6, 11 ) + +... Now type... + + /js biggest + +... Not all Javascript functions return data but most do. As well as +the functions provided to you by the Javascript Language and +ScriptCraft, you can write your own functions like this... + + /js function whatTimeIsIt () { return new Date() } + +... Here you've created a new `function` called `whatTimeIsIt` and +told the function it should return a new `Date` object every time it's +called. You'll notice the above statement didn't actually do anything +- it certainly didn't display the current time. That's because all + you've done is is say what the function should do when it's called, + you haven't called it yet. To call the function... + + /js whatTimeIsIt() + +... The current time is displayed. Congrats! You've just written your +first Javascript function - you're well on your way to becoming a +Minecraft Modder. There are many functions for working with Text, +numbers and dates in Javascript... + + /js Math.random() + +... prints out a random number every time you call it. Try it! Then press +the `/` key then the UP Arrow key to repeat the last statement in your +in-game console. You'll see the number displayed is different each +time. Think of Math.random() as a Dice with many many sides. You can +rely on it to never return the same value twice. + +## Building stuff in Minecraft + +Now we get to the fun stuff - creating structures and buildings in +Minecraft. Building by hand is fun but tedious when you want to build +big - Towers, Castles and Fortresses. That's where ScriptCraft comes in. +ScriptCraft comes with a couple of javascript functions that can be +combined to build interesting things. Let's start small though to get a +feel for how ScriptCraft's building functions work. The function you'll +probably use most for building is called `box()` and - as its name +implies - it is used to create cubes and cuboids of any size. A cube is +a 3D shape whose sides are all the same length. A cuboid is a 3D shape +whose width, height and length can differ. + +![3D Shapes][img_3d_shapes] + +You can create a Cube or a Cuboid in ScriptCraft using the `box()` +function. You must tell the function what material you want the shape to +be made of. For example, in the game, point the cross hairs at the +ground, then type the following and hit enter... + + /js box( blocks.oak ) + +... This will change the targeted block to wood. What's happened here +is the `box()` function has created a single new wooden +block. `blocks` is another one of those *free* variables you get in +ScriptCraft, you can see a list of block materials by typing ... + + /js blocks. + +... then pressing the `TAB` key. Repeatedly pressing the `TAB` key +will cycle through all of the block materials. Alternatively, you can +see many more current materials and the numbers Minecraft uses for +them by visiting the [Minecraft Data Values][mcdv] site. + +## Common Block Materials + +In Minecraft Programming, Materials aren't known by their name, +instead numbers (sometimes 2 numbers) are used to indicate which +material should be used. For example the number 2 is grass, 1 is +cobblestone etc, while 5 is wood (oak). There are different types of +wood so the text '5:1' means Spruce, '5:2' means Birch and '5:3' means +Jungle wood. There are many different materials in the Minecraft world, the most +commonly used materials for building are: + + * '4' - Cobblestone or `blocks.cobblestone` + * '5' - Wooden Planks or `blocks.oak` + * '5:2' - Birch wood Planks (light wood) or `blocks.birch` + * '98' - Stone bricks or `blocks.brick.stone` + * '45' - Red bricks or `blocks.brick.red` + * '68' - Sign or `blocks.sign` + * '102' - Glass panes (for windows) or `blocks.glass_pane` + +You can create a single wooden block using the numeric values or the `blocks` variable. For example... + + /js box( '5' ) + +... and ... + + /js box( blocks.oak ) + +... both do exactly the same thing but I personally prefer `/js box( +blocks.oak )` because it's easier to remember. For reference, here is +a chart of all of the blocks (not items) in the Minecraft world... + +![Minecraft Data Values][img_dv] + +## Dimensions + +`box()` can do more than just +create single blocks - it can create cubes and cuboids of any +size. Take a look at the following picture which shows how shapes are +measured in 3D space. There are 3 dimensions (or sizes) to consider. + +1. Width +2. Height +3. Depth (or length) - not to be confused with how deep underground a +mine-shaft can go. Think of Depth (or length if you prefer) as how far +away you want something to extend. + +![Width, Height and Depth][img_whd] + +## More shapes + + * `box0( block, width, height, depth )` - creates an empty box (with the + insides hollowed out - perfect for dwellings. `box0` will remove both + the floor and ceiling too. + * `cylinder( block, radius, height )` - creates cylinders, perfect for + Chimneys. + * `cylinder0( block, radius, height )` - creates empty cylinders - + perfect for Towers. `cylinder0` will remove both the floor and + ceiling too. + * `prism( block, width, depth )` - creates a Prism - good for roofs. + * `prism0( block, width, depth )` - creates an empty prism. + +## The Drone Object + +ScriptCraft is a Minecraft Mod that lets you execute Javascript code +in the game. It also lets you write your own Mod in Javacript. One +such mod that comes bundled with ScriptCraft is called the `Drone` +mod. The `Drone` is an (invsible) object you create every time you +execute any of the building or movement functions. When you execute... + + /js box(5,3,2,4) + +... a new `Drone` object is created and does the work of building on +your behalf. Think of a `Drone` as something like a remote control +plane that can move about freely and build things for you. Moving the +Drone is easy... + +### Movement + + * `up( numberOfBlocks )` - moves the Drone Up. For example: `up()` + will move the Drone 1 block up. You can tell it how many blocks to + move if you want it to move more than one block. + * `down( numberOfBlocks )` - moves the Drone Down. + * `left( numberOfBlocks )` - moves the Drone Left. + * `right( numberOfBlocs )` - moves the Drone Right. + * `fwd( numberOfBlocs )` - moves the Drone Forward (away from the player). + * `back( numberOfBlocs )` - moves the Drone Back (towards the player) + * `turn( numberOfTurns )` - Turns the Drone Clock-wise (right). For example: + `turn()` will make the Drone turn right 90 degrees. `turn(2)` will + make the Drone turn twice so that it is facing in the opposite + direction. + +### Chaining - combining building and movement. + +You can make a Drone move around before and after building by +*daisy-chaining* the building and movement functions together. In the +game, point at the ground then type the following... + + /js up(1).box( blocks.oak ).fwd(3).box( blocks.oak ) + +A series of 2 boxes is created 3 blocks apart. + +![Two Boxes 3 blocks apart][img_2boxes] + +### Exercise - Build a simple dwelling + +OK. You know enough now about the `Drone` functions to be able to +build a simple dwelling. The dwelling should be a hollow building with +a sloped roof. *Don't worry about doors or windows for now*. The walls +should be made of Cobblestone ('4') and the roof made of wood ('5'). You can use +the following `Drone` functions to create a dwelling 7 blocks wide by +3 blocks high by 6 blocks long with a wooden sloped roof. It's up +to you to figure out how. + + * `up()` + * `box0()` + * `prism0()` + +Your dwelling should end up looking something like this... + +![Exercise Dwelling][img_ed] + +### Remembering where you started. + +Sometimes when you're building something big that requires lots of +manoeuvering by your Drone, you need to leave breadcrumbs as you go so +your `Drone` can return to where it started. Every new Drone has a +`'start'` checkpoint that it can return to by executing +`move('start')` ... + + /js box('5').up(3).left(4).box('1').turn(3).fwd(5).right().box('1').move('start') + +... A genius would have trouble figuring out how to get back +to where they started. Fortunately, they don't have to - the +`move('start')` function will take the Drone back to its starting +point. + + * `chkpt( breadCrumb )` - Leaves a mark at your Drone's current + location so it can return there later. Think of it as giving a name + to the place where your Drone is located. `chkpt` is short for + Check-Point - a place in a game where you usually save your + progress. + + * `move( breadCrumb )` - Moves your Drone to a location you named + using `chkpt()` . It brings your Drone back to the place where you + saved it. + +Both `chkpt()` and `mark()` are useful for when you want to build +complex things that require your Drone to move about a lot ( for +example, Castles, mansions, palaces, etc). + +## Saving your work + +You can build cool things using the in-game command-prompt and the +`/js` command but sooner or later you'll probably want to build +something more complex and save your commands so you can run them +again when you quit the game and start it up again. + +[Notepad++][np] Is a special text editor (like Notepad which comes +installed on every Windows machine) that is well suited for writing +code. If you don't already have it on your machine, you can [install +Notepad++ here][np]. I recommend using NotePad++ rather than plain old +Notepad because it understands Javascript. If you prefer coding on a +Macintosh, then [TextWrangler][twl] is a good programming editor which +also understands Javascript code. + +## Your First Minecraft Mod! + +So, You've learnt a little bit about Javascript and what the Drone() +object can do, let's use that knowledge to create a Minecraft Mod! + +Once you've installed Notepad++, Launch it, create a new file and type the following... + + exports.greet = function(player){ + player.sendMessage('Hi ' + player.name); + } + +... then save the file in a new directory +`craftbukkit/plugins/scriptcraft/plugins/{your_name}` (replace +{your_name} with your own name) and call the file `greet.js` (be sure +to change the file-type option to '*.* All Files' when saving or +NotePad++ will add a '.txt' extension to the filename. Now switch back +to the Minecraft game and type... + + /js refresh() + +... to reload all of the server plugins. Your mod has just been +loaded. Try it out by typing this command... + + /js greet(self) + +... it should display ... + + Hi {your-username-here} + +... where {your-username-here} will be replaced with your own +minecraft username. Congratulations - You've just written your very +first Minecraft Mod! With ScriptCraft installed, writing Minecraft +Mods is as simple as writing a new javascript function and saving it +in a file in the craftbukkit/plugins/scriptcraft/plugins +directory. This function will now be avaible every time you launch +minecraft. This is a deliberately trivial minecraft mod but the +principles are the same when creating more complex mods. + +The `exports` variable is a special variable you can use in your mod +to provide functions, objects and variables for others to use. If you +want to provide something for other programmers to use, you should +*export* it using the special `exports` variable. The syntax is +straightforward and you can use the same `exports` variable to export +one or more functions, objects or variables. For example... + +#### thrower.js + + exports.egg = function(player){ + player.throwEgg(); + } + exports.snowball = function(player){ + player.throwSnowball(); + } + +... is a plugin which provides 2 javascript functions called `egg()` +and `snowball()` which can be invoked from the in-game prompt like +this `/js egg(self)` or `/js snowball(self)`. + +## Parameters +If you want to change the `greet()` function so that it displays a +greeting other than 'Hi ' you can change the code in the `greet()` +function, or better still, you can use *Parameters*. Parameters are +values you provide to a function so that the function behaves +differently each time it is called. + +![greeting][img_greet] + +Change the `greet()` function so that it looks like this... + + exports.greet = function ( greeting , player) { + player.sendMessage( greeting + player.name ); + } + +... Save your greet.js file and issue the `/js refresh()` command in +minecraft. Now enter the following command in Minecraft... + + greet('Hello ',self); + +... Now try ... + + greet('Dia Dhuit ',self); + +... you should see the following messages in your chat window... + + Hello {your name} + Dia Dhuit {your name} + +... Parameters let you provide different values to functions each time +they're called. As you'll see later, Parameters are very useful when +changing the behaviour of MineCraft. + +## true or false + +Try entering each of the following statements and make a note of the +answers given by minecraft... + + /js 1 < 2 + + /js 1 > 2 + +... the answer given by the first statement ( `1 < 2` ) should be +`true` since 1 is less than 2. The `<` symbol - usually found near the +bottom right of your keyboard - means test to see if something is less +than another so `1 < 2` is a way of asking the computer "is 1 less +than 2 ?". This is a silly example of course since we know 1 is less +than 2 but when dealing with variables we might not know in advance +what its value is or whether it's greater than (bigger) or less than +(smaller) another number or value. The result of the 2nd statement (`1 > 2`) +should be `false` since 1 is not greater than 2. Now try this... + + /js 1 = 2 + +... The result won't be what you expected. You'll see an Error message +- that's OK. What's happened here is I've tried to test to see if 1 is +equal to 2 but I've made one of the most common mistakes even +experienced programmers make. If you want to test to see if two things +are the same, you use `==` that's two equals signs right next to each +other. Let's try again... + + /js 1 == 2 + +... this time you should get an answer `false` since 1 obviously isn't +equal to 2. These are the different *operators* used when comparing +things... + + * `<` Is less than ? + * `>` Is greater than ? + * `==` Is equal to ? + * `<=` Is less than or equal to ? + * `>=` Is greather than or equal to ? + * `!=` Is not equal to ? + +... try comparing some more numbers yourself - say for example, +compare the ages of your friends or siblings to your own age. + +## More fun with `true` or `false` +You can find out if you can Fly in minecraft by typing the following statement... + + /js self.allowFlight + +... the result will be `true` or `false` depending on whether you can +fly or not. You can turn on and off your ability to fly by setting +your `allowFlight` property to `true` or `false`. Try it... + + /js self.allowFlight = true + +... Now you can fly! To turn off flight... + + /js self.allowFlight = false + +... and you come crashing down to earth. This is just one example of +how `true` and `false` are used throughout ScriptCraft - these are +called `boolean` values - named after [George Boole][boole], a 19th Century +Maths Professor at University College Cork. There are plenty more +examples of boolean values in Minecraft. You can find out if monsters +are allowed in your minecraft world by typing the following +statement... + + /js self.location.world.allowMonsters + +... The result of this statement will be either `false` (Phew!) or +`true` (Yikes!) depending on how your server has been +configured. However, typing the following statement doesn't work as +expected... + + /js self.location.world.allowMonsters = true + +... This statement won't work as expected - it will give an Error +message. This is because sometimes we can read variables but we can't +change them the same way we read them (this is because of how +Javascript, Java and the CraftBukkit API work together). To turn on or +off the spawning of monsters, type the following... + + /js self.location.world.setSpawnFlags(false, true) + +... the `setSpawnFlags()` method takes 2 parameters, the first +parameter says whether or not monsters can spawn, and the 2nd says +whether or not Animals can spawn. (SIDENOTE: You may be wondering how +to change other aspects of the Minecraft game - pretty much all +aspects of the game can be changed. Changes are made using what are +called `API` calls - these are calls to functions and methods in +Minecraft - you can read more about these on the [CraftBukkit API +Reference][cbapi].) + +## ...and Again, and Again, and Again,... + +One of the things Computers are really good at is +repetition. Computers don't get tired or bored of doing the same thing +over and over again. Loops are handy, if you want to run the same +code over and over again, each time with a different value. + +### Counting to 100 + +At the in-game command prompt (hint: press 't') type the following then hit Enter... + + /js for (var i = 1 ; i <= 100 ; i = i + 1) { echo( i ); } + +... The above code will count from 1 to 100. The first thing you'll +notice if you run the above code is how quickly the count +happened. You're probably curious how long it would take to count to +1000. Try it out for yourself. Change the above line of code so that +it counts to 1000 instead of 100. If you're feeling adventurous, see +how long it takes to count to ten thousand, one hundred thousand or even one million. + +The `for` statement is useful when you want to repeat something over and over. It has 4 parts... + + 1. The initialiser: `var i = 1` - this happens once at the start of the loop. + 2. The test: `i <= 100` - this happens at the start of each run around the loop. If the test fails, then the loop ends. + 3. The increment: `i = i + 1` - this happens at the end of each run + around the loop. If you didn't have a statement here, the loop might + never finish. `i = i + 1` is often written as `i++` - it's shorter + and does basically the same thing. + 4. The body - everything that appears between the `{` and `}` (opening and closing curly braces). + + +`for` loops becomes very useful when you combine it with Arrays - +remember, an Array is just a list of things, for example - the players +connnected to a server, the worlds of a server and so on. + +### Saying "Hi!" to every player + +At the in-game command prompt type the following then hit Enter... + + /js for (var i = 0;i < server.onlinePlayers.length; i++){ server.onlinePlayers[i].sendMessage('Hi!'); } + +... Lets look at these statements in more detail. We had to enter the +statements on a single line at the in-game command prompt but the +statements could be written like this... + + var players = server.onlinePlayers; + for (var i = 0; i < players.length; i++) { + var player = players[i]; + player.sendMessage('Hi!'); + } + +... On the first line, a new variable `players` is created from the +server object's onlinePlayers property. `players` is more concise and +easier to type than the long-winded `server.onlinePlayers`. On the +second line, the for loop is declared, a counter variable `i` is set +to 0 (zero - arrays in javascript start at 0 not 1) and each time +around the loop is tested to see if it's less than the number of +players online. At the end of each run around the loop the `i` +variable is incremented (increased by 1) so that the next player can +be messaged. Inside the body of the for loop (everything between the +opening `{` and closing `}` curly braces) the `players[i]` expression +refers to the player in the players array at position[i]. Imagine +there are 4 players online on a minecraft server, the `players` array +might look like this... + + * players[0] = 'CrafterJohn' + * players[1] = 'MinerPaul' + * players[2] = 'ExplorerRingo' + * players[3] = 'TraderGeorge' + +... in this case `players.length` will be 4 (since there are 4 online +players), the for-loop will go around 4 times starting from position 0 +and going all the way up to position 3, sending a message to each of +the players in the array. It's time for a new scriptcraft +function. Open the `hi.js` file you created earlier (using NotePad++ , +TextWrangler or your editor of choice) and add the following code at +the bottom of the file... + + exports.hiAll = function (){ + var players = server.onlinePlayers; + for (var i = 0; i < players.length; i++) { + var player = players[i]; + player.sendMessage('Hi!'); + } + } + +... save the file, at the in-game command prompt type `reload` and +then type `/js hiAll()`. This will send the message `Hi!` to all of +the players connected to your server. You've done this using a `for` +loop and arrays. Arrays and `for` loops are used heavily in all types +of software, in fact there probably isn't any software that doesn't +use `for` loops and Arrays to get things done. + +## While Loops + +Another way to repeat things over and over is to use a `while` +loop. The following `while` loop counts to 100... + + var i = 1; + while (i <= 100){ + console.log( i ); + i = i + 1; + } + +A `while` loop will repeat until its condition is `false` - the +condition in the above example is `i <= 100` so while i is less than +or equal to 100 the code within the `while` block (everything between +the starting `{` and ending `}` curly braces) will run. It's important +that you change the variable being tested in a while loop, otherwise +the while loop will never it - it will run forever. Try running the +following code... + + /js var i = 1; while (i <= 100){ echo( i ); } + +The code above will contine printing out the number 1 until the end of +time (or until you unplug your computer). That's because the `i` +variable is never incremented (remember - incrementing just means +adding 1 to it) so i will always be 1 and never changes meaning the +loop goes on forever. Again - this is a mistake even experienced programmers sometimes make. + +Just like `for` loops, `while` loops can be also be used to loop +through arrays. The following loop prints out all of the players on +the server... + + var players = server.onlinePlayers; + var i = 0; + while ( i < players.length ) { + console.log( players[i] ); + i = i + 1; + } + +... whether you chose to use a `for` loop or a `while` loop is largely +a matter of personal taste, `for` loops are more commonly used with +Arrays but as you see from the example above, `while` loops can also +loop over Arrays. + +## `utils.foreach()` - Yet another way to process Arrays + +Both the `for` statement and `while` statement are standard commonly +used javascript statements used for looping. ScriptCraft also comes +with a special function for looping called `utils.foreach()`. +utils.foreach() is a convenience function, you don't have to use it if +you prefer the syntax of javascript's `for` and `while` +loops. utils.foreach() takes two parameters... + + 1. An array + 2. A function which will be called for each item in the array. + +...that's right, you can pass functions as parameters in javascript! +Let's see it in action, the following code will `console.log()` (print) the +name of each online player in the server console window... + + utils.foreach( server.onlinePlayers, console.log ); + +... in the above example, the list of online players is processed one +at a time and each item (player) is passed to the `console.log` +function. Note here that I used `console.log` not `console.log()`. The round braces +() are used to call the function. If I want to pass the function as a +parameter, I just use the function name without the round braces. The +above example uses a named function which already exists ( `console.log` ), +you can also create new functions on-the-fly and pass them to the +utils.foreach() function... + + /* + give every player the ability to fly. + */ + var utils = require('utils'); + utils.foreach( server.onlinePlayers, + function (player) { + player.setAllowFlight(true); + } + ); + +... Another example, this time each player will hear a Cat's Meow... + + /* + Play a Cat's Meow sound for each player. + */ + var utils = require('utils'); + utils.foreach( server.onlinePlayers, + function (player) { + player.playSound(player.location, + org.bukkit.Sound.CAT_MEOW, + 1, + 1); + + } + ); + +### Exercise +Try changing the above function so that different sounds are played +instead of a Cat's Meow. You'll need to lookup the [CraftBukkit API's +Sound class][soundapi] to see all of the possible sounds that can be +played. + +Loops are a key part of programming in any language. Javascript +provides `for` and `while` statements for looping and many javascript +libraries also provide their own custom looping functions. You should +use what you feel most comfortable with. + +## Putting `for` loops to use - Building a Skyscraper + +For loops can be used to build enormous structures. In this next +exercise I'm going to use a for loop to build a skyscraper. This +skyscraper will be made of Glass and Steel (just like most skyscrapers +in real-life). The first thing to do is see what a single floor of +the skyscraper will look like. Place a block (of any type) where you +want to eventually build the skyscraper, then while your cursor is +pointing at the block, type the following into the in-game prompt... + + /js var drone = box(blocks.iron,20,1,20).up().box0(blocks.glass_pane,20,3,20).up(3) + +... you should a large (20x20) iron floor with 3 block high glass all around. + +![skyscraper-floor.png][img_ssf] + +... A skyscraper with just a single floor isn't much of a skyscraper +so the next step is to repeat this over and over. This is where `for` +loops come in. Open your favorite text editor and create a new file in +your scriptcraft/plugins/{your-name} directory, name the file `myskyscraper.js`, then +type the following... + + exports.myskyscraper = function(floors) + { + floors = floors || 10; // default number of floors is 10 + this.chkpt('myskyscraper'); // saves the drone position so it can return there later + for (var i = 0; i < floors; i++) + { + this.box(blocks.iron,20,1,20).up().box0(blocks.glass_pane,20,3,20).up(3); + } + return this.move('myskyscraper'); // return to where we started + }; + + load('../drone/drone.js'); + Drone.extend('myskyscraper',myskyscraper); + +... so this takes a little explaining. First I create a new function +called myskyscraper that will take a single parameter `floors` so that +when you eventually call the `myskyscraper()` function you can tell it +how many floors you want built. The first statement in the function +`floors = floors || 10;` just sets floors to 10 if no parameter is +supplied. The next statement `this.chkpt('myskyscraper')` just saves +the position of the Drone so it can eventually return to where it +started when finished building (I don't want the drone stranded atop +the skyscraper when it's finished). Then comes the `for` loop. I loop +from 0 to `floors` and each time through the loop I build a single +floor. When the loop is done I return the drone to where it started. +The last 2 lines load the drone module (it must be loaded before I can +add new features to it) and the last line extends the 'Drone' object +so that now it can build skyscrapers among other things. Once you've +typed in the above code and saved the file, type `reload` in your +in-game prompt, then type ... + + /js myskyscraper(2); + +... A two-story skyscraper should appear. If you're feeling +adventurous, try a 10 story skyscraper! Or a 20 story skyscraper! +Minecraft has a height limit (256 blocks from bedrock) beyond which +you can't build. If you try to build higher than this then building +will stop at that height. + +![skyscraper][img_ss] + +I'll leave it as an exercise to the reader to create a city block of +skyscrapers, 5 blocks apart using a for loop. Once you've figured +that out, creating an entire city of blocks of skyscrapers is the next +logical step. Of course, Minecraft doesn't have the same constraints +as real-world densely populated areas so let your imagination go wild. + +## Making Decisions + +All the programs we have seen so far have been fairly predictable - they went +straight through the statements, and then went back to the beginning again. This is +not very useful. In practice the computer would be expected to make decisions and +act accordingly. The javascript statement used for making decisions is `if`. +While standing on the ground in-game, type the following at the command prompt... + + /js if ( self.flying ) { echo('Hey, You are flying!'); } + +... No message should appear on screen. That is - `Hey, You are +flying!` should *not* appear on screen. Now double-tap the `space` +bar to start flying in-game (tap the space bar twice in rapid +succession), then press and hold space to rise above the ground. Now +enter the same statement again (If you don't want to type the same +statement again, just press `/` then press the `UP` cursor key on your +keyboard, the statement you entered previously should reappear. + + /js if ( self.flying ) { echo('Hey, You are flying!'); } + +... this time the following message should have appeared on your screen... + + Hey, You are flying! + +The `if` statement tests to see if something is `true` or `false` and +if `true` then the block of code between the curly braces ( `{` and +`}` ) is executed - but only if the condition is true. The condition +in the above example is `self.flying` which will be `true` if you are +currently flying or `false` if you aren't. + +What if you wanted to display a message only if a condition is *not* +true ? For example to only display a message if the player is *not* +flying... + + /js if ( ! self.flying ) { echo ('You are not flying.'); } + +... This code differs in that now there's a `!` (the exclamation mark) +before `self.flying`. The `!` symbol negates (returns the opposite of) +whatever follows it. + +What if you want to display a message in both cases - whether you're +flying or not? This is where the `if - else` construct comes in handy. +Open your favorite editor and type the following code into a new file +in your scriptcraft/plugins directory... + + function flightStatus(player) + { + if ( player.flying ) + { + player.sendMessage( 'Hey, You are flying!' ); + } + else + { + player.sendMessage( 'You are not flying.' ); + } + } + +... now type `/reload` at the in-game prompt then type `/js +flightStatus(self)` and an appropriate message will appear based on +whether or not you're currently flying. Type the `/js flightStatus()` +command while on the ground and while flying. The message displayed in +each case should be different. + +## Event-Driven programming + +So far we've written code which executes when you invoke the `/js ` +command. What if - for example - you want to have some special +behaviour which occurs when a player joins the game? What if you +wanted to display a custom welcome message (in addition to the MotD - +message-of-the-day which is configurable in your server.properties +file) ? This is where *Event-Driven Programming* comes +in. Event-Driven Programming is just a fancy way of saying 'Do this +when that happens' where 'this' is a function you define, and 'that' +is some event which occurs. There are hundreds of events in the +minecraft game... + + * Every time someone joins the server - that's an event! + * Every time someone breaks a block - that's an event! + * Every time someone shoots an arrow - that's an event! and so on... + +You can write a function which will be called whenever a specific type +of event occurs, it's probably best to illustrate this by example. The +following code sends a message to any player who breaks a block in the +game... + + events.on('block.BlockBreakEvent', function (listener, event) { + var breaker = event.player; + breaker.sendMessage('You broke a block'); + }); + +The `events.on()` function is how you *register* a function which you +want to be called whenever a particular type of event occurs. In the +above code the first parameter `'block.BlockBreakEvent'` is the type +of event I want to listen for and the second parameter is the function +I want to be called when that event occurs. The function I want called +in turn takes 2 parameters. The `event` object has all the information +about the event which just occurred. I can tell who broke the block +and send a message to the player. The important thing to note is that +the function defined above will not be called until a player breaks a +block. Try it - save the above code in a new file in the +`scriptcraft/plugins` directory then type `/js refresh()` to reload +scriptcraft. Then break a block in the game and you should see the +message 'You broke a block'. + +There are many types of events you can listen for in Minecraft. You can +browse [all possible Bukkit events][bkevts] (click the 'Next +Package' and 'Previous Package' links to browse). + +It's important to note that when browsing the Bukkit API's +[org.bukkit.event][bkevts] package, if you see a class called +'org.bukkit.events.entity.EntityShootBowEvent', then when calling +`events.on()` you can listen to such an event using either the fully +qualified Class name... + + events.on(org.bukkit.events.entity.EntityShootBowEvent, function( listener, event) { + ... + }); + +or an abbreviated name in string form... + + events.on('entity.EntityShootBowEvent', function( listener, event) { + ... + }); + +If the `events.on()` function gets a String (text) as its first +parameter it automatically converts it to the appropriate Class by +prepending the 'org.bukkit.events' package. + +For custom events (events which aren't in the org.bukkit.event tree) +just specify the fully qualified class name instead. E.g. ... + + events.on ( net.yourdomain.events.YourEvent, function(listener, event ) { + ... + }); + +### Stop listening to events. + +If you want an event handler to only execute once, you can remove the handler like this... + + events.on('block.BlockBreakEvent', function(listener, evt) { + var breaker = evt.player; + breaker.sendMessage('You broke a block'); + evt.handlers.unregister( listener ); + }); + +The `evt.handlers.unregister( listener );` statement will remove this +function from the list of listeners for this event. + +## Keeping Score - Lookup tables in Javascript + +In the *Event-Driven Programming* section, I defined a function which +displayed a message to players every time they broke a block. Imagine +if I wanted to keep a count of how many blocks each player has broken? +This is where Javascript's Objecct literals come in handy. An object +literal in javascript is simply a way of creating a new Object +on-the-fly in your code. This is an example... + + var myNewObject = { name: 'walter', country: 'Ireland' }; + +... I created a new object with two properties 'name' and +'country'. The notation used to create this object is called JSON +which is short for JavaScript Object Notation. If I want to find out +the 'country' property of the myNewObject variable there are a few +ways I can do it... + + var playerCountry = myNewObject.country; + +... or ... + + var playerCountry = myNewObject['country'] + +... JavaScript lets you access any object property using either +dot-notation ( `object.property` ) or by index ( `object['property']` +). The result in both cases is the same - playerCountry will be +'Ireland'. When accessing the object by indexing, the property doesn't +even have to be a string literal - it can be a variable like this... + + var propertyName = 'country'; + var propertyValue = myNewObject[propertyName]; + +... in the above example, the propertyName variable is used when +indexing. What this means is that every object in JavaScript can act +like a lookup table. What's a lookup table? A table you 'look up' of +course. This is a table of names and scores... + + Name Score + -------- ----- + walter 5 + tom 6 + jane 8 + bart 7 + +... If I want to find Jane's score, I look *down* the list of names in +the name column until I find 'jane' then look *across* to get her +score. In Javascript, an object which stored such a table would look +like this... + + var scoreboard = { + walter: 5, + tom: 6, + jane: 8, + bart: 7 + }; + +... and if I wanted to write a function which took a player name as a +parameter and returned their score, I'd do it like this... + + function getScore(player){ + return scoreboard[ player ]; + } + +... I might call such a function like this... + + var janesScore = getScore('jane'); // returns 8 + +... putting it all together, a hypothetical scoreboard.js mdoule might +look something like this... + + var utils = require('utils'); + var scores = {}; + + exports.initialise = function(names){ + scores = {}; + utils.foreach(names, function(name){ + scores[name] = 0; + }); + }; + + /* changes score by diff e.g. to add 6 to the player's current score + updateScore('walter',6); // walter's new score = 5 + 6 = 11. + */ + exports.updateScore = function(name, diff){ + scores[name] += diff; + }; + + exports.getScore = function(name){ + return scores[name]; + }; + +## Counting block break events for each player + +I can use a Javascript lookup table (a plain old Javascript object) to +keep a count of how many blocks each player has broken ... + +#### block-break-counter.js + + var breaks = {}; + // every time a player joins the game reset their block-break-count to 0 + events.on('player.PlayerJoinEvent', function(listener, event){ + breaks[event.player] = 0; + }); + events.on('block.BlockBreakEvent', function(listener, event){ + var breaker = event.player; + var breakCount = breaks[breaker.name]; + breakCount++; // increment the count. + breaks[breaker.name] = breakCount; + + breaker.sendMessage('You broke ' + breakCount + ' blocks'); + + }); + +With a little more work, you could turn this into a game where players +compete against each other to break as many blocks as possible within +a given time period. + +## Next Steps + +This guide is meant as a gentle introduction to programming and +modding Minecraft using the Javascript Programming Language. +Javascript is a very powerful and widely-used programming language and +there are many more aspects and features of the language which are not +covered here. If you want to dive deeper into programming and modding +minecraft, I recommend reading the accompanying [ScriptCraft API +reference][api] which covers all of the ScriptCraft functions, objects +and methods. I also recommend reading the source code to some of the +existing scriptcraft add-ons, the *chat* module ( +`scriptcraft/plugins/chat/color.js` ) is a good place to start, followed by +[Anatomy of a ScriptCraft Plug-in][ap]. The online [Craftbukkit API +Reference][cbapi] provides lots of valuable information about the +different objects and methods available for use by ScriptCraft. + + +[buk]: http://wiki.bukkit.org/Setting_up_a_server +[dlbuk]: http://dl.bukkit.org/ +[sc-plugin]: http://scriptcraftjs.org/download/ +[ce]: http://www.codecademy.com/ +[mcdv]: http://www.minecraftwiki.net/wiki/Data_values +[np]: http://notepad-plus-plus.org/ +[cbapi]: http://jd.bukkit.org/beta/apidocs/ +[boole]: http://en.wikipedia.org/wiki/George_Boole +[soundapi]: http://jd.bukkit.org/beta/apidocs/org/bukkit/Sound.html +[ap]: Anatomy-of-a-Plugin.md +[api]: API-Reference.md +[twl]: http://www.barebones.com/products/textwrangler/ +[bkevts]: http://jd.bukkit.org/dev/apidocs/org/bukkit/event/package-summary.html +[img_echo_date]: img/ypgpm_echo_date.png +[img_3d_shapes]: img/ypgpm_3dshapes.jpg +[img_whd]: img/ypgpm_whd.jpg +[img_dv]: img/ypgpm_datavalues.png +[img_ed]: img/ypgpm_ex_dwell.png +[img_2boxes]: img/ypgpm_2boxes.png +[img_cr]: img/ypgpm_mc_cr.png +[img_greet]: img/ypgpm_greet.png +[img_ssf]: img/skyscraper_floor.png +[img_ss]: img/skyscraper.png + From 2ee9dd651544c7fc5c011d00cabd00b54fdbaede Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 4 Jan 2014 18:39:49 +0000 Subject: [PATCH 081/456] Added Table of Contents to API Reference - Issue #104 --- build.xml | 45 +- docs/API-Reference.md | 497 +++++++++++------- src/main/javascript/lib/scriptcraft.js | 64 +-- src/main/javascript/plugins/alias/alias.js | 2 +- src/main/javascript/plugins/arrows.js | 2 +- .../javascript/plugins/classroom/classroom.js | 2 +- .../javascript/plugins/drone/blocktype.js | 6 +- .../plugins/drone/contrib/rainbow.js | 6 +- .../plugins/drone/contrib/spiral_stairs.js | 6 +- src/main/javascript/plugins/drone/drone.js | 247 ++++----- src/main/javascript/plugins/drone/sphere.js | 24 +- .../examples/example-1-hello-module.js | 2 +- .../examples/example-2-hello-command.js | 2 +- .../examples/example-3-hello-ops-only.js | 2 +- .../examples/example-4-hello-parameters.js | 3 +- .../examples/example-5-hello-using-module.js | 2 +- .../examples/example-6-hello-player.js | 2 +- .../examples/example-7-hello-events.js | 2 +- src/main/javascript/plugins/homes/homes.js | 2 +- 19 files changed, 503 insertions(+), 415 deletions(-) diff --git a/build.xml b/build.xml index 159438f..749010c 100644 --- a/build.xml +++ b/build.xml @@ -61,20 +61,53 @@ - + + + + - + + + - + + + +
# ScriptCraft API Reference + +Walter Higgins + +[walter.higgins@gmail.com][email] + +[email]: mailto:walter.higgins@gmail.com?subject=ScriptCraft_API_Reference + +
+ + +
+
+ + - + + + + + + + + + + + + @@ -84,12 +117,12 @@ - +
# The Young Person's Guide to Programming in Minecraft
- +
diff --git a/docs/API-Reference.md b/docs/API-Reference.md index ac8b153..c450098 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -6,6 +6,134 @@ Walter Higgins [email]: mailto:walter.higgins@gmail.com?subject=ScriptCraft_API_Reference +## Table of Contents + * [Modules in Scriptcraft](#modules-in-scriptcraft) + * [Module Loading](#module-loading) + * [The plugins directory](#the-plugins-directory) + * [The modules directory](#the-modules-directory) + * [The lib directory](#the-lib-directory) + * [plugins sub-directories](#plugins-sub-directories) + * [Global variables](#global-variables) + * [__plugin variable](#plugin-variable) + * [server variable](#server-variable) + * [self variable](#self-variable) + * [config variable](#config-variable) + * [events variable](#events-variable) + * [Module variables](#module-variables) + * [__filename variable](#9595filename-variable) + * [__dirname variable](#9595dirname-variable) + * [Global functions](#global-functions) + * [echo function](#echo-function) + * [require() function](#require-function) + * [load() function](#load-function) + * [save() function](#save-function) + * [plugin() function](#plugin-function) + * [command() function](#command-function) + * [setTimeout() function](#settimeout-function) + * [clearTimeout() function](#cleartimeout-function) + * [setInterval() function](#setinterval-function) + * [clearInterval() function](#clearinterval-function) + * [refresh() function](#refresh-function) + * [addUnloadHandler() function](#addunloadhandler-function) + * [require - Node.js-style module loading in ScriptCraft](#require---nodejs-style-module-loading-in-scriptcraft) + * [math.js](#mathjs) + * [inc.js](#incjs) + * [program.js](#programjs) + * [Important](#important) + * [module name resolution](#module-name-resolution) + * [events Module](#events-module) + * [events.on() static method](#eventson-static-method) + * [console global variable](#console-global-variable) + * [Example](#example) + * [Using string substitutions](#using-string-substitutions) + * [Blocks Module](#blocks-module) + * [Examples](#examples) + * [Fireworks Module](#fireworks-module) + * [Examples](#examples) + * [http.request() function](#httprequest-function) + * [Parameters](#parameters) + * [Example](#example) + * [Utilities Module](#utilities-module) + * [utils.player() function](#utilsplayer-function) + * [utils.locationToJSON() function](#utilslocationtojson-function) + * [utils.locationToString() function](#utilslocationtostring-function) + * [utils.locationFromJSON() function](#utilslocationfromjson-function) + * [utils.getPlayerPos() function](#utilsgetplayerpos-function) + * [utils.getMousePos() function](#utilsgetmousepos-function) + * [utils.foreach() function](#utilsforeach-function) + * [utils.nicely() function](#utilsnicely-function) + * [utils.at() function](#utilsat-function) + * [utils.find() function](#utilsfind-function) + * [Drone Plugin](#drone-plugin) + * [TLDNR; (Just read this if you're impatient)](#tldnr-just-read-this-if-youre-impatient) + * [Constructing a Drone Object](#constructing-a-drone-object) + * [Drone.box() method](#dronebox-method) + * [Drone.box0() method](#dronebox0-method) + * [Drone.boxa() method](#droneboxa-method) + * [Drone Movement](#drone-movement) + * [Drone Positional Info](#drone-positional-info) + * [Drone Markers](#drone-markers) + * [Drone.prism() method](#droneprism-method) + * [Drone.prism0() method](#droneprism0-method) + * [Drone.cylinder() method](#dronecylinder-method) + * [Drone.cylinder0() method](#dronecylinder0-method) + * [Drone.arc() method](#dronearc-method) + * [Drone.door() method](#dronedoor-method) + * [Drone.door2() method](#dronedoor2-method) + * [Drone.sign() method](#dronesign-method) + * [Drone Trees methods](#drone-trees-methods) + * [Drone.garden() method](#dronegarden-method) + * [Drone.rand() method](#dronerand-method) + * [Copy & Paste using Drone](#copy--paste-using-drone) + * [Drone.copy() method](#dronecopy-method) + * [Drone.paste() method](#dronepaste-method) + * [Chaining](#chaining) + * [Drone Properties](#drone-properties) + * [Extending Drone](#extending-drone) + * [Drone.extend() static method](#droneextend-static-method) + * [Drone Constants](#drone-constants) + * [Drone.times() Method](#dronetimes-method) + * [Drone.blocktype() method](#droneblocktype-method) + * [Drone.rainbow() method](#dronerainbow-method) + * [Drone.sphere() method](#dronesphere-method) + * [Drone.sphere0() method](#dronesphere0-method) + * [Drone.hemisphere() method](#dronehemisphere-method) + * [Drone.hemisphere0() method](#dronehemisphere0-method) + * [Drone.spiral_stairs() method](#dronespiralstairs-method) + * [Example Plugin #1 - A simple extension to Minecraft.](#example-plugin-1---a-simple-extension-to-minecraft) + * [Usage:](#usage) + * [Example Plugin #2 - Making extensions available for all players.](#example-plugin-2---making-extensions-available-for-all-players) + * [Usage:](#usage) + * [Example Plugin #3 - Limiting use of commands to operators only.](#example-plugin-3---limiting-use-of-commands-to-operators-only) + * [Usage:](#usage) + * [Example Plugin #4 - Using parameters in commands.](#example-plugin-4---using-parameters-in-commands) + * [Usage:](#usage) + * [Example Plugin #5 - Re-use - Using your own and others modules.](#example-plugin-5---re-use---using-your-own-and-others-modules) + * [Usage:](#usage) + * [Example Plugin #6 - Re-use - Using 'utils' to get Player objects.](#example-plugin-6---re-use---using-utils-to-get-player-objects) + * [Usage:](#usage) + * [Example Plugin #7 - Listening for events, Greet players when they join the game.](#example-plugin-7---listening-for-events-greet-players-when-they-join-the-game) + * [Arrows Plugin](#arrows-plugin) + * [Usage:](#usage) + * [alias Plugin](#alias-plugin) + * [Examples](#examples) + * [Classroom Plugin](#classroom-plugin) + * [classroom.allowScripting() function](#classroomallowscripting-function) + * [Commando Plugin](#commando-plugin) + * [Description](#description) + * [Example hi-command.js](#example-hi-commandjs) + * [Example - timeofday-command.js](#example---timeofday-commandjs) + * [Caveats](#caveats) + * [homes Plugin](#homes-plugin) + * [Basic options](#basic-options) + * [Social options](#social-options) + * [Administration options](#administration-options) + * [NumberGuess mini-game:](#numberguess-mini-game) + * [Description](#description) + * [Example](#example) + * [SnowballFight mini-game](#snowballfight-mini-game) + * [Description](#description) + ## Modules in Scriptcraft ScriptCraft has a simple module loading system. In ScriptCraft, files @@ -158,10 +286,10 @@ 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 +### __filename variable The current file - this variable is only relevant from within the context of a Javascript module. -### __dirname variable +### __dirname variable The current directory - this variable is only relevant from within the context of a Javascript module. ## Global functions @@ -174,7 +302,7 @@ 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). -### Example +#### Example /js echo('Hello World') @@ -182,7 +310,7 @@ 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 +#### Notes The `echo` and `alert` functions are provided as convenience functions for beginning programmers. The use of these 2 functions is not @@ -190,26 +318,7 @@ 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]: - - * require (modulename) - Will load modules. See [Node.js modules][njsmod] - - * load (filename,warnOnFileNotFound) - loads and evaluates a - javascript file, returning the evaluated object. (Note: Prefer - `require()` to `load()`) - - * save (object, filename) - saves an object to a file. - - * plugin (name, interface, isPersistent) - defines a new plugin. If - isPersistent is true then the plugin doesn't have to worry about - loading and saving state - that will be done by the framework. Just - make sure that anything you want to save (and restore) is in the plugin's - 'store' property - this will be created automatically if not - already defined. (its type is object {} ) . More on plugins below. - - * command (name, function) - defines a command that can be used by - non-operators. The `command` function provides a way for plugin - developers to provide new commands for use by players. +[plsm]: http://jd.bukkit.org/dev/apidocs/org/bukkit/command/CommandSender.html#sendMessage(java.lang.String) ### require() function @@ -252,17 +361,16 @@ load() will return the result of the last statement evaluated in the file. var myData = load("myData.json"); // loads a javascript file and evaluates it - eval'd contents are returned. -myData.json contents... +##### myData.json contents... - __data = { - players: { - walterh: { - h: ["jsp home {1}"], - sunny:["time set 0", - "weather clear"] - } - } - } + { players: { + walterh: { + h: ["jsp home {1}"], + sunny:["time set 0", + "weather clear"] + } + } + } ### save() function @@ -286,11 +394,12 @@ restored using the `load()` function. date_of_birth: '1982/01/31' }; save(myObject, 'johndoe.json'); -johndoe.json contents... +##### johndoe.json contents... - var __data = { "name": "John Doe", - "aliases": ["John Ray", "John Mee"], - "date_of_birth": "1982/01/31" }; + { "name": "John Doe", + "aliases": ["John Ray", "John Mee"], + "date_of_birth": "1982/01/31" + }; ### plugin() function @@ -1024,8 +1133,8 @@ a given directory and recursiving trawling all sub-directories. return name.match(/\.js$/); }); -Drone Module -============ +## Drone Plugin + The Drone is a convenience class for building. It can be used for... 1. Building @@ -1037,8 +1146,8 @@ be chained together like so... var theDrone = new Drone(); theDrone.up().left().box(blocks.oak).down().fwd(3).cylinder0(blocks.lava,8); -TLDNR; (Just read this if you're impatient) -=========================================== +### TLDNR; (Just read this if you're impatient) + At the in-game command prompt type... /js box( blocks.oak ) @@ -1051,8 +1160,7 @@ At the in-game command prompt type... wide x 9 tall x 1 long in size. If you want to see what else ScriptCraft's Drone can do, read on... -Constructing a Drone Object -=========================== +### Constructing a Drone Object Drones can be created in any of the following ways... @@ -1143,8 +1251,8 @@ Drones can be created in any of the following ways... // do more stuff with the drone here... }); -Parameters ----------- +#### Parameters + * location (optional) : *NB* If an `org.bukkit.Location` object is provided as a parameter, then it should be the only parameter. * x (optional) : The x coordinate of the Drone * y (optional) : The y coordinate of the Drone @@ -1153,12 +1261,12 @@ Parameters facing. Possible values are 0 (east), 1 (south), 2 (west) or 3 (north) * world (optional) : The world in which the drone is created. -Drone.box() method -================== +### Drone.box() method + the box() method is a convenience method for building things. (For the more performance-oriented method - see cuboid) -parameters ----------- +#### parameters + * b - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` * w (optional - default 1) - the width of the structure @@ -1167,8 +1275,8 @@ parameters not how deep underground the structure lies - this is how far away (depth of field) from the drone the structure will extend. -Example -------- +#### Example + To create a black structure 4 blocks wide, 9 blocks tall and 1 block long... box(blocks.wool.black, 4, 9, 1); @@ -1180,12 +1288,12 @@ To create a black structure 4 blocks wide, 9 blocks tall and 1 block long... ![box example 1](img/boxex1.png) -Drone.box0() method -=================== +### Drone.box0() method + Another convenience method - this one creates 4 walls with no floor or ceiling. -Parameters ----------- +#### Parameters + * block - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` * width (optional - default 1) - the width of the structure @@ -1193,28 +1301,28 @@ Parameters * length (optional - default 1) - the length of the structure - how far away (depth of field) from the drone the structure will extend. -Example -------- +#### Example + To create a stone building with the insided hollowed out 7 wide by 3 tall by 6 long... box0( blocks.stone, 7, 3, 6); ![example box0](img/box0ex1.png) -Drone.boxa() method -=================== +### Drone.boxa() method + Construct a cuboid using an array of blocks. As the drone moves first along the width axis, then the height (y axis) then the length, each block is picked from the array and placed. -Parameters ----------- +#### Parameters + * blocks - An array of blocks - each block in the array will be placed in turn. * width * height * length -Example -------- +#### Example + Construct a rainbow-colored road 100 blocks long... var rainbowColors = [blocks.wool.red, blocks.wool.orange, blocks.wool.yellow, blocks.wool.lime, @@ -1224,8 +1332,8 @@ Construct a rainbow-colored road 100 blocks long... ![boxa example](img/boxaex1.png) -Drone Movement -============== +### Drone Movement + Drones can move freely in minecraft's 3-D world. You control the Drone's movement using any of the following methods.. @@ -1248,13 +1356,12 @@ drone.turn() will make the turn face east. If the drone is facing east then drone.turn(2) will make the drone turn twice so that it is facing west. -Drone Positional Info -===================== +### Drone Positional Info * getLocation() - Returns a Bukkit Location object for the drone -Drone Markers -============= +### Drone Markers + Markers are useful when your Drone has to do a lot of work. You can set a check-point and return to the check-point using the move() method. If your drone is about to undertake a lot of work - @@ -1270,12 +1377,11 @@ Markers are created and returned to using the followng two methods... * move - moves the drone to a saved location. Alternatively you can provide an org.bukkit.Location object or x,y,z and direction parameters. -Parameters ----------- +#### Parameters + * name - the name of the checkpoint to save or return to. -Example -------- +#### Example drone.chkpt('town-square'); // @@ -1289,68 +1395,65 @@ Example // drone.move('town-square'); -Drone.prism() method -==================== +### Drone.prism() method + Creates a prism. This is useful for roofs on houses. -Parameters ----------- +#### Parameters * block - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` * width - the width of the prism * length - the length of the prism (will be 2 time its height) -Example -------- +#### Example prism(blocks.oak,3,12); ![prism example](img/prismex1.png) -Drone.prism0() method -===================== +### Drone.prism0() method + A variation on `prism` which hollows out the inside of the prism. It uses the same parameters as `prism`. -Drone.cylinder() method -======================= +### Drone.cylinder() method + A convenience method for building cylinders. Building begins radius blocks to the right and forward. -Parameters ----------- +#### Parameters * block - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` * radius * height -Example -------- +#### Example + To create a cylinder of Iron 7 blocks in radius and 1 block high... cylinder(blocks.iron, 7 , 1); ![cylinder example](img/cylinderex1.png) -Drone.cylinder0() method -======================== +### Drone.cylinder0() method + A version of cylinder that hollows out the middle. -Example -------- +#### Example + To create a hollow cylinder of Iron 7 blocks in radius and 1 block high... cylinder0(blocks.iron, 7, 1); ![cylinder0 example](img/cylinder0ex1.png) -Drone.arc() method -================== +### Drone.arc() method + The arc() method can be used to create 1 or more 90 degree arcs in the horizontal or vertical planes. This method is called by cylinder() and cylinder0() and the sphere() and sphere0() methods. -Parameters ----------- +#### Parameters + arc() takes a single parameter - an object with the following named properties... * radius - The radius of the arc. @@ -1372,8 +1475,8 @@ arc() takes a single parameter - an object with the following named properties.. circle to draw. If the quadrants property is absent then all 4 quadrants are drawn. -Examples --------- +#### Examples + To draw a 1/4 circle (top right quadrant only) with a radius of 10 and stroke width of 2 blocks ... arc({blockType: blocks.iron, @@ -1391,16 +1494,16 @@ To draw a 1/4 circle (top right quadrant only) with a radius of 10 and stroke wi [bres]: http://en.wikipedia.org/wiki/Midpoint_circle_algorithm [dv]: http://www.minecraftwiki.net/wiki/Data_values -Drone.door() method -=================== +### Drone.door() method + create a door - if a parameter is supplied an Iron door is created otherwise a wooden door is created. -Parameters ----------- +#### Parameters + * doorType (optional - default wood) - If a parameter is provided then the door is Iron. -Example -------- +#### Example + To create a wooden door at the crosshairs/drone's location... var drone = new Drone(); @@ -1412,33 +1515,33 @@ To create an iron door... ![iron door](img/doorex1.png) -Drone.door2() method -==================== +### Drone.door2() method + Create double doors (left and right side) -Parameters ----------- +#### Parameters + * doorType (optional - default wood) - If a parameter is provided then the door is Iron. -Example -------- +#### Example + To create double-doors at the cross-hairs/drone's location... drone.door2(); ![double doors](img/door2ex1.png) -Drone.sign() method -=================== +### Drone.sign() method + Signs must use block 63 (stand-alone signs) or 68 (signs on walls) -Parameters ----------- +#### Parameters + * message - can be a string or an array of strings. * block - can be 63 or 68 -Example -------- +#### Example + To create a free-standing sign... drone.sign(["Hello","World"],63); @@ -1451,16 +1554,15 @@ To create a free-standing sign... ![wall sign](img/signex2.png) -Drone Trees methods -=================== +### Drone Trees methods * oak() * spruce() * birch() * jungle() -Example -------- +#### Example + To create 4 trees in a row, point the cross-hairs at the ground then type `/js ` and ... up().oak().right(8).spruce().right(8).birch().right(8).jungle(); @@ -1473,35 +1575,33 @@ the `up()` method is called first). ![tree example](img/treeex1.png) - None of the tree methods require parameters. Tree methods will only be successful if the tree is placed on grass in a setting where trees can grow. -Drone.garden() method -===================== +### Drone.garden() method + places random flowers and long grass (similar to the effect of placing bonemeal on grass) -Parameters ----------- +#### Parameters * width - the width of the garden * length - how far from the drone the garden extends -Example -------- +#### Example + To create a garden 10 blocks wide by 5 blocks long... garden(10,5); ![garden example](img/gardenex1.png) -Drone.rand() method -=================== +### Drone.rand() method + rand takes either an array (if each blockid has the same chance of occurring) or an object where each property is a blockid and the value is it's weight (an integer) -Example -------- +#### Example + place random blocks stone, mossy stone and cracked stone (each block has the same chance of being picked) rand( [blocks.brick.stone, blocks.brick.mossy, blocks.brick.cracked ],w,d,h) @@ -1512,34 +1612,32 @@ to place random blocks stone has a 50% chance of being picked, regular stone has a 50% chance, mossy stone has a 30% chance and cracked stone has just a 20% chance of being picked. -Copy & Paste using Drone -======================== +### Copy & Paste using Drone + A drone can be used to copy and paste areas of the game world. -Drone.copy() method -=================== +### Drone.copy() method + Copies an area so it can be pasted elsewhere. The name can be used for pasting the copied area elsewhere... -Parameters ----------- +#### Parameters * name - the name to be given to the copied area (used by `paste`) * width - the width of the area to copy * height - the height of the area to copy * length - the length of the area (extending away from the drone) to copy -Example -------- +#### Example drone.copy('somethingCool',10,5,10).right(12).paste('somethingCool'); -Drone.paste() method -==================== +### Drone.paste() method + Pastes a copied area to the current location. -Example -------- +#### Example + To copy a 10x5x10 area (using the drone's coordinates as the starting point) into memory. the copied area can be referenced using the name 'somethingCool'. The drone moves 12 blocks right then pastes the copy. @@ -1548,8 +1646,7 @@ point) into memory. the copied area can be referenced using the name .right(12) .paste('somethingCool'); -Chaining -======== +### Chaining All of the Drone methods return a Drone object, which means methods can be 'chained' together so instead of writing this... @@ -1580,30 +1677,28 @@ commands in a new script file and load it using /js load() [fl]: http://en.wikipedia.org/wiki/Fluent_interface -Drone Properties -================ +### Drone Properties * x - The Drone's position along the west-east axis (x increases as you move east) * y - The Drone's position along the vertical axis (y increses as you move up) * z - The Drone's position along the north-south axis (z increases as you move south) * dir - The Drone's direction 0 is east, 1 is south , 2 is west and 3 is north. -Extending Drone -=============== +### Extending Drone + The Drone object can be easily extended - new buidling recipes/blue-prints can be added and can become part of a Drone's chain using the *static* method `Drone.extend`. -Drone.extend() static method -============================ +### Drone.extend() static method + Use this method to add new methods (which also become chainable global functions) to the Drone object. -Parameters ----------- +#### Parameters + * name - The name of the new method e.g. 'pyramid' * function - The method body. -Example -------- +#### Example // submitted by [edonaldson][edonaldson] Drone.extend('pyramid', function(block,height){ @@ -1625,11 +1720,10 @@ Once the method is defined (it can be defined in a new pyramid.js file) it can b [edonaldson]: https://github.com/edonaldson -Drone Constants -=============== +### Drone Constants + +#### Drone.PLAYER_STAIRS_FACING -Drone.PLAYER_STAIRS_FACING --------------------------- An array which can be used when constructing stairs facing in the Drone's direction... var d = new Drone(); @@ -1637,8 +1731,8 @@ An array which can be used when constructing stairs facing in the Drone's direct ... will construct a single oak stair block facing the drone. -Drone.PLAYER_SIGN_FACING ------------------------- +#### Drone.PLAYER_SIGN_FACING + An array which can be used when placing signs so they face in a given direction. This is used internally by the Drone.sign() method. It should also be used for placing any of the following blocks... @@ -1652,22 +1746,22 @@ To place a chest facing the Drone ... drone.box( blocks.chest + ':' + Drone.PLAYER_SIGN_FACING[drone.dir]); -Drone.PLAYER_TORCH_FACING -------------------------- +#### Drone.PLAYER_TORCH_FACING + Used when placing torches so that they face towards the drone. drone.box( blocks.torch + ':' + Drone.PLAYER_TORCH_FACING[drone.dir]); -Drone.times() Method -==================== +### Drone.times() Method + The times() method makes building multiple copies of buildings easy. It's possible to create rows or grids of buildings without resorting to `for` or `while` loops. -Parameters ----------- +#### Parameters + * numTimes (optional - default 2) : The number of times you want to repeat the preceding statements. -Example -------- +#### Example + Say you want to do the same thing over and over. You have a couple of options... * You can use a for loop... @@ -1723,17 +1817,17 @@ Another example: This statement creates a row of trees 2 by 3 ... ![times example 1](img/times-trees.png) -## Drone.blocktype() method +### Drone.blocktype() method Creates the text out of blocks. Useful for large-scale in-game signs. -### Parameters +#### Parameters * message - The message to create - (use `\n` for newlines) * foregroundBlock (default: black wool) - The block to use for the foreground * backgroundBlock (default: none) - The block to use for the background -### Example +#### Example To create a 2-line high message using glowstone... @@ -1743,31 +1837,31 @@ To create a 2-line high message using glowstone... [imgbt1]: img/blocktype1.png -## Drone.rainbow() method +### Drone.rainbow() method Creates a Rainbow. -### Parameters +#### Parameters * radius (optional - default:18) - The radius of the rainbow -### Example +#### Example var d = new Drone(); d.rainbow(30); ![rainbow example](img/rainbowex1.png) -## Drone.sphere() method +### Drone.sphere() method Creates a sphere. -### Parameters +#### Parameters * block - The block the sphere will be made of. * radius - The radius of the sphere. -### Example +#### Example To create a sphere of Iron with a radius of 10 blocks... @@ -1778,16 +1872,16 @@ To create a sphere of Iron with a radius of 10 blocks... Spheres are time-consuming to make. You *can* make large spheres (250 radius) but expect the server to be very busy for a couple of minutes while doing so. -## Drone.sphere0() method +### Drone.sphere0() method Creates an empty sphere. -### Parameters +#### Parameters * block - The block the sphere will be made of. * radius - The radius of the sphere. -### Example +#### Example To create a sphere of Iron with a radius of 10 blocks... @@ -1796,17 +1890,17 @@ To create a sphere of Iron with a radius of 10 blocks... Spheres are time-consuming to make. You *can* make large spheres (250 radius) but expect the server to be very busy for a couple of minutes while doing so. -## Drone.hemisphere() method +### Drone.hemisphere() method Creates a hemisphere. Hemispheres can be either north or south. -### Parameters +#### Parameters * block - the block the hemisphere will be made of. * radius - the radius of the hemisphere * northSouth - whether the hemisphere is 'north' or 'south' -### Example +#### Example To create a wood 'north' hemisphere with a radius of 7 blocks... @@ -1814,17 +1908,17 @@ To create a wood 'north' hemisphere with a radius of 7 blocks... ![hemisphere example](img/hemisphereex1.png) -## Drone.hemisphere0() method +### Drone.hemisphere0() method Creates a hollow hemisphere. Hemispheres can be either north or south. -### Parameters +#### Parameters * block - the block the hemisphere will be made of. * radius - the radius of the hemisphere * northSouth - whether the hemisphere is 'north' or 'south' -### Example +#### Example To create a glass 'north' hemisphere with a radius of 20 blocks... @@ -1832,11 +1926,11 @@ To create a glass 'north' hemisphere with a radius of 20 blocks... ![hemisphere example](img/hemisphereex2.png) -## Drone.spiral_stairs() method +### Drone.spiral_stairs() method Constructs a spiral staircase with slabs at each corner. -### Parameters +#### Parameters * stairBlock - The block to use for stairs, should be one of the following... - 'oak' @@ -1853,13 +1947,13 @@ Constructs a spiral staircase with slabs at each corner. ![Spiral Staircase](img/spiralstair1.png) -### Example +#### Example To construct a spiral staircase 5 floors high made of oak... spiral_stairs('oak', 5); -## Example Plugin #1 +## Example Plugin #1 - A simple extension to Minecraft. A simple minecraft plugin. The most basic module. @@ -1884,7 +1978,7 @@ permission since it relies on the `/js` command to execute. player.sendMessage('Hello ' + player.name); }; -## Example Plugin #2 +## Example Plugin #2 - Making extensions available for all players. A simple minecraft plugin. Commands for other players. @@ -1910,7 +2004,7 @@ command does not evaluate javascript code so this command is much more secure. player.sendMessage('Hello ' + player.name); }); -## Example Plugin #3 +## Example Plugin #3 - Limiting use of commands to operators only. A simple minecraft plugin. Commands for operators only. @@ -1939,7 +2033,8 @@ message for operators. } player.sendMessage('Hello ' + player.name); }); -## Example Plugin #4 +## Example Plugin #4 - Using parameters in commands. + A simple minecraft plugin. Handling parameters. ### Usage: @@ -1963,7 +2058,7 @@ a fixed 'Hello ' to anything you like by passing a parameter. player.sendMessage( salutation + ' ' + player.name); }); -## Example Plugin #5 +## Example Plugin #5 - Re-use - Using your own and others modules. A simple minecraft plugin. Using Modules. @@ -1997,7 +2092,7 @@ Source Code... greetings.hello(player); }); -## Example Plugin #6 +## Example Plugin #6 - Re-use - Using 'utils' to get Player objects. A simple minecraft plugin. Finding players by name. @@ -2039,7 +2134,7 @@ Source Code ... sender.sendMessage('Player ' + playerName + ' not found.'); }); -## Example Plugin #7 +## Example Plugin #7 - Listening for events, Greet players when they join the game. A simple event-driven minecraft plugin. How to handle Events. @@ -2125,7 +2220,7 @@ cleaner and more readable. Similarly where you see a method like } }); -## Arrows Module +## Arrows Plugin The arrows mod adds fancy arrows to the game. Arrows which... @@ -2149,7 +2244,7 @@ All of the above functions can take an optional player object or name as a parameter. For example: `/js arrows.explosive('player23')` makes player23's arrows explosive. -## alias Module +## alias Plugin The alias module lets players and server admins create their own per-player or global custom in-game command aliases. @@ -2199,7 +2294,7 @@ Aliases can be used at the in-game prompt by players or in the server console. Aliases will not be able to avail of command autocompletion (pressing the TAB key will have no effect). -## Classroom Module +## Classroom Plugin The `classroom` object contains a couple of utility functions for use in a classroom setting. The goal of these functions is to make it @@ -2315,7 +2410,7 @@ global commands for a plugin, please let me know. [pcppevt]: http://jd.bukkit.org/dev/apidocs/org/bukkit/event/player/PlayerCommandPreprocessEvent.html -## homes Module +## homes Plugin The homes plugin lets players set a location as home and return to the location, invite other players to their home and also visit other diff --git a/src/main/javascript/lib/scriptcraft.js b/src/main/javascript/lib/scriptcraft.js index 5cce36d..6e7e854 100644 --- a/src/main/javascript/lib/scriptcraft.js +++ b/src/main/javascript/lib/scriptcraft.js @@ -1,12 +1,5 @@ 'use strict'; /************************************************************************ -# ScriptCraft API Reference - -Walter Higgins - -[walter.higgins@gmail.com][email] - -[email]: mailto:walter.higgins@gmail.com?subject=ScriptCraft_API_Reference ## Modules in Scriptcraft @@ -160,10 +153,10 @@ 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 +### __filename variable The current file - this variable is only relevant from within the context of a Javascript module. -### __dirname variable +### __dirname variable The current directory - this variable is only relevant from within the context of a Javascript module. ## Global functions @@ -176,7 +169,7 @@ 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). -### Example +#### Example /js echo('Hello World') @@ -184,7 +177,7 @@ 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 +#### Notes The `echo` and `alert` functions are provided as convenience functions for beginning programmers. The use of these 2 functions is not @@ -192,26 +185,7 @@ 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]: - - * require (modulename) - Will load modules. See [Node.js modules][njsmod] - - * load (filename,warnOnFileNotFound) - loads and evaluates a - javascript file, returning the evaluated object. (Note: Prefer - `require()` to `load()`) - - * save (object, filename) - saves an object to a file. - - * plugin (name, interface, isPersistent) - defines a new plugin. If - isPersistent is true then the plugin doesn't have to worry about - loading and saving state - that will be done by the framework. Just - make sure that anything you want to save (and restore) is in the plugin's - 'store' property - this will be created automatically if not - already defined. (its type is object {} ) . More on plugins below. - - * command (name, function) - defines a command that can be used by - non-operators. The `command` function provides a way for plugin - developers to provide new commands for use by players. +[plsm]: http://jd.bukkit.org/dev/apidocs/org/bukkit/command/CommandSender.html#sendMessage(java.lang.String) ### require() function @@ -254,17 +228,16 @@ load() will return the result of the last statement evaluated in the file. var myData = load("myData.json"); // loads a javascript file and evaluates it - eval'd contents are returned. -myData.json contents... +##### myData.json contents... - __data = { - players: { - walterh: { - h: ["jsp home {1}"], - sunny:["time set 0", - "weather clear"] - } - } - } + { players: { + walterh: { + h: ["jsp home {1}"], + sunny:["time set 0", + "weather clear"] + } + } + } ### save() function @@ -288,11 +261,12 @@ restored using the `load()` function. date_of_birth: '1982/01/31' }; save(myObject, 'johndoe.json'); -johndoe.json contents... +##### johndoe.json contents... - var __data = { "name": "John Doe", - "aliases": ["John Ray", "John Mee"], - "date_of_birth": "1982/01/31" }; + { "name": "John Doe", + "aliases": ["John Ray", "John Mee"], + "date_of_birth": "1982/01/31" + }; ### plugin() function diff --git a/src/main/javascript/plugins/alias/alias.js b/src/main/javascript/plugins/alias/alias.js index ca7eb2e..9c4a4ee 100644 --- a/src/main/javascript/plugins/alias/alias.js +++ b/src/main/javascript/plugins/alias/alias.js @@ -1,5 +1,5 @@ /************************************************************************* -## alias Module +## alias Plugin The alias module lets players and server admins create their own per-player or global custom in-game command aliases. diff --git a/src/main/javascript/plugins/arrows.js b/src/main/javascript/plugins/arrows.js index a3d728f..9b0bbe7 100644 --- a/src/main/javascript/plugins/arrows.js +++ b/src/main/javascript/plugins/arrows.js @@ -1,5 +1,5 @@ /************************************************************************* -## Arrows Module +## Arrows Plugin The arrows mod adds fancy arrows to the game. Arrows which... diff --git a/src/main/javascript/plugins/classroom/classroom.js b/src/main/javascript/plugins/classroom/classroom.js index 04f8d62..2822b27 100644 --- a/src/main/javascript/plugins/classroom/classroom.js +++ b/src/main/javascript/plugins/classroom/classroom.js @@ -1,7 +1,7 @@ var utils = require('utils'); /************************************************************************ -## Classroom Module +## Classroom Plugin The `classroom` object contains a couple of utility functions for use in a classroom setting. The goal of these functions is to make it diff --git a/src/main/javascript/plugins/drone/blocktype.js b/src/main/javascript/plugins/drone/blocktype.js index f142e34..2265763 100644 --- a/src/main/javascript/plugins/drone/blocktype.js +++ b/src/main/javascript/plugins/drone/blocktype.js @@ -2,17 +2,17 @@ var Drone = require('./drone').Drone; var blocks = require('blocks'); /************************************************************************ -## Drone.blocktype() method +### Drone.blocktype() method Creates the text out of blocks. Useful for large-scale in-game signs. -### Parameters +#### Parameters * message - The message to create - (use `\n` for newlines) * foregroundBlock (default: black wool) - The block to use for the foreground * backgroundBlock (default: none) - The block to use for the background -### Example +#### Example To create a 2-line high message using glowstone... diff --git a/src/main/javascript/plugins/drone/contrib/rainbow.js b/src/main/javascript/plugins/drone/contrib/rainbow.js index cf8cad3..31d33b5 100644 --- a/src/main/javascript/plugins/drone/contrib/rainbow.js +++ b/src/main/javascript/plugins/drone/contrib/rainbow.js @@ -2,15 +2,15 @@ var Drone = require('../drone').Drone; var blocks = require('blocks'); /************************************************************************ -## Drone.rainbow() method +### Drone.rainbow() method Creates a Rainbow. -### Parameters +#### Parameters * radius (optional - default:18) - The radius of the rainbow -### Example +#### Example var d = new Drone(); d.rainbow(30); diff --git a/src/main/javascript/plugins/drone/contrib/spiral_stairs.js b/src/main/javascript/plugins/drone/contrib/spiral_stairs.js index 5f6bf35..ad51633 100644 --- a/src/main/javascript/plugins/drone/contrib/spiral_stairs.js +++ b/src/main/javascript/plugins/drone/contrib/spiral_stairs.js @@ -2,11 +2,11 @@ var Drone = require('../drone').Drone; var blocks = require('blocks'); /************************************************************************ -## Drone.spiral_stairs() method +### Drone.spiral_stairs() method Constructs a spiral staircase with slabs at each corner. -### Parameters +#### Parameters * stairBlock - The block to use for stairs, should be one of the following... - 'oak' @@ -23,7 +23,7 @@ Constructs a spiral staircase with slabs at each corner. ![Spiral Staircase](img/spiralstair1.png) -### Example +#### Example To construct a spiral staircase 5 floors high made of oak... diff --git a/src/main/javascript/plugins/drone/drone.js b/src/main/javascript/plugins/drone/drone.js index 9eca9ca..0b5913b 100644 --- a/src/main/javascript/plugins/drone/drone.js +++ b/src/main/javascript/plugins/drone/drone.js @@ -2,8 +2,8 @@ var utils = require('utils'); var blocks = require('blocks'); /********************************************************************* -Drone Module -============ +## Drone Plugin + The Drone is a convenience class for building. It can be used for... 1. Building @@ -15,8 +15,8 @@ be chained together like so... var theDrone = new Drone(); theDrone.up().left().box(blocks.oak).down().fwd(3).cylinder0(blocks.lava,8); -TLDNR; (Just read this if you're impatient) -=========================================== +### TLDNR; (Just read this if you're impatient) + At the in-game command prompt type... /js box( blocks.oak ) @@ -29,8 +29,7 @@ At the in-game command prompt type... wide x 9 tall x 1 long in size. If you want to see what else ScriptCraft's Drone can do, read on... -Constructing a Drone Object -=========================== +### Constructing a Drone Object Drones can be created in any of the following ways... @@ -121,8 +120,8 @@ Drones can be created in any of the following ways... // do more stuff with the drone here... }); -Parameters ----------- +#### Parameters + * location (optional) : *NB* If an `org.bukkit.Location` object is provided as a parameter, then it should be the only parameter. * x (optional) : The x coordinate of the Drone * y (optional) : The y coordinate of the Drone @@ -131,12 +130,12 @@ Parameters facing. Possible values are 0 (east), 1 (south), 2 (west) or 3 (north) * world (optional) : The world in which the drone is created. -Drone.box() method -================== +### Drone.box() method + the box() method is a convenience method for building things. (For the more performance-oriented method - see cuboid) -parameters ----------- +#### parameters + * b - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` * w (optional - default 1) - the width of the structure @@ -145,8 +144,8 @@ parameters not how deep underground the structure lies - this is how far away (depth of field) from the drone the structure will extend. -Example -------- +#### Example + To create a black structure 4 blocks wide, 9 blocks tall and 1 block long... box(blocks.wool.black, 4, 9, 1); @@ -158,12 +157,12 @@ To create a black structure 4 blocks wide, 9 blocks tall and 1 block long... ![box example 1](img/boxex1.png) -Drone.box0() method -=================== +### Drone.box0() method + Another convenience method - this one creates 4 walls with no floor or ceiling. -Parameters ----------- +#### Parameters + * block - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` * width (optional - default 1) - the width of the structure @@ -171,28 +170,28 @@ Parameters * length (optional - default 1) - the length of the structure - how far away (depth of field) from the drone the structure will extend. -Example -------- +#### Example + To create a stone building with the insided hollowed out 7 wide by 3 tall by 6 long... box0( blocks.stone, 7, 3, 6); ![example box0](img/box0ex1.png) -Drone.boxa() method -=================== +### Drone.boxa() method + Construct a cuboid using an array of blocks. As the drone moves first along the width axis, then the height (y axis) then the length, each block is picked from the array and placed. -Parameters ----------- +#### Parameters + * blocks - An array of blocks - each block in the array will be placed in turn. * width * height * length -Example -------- +#### Example + Construct a rainbow-colored road 100 blocks long... var rainbowColors = [blocks.wool.red, blocks.wool.orange, blocks.wool.yellow, blocks.wool.lime, @@ -202,8 +201,8 @@ Construct a rainbow-colored road 100 blocks long... ![boxa example](img/boxaex1.png) -Drone Movement -============== +### Drone Movement + Drones can move freely in minecraft's 3-D world. You control the Drone's movement using any of the following methods.. @@ -226,13 +225,12 @@ drone.turn() will make the turn face east. If the drone is facing east then drone.turn(2) will make the drone turn twice so that it is facing west. -Drone Positional Info -===================== +### Drone Positional Info * getLocation() - Returns a Bukkit Location object for the drone -Drone Markers -============= +### Drone Markers + Markers are useful when your Drone has to do a lot of work. You can set a check-point and return to the check-point using the move() method. If your drone is about to undertake a lot of work - @@ -248,12 +246,11 @@ Markers are created and returned to using the followng two methods... * move - moves the drone to a saved location. Alternatively you can provide an org.bukkit.Location object or x,y,z and direction parameters. -Parameters ----------- +#### Parameters + * name - the name of the checkpoint to save or return to. -Example -------- +#### Example drone.chkpt('town-square'); // @@ -267,68 +264,65 @@ Example // drone.move('town-square'); -Drone.prism() method -==================== +### Drone.prism() method + Creates a prism. This is useful for roofs on houses. -Parameters ----------- +#### Parameters * block - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` * width - the width of the prism * length - the length of the prism (will be 2 time its height) -Example -------- +#### Example prism(blocks.oak,3,12); ![prism example](img/prismex1.png) -Drone.prism0() method -===================== +### Drone.prism0() method + A variation on `prism` which hollows out the inside of the prism. It uses the same parameters as `prism`. -Drone.cylinder() method -======================= +### Drone.cylinder() method + A convenience method for building cylinders. Building begins radius blocks to the right and forward. -Parameters ----------- +#### Parameters * block - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` * radius * height -Example -------- +#### Example + To create a cylinder of Iron 7 blocks in radius and 1 block high... cylinder(blocks.iron, 7 , 1); ![cylinder example](img/cylinderex1.png) -Drone.cylinder0() method -======================== +### Drone.cylinder0() method + A version of cylinder that hollows out the middle. -Example -------- +#### Example + To create a hollow cylinder of Iron 7 blocks in radius and 1 block high... cylinder0(blocks.iron, 7, 1); ![cylinder0 example](img/cylinder0ex1.png) -Drone.arc() method -================== +### Drone.arc() method + The arc() method can be used to create 1 or more 90 degree arcs in the horizontal or vertical planes. This method is called by cylinder() and cylinder0() and the sphere() and sphere0() methods. -Parameters ----------- +#### Parameters + arc() takes a single parameter - an object with the following named properties... * radius - The radius of the arc. @@ -350,8 +344,8 @@ arc() takes a single parameter - an object with the following named properties.. circle to draw. If the quadrants property is absent then all 4 quadrants are drawn. -Examples --------- +#### Examples + To draw a 1/4 circle (top right quadrant only) with a radius of 10 and stroke width of 2 blocks ... arc({blockType: blocks.iron, @@ -369,16 +363,16 @@ To draw a 1/4 circle (top right quadrant only) with a radius of 10 and stroke wi [bres]: http://en.wikipedia.org/wiki/Midpoint_circle_algorithm [dv]: http://www.minecraftwiki.net/wiki/Data_values -Drone.door() method -=================== +### Drone.door() method + create a door - if a parameter is supplied an Iron door is created otherwise a wooden door is created. -Parameters ----------- +#### Parameters + * doorType (optional - default wood) - If a parameter is provided then the door is Iron. -Example -------- +#### Example + To create a wooden door at the crosshairs/drone's location... var drone = new Drone(); @@ -390,33 +384,33 @@ To create an iron door... ![iron door](img/doorex1.png) -Drone.door2() method -==================== +### Drone.door2() method + Create double doors (left and right side) -Parameters ----------- +#### Parameters + * doorType (optional - default wood) - If a parameter is provided then the door is Iron. -Example -------- +#### Example + To create double-doors at the cross-hairs/drone's location... drone.door2(); ![double doors](img/door2ex1.png) -Drone.sign() method -=================== +### Drone.sign() method + Signs must use block 63 (stand-alone signs) or 68 (signs on walls) -Parameters ----------- +#### Parameters + * message - can be a string or an array of strings. * block - can be 63 or 68 -Example -------- +#### Example + To create a free-standing sign... drone.sign(["Hello","World"],63); @@ -429,16 +423,15 @@ To create a free-standing sign... ![wall sign](img/signex2.png) -Drone Trees methods -=================== +### Drone Trees methods * oak() * spruce() * birch() * jungle() -Example -------- +#### Example + To create 4 trees in a row, point the cross-hairs at the ground then type `/js ` and ... up().oak().right(8).spruce().right(8).birch().right(8).jungle(); @@ -451,35 +444,33 @@ the `up()` method is called first). ![tree example](img/treeex1.png) - None of the tree methods require parameters. Tree methods will only be successful if the tree is placed on grass in a setting where trees can grow. -Drone.garden() method -===================== +### Drone.garden() method + places random flowers and long grass (similar to the effect of placing bonemeal on grass) -Parameters ----------- +#### Parameters * width - the width of the garden * length - how far from the drone the garden extends -Example -------- +#### Example + To create a garden 10 blocks wide by 5 blocks long... garden(10,5); ![garden example](img/gardenex1.png) -Drone.rand() method -=================== +### Drone.rand() method + rand takes either an array (if each blockid has the same chance of occurring) or an object where each property is a blockid and the value is it's weight (an integer) -Example -------- +#### Example + place random blocks stone, mossy stone and cracked stone (each block has the same chance of being picked) rand( [blocks.brick.stone, blocks.brick.mossy, blocks.brick.cracked ],w,d,h) @@ -490,34 +481,32 @@ to place random blocks stone has a 50% chance of being picked, regular stone has a 50% chance, mossy stone has a 30% chance and cracked stone has just a 20% chance of being picked. -Copy & Paste using Drone -======================== +### Copy & Paste using Drone + A drone can be used to copy and paste areas of the game world. -Drone.copy() method -=================== +### Drone.copy() method + Copies an area so it can be pasted elsewhere. The name can be used for pasting the copied area elsewhere... -Parameters ----------- +#### Parameters * name - the name to be given to the copied area (used by `paste`) * width - the width of the area to copy * height - the height of the area to copy * length - the length of the area (extending away from the drone) to copy -Example -------- +#### Example drone.copy('somethingCool',10,5,10).right(12).paste('somethingCool'); -Drone.paste() method -==================== +### Drone.paste() method + Pastes a copied area to the current location. -Example -------- +#### Example + To copy a 10x5x10 area (using the drone's coordinates as the starting point) into memory. the copied area can be referenced using the name 'somethingCool'. The drone moves 12 blocks right then pastes the copy. @@ -526,8 +515,7 @@ point) into memory. the copied area can be referenced using the name .right(12) .paste('somethingCool'); -Chaining -======== +### Chaining All of the Drone methods return a Drone object, which means methods can be 'chained' together so instead of writing this... @@ -558,30 +546,28 @@ commands in a new script file and load it using /js load() [fl]: http://en.wikipedia.org/wiki/Fluent_interface -Drone Properties -================ +### Drone Properties * x - The Drone's position along the west-east axis (x increases as you move east) * y - The Drone's position along the vertical axis (y increses as you move up) * z - The Drone's position along the north-south axis (z increases as you move south) * dir - The Drone's direction 0 is east, 1 is south , 2 is west and 3 is north. -Extending Drone -=============== +### Extending Drone + The Drone object can be easily extended - new buidling recipes/blue-prints can be added and can become part of a Drone's chain using the *static* method `Drone.extend`. -Drone.extend() static method -============================ +### Drone.extend() static method + Use this method to add new methods (which also become chainable global functions) to the Drone object. -Parameters ----------- +#### Parameters + * name - The name of the new method e.g. 'pyramid' * function - The method body. -Example -------- +#### Example // submitted by [edonaldson][edonaldson] Drone.extend('pyramid', function(block,height){ @@ -603,11 +589,10 @@ Once the method is defined (it can be defined in a new pyramid.js file) it can b [edonaldson]: https://github.com/edonaldson -Drone Constants -=============== +### Drone Constants + +#### Drone.PLAYER_STAIRS_FACING -Drone.PLAYER_STAIRS_FACING --------------------------- An array which can be used when constructing stairs facing in the Drone's direction... var d = new Drone(); @@ -615,8 +600,8 @@ An array which can be used when constructing stairs facing in the Drone's direct ... will construct a single oak stair block facing the drone. -Drone.PLAYER_SIGN_FACING ------------------------- +#### Drone.PLAYER_SIGN_FACING + An array which can be used when placing signs so they face in a given direction. This is used internally by the Drone.sign() method. It should also be used for placing any of the following blocks... @@ -630,8 +615,8 @@ To place a chest facing the Drone ... drone.box( blocks.chest + ':' + Drone.PLAYER_SIGN_FACING[drone.dir]); -Drone.PLAYER_TORCH_FACING -------------------------- +#### Drone.PLAYER_TORCH_FACING + Used when placing torches so that they face towards the drone. drone.box( blocks.torch + ':' + Drone.PLAYER_TORCH_FACING[drone.dir]); @@ -763,16 +748,16 @@ Drone.extend = function(name, func) }; /************************************************************************** -Drone.times() Method -==================== +### Drone.times() Method + The times() method makes building multiple copies of buildings easy. It's possible to create rows or grids of buildings without resorting to `for` or `while` loops. -Parameters ----------- +#### Parameters + * numTimes (optional - default 2) : The number of times you want to repeat the preceding statements. -Example -------- +#### Example + Say you want to do the same thing over and over. You have a couple of options... * You can use a for loop... diff --git a/src/main/javascript/plugins/drone/sphere.js b/src/main/javascript/plugins/drone/sphere.js index 2dbdf5c..3ba43eb 100644 --- a/src/main/javascript/plugins/drone/sphere.js +++ b/src/main/javascript/plugins/drone/sphere.js @@ -1,16 +1,16 @@ var Drone = require('./drone').Drone; /************************************************************************ -## Drone.sphere() method +### Drone.sphere() method Creates a sphere. -### Parameters +#### Parameters * block - The block the sphere will be made of. * radius - The radius of the sphere. -### Example +#### Example To create a sphere of Iron with a radius of 10 blocks... @@ -67,16 +67,16 @@ Drone.extend('sphere', function(block,radius) return this.move('sphere'); }); /************************************************************************ -## Drone.sphere0() method +### Drone.sphere0() method Creates an empty sphere. -### Parameters +#### Parameters * block - The block the sphere will be made of. * radius - The radius of the sphere. -### Example +#### Example To create a sphere of Iron with a radius of 10 blocks... @@ -166,17 +166,17 @@ Drone.extend('sphere0', function(block,radius) }); /************************************************************************ -## Drone.hemisphere() method +### Drone.hemisphere() method Creates a hemisphere. Hemispheres can be either north or south. -### Parameters +#### Parameters * block - the block the hemisphere will be made of. * radius - the radius of the hemisphere * northSouth - whether the hemisphere is 'north' or 'south' -### Example +#### Example To create a wood 'north' hemisphere with a radius of 7 blocks... @@ -236,17 +236,17 @@ Drone.extend('hemisphere', function(block,radius, northSouth){ }); /************************************************************************ -## Drone.hemisphere0() method +### Drone.hemisphere0() method Creates a hollow hemisphere. Hemispheres can be either north or south. -### Parameters +#### Parameters * block - the block the hemisphere will be made of. * radius - the radius of the hemisphere * northSouth - whether the hemisphere is 'north' or 'south' -### Example +#### Example To create a glass 'north' hemisphere with a radius of 20 blocks... diff --git a/src/main/javascript/plugins/examples/example-1-hello-module.js b/src/main/javascript/plugins/examples/example-1-hello-module.js index f4e36f7..a77bdde 100644 --- a/src/main/javascript/plugins/examples/example-1-hello-module.js +++ b/src/main/javascript/plugins/examples/example-1-hello-module.js @@ -1,5 +1,5 @@ /************************************************************************* -## Example Plugin #1 +## Example Plugin #1 - A simple extension to Minecraft. A simple minecraft plugin. The most basic module. diff --git a/src/main/javascript/plugins/examples/example-2-hello-command.js b/src/main/javascript/plugins/examples/example-2-hello-command.js index a41ea7e..84749b9 100644 --- a/src/main/javascript/plugins/examples/example-2-hello-command.js +++ b/src/main/javascript/plugins/examples/example-2-hello-command.js @@ -1,5 +1,5 @@ /************************************************************************* -## Example Plugin #2 +## Example Plugin #2 - Making extensions available for all players. A simple minecraft plugin. Commands for other players. diff --git a/src/main/javascript/plugins/examples/example-3-hello-ops-only.js b/src/main/javascript/plugins/examples/example-3-hello-ops-only.js index 4ec44ca..abe456a 100644 --- a/src/main/javascript/plugins/examples/example-3-hello-ops-only.js +++ b/src/main/javascript/plugins/examples/example-3-hello-ops-only.js @@ -1,5 +1,5 @@ /************************************************************************* -## Example Plugin #3 +## Example Plugin #3 - Limiting use of commands to operators only. A simple minecraft plugin. Commands for operators only. diff --git a/src/main/javascript/plugins/examples/example-4-hello-parameters.js b/src/main/javascript/plugins/examples/example-4-hello-parameters.js index f2fc391..bc4fa93 100644 --- a/src/main/javascript/plugins/examples/example-4-hello-parameters.js +++ b/src/main/javascript/plugins/examples/example-4-hello-parameters.js @@ -1,5 +1,6 @@ /************************************************************************* -## Example Plugin #4 +## Example Plugin #4 - Using parameters in commands. + A simple minecraft plugin. Handling parameters. ### Usage: diff --git a/src/main/javascript/plugins/examples/example-5-hello-using-module.js b/src/main/javascript/plugins/examples/example-5-hello-using-module.js index 80ae39f..270a2bd 100644 --- a/src/main/javascript/plugins/examples/example-5-hello-using-module.js +++ b/src/main/javascript/plugins/examples/example-5-hello-using-module.js @@ -1,5 +1,5 @@ /************************************************************************* -## Example Plugin #5 +## Example Plugin #5 - Re-use - Using your own and others modules. A simple minecraft plugin. Using Modules. diff --git a/src/main/javascript/plugins/examples/example-6-hello-player.js b/src/main/javascript/plugins/examples/example-6-hello-player.js index 0d1971b..0233842 100644 --- a/src/main/javascript/plugins/examples/example-6-hello-player.js +++ b/src/main/javascript/plugins/examples/example-6-hello-player.js @@ -1,5 +1,5 @@ /************************************************************************* -## Example Plugin #6 +## Example Plugin #6 - Re-use - Using 'utils' to get Player objects. A simple minecraft plugin. Finding players by name. diff --git a/src/main/javascript/plugins/examples/example-7-hello-events.js b/src/main/javascript/plugins/examples/example-7-hello-events.js index 92066b0..8c1b4d4 100644 --- a/src/main/javascript/plugins/examples/example-7-hello-events.js +++ b/src/main/javascript/plugins/examples/example-7-hello-events.js @@ -1,5 +1,5 @@ /************************************************************************* -## Example Plugin #7 +## Example Plugin #7 - Listening for events, Greet players when they join the game. A simple event-driven minecraft plugin. How to handle Events. diff --git a/src/main/javascript/plugins/homes/homes.js b/src/main/javascript/plugins/homes/homes.js index 47d2b51..1c5e378 100644 --- a/src/main/javascript/plugins/homes/homes.js +++ b/src/main/javascript/plugins/homes/homes.js @@ -1,5 +1,5 @@ /************************************************************************* -## homes Module +## homes Plugin The homes plugin lets players set a location as home and return to the location, invite other players to their home and also visit other From d742c5ae44395077b4238c6bb1902742fb54403c Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 4 Jan 2014 20:08:24 +0000 Subject: [PATCH 082/456] fix bug in toc generation - escaped chars not linked right --- src/docs/javascript/generateTOC.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/docs/javascript/generateTOC.js b/src/docs/javascript/generateTOC.js index 555abf2..6afcac5 100644 --- a/src/docs/javascript/generateTOC.js +++ b/src/docs/javascript/generateTOC.js @@ -19,14 +19,16 @@ for (var i = 0; i < contents.length; i++){ line = contents[i]; if (line.match(/^##\s+/)){ var h2 = line.match(/^##\s+(.*)/)[1].trim(); - var link = h2.replace(/[^a-zA-Z0-9 \-]/g,''); + var link = '' + java.net.URLDecoder.decode(h2,"UTF-8"); + link = link.replace(/[^a-zA-Z0-9 \-]/g,''); link = link.replace(/ /g,'-'); link = link.toLowerCase(); println (' * [' + h2 + '](#' + link + ')'); } if (line.match(/^###\s+/)){ var h3 = line.match(/^###\s+(.*)/)[1].trim(); - var link = h3.replace(/[^a-zA-Z0-9 \-]/g,''); + var link = '' + java.net.URLDecoder.decode(h3,"UTF-8"); + link = link.replace(/[^a-zA-Z0-9 \-]/g,''); var link = link.replace(/ /g,'-'); link = link.toLowerCase(); println (' * [' + h3 + '](#' + link + ')'); From 5239709fcfbbdd11c3593b5eb9785ef77ebd848e Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 4 Jan 2014 20:17:49 +0000 Subject: [PATCH 083/456] problems with links in toc --- docs/API-Reference.md | 4 ++-- src/docs/javascript/generateTOC.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index c450098..9862419 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -20,8 +20,8 @@ Walter Higgins * [config variable](#config-variable) * [events variable](#events-variable) * [Module variables](#module-variables) - * [__filename variable](#9595filename-variable) - * [__dirname variable](#9595dirname-variable) + * [__filename variable](#filename-variable) + * [__dirname variable](#dirname-variable) * [Global functions](#global-functions) * [echo function](#echo-function) * [require() function](#require-function) diff --git a/src/docs/javascript/generateTOC.js b/src/docs/javascript/generateTOC.js index 6afcac5..de840c4 100644 --- a/src/docs/javascript/generateTOC.js +++ b/src/docs/javascript/generateTOC.js @@ -19,7 +19,7 @@ for (var i = 0; i < contents.length; i++){ line = contents[i]; if (line.match(/^##\s+/)){ var h2 = line.match(/^##\s+(.*)/)[1].trim(); - var link = '' + java.net.URLDecoder.decode(h2,"UTF-8"); + var link = h2.replace(/_/g,''); link = link.replace(/[^a-zA-Z0-9 \-]/g,''); link = link.replace(/ /g,'-'); link = link.toLowerCase(); @@ -27,7 +27,7 @@ for (var i = 0; i < contents.length; i++){ } if (line.match(/^###\s+/)){ var h3 = line.match(/^###\s+(.*)/)[1].trim(); - var link = '' + java.net.URLDecoder.decode(h3,"UTF-8"); + var link = h3.replace(/_/g,''); link = link.replace(/[^a-zA-Z0-9 \-]/g,''); var link = link.replace(/ /g,'-'); link = link.toLowerCase(); From cc3c87da23080cebd0b00296a2bd5e6d357c111e Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 4 Jan 2014 20:23:17 +0000 Subject: [PATCH 084/456] fix links --- docs/API-Reference.md | 8 ++++---- src/docs/javascript/generateTOC.js | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 9862419..2d7b345 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -14,14 +14,14 @@ Walter Higgins * [The lib directory](#the-lib-directory) * [plugins sub-directories](#plugins-sub-directories) * [Global variables](#global-variables) - * [__plugin variable](#plugin-variable) + * [__plugin variable](#__plugin-variable) * [server variable](#server-variable) * [self variable](#self-variable) * [config variable](#config-variable) * [events variable](#events-variable) * [Module variables](#module-variables) - * [__filename variable](#filename-variable) - * [__dirname variable](#dirname-variable) + * [__filename variable](#__filename-variable) + * [__dirname variable](#__dirname-variable) * [Global functions](#global-functions) * [echo function](#echo-function) * [require() function](#require-function) @@ -99,7 +99,7 @@ Walter Higgins * [Drone.sphere0() method](#dronesphere0-method) * [Drone.hemisphere() method](#dronehemisphere-method) * [Drone.hemisphere0() method](#dronehemisphere0-method) - * [Drone.spiral_stairs() method](#dronespiralstairs-method) + * [Drone.spiral_stairs() method](#dronespiral_stairs-method) * [Example Plugin #1 - A simple extension to Minecraft.](#example-plugin-1---a-simple-extension-to-minecraft) * [Usage:](#usage) * [Example Plugin #2 - Making extensions available for all players.](#example-plugin-2---making-extensions-available-for-all-players) diff --git a/src/docs/javascript/generateTOC.js b/src/docs/javascript/generateTOC.js index de840c4..4d0b1c9 100644 --- a/src/docs/javascript/generateTOC.js +++ b/src/docs/javascript/generateTOC.js @@ -13,24 +13,24 @@ while ( (line = br.readLine()) != null){ } br.close(); +var createLink = function(text){ + var result = text.replace(/_/g,'_'); + result = result.replace(/[^a-zA-Z0-9 _\-]/g,''); + result = result.replace(/ /g,'-'); + return result.toLowerCase(); +}; println('## Table of Contents'); for (var i = 0; i < contents.length; i++){ line = contents[i]; if (line.match(/^##\s+/)){ var h2 = line.match(/^##\s+(.*)/)[1].trim(); - var link = h2.replace(/_/g,''); - link = link.replace(/[^a-zA-Z0-9 \-]/g,''); - link = link.replace(/ /g,'-'); - link = link.toLowerCase(); + var link = createLink(h2); println (' * [' + h2 + '](#' + link + ')'); } if (line.match(/^###\s+/)){ var h3 = line.match(/^###\s+(.*)/)[1].trim(); - var link = h3.replace(/_/g,''); - link = link.replace(/[^a-zA-Z0-9 \-]/g,''); - var link = link.replace(/ /g,'-'); - link = link.toLowerCase(); + var link = createLink(h3); println (' * [' + h3 + '](#' + link + ')'); } } From c591ec06a6d412b1ccb04efdbcbebe9e088b5bf6 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 4 Jan 2014 22:06:23 +0000 Subject: [PATCH 085/456] fix issue #107 --- .../YoungPersonsGuideToProgrammingMinecraft.md | 18 +++++++++++------- src/docs/templates/ypgpm.mdt | 18 +++++++++++------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/docs/YoungPersonsGuideToProgrammingMinecraft.md b/docs/YoungPersonsGuideToProgrammingMinecraft.md index 0cd95d2..33f5d9c 100644 --- a/docs/YoungPersonsGuideToProgrammingMinecraft.md +++ b/docs/YoungPersonsGuideToProgrammingMinecraft.md @@ -873,26 +873,30 @@ loops come in. Open your favorite text editor and create a new file in your scriptcraft/plugins/{your-name} directory, name the file `myskyscraper.js`, then type the following... - exports.myskyscraper = function(floors) - { - floors = floors || 10; // default number of floors is 10 + var myskyscraper = function(floors) { + if (typeof floors == 'undefined'){ + floors = 10; + } this.chkpt('myskyscraper'); // saves the drone position so it can return there later for (var i = 0; i < floors; i++) { - this.box(blocks.iron,20,1,20).up().box0(blocks.glass_pane,20,3,20).up(3); + this.box(blocks.iron,20,1,20) + .up() + .box0(blocks.glass_pane,20,3,20) + .up(3); } return this.move('myskyscraper'); // return to where we started }; - load('../drone/drone.js'); + var Drone = require('../drone/drone.js').Drone; Drone.extend('myskyscraper',myskyscraper); ... so this takes a little explaining. First I create a new function called myskyscraper that will take a single parameter `floors` so that when you eventually call the `myskyscraper()` function you can tell it how many floors you want built. The first statement in the function -`floors = floors || 10;` just sets floors to 10 if no parameter is -supplied. The next statement `this.chkpt('myskyscraper')` just saves +`if (typeof floors == 'undefined'){ floors = 10; }` sets floors to 10 if no parameter is +supplied. The next statement `this.chkpt('myskyscraper')` saves the position of the Drone so it can eventually return to where it started when finished building (I don't want the drone stranded atop the skyscraper when it's finished). Then comes the `for` loop. I loop diff --git a/src/docs/templates/ypgpm.mdt b/src/docs/templates/ypgpm.mdt index 0d7ea54..bc04520 100644 --- a/src/docs/templates/ypgpm.mdt +++ b/src/docs/templates/ypgpm.mdt @@ -837,26 +837,30 @@ loops come in. Open your favorite text editor and create a new file in your scriptcraft/plugins/{your-name} directory, name the file `myskyscraper.js`, then type the following... - exports.myskyscraper = function(floors) - { - floors = floors || 10; // default number of floors is 10 + var myskyscraper = function(floors) { + if (typeof floors == 'undefined'){ + floors = 10; + } this.chkpt('myskyscraper'); // saves the drone position so it can return there later for (var i = 0; i < floors; i++) { - this.box(blocks.iron,20,1,20).up().box0(blocks.glass_pane,20,3,20).up(3); + this.box(blocks.iron,20,1,20) + .up() + .box0(blocks.glass_pane,20,3,20) + .up(3); } return this.move('myskyscraper'); // return to where we started }; - load('../drone/drone.js'); + var Drone = require('../drone/drone.js').Drone; Drone.extend('myskyscraper',myskyscraper); ... so this takes a little explaining. First I create a new function called myskyscraper that will take a single parameter `floors` so that when you eventually call the `myskyscraper()` function you can tell it how many floors you want built. The first statement in the function -`floors = floors || 10;` just sets floors to 10 if no parameter is -supplied. The next statement `this.chkpt('myskyscraper')` just saves +`if (typeof floors == 'undefined'){ floors = 10; }` sets floors to 10 if no parameter is +supplied. The next statement `this.chkpt('myskyscraper')` saves the position of the Drone so it can eventually return to where it started when finished building (I don't want the drone stranded atop the skyscraper when it's finished). Then comes the `for` loop. I loop From 73fdf04bc27da975b8a08e19ac51e6747ada47ee Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sun, 5 Jan 2014 12:23:09 +0000 Subject: [PATCH 086/456] Target java 1.6 and fix exception on Mac OS due to alias plugin --- build.xml | 12 ++++++------ docs/release-notes.md | 8 ++++++++ src/main/javascript/modules/utils/string-exts.js | 6 ++++++ 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/build.xml b/build.xml index 749010c..d82e4bb 100644 --- a/build.xml +++ b/build.xml @@ -58,15 +58,17 @@
- + - + + + @@ -94,8 +96,7 @@ Walter Higgins - - + @@ -105,8 +106,7 @@ Walter Higgins - - + diff --git a/docs/release-notes.md b/docs/release-notes.md index e081839..87a6af8 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,3 +1,11 @@ +# 2014 01 05 + +Bug Fix: On Mac OS, alias plugin caused Exceptions due to missing +.trim() function on String. + +Changed target for compilation to 1.6 so that ScriptCraft will work +with both java 1.6 and 1.7. + # 2014 01 02 Added a warning in console at start-up if legacy directories are detected. diff --git a/src/main/javascript/modules/utils/string-exts.js b/src/main/javascript/modules/utils/string-exts.js index bb33e23..1e7159b 100644 --- a/src/main/javascript/modules/utils/string-exts.js +++ b/src/main/javascript/modules/utils/string-exts.js @@ -77,3 +77,9 @@ for (var method in formattingCodes){ return function(){return c+this;}; }(formattingCodes[method]); } +// wph 20140105 trim not availabe in String on Mac OS. +if (!String.prototype.trim){ + String.prototype.trim = function(){ + return this.replace(/^\s+|\s+$/g,''); + } +} From 8c3dc92c2a79204e6e441d9f2203aace5781fe2f Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sun, 5 Jan 2014 15:20:29 +0000 Subject: [PATCH 087/456] Added documentation for the Signs module and changed Signs examples so that a valid sign must be provided to the function returned by signs.menu(). --- docs/API-Reference.md | 92 +++++++++++++++ src/main/javascript/modules/http/request.js | 11 +- src/main/javascript/modules/signs/menu.js | 17 ++- .../javascript/modules/signs/package.json | 2 +- src/main/javascript/modules/signs/signs.js | 107 ++++++++++++++++++ src/main/javascript/plugins/arrows.js | 16 ++- src/main/javascript/plugins/signs/examples.js | 35 ++++-- 7 files changed, 259 insertions(+), 21 deletions(-) create mode 100644 src/main/javascript/modules/signs/signs.js diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 2d7b345..a53566b 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -53,6 +53,9 @@ Walter Higgins * [http.request() function](#httprequest-function) * [Parameters](#parameters) * [Example](#example) + * [Signs Module](#signs-module) + * [signs.menu() function](#signsmenu-function) + * [signs.getTargetedBy() function](#signsgettargetedby-function) * [Utilities Module](#utilities-module) * [utils.player() function](#utilsplayer-function) * [utils.locationToJSON() function](#utilslocationtojson-function) @@ -821,6 +824,95 @@ The following example illustrates how to use http.request to make a request to a var jsObj = eval("(" + responseBody + ")"); }); +## Signs Module + +The Signs Module can be used by plugin authors to create interactive +signs - that is - signs which display a list of choices which can be +changed by interacting (right-clicking) with the sign. + +### signs.menu() function + +This function is used to construct a new interactive menu on top of an +existing sign in the game world. + +#### Parameters + + * Label : A string which will be displayed in the topmost line of the + sign. This label is not interactive. + * options : An array of strings which can be selected on the sign by + right-clicking/interacting. + * callback : A function which will be called whenever a player + interacts (changes selection) on a sign. This callback in turn + takes as its parameter, an object with the following properties... + + * player : The player who interacted with the sign. + * sign : The [org.bukkit.block.Sign][buksign] which the player interacted with. + * text : The text for the currently selected option on the sign. + * number : The index of the currently selected option on the sign. + + * selectedIndex : optional: A number (starting at 0) indicating which + of the options should be selected by default. 0 is the default. + +#### Returns +This function does not itself do much. It does however return a +function which when invoked with a given +[org.bukkit.block.Sign][buksign] object, will convert that sign into +an interactive sign. + +#### Example: Create a sign which changes the time of day. + +##### plugins/signs/time-of-day.js + + var utils = require('utils'), + signs = require('signs'); + + var onTimeChoice = function(event){ + var selectedIndex = event.number; + // convert to Minecraft time 0 = Dawn, 6000 = midday, 12000 = dusk, 18000 = midnight + var time = selectedIndex * 6000; + event.player.location.world.setTime(time); + }; + + // signs.menu returns a function which can be called for one or more signs in the game. + var convertToTimeMenu = signs.menu('Time of Day', + ['Dawn', 'Midday', 'Dusk', 'Midnight'], + onTimeChoice); + + exports.time_sign = function( player ){ + + var sign = signs.getTargetedBy(player); + if (!sign){ + throw new Error('You must look at a sign'); + } + convertToTimeMenu(sign); + }; + +To use the above function at the in-game prompt, look at an existing +sign and type... + + /js time_sign(self); + +... and the sign you're looking at will become an interactive sign +which changes the time each time you interact (right-click) with it. + +### signs.getTargetedBy() function + +This function takes a [org.bukkit.entity.LivingEntity][bukle] as a +parameter and returns a [org.bukkit.block.Sign][buksign] object which +the entity has targeted. It is a utility function for use by plugin authors. + +#### Example + + var signs = require('signs'), + utils = require('utils'); + var player = utils.player('tom1234'); + var sign = signs.getTargetedBy( player ); + if (!sign){ + player.sendMessage('Not looking at a sign'); + } + +[buksign]: http://jd.bukkit.org/dev/apidocs/org/bukkit/block/Sign.html + String class extensions ----------------------- The following chat-formatting methods are added to the javascript String class.. diff --git a/src/main/javascript/modules/http/request.js b/src/main/javascript/modules/http/request.js index 9be0b36..00d2965 100644 --- a/src/main/javascript/modules/http/request.js +++ b/src/main/javascript/modules/http/request.js @@ -1,5 +1,10 @@ /************************************************************************* -## http.request() function +## Http Module + +For handling http requests. Not to be confused with the more robust +and functional 'http' module bundled with Node.js. + +### http.request() function The http.request() function will fetch a web address asynchronously (on a separate thread)and pass the URL's response to a callback function @@ -7,7 +12,7 @@ which will be executed synchronously (on the main thread). In this way, http.request() can be used to fetch web content without blocking the main thread of execution. -### Parameters +#### Parameters * request: The request details either a plain URL e.g. "http://scriptcraft.js/sample.json" or an object with the following properties... @@ -19,7 +24,7 @@ main thread of execution. - responseCode: The numeric response code from the server. If the server did not respond with 200 OK then the response parameter will be undefined. - response: A string (if the response is of type text) or object containing the HTTP response body. -### Example +#### Example The following example illustrates how to use http.request to make a request to a JSON web service and evaluate its response... diff --git a/src/main/javascript/modules/signs/menu.js b/src/main/javascript/modules/signs/menu.js index f07cb84..32aba39 100644 --- a/src/main/javascript/modules/signs/menu.js +++ b/src/main/javascript/modules/signs/menu.js @@ -48,12 +48,12 @@ signs.menu = function( /* String */ label, /* Array */ options, /* Function */ callback, - /* Number */ selectedIndex) + /* Number */ selectedIndex +) { if (typeof selectedIndex == "undefined") selectedIndex = 0; - // // variables common to all instances of this menu can go here // @@ -76,15 +76,24 @@ signs.menu = function( { if (typeof save == "undefined") save = true; - + /* + @deprecated start + all calls should explicitly provide a [org.bukkit.block.Sign][buksign] parameter. + */ if (typeof sign == "undefined"){ var mouseLoc = utils.getMousePos(); if (mouseLoc){ sign = mouseLoc.block.state; + if (!(sign && sign.setLine)){ + throw new Error("You must first provide a sign!"); + } }else{ - throw new Exception("You must provide a sign!"); + throw new Error("You must first provide a sign!"); } } + /* + @deprecated end + */ // // per-sign variables go here // diff --git a/src/main/javascript/modules/signs/package.json b/src/main/javascript/modules/signs/package.json index e59258e..520d7aa 100644 --- a/src/main/javascript/modules/signs/package.json +++ b/src/main/javascript/modules/signs/package.json @@ -1,4 +1,4 @@ { name: 'signs', - main: './menu.js' + main: './signs.js' } diff --git a/src/main/javascript/modules/signs/signs.js b/src/main/javascript/modules/signs/signs.js new file mode 100644 index 0000000..686a6b1 --- /dev/null +++ b/src/main/javascript/modules/signs/signs.js @@ -0,0 +1,107 @@ +/************************************************************************ +## Signs Module + +The Signs Module can be used by plugin authors to create interactive +signs - that is - signs which display a list of choices which can be +changed by interacting (right-clicking) with the sign. + +### signs.menu() function + +This function is used to construct a new interactive menu on top of an +existing sign in the game world. + +#### Parameters + + * Label : A string which will be displayed in the topmost line of the + sign. This label is not interactive. + * options : An array of strings which can be selected on the sign by + right-clicking/interacting. + * callback : A function which will be called whenever a player + interacts (changes selection) on a sign. This callback in turn + takes as its parameter, an object with the following properties... + + * player : The player who interacted with the sign. + * sign : The [org.bukkit.block.Sign][buksign] which the player interacted with. + * text : The text for the currently selected option on the sign. + * number : The index of the currently selected option on the sign. + + * selectedIndex : optional: A number (starting at 0) indicating which + of the options should be selected by default. 0 is the default. + +#### Returns +This function does not itself do much. It does however return a +function which when invoked with a given +[org.bukkit.block.Sign][buksign] object, will convert that sign into +an interactive sign. + +#### Example: Create a sign which changes the time of day. + +##### plugins/signs/time-of-day.js + + var utils = require('utils'), + signs = require('signs'); + + var onTimeChoice = function(event){ + var selectedIndex = event.number; + // convert to Minecraft time 0 = Dawn, 6000 = midday, 12000 = dusk, 18000 = midnight + var time = selectedIndex * 6000; + event.player.location.world.setTime(time); + }; + + // signs.menu returns a function which can be called for one or more signs in the game. + var convertToTimeMenu = signs.menu('Time of Day', + ['Dawn', 'Midday', 'Dusk', 'Midnight'], + onTimeChoice); + + exports.time_sign = function( player ){ + + var sign = signs.getTargetedBy(player); + if (!sign){ + throw new Error('You must look at a sign'); + } + convertToTimeMenu(sign); + }; + +To use the above function at the in-game prompt, look at an existing +sign and type... + + /js time_sign(self); + +... and the sign you're looking at will become an interactive sign +which changes the time each time you interact (right-click) with it. + +### signs.getTargetedBy() function + +This function takes a [org.bukkit.entity.LivingEntity][bukle] as a +parameter and returns a [org.bukkit.block.Sign][buksign] object which +the entity has targeted. It is a utility function for use by plugin authors. + +#### Example + + var signs = require('signs'), + utils = require('utils'); + var player = utils.player('tom1234'); + var sign = signs.getTargetedBy( player ); + if (!sign){ + player.sendMessage('Not looking at a sign'); + } + +[buksign]: http://jd.bukkit.org/dev/apidocs/org/bukkit/block/Sign.html + +***/ +var utils = require('utils'); +var menu = require('./menu'); +// include all menu exports +for (var i in menu){ + exports[i] = menu[i]; +} + +exports.getTargetedBy = function( livingEntity ){ + var location = utils.getMousePos( livingEntity ); + if (!location) + return null; + var state = location.block.state; + if (!(state || state.setLine)) + return null; + return state; +}; diff --git a/src/main/javascript/plugins/arrows.js b/src/main/javascript/plugins/arrows.js index 9b0bbe7..dcac397 100644 --- a/src/main/javascript/plugins/arrows.js +++ b/src/main/javascript/plugins/arrows.js @@ -91,9 +91,19 @@ for (var type in _types) var _onMenuChoice = function(event){ arrows.store.players[event.player.name] = event.number; }; -arrows.sign = signs.menu("Arrow", - ["Normal","Explosive","Teleport","Flourish","Lightning","Firework"], - _onMenuChoice ); +var convertToArrowSign = signs.menu( + "Arrow", + ["Normal","Explosive","Teleport","Flourish","Lightning","Firework"], + _onMenuChoice); + +arrows.sign = function(cmdSender) +{ + var sign = signs.getTargetedBy(cmdSender); + if (!sign){ + throw new Error('You must first look at a sign!'); + } + return convertToArrowSign(sign,true); +}; /* event handler called when a projectile hits something diff --git a/src/main/javascript/plugins/signs/examples.js b/src/main/javascript/plugins/signs/examples.js index dd1cdd3..179ee3f 100644 --- a/src/main/javascript/plugins/signs/examples.js +++ b/src/main/javascript/plugins/signs/examples.js @@ -10,12 +10,25 @@ var signs = require('signs'); // // /js signs.menu_time() // + +var onDinnerChoice = function(event){ + event.player.sendMessage("You chose " + event.text); +}; +var convertToDinnerMenu = signs.menu("Dinner", ["Lamb","Pork","Chicken","Duck","Beef"], onDinnerChoice); + +var onTimeChoice = function(event){ + event.player.location.world.setTime( event.number * 6000 ); +}; +var convertToTimeMenu = signs.menu("Time", ["Dawn","Midday","Dusk","Midnight"], onTimeChoice); + exports.signs = { - menu_food: signs.menu("Dinner", - ["Lamb","Pork","Chicken","Duck","Beef"], - function(event){ - event.player.sendMessage("You chose " + event.text); - }), + menu_food: function(cmdSender){ + var sign = signs.getTargetedBy(cmdSender); + if (!sign){ + throw new Error('You must look at an existing sign'); + } + convertToDinnerMenu(sign); + }, // // This is an example sign that displays a menu of times of day // interacting with the sign will change the time of day accordingly. @@ -25,10 +38,12 @@ exports.signs = { // /js var signExamples = require('./signs/examples'); // /js signExamples.timeOfDay() // - menu_time: signs.menu("Time", - ["Dawn","Midday","Dusk","Midnight"], - function(event){ - event.player.location.world.setTime( event.number * 6000 ); - }) + menu_time: function(cmdSender){ + var sign = signs.getTargetedBy(cmdSender); + if (!sign){ + throw new Error('You must look at an existing sign'); + } + convertToTimeMenu(sign); + } } From 06f9007369d6202554d37711bebb97f0d328ef93 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sun, 5 Jan 2014 17:20:54 +0000 Subject: [PATCH 088/456] self is available for autocompletion --- docs/API-Reference.md | 16 ++++++++++------ docs/release-notes.md | 2 ++ src/main/javascript/lib/tabcomplete.js | 4 ++++ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index a53566b..61cad66 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -50,9 +50,8 @@ Walter Higgins * [Examples](#examples) * [Fireworks Module](#fireworks-module) * [Examples](#examples) - * [http.request() function](#httprequest-function) - * [Parameters](#parameters) - * [Example](#example) + * [Http Module](#http-module) + * [http.request() function](#httprequest-function) * [Signs Module](#signs-module) * [signs.menu() function](#signsmenu-function) * [signs.getTargetedBy() function](#signsgettargetedby-function) @@ -784,7 +783,12 @@ location. For example... ![firework example](img/firework.png) -## http.request() function +## Http Module + +For handling http requests. Not to be confused with the more robust +and functional 'http' module bundled with Node.js. + +### http.request() function The http.request() function will fetch a web address asynchronously (on a separate thread)and pass the URL's response to a callback function @@ -792,7 +796,7 @@ which will be executed synchronously (on the main thread). In this way, http.request() can be used to fetch web content without blocking the main thread of execution. -### Parameters +#### Parameters * request: The request details either a plain URL e.g. "http://scriptcraft.js/sample.json" or an object with the following properties... @@ -804,7 +808,7 @@ main thread of execution. - responseCode: The numeric response code from the server. If the server did not respond with 200 OK then the response parameter will be undefined. - response: A string (if the response is of type text) or object containing the HTTP response body. -### Example +#### Example The following example illustrates how to use http.request to make a request to a JSON web service and evaluate its response... diff --git a/docs/release-notes.md b/docs/release-notes.md index 87a6af8..9445e24 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -6,6 +6,8 @@ Bug Fix: On Mac OS, alias plugin caused Exceptions due to missing Changed target for compilation to 1.6 so that ScriptCraft will work with both java 1.6 and 1.7. +Added documentation for the Signs module. + # 2014 01 02 Added a warning in console at start-up if legacy directories are detected. diff --git a/src/main/javascript/lib/tabcomplete.js b/src/main/javascript/lib/tabcomplete.js index 9b85d3d..2a2d560 100644 --- a/src/main/javascript/lib/tabcomplete.js +++ b/src/main/javascript/lib/tabcomplete.js @@ -78,6 +78,8 @@ var onTabCompleteJS = function( __onTC_result, __onTC_sender, __onTC_cmd, __onTC if (__onTC_cmd.name == 'jsp') return tabCompleteJSP( __onTC_result, __onTC_sender, __onTC_cmd, __onTC_alias, __onTC_args ); + global.self = __onTC_sender; // bring in self just for autocomplete + var _globalSymbols = _getProperties(global) var result = __onTC_result; var args = __onTC_args; @@ -169,5 +171,7 @@ var onTabCompleteJS = function( __onTC_result, __onTC_sender, __onTC_cmd, __onTC } for (var i = 0;i < propsOfLastArg.length; i++) result.add(propsOfLastArg[i]); + + delete global.self; // delete self when no longer needed for autocomplete }; module.exports = onTabCompleteJS; From cc4a3e3a1467a0e74d1adbc636ff4ba9d3a33497 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Mon, 6 Jan 2014 20:54:53 +0000 Subject: [PATCH 089/456] Added java apis in js doc. Fixed toc links. save uses pretty json. --- build.properties | 2 +- docs/API-Reference.md | 22 ++-- docs/Using-Java-APIs-In-Javascript.md | 121 +++++++++++++++++++++ src/docs/javascript/generateTOC.js | 9 +- src/main/javascript/lib/plugin.js | 2 +- src/main/javascript/lib/tabcomplete-jsp.js | 16 ++- src/main/javascript/lib/tabcomplete.js | 16 +-- 7 files changed, 157 insertions(+), 31 deletions(-) create mode 100644 docs/Using-Java-APIs-In-Javascript.md diff --git a/build.properties b/build.properties index 4ef2b33..9d6409b 100644 --- a/build.properties +++ b/build.properties @@ -1,2 +1,2 @@ bukkit-version=1.7.2 -scriptcraft-version=2.0 +scriptcraft-version=2.0.1 diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 61cad66..7119a64 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -49,7 +49,7 @@ Walter Higgins * [Blocks Module](#blocks-module) * [Examples](#examples) * [Fireworks Module](#fireworks-module) - * [Examples](#examples) + * [Examples](#examples-1) * [Http Module](#http-module) * [http.request() function](#httprequest-function) * [Signs Module](#signs-module) @@ -105,20 +105,20 @@ Walter Higgins * [Example Plugin #1 - A simple extension to Minecraft.](#example-plugin-1---a-simple-extension-to-minecraft) * [Usage:](#usage) * [Example Plugin #2 - Making extensions available for all players.](#example-plugin-2---making-extensions-available-for-all-players) - * [Usage:](#usage) + * [Usage:](#usage-1) * [Example Plugin #3 - Limiting use of commands to operators only.](#example-plugin-3---limiting-use-of-commands-to-operators-only) - * [Usage:](#usage) + * [Usage:](#usage-2) * [Example Plugin #4 - Using parameters in commands.](#example-plugin-4---using-parameters-in-commands) - * [Usage:](#usage) + * [Usage:](#usage-3) * [Example Plugin #5 - Re-use - Using your own and others modules.](#example-plugin-5---re-use---using-your-own-and-others-modules) - * [Usage:](#usage) + * [Usage:](#usage-4) * [Example Plugin #6 - Re-use - Using 'utils' to get Player objects.](#example-plugin-6---re-use---using-utils-to-get-player-objects) - * [Usage:](#usage) + * [Usage:](#usage-5) * [Example Plugin #7 - Listening for events, Greet players when they join the game.](#example-plugin-7---listening-for-events-greet-players-when-they-join-the-game) * [Arrows Plugin](#arrows-plugin) - * [Usage:](#usage) + * [Usage:](#usage-6) * [alias Plugin](#alias-plugin) - * [Examples](#examples) + * [Examples](#examples-2) * [Classroom Plugin](#classroom-plugin) * [classroom.allowScripting() function](#classroomallowscripting-function) * [Commando Plugin](#commando-plugin) @@ -131,10 +131,10 @@ Walter Higgins * [Social options](#social-options) * [Administration options](#administration-options) * [NumberGuess mini-game:](#numberguess-mini-game) - * [Description](#description) - * [Example](#example) + * [Description](#description-1) + * [Example](#example-1) * [SnowballFight mini-game](#snowballfight-mini-game) - * [Description](#description) + * [Description](#description-2) ## Modules in Scriptcraft diff --git a/docs/Using-Java-APIs-In-Javascript.md b/docs/Using-Java-APIs-In-Javascript.md new file mode 100644 index 0000000..3166ea6 --- /dev/null +++ b/docs/Using-Java-APIs-In-Javascript.md @@ -0,0 +1,121 @@ +# Using Java APIs in Javascript + +## Using java.lang package classes + +ScriptCraft uses the Javascript Engine bundled with Java 6 and later +versions. This means that all of the core Java classes can be used +from within ScriptCraft. For example, in Java the following code will +print out the `user.dir` and `user.timezone` properties... + + System.out.println( System.getProperty( "user.dir" ) ); + System.out.println( System.getProperty( "user.timezone" ) ); + +... In Java, any classes in the `java.lang` package don't need to be +prefixed with the package so the `java.lang.System` class can simply +be written as `System`. In Javascript you need to write... + + println( java.lang.System.getProperty( "user.dir" ) ); + println( java.lang.System.getProperty( "user.timezone" ) ); + +... the `println()` function is one of the default functions provided +by the JS Engine in Java so there is no need to add the class name +prefix, but for other System class methods you need to explicitly +include the package name e.g. `java.lang.`. If you are using the +System class in a number of statements you can save yourself some +typing by declaring a System variable and using that instead of the +fully-qualified package and class name... + + var System = java.lang.System; + println( System.getProperty( "user.dir" ) ); + println( System.getProperty( "user.timezone" ) ); + +The JS Engine provides an `importPackage()` function which can be used +to import packages. This also saves you having to type full package +names before classes. For example... + + importPackage(java.util); + var hMap = new HashMap(); + hMap.put('name','Walter'); + +... makes all of the classes in the Java Library's `java.util` package +available for use without having to use the `java.util` +prefix. However, importing the `java.lang` package is not recommended +as some of the java.lang classes (e.g. String, Object) conflict with +Javascript Object types. + +## Using Java Beans + +The Javascript Engine bundled with Java comes with a handy notation +for accessing and modifying Java Beans. A Java Bean is any Java class +which uses a `get{Property}()` method to retrieve an object's property +and a `set{Property}()` method to set the object's property. One +example of a Java Bean in the [Bukkit API][bukapi] is the +[org.bukkit.entity.Player][bukpl] Class which has many methods which +conform to the JavaBean specification. + +For example the [Player.getWalkSpeed()][bukplws] can be used to get a +player's walking speed. In Java you would have to write code like this +to obtain the walking speed... + + float walkingSpeed = player.getWalkSpeed(); + +... however, in Javascript you can access the walking-speed property +using the more succinct... + + var walkingspeed = player.walkSpeed; + +... or if you prefer to use Java-style access... + + var walkingspeed = player.getWalkSpeed(); + +... I personally prefer to use the simpler `player.walkSpeed` because +it is easier to read. The important thing to remember when using the +Bukkit (or any Java API) from Javascript is that for any Java Bean, a +property called `propertyName` will have a getter called +`getPropertyName()` and a setter called `setPropertyName`. From this +rule you can infer what any Bukkit class properties are. For example, +the [Bukkit Player][bukpl] object has the following methods... + + * float getWalSpeed() + * void setWalkSpeed(float speed) + +... so from this you can infer that every Player object has a +`walkSpeed` property which can be read and changed. For example you +can triple your own walking speed (from the default 0.2) at the +in-game prompt using the following command... + + /js self.walkSpeed = self.walkSpeed * 3; + +... If we were limited to using Java's notation we would have had to +write `/js self.setWalkSpeed( self.getWalkSpeed() * 3 )` . Since +almost every class in the Bukkit API is also a JavaBean you can access +properties of properties and so on. For example, to get the name of +the world in which a player is located... + + /js self.location.world.name + +... is more concise than `/js self.getLocation().getWorld().getName()`. +If you're new to Java and the [Bukkit API][bukapi] is the first time +you've browsed Java documentation, you may be wondering where the +`location` property came from - the `location` property is "inherited" +by one of the Player classes super-classes. You'll see the +`getLocation()` method listed under a section titled **Methods +inherited from interface org.bukkit.entity.Entity** in the +[Player][bukpl] javadoc page. + +## Summary + +When writing modules or plugins in ScriptCraft, you can access and +change JavaBean properties using a simple .{propertyName} notation +instead of using the Java .get{PropertyName}() and .set{PropertyName() +methods. This results in more concise code. This simpler notation is +provided by the Javascript Engine embedded in Java 6 and later +versions. Javascript does not have access to private members, the +.{propertyName} notation is automagically converted to the appropriate +.get{PropertyName}() or .set{PropertyName}() method by Java. + +[bukapi]: http://jd.bukkit.org/beta/apidocs/ +[bukpl]: http://jd.bukkit.org/beta/apidocs/org/bukkit/entity/Player.html +[bukplws]: http://jd.bukkit.org/beta/apidocs/org/bukkit/entity/Player.html#getWalkSpeed() +[buksrv]: http://jd.bukkit.org/beta/apidocs/org/bukkit/Server.html + diff --git a/src/docs/javascript/generateTOC.js b/src/docs/javascript/generateTOC.js index 4d0b1c9..5724472 100644 --- a/src/docs/javascript/generateTOC.js +++ b/src/docs/javascript/generateTOC.js @@ -13,11 +13,18 @@ while ( (line = br.readLine()) != null){ } br.close(); +var anchors = {}; + var createLink = function(text){ var result = text.replace(/_/g,'_'); result = result.replace(/[^a-zA-Z0-9 _\-]/g,''); result = result.replace(/ /g,'-'); - return result.toLowerCase(); + var result = result.toLowerCase(); + if (anchors[result]){ + result = result + '-' + (anchors[result]++); + } + anchors[result] = 1; + return result; }; println('## Table of Contents'); diff --git a/src/main/javascript/lib/plugin.js b/src/main/javascript/lib/plugin.js index 87d7978..0b8cb47 100644 --- a/src/main/javascript/lib/plugin.js +++ b/src/main/javascript/lib/plugin.js @@ -10,7 +10,7 @@ var PrintWriter = java.io.PrintWriter; var _save = function(object, filename){ var objectToStr = null; try{ - objectToStr = JSON.stringify(object); + objectToStr = JSON.stringify(object,null,2); }catch(e){ print("ERROR: " + e.getMessage() + " while saving " + filename); return; diff --git a/src/main/javascript/lib/tabcomplete-jsp.js b/src/main/javascript/lib/tabcomplete-jsp.js index e2106ed..29490c2 100644 --- a/src/main/javascript/lib/tabcomplete-jsp.js +++ b/src/main/javascript/lib/tabcomplete-jsp.js @@ -3,32 +3,30 @@ var _commands = require('command').commands; /* Tab completion for the /jsp commmand */ -var __onTabCompleteJSP = function( __onTC_result, __onTC_sender, __onTC_cmd, __onTC_alias, __onTC_args) { - var result = __onTC_result; - var args = __onTC_args; - var cmdInput = args[0]; +var __onTabCompleteJSP = function( result, cmdSender, pluginCmd, cmdAlias, cmdArgs) { + var cmdInput = cmdArgs[0]; var cmd = _commands[cmdInput]; if (cmd){ var opts = cmd.options; var len = opts.length; - if (args.length == 1){ + if (cmdArgs.length == 1){ for (var i = 0;i < len; i++) result.add(opts[i]); }else{ // partial e.g. /jsp chat_color dar for (var i = 0;i < len; i++){ - if (opts[i].indexOf(args[1]) == 0){ + if (opts[i].indexOf(cmdArgs[1]) == 0){ result.add(opts[i]); } } } }else{ - if (args.length == 0){ + if (cmdArgs.length == 0){ for (var i in _commands) result.add(i); }else{ - // partial e.g. /jsp al - // should tabcomplete to alias + // partial e.g. /jsp ho + // should tabcomplete to home // for (var c in _commands){ if (c.indexOf(cmdInput) == 0){ diff --git a/src/main/javascript/lib/tabcomplete.js b/src/main/javascript/lib/tabcomplete.js index 2a2d560..92133b3 100644 --- a/src/main/javascript/lib/tabcomplete.js +++ b/src/main/javascript/lib/tabcomplete.js @@ -74,18 +74,18 @@ var _getProperties = function(o) return result.sort(); }; -var onTabCompleteJS = function( __onTC_result, __onTC_sender, __onTC_cmd, __onTC_alias, __onTC_args) { - if (__onTC_cmd.name == 'jsp') - return tabCompleteJSP( __onTC_result, __onTC_sender, __onTC_cmd, __onTC_alias, __onTC_args ); +var onTabCompleteJS = function( result, cmdSender, pluginCmd, cmdAlias, cmdArgs) { - global.self = __onTC_sender; // bring in self just for autocomplete + if (pluginCmd.name == 'jsp') + return tabCompleteJSP( result, cmdSender, pluginCmd, cmdAlias, cmdArgs ); + + global.self = cmdSender; // bring in self just for autocomplete var _globalSymbols = _getProperties(global) - var result = __onTC_result; - var args = __onTC_args; - var lastArg = args.length?args[args.length-1]+'':null; + + var lastArg = cmdArgs.length?cmdArgs[cmdArgs.length-1]+'':null; var propsOfLastArg = []; - var statement = args.join(' '); + var statement = cmdArgs.join(' '); statement = statement.replace(/^\s+/,'').replace(/\s+$/,''); From c77e78c2e5bd1827755f223dab9740c35b71e7be Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Mon, 6 Jan 2014 20:59:59 +0000 Subject: [PATCH 090/456] switch paragraph order in java api doc --- docs/Using-Java-APIs-In-Javascript.md | 85 ++++++++++++++------------- 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/docs/Using-Java-APIs-In-Javascript.md b/docs/Using-Java-APIs-In-Javascript.md index 3166ea6..f295159 100644 --- a/docs/Using-Java-APIs-In-Javascript.md +++ b/docs/Using-Java-APIs-In-Javascript.md @@ -1,47 +1,10 @@ # Using Java APIs in Javascript -## Using java.lang package classes - ScriptCraft uses the Javascript Engine bundled with Java 6 and later versions. This means that all of the core Java classes can be used -from within ScriptCraft. For example, in Java the following code will -print out the `user.dir` and `user.timezone` properties... - - System.out.println( System.getProperty( "user.dir" ) ); - System.out.println( System.getProperty( "user.timezone" ) ); - -... In Java, any classes in the `java.lang` package don't need to be -prefixed with the package so the `java.lang.System` class can simply -be written as `System`. In Javascript you need to write... - - println( java.lang.System.getProperty( "user.dir" ) ); - println( java.lang.System.getProperty( "user.timezone" ) ); - -... the `println()` function is one of the default functions provided -by the JS Engine in Java so there is no need to add the class name -prefix, but for other System class methods you need to explicitly -include the package name e.g. `java.lang.`. If you are using the -System class in a number of statements you can save yourself some -typing by declaring a System variable and using that instead of the -fully-qualified package and class name... - - var System = java.lang.System; - println( System.getProperty( "user.dir" ) ); - println( System.getProperty( "user.timezone" ) ); - -The JS Engine provides an `importPackage()` function which can be used -to import packages. This also saves you having to type full package -names before classes. For example... - - importPackage(java.util); - var hMap = new HashMap(); - hMap.put('name','Walter'); - -... makes all of the classes in the Java Library's `java.util` package -available for use without having to use the `java.util` -prefix. However, importing the `java.lang` package is not recommended -as some of the java.lang classes (e.g. String, Object) conflict with -Javascript Object types. +from within ScriptCraft. In addition, all of the Bukkit API can be +used from Javascript too. There are some things to consider when using +Java classes in Javascript... ## Using Java Beans @@ -103,6 +66,48 @@ by one of the Player classes super-classes. You'll see the inherited from interface org.bukkit.entity.Entity** in the [Player][bukpl] javadoc page. +## Using java.lang package classes + +In Java the following code will print out the `user.dir` and +`user.timezone` properties... + + System.out.println( System.getProperty( "user.dir" ) ); + System.out.println( System.getProperty( "user.timezone" ) ); + +... In Java, any classes in the `java.lang` package don't need to be +prefixed with the package so the `java.lang.System` class can simply +be written as `System`. However, in Javascript classes in the +`java.lang` package need to be fully qualified so you need to write... + + println( java.lang.System.getProperty( "user.dir" ) ); + println( java.lang.System.getProperty( "user.timezone" ) ); + +... the `println()` function is one of the default functions provided +by the JS Engine in Java so there is no need to add the class name +prefix, but for other System class methods you need to explicitly +include the package name e.g. `java.lang.`. If you are using the +System class in a number of statements you can save yourself some +typing by declaring a System variable and using that instead of the +fully-qualified package and class name... + + var System = java.lang.System; + println( System.getProperty( "user.dir" ) ); + println( System.getProperty( "user.timezone" ) ); + +The JS Engine provides an `importPackage()` function which can be used +to import packages. This also saves you having to type full package +names before classes. For example... + + importPackage(java.util); + var hMap = new HashMap(); + hMap.put('name','Walter'); + +... makes all of the classes in the Java Library's `java.util` package +available for use without having to use the `java.util` +prefix. However, importing the `java.lang` package is not recommended +as some of the java.lang classes (e.g. String, Object) conflict with +Javascript Object types. + ## Summary When writing modules or plugins in ScriptCraft, you can access and From fbb1cbd92fb2770a611a42159e12ce6c430bacc0 Mon Sep 17 00:00:00 2001 From: jonathan Date: Tue, 7 Jan 2014 20:56:33 -0800 Subject: [PATCH 091/456] fix typo in sapling/birch --- src/main/javascript/modules/blocks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/javascript/modules/blocks.js b/src/main/javascript/modules/blocks.js index 019a6bf..b16d358 100644 --- a/src/main/javascript/modules/blocks.js +++ b/src/main/javascript/modules/blocks.js @@ -33,7 +33,7 @@ var blocks = { sapling: { oak: 6, spruce: '6:1', - birch: '62:2', + birch: '6:2', jungle: '6:3' }, bedrock: 7, From c2886555e30faa6dd3ea7136048da0a1d7eda81e Mon Sep 17 00:00:00 2001 From: "K. M. Lawson" Date: Sat, 11 Jan 2014 12:32:16 -0500 Subject: [PATCH 092/456] reversed step 4-5 in install Reference to CraftBukkit command window seemed a bit out of place if the server is not running. Thanks for great work here! --- docs/YoungPersonsGuideToProgrammingMinecraft.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/YoungPersonsGuideToProgrammingMinecraft.md b/docs/YoungPersonsGuideToProgrammingMinecraft.md index 33f5d9c..c043c70 100644 --- a/docs/YoungPersonsGuideToProgrammingMinecraft.md +++ b/docs/YoungPersonsGuideToProgrammingMinecraft.md @@ -72,7 +72,9 @@ Install ScriptCraft on your computer... 3. [Download the latest version of the ScriptCraft Mod][sc-plugin]. Then copy the ScriptCraft.jar file to the `craftbukkit/plugins` folder (This folder won't be created until you run Bukkit for the first time (see previous step). -4. In the CraftBukkit command window type `op {your_username}` and hit +4. Start up the craftbukkit server again (see [instructions for starting the server][bii]). + +5. In the CraftBukkit command window type `op {your_username}` and hit enter, replacing {your_username} with your own minecraft username. This will give you `operator` access meaning you can perform more commands than are normally available in Minecraft. You should @@ -80,8 +82,6 @@ Install ScriptCraft on your computer... for the server) permanently by editing the craftbukkit/ops.txt file and adding your username (one username per line). -5. Start up the craftbukkit server again (see [instructions for starting the server][bii]). - 6. In the CraftBukkit command window type `js 1 + 1` and hit enter. You should see `> 2` . ... Congratulations! You just installed your own Minecraft Server with From aefc98f17235c3e54733937f2754e800c7326eea Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sun, 12 Jan 2014 11:26:26 +0000 Subject: [PATCH 093/456] Fix issue #111, reorg of lib/ and (undoc'd) persistence --- build.xml | 11 +- docs/API-Reference.md | 59 +++++ docs/Using-Java-APIs-In-Javascript.md | 6 +- docs/img/cowclicker.png | Bin 0 -> 100388 bytes docs/release-notes.md | 22 ++ src/docs/javascript/generateTOC.js | 7 + .../scriptcraft/ScriptCraftPlugin.java | 29 ++- src/main/javascript/lib/js-patch.js | 33 +++ src/main/javascript/lib/persistence.js | 37 +++ src/main/javascript/lib/plugin.js | 52 +--- src/main/javascript/lib/require.js | 13 +- src/main/javascript/lib/scriptcraft.js | 66 ++--- .../javascript/modules/utils/string-exts.js | 6 - .../javascript/plugins/classroom/classroom.js | 1 + .../plugins/minigames/NumberGuess.js | 16 ++ .../plugins/minigames/cow-clicker.js | 225 ++++++++++++++++++ src/main/javascript/plugins/spawn.js | 35 +++ src/main/resources/plugin.yml | 2 +- 18 files changed, 514 insertions(+), 106 deletions(-) create mode 100644 docs/img/cowclicker.png create mode 100644 src/main/javascript/lib/js-patch.js create mode 100644 src/main/javascript/lib/persistence.js create mode 100644 src/main/javascript/plugins/minigames/cow-clicker.js create mode 100644 src/main/javascript/plugins/spawn.js diff --git a/build.xml b/build.xml index d82e4bb..f9988f5 100644 --- a/build.xml +++ b/build.xml @@ -7,13 +7,6 @@ - - - - @@ -144,7 +137,7 @@ Walter Higgins - + @@ -157,6 +150,6 @@ Walter Higgins
- +
diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 7119a64..4caef0f 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -117,6 +117,8 @@ Walter Higgins * [Example Plugin #7 - Listening for events, Greet players when they join the game.](#example-plugin-7---listening-for-events-greet-players-when-they-join-the-game) * [Arrows Plugin](#arrows-plugin) * [Usage:](#usage-6) + * [Spawn Plugin](#spawn-plugin) + * [Usage](#usage-7) * [alias Plugin](#alias-plugin) * [Examples](#examples-2) * [Classroom Plugin](#classroom-plugin) @@ -135,6 +137,10 @@ Walter Higgins * [Example](#example-1) * [SnowballFight mini-game](#snowballfight-mini-game) * [Description](#description-2) + * [Cow Clicker Mini-Game](#cow-clicker-mini-game) + * [How to Play](#how-to-play) + * [Rules](#rules) + * [Gameplay Mechanics](#gameplay-mechanics) ## Modules in Scriptcraft @@ -2340,6 +2346,18 @@ All of the above functions can take an optional player object or name as a parameter. For example: `/js arrows.explosive('player23')` makes player23's arrows explosive. +## Spawn Plugin + +Allows in-game operators to easily spawn creatures at current location. + +### Usage + + /jsp spawn cow + /jsp spawn sheep + /jsp spawn wolf + +See for a list of possible entities (creatures) which can be spawned. + ## alias Plugin The alias module lets players and server admins create their own @@ -2621,3 +2639,44 @@ player returns to their previous mode of play (creative or survival). Create a small arena with a couple of small buildings for cover to make the game more fun. +## Cow Clicker Mini-Game + +### How to Play + +At the in-game prompt type `jsp cowclicker` to start or stop +playing. Right-Click on Cows to score points. No points for killing +cows (hint: use the same keyboard keys you'd use for opening doors). + +Every time you click a cow your score increases by 1 point. Your score +is displayed in a side-bar along the right edge of of the screen. + +![cow clicker](img/cowclicker.png) + +### Rules + + * You can join and leave the Cow Clicker game at any time by typing + `/jsp cowclicker` at the in-game prompt. + + * Once you leave the game, your score is reset to zero. + + * You can disconnect from the server and your score will be saved for + the next time you join. + +### Gameplay Mechanics + +This is meant as a trivially simple use of the [Bukkit Scoreboard +API][bukscore]. There are many things you'll want to consider when constructing +your own mini-game... + + * Is the game itself a long-lived game - that is - should players and + scores be persisted (stored) between server restarts? + + * What should happen when a player quits the server - should this also be + understood as quitting the mini-game? + + * What should happen when a player who was previously playing the + mini-game, joins the server - should they automatically resume the + mini-game? + +[bukscore]: http://jd.bukkit.org/beta/apidocs/org/bukkit/scoreboard/package-summary.html + diff --git a/docs/Using-Java-APIs-In-Javascript.md b/docs/Using-Java-APIs-In-Javascript.md index f295159..cfc2cc4 100644 --- a/docs/Using-Java-APIs-In-Javascript.md +++ b/docs/Using-Java-APIs-In-Javascript.md @@ -35,11 +35,11 @@ using the more succinct... it is easier to read. The important thing to remember when using the Bukkit (or any Java API) from Javascript is that for any Java Bean, a property called `propertyName` will have a getter called -`getPropertyName()` and a setter called `setPropertyName`. From this +`getPropertyName()` and a setter called `setPropertyName()`. From this rule you can infer what any Bukkit class properties are. For example, the [Bukkit Player][bukpl] object has the following methods... - * float getWalSpeed() + * float getWalkSpeed() * void setWalkSpeed(float speed) ... so from this you can infer that every Player object has a @@ -61,7 +61,7 @@ the world in which a player is located... If you're new to Java and the [Bukkit API][bukapi] is the first time you've browsed Java documentation, you may be wondering where the `location` property came from - the `location` property is "inherited" -by one of the Player classes super-classes. You'll see the +from one of the Player class's super-classes (it's ancestors). You'll see the `getLocation()` method listed under a section titled **Methods inherited from interface org.bukkit.entity.Entity** in the [Player][bukpl] javadoc page. diff --git a/docs/img/cowclicker.png b/docs/img/cowclicker.png new file mode 100644 index 0000000000000000000000000000000000000000..43e2fe9c1dace0ea52bce716fd4a904f9029c61f GIT binary patch literal 100388 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

9ncSYVlS}?Y6squhnMijTNZxtoWT0uiJsxgpdY^ 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%0Di+xV{7Kw{wC$M(!iMk8ujTs@$$q>9S^{pnrUuk zk(p(%^cIns(r^(?QyS0BU=D{g?1? zgi_CC%tf7XIxK4NS{6gM4I0rk*9_8tr6HWExN%5_q35MB3j!L<7##-CjX?b_4KQ0Y za5WY%gP~@QuF;83gUTv$sk_7=Lz$6bK9q*gi?D2=%@+lYi@p~W&ahY{-XbHzEZ5S- zg2sH8O4_+XwsNql(UIamray9}ZwiXF@ z?SMu<2^?0=;V;TEC|V?G87@%Up&_H-Yy;Pgz_1ms`(feC^AOmE+%NL9u*1SC>}VMk zh1%+4csJKCoXUg%kU($0Z9UKH1AmaFr5Bx!9a$Q|yQo+R*STdKLQq~uKuPn}s1dWQ-$X0Dz#&A(Y z#eSJ{4OP@^o_t-6d^s%7Ees71$z^2qqEL3RA{#2AGSqB^vTnl&vQJc{qmqt{&Yt{4 zQk7ilRju<2g8Ddxvs6mDw2M;i70dHzsmu1#M*q9DRv8fL^K`icR@PRnC0qlqy3Edt zGCwbtGSsd1lBKGuL{{YlPQ8;JI(QQRZ&D;H`EZHe(Ro6)frA&N#078+u;3nj(H3EgqvgqOCn30{mGKTcR? z1LQ{~b(j+-G^4;gn}sr3Fej&@Oo<#W&r-NEugGK@_k`f3#7rYP0-&H+!yH8f)<+ta zj1uVgDV(O^Mx=~F2X}`AT}zD!g`)&`x&37YUW#>SBj~|V(JR-u5j>uym}3Wp9izh0 z&<-B;yWrwu?zv%PN|?=YM;nc*);WcOIyr?zq!KEIGjphG4z-r3Sq9ZYUX+lTxLAt5 z$`Xo7XI!fcc8^g@$W-808jTm4xFY$+u?<;1M~$2;+ZeDVbD+H`4ANll97AT$h5Am) zJ^HQ}z(7=f)y3Hu6#)2xN}bG}+uUi%d@i)wQL-%DnCPJu&gSvlpM~}o<+Z}Vn}u90 z4b{(p@0j5%vo}j;X*i8TR851G5DxNEpU1732JYrPp*k-fz$S18k8N=s_>)wJQ?8+J zpIZDh4CtVt-erioiZ73bnrjR-SZb)5GZ{=LgAtz{L)EdN9z&iEdwN_K8#?UL zaMu)bFzkAPt9p~Q=eIQg03ZNKL_t)6>cs;U&em|+4sK?9y!Pzs*{IHBBZ8~wKYncqXlHTn}`T(`cRPK~a?-IQ(7JkZfz0xKdbY=LJDvdHY ztBVR{SueYT-3JfeBtzxhY&FfhD3*)pO_D8=+8&-qt5`Wt=W^pnEb5=+&AdJt*#^{h z;GT4xsD+lCJ^%qV)P<-6e_Kxf0=0BdmW<$?ujhG}$Tnc`W3HWc$z!@*I(@!=>5MSK zn=`r`JGMExG$Y##eHgoqXt{oK#o|q5GtI%k770bY2FIsnG6GHmS2Bv~5Frlvfa5gh zp*emdj-sYXDXXbHR6n-r_u8cL&Qq$$G ze0>zR`D-iqhJ>whQ7I&B8;+=T6c(cw2jgCSho;DF<@dHMP{8K)xY(^*1)Jzr@h9s4 zZdKH+chm<8Cr!OA2^;DktYcVot24TdLM|2VZCs$*TJ47%HG=@5WcYem$G7S=!(BD< z`VQ)@n*iM+puxK zJA9Vh=^I!ey^WTW%O@9OD{u%N1Xhd&qafN%>!F>g%VnGy7MzIV zzg800yth5?KBxY_xACxi`HO#Bda-KDzp57e~C*>zx;jH{)=Dy(-ySe zYrLnIU;Rk_H2^yO*PQfi`u9nH(l6(|({Iza=}#w}9CO$n^YkrvzfFHi>G;3!^jDDn z;xE1h@3-ksD4m?1VdBeIuf7HEx9LwdeGA?PlD5V&f9U4?yQFWy`!Lek|M9=<@U;6J z)8GA}+w<>|z6I|eYpU40v#)pb+28K$iuC09^MmKd|8v{+N0Pn;?;m60zfsF8j8D5Id%`e z1@ArSTk!rdrf#$U`EvF1vf8*K^E$a(2S4ZJSWJwaZiDxE z9M7KrteJkaNYd+L})z|q4PJi6s-GXTYMkfvE@a;Pg zxd&r+58RNbZs4}D|Mbq88^`d|jw{5zY}OzSJw4q((drIXb--CGhTyZm=IK9ggZKRW zd{Uby#PRj4+g_)m^Pl|ahd=z8IPge)esuK9b$YqMW}KY9*l2s>IW`-0@~0tR{(XYC zhT7M-@vTR+^}x2D;2I4aBAW=>v~#%c#Ep&P#^&~g-_fLdTi3YhsvTQx-?+}*YJ^TI z-~8;efBfH9Nw5YV?ryOSP*%};_?{>8HPXMH2fHWz{=f@&Uah>lE2{AZt7pxE&E4zG z>alI&a&yU#JgWgz%TyZ`z1&t>*~V9M{jAvr)V$*qRdnVXWOe=8S0{+guJ5qf0lzre zUr=g*HsLh$~_ zGkz1$l8vXd_1xE+#~ZtjkMBEj zH-&XoL$a4E>&lbkziy_#uiJ9xPOG+xjaLG^>-4?veIM1s>RDSPyg%F|%vJjH?p7Ms zadys+n&5p^Umsh$dQE55+}m$|soS6NyFP1v?90=$t0byo_OI)~*w!nk+Vmg4_x(HR z@#DvphT~IAd*A!+_kXxb8vw2ge*o#{gZsf-_X;Q2Vq{yycYIvk8hx@zJ3Eze2S0QkPgqkwfX?5o- zVknMk(7B3Gtx);u_;^jbPMW-hsN-ex@$xF`f(B$?RhKzwZi}N|oNUh=oV=)n1FJcx z|GM_{z=QbwRgw6n+4ny2{Q_rLdn zNI$4x`ynR2_Viug_dST% zeDQZcbPpwbXF&yC(YmUt4H{dildjIh_J|fAR|s6us+S_2ynL}e_{W=mwS~8Bu$;c) z39tO}c=fcJ=y`dvRRATcrzKj`e0-vkRmuIYZB^68W3A6w(?@Jteb+CBE6x%yA~u5i zdh~lBd|n}|*X5TMwVs{*<2%61(|zDwr5~)Z``f|$8Qjk4i?ilE!act@+nD6zukoaL z*XvKbQSz#J2!A1sx72t;tM~Y#!A>?nUO}i?Qqfsx7O!RX2I@K+1uM#X|2ME*aw4j1 zaFG=sx$)htN?zTmV6$`LKz+G=%&R-Sz^hl=CEKHXfw#?1rsgPCu-$;Rco}CED62M$ zp=I-8YtJ4pe-l^H(zRWFV8RHK5`Lp(b)8P9HcPrO!ip!Z>kc6S!iGGGTk!h>@6YvZ@ID9Ze)!qlHzQzJP2!#MxsjUV z+CcQ`37tSJt0(>PW%G)wHmoXY3{AG!LXNiT*s6~|y+hWZsOd4M zPA~5IQE7@9*B!^n*}eBx;h=8g$p&6d8qSXdPFF;e?|=F7)vG(;0}@$3a^O2&RtIk1 z43biW|MqAW=jX)p;naqAe;ZHmY{d+5O7YnTPcS`u_Tlq357yUx_WVch0&Ek^Fj#}* ze)Q)s?0;63owv{VVU=3K&z?Q|`Oklbz~yp;U^pD~et+OSuPJZu=ni;KP8;vP<_urp zi$czDn{Rrxf=)GeC_sLCR!vOu<4(`E$i&&{CNaf2@N0Ons%?FRv(1;+06OO{?v4bx zIBK)#y%p})MCs+mAgZRDcl!8~jj|ODJiD*#MSTfD;a+a?nVVGMWNXJ(eH^28<4(kA zqb9yvY<08J?d^N6QFB25_yNZV9~k{y9}GA>>ps5^vOoDr8|4C+j(+&VdcqZ>p^7Bq znO(Un%WZ`3bABkg_0)fkT=9o-9K^pHc)$N!z*`gEp8R|b-VZ;lrwNbw;G_AhP6Q!c zKbO;!&4;!9oVGyys%E`bGm+vYy;|WwkocO0GzUY1cyHkeNSC< zKSbz$SW({9yTOiU_5H6#Ec`uezJ(f>cW319M?v!%XRoS3cmr|wApONXFuNa8Vj|$I zp0VPmZ?O&Hm5SHADy!f8X+1>brE4UMFSvgDo0G9CCj0E3yR8~n$pemGt?=Ery^~iP zg(r7FS65eEl%ss(o?g|DXR}Jdy4B1U-{Ks~>FGZo^7bt;*!SDER^a;Xci%@fgb(g8zCwoUPEZv_@Cz&O z;)Gf)!@aJ#ii)X#eZ#?Q6TEPhpZ)A-kG}?ZKiHn;MewejH_PAspaL%kVDm-R%ma4Y z)r{5ZNv_FEQ*!<;>bOyG)=*ffs%@NnTBGu0&0d^}rZq`bb;2g{8aKcRrC`BNSCsdR z({wTIaiINR{OJK(M0#miy+Cm(*eMS{7W?K9z#e~sOmQD|r( zG?9IM7rcM-fB#J_V5`9U_}$=b5Z({sIdFPe>hv;UyV#bQO`CXU9D|)db*_q z@!46E7h4s-TG5B&J8y7Z^NZ87=JQ=mN4(fPxGG%}-PH?Rr7gbW`1rlX1;w?R5|cG^ zR8`BTsW*83<2$oYr>70Xo!sdp)|gv83o$3nF;_pLyWe61bf=r-S`cVaE5C#7=lr56 zuxozi$RK|vKEQuim%kId-(R7psYG+%)Y&urE_%09rHG(!gZF&^@1vS;;Mm>$o3Et2 zxcG^2FL?iv4+8`*hF?iE>1RLq!T(R)+q}k+rum`?S_n%1 z0AqP$O4W0PPo@@`oJA(tGwF-y#Efoc-lTFYEar+nNinwCGvMgJ(IHJ7bh?6o5ab}T zAq_^g=pNHzFoS6rI2(Ia>t6VRp}{NHLgz2gQiGu5Jip)bypfUmaD7boaBom$Wk$y5 z`~KeN{XL)W#gdoX+V2Y3or2doOa;|rY7X=>AS~nP)*xgJxH-Yb=U27!;SA3T1=R}+iuR{ zELLph&EgJ0sl^=wS4EZThZpq5D1;1qkQh1LNS^mJ1Cml6nKfnSwV$x(2qS*iyyDQ-N4uYYwTi zVlPsphq+Xmfg%Y*>@+sJ(6-SyV=hs#V-tt%h{Atnl$-+<2~HH|Cq%iaEV1jK$KZKH zSvXx>U>`I0i3>AzRCppcRGrY%ZychAsUivsZ!xQA(H>%fw{bMnx>Lg(%@h#{yV-cQ zU1)|fnp~9&%{ZKF!2|_%v-MJrC#YDM^>j=QblEXWpw|w95Ai?q+1<^h~#Rd-_(9 z4a;exhHj^xqEFy}I}OH|wk!TgAn&8I(Q1WeO{hI<)Bg#;t3dp$xXlt`h+fc!KYTCn z7C6jz0Pme2-}&_}0$FzPZ!%Ryl4+D*-V58b@gy^{BfeCs%*@fmbpZtDI)W#JreEZ| zw4$dW1;Z0^nud}`6zf2LU*5Sa#TUg4hW5{mo z7H2INEDdTAxE*JjbJ~t_n{+g?-7t}0L}~c7{K)aUjhLMod(N%}JBb1k?@s&Yu9 zg*1QZXbSZt7EF(9{*hl5(}BLPPL;cI!!*!vQyqKp{498{6u}Ex`<=m?Blya!@cnE` zgP&geVc)t7_lf?XI+5=PL(Rr(< znWy4uQm4T^hjuR0Tr?^~P3q*`%oJR-NGoT_N09DzrZ!x#Bpnx@2`@N13`|$b5$&oB zL^Ip$ETn#~aAqfkrm&Gn#cenZ#G6tCj_>?Y*xUuhBAKeq_Wa3IRW3e)zx#pUz2Pinzgt59 z@z-kC_ zFHuc$&iki%V13ZO@!Fqa*o2WJAn7>+Iv|=+qw~x@jnF3$kDDo#e_= z;W=Y>$0XTR>A+^ePutr-nB7zkA-$GKFg7=l3~Z(FW)=&lR4f+}yZYGiROV_J1i6;-@p)1y}k(;CFMGmcr;Tm$8h4%B)ktYCNg1KUdQ zn5%|)Tfwg7RN(1rM2hOb-VmcawAgjs!l@s_zJ_i(oN|}Eg|??2ivK+D7LPas-lgvX z-kUF+*O}n`aSmR{A+`_4XcXic(+qBuZ<)u*WvFS_O_3BBmncl#+Tq37Gc4GLojZ@1 z$lY8{$>xvSyBFx#?)+VbcXQOucrX+W*-nw#%0s?qE*e4|OsSKj(`OCHayf?3Cc;rdg|&+` z(YsBvv)C8>BydN2!G zcl%v@w@d2R+i?&JQ!ct4k6b4w2ltxfO2G$f|GHqV>*Uc2u8N$hA9hhzY#I@QgK)BaW;ltqxJ3le5gMk-OA zmKrkLm#|6H`kI@H^I;Qfu9Hq0BR_9KZ7OnDdj?uEaK~*eQec*5V=$_qK=#_;2pzdX zXQj$00zU!GS;#}m&V%>LweJSrOQrC41n-p#zbe z!>Rcj4A&4u8G~_)7E1D{Qx@4MND3xMM0pBVUcSPa(X8=u4~Gj|Gc04OO`Hw~`fRC; zZc4V<)A3`nJSX-{WERZ2xm3+RmJ*{YkyDcBTr>ZIS`Tnr?N~olSQ|HV)Khb(Lqjv& zSV^iA_;iq2;89`pa0D^fLyq%9MjgaXBjeB>8Pl+f;AG#mZ*0-OxCB}#wl@!KJ_^w@ z`5c0MEEpN!Io!`FC5pZd+%d7t0eC!L%ZFVI-fJ^k5p6#JypQ+qTn66HE)cw*O)v8H z%`A8~i>yHuwzqGO=S!v1&{|MlT|SK1#}FT)n{w=fCWSQr>ii!%*-J?fDqo8NrHxKy z)9KYFWanU_BJ}VuM=vn=bRKaC5OEx+BkDS zT-}Czf{*Pgb|Zac)6saO{FGA~985Uo95Xa*F2U^G18O4umK1>w4N$f~AHg=lyZL}~8P6ys; zaKoPhypK^s=hY7d@0&B=-7A9k=C40|_;7Mnz`J2x7eF9U_Q2Ic3yjG>CIRTY9@xN! zoC+8rh6B;;miwr3ufWOZyN`1^xbeNWe*e21A_onJ#XYilf!TR;8c`&!iTR~-R626# zWPW7qGH_fWuY%yrGrlwE@tQ$yqM7=Eql7${f-|=t>fLRyrKS%kw(XAC2wnF~usj?7 z{Ly$k77k52nKQIdqTo)68)(#}Ba<(B10UEH#0TFUyw?iOjBf+4?I3vfpFe*^x5Axk zv-kRsg7?+$V1sz`;luH!V7D=+B5K7cTzUWe#i%i0vC>@ z!Tdh^f}KuO^{{nh!{`1-w+%hK%|GrR-L2Q*2fY;jD%AB?4}m#GZQcA%K)GJe1#;j= zTR2_-03ZNKL_t)9m07a#LP@!#G~v&koC$KgnXHR)+$VA3a3AF)2$w-MH6Bm90-qJzidep(VViM60c{}eQB##bfqw?PH!laT z7QOq<;KjD^TsL**-kH7Le+<0OpYPp3QZd0h0C+8ER9o3*JE+L!tn{7Cgb4NN@Ue;1 z2XP2}>-(6t%^cv`T|CkNzCDrwkpfuN4BOoeU=EwZ?<|ft454~-wI_e?%9qn>euV6) z634~CfbCKC8mRV@l6qQE5-PS?J(;OHwOd3sO|QzSgMxmA6ZukrnIrs8r0XvbqT4nS z5}l9o7`i+Y5@-~yA}Z8(Q!b6hOV#k0o9&$SEpeueB+b2Lq0VFhPxbvIFd-2H>g|9O z8tEisbb+HarhhV#CtjFElhtMXQ-OE?BjBB$@_oVk>cxwH{_zdXho6F1S|=pKa*B_& zbD%90P$ocLQ6>fE0iZUMve{G>%00)>Ou%V*nQf{;k;^qmM*`kwFORUjd^QEE!>^O8 z8~HCicl1v6RB+ondOtm@dYVmipW4l^h1s%${xotfylnVhoM14q&3(vE$G=TAanvXJQ7sZxKz1W2btJyRN8%JFLs)ww z>bQ76{w%D+wSeI~DAI-;yypTP{@mc@8K@ckZQy;5;NTm~_B;pPMyexyGM-Gv_~7Av z!P{~*VpO12QEs(m2m3SVh-f=VMNYUv+4+DldvDkQcy(@nB>!Zmr@&d%aV4mT-@|vPV8)ySuT`>11-Mg17HOVI%Dc;a_pa zqp9_u)MsXo8D*2m8F4M}&;ipov`>`;T2O_Vdfsjx!h|dOW+_qgh8qDR*Np1>n#h)` z9HSzY4h-GqJTs1x+bGRF9<%O0ZD1qagLm29cFbcm3cSE^KT>55yonr}x057-C zz6HE*ZhQp1V7h3eByV6UUuN3fb;d=ub~=ftaw3dHHE`O1uex+#s*4&t9S6xa_3IbNaW|z80O(GZ0wwoiELYYt{6E;sDr)svBn_760 zU3W%6?Up}@+LcmT)0~YDCemXyHfYj?nsIzzZ4ji?#2iB$WiX8FciMtr=LP2r6FBf_ zkGCn&Odhy6?PluWi_|ME5Agq0@a9g6g%$YM_TRoe{%}n2GS7a~RZHihD=Twz*JEFi zhcH#u(EQxO&2&?<;cz+&Lg=zoUDWuArj0Dg$47ISO_Z*l+@lHmnsu*$_kCvAy?dJ7 zmg&Bqy$2>m%nEpW+41da^;6*hMjMW^o@2Oz(z|k?z!}6s4wUoXXF@ZMv)iBEzI|Kw zRBsD@UxP>w{SqlBY_~4n2IIbz-apvKCNGj+f1}n*| z6esaCxH(Qu8bmM|1TKV#*w#nD&!d=?^Z;~Pe=+rIT(vy=cr>Ok_dGKzSj0^juz%#T z8tpMs|BIj$rXdYi0#Q+!&5mJoI-Mg?QdlSA9C**=ReU>m7cK?wi|+;ApX6TWxwmfy zo13L$R)hd#H*v*HCwTXdk3S6NKKtyiG-Qls6lx2wB|*)62U7B`t^I#hK?7M%=lq2npR$-VAVM$aU&7>lr6J^k(j9E4;fOT zmQgl4v*|S6P3I&|L(Y#Q%oL}BK2g782+HAYZwq}>iE8s*Km!EaGMMes z#OZooh)6NKQZ#1FP2%|Y7)COi2aRq(F!K=*K!r&J_KSG9FaxK{!E2U?kAruii&UWV z;Dw#{`D|H}ZwId(bPFDcOTqi=y}`x*@3{ihwZJOI2f zfmM5V&wy9XKNS&sy9e;&V54}NSQY4guCr>%t*^)SVmUce7%uqQvH7#d&O0V4b2P zfa)OK$)pvW4DkjLjS6r{C`Fk};Do@#@&E{S(7Y70i}W6DL0Rn&&#sMj7Ov;MeLHyn z{_h7ZVt4QP^ZotjX9IKoEa1JvIVNU8CB-F}kuz!{NWe^vjEB_DrJ_P~LgG3TuUbQM z69wm8Mg!|KO5P1if%`pKi}d)f2ki5ylvImL$^<-BGp(_9UzVxfaDNo?-k z)dA*fP_dZ@ZN5-KVMYa=kEwH|LZ=_MPcLiKKDMjqlF#Zr)GEeZq@d{!`8cP|ByCR?TTjSjY{-=O?_8|GHaYL zOr!;tNn@H?ZdnK|bwOGLW2I28Ii7oOCvmGh2T8hM-G|oxN(h0?5$4M=W+V|i z9^8syY@xaoDIy`#$W!SMOasAPHV&0LayGR$sGl?CJSo$g2*iHxB{n+LUjO#x%iqGD zd;jHc<>Sjo{g?Rja%bvd>%IPs?C3$sd-giAk}0jm5X$;VKl?I5zA*6KybM==M)01y*|&k0_OZ=#QWxOeRvsJr zo=%Ns7FVwV`0dE^gzc;DoM2$1iM8GsyFHjX=0d8!iO#~bxj_{Bc1cWi@hHb+?gZ{> zWJ0f}mR-i$FjApk+=MH4+OidX>Bx>w{X$YCnVJ)iC3sa&JdAvX5f9-E@v5a0h1j4kp71Wv#>oQCQ8Ljg+rie6mW3x z+Gu0@39iY21ILbxRe6i!@tAs~Gt!Zb>oftfr`C~hJknrO3VM-3K)3pgM6O42Mml=v zzxZN3rLr?K*vl*s2c9}=XDILIQXqlqW7ql~aDG*BI+JqJ{ z_BcIra?oh(YBmI>jo@Mto$7J8(JzKL_WLdhY$n8Ucs3Z)ePYyOzu&(!OtJv>jSYrG zI{%8sm+iVxjL_g5>q2gt!JnrA!~def$`2!p7)ujCTSEpCW~s>!N~fYxqANcbVO0Y% z!yXcK`iy9!kt=&1V`pOCIfVf%&Sx+1Uui$}W~f~llU1-S?I!|blsa&8qHvE&dV@#D zlSuogy9q6vI&na+Gg%+TlLOTvhg0z0JX7xJ-1hC@z4n8_%S+t20K8Wmc8`x=eFVJd z)X5~Iq=cQo6ZXppT^d&8^#j=XL$n^6kcpFlXLIbXbyQ&XTr?MCz&1v}Zsa;PiG$Ew zl%pA>O#%082Y4I)=8GwyHwxjo&wg_OM$e3apZ!#}FZ8>7+Sur9`K8gwc~G3@rZLN$ z`!%MibWHdQ}71;zWrwVNl7-5n} z7Nk%FX-ztnX>nJa1PdBy2kj!2re*9a|yKk^|w+)UgL=FuW0-qjPaAG6Q$^ zA`$r@&mz}Re}WMwB7J>EOO0Td5IH}zf=|wd`!V!>1iTu-yU_ak%fWljRCWP;7yipn zG>3Q!-Z=&D@!S3V%dz{}orB2;Y8dF5NLZj1`4sd;jYdYnMsNc~yVO6RAA>wN|1WL$ zp~aRd9+)Eo<68=Ej|WocTM?M?WQdKx&EY2>?`H7v_Mc_XW`8nd-aXCk=11qTlKmN> z{C|Fh?QcKl&+FI$Izf`VGTp+vSLAo=-4x#Ygb(1m+1GFOysBrRo?I zZHsO?79W!dPNeHNRim%3F?&L?X-Gk{Pht$1hz7O^%oNbT5nm03nX)(xhYRPxds%Ax zwF|1J=aANTo|VU!gLm+)*uAFJ&KSUu?^Ez@Zn_xAbKA|El)TbHaT20^63#PWl#B(? z8m2E@tCBUYG4Q{SVUKUh(-($nV$OIb_{+f*GbOK^PQeZ zSOu>LSOf12S1|AzZ5U3e!Hnqs*;hXoar?7h{9HNX(6(3XFEMB6gx7O85xNIh}^k|UaISuiZx zqS|1g`P-@nnkSxi;E;@fb3tAqgBd9!i`_XuT2*R1MyaqIv8&z=Fj1MWw4?OVPp+4D$EQrm&IwZ$`{(a4fw znFiGB_4&qMe)ZK~ z@&bNT^7CcMEK+cLoAU+ocw%b*E@cU$mC@)hX!}sum4}9@s~~};#Upie=jQA&bSM?! zv1#)z&?!*2fmvutNLlK_R$S9yMQKSDhKkq;)D5ntkWfGb&?`lZr}C)4=N5DfxmAc{ z4G0>U5j?08xZs!{eD$xvC`SpM;_~Pp{PoA`#pqmdl5-Zk=OO$N@GhL5E)>DLc?P`j zwEZyf-kiG}x@&jdvMRb@ScCPzxGW`x%7xiKClh&T>S?5NpaU{=MO_$C2?6Os2aLzD zvPqa6MYx&h86hi^R^n~;9i>euKs{o_ST8$q(O`FnFyD&!)dY*X7FE_i%KAIpvq5hJ zGARmNx9_-bLH-JEH-7%r#^<^R3L6U~(OX6WARDDD!@@_Qsg{UU2l><~I|4UMjS0cd zl!jbU&hUzlBl~?!L#qq?Q9H&6A`&DbrK=L659$L8^pSU@>Ma?op8{k=6=d*c9Y?sA z$bCOaf?aKVFj)$d0E+WCNOnE5fdSz#7X#L*E&(GGrmz)`mDH3O25wByBbZ#{{$=1j zCxJf(-jkCV@IHMECxrv=9(}~!A0~M(k+?T*Jl_Aq+cyg;cyG{5#wnFdKo}gd-KjLg zbd+w(#74)G2w)i0d^Pm*aO$&BI?|8_%_|U^5vrX$Qcn;sIuTa39nl0Q7<->l{AS1U z?=iC^{^|4Vw7PNk_&7U0?mc>qeaA0|k2Qge18q0f!is8YqLnp3Zz~3w2@=AfG8$=ImPK}W z8gx&==_4tK7Th>x6)_E-NL;FO?3``>$PC3=AsV_bl(7URbDVi_Frj74*8pB6E)eQ2 zP#BbXBqYHCeEl($Gf%@j$vML0inXN8juc0!q$*vdz-hTw(C)6fLRjMV@R{6hX@6Vhe#)Z zEl-2pWq=$U93+w@_>jUYZ$T zOE^3jLwDO!AIxZL%R*qK0|n1xhz&X^M1z%GY2+;@r~hV0QJ+56~G^)wrP!SFN1tRgtq-RPy* z_{CRW{O#Y$ho67-v%me@zx|u9ez6gxB?M++!1)ZQRdX$*G zAl>-|)U+?Y5Xt*DAjBej1-v1X@zB}ox`pV`jJqgS3<$P0o|_5dobzSouso7PwH?Nk zB`lQBqeK_ggiaXFCY0qu0!-`8iLsnS6f*}kMDrw8meiusVWx;6ez&%yDdFDh_nSutl(P3I?~R55XvkhuUU{kt+hsjIWf2fr!OGV>Ps>G*rhH zm&2T3Qf5GT069z38$qoR6V51(6;lJ4cR{j{8R)ClBs0-C(t2irYOlmF43ik+K2@$% z1of7~Ka{`mWaw#3o&XcpVnO_HJ2XKebT=3wD6;7q84@Mz9^CDNIXJYn)hY)q4Wvcj& z4-nFys_`8XN911|D47&o{pS7qqwM&#NZ!|)HX)K%{uO2Hk^JfH=+5`A@493+1utpC ztmN&pY6I;KKmF<#KbP&RuRj0j&uv4+9L(8DM3n$2bBHW1$Ec55_ma>RYGe3}r>pkL zAt9{48W_V3S#ZG+W0W+(e8NhidDT!&ZF({4cLFEg(cC24k#DxwNd4-NvGIsDuJk}u z1STcZFd%XjXO5XCCwPQiVlp`(1r{idX+ZPz&0>+>I>8(s9$Hj}#^R`hIvfR>eB^bm z<;5T!cv~=YZO&4LYg0B+gxbPWn;TE{&;Fkgyf=RR`?qJdwVPK!S8fbxR*X4kB7iIy zANpt@<$mnP1TJGM^%ThWZ>Z9{O+l`Z@KBi;TGYRP=QtY3cQN4Z!wg@7{L+Lt1D#o;DCBO#i;O7 zZ!6TWF{og>26ZD;2QVrpo}W{zSoO3}!`!J!jC2G@YlVl0TBsfQ>qMzsW9u*-S}Gxq zo`_Zw%U>NMFgU~cHik7LL|hO!x?EP8K9Bbq%bdwaxM#^}XTZBD5bQ22d>eR;2@B_) z$F7gjhVNtNo$kuPd-LX%bJ%@*W$o=-f|s4fB#A)VAqo%2B(;L)U4cfdc~Vg%Jl|L6 z(AL%#lof76%`^dZ_N;CTwFk|{@KxK8nH91lO(%Y!!21H(65PHJ^nKAc?}MWPh)}qH ze`DyN*_Wg3PrpBvxJ3eiOMUT$osLb;^!*Y>VXDHEw(2;a-K*_&`p5;MiKFjgohy%F94q&B`X$-<4asr-VcF+?my^;H7x+ z)lWp*9@ zc%F++0AxkBZa67Abi$N2g`PRoHgd@Jb6IN!hhhGedvZa{i*GTLB9y)yC9H{*KwZ&T zL%xm1xgxRafcDpIbibzoi{@yt06EY0dN~fIec)prZfUY=;L*is9D>>Kfoi`p>NNt) zv|*X9JmFk9C^e);D41^p z@(Uo_ z@J9BkA%9~l}KHa}sGVop*p)>)~5m9_jhVwW|qY(|95Lb9jnK&ntUEfom4_r_% zAtEdV23&yxb5}*1QyZjdBCOj0`@R`<(0EBY8SSPU7RXBvnZUB$={O16>nRwA8~Q8$ zLx8RWqYKE%v1{d_Hj3W1zHwlD_dHS9Le3nT^hMPQEFnoq#Un(IX~<%z^>Lw(p{iow zGKb_HHDM^Fv0D_Pq58ux>sv;h?Uqyyt~#jvUJIoCZV9?SAONPd!8w+tAnW zRgGQ_f-Q6wWqEq=S2N&+Uw8(*o13lH(na8P8r=op9n5lvSIi#H!Fwry&w>}*jZ470 zzmIqw1MdQ6eHvsB567A+pgAXg5{EMGMN$c2lCJk@pcjIOx=4vh+A(;o?Tg(@px z3L|d_3lvP~TId0)#@5y_7>!fuxS_}d6|q~=j4)gdDWnkwF_d{*-I>0|V`4W$dT7w3 z43XpI0)GQ$JV%fuEYjv0lF0XRb5%@}ElW@e=W1`<@6 z0Vw21UE!m#kq?jnj#JMCG5}(>-jtdLk;Fh7)ld(BhM3ZbLkn`s(rI8JA}S9LwQ>k1 zgyAsswjO%QdqbxNg10hoP4@E<E;Z2M3OC$aPr4M{R2W`*r}^$wbX001BW zNklwRz;+nQBZSU=Os}-y zM%k+}&9N#;q?fRjlpKu=G%YP6u?eA8!fYUVoT5-52~O|)h@o)BE%R28_8>oAT1v2{ zE{ka5d%p9Xq0q-fMuwJPkWnh*HI_+RP0Wz^@(u!J_0S>4OrHivLlK~zIFJM6@_~+r z5yNm$pg~*Ga+q+ehWQ9x&SQT-ibSA1)KpiH4PWzz?G2b*;7tw>w>7X27hk?MyDWu* zcTfcH`kgZ+8*=d4_5;9s0e~+B@69vdMSkZjctx}DtQiuKGZ6*JLoDSZoj=4RHPLcy z4=G;`5}~ou#>!-)5%aX=`zT7FC#0i^y4HXRxwg7~SQd&RbIaE_mN7~dX!KeTJ~XGN zI_x4isXfcz(xP)nLmy9Q&-G z4vJt`sJPj2thbKn?SRZ0PY83?j8L6?m;SRLcbCPJlIld})b%5ceDrl4TDWIC$AAu) zyE`^1%~WS(LeLk0px*JsQiYRUw+K8&pSoOuPz{Ypm=peA{wouE9&@;TABPz3Me9K3qOMc}=5&5UZ_AG}w-7kEEB-Dg()6ufT-ki4S< z%_vbjzBfkwT{6IkJ%z%jl)WUzL9WB-a$ZmuAVYnv&gn-q)u;zartp}C^gtlN)`NvT zQdb;!F{;bU6`>Ia5Ee4`K~WZ>H>6HTRnJeY%m+eKSsmCpS_1}!kzHuzbtZVbns*j} z;HM>w01Fl)VnXAIA$1tgj=*Z-tF{&}hBj5&LiFkusMyjR-~*%B@+=ejC&9seb;4jY z``)(5w~42(Fm|>WU>m{f2T4G~vz#Un7410K8Zw5Qa2EChOYe3JvpLCw7P`a(jYpW@ zfL(#DAo)P0OBB<*z)T65Lh?{#XsXPH_i1I}T7u}hC>eblcsGmSo!JWDwNw9`;9Yq8 z_Ucl}pgWbkgU>#@@dbgDjPh{|yAHaRmsHjpcqw-pED5%b95a78fRc%s&&NLA|iK&2v+w?roC6Ik%9PV3UA z6ZGO16S)+U2xRlAziEY(padQcf$c~MQoQh7p}q>OQX=40FeBJoKxeto&_vM!)X26( z=?Rp@Oz2RUglZkOOm-FvfUSH&eIbND0G4G_+{}9{?;|f9TS`41ZETTe{)Xyza-UQN z!*n6tfGNOZ^O6MGB}#GNCm9I|fCX<0UN#J+v2bgp`5_6iJX*SC+)BpEmh`}Qlzde3 zBChdI1m55M2f=&g*YlbWbP;${jm6U>?a^4plqod=$RXQ=A%N03ruxv~N;FyZ4p9)` zcp}+!o;hF7jI_U_F*Lq(i?RQlxQA*NR=y25dYpF6nt%W{pS|oW7U;5LT85_rjt^ui zl#zC|Gb^~c1g1P^$%;Cg9yAu}1Fq;abuSVg%K@mWqcR{SvMF^B7{U`$x#t1fWRWPZ zk_@+o3dtY}$H?DQKWzwP!#cJdYV@w4T_7D=W;`90g5WXzJb~aSp)L2fRS_KynLU#F9|!N#rx$^D znsGb}-WAILIt$(x&-ZWKyfnS&Tfuwd=FL0%D}ZbFBJh6JH~fWc6-+_|lJpD}U$wF; zUO|Z&v5;a!GaX8YzX(y8|0Ws^3vx#l-iI$1gCYT$pcxGvA^!(jNJD1E0J?*w>mtrt zWGLfZMEo5bj+t2IvG{>V2?GwAeosS zBbm2SnKPMkoFw!rqZc@pO(3t8A&a;er;#^QJLDPg3K|y%>$Bh;+_^c6UIo^L;Du#) za~8Z$_wL-d@!8Ew;CXKBoCmLPg>Sx@1#hc#b!A}Sy|=BCw8lNb@)9Mdat!iYG!levCUZ$0q(PWVIwTz=6bo@+5tKoqi<)RIfiCHS z+4eBuY9Y|lIR%eEK!l(~Tmc!m=~PPK1{|sVVnlN)X(BUTaN80@HDE7u@<^SzX0T%A zFdA7>Sn@fpXpk@|hd3^b(z9Zlr#dL%R@wQIMupUrH2pO=#*ct&2j0a<_ zFktm#FlzxzDQk(KcctB7ao51Z5Q*Dc7!LuuhdLf+SSa|2*8dzjd}{4tYJp=66CPcbO6?@i~u5BB2hh0k?w# z3z?xgSYRxwK4$SqeQ=Tb1Q{yBQVX2TiB!5aSkHoubtvK`^_0l$G}d4- z<;e0PN;I?@oBS^H;m|`p4t3;#Xa(_^<Mj059NkKE(wCIs81-1fDDn;Ra{WxFnV@ z5THOu*5$z9U+~g{G}f#ckWJWhKyin(mLr`)R0VIWD=JZB>soCrtE9=E6SXuMu|u3J zE>80)i8iF395No!UJouTnmPjiCZb&8(bAD>SJJTp3OsL!qOB#X!prot3FGPyVwWc_ zqAP?Sam(x$ySY;fY{K+K*!(%H6HtYNAg9ZoEfSm0D!OSNBqtRa2slM9E-faU5?=TL{fA8i?mPC2R|B3o}^psI4)yFrC^pHRB*LGnBgJp-PZ# zd$e=H(00c2hP2|00LF1-I?>S(h90EQ!Y{2Bb`mqL3(RKE9g%xHu!&W26{ zj}($q4ZFfBwFHWXD7jL>3s zQRpc&%77{lh^F5{q&*Fh8ZS4p6^TZZgIQ2c!E4#S1n=dN*8(Xo0`I1Q_p@`7_xx2Z z0qUD8)0zUk5TS)x8xOBljR0U<_7F|iO}f+tDEevAMWHP=EiZQ+N&L>yiD6J>9n zr>^TnSBG^6gb|n5zOnBX8VKY8Ha7T3!`aP(((s2rYRsBUNE>;un;xg9W8R(? zq#9aI9rtb^1VQfla7%y7QerhMcyLT@y=tA{K?IMvRXQ zBd+X79q5279>bdi*{@k~C{ZMnKJ{X?waOXHl~rw39!QAQS$fS1_T7?9Br zmPDd0E>>!?EtV^tPN!L`HENA}<#Mf7sn=`$Mx%ybE9J8MSFW`i7 z%Xp?H9>`v!)@U^Am5SUQNAg~cW)r_Q7eBdveNhgVBigm{Txl-f02%pHk`GsJ2}+2{ zs|qFAnI9)entqv^TarUcrK<$i)vLF3GXw&ebNQ)-dwu)H^Ov9iE-{$ls@rh+!RF)5 zWqIokD}v(1rL`+;%t}pqaJmb7n>F;L^h~{p!`Xd}+E6XHE)xuLY&+IM$w^GI!DunA zzPlxqlzCYRodbc|)VTOFybdr&FlBq9W~P9ax5MCIV4U51luPWAk@iTH?dMI!Bx(VqzAFJ^^N33EADGk5Q0LfvdK#~8x^3h zQeHgD8s)MzQmq2$$nIMCSkpchE4*T}SyuQq8e9Tq5uXTN0Tu6B=2BfRaeh;d?`S9B z39zTrLz$JuCYI}HENYs~i43UhxVq8b=-@@#IHS|h>;5WxP(eR-><#3{cCB8+)8n#F zwB$D4qut@UVJD0F5}nGgvXwa}>jY%-&tn|$_Uh8g(i?o**PpVtIRxLz#Y*#rblJMR z;?)(|uAXG`=fEqc>Md?%`}w`5=T9#*yHO%9fr)@M$VhV7#jsj`%>Jm<@J{_+UpfQ* zV*QC8Xx8OVSzi+!iuS6Z0pzdrz5Huzwm_LD2d2SCmR~+rdpp^sY4aLt5Icp)T>!rb z4a8R(>A~uv!-;8*<{t=?Xb5Zm$PDlZD$+a;%4=A6>CD3UjDnYeQi`L0IL$l>YuGLfPJ7>)4AmyE;9k5!@CkVP9Z2b>ZfL+p`(EdWiTbCD^=5~<yBHSyke`hw6xMX$*wNVg7?my z?1Npon?0YOI|ts&wjy{r4HUrJy5D4jQQ)*u*clDCp@If4rb3~?C~)f!)N#gG=lmyI zFE~(j9JU7p=WGol*4DJBu?ZuuMZ-v`jFE`Z0MDRdP3vMbp(ftqN7x6{zd58{Y|B!j zZnA~^`nqg7$?Y~ws3L1aHEt=14a0{+^<%5GIWGCWfVZ{Q6tVMlx7NTcdR=-s`=)!Z z-|xw{Pcd5Sf~3VVCgF0msq-<=+^kmn?V2LEE#Pf+F&UQy=5;ikV5#>c+gT(TTVHGN zgm$CW6iN9ht|pWA`r4w*UDYZ`#bS26QmbU|K9S!3CKLX^@zN0X(r5@*z!;KaPNcuq z*FHJTR@N4?Q%uQ^vtJ2#TlIFifq{m;LEl{mAt+z(u2-8XdB7Ne-)fmGXt7z&PCpSA zyPxeyccY5SYHMjzgud{ePFrN=(?O{KsX39VORbeR*`SrZnQyhO9%onQ=T1b}t{Yaf zy;644z3RYQ$}%}Y9&dkXX-_WKYR!O`r*^Li4&}h?&EOL7UV!5qyqn#nS@7Ok7NJq+ zq=Sh@DP4urS|8m8@=6eWm*qd{0Y(JP`^y z`SG*r%a^#Bqaqw+dA0uGID55*8MH08dODaZf_MKhMCYq)bCA7hwKk7s{Iss_ z?MYh^U~k{<4Q^#8t*Z`krPEWvu3YhXYiaQO)WCZ+zux0L1@FS#-mkMa>z9G|ych;} zuMFU{>)x6H@A9%vl0a{*(tuDE*~@(*NsRS3kYl4WWWx<~0k=wP$lzK9k*WcFpQr(b zYWA?!Xn2dybVixKQKpa1;qfHUpl#rA6w@IT@9sv4)|^gBFFfxd%(D^FEdpJ_Wy@C! zK`KXXg-ptV+*aHE$YL>U%~=?n;G=~@jSl4?iYBE|`i}r_qj_JNZ}9;+TB$4(c(+zr zWOIgJ>@w-nf!+deAE8txQ zQKZztPZZ?uJov{vzqzgw5v%i#PK z!8_Hc=I5@ii$=DeJ>C>Kd37L%cDuK3%^7$hXIHu+dFQp{eo4TawN@ZiUkpTFJCTWL zW#!iV6qAtU1nN1s7S`AO2gQl z0=kSd_`48{N_oTbE0AEp9Ie32j$i@ETt(^6aH!Qjk}-ic8FO6)C|@WAHJ zd;=uTI0d{}_R}#$Td|x{2wI64EHD}6K7v4tatU?DbfB5DL1{9fVa*?t2hzot`|2~;kO^Sv}>a)oSkBgtoEWR10aOt@_;q+$|B zOu@~6He&s zWfH0aylVor)xp{zdskD1OVqT^U`=*k|Cj86pndJ+SWZ1TYObxFzONFzC&%TrH34rK z2<$3&K}DX3wo_SMJ349JYd?9ixA!zV{gG&9C#`{8M!?$|tYxS2Pj{_51+S=S?*QJH zb$MWdSL7tXyY?Zg)kO$3YXI+RuMO~)>rakOPCiuDgshiOv&U~wR|(#Vyint%(uU&` zZl{m0&Ry+(I5~MW*OF&^dUAZV0N(jIu!+6LG6+}R>Io;WO1$FA<05!lr*=6Q+RlPk z&AM}Q7h!jPDuHQLR`9kQcyH1AMfoYs4mGT8=hWWEeX{~mp z+Lp}LF8(n}G`1Qg+`vj9OcMq%2As)J-&v&Q#KjY6S~e18Com~kz%NpTeBW_j_(G7x z9a+5rjDpb9oJNm@4UoPzfnvy%dMrPw%G}maYhhzJl)T70&6d8-?zaJ6+xENQy!Gt& z0;A^Q_4U2%`8p-m%GcTEjQSrb$Q{{ez~4~yx&RoQ`$i1Rcn2{^f+57bz9wAXsD}K2ggOp`^MS>v_Vf8K+3+Fmxn2U zcjeZKx+*ria~FYk;jF1){@g~vtKc0Nc7^LUo$-q%vSz(WqY%Eg)xLutRK%(^qEa`* zOv9LiAiGIQ(QXV!k%s1mA{UX|HQGkKakmOFqDu}AMrrPqLqvJROj`O;;kARM7_}i% zW*UPzBQ#~=!fmm@147zjW+!>kcziI1>w2X1&?d-PWQp4p4v;7ujSmqwfiRZg!=WFH zWpI>6W0Aba@17hVQ}XWZ%l3TJ!21@0?Dfg&mjd2i2jG1p^5xlM1@At$r|ZjQdI5^y zZR6BVMVjcr$rEW&J@xURy-_|sx-JrHP?uJ!msRqfoE)F*e7U}=CIfKZcJ=j%NZuFg zSaGYn>R{1fBbgPZO!kUwpOwa z``y;98Ogh{iPqSo!e%GT@rp|`l6Pfg$*ETtN?zwtyBxf8=fJysZ=+Y6ZUWrg=t+MK zRzHJ~ZQG_(B{ns7!%|NQp_Vk!*#aT3Nvt)!VHG%-(B&Ihl|-6a5ut-}vq;fll++d@ zLjnYkq?7T1mb(MduWKtx=t(L?^%&=&EAwmi@#2pwvfI28j8rFP*2UYFNtTY6#FR>>Eg48ShWBXRrY)?z`3Mr{uidwI-%7Y!Ah0EB-n*;@@Irs+H(zG= z%jCQUUR=>kWg>Z@n}7=Q)OJ};9Sjz2VsPN?^qT@+5x{EWrLIS&{mELlxdS!v2m-m= zZ9fw5s;A;v_P9GUg=Zg1t*$6+0^T>RZVQ~ZB@mUn(!4+quvr^a$IEa<(d=FfZrz%f zCTJ}|^cHk7X=V|m^68pz7&C#{zD8_g@mJZSURkXT z*VntB5DSmW>p~j_jS4`sY!%av7Hf@$x{t4CC)K9zzP`HNt~cv4Qz?udEV{XWaj|<0 z@HTSr(&xWE=n3t7mOUw7R}b5wOyDopRhekto}mr*MIo*MfeK#qRkJJztf&zp6Y$EQ zYqgf9w4sBsHCW@k2}|$m>|Pmao^5;UGTIi)X1Zw2Xk8?s3SOtW4N4qxd-H>Rx6gdU zr6PFe=9YLxp%Lz|%_(>{^HX2hhts(tc(1h<=I1X$?<{ytU3Oq?JCW=50hrEhQ8V<` z%o0(CXfSzQF*9(bE`O1F$r@O0T~t&x_7Q=2pg9?yDpett7s5gbU;__f7vsVZp$6Sd zP|+Fye28#H1lt1O)ckCvYDm*44x?06Kh$*QHlZs*W3iAF#L67d?=)!GFkMOl@BOhT zTJ`$zi}m`WV{T7Yn>)u3?lqs`%l_)xTJyDhIC-$T_AkeqgJ$j1qj#-NPmg`I+5mMz zQ#6m?t=1ddD~eX9Elf*5_Qp42$=;Z4hzpNY`byuNc)a&gXxkq_N zpJ}l9>{riz{o|g%13%Uj(Hj*HdH5=L7X-WOgQYqh^cB*81|0SkC2?)c|FnU@d#Ey4|HAUy zX>-U|a7N#Fa?@Oyh#(n8khn1ev~8_MhFzN@Ei{KMSEd-T;y}WR3~``#F^hfhxSEjp ziBaMR8I;2zU@WIH(}O8Hs2R-as$r6j#}v9_Duzf~@U%?eq4_W1w_s?e@0HR~+Wd5V z4G}M^!Z53Auqv&uH=5M}bTld|J&;Q7mOUcXn$2o$eYI1qL1cE<>y28u4tJVR(e>5Z z;xf3z8eBFs!!)bBND~0>Ld2G@i!2BGEZ3^l?jHoa6vK6R(#nJNp4=5zXf(@f>um~y zcBcxXo!o0xcC4>9Y4loLT`xD*S1Sy)==I7neJqw$x3UtWJ0kDpJ*qMdwCjz<^)93m zS#+~1Pb7CJlljQwmF4Yw)zvQU(GU((uPP}TtWUu^H*fm}LRj&4Q`o?Yktd6Tn48}$ zfcMt?5{^UAmpipU8{DZ&bB=-CDuDMI+|V+8+&YV2Wf!f@_4U=&ra2zeuS*D4rg04X zwXRP5PTTO=<`11-r)^;)_@Pcmavh!wTnv*q$|bRuo7M>3#(EDUt!BWbLsBv<~glA%WcrZ>ajAA#|8g|g#P?~EvM5f0U zQ!8T4FvjWIEOj8X!EcM;YBqY(rTv;g7?S}FyrH)HHt$f! z+u(U|E{rGTUahL^&hp0oUt zUr9&cK-RUZYo#e-gX%xkPw=)V#g%LyjH4ULv8duSXPL0?s z^U|=^TSy!)$oLc&eAP5_r;E#R;9Z!;?#^3?*sY};vkqHM(IR+xhyu{Q6u?^y{w;>@ z@)M=818-|^%Q-Oc9H7jUAvt)}7)BB7zSK`vl8hj%5U;{DP6}pFO4Grh4XryHY!$wW`>NUkZ`wJ%UE&wn7 zd>p)hFJ<#Ac&D)qMaa&^o@?yF6ud$^=G?`3tA#7})}3BWgLNzI9H%vlfvj-o+kmd? z6o{79`KyNZBL8J0Hm5>C+SVJxrN$_{T0mE_SI^ z0hiOGg92rwxYi!EFjUfqyZF%A5Y!9Ij7?MveF!MWp1&PxjS+g}Mphqc8yUzlDoXKq z{QC9#cYk>M_M81x!(FFM`=8*kjNiUlT@~KI2y_jb;~+*9;xfRy3Z_(MYoIk@Hl(bR zHfqz11tbWHUSkVsDrrn zRe?rW6k^9~8j8**XuIJj%+9!8hgQu#?4gS%*)lto(d(c(xN$9)W9EM9b($`!*6d|D z(&p`7#5i6qdF7*^?%TmzNSi2vx9h+wd}H2(^ZfPexbpR76_^--d}Iu}4w67>%8C@4 zXprWVy|p!h%(bqLd`BtJMn=YjjfpM~o7Q%!i;u)Um+;VJbkroQJ94Ji)T1dY|IrbGxOMxL)@6aluvKA-I{6~ocuIoffG#=b8F7qm_WieH6BsRLn6b&!vhrOdq`5i zRmS}-EnUN!ow}4MbSjit7hGu)_=J|S=x_{OO*AtkYB2y8?qd&XU1-7S;l$QkU|3FU z8(eliI4zecb>x?X0TGy*ImB#n)*~y8eUHAX(=2;gt}&|YxNPF5VT)#!>zWSnpxk?% zJwlLHt+IH8Jx_YshZVS08|9oY(qdWdR4vP4QCb7Z57pP%<3Ug5pxS~3mGJQ{o}Q9B z;6{u)cu+k(ExTUW(MNdDZ1Q1#rSUp2LwDLW&7@GZgoIp8#0W*v$E#F+n?35oA9q@| zyJp8#jYM+9T@=7xftCOPWd1!lV(KL*fOiV8d0_He!Rxk*z&k@8E&^{$CA03HUm|hW z&4n+Rt3d-BG}hmJsJKq4nXa4vt>y3QC}KOuw6vZUN``P{?G{&A3vuXN#YPu5W9>+v zX}X6Aay&<>*(H-hZV#U6 zDvA&4_ut{~%li$0xBgOYEfc=}cHa@?)t7kOH=TN&`5W1n4}`2dK^|RpTE2f@Q#y_q zRo1ldK;5P`er(Po)o;&&cdmf1DR@^_7QPLGnsATv_DkxBYLesX^0RV??98~xlC0W+d{uV;>v}a%$-`yZ$;W%e}4&Fd;MKd9$PIa@}vT_M_}q z>S0?%XjrwW_qcvta3s&f==*kCZTM=xXexG9fpf}G`#XZSx%|o8Iq=RE!AtmlPw)-~ zD=Veq?yIep6#-lUyd(ro<_6;}%y6p$*%E<(UIaFjHE*!NFAay+e88&4?DX`?(APk5 z4^_iCa|c?AGZI`qj7PfUrTX7|&D4xjESD$5AW6KyN^W99*}BNe+@bcJ6$(MUd#yM# znH+LSC<{PiaY}!v(dkwQFvQwufrSBSZH8dW_exFSR^2xwa-M9MS}vuV3S8FIHC{Wban*uP)1h zdklI*B*L5Q=;#}{*U4I)x{DBSOTu`Zy}$#DX7)<%di13X!j=7(IQkpzZaqM5mzhJT z$tZ|j_f^zX()o_yZMT=tf>#X#Ie1rAoLyMKJDbsdvE;oa;=HhXL9n~hHbbU49~e_p z!>S~07vz}lEvp7A!WKfd;ly(@H`9<)5cF+Jx6W-8EK#@R_$DeNux6L$rLiz6%Z#(y zy{;qzaH3B47``=`;@6==DI=~?4ip~s65vICRT+Uf?Ox~8)z%cegF#Qm%ChXq-YpKC zXRY0P#`uZr%}(~V+W^z?E>r;`1JgoMegc1y;-xJ z`_zcJI^b4ULl5%0E47ZBL6pC$>9)~tSKKa(jHD-HYS2WeSy#l$$I7wxxwXwouwctM6zOu961UY?w^IW-YTPPIB78m2>!w*M+ac7Co8Jcfmq7 zK8s7^k|?#PGk|QRt>Hfsyln^GW>dgBQ0={mfY}Fa+4Fo|kYnKOb(BWs@!#b|?B0M- zD9cQ}x;_{fc*_j~?`pYE&MS>c1g`Z2YRIHOs13o}6=hchD(`Wh6JH&MoOYu-=)OWO z&)aUNQ&sS;_ZSL{yKu@`rwC4VfwwdkSmBI37p<0qR}Y|0M!Mk8i~oB~Gf^~Z1@yuTp`mH{B|t7jxjeO$(U%rz z-1+v1DaN|2fG%@`B^nNO9f*|c>LyG%Iz)|sUX8bed$?*jlWXf`O~8~-r0}AAWg;4- z!7KrDas6wiLrAmr+O@@_0GyJfEQvqI?z@NELLYjixE_ZWvUDU+) zyoXKc6%vV0vaAa0d&Mkh+0pI6`tA2LgDtKTybTI%qbR3~O*PPJNaX9=>lV+8z#Ka! zbEzMnEQ{{A*f#$^T*dVU#aFYT%-2;ffcp6z!7Ha2c!d@0Uj zbBE;MUE_0`FTvSrZL`8gYT0GjZo6-JYg+U|oiDu}1J}#VoK!T7Fi>q(YBAK{TCLSf z+wGWzMG!`V0uF)UHBULi!g>)4k)(`>4B-t6!)=kIsQ=_!1`vwt#kvp$tD=G*MA0M+ zT+p!3h;yGSsvI09D8Y`JXuAmdq5O9S3>;Jv`P;7S>_2}Fm8@2OCZG23Nd_b89wEU2 zFTL~Z`STZpwbcjD1_NmkSonorJ=Y*XnH*T=S@OWrB{bMS_>#wT>U`kmn{tPy9<$&5k`E#GSl-!LTW!LBXpT4+SJ5;k zsD(Ky2HrD?k44lu+R$z`=5lM9<_8y<@~xH2!MkuCyoEb1EX;yevnZXsMCMOUduTB$ zHw$J63)pBnS3BYBq;aj?b3Zm_&S@j-Vi;zD19_$15Z75r&RwpUD6OfUX_i?badmYQIeV==f1_t_S9XT8nzKfzTz1J7W^%w) zQPlxe9~l|Ww8~MDC&)(qi(*p4)LoAH!>z4{TTb<2t&k-5kYOzof0)Pb&0q)1V5n`S%XES zG!wSw>Nu$~oy#6MzR4{U?wU})6hxcLqs_^{w@BXh40z{mtt?$V173zk zo&)b$=q?!g=jSc}FYmQnwseSQ+sRmaL#K6?YsKD~vU*2`Dh;RE@$5XeLL0)2E|p;b zgDklMQ`K_=7NHl=w~+fPygW1vDj5wAT#ypsk**?{uW^q|keK={xJq9hV41S!x-O{| z#le=VT}pc}u<#3g3HyRvfiEO_(qoE*I8kgLa@74yBNE6+1ZjK`Diil*7E={oq%d)Q|RZe z2{Y_EBB0F#vo%|;O%c*#HZjbQbxpP~>jgY@bS>AwdEsY>H0x(~7a5liy&-K`zL5wN zBwuw|=+W*i{FT~L}%xF!HIGS!{I0#R@Wrw1cl zzC~`z)m6r^{OBXvOtXVnNVp_NYXEn}**pQ&`XB$-e-+gJ&p-a(f6azzI;W)0LW0u& zDyXVyBzLVsBe^ug!-s#(E6`f_@c;Sizpmw`abpB+R5tDP1-!rfYmE>Q@c#S%@z=Z} z=RqAwPHE;X835^^tHcHA+!5&h$|Em9q?GNZfjRV#OTEnZr{en$p?4;o6$vC)H)T6ZOI{0J zzo+K1W9JM9EtaE1{O+rAk76 zFi+-T&Ts{5wyx{6|*kE0|Ai*pJ@@f8uzRaWT@CA>4DQ{YWmWf9(AF}&(yd#&WXrrE<8 zp_>TwAAwiqGT$}YtaX>t^@!+#rRYuK9C+cTc`uxQ$?&S7jk2YdC7)eV8ca?DwG^R# z5VGc?hOgAf1a$#YEKq4>b%N7+>WnYD7Al$_%=zt{Ye~o2(G>SJ({G9$BBWsRy0OA7 zHK?h^fdjpw zEiPplp{8Brqr7t5Mf!TZp%4E&a8JH#me5CZox-$!BRFIdcGe=#z!B>P{N>yh;%gsi)ZUdIYLo2Ul zqrGT6k-7}rcn#^@Ay@AfsS}#s&PptcYlAO4-fEO!i6-VMHWR@j!5dpTsHYmbINEj^ z)gMlRg6{?=uV4}XEsj@;yuRa4XU(utoy2xoVcTT<^ z(Yo^Mb1$xH90Z2qIeZ~PYErJ(WXGobg|&xcQbz!zS%Vuv`N3)p&S7aqFK1)hAeN6>Z;#1h-DFVkAcYjLD@zU_NvJd<-1}-8D zYM2mwjpU{OU$b@IG`xxW+ydV7S$Ms~F5(;83iX>vGzCh^^i{vYq^)8^H6f!Ql$ns! zBD$GFS#7JEwhqRdRq~^GFtb@%9=a#5 z+Ti29JqcMt!l(8G^K$^GKv%!>C$Hur`g(ta9Z$Z859)n&QYxRk>>VEN`TnHGr)3MR zwmJ2XOg}7Q=n5Gw*}#BTsubzQK&Dv9AYwNgT*wr>NsyNj!4xr$2N*H%W=iAX5#GIa z?G$*s6L{5F@fvtX*a%3=Et0_!OGe^Tr=G~zyg0{{9+8IRoAofegA+qTSPV%c2|}*( zU*T@U8{FhnQW>xdnp(>$>l@@>s2J^ceSL#FHc>bNiCua#1*n74yGq>vDM5&+ux#9F1TUIlpbuTK6I)P}kEAgGnV$!xwmerj`2in&4g{KM3so!9CJJ-FM@DPYuO?gtTDmg2%#-McWiD0$BzRZ&iCNw^>p zvu(+6O3FfPhO#S4ul%A!=Y2N|uNtrtcnzxAx{mz!T1$Alv+#B&8FMY*{iTrU(}FUz zf*>W7oK8UEmCy^LO24t?uoZ$#Ktiv)PkJ|CpGV4xsAc>U*cgzPUKP|Ui0it#B(QNN zjG7eb2s@*=`pn49;JK%R0+|BCGPD7;ae z!0V^s%_FKofI6>C=n5|!F~lPpc;`vHIN!q~pd7y5;va2zf5_YL&donmcGKp|K;71C zQz{$dg2is6>z<;`F211fV&b`hHz6*8*YamH!AqObNqj8@Z?|PK?l$mtGLx;LlyW)p1Nk&2}qom8~kv zuz8eo} zi&=O(%pb+~-FGP==v}4JlDsIH(-z*ev7wv&AtT~lOW^eklkA4UW@}cooX-i>_%c^{ zF$2vtr4+o%HQwrrgyY5z(~DLG6n4$@ZfuC!fHAtJX!I`I1;`|Tab?&w9+cS{_co;h zIu-S?NpR%DWeOy#Q(`=QBJoKFqjVxve7RpYD>5kS+a9h}U@`Um$@j1#y2Y!X?}3h2 zuf8t_(PywI>iO*Dx4cdo0eI)AmJY%@7rCEV@XpNzk$dt({_~fzyvvK^jY1dh1X-}b z%Iu|n#ylGwJ?G`C??2<)f#BsY@$L6$Np{iS=D)&m@@m$+bq?2)et@@}?ENj=Nq^)o z$BPvfwu!+F)8Iiz(UrKwSh1lET^zKiOG+1Lq>6#}-FM%4r=#0mR}omhx&}Ohh8+6| zycWIbGEE)W1nZj3zP|MCwO_pRI(WsIH{yEM%wUvTE;%5zS7NUFQw$Odn5PyM3*NYF z2H&P?#wRD!a*eHC=%*#CZUsYZH>@Bsj%dIprdI6+eDs)N)hPyzB(T;LS;YiP4oi59 zl^8UI9nBFePSLgYY$vHfvQ7cK&vu{fY*C+Ju79S649p&0Z|uaOh_*RjmCAV!mQHXQ zKq{Vd&WYO4EAWPqI4KY{zN=wnR&bnN6rXHZb3nB?0Ucs)u82m6*}#?0=yB%tGxWUr zWipi&fVY>h*?Gk~s33Hl9ygzdCE#52uZh}li&XLlbB>zhcF_1%G=^Ljz?QMtXoqG( z6@vqn{wYhzf0o*bz1V`cDQg{>dIt-Dgdo*}4m+YoIP6Yoz_k7SYpN$C>~f#B7H!wE z@cz;Y!S$uQIe9FQU{g3q2L4v+M5gp6mi$2a1S!47daUJzme=bK2DFvO$(oDM%NCs8&a6RcootFGpY|pG2k#sxq}sN21C(RV|QO zZ39&TmYtx>;(%W|E05xMZl10)$j)evHwOOvtnbsE>ds@u4tPQ84e`|O2J?Z(d(Xn@ zgtx+fI5ER!S3jd%mcwyB8Uk!?-h;RDBiD0z2NI-#BkHmP2l0J*Ym6TRiVEHvA#a+4 zFC1!cl^2C-QBqWOVdENuJqK9P<21WE(^Bp`hv~M;om8tscMpiMzz)UfP6=OQL9>jv zwz1(SzYtZpBZ10FvMdQ|K;@q=O0A)nVK%Ra$x2R)gk_L% zWU}2#0&A2haZPZnZ_UtWd>FgCYwJ?kjO|`)CRD7kme5-@M_R9C-rq20WZq?0+N)Q% zmDLNXt5c)Y_ZN$?Uzno-Ut1o*m7bfU%Q!a&gz-jzcMh#GaI%6q`tA546_oG!0Y^}n z$LJ2+x8MQA2rr%8IS|);3_O55GE3YDKHq~QVhG9H9Nho11J%fJzNbU~;KMv*6`=BL4uqC=RWXnZ^|DXRRpth4hmp zR1y!uRk)IwhUg`GT2R?#9TdB^uF2vH`>d&DmI4^KF=c&+0u>~pp;}oNddb3uNozH( zKZZ$YRqh^7ZLrabjPIz|BxR^tt02Cdvy2LcEC%7jt83K__KIK{GYV)pR_#e!eQkAb z-+|vVx4#5u0sC;kZ!GR_I`%X|+7Mbu@yEH^!hq$R{`NVHYOu&<7Cs!Di~;!DgGs?G zGU+U$Qyosz;FwJ7^ZO4l;HjL{a*0HM080fDC%iF{A^}T{vh)$=h_|F#tyF-LAi#j_ zzmhVp>4@}2i-DTRSeQ}dgiNCy#%%D81#c2%i;sKY5S2=cZ&BQ5CvIAObu?Big?N~LYvw~^ETj;l_YBi_z8o7e2+@oS$&B#e;IyJtk zl19OKZX5-|pN*C{_^`I>#@4~116<4Ymw0mM*GlL504g(R&b%5%wjwU%s||p zJZ8lUMoNHBq!Eq*8E_NDmA{8|h4|~aCZduJWqgaU>w?+E$UvE(6<)G*DZJ;;Cs~pB z2jf78Wu2z*cBHi3(s)>RL|V^(5^QEAsiK+H_In z4W9v?Wi9oM)@*P->o3-J4ZfJZTc;X0FIY%Y%sJc5NbjY!9eFAkHdYNP?9A1{_}1~g zRqBk*2d|jmAHE-|#uB%?freyu(_vKE7F*7w>Oal}pz{K}1Gf;~?VO6G&_CK6ApbYr z)9ga!)NrokOeKRzB`xB{k|<#m*P+$0fFlsEAC8{w^by|n3lWur!)y(1&rbM__I2{j zv2e7M=fKjmHByIVj!a^vQbX?Bq@Saa@6M*+uAMtmE^BV4emW#BWW*BHw<5sh@%XG|}J+t6Zb2lIp)QkKEgdSEzf z*gi!pxLefpl8z&5FfP8c&UT00I^H(yf?MA*`0i>VW*dA6z*}H=OXbg3V0d8>;fTC!t33c z92`1(8R)qTV-jX?_%lh50N!@kc8B4Ol7eBhGB)Iv$QerNbwq;NlTdb@BU#e;*1DOc zHS>v*CncbPaOK&N57Pb-&ktzk9xy2jjvVY*4f_VadLFT(4lsCzxUnRv(+@QS_I z+UY01_>~QBayt`?#Y7_u%`K^?Co=HzKxISTkm$}yOeJ>K2I2XvV(5LsHUp|GmUy|W z1r=83Ut8sfFQpfaw&bT1@x9UD4w<0NK7P!LvmIQ)QUBsKE*Moc_s04L+Z}3EjPOY; z$*WjF4f_x7G^*=u)vfge-f;f-=;&y_*CWYW?%jr`$D6&b+q;jC=6amXqoc##y<^V_ zoL=1bxYyy+Ms4qDJrosqz92|u$zCYU6 zK|6*BZ=m3EOqAA)<4K&|MXa9C|NU4hqw8rG1B3b?F!CnGG6k+dm9gfx3A=VFB+6x? z^$fEM&&2rJ3SJ{WdoUCE&Ed^vySIXu?-^^Scd?15O4?$Sco*zJXj+oil*kDs-kQ5K z*fvBtE3WPS@crfW`c8vNieVMU9a*Jmd}9rf0+n?+;kSl?Ifx>)tHjg^t)l*7w^8RD zVRT0D3heP?Dte|tKI9OVRAS=?c|tS1$Yk+oLoM?boBXe4*}lbXxq!@b9+a)m~!*^f&D{( z_ujpuP3Lgm**h#2;MJo&ZgN2C7Qp+S3vw3Vb>q&?&c0E165)-eK=>;8ymUUXA4Ot>^%5gwOo2MCO#RHRnF!f*7AA>8f-z-%#a3|w zo>ry2h{tKn#x;ctJc)SV4Q^ug!pMYoEO?WF@Q!q}+4Oc>82=;izH33>VQ!M{1YS;H zvVxA?EWASRqTJgp;PoYyHcD1~RQNQJ$cxL%M)6ca;eR!fiQGGa*rc^uWrr7J8yixz zm)v7(Tz;BirTj{6X3S~^4bq}ttR*@~J>v+Wnhxv;v649?AiiE{TVG$@KOERRo=3@h zcz8r(378xR0Nxz@D96b!9WUi$rx$kwc#k&+4)=TZcweAgSb!f6@D7e%MR>#8q#=v~ zps--`5#9@kERD16uwy5zQId)9&Vk~YbwdgsbI?Zk?m(xq$Ep7^%T|36SBT10R!izGlt<9o0u@}5X@`?hHhW8pq z044Bti#3knrM4R>NmhiC#ueV?w_l)c6q4Zo{C3?BG8Ck{}4I_M+ zUZ#2rlPPu{n+7XYUb?k`DzSgKxxKf!Ns@Pa566Om7bee@rv;qS9arJqTVi;5-_3zd zvI*nqM0kfTqS`Rlce}5zZ&2aA3h=gH9UAJRLGZS>!^ysIVYILF!i5Ony}Y-OFM37p z4;aOk!C1W+>glo*;p^jF_xYtdKV@7o2%VblW;P!+cLiEq|r9e_O(o} zZPY>bl6+lVtq|s0R7aY$SW<_viv`fjt=KU4qCsjgCk?ZcnJBMU$(&1ZX-s+@vnRG1 z9M*tQJs7^$wozR^I6U6#Jv=%@c+d6V$mNdr&INPF+dYos!18S2cxj;5={+EL^G*)o z-9P4i2L{ki%x2)x!49Ald485&!~$=(ftL)*68o;7*M05%?(^KQizfzab;ai+D0*57(o5-5 ztWNnFm2NH6#PrL`6w@nGy$rlimq6|{q4ougFUOp7s@BWqhZj4`>FSRgoOrP&!DrOW z($s*&k|EV+yGZZjb(HFCieM^hXko)%6Rb`fR|vUAb}`pzcdJGv&e-O*+qSm4cyO4% zm!F#lIXky-cmU&Yr8KvBI3LVC#b=wd`FyZXo9A)}Z!rL!ci>jxfW}i~I_FYg-t6QM z4uE%dj30G&2DgV`*)Tjj)aMW1p^Zahw6dcUHH+{D2=5qtYK)zkTy=KHVnVzf97yhJ zM2WJSXud&WtL5ut1z^j->yu)ahcE9}Ra#Sr$?e^3l6fq6)lSAFjvzjzZlTxa_cic# zzYbm)Y?w%H7Wm7(6X{1;LGxxh3AllrE48o8>QwYC)rTy2<^9UK=|k);qLOJz-Nt%C zuL*t>{k1~%q}LK{n5#%`>{7t^Rzk1oVgYQE#9b#p9`lPHiH!}`f=RQkC9V+av9hzp znQRzi%-^Qk1~PyDU;yx(JKW604+P$z`1#>{6m8>^C0MJ)1n<0?+dRCN2L|^=5d7&UVHO?-1&!Ci+9nQjAgv}^<`-VElj#i>rdap6S>g(_%kX$^S zrh2mHm9pq2jwM(z@>NQ+t9IkCiQUBM5tW2pG$IiVi)&8Q9~^uzJfEqX7O1;4$qyz#cjtQ>+(sMxj26JBR61;bs&OuupF;A4xfS(#wjb31&b_S!7L;q53J<<*Dx7gmw*I zl}Lynh|=yl)`GeFlhsyJ>KgEIX!A%%3wUj!;Lk<5;b7I)k;eBQhW8zd-fQ+J;YXGH zCV0h&5=p>g;92xaz>-;i%37D#c3dPfGS*$Y=1Q?xGydBh37aAAHj=#8lvwA2jaH7N}#i!f?y`GVGSt87-II{yta5U*Q`O7iXC(VI)BgH;!*^<_boP4PSwA z;Q}qjE?l^BWf-*03s*1+W#j^=xx*J|c*7TleKsr%U%*mC?uCnfp zC2&>pEiQvsYe?Z!#G1u0n#pz>HH%&`Js`taink&JR}ymtY7|?$n9{bkS|!WdhWbLP zTFA7zv7YwjVFAE(X^PQktMBaomh54UqaJ?GgcN#wwOO38+eyPdue#nm`KT2bd=|o}QUGT~|@igaiL~9mf@mI``-<@dwH>&RjbD>XF2l zrDL*rM|LOc;qfZzHKZR##$;FZT}hW`Nd)Tkh*fH@8D0*X=?Ue^0|oAgy>R=7;l&So z_gV{h;h&$789`Ji;nnvIyG-vQ4KC3TCnHK~MQX%dSv=>i2AxU4Nsr=rD=$o;8Y%U` z`YCCwq|fefDjI7MD4ey6FpFL`PCR5YFZGD8Na$mNujIpFi$SD&U2=&li8F+AjT^S2 zQeRhZNZZct*1;ih6Mf21 zC@*@RvwwKFM?scpTe*=8vwGY66z?;4v{`iLkCxPuiMu^LN=nd1mYU$eS6@+flf_&> zTU0xi%@+JJfx1cgRMIC74>%T-+#PV3kyL6~6THM#kFJa8ojqB2)0q@M0xy|0u37LV zAwVg3h2BI4sN^MhlRkp6v}TKpV3K%9j*lo~IFl+NB8(*%onr}>4ZT>mW_L&D{|Xf? z-PIM`sy)TC-FDaVt8GyzuqBf@f4-Vzb0*FZ40ot!e0f{8J(&_U&==hXs_*{({=wnF z{=ue7AiPV99v35xT@z5C(JJr*?@T_Qbe=zQWSZua2d823`R`|ARiFYrJzilRK4Kfo z($J}~EdmBM)N8TG8;M$g*LCL)R$Q}2i;E0zWJy*!%-jR`>MNzx@{D53Bq4|cSz58H zD;a?|R(J#7vNPH6cDIDLyZfT;wuAR#HGdoS4ynM`8s77i!gkF9z2p6BCiLBkK>LZL zFtvs^vR{=Gn&=GyD+{(_WLs*)UqgpMgAXj&maG4cP?EhDD}_j-zebu~G7eCRJF5)# ztE64*vNFphOnD_Iel)GW3tECM?MQ~BD{o6gGuHjzz%ZPhoi&56wqIFmgG0v7z~)gN z*JC|Bw90pif#cz9OmmWtDuo(X_^MAme?Il|&gYLhhp%4ci0?4GdKDzqgvW%6cXj>O9Gd~eS{KA^l-B)-Q%MYL!+c^n6xim8#{&ACqRK;CtF9DGw8Ion6M zT+!Eb3j9)3P-pMhan9xPI{o2x2Yn9w2y`9PDzGOe90K3sNI+GwNVVT_8@`C8Tv0VS zJQyg@FLoSX;q7=0yxk;uEx`+yKJYZU!4kQrN?z)(mVp;@A2!brC3_!VtwA1-+c4$|Gv!SqL+z12|M7}9Lk8KZrLxM zvGq&TS$5zuy}KqH5-Ug7)*Ce{#7jy<1IzDd{pflF#X8*>t92@}Q?F4pyC}ymFl?Cn z8dj;>D;=S&zFvc0jpo}@p=WQ;fiM4H|ADu^>Fn+AZ{kni1j)Srz)#Q(!mH1pU-;Pw z!29UYqd(!9`8_;%^e+*p4P%$X$wzm5csi>l^5Xvf;r`zKW*C?8P~A|y;qZyV z=D`m8`x46Md9w)b{QgQIT-c{E7tleB=0Te`U4I@X4S2M`x?JGnx8C1JsqE1E0M>DZj;O@OE@q@LtnE3d^4CM|KYtKM|P{=Mh>{jSW@AaYrf9yN(h>qwK9r_*csi+*o?>O3b0IyAa|7KiXA#MNY zK{4VTdjZ~{$X|FLW$%b3HJjiSFT%OR9Hpzo_`GFcdoy^Yyst>!7VwG?jRQ+#NJS~} z_M#hRHX&%OK}0{Sme=k4crW}p9QsPj3vxfJm#Lq$@%byfXYm%TLwuEnD3=SYxoQ67(ZxKl$PtYp`KJ%97F= zib$qn=h&yJR;#a5O`Kg;Mp-mB#0+5#O1|LicCPU&3%pfwJ8%p5ow`LY>A8zyWdk7% zZ{wzJWaq%L{=3WN7~zE()`It7f4-F4KAL-Q^mJuuX=(p(fEGLW+kXG2M|XyQJ{$p~ zlXrgpd-(JG(d5|J*rVse0`H?o{*?>QpTlY4g%7}+&jY*^ak+4`>BHd{g51*nJ&G_# zXT%DA%Hh6C6=RDEZ*G4Nw=clk{B8J-`2v}DoTELoJ8vJ|3SeT$!^p85g5MG3@&lk> z>>UP${H@&4UeCcHFj=^FwAq6Z8HeuD(R|>|<6-xXO7IzZyf+U)7S9)QoBMDIw)a1W z7nca$V5Iw0c+H~`ULJs0b^rh%07*naR3x1SZ$}p1?v&rR6};D4!5c*)BmJZ#jsQ%B z`WMTA4X;@I5bt7QCvyX?apqYgvXc5De|GkhG`uhWoqTLWFWUiFCQ1@!ol=J-r=3j+ z>;sd^q>XxtURlnmKD$q6dlP;GFXWB9?xy`XY{vUJ|!E=_L+UXgxm42cqWo7ILyXrBTv*W}B< zd*0RHbN;OYuV=iHCaBYbmr5r~E`|7f*ry&PS#DgmO>T_{(9OUL_`a;Lhj?d~>-JKs z9+N`6%P(>0IMK`LY#Ssqw@e0`WXqcxI~-b0t~<6UP~OB^rOxX^Dx@P7OIXSiz{~Yr zF08%=@a~qQB3^X(`43hO&~C<(7ndv#;OcHKpd4Dt7ea;i_T^#xy=ZM12045A^7BVy zG!$MMlarUPa614WC9j{rix>@vZIP6%;?si#_>={Nmm<0hyis{&VQFFWU@jzhrAUDX zJGu-nY7==A#)?}&P1)ZEfsAg#B1_(T3wefjV16No@E$yU0EgXUcwJah3cNJ=6pHj^ z_MgIWy!c$(kHOp4ldvl`Dlf3%?M}mMsxh1jZ+G`KEun5~8t2bzKmzqmz^;h~Ue9L} zJuA1Ng{tprz`c4g#PL?LMK8Y^q1gD{CoSNG@figuZfuANwq{#*O}9bhk4?FxV~AQT zOE{kWF;-HFM+?FmyGh}Gq895sY4LtEQS8#1Pt{;d0&4K;u5GummB8!$j+ZK^^Wxa{ z@}QyS@-zw(&W-TW8A5o2#Xk|eotJ6Y@Xq=GFBxFm6$XRhou^>-fglM^js3kNH^}D) z2;Rts7hVLNVDo6sbGA{$vONmclOP`%ULY{X>bPHI$y>zb8Ny46qzG@JsPG1U9^pND zia8?N3~%iAP(1!Ul)(gVF`yg~P?r(jRE9(|x@~RelDLUCfj6^WV|d?B!;2M7PlZ=L zilwHyL@BE;2(;^+)V*?ph)uW*uXWC2%Ydfz#-GS&hWDFqsM)GEnW(QP5i}KPrKYVW zv|G^Hn$Rl+OGWBRT_;Xpr?H!MMsp%32>FIo;@Of2A&lf*GmE&b9kTO=4qiE&U%cLd zfq{i%R2zCI5~sXa+~e#DhbN+|unzkfC=Edo;hkM1c-h(juau@9J)Z(u49hpbIi$rG zoZyvXP#dChTt?;Kpf^VtVWbG3MAvhAIW1$KG;2^CoxS7P2gk5rqp{bU=PBLsDXJ!b zRTpf2+AHfgW*4dM#r%$A-^shZ@~ts$2!NJb2BZ^y8B8RFj{|s*G0Yy}&GjD8He9gL z=yfUN=-lZsj>0a&%S)qB*?l8;)r3n;S&W&tNwYq$J5v3|EW9R&16_$hz|xgfVzH}P zULbM8S@4SW&82EZhTWF%ezURhxSr74cp-KAvGk2Cdv|M08R@9vui%OcQq@%JLZPC; z70Vdzoh^H?MS~OUHRzXuh7%J++j_kL_vXSLiJeUv+nh;30vBedO-h*9Bf~bFq(_g2 zfA(|w!H@pbIUC3S@(3s;JMSY{$3=g7G>nSG&v6)6@B=tJop3zRHY||#Hgkd27NMXR zhd%^2FePA>Z_NuAj4o8Ok!ht>*T8yjdw?5fOT=E%QF(LFHGG%R0mL-*x98< zR@!xfMLS37NRftm%^ap$<7_#zUTlMV1a$Wz%%UDF;%V&nVkX6aR`Bwi9NCR>8qak0 zXWR=k946ftS8pzw%23$C>z8i>yrxhT2R=GEgcl^{09l|s$IS<(g9qLJ^a{QvAv#4^ z%)z@ra?%Vy8k`g5=de(e?*;s&U~`0Dy(m#Y5!R+1$PM5U4vvX?+=4Bz14zSM-gn6n z2ZwbfoD*Jw&G5^e{H>_yvw_T&AAIY#QJgPypBm$sgzt_-vM>^?;<)_dlGiN6u()~x zZ~8;dYp)}o4R1%G(>4!~=Ba-=(Dr2XDp zQ`I9%m2+`r(M)ICqpl}0wQHnKuhMg^%)Q?v6+kW)OAlF!@PTEHq@p&&7o-E)#aIO)-|7O zxl72`Rx}2MgkDcA#R=mmc7Gh+ufF=`rP8S)lU8sGY*nj9mFC9Z>7z?-1k)>hU`1&g58 z@OB%?+Y(;m&S-)cHQzuz(0<@0W#3JWJpNf}(Ocwh#GVO;G8(RBY@-|d(@)CYku! z>3aV7X%s&=-jtg2+{Ia<&wYm{eo1K|Tv>&~p1?yxQ}Cg8M#tzD16ru`i!40ctAnE? z%Zq^~Ur48H%cQAufwfpk-!>+qT<#mhP&C4X-i*f3(Qz&)YifOHlz8}*R`6<8rFtEj zByVfgTJ{bSk;*q+V6s$0125lloW~USrXH$^gOs^JuOsOlelYuKYk2?OlHRXSSFjDX zb&9z~@%ZrJ!wQGtuBBuz8Q5x)$Np^h*?NsC`&Y&G!1zjaGw#i{O1bu0V;Wn*is_9) zA3zD&snpuwzIpoe={Ag~D_jiJ=Np#qNfbPA5f|;siIx#Oz|uNyaI{UNicDM7+1(4I zhFB1@Z)pB#yAaQBFQCcTX9*BU{+~a;_d73QCxk3GBz}raIfFwNhY8*bv0+7oCw}Jb z(F<3&vNWZ!sPWw*R3lP^TDDEtKgB5Nq?E|+VmwIbO-bF3j**@yfO|;45Dl8A{S>@O zEPF?~vaiDXPrpjR+bUN%4X-`K*1%tw;f+Q05&`Qa(w5h6YH*;b@i(7*+7jOH{(cI) zpgX+S#Rd~KVQ<}179KLG+&y!RO7Ao_L_uKDxz6pj*{88Z(L7R!fg1j(@2$2@$(ieQ z;#oRxQaf#odq)de+v?it!6AkL+&g|67crIwo#9v?PgD#3M{)*I5;v`3*fXls z+f%j0cU;NtLBNuAn%0uy`X-TT>sff;p^)m%r8imT zW~UDoy(KNFsrRsTi~8x69c=%@r}i*TiofIdt~I=X?>9TOBw)O@ZuM)!{%T3r2~c&> zCb1LK17rM^ zqZR)Uqsk7q9e)msI~^XbfF#Crh{MCV(0|G<49DXw^91kw(I!?^-#?70Lpz+aIec;7 zDLTCSUR=WT`ryIQl5TN80B=(ghhIc@K}hFwY;+c+_|>7otFBMk#$k-`UYVjNaTpC^ zQvHW<3_H$_4vnH<9&JC1JGzn$i~Nxkmf?fyOaA}VWq>cL>xS}u+5 zk|_}?x)qnP^P1p2Pr1PQM0HpT-gh%$5Gkpxl6O}8Ag92~G8u2d*wllSn>YfZYOSI`k;*Pdhxd*M8ZNufzu!$Q1UpWL zmrz%D(M@C8v}WN|-Ay9YMLS|u!MAr5c!Awk@c!rDTf&>B_vJ=adb(rBDr!!p^h#YO zdi;>f;=HJ?S$47-Rr+ULV0Zlq>kgtIYfUFq7aBq@3iI{KnniDYYp1PJeL|`0k^?i~ zYDna9$r;!_4(5)IFjZmy*jYNB>+R_|cX-(Q^ayRFJxBWvsSWvi0&kw+%u4e5HJ`krN%hSc=;6LytTWm21^ zva;B7E-{X^oueJn>U7b9H*A9U-D|80B;*<{aZ`s$^K2z~@gJ*mT1j+uoFuqKVnVNc z8EZ(-IlVK3gkD6q8Q$N0^5-{#_p2{oe)c4hy=#eESbe~cu~W+9>J}F}tr-h18Y*fW ze#V~Rb$+i1->QMPD(<)qjt5%TJyxOnz~pvL19J+60&Ka( z2R%5-6~aP+obM6BJ2<(Bg-N2@BxU=4GKBC_rRpn}M+a%+(D}a6zO(HYhR4P(41(ls zzj$%9uiY5o&?=!B!t7Davdhao$)#<$;LU>l%2|^oFv#XO*ak;@>d48F+v9$)|t*^b~l% zY8sy`8-TD7|@c-=Ru_8mn6yfum?kAqRM* zBfOr+0gwZwvM2DC1>PVmZXPY%JK|#=<#LV~%@+|~EQ1R0PEl3Kp}uy4H@<`QCnra_ z*yIqFLhJ0jQZCQ7gU;L8IqXsGsAN)1Wxi;lXo_7s)fr8@k#CvIY!!kZ6TPrbOIt2j zUC?D8BOkSb7e;p$T?^ih*6_;yBm7zTZMA5MSQ8}wOm&?yl@@9NZy1;;9Gshe`v)C1 zyr29T#%b`jp!ch4HAAm9PqBy4yT!hTnuM2g9wp1(XS|%_c%K&yK88qNO#3nTa@yRB zXYib>L=NO!uW-EC%OO<)FW31%fyxHkB}#|LQ4lyfTpfyY5mQ+XQz4nd0S@v?!y8bZ zTq}6D#V@>cyh4c@iwtjHJJm~_@;W=)M;YG86L`;d@<4cLGF?P#Lx^S8JBJOsC8@*g zrpD>)da6)*&FpHncn_@8%CYuTx7jVGY!+^C3cT04(_pIWH|;@gHAWKqfqb-P0vXMl zKbb6*iz&51=q+jlB$nI!!yj70`)L~9Q_T(5c)X!vk&s*!dZlj2sx5oDKcHz4$b<1k zuLCA+GQBV44iK}0y*8}A(2yWpu|JBA8x;9&?YY@v}`pehYG?T!teJqro} zz}q)CiKWy5-rIv`FMHmd!M?NJB>bU4l9w;lAixV(3E}0*6ZtfAsx@?#Mh{{w)5R0M zuuGr@GXIAulCgbp$p~-tZSlKP?7D6$82z1hY}uP;SG3+AgSTUtKa2x_5?@{^uo`&L zSCxA(DHZ8!3FFWNK+ewo;lBiLOMJh4`GPAgNMFF!dYWGL`fBl=7W7sdREL3AbE~@| zeCsTGIqibO%0w1(Z%bjt#6p^tPhIFbA#fj^~s zfv63mgPmbC+6Nl!)a_9h{b@9&RPvTWg4bhsHO`6)ZKUayi%yM$sFZ3L>MA_cKtmQ* z(zql}I<#nk)O4+hP+*qA)LtuiwFluT@X|j&0&hnfOJ0u7HO;=%*I;VrxW-AO>rC5! z5^`B9Pqp12hd0aaUrxcd!G%C10j*kZm|Tcjjco+0YgWzZMumMCYI|UTERBDtlJxEt zx4G0V6?O50EU#j<-UhX{YJ&&ma0Qbka)m-Pw?JtV_wp+b@}&|wbY@rXaiT)ecOP(0 zMeg1TO5Wni0%#lF+(JI*&#&a~QSQaT5S;-XBqo_HBe>;#WE%6_vJvlS&xSYgT*LT7X1oU8EWLmE%U@cm4qxqZ zQwqsfGgPMOWg8b8Cp3pmR(V{=kK-fRSkb6+6M>ZQm7*uB6jsp&cR>lma4=N@FOsB9 z#D%##QAmD*^0eN_mQ7Q|~ijSK*Z-r|rXBj9S_CTEfeAgm=>Lo=>#}N*ATh<{Py! zyi}sfW-$$K7+dwTxNdw_^y2sXO_tq%5Z=@9U0-iOFB7`jg5J7T6roXPS>2GO99bCZ zqVZN%#r`V+K&otD*Y};cNIy=B;&X4kQfxeoWGIvsZQ>x;e!}M56&ReUtcrbeEXWsw$I<_6Oh(zj_vq=Lf}=%`o&d&Vx=&23@Uqn#;w_ z=t|YsVqn!82s8jHw1k><{NJWCSnVfu-|_)HU!@1+omhqWIb|!&!XU8SQfE zu{6Ar+Fp{LY=JF#wE&#nUi56X8Ql!LpS~7erS}xo7b)CeB-dnq7kj}5XBBfe0~L~% z1^^Y6sJ6#=w_01T+Kysv@FGjK`Z_i!ZmU&R4~}}hz$qR-r5?2g-IxGDbfd`GKk}?# zLlc%%(q<)$fUL{|6>EZmcxeSJsfrt9#jX*|7;K%0ItoAQCBGiOK}(-AJk|$c^E4Ws zjecO*6=`fcqSO;%=H1u^L{3BMPAaM+jU_fWFjGH;?1}pR`6Tb%!2ACD*A!k&OjF&V z?NoXtrUGwhbN&syQfpU@d}`6N;T0i4os(u;h~57hynt^Dc)xu0vaz)zy{M?CN=;>N zW!?#?CT~<&`x;7nm?m-Sy-^-B{ z)s%5+>Yj`~7AlbZyt$#ee8avCS-(ogK=n!=01 z*9cy*kukid_Op4SKx)~5VU6Z51+gS-c$63*1E%&Fka%K zirNBhq-?cmdNK740k%EHJ3DE5cdRPwH7fp#CO5H@J*yJD^|*Mr=NugL;5sS@3I&u^ z1+pdu1-51qym@$uq~~0zJclYKD4+;eiFPamvlSUY+da(uIknZemw?Y{{-Pb&z#tvizaV zz6Ebw(w=|sVf|_F2HW_pp1s=fLmYKAyu`x1o>lVzzlNNXt1g-#QVNtb>z@c3v{p9&c*7 zUPlbVPFRjt=!jgfIq;mPT!6=MJ%_BzSP2hH;!-@XOuf?I7$->C$M+5n;jP%q#Se_w z)kPLFQ{oqbh^KEq1Fu8_@Q4DdW1Fz6VmFfgW!0$!hZdy=sx*C)Ev%C)>iZ{t3TToAkA{YTNQ!C3LaeQx4eO-rl5~ig}ls z2>MTtZWTzM1$ehP+oBkiIq%{DxreDp%`HyF*nTSK&N(>brB3y1NXp32 zR;(UQIU~hpc9S(9Au29alN%<2FEH8@n>}p7%bM~(2rs;2q%9?V6S3Q4XRN96AWmte zR}^9Tji#fk$YygNhyT;?D!qSsxyFHuLT@ciuc!_U4Nzwymu-5rm+{sMF{M#M!D`ww z*9K}tIi@O+F4j8g&E;}S$5`TYAOLizgc-vN;xm^!+Jo!mxB~5jDw!Oj=5$nrgc^A*M z*;VLWTk$*<-fphC+Zx^wFgr*u9v!PnYSY1e2XVUN{Eir#?Zb6U2E-m4-r zr8(@X!yy%crZH!0brHVLOyEv(1cnz>-!!~on83Tu@Upc4zd%60f#BU9n14WN6aeqR z-n~L`e)9lO<7Su#056_UhkIhqp9XJKBzXBfn*{Gs0xv2e_YUc;_vZMs47|m;`82$? z#R!z(#f;pwEk`PVPGMITITpJ{DfVNHH%raKYUobG%OgCWf>(TI=dW4T2D@%F!wZ5w zp_fMwxoeu>H4&J)fDBH9*ZNU!3NQTszXRScKVt;b^vW}-Y}RN=uXJ@^qX_hk`c5l) zF?@pHjUxkZkLx%qRI6;j;YxO7jv`YT&~dcga|qLYPtUz$DnGj59Pjn!?;US*A7U)Z zr0{x}K{TL8BzVJ!;f)F2Bkf&$cmNs)rd;I7yhGLUKsBNM#=W?U6xlt_<*)-q6S}6} zR#@g`OPGRJA}v(i2(U{G=?tmMonD}e*+j*f9jK+5#eq~!D!DD#b-nYJZ_O&$|IYgg zueM6zKLQGvB)&2=N^b)1 zr*AHKzy1f{P0{<3=lNQSUR5Nxwcu7|jn`q7YKmUbE6`9;tyif?D&td;!D zxe>hBwq$=`MM{D#x$vnVT=9q;!AG}@+XMS7EYU73dGSdvS1uKHO|2+aL_?KEQp`wf z32An1Vcf*77EM$hWIZoRLttkXOVJ-PC5CP*cso+?s@*o}8{gdoFWrTnlc)@CDaty` zjIJQhvf;}7dy1_^ufmIro&xWm|D1(48x{HHq8DAf*pZE^-o4MkG9+9YUo*omt7!v6pVMWKIr;a~=e0WGlWYIraYYy@yK*%p;>Jj06B{5;Q$o~~l!@%XF)wX#P`uIw zdA##RS7_DGB7s+Y#8m!6b!eB;>pObl*kafCtV?WgZVj*4szrr1<)~BeGNrcYO{1HE zH>)@N^^M{EvPzDyn$#|RvMh;0q6e!QAk`(+8BwuwFLq^-r)xBRZ+%_h-DptvvwWV; zOi`=dX|*|omzFcs<+6zViK&7ey%Km%p1Qe4vDup#b+DRm=ojFi$q{iBV=A&62YD=t zBenH>FZnE*m_fDhT`I=sxcrjuQ#*>h>uJ!MLT7Y_CU%KG$y_^yU3ssO&S}9bmt6eO zkzM7^R9iEeZ~AlX9JbSQdyIcL125VgI?k2&S7zZ=^9qaK47{!^KJBjX8F)WE72ZGp z`HkTH>l?$nMvZ&wH7#2z;bm)SdL_SoN2IPg?f>suJdTE2CQE3XvL zltrjX&QDRU4)r5;0bUHCq`*mskKqj^{22JK zg2Jnxr4v0xTt+1fqzw48f&dFe;vaawvP*-@erDNKC4sK=qSUf=J??Ctgs!C|Y%!y~ z8g}`PQgehbYS^{&j&-=v`2^O}L(DC;Aq}s4uKT=QMY9QB6Krn2t*4^b`j*M}aY@&* zi!?OB`{zHu8N5L6U*C}4&m@ajWiQ5(+p&=}|5sKwB(`Bag|8NO*rH%GlT|N`VHw_C zv^Ky6rt=?|iAalv^0}Cb0$eCvH7zDJFpy98RA{S`gqHj}Jr1XTirc(W*}M4Tk2Pdc z<7(y1%0WUZHEWpxQkDbh17O9iO@EA%pMwsva#w>=9sjN{tJd z7~w0XvPCSGOY9mrRiKg)zz?ECXAr@A|9XkSg>guMwJf_~X2@5~ zG9u5}Dpv9|AVX{nacY#5Ubm&B3(Ds^tR$e*=v8#Phi%J8243-_Ni2|>K1!uj-A7|d zi$V@XZVm6Jt>OJ7&29$X?1~K}FW`Hcy}`!!t16eEe)jA|qsjnRQ}kBU;9FVd9#uQ* z5=BPii3(rwZtUz(T=3Rb3f|QXQr1#^A>vxG=jmM2-=l+?SC|shZ*1(2cp3zh2g5bR|Q;Ma|vXPZi z0(7x8yz=WZjHKAb<(a4ba(qiF8qu41SB4dY*W^Pa**z^P@3PukOyG@Lz{|f*%y^vR zp+;x3PBbCDesW%uFXX)r-VC};@P6G4ug!0kUJKuskLw$f4y;u~s%xpLO&UX{LU3~D{3k%W&*sLf-XHQJBIgF3E=HI!_tO> zE-7M|uGcAoHxWi`Z%Zo;-eSrwW&+qwfj4sl=!i?_6BC0acv}=Nr9uuCyyDfx^K z?d>2OQ&md-(Wdnoc=k^U zX*E=BZ^50mjcDffL6w?KZyV?wH$miu@P{jc`(u~!>fad~_B?D2ICdxU5Z?I1vB9CU zmjPSqS_SNmO_pJYNrJZ>Hhq`~<=`fckl}@6pBZs^OGxmR1G`1|*T4(-j?*iK z-N5?*pZDVhpjY1WylXc{Ksklu7+ilvg*hSv_nB$u`tP+GyuQUZTq3wWE+&Dwdt{yIai zz^e=TZZ1YTJ7H^ zX6{bkote2Ca{ckq!6`U>W21L`uMacK$EE^;cWiWMX!LeG6;N9G$K}P*!H)+=M{j?O z@OJQyBD|-@XJAa+9Vb-BXRc3=U&Sr(8Hn@9UEDIwTgE{!!@tuX76Pgznh-!` zn7)b6?@zlfpbDdZ+`BpRKI}X_F>x2+E%~JH7873;VJOakZ&s4D$Vf90#1jQf;W}Y6lLLU zQW&!Eeuv}h488_lUbwY_SMmJ}1?mz60e*5i-n+Wf+OjNg%{ZP=mU7=`t@;2`3%B|8}GsMOA{kMo4AROu1}2LoO%09gz&zF@Gh2c z{Sq{@D_Vg_bSi0`7QFTx*GgU*lvEO@;nmf$ z=0~u2XBE5!yh$+>2|de--WKq-MmK@?yYCFVU$>OJjBjgt8Q+=&I8bK0_7zro(?MdK zG>l!cb}ZEa#0=&k(%jvbv0AYW1WO zLxw7|z7mIS8r_5+QGPP)8V7;;iP?(WLh!<_9ZmGMfL9+7{Vp#@o8gs7L=GNl_hCN; zZ#l{g{S~Ledy3dK@P7B54eu%R0>A(JKTe_dD-)fg>F!$0x0Ue4!Et08oNSy3-weHI zU1K}%`m>3dUyr;sF*D8A^(xm0LwKi1Z5SE_Q~_SBt>1oOYKY6(jGo0}Vx66f(G*n{ z>+GDmJ&I)%;8X*=XZqoMO}}*pP`x-#@Sf=(55v1a>CDW{yIp7QUY{W4VCDvJ3ww*& zK&!C3^a8x~U6PytZDW@DwF*bOPN0CV0dLos;YK)D!ei~_$-9w zj%tQi1UZc+cuz-{;r;Hr47>@xU#H;x>lgp=AFYd+A-$iiwV=07^>6BSa)b;CgWM2m zYh=~ksI%sq47QcEHMVwd-tX`4zkk2~T|9A7jPMR!M0jua-R|q_8)SIl_Y1szGzi`c z7vL;iyfWI?dEo-p)~7W*!Rxvsmu_AI0zdj#;0=A)^3#clOPB8VUz@%@a|y?dw-MfJ zQHbzHK@`NL5ANT;53eBi%mM%onBBb8e+Kp`#nTG!jUIUG&HG(v%D&BRKvp}PI-?*x zOnJvtbFwh>Gg#=_#~3%nTxNS_6L?J+o6r3<*kF<=#L(Ks)&#GYv_A18Ho3(74Gg>{ zm`JA@>Zg|08*F+5-7QR&@P7@v0PlC-wSd>~+XU}x@D;~ymR|L_RoT9)F}!QFjTFA7 zhoC6B%J2^2+XnAnEWvu+bwO>wOopk^!49Alc^&PWYH!DKcJQPf;T7>jw?&=CH{gx;UZBIue z?MyYT*bOu6ig-2o`l>A^>@ronnd_k9>Od!Ka}TJH!;vi`(qS63HjTDZ;1y+71hO${ zB&CJr4Pt1PDiT?EqeP=}-w@tU((wKWy#z0e*6^;1N8vPj>tbYB=2q{TL1Bb%ozvmC z>pU#qs5$Jj7pOX5#hfy?1so9-e|D03KcH!Mj30qEFgO%qSGCc;D43;v}NLc2&ybT5O{H! zO)n6N z;bnZ&@V=JbucSU?gPgoA=&fn@KtMsl_bc4q8Y_&amRtqxMbaDtAK@5SW zFvgEMJEPk}q$m#c`Qb2cyc`bQCU{|aHH7fOKCmNuw?-xe-tq?%H{FRFXGVP50`Oi( zJMYAeuoyCZ_r35Ve)KlO>w{i!UEn2U6f_3fF*ev2w%}z(6&me@>b8bA5VwFQZQfo7Z&L}UW_US2*rP5kuH7-s&xGJ;_?+f7 z$7Vg5f!CJ5mVWVu@Zw9ah4<^$dc&!@!$T=YwuP25u{PALNXad+kS(ui@EMD0fOLIB zQs-H9$k4miwza&LYVw3lpQl7G@LhcrhOjJy(=c_hbJ(5w32&ToJBRu7z-D?Mz$^W8 zrhf|hK-bR}%cZ+#y3oVsPhZ3>{iW%!>%C}txa(baya$zruC8J@-OrD(qEs=C;^}G9 zC%P_%5?W5ryGrGF`eWL0JoKlo@;*Pki%&ImHau1C22vZO=y4o|LCzMuzHhpkvLLkB zHF-FRdxJF4QZdag?-zG8(c2QdIRFy%yo&Jpyhzrl znBj{w-~wH|LIY5^GK^(8{3`*--Ydf}u0+8V4Yl&G&?4mu!;Ds%U%vO=d)H>Mu-LVJ zf21F-^vJb~7q5*(QSlm_(vkiVkk9=zV1*-(sLT!>;b+%i^p7YK;YB#y&q$2Wjv>k+ ze$NQ(OSRMZnu+Xd#V(tdte`n!&O3!&iQYvsS@Af%_5fHkMZR@UdAeb5tCHWjQ>S+ur%MybD?c<>dA&BA(8NP{j2&5UkO2lw{nvSjP zGMa9&TsnkQ>?@*>-3Tcx-4_Yy+B17 z>I@>|jDoAbEgmxrSe;H7LE0W%Z~K$?#B4QtKQC_y)(FdVnIc>M+%(o4hvZW&3V z%T9cN_gy{r88D1-$fU2+hX7y^!aFjewl8CuRl~i>3fAD3jNJ8gaTk^168zZE55=sO z6}&phfc&Sgl}+79LKBVWBI@aGqPL@o-Hz@cqq(NxwZh=JU3S)Hmd^P}eQ92%CG>`; z!u#o`|2Vuhz8QK`y2Hz=MeiDx9ayeZG^%WESLt0g^b*>*s6%|Un%Sz#>@96cu zuxqyb@$}TxxF6t_es}yTu#3kTrxs`*Pfy2W=jF>E+0iTyBx_xSVY8qLa zMw>k7Amf)XFo$oID2YNZmD3$Bt)!Wh(ja%4VN+8ivKVA44#(UHt=KGhU9q1nQuXa~ z&5CbU-92Z*ckxuyop&0%Sn?arf>HDITQ$L}7XrN|WGDIWhWG0)zEFHy!VCC*nb7-L z#ikdPTLf5RB6p>~Yu%<7eaC836I;Wo3SSL<*lMd*|Lw;0@nGu4jVUhs9S-)P5S<*H zjpH#iie2`7f_HLoXlQT@h{27wLGBLrP4x{9jxAD1By5}XO@__vbr?6M8Q#U~mx|?x zwv7AJbk(q%F=PPzXoyiN3eVFnA|CU`A;zf8dkvR91@mO@b3efFY3 z#hzNwt7sB zk*FjL0mV}oeg?95=r+TPJDMf=c*G%*0`JU?kHdIm`Lk2xfAJP0@>@`F<8Cj0@xD`V zUZVGlR`e#SZzDx-Md_7Vk|sI_R%@@Lm-5`$NxY`;KAFbI+TwUuKR4h&#~H$Vw$pRj zJxsyqzBeUNWm6a1+s}@Uh1hz15ni4xVe56beQ;EDhKZTGKRt7%Yv%sP4DaU0-QU!Ukfx5JH_zQFs|^@$PQGBPo7>BfzB`+>ll z7=ANzsb>OS>%Vb>qRI&W(#@L`n1c@MGQo>>;QKe;>z}#*-kJXX>oX$=@68YHTJV}> z4qwh>*{Cr1x~xkC6tQkC+gr2iCP!?7Hz?Zd624*jK8T|8w(5{ts)fPn@Fw{BS>bPP zQyX4eRSzO<43wew4dKlM=D_&oweWt?Qg29BZeOlTkeH?h1F4Iw_onDw*6>gOEtH2zXNx5_y**8Qz=Ls502{6Ag)+ zPK8ANEE*@Bm#$Lj*BTP}9y?%RpLlWc<6l!qB*?}eoIu|EW0`@V|xN(&ToR(%ZC1xBrY<@StjYe^EW62_Q%v_du;BgV4dMM8jqkqs5qeLBm!$7Y4NK5sWNI+js)!&? z^j;YohFv#bH-J$Rp0A5O_QLC?wM7_S6R6PK39eTlgWovvk!F z-q|QZ$xDkPg11=w1!=gH!$$DV7C&O0_r?tE)5U{=(Tl|d-upA}jo@Yr-mclWe1C?_ zX~iOj3p2d@R)E)BH80Dqf?&kgY3#W2AcIuT3y;>@#UXf*6Tea3GhuGF9&>qp@}KmIB#qYmEWAm^fhIaCkG9f4 zNE$ShT_cY*ia_CQp2w}4{fKT`HUlZSiCDFvZ;*jkgWpS<*<4;+q#x7_uc23SL>93z zt;RMWx}UsG>HW_XxeD*s-@Ha|Xl*Z3d^hSG7HKqxn)(`c)U=?hxa9^pfOmFlr_ozm zU48iQ;c^4pw^A01X-gSJcWzUvL}zC>*(Z;pvAzpe;Mez!UGM^a`SQivW1R>uOk#a! zvD^CP+nwzkoah%X!K&=iyZ!y)|GIRu|Ni~1k%+cj#FPyX@t5xRk9fG{#*Mf8i@&4? zH{K?AL46#*e;=P;>UvKyu|B}(H(}8>eiI*c_4i-Yea^gnsb8iH&y9mq*frO(D3JN4 zUQ?D`&%XSL1h$D(cC{wD=iA2Kmhc8{w@q(PQ~F#QUbarNKP7=TO2Lbthn_stG(^O| zM)Yz$cvN*6-8X>uZ}#|_>HX$)@V z1>0_~wr#C9A`=!k%tdY^4T`Grj9Iq_+*<{qP~Sz*>E{EQ>|ZaCKv=u>n&-W5evaE?eqP za8U7f98WfqmbJL?$p(fMk=c5A8Dr}oqs>H}GxAec)oQr7E5$8dWV8C#Do@1TVz#Vn zA}ja(+TB?-fgzbu(Q{mrmd`aUGylwdTeEXr9P;T>$uJ%+!&E1xvJTj7J^L}X&t695 zA^)(4`Y{bt8%c-$;um62S~rp^^ZB(ZHs5<@+iSo3Ki2qr%Dm8oZUS#2d|SbLy7Yay zwZ&!vTxlgxZmHf&rF0A+l{CGyZ&r0+c(s={l~E<8GCIZ+4VvHv#_RRRrg-X($v~oa zCP8l%iLKy7QT_=1W*W8G(92X^u2bHJ+DDp$=GWx#a0)WZQ)BVg!YnTuX10-CO<6%h zo2ini$1prJU3AlyQ5Fyz^&LrNHT}n%7eh4Wc?M)b;QGVmJUbzo2|-bW_goH|E-eHy&~$FA?xjIJeljp};}yrZ(n-%SRSsJVTjS|aI3>wpoW!0$oR6ynAm{dQOg;U zAh~XphcH^aB_E-CrCcmV*ll%a9z>wy)`VensT&GynV({TILWRX$(C=ZgG;>AabxV# zu_38w@R*+x`I6l`^s!y+%I)%6DYiDbAqkm$MxL^x?7H$)O>H*Ww^^j!b>tn5T{y(Z*f=kyM>ui@F2E*`>=O zv}G95DTgnO&=CKExKCatW?1;oY3#oRwzw!qpWF<&ZdNI9BgO`)*y0Rk)TSTDC zMT2BF7DML8IaSo_?AGp3gA>vnm?s}*S6(tso_l5ZTEaz|p%G(OF58N-D-ci}MKJeD#@X}k4hPUd3f4Tnf(#ND9ECz2&!RwUw4}teE za{Iy?!}oA{YvN?eKTh?~qL`gE@?tU7CAt*UOQ? zSKXK0w#F`hnb@tWYt1UV8*;j%0BfTeftUKpx2kJ8B@UWQ`cC9F8xeL@ z@8gavz8VB?t$`<&s&1`2wE^(5opAXNa|FE1&6_E6&R@(YfDd4Zpd z_2Q^fB6mq>BLVLfmvUXu2cmO^I^3MEwEV{JUW?sl(dPa_{O$~fH)?*b@VrQhl(a?g zp)EhSl76%deQ#1Qmc`Yuux!3PROe2OBh{(Ox)cNR)Q}fvtxmbXakDE;GyN>GvtPSN z?3N=r#@Fb?SR80~6}t^=P3&qNF7fOfWw#~*#ttl2<^GnmKWkBTG2+$PYPZ^mFLNWJ zqM@uXl!jMI$QZnIbgb34gx{e2wSn-m$Pe43e6|m~^dZ{f2dI~^(w4nJx4dh^`j{IP zLq0P)Jj=!#!TYQ)yvXk%@P<6MU)S`kUZ7XReOpq#wL~M|IK3{#w_02U08Vaw2z5M| zUDBFvgC*BARq9g;kb29iTPYWH2*(<#pWw${;uA(Mnc=;}h z%sY7q6W7Z6&?c#Dbqo8+v)xk%g=9^(CZrj8M+91c-9}`{pP)$Zy3^rjDQrwDhWuJp zt`xnW{W|zPi0hlyl)WbVFWby*NuNjRY+QJC=y>Dp!u$2kA@J(>Zj0|sp4e&Dq9ZFR z$jQFLDJpC8Khlm!)|?@J^1*Kdzms-p5WB6GH1}HJvRGEub?CXpc!+dF2p7C0GP^N$ zU6+4|7M>jrXBP(#sK62239)<3-|av6czM9a5AZi=Jh_^D%kWC++ZSGi?^k4BsC`qZ zP58=MVwW5+B$A(+2GcvlC*JUEg{48I0qBCYrAuewbplxATbW(X?&@0X?d&>mRkHtO zy(~$Z6_+YI+NdAE7i;v>404BrQMlo?80_Xh;g|w=U%h(y z>eY)E@Hb#Q+~^DMn~L5Lz60nbe1AebWd&mTQ{^b~LYGmhTG%Y!Ge@i6@GdQQN*|LpCVUV-l*cm=+{mT8c6fdDvL6p18y z$OElPOUr{x)oQIp!dE~y%c``0POtctKA-<$L;I54(Za?W(uL z7YjVE*Y)C~A~xmY0>0d?ukLzs0l%jn2EG?v!1ue|=(|F@;H!Pq-$!@scLCmqHwKS~ z3f}$ww+t`hI~ZObcaTc2Ekk@FdeC7-X`Eg+N^jK50k>@cys~?gmMttsJ%X;xD6Xvv z-#5fAyl{Lp-lG13358`+PtAes&N9UA6M?W@s0> z@s~Y%g2Q_~G;Z9`9!Pli_g@#@@4ox)?%gBk-8qb2G`>G?$LOuf6g$IY2W{76VMc8FS+Fil! zojAL#7Qh>L7!HE*{^VD^H_ zN$sUvZb^$w zYOT)0J8|19>BYI15WHoDZd(RO-D_fvl<7R`zaDf-s%Lv5>pxR~Wqf`MGhw2fmTN0dVtGZrnn6E2DfDkojq2ZhD$_WP051 zdhl0)6=2QkG(8Wy%WfCux&ecCbXvUp$>xtc+quoraa!;mJ3B0m@_m~d|JB0dC!c?I zX?&6vn0Suxo*bPHj|H){afJ8A>%%Lue0PpP?^nM)4Bx#n#To$&sT^YTVtS|+KV+e( ze)Q__He{By3i~rMFz85TS6)}%A#}0GoyVy~3S#E~nm^Gg51RiI*R6Lqp z-XybgM`KqdpOgUJM_y%k+^{UmSg=gf#DAtahfi3R304irv4;)*bSBRv=DFO`X#dc^qE0x|y5*_LBMaFCVAJ*&0m-FR(cZ_YKo9VAFVOS__70 zrQr@7JRXDB`rre2#lRPF4<|o>T`*x60-sx6+#}e{U+nE{Hk;c&ZeE;Z3mXQ(3+uP6 znX5Oi!j61%^}>a#xE^>xv;d+B}D{E)zQ{!Ri9G&gSOQ^_!wZ&o1|Q@rc@UVpy>8df+i6@J z!Qk*3hJqKVEi`{tC^SD!e0WR2Yo=cUw>VzHcs|Jvm5uuh-tln;W*X5n7*ib41qLt3 zhS3ExnvR8|kTq@xm2Qv0YZ{ZkeO_qpH4Dw=PIL2*pK#a(yu*z9S5M%{Gv+rdxci*m z^aR1nh-H85pJ5qq25-3Yh`3Ppk=FMRdL3TlQ4Nhg^tNN8Q;pF(BskGB${KNW*{L0k zw@kZIYap|$HSv*Ii7H)WFuSFiD3Q2wJa&27OG_8Lr5L;)^Y3CsJk;Se(&zHM-N5ew zyIl|bv~z|I@AQH+y`M06KP>>f&BUw*@AFq;ykz{odYmSBeFpDBA9xLf*B9{aD90QF zO!x=zb_KjMhV{wb9&GiuKmOR<-n@AE@-H@#MjhVAE7mtx@jx@#dBghrj1_~|)ZjJW z0N!ZV9D?^~>pK|UYk-=}#~^s?rMT{ud?|bxM@RsLvDge7E-U99`*WJJA$BX7jzosl8?bEbk{4S>B5m zn=p1BJljLV`z_qUas@aXXdiEW@x`Rcc)V(^qr3srd-LH)axa&>tB4DsOfrFV;`hwAxB8nwilBQMCpEij)RKEGCWd2UWCZg*)Gp{3~>@-g#5{1+l!h0IFA0WIt%?Hiq-jC0o{js?X z)HXMF_I3(|Y#PrFaMUdgKY#h$dd#f)4f7Fvy3-G>m2bZJ2H}0d;I%BVQNvX5!trw$ zygI%6-@(gqc)z;)XXCqF*67vL1FNv>w7}5TBJ{R&dL4O|$BY98uS^KjWTXVV zh2fo8o|?~t@$L4&_wRw_we9@K$cTW~LwM5+UfcV-6x!Truvq7EmV)%N1`iiuVK!BjrRn+&G+7W4~=dDZr(d_D)AnJwbS?v}OIhwQ_sol7bhE@dEQ&#eN8n z@R;z5mydw=Na@=@?y%_`LN6Qqu|}`190a`%bCQN+3Hh2Rvs{a^%Y#{c3O9KEWm#u; zO$D~%%VY5d3wbD`+#qz_7`wIF8cCoyyVCHkiB$Er2Je^O9~>O~iNpKl7K3+^;2oJ? zo|?#HGG4C>lEJUo%Tps$69BIWl?`*doAq~hy$$BdnVAeSkfk{9z1`hjPvHyq?23o* z-WKqlVL~Nsr8&9Af>6JH&y$)rZjqSgwY$3jZ!%R#Ha9n)J=?r|adY!>;qouC$)r7- zwkX=LYtD3k^Yrt7lh5){8!!aHKcTh1B|o$=TUd^9;i4``*&%?|-*$Uca8c{)ekCzxW^jdLxHN z*+(lmbAjT#!s3E*FwEn_``!Nj;qbC)ir2(9M(-~f4|S0`2)6fHOgXd!y;aN_6slEk z4pkQP4u#1DbgAyV;jl}$U4eJT(JgI7*ey}kdtY?Rr9LG)SmMod!ur@<75kH%s>woE zJx4363(E`e0cpLD>9k*?Mg$nxRSlSg%T-5u~ zTcbR(j^w7%plDQiD9Wy?x)C)I#je1z;cQCv$$#7hF zkESAv9V?!alNa-TT<(Q6|S*Y9Pwdr?u5&RIio+klf9np z2VnN57Be6hm~@x`&5&ON)sV?dEMOR_*JJRWYJNJF;Jm8>L_i2@6i zG0XeAv)3*2=}vSD8%_x~llcl*Lyu0`p^ zbOBe=E6Ne8GT&P(vlWXEY3!;D+L~n7>8Kfs7`vSsWe}@bh`I`95x$4ATa|TLE!kk| zN-=JDTOvx*Xo6Wv`OY+h~4AMixOrSi zux{RFXNUZ#-z*fGse+eUzj$%1P`J9D7%L=aQwVP*KzM(3!?Z4Ze$~wV@6SKmFpcS- z@`#wB!OQjmGb=1f@BnzD5wgSJjd^Uxv%Ggh`0f!$^*Fs^lAyJ<<%&jc^*Yh3*a{{$GN@I`w{28QJX;uWmbSeVmkcTmh~$WB)ZT~Ps;g=WSuGN32RDxHnk*~U zGTX7_$;HMDRY$-(EF3n4F2>4M!G8np&|w>2$_D}J1{=JziA7|1835++VyXkeTPOg$ z2F7>OTsWc$L)p*+c<1g*cy~Ri8W7(5Y=-S5hZihg&OW#J)3j+=VzSnU-vmoZfvAK+c1oe z@^glfgDr7~4d4|vmt`qMWrGfHcv9_Xc)yFodn|gxRTlE+Q#}}EDZgyFy<+O=!-quJfkkmRA~kh3P) z?bBVuSu43!F+FTrAL9hum&EQ?YxU*J?2`jK&)^-M&-*@#-@LsHmKSc{eb>%sCZ=iX zb<{Ld&E^(est8lw|KPSeFbliS5Yv2?bBTI@=!Cgyhb{mHrWI%k6SR!>mj_! z(D?A+@p`3l>FKMku)K*x61=rfnimTXHgN(D0L~_7XN@#Q>zL*hP9XekSUB zt!w|~)xr3~gM5aE$`D@6YOphlBO@6*|L!{o?^GDxPZ_-Da?)gJ@SfCVgC}jTfS1ja zps`C^JhkB%@Fou)Co`Glrw62Yv+LR9`o%&MMeoVkr9?87NF@>pu)G0>*ZLgR_#4m; z=Gp7ur-}KzOM~ELvWNMMI=q0>Ulv}4udr&ExJyGzJ<#YScbvb{(G6fj!OO0P*+Kaz z+BS)CIk zP?X8yabAz&yu$J-*>yG(XhYZ@xm0X2xOs%}!9jn*MxJQ{44cT-F)lX?g=E?qeTWX*e;*f?cQ`%MEF=?~ z*<>=C`fxS@x*;(;28P$TFAlH^8~?Zh$H(}uHcTwh_;|&#a`{}&N-KD|V36?gnhx{z z;C=hLfwaRfwYmivc4aNKSkJ6dl-`C;ubNOEDyu7EwN!F22+juA4CRR2#m0+@U6rcX zrmpno*N$RW1Vx-$U%e}okIt?bwaE3C*;tLh%TAPrsHFJVJUDp8{_+|01PlQy5Llm#XmaFXJ$3NPZjsiktM39l`;TBVK(N;yy{U(qXTH-}W( zLrW$}lti4W1fr8!N@lHN<@B9)RcDvk?oR12bTxLx{4|^|+!aF}TvcRhh1iZtkR1@IM9Re?geuJKzVY6)x z$#LODT`uUA@S4nF^98)qbB2-5W|M^ng=BKwPmIkn`5c1RvR3{PXV|V^vdpw~iEo79 zJsCFnA}ii_3-CUBGkEWY>D}H`XG{&=I~`Zgl+@^LD6dVoy+d(yowDqWCxcD|UzwqhT88(M-dT`(JkDL220UMCE% zEaj|~RcfTbWP+EJfC<2ghoe zEH4w2hL~BvjK*v_oqkO4j*=I%@RP-fiIdX|P*Vc~EN>w>E&X#)qDxr-00crwL_t(F zJtZ|SuWJNY(bVR$RBmY?yn$C4pVN$9$Yo>I!_4xE*p*pWPbLB0hmZe&U6~$|!!{$c z*-s^BlUcQjkma>-lH&7!-pJ)-;uz0u5b&BjD^lDrfG3|E90u>99vdz5R^Yw+bN#Ao z<&ba)4>;jF;u2z2rF)C~Qe*;NC4%Ffx>-s)+HSoIgl2aoyB&>A79GHIyry3cXBXv2 zdkv!@TIr}V%Jne2JgcYCsIg{Kt3^kYzMC^MTTmypyuLVP=L^ z5@RvgX&5pk()gR{8Cql#)*|kIB;Xx3CntHKIm>*sCMRbm4Qpm{W+SM00PpBHZNM^3 z%BVd9i_Ulf3wXdTOipsk3+N_4G|h45ust-<^7ib^5N;GUJC4{*%eBD+4PFs!`zW23 zAILO#sO_adu@eqOFqCRUL9oy{xTUL9SWWL)mpy8#2YN3NJMJuq#v5R@cf1 zZ{T;sW0zG3`#$`iL3ptj*zIn&$6vk0ZhN$FP=Q5*?mc`0-q)iSZU+H>;8j?!upqLL z9*o=U9%$Ym`l?zFF5tx%2;iC9uzR3-yMgToegG@GeOr8hUABZ=7~$>qcDuJjtMFk3 zY%3m455J&ZrY~aMP2ms;S>EI5hA7~D;C&O*Tb2E^8NrI)XjwcOU)cjvr?;{LjiR zJ)g0HI0WLKh5(0G(Mc~O^7w$k8-(5z=u0zr4284j;c zyuv$ry+CfC?ShS~=>?Hh1Y$oJyiW!;H+=!F>ubUrMfYvNyM5Pj-7A`dzQ$tys2pm? zrPr+vDbn3?AIVUy6T3>;vP-Z;%xJ)72xP-MdVhGSz}I@>@ossv9JbZNPLlK%`9|70dj=QqLtDpi zg_c=~OS8^^+J-|r;ro^F@Kn=1&wl5{5AhbKk#XZS;El8U>X(^WaFuhLRVu~6S>028o;hLPpq-qYSijA%x0_g zVb@VoyNXw<7lnSdAczv|>ZNOPNO&;|SX>fBd203k>^kb|ZZXMFRJvF<@rt=4 zHA7@Hv{9Gqah23|Su23Qc0>LDf5zqQzXH5p$F7$-7~Ya*a^?S$UQIdtJL7Abx1h?X QIRF3v07*qoM6N<$f<~M13;+NC literal 0 HcmV?d00001 diff --git a/docs/release-notes.md b/docs/release-notes.md index 9445e24..5969b29 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,3 +1,25 @@ +# 2014 01 12 + +## Important +The ScriptCraft.jar file has been renamed scriptcraft.jar (see bug fix +details below). This means that you will have to remove the existing +`plugins/ScriptCraft.jar` file if present. + +Bug Fix: On Mac OS, the plugins/scriptcraft directory is copied to +plugins/ScriptCraftPlugin the 2nd time ScriptCraftPlugin is loaded. +This has been fixed by changing the plugin name from ScriptCraftPlugin +to scriptcraft. The jar file has also been rename from +ScriptCraft.jar to scriptcraft.jar. + +New command: `jsp spawn` lets in-game operators spawn any type of +entity. For example `/jsp spawn cow` will spawn a cow at the in-game +operator's current location. + +New minigame: Cow Clicker. A simple demonstration of using Bukkit's +Scoreboard API. Players click cows to score points. Scores are +displayed in a side bar on screen. Players join or leave the game by +typing `/jsp cowclicker` at the in-game prompt. + # 2014 01 05 Bug Fix: On Mac OS, alias plugin caused Exceptions due to missing diff --git a/src/docs/javascript/generateTOC.js b/src/docs/javascript/generateTOC.js index 5724472..f2fe8f8 100644 --- a/src/docs/javascript/generateTOC.js +++ b/src/docs/javascript/generateTOC.js @@ -1,5 +1,12 @@ args = args.slice(1); +// wph 20140105 trim not availabe in String on Mac OS. +if (typeof String.prototype.trim == 'undefined'){ + String.prototype.trim = function(){ + return this.replace(/^\s+|\s+$/g,''); + }; +} + var template = args[0]; var BufferedReader = java.io.BufferedReader; diff --git a/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java b/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java index bbdfae3..6d6b16e 100644 --- a/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java +++ b/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java @@ -29,7 +29,7 @@ public class ScriptCraftPlugin extends JavaPlugin implements Listener /** * Unzips bundled javascript code. */ - private void unzipJS() + private void unzipJS() throws IOException { // // does the js-plugins directory exist? @@ -37,9 +37,13 @@ public class ScriptCraftPlugin extends JavaPlugin implements Listener File jsPlugins = new File(JS_PLUGINS_DIR); if (!jsPlugins.exists()) { - getLogger().finest("Directory " + JS_PLUGINS_DIR + " does not exist."); - getLogger().finest("Initializing " + JS_PLUGINS_DIR + " directory with contents from plugin archive."); - jsPlugins.mkdir(); + getLogger().info("Directory " + jsPlugins.getCanonicalPath() + " does not exist."); + getLogger().info("Initializing " + jsPlugins.getCanonicalPath() + " directory with contents from plugin archive."); + try{ + jsPlugins.mkdirs(); + }catch(Exception e){ + throw new RuntimeException("Failed to create directory " + jsPlugins.getCanonicalPath() + ":" + e.getMessage()); + } } ZipInputStream zis = new ZipInputStream(getResource(JS_PLUGINS_ZIP)); @@ -48,7 +52,7 @@ public class ScriptCraftPlugin extends JavaPlugin implements Listener while ( ( entry = zis.getNextEntry() ) != null) { String filename = entry.getName(); - //File newFile = new File(jsPlugins.getName() + File.separator + filename); + File newFile = new File(jsPlugins, filename); //create all non exists folders @@ -59,17 +63,23 @@ public class ScriptCraftPlugin extends JavaPlugin implements Listener // // only write out to file if zip entry is newer than file // + String reason = null; long zTime = entry.getTime(); boolean unzip = false; - if (!newFile.exists()) + if (!newFile.exists()){ + reason = "NE"; unzip = true; + } else{ long fTime = newFile.lastModified(); - if (zTime > fTime) + if (zTime > fTime){ + reason = "" + new Long((zTime-fTime)/3600000) + "h"; unzip = true; + } + } if (unzip){ - getLogger().info("Unzipping " + newFile.getCanonicalPath()); + getLogger().info("Unzipping " + newFile.getCanonicalPath() + " (" + reason + ")" ); FileOutputStream fout = new FileOutputStream(newFile); for (int c = zis.read(); c != -1; c = zis.read()) { fout.write(c); @@ -90,9 +100,9 @@ public class ScriptCraftPlugin extends JavaPlugin implements Listener @Override public void onEnable() { - unzipJS(); FileReader reader = null; try{ + unzipJS(); ScriptEngineManager factory = new ScriptEngineManager(); File bootScript = new File(JS_PLUGINS_DIR + "/lib/scriptcraft.js"); this.engine = factory.getEngineByName("JavaScript"); @@ -103,6 +113,7 @@ public class ScriptCraftPlugin extends JavaPlugin implements Listener }catch(Exception e){ e.printStackTrace(); + this.getLogger().severe(e.getMessage()); }finally { if (reader != null){ try { diff --git a/src/main/javascript/lib/js-patch.js b/src/main/javascript/lib/js-patch.js new file mode 100644 index 0000000..4403bbc --- /dev/null +++ b/src/main/javascript/lib/js-patch.js @@ -0,0 +1,33 @@ + +module.exports = function($){ + + // wph 20140105 trim not availabe in String on Mac OS. + if (typeof String.prototype.trim == 'undefined'){ + String.prototype.trim = function(){ + return this.replace(/^\s+|\s+$/g,''); + }; + } + + $.setTimeout = function( callback, delayInMillis){ + /* + javascript programmers familiar with setTimeout know that it expects + a delay in milliseconds. However, bukkit's scheduler expects a delay in ticks + (where 1 tick = 1/20th second) + */ + var bukkitTask = server.scheduler.runTaskLater(__plugin, callback, delayInMillis/50); + return bukkitTask; + }; + $.clearTimeout = function(bukkitTask){ + bukkitTask.cancel(); + }; + + $.setInterval = function(callback, intervalInMillis){ + var delay = intervalInMillis/ 50; + var bukkitTask = server.scheduler.runTaskTimer(__plugin, callback, delay, delay); + return bukkitTask; + }; + $.clearInterval = function(bukkitTask){ + bukkitTask.cancel(); + }; +}; + diff --git a/src/main/javascript/lib/persistence.js b/src/main/javascript/lib/persistence.js new file mode 100644 index 0000000..52614e1 --- /dev/null +++ b/src/main/javascript/lib/persistence.js @@ -0,0 +1,37 @@ + +var _dataDir = null; +var _persistentData = {}; + +module.exports = function( rootDir, $ ){ + + _dataDir = new java.io.File( rootDir, 'data'); + + $.persist = function(name, data, write){ + var i, dataFromFile; + if (typeof data == 'undefined') + data = {}; + if (typeof write == 'undefined') + write = false; + if (!write){ + dataFromFile = $.load(_dataDir.canonicalPath + '/' + name + '-store.json'); + if (dataFromFile){ + for (i in dataFromFile){ + data[i] = dataFromFile[i]; + } + } + }else{ + // flush data to file + $.save(data, _dataDir.canonicalPath + '/' + name + '-store.json'); + } + _persistentData[name] = data; + return data; + }; + + $.addUnloadHandler(function(){ + for (var name in _persistentData){ + var data = _persistentData[name]; + $.save(data, _dataDir.canonicalPath + '/' + name + '-store.json'); + } + }); +}; + diff --git a/src/main/javascript/lib/plugin.js b/src/main/javascript/lib/plugin.js index 0b8cb47..d41a0ef 100644 --- a/src/main/javascript/lib/plugin.js +++ b/src/main/javascript/lib/plugin.js @@ -3,23 +3,6 @@ var console = require('./console'); var File = java.io.File; var FileWriter = java.io.FileWriter; var PrintWriter = java.io.PrintWriter; - -/* - Save a javascript object to a file (saves using JSON notation) -*/ -var _save = function(object, filename){ - var objectToStr = null; - try{ - objectToStr = JSON.stringify(object,null,2); - }catch(e){ - print("ERROR: " + e.getMessage() + " while saving " + filename); - return; - } - var f = (filename instanceof File) ? filename : new File(filename); - var out = new PrintWriter(new FileWriter(f)); - out.println( objectToStr ); - out.close(); -}; /* plugin management */ @@ -34,25 +17,18 @@ var _plugin = function(/* String */ moduleName, /* Object */ moduleObject, isPer return _plugins[moduleName].module; var pluginData = {persistent: isPersistent, module: moduleObject}; - moduleObject.store = moduleObject.store || {}; + if (typeof moduleObject.store == 'undefined') + moduleObject.store = {}; + _plugins[moduleName] = pluginData; if (isPersistent){ - if (!moduleObject.store){ - moduleObject.store = {}; - } - var loadedStore = load(dataDir.canonicalPath + "/" + moduleName + "-store.json"); - if (loadedStore){ - for (var i in loadedStore){ - moduleObject.store[i] = loadedStore[i]; - } - } + moduleObject.store = persist(moduleName, moduleObject.store); } return moduleObject; }; exports.plugin = _plugin; -exports.save = _save; var scriptCraftDir = null; var pluginDir = null; @@ -65,7 +41,7 @@ exports.autoload = function(dir) { dataDir = new File(dir, "data"); var _canonize = function(file){ - return "" + file.getCanonicalPath().replaceAll("\\\\","/"); + return '' + file.canonicalPath.replaceAll("\\\\","/"); }; /* recursively walk the given directory and return a list of all .js files @@ -78,9 +54,7 @@ exports.autoload = function(dir) { if (file.isDirectory()){ _listSourceFiles(store,file); }else{ - if ((file.getCanonicalPath().endsWith(".js") - || file.getCanonicalPath().endsWith(".coffee")) - ) { + if ( file.canonicalPath.endsWith('.js') ){ store.push(file); } } @@ -97,11 +71,11 @@ exports.autoload = function(dir) { var len = sourceFiles.length; if (config.verbose) - console.info(len + " scriptcraft plugins found."); + console.info(len + ' scriptcraft plugins found.'); for (var i = 0;i < len; i++){ var pluginPath = _canonize(sourceFiles[i]); if (config.verbose) - console.info("Loading plugin: " + pluginPath); + console.info('Loading plugin: ' + pluginPath); var module = {}; try { module = require(pluginPath); @@ -119,13 +93,3 @@ exports.autoload = function(dir) { _reload(pluginDir); }; -addUnloadHandler(function(){ - // - // save all plugins which have persistent data - // - for (var moduleName in _plugins){ - var pluginData = _plugins[moduleName]; - if (pluginData.persistent) - _save(pluginData.module.store, dataDir.canonicalPath + "/" + moduleName + "-store.json"); - } -}); diff --git a/src/main/javascript/lib/require.js b/src/main/javascript/lib/require.js index 5bce81e..30923cb 100644 --- a/src/main/javascript/lib/require.js +++ b/src/main/javascript/lib/require.js @@ -187,11 +187,12 @@ When resolving module names to file paths, ScriptCraft uses the following rules. { var file = resolveModuleToFile(path, parentFile); if (!file){ - var errMsg = java.lang.String - .format("require() failed to find matching file for module '%s' " + - "while searching directory '%s' and paths %s.", - [path, parentFile.canonicalPath, JSON.stringify(modulePaths)]); - console.warn(errMsg); + var errMsg = '' + java.lang.String.format("require() failed to find matching file for module '%s' " + + "in working directory '%s' ", [path, parentFile.canonicalPath]); + if (! ( (''+path).match(/^\./) )){ + errMsg = errMsg + ' and not found in paths ' + JSON.stringify(modulePaths); + } + logger.warning(errMsg); throw new Error(errMsg); } var canonizedFilename = _canonize(file); @@ -242,7 +243,7 @@ When resolving module names to file paths, ScriptCraft uses the following rules. .apply(moduleInfo.exports, /* this */ parameters); } catch (e){ - console.error("Error:" + e + " while executing module " + canonizedFilename); + logger.severe('Error:' + e + ' while executing module ' + canonizedFilename); throw e; } if (verbose) diff --git a/src/main/javascript/lib/scriptcraft.js b/src/main/javascript/lib/scriptcraft.js index 6e7e854..6fa7e45 100644 --- a/src/main/javascript/lib/scriptcraft.js +++ b/src/main/javascript/lib/scriptcraft.js @@ -422,7 +422,9 @@ function __onEnable (__engine, __plugin, __script) return ; var File = java.io.File ,FileReader = java.io.FileReader - ,BufferedReader = java.io.BufferedReader; + ,BufferedReader = java.io.BufferedReader + ,PrintWriter = java.io.PrintWriter + ,FileWriter = java.io.FileWriter; var _canonize = function(file){ return "" + file.getCanonicalPath().replaceAll("\\\\","/"); @@ -432,6 +434,23 @@ function __onEnable (__engine, __plugin, __script) var jsPluginsRootDir = libDir.parentFile; // scriptcraft var jsPluginsRootDirName = _canonize(jsPluginsRootDir); + /* + Save a javascript object to a file (saves using JSON notation) + */ + var _save = function(object, filename){ + var objectToStr = null; + try{ + objectToStr = JSON.stringify(object,null,2); + }catch(e){ + print("ERROR: " + e.getMessage() + " while saving " + filename); + return; + } + var f = (filename instanceof File) ? filename : new File(filename); + var out = new PrintWriter(new FileWriter(f)); + out.println( objectToStr ); + out.close(); + }; + var _loaded = {}; /* Load the contents of the file and evaluate as javascript @@ -487,7 +506,7 @@ function __onEnable (__engine, __plugin, __script) /* now that load is defined, use it to load a global config object */ - var config = _load(new File(jsPluginsRootDir, "data/global-config.json" )); + var config = _load(new File(jsPluginsRootDir, 'data/global-config.json' )); if (!config) config = {verbose: false}; global.config = config; @@ -510,27 +529,6 @@ function __onEnable (__engine, __plugin, __script) } }; - global.setTimeout = function( callback, delayInMillis){ - /* - javascript programmers familiar with setTimeout know that it expects - a delay in milliseconds. However, bukkit's scheduler expects a delay in ticks - (where 1 tick = 1/20th second) - */ - var bukkitTask = server.scheduler.runTaskLater(__plugin, callback, delayInMillis/50); - return bukkitTask; - }; - global.clearTimeout = function(bukkitTask){ - bukkitTask.cancel(); - }; - - global.setInterval = function(callback, intervalInMillis){ - var delay = intervalInMillis/ 50; - var bukkitTask = server.scheduler.runTaskTimer(__plugin, callback, delay, delay); - return bukkitTask; - }; - global.clearInterval = function(bukkitTask){ - bukkitTask.cancel(); - }; global.refresh = function(){ __plugin.pluginLoader.disablePlugin(__plugin); __plugin.pluginLoader.enablePlugin(__plugin); @@ -547,6 +545,7 @@ function __onEnable (__engine, __plugin, __script) global.echo = _echo; global.alert = _echo; global.load = _load; + global.save = _save; global.addUnloadHandler = _addUnloadHandler; @@ -556,21 +555,30 @@ function __onEnable (__engine, __plugin, __script) */ var modulePaths = [jsPluginsRootDirName + '/lib/', jsPluginsRootDirName + '/modules/']; - global.require = fnRequire(__plugin.logger, __engine, config.verbose, jsPluginsRootDirName, modulePaths); + global.require = fnRequire(__plugin.logger, + __engine, + config.verbose, + jsPluginsRootDirName, + modulePaths); + require('js-patch')(global); global.console = require('console'); + /* + setup persistence + */ + require('persistence')(jsPluginsRootDir,global); + global.command = require('command').command; var plugins = require('plugin'); global.__onTabComplete = require('tabcomplete'); global.plugin = plugins.plugin; - global.save = plugins.save; var events = require('events'); events.on('server.PluginDisableEvent',function(l,e){ // save config - plugins.save(global.config, new File(jsPluginsRootDir, "data/global-config.json" )); + save(global.config, new File(jsPluginsRootDir, "data/global-config.json" )); _runUnloadHandlers(); org.bukkit.event.HandlerList["unregisterAll(org.bukkit.plugin.Plugin)"](__plugin); @@ -625,10 +633,12 @@ function __onEnable (__engine, __plugin, __script) for (var i = 0; i < legacyDirs.length; i++){ if (legacyDirs[i].exists() && legacyDirs[i].isDirectory()){ legacyExists = true; - console.warn('Legacy ScriptCraft directory ' + legacyDirs[i].canonicalPath + ' was found. This directory is no longer used.'); + 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 ' + __plugin + ' is ' + jsPluginsRootDir.canonicalPath); + console.info('Please note that the working directory for %s is %s', + __plugin, jsPluginsRootDir.canonicalPath); } } diff --git a/src/main/javascript/modules/utils/string-exts.js b/src/main/javascript/modules/utils/string-exts.js index 1e7159b..bb33e23 100644 --- a/src/main/javascript/modules/utils/string-exts.js +++ b/src/main/javascript/modules/utils/string-exts.js @@ -77,9 +77,3 @@ for (var method in formattingCodes){ return function(){return c+this;}; }(formattingCodes[method]); } -// wph 20140105 trim not availabe in String on Mac OS. -if (!String.prototype.trim){ - String.prototype.trim = function(){ - return this.replace(/^\s+|\s+$/g,''); - } -} diff --git a/src/main/javascript/plugins/classroom/classroom.js b/src/main/javascript/plugins/classroom/classroom.js index 2822b27..d674f96 100644 --- a/src/main/javascript/plugins/classroom/classroom.js +++ b/src/main/javascript/plugins/classroom/classroom.js @@ -47,6 +47,7 @@ var classroom = plugin("classroom", { allowScripting: function (/* boolean: true or false */ canScript, sender) { if (typeof sender == 'undefined'){ console.log("Attempt to set classroom scripting without credentials"); + console.log("classroom.allowScripting(boolean, sender)"); return; } if (!sender.op){ diff --git a/src/main/javascript/plugins/minigames/NumberGuess.js b/src/main/javascript/plugins/minigames/NumberGuess.js index 01424a4..a3ae132 100644 --- a/src/main/javascript/plugins/minigames/NumberGuess.js +++ b/src/main/javascript/plugins/minigames/NumberGuess.js @@ -15,8 +15,20 @@ Once the game begins, guess a number by typing the `/` character followed by a number between 1 and 10. ***/ + +var sb = function(cmd){ + org.bukkit.Bukkit.dispatchCommand(server.consoleSender, 'scoreboard ' + cmd) +}; + exports.Game_NumberGuess = { start: function(sender) { + + var guesses = 0; + + sb('objectives add NumberGuess dummy Guesses'); + sb('players set ' + sender.name + ' NumberGuess ' + guesses); + sb('objectives setdisplay sidebar NumberGuess'); + importPackage(org.bukkit.conversations); var number = Math.ceil(Math.random() * 10); @@ -38,6 +50,7 @@ exports.Game_NumberGuess = { if (s == number){ setTimeout(function(){ ctx.forWhom.sendRawMessage("You guessed Correct!"); + sb('objectives remove NumberGuess'); },100); return null; }else{ @@ -45,6 +58,9 @@ exports.Game_NumberGuess = { ctx.setSessionData("hint","too low\n"); if (s > number) ctx.setSessionData("hint","too high\n"); + guesses++; + sb('players set ' + sender.name + ' NumberGuess ' + guesses); + return prompt; } }, diff --git a/src/main/javascript/plugins/minigames/cow-clicker.js b/src/main/javascript/plugins/minigames/cow-clicker.js new file mode 100644 index 0000000..4abb5ef --- /dev/null +++ b/src/main/javascript/plugins/minigames/cow-clicker.js @@ -0,0 +1,225 @@ +/************************************************************************* +## Cow Clicker Mini-Game + +### How to Play + +At the in-game prompt type `jsp cowclicker` to start or stop +playing. Right-Click on Cows to score points. No points for killing +cows (hint: use the same keyboard keys you'd use for opening doors). + +Every time you click a cow your score increases by 1 point. Your score +is displayed in a side-bar along the right edge of of the screen. + +![cow clicker](img/cowclicker.png) + +### Rules + + * You can join and leave the Cow Clicker game at any time by typing + `/jsp cowclicker` at the in-game prompt. + + * Once you leave the game, your score is reset to zero. + + * You can disconnect from the server and your score will be saved for + the next time you join. + +### Gameplay Mechanics + +This is meant as a trivially simple use of the [Bukkit Scoreboard +API][bukscore]. There are many things you'll want to consider when constructing +your own mini-game... + + * Is the game itself a long-lived game - that is - should players and + scores be persisted (stored) between server restarts? + + * What should happen when a player quits the server - should this also be + understood as quitting the mini-game? + + * What should happen when a player who was previously playing the + mini-game, joins the server - should they automatically resume the + mini-game? + +[bukscore]: http://jd.bukkit.org/beta/apidocs/org/bukkit/scoreboard/package-summary.html + +***/ + +var store = {}; + +var scoreboardConfig = { + cowclicker: {SIDEBAR: 'Cows Clicked'} +}; + +/* + The scoreboard is a simple wrapper around the Bukkit Scoreboard API. + It's only concerned with display of scores, not maintaining them - that's the game's job. +*/ +var scoreboard = (function(options){ + var temp = {}; + var ccScoreboard; + var DisplaySlot = org.bukkit.scoreboard.DisplaySlot; + + return { + start: function(){ + var objective, slot; + ccScoreboard = server.scoreboardManager.getNewScoreboard(); + for (objective in options){ + var ccObj = ccScoreboard.registerNewObjective(objective,'dummy'); + for (slot in options[objective]){ + ccObj.displaySlot = DisplaySlot[slot]; + ccObj.displayName = options[objective][slot]; + } + } + }, + stop: function(){ + var objective, slot; + for (objective in options){ + ccScoreboard.getObjective(objective).unregister(); + for (slot in options[objective]){ + ccScoreboard.clearSlot(DisplaySlot[slot]); + } + } + }, + update: function(objective,player,score){ + if (player.scoreboard && player.scoreboard != ccScoreboard) + { + temp[player.name] = player.scoreboard; + player.scoreboard = ccScoreboard; + } + ccScoreboard.getObjective(objective).getScore(player).score = score; + }, + restore: function(player){ + // offlineplayers don't have a scoreboard + if (player.scoreboard) + player.scoreboard = temp[player.name]; + } + }; +}(scoreboardConfig)); + +var _onPlayerInteract = function(listener, event){ + var player = event.player; + var Bukkit = org.bukkit.Bukkit; + + if (!store[player.name]) + return; + + var clickedEntity = event.rightClicked; + var loc = clickedEntity.location; + var sound = function(snd,vol,pitch){ + loc.world.playSound(loc,snd,vol,pitch); + }; + if (clickedEntity instanceof org.bukkit.entity.Cow) + { + store[player.name].score++; + scoreboard.update('cowclicker', player, store[player.name].score); + + Bukkit.dispatchCommand(player, 'me clicked a cow!'); + sound(org.bukkit.Sound.CLICK,1,1); + setTimeout(function(){ + sound(org.bukkit.Sound.COW_HURT,10,0.85) + },200); + } +}; +var _onPlayerQuit = function(listener, event){ + _removePlayer(event.player) +}; +var _onPlayerJoin = function(listener, event){ + var gamePlayer = store[event.player.name]; + if (gamePlayer) + _addPlayer(event.player, gamePlayer.score); +}; + +var _startGame = function(){ + if (config.verbose) + console.log('Staring game: Cow Clicker'); + + events.on('player.PlayerQuitEvent', _onPlayerQuit); + events.on('player.PlayerJoinEvent', _onPlayerJoin); + events.on('player.PlayerInteractEntityEvent', _onPlayerInteract); + + scoreboard.start(); + + store = persist('cowclicker',store); + for (var p in store){ + var player = server.getPlayer(p); + if (player){ + /* + only add online players + */ + var score = store[p].score; + _addPlayer(player, score); + } + } +}; +var _addPlayer = function(player,score){ + if (config.verbose) + console.log('Adding player %s to Cow Clicker game',player); + + if (typeof score == 'undefined') + score = 0; + store[player.name] = {score: score}; + scoreboard.update('cowclicker', player,store[player.name].score); + + player.sendMessage('Go forth and click some cows!'); +}; + +var _removePlayer = function(player,notify){ + + if (player instanceof org.bukkit.OfflinePlayer && player.player) + player = player.player; + + if (!store[player.name]) + return; + if (config.verbose) + console.log('Removing player %s from Cow Clicker', player); + + var playerScore = store[player.name].score; + + scoreboard.restore(player); + + delete store[player.name]; + if (notify && player){ + player.sendMessage('You clicked ' + playerScore + ' cows! ' + + 'You must be tired after all that clicking.'); + } +}; +var _removeAllPlayers = function(notify){ + if (typeof notify == 'undefined') + notify = false; + for (var p in store){ + var player = server.getOfflinePlayer(p); + if (player) + _removePlayer(player,notify); + delete store[p]; + } +} +var _stopGame = function(removePlayers){ + if (typeof removePlayers == 'undefined') + removePlayers = true; + if (config.verbose) + console.log('Stopping game: Cow Clicker'); + scoreboard.stop(); + if (!removePlayers) + return; + _removeAllPlayers(false); + persist('cowclicker',store.pers,'w'); + +}; +/* + start the game automatically when this module is loaded. +*/ +_startGame(); +/* + players can join and leave the game by typing `jsp cowclicker` +*/ +command('cowclicker', function(params, sender){ + if (!store[sender.name]) + _addPlayer(sender); + else + _removePlayer(sender); +}); +/* + stop the game when ScriptCraft is unloaded. +*/ +addUnloadHandler(function(){ + _stopGame(false); +}); + diff --git a/src/main/javascript/plugins/spawn.js b/src/main/javascript/plugins/spawn.js new file mode 100644 index 0000000..975de56 --- /dev/null +++ b/src/main/javascript/plugins/spawn.js @@ -0,0 +1,35 @@ +/************************************************************************* +## Spawn Plugin + +Allows in-game operators to easily spawn creatures at current location. + +### Usage + + /jsp spawn cow + /jsp spawn sheep + /jsp spawn wolf + +This command supports TAB completion so to see a list of possible +entitities, type `/jsp spawn ' at the in-game command prompt, then +press TAB. Visit + +for a list of possible entities (creatures) which can be spawned. + +***/ +var entities = []; +var Types = org.bukkit.entity.EntityType +for (var t in Types){ + if (Types[t] && Types[t].ordinal){ + entities.push(t); + } +} +command('spawn', function(parameters, sender){ + if (!sender.op){ + sender.sendMessage('Only operators can perform this command'); + return; + } + var location = sender.location; + var world = location.world; + var type = ('' + parameters[0]).toUpperCase(); + world.spawnEntity(location, org.bukkit.entity.EntityType[type]); +},entities); diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index c856198..55e6fd2 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,4 +1,4 @@ -name: ScriptCraftPlugin +name: scriptcraft main: net.walterhiggins.scriptcraft.ScriptCraftPlugin version: [[version]] commands: From 7f1e5e637e9d4e9ae798963ac00e4bd6556a0fe5 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sun, 12 Jan 2014 12:06:30 +0000 Subject: [PATCH 094/456] updated young persons template and refactored minigame scoreboard. --- .gitignore | 7 +++ docs/API-Reference.md | 9 ++-- src/docs/templates/ypgpm.mdt | 6 +-- .../modules/minigames/scoreboard.js | 45 +++++++++++++++++ .../plugins/minigames/cow-clicker.js | 50 +------------------ 5 files changed, 63 insertions(+), 54 deletions(-) create mode 100644 src/main/javascript/modules/minigames/scoreboard.js diff --git a/.gitignore b/.gitignore index d63443c..663489b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,14 @@ target *.class *.js~ *.js# +*.md# +*.md~ +*.mdt~ +*.mdt# *.iml +*.#* *.idea .DS_Store build.local.properties +/src/main/javascript/lib/.#tabcomplete.js +/src/main/javascript/plugins/.#example-1.js diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 4caef0f..39a4715 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -2356,7 +2356,11 @@ Allows in-game operators to easily spawn creatures at current location. /jsp spawn sheep /jsp spawn wolf -See for a list of possible entities (creatures) which can be spawned. +This command supports TAB completion so to see a list of possible +entitities, type `/jsp spawn ' at the in-game command prompt, then +press TAB. Visit + +for a list of possible entities (creatures) which can be spawned. ## alias Plugin @@ -2659,8 +2663,7 @@ is displayed in a side-bar along the right edge of of the screen. * Once you leave the game, your score is reset to zero. - * You can disconnect from the server and your score will be saved for - the next time you join. + * When you disconnect from the server, your score will be reset to zero. ### Gameplay Mechanics diff --git a/src/docs/templates/ypgpm.mdt b/src/docs/templates/ypgpm.mdt index bc04520..46a04f8 100644 --- a/src/docs/templates/ypgpm.mdt +++ b/src/docs/templates/ypgpm.mdt @@ -36,7 +36,9 @@ Install ScriptCraft on your computer... 3. [Download the latest version of the ScriptCraft Mod][sc-plugin]. Then copy the ScriptCraft.jar file to the `craftbukkit/plugins` folder (This folder won't be created until you run Bukkit for the first time (see previous step). -4. In the CraftBukkit command window type `op {your_username}` and hit +4. Start up the craftbukkit server again (see [instructions for starting the server][bii]). + +5. In the CraftBukkit command window type `op {your_username}` and hit enter, replacing {your_username} with your own minecraft username. This will give you `operator` access meaning you can perform more commands than are normally available in Minecraft. You should @@ -44,8 +46,6 @@ Install ScriptCraft on your computer... for the server) permanently by editing the craftbukkit/ops.txt file and adding your username (one username per line). -5. Start up the craftbukkit server again (see [instructions for starting the server][bii]). - 6. In the CraftBukkit command window type `js 1 + 1` and hit enter. You should see `> 2` . ... Congratulations! You just installed your own Minecraft Server with diff --git a/src/main/javascript/modules/minigames/scoreboard.js b/src/main/javascript/modules/minigames/scoreboard.js new file mode 100644 index 0000000..20d8628 --- /dev/null +++ b/src/main/javascript/modules/minigames/scoreboard.js @@ -0,0 +1,45 @@ +/* + The scoreboard is a simple wrapper around the Bukkit Scoreboard API. + It's only concerned with display of scores, not maintaining them - that's the game's job. +*/ +module.exports = function(options){ + var temp = {}; + var ccScoreboard; + var DisplaySlot = org.bukkit.scoreboard.DisplaySlot; + + return { + start: function(){ + var objective, slot; + ccScoreboard = server.scoreboardManager.getNewScoreboard(); + for (objective in options){ + var ccObj = ccScoreboard.registerNewObjective(objective,'dummy'); + for (slot in options[objective]){ + ccObj.displaySlot = DisplaySlot[slot]; + ccObj.displayName = options[objective][slot]; + } + } + }, + stop: function(){ + var objective, slot; + for (objective in options){ + ccScoreboard.getObjective(objective).unregister(); + for (slot in options[objective]){ + ccScoreboard.clearSlot(DisplaySlot[slot]); + } + } + }, + update: function(objective,player,score){ + if (player.scoreboard && player.scoreboard != ccScoreboard) + { + temp[player.name] = player.scoreboard; + player.scoreboard = ccScoreboard; + } + ccScoreboard.getObjective(objective).getScore(player).score = score; + }, + restore: function(player){ + // offlineplayers don't have a scoreboard + if (player.scoreboard) + player.scoreboard = temp[player.name]; + } + }; +}; diff --git a/src/main/javascript/plugins/minigames/cow-clicker.js b/src/main/javascript/plugins/minigames/cow-clicker.js index 4abb5ef..16615ae 100644 --- a/src/main/javascript/plugins/minigames/cow-clicker.js +++ b/src/main/javascript/plugins/minigames/cow-clicker.js @@ -19,8 +19,7 @@ is displayed in a side-bar along the right edge of of the screen. * Once you leave the game, your score is reset to zero. - * You can disconnect from the server and your score will be saved for - the next time you join. + * When you disconnect from the server, your score will be reset to zero. ### Gameplay Mechanics @@ -47,52 +46,7 @@ var store = {}; var scoreboardConfig = { cowclicker: {SIDEBAR: 'Cows Clicked'} }; - -/* - The scoreboard is a simple wrapper around the Bukkit Scoreboard API. - It's only concerned with display of scores, not maintaining them - that's the game's job. -*/ -var scoreboard = (function(options){ - var temp = {}; - var ccScoreboard; - var DisplaySlot = org.bukkit.scoreboard.DisplaySlot; - - return { - start: function(){ - var objective, slot; - ccScoreboard = server.scoreboardManager.getNewScoreboard(); - for (objective in options){ - var ccObj = ccScoreboard.registerNewObjective(objective,'dummy'); - for (slot in options[objective]){ - ccObj.displaySlot = DisplaySlot[slot]; - ccObj.displayName = options[objective][slot]; - } - } - }, - stop: function(){ - var objective, slot; - for (objective in options){ - ccScoreboard.getObjective(objective).unregister(); - for (slot in options[objective]){ - ccScoreboard.clearSlot(DisplaySlot[slot]); - } - } - }, - update: function(objective,player,score){ - if (player.scoreboard && player.scoreboard != ccScoreboard) - { - temp[player.name] = player.scoreboard; - player.scoreboard = ccScoreboard; - } - ccScoreboard.getObjective(objective).getScore(player).score = score; - }, - restore: function(player){ - // offlineplayers don't have a scoreboard - if (player.scoreboard) - player.scoreboard = temp[player.name]; - } - }; -}(scoreboardConfig)); +var scoreboard = require('minigames/scoreboard')(scoreboardConfig); var _onPlayerInteract = function(listener, event){ var player = event.player; From 2e9ce317135cd36e1336bff6a4a5a5716d691372 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sun, 12 Jan 2014 16:29:28 +0000 Subject: [PATCH 095/456] Adding MIT License --- license.txt | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 license.txt diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..3f3b290 --- /dev/null +++ b/license.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Walter Higgins + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 7cb679cfd151ada8e9cbce193eb5fc24ac2f095e Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Mon, 13 Jan 2014 21:06:17 +0000 Subject: [PATCH 096/456] Fix issue #112 (Support for Nashorn in Java8) --- docs/API-Reference.md | 26 ++++++------ docs/release-notes.md | 5 +++ src/main/javascript/lib/events.js | 6 +-- src/main/javascript/lib/persistence.js | 6 +-- src/main/javascript/lib/plugin.js | 1 - src/main/javascript/lib/require.js | 2 +- src/main/javascript/lib/scriptcraft.js | 59 ++++++++++++-------------- src/main/javascript/lib/tabcomplete.js | 2 + 8 files changed, 51 insertions(+), 56 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 39a4715..42263b9 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -25,8 +25,8 @@ Walter Higgins * [Global functions](#global-functions) * [echo function](#echo-function) * [require() function](#require-function) - * [load() function](#load-function) - * [save() function](#save-function) + * [scload() function](#scload-function) + * [scsave() function](#scsave-function) * [plugin() function](#plugin-function) * [command() function](#command-function) * [setTimeout() function](#settimeout-function) @@ -348,11 +348,11 @@ to load the named module. require() will return the loaded module's exports. -### load() function +### scload() function #### No longer recommended for use by Plugin/Module developers (deprecated) -load() should only be used to load .json data. +scload() should only be used to load .json data. #### Parameters @@ -361,13 +361,13 @@ load() should only be used to load .json data. #### Returns -load() will return the result of the last statement evaluated in the file. +scload() will return the result of the last statement evaluated in the file. #### Example - load("myFile.js"); // loads a javascript file and evaluates it. + scload("myFile.js"); // loads a javascript file and evaluates it. - var myData = load("myData.json"); // loads a javascript file and evaluates it - eval'd contents are returned. + var myData = scload("myData.json"); // loads a javascript file and evaluates it - eval'd contents are returned. ##### myData.json contents... @@ -380,15 +380,15 @@ load() will return the result of the last statement evaluated in the file. } } -### save() function +### scsave() function -The save() function saves an in-memory javascript object to a -specified file. Under the hood, save() uses JSON (specifically +The scsave() function saves an in-memory javascript object to a +specified file. Under the hood, scsave() uses JSON (specifically 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 `save()` function can later be -restored using the `load()` function. +in-memory object saved using the `scsave()` function can later be +restored using the `scload()` function. #### Parameters @@ -400,7 +400,7 @@ restored using the `load()` function. var myObject = { name: 'John Doe', aliases: ['John Ray', 'John Mee'], date_of_birth: '1982/01/31' }; - save(myObject, 'johndoe.json'); + scsave(myObject, 'johndoe.json'); ##### johndoe.json contents... diff --git a/docs/release-notes.md b/docs/release-notes.md index 5969b29..1232275 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,3 +1,8 @@ +# 2014 01 13 + +Bug Fix: Make ScriptCraft work with Nashorn javascript engine bundled with Java 8 + + # 2014 01 12 ## Important diff --git a/src/main/javascript/lib/events.js b/src/main/javascript/lib/events.js index 4945955..651b1f5 100644 --- a/src/main/javascript/lib/events.js +++ b/src/main/javascript/lib/events.js @@ -93,11 +93,7 @@ exports.on = function( priority = bkEvent.EventPriority[priority]; } if (typeof eventType == "string"){ - var subPkgs = eventType.split('.'); - eventType = bkEvent[subPkgs[0]]; - for (var i = 1;i < subPkgs.length; i++){ - eventType = eventType[subPkgs[i]]; - } + eventType = eval('org.bukkit.event.' + eventType); } var handlerList = eventType.getHandlerList(); var listener = {}; diff --git a/src/main/javascript/lib/persistence.js b/src/main/javascript/lib/persistence.js index 52614e1..cea7147 100644 --- a/src/main/javascript/lib/persistence.js +++ b/src/main/javascript/lib/persistence.js @@ -13,7 +13,7 @@ module.exports = function( rootDir, $ ){ if (typeof write == 'undefined') write = false; if (!write){ - dataFromFile = $.load(_dataDir.canonicalPath + '/' + name + '-store.json'); + dataFromFile = $.scload(_dataDir.canonicalPath + '/' + name + '-store.json'); if (dataFromFile){ for (i in dataFromFile){ data[i] = dataFromFile[i]; @@ -21,7 +21,7 @@ module.exports = function( rootDir, $ ){ } }else{ // flush data to file - $.save(data, _dataDir.canonicalPath + '/' + name + '-store.json'); + $.scsave(data, _dataDir.canonicalPath + '/' + name + '-store.json'); } _persistentData[name] = data; return data; @@ -30,7 +30,7 @@ module.exports = function( rootDir, $ ){ $.addUnloadHandler(function(){ for (var name in _persistentData){ var data = _persistentData[name]; - $.save(data, _dataDir.canonicalPath + '/' + name + '-store.json'); + $.scsave(data, _dataDir.canonicalPath + '/' + name + '-store.json'); } }); }; diff --git a/src/main/javascript/lib/plugin.js b/src/main/javascript/lib/plugin.js index d41a0ef..ba9c9ab 100644 --- a/src/main/javascript/lib/plugin.js +++ b/src/main/javascript/lib/plugin.js @@ -65,7 +65,6 @@ exports.autoload = function(dir) { */ var _reload = function(pluginDir) { - _loaded = []; var sourceFiles = []; _listSourceFiles(sourceFiles,pluginDir); diff --git a/src/main/javascript/lib/require.js b/src/main/javascript/lib/require.js index 30923cb..fa3feaf 100644 --- a/src/main/javascript/lib/require.js +++ b/src/main/javascript/lib/require.js @@ -68,7 +68,7 @@ module specification, the '.js' suffix is optional. // look for a package.json file var pkgJsonFile = new File(dir, './package.json'); if (pkgJsonFile.exists()){ - var pkg = load(pkgJsonFile); + var pkg = scload(pkgJsonFile); var mainFile = new File(dir, pkg.main); if (mainFile.exists()){ return mainFile; diff --git a/src/main/javascript/lib/scriptcraft.js b/src/main/javascript/lib/scriptcraft.js index 6fa7e45..9588338 100644 --- a/src/main/javascript/lib/scriptcraft.js +++ b/src/main/javascript/lib/scriptcraft.js @@ -207,11 +207,11 @@ to load the named module. require() will return the loaded module's exports. -### load() function +### scload() function #### No longer recommended for use by Plugin/Module developers (deprecated) -load() should only be used to load .json data. +scload() should only be used to load .json data. #### Parameters @@ -220,13 +220,13 @@ load() should only be used to load .json data. #### Returns -load() will return the result of the last statement evaluated in the file. +scload() will return the result of the last statement evaluated in the file. #### Example - load("myFile.js"); // loads a javascript file and evaluates it. + scload("myFile.js"); // loads a javascript file and evaluates it. - var myData = load("myData.json"); // loads a javascript file and evaluates it - eval'd contents are returned. + var myData = scload("myData.json"); // loads a javascript file and evaluates it - eval'd contents are returned. ##### myData.json contents... @@ -239,15 +239,15 @@ load() will return the result of the last statement evaluated in the file. } } -### save() function +### scsave() function -The save() function saves an in-memory javascript object to a -specified file. Under the hood, save() uses JSON (specifically +The scsave() function saves an in-memory javascript object to a +specified file. Under the hood, scsave() uses JSON (specifically 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 `save()` function can later be -restored using the `load()` function. +in-memory object saved using the `scsave()` function can later be +restored using the `scload()` function. #### Parameters @@ -259,7 +259,7 @@ restored using the `load()` function. var myObject = { name: 'John Doe', aliases: ['John Ray', 'John Mee'], date_of_birth: '1982/01/31' }; - save(myObject, 'johndoe.json'); + scsave(myObject, 'johndoe.json'); ##### johndoe.json contents... @@ -415,11 +415,6 @@ var server = org.bukkit.Bukkit.server; */ function __onEnable (__engine, __plugin, __script) { - /* - don't execute this more than once - */ - if (typeof load == "function") - return ; var File = java.io.File ,FileReader = java.io.FileReader ,BufferedReader = java.io.BufferedReader @@ -450,8 +445,15 @@ function __onEnable (__engine, __plugin, __script) out.println( objectToStr ); out.close(); }; - - var _loaded = {}; + /* + make sure eval is present + */ + if (typeof eval == 'undefined'){ + global.eval = function(str){ + return __engine.eval(str); + }; + } + /* Load the contents of the file and evaluate as javascript */ @@ -465,11 +467,6 @@ function __onEnable (__engine, __plugin, __script) file = new File(filename); var canonizedFilename = _canonize(file); - /* - wph 20130123 don't load the same file more than once. - */ - if (_loaded[canonizedFilename]) - return _loaded[canonizedFilename]; if (file.exists()) { var parent = file.getParentFile(); @@ -484,9 +481,6 @@ function __onEnable (__engine, __plugin, __script) wrappedCode = "(" + code + ")"; result = __engine.eval(wrappedCode); // issue #103 avoid side-effects of || operator on Mac Rhino - _loaded[canonizedFilename] = result ; - if (!_loaded[canonizedFilename]) - _loaded[canonizedFilename]= true; }catch (e){ __plugin.logger.severe("Error evaluating " + canonizedFilename + ", " + e ); } @@ -544,12 +538,12 @@ function __onEnable (__engine, __plugin, __script) global.echo = _echo; global.alert = _echo; - global.load = _load; - global.save = _save; + global.scload = _load; + global.scsave = _save; global.addUnloadHandler = _addUnloadHandler; - var fnRequire = load(jsPluginsRootDirName + '/lib/require.js',true); + var fnRequire = _load(jsPluginsRootDirName + '/lib/require.js',true); /* setup paths to search for modules */ @@ -570,15 +564,13 @@ function __onEnable (__engine, __plugin, __script) global.command = require('command').command; var plugins = require('plugin'); - global.__onTabComplete = require('tabcomplete'); - global.plugin = plugins.plugin; var events = require('events'); events.on('server.PluginDisableEvent',function(l,e){ // save config - save(global.config, new File(jsPluginsRootDir, "data/global-config.json" )); + _save(global.config, new File(jsPluginsRootDir, "data/global-config.json" )); _runUnloadHandlers(); org.bukkit.event.HandlerList["unregisterAll(org.bukkit.plugin.Plugin)"](__plugin); @@ -586,7 +578,6 @@ function __onEnable (__engine, __plugin, __script) // wph 20131226 - make events global as it is used by many plugins/modules global.events = events; - plugins.autoload(jsPluginsRootDir); global.__onCommand = function( sender, cmd, label, args) { var jsArgs = []; @@ -620,6 +611,8 @@ function __onEnable (__engine, __plugin, __script) } return result; }; + + plugins.autoload(jsPluginsRootDir); /* wph 20140102 - warn if legacy 'craftbukkit/js-plugins' or 'craftbukkit/scriptcraft' directories are present */ diff --git a/src/main/javascript/lib/tabcomplete.js b/src/main/javascript/lib/tabcomplete.js index 92133b3..04aac7b 100644 --- a/src/main/javascript/lib/tabcomplete.js +++ b/src/main/javascript/lib/tabcomplete.js @@ -76,6 +76,8 @@ var _getProperties = function(o) var onTabCompleteJS = function( result, cmdSender, pluginCmd, cmdAlias, cmdArgs) { + cmdArgs = Array.prototype.slice.call(cmdArgs, 0); + if (pluginCmd.name == 'jsp') return tabCompleteJSP( result, cmdSender, pluginCmd, cmdAlias, cmdArgs ); From 93fb626276597c8132cf61a7ae5d5d6e03c4e3cf Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Mon, 13 Jan 2014 21:56:21 +0000 Subject: [PATCH 097/456] Adding a contributing.md file for contributors. --- README.md | 6 ++ contributing.md | 107 +++++++++++++++++++++ src/docs/templates/{ypgpm.mdt => ypgpm.md} | 0 3 files changed, 113 insertions(+) create mode 100644 contributing.md rename src/docs/templates/{ypgpm.mdt => ypgpm.md} (100%) diff --git a/README.md b/README.md index 39e7ff5..c4ac273 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,11 @@ ScriptCraft plugin... [cbdl]: http://dl.bukkit.org/downloads/craftbukkit/ [bukapi]: http://jd.bukkit.org/apidocs/ +Contributing +============ + +If you would like to contribute source code and/or documentation changes please [read contributing.md][contrib] + Further Reading =============== @@ -150,3 +155,4 @@ You can find more information about [ScriptCraft on my blog][blog]. [cda]: http://cdathenry.wordpress.com/category/modderdojo/ [ytpl]: http://www.youtube.com/watch?v=DDp20SKm43Y&list=PL4Tw0AgXQZH5BiFHqD2hXyXQi0-qFbGp_ [ex]: ../../tree/master/src/main/javascript/plugins/examples +[contrib]: contributing.md diff --git a/contributing.md b/contributing.md new file mode 100644 index 0000000..10e6b15 --- /dev/null +++ b/contributing.md @@ -0,0 +1,107 @@ +# Notes for Contributors + +This project uses a Maven-like directory structure... + + src + + main + + java + + net + + walterhiggins + + scriptcraft + + ScriptCraftPlugin.java + + javascript + + lib + + (core javascript code goes here. Modules in this directory + should not be 'require'd by plugin or module authors) + modules + + (this is where module authors should put modules for + use by others) + plugins + + (this is where plugins - scriptcraft extensions for use by + operators and players should go) + resources + + plugin.yml + docs + + templates + + (documentation templates go here. If you want to make + changes to the young persons guide should be made to ypgpm.md) + ypgpm.md + javascript + + (javascript source used to build the API reference and + table of contents for the API reference and Programming Guide + is located here) + docs + + (the following files should not be edited directly because they are constructed + during the build process - yeah I know they strictly shouldn't be under source control but + it's nice to have the markdown docs on github for reading by non-contributors) + + API-Reference.md + Young-Persons-Guide.md + + (this project is build using Ant, type `ant` at the command line to build.) + + build.xml + build.properties + +## Core javascript modules + +ScriptCraft's deployed core consists of a single Java source file (the +Bukkit Plugin) and a tiny set of javascript source files located in +the src/main/javascript/lib directory. All other javascript files are +optional modules and plugins. `scriptcraft.js` is the first file +loaded by the Java plugin. scriptcraft.js in turn loads the require.js +file which initializes the commonJS `require()` function and all other +files in the lib directory are then loaded. Finally all of the modules +in the plugins directory are automatically loaded and any exports are +automatically exported to the global namespace. For example a file +called `greet.js` located in the plugins folder... + + // plugins/greet.js contents + + exports.greet = function(sender){ + sender.sendMessage('hello') + } + +... will be loaded at startup and the `greet` function will be +global. Anyone with operator privileges can type `/js greet(self)` at +the in-game command prompt to execute the function. + +## Documentation contributions + +The Young persons guide to programming source file is located at +/src/docs/templates/ypgpm.md . *Do not make changes to +/docs/YoungPersonsGuide.md* + +The API Reference is generated by the build from markdown comments +embedded in the javascript source. If you would like comments for +contributed code to be included in the API reference then enclose your +comment as follows: + + * Start the comment block with a `/**********` (a forward-slash + followed by 10 or more asterisk characters). *The start block must + be at the start of the line*. + + * End the comment block with a `***/` (3 asterisk followed by a + forward slash) at the start of a new line. + +This is an example of a comment which will be included in the API reference... + + /********************* + ## foo() function + + The foo() function performs foo-type operatations on all bars. + + ### Parameters + + * name : Name of the foo + * count: Number of foos to perform + + ### Returns + foo() returns a list of foos that were changed. + + ***/ + +Top level comments for a module should be a H2 heading `##`. Please +don't use a H1 heading ( `#` ) as this is used for the top-level API +document title. diff --git a/src/docs/templates/ypgpm.mdt b/src/docs/templates/ypgpm.md similarity index 100% rename from src/docs/templates/ypgpm.mdt rename to src/docs/templates/ypgpm.md From 3a6cb1057da7cecaad9448ca65e9409bd927e217 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Mon, 13 Jan 2014 23:01:17 +0000 Subject: [PATCH 098/456] Further changes to achieve compat with Nashorn java 8 --- build.xml | 5 ++--- docs/YoungPersonsGuideToProgrammingMinecraft.md | 1 - src/docs/javascript/generateApiDocs.js | 9 +++++++-- src/docs/javascript/generateTOC.js | 8 ++++---- src/main/javascript/lib/command.js | 3 +-- src/main/javascript/modules/fireworks/fireworks.js | 7 ++++--- src/main/javascript/plugins/minigames/NumberGuess.js | 6 ++++-- 7 files changed, 22 insertions(+), 17 deletions(-) diff --git a/build.xml b/build.xml index f9988f5..22c4f74 100644 --- a/build.xml +++ b/build.xml @@ -105,7 +105,7 @@ Walter Higgins - + @@ -113,10 +113,9 @@ Walter Higgins
# The Young Person's Guide to Programming in Minecraft -
- +
diff --git a/docs/YoungPersonsGuideToProgrammingMinecraft.md b/docs/YoungPersonsGuideToProgrammingMinecraft.md index c043c70..2c2ba4b 100644 --- a/docs/YoungPersonsGuideToProgrammingMinecraft.md +++ b/docs/YoungPersonsGuideToProgrammingMinecraft.md @@ -1,5 +1,4 @@ # The Young Person's Guide to Programming in Minecraft - ## Table of Contents * [Introduction](#introduction) * [Installation](#installation) diff --git a/src/docs/javascript/generateApiDocs.js b/src/docs/javascript/generateApiDocs.js index c0ca1b9..b1570ce 100644 --- a/src/docs/javascript/generateApiDocs.js +++ b/src/docs/javascript/generateApiDocs.js @@ -3,7 +3,12 @@ */ var err = java.lang.System.err; -args = args.slice(1); +args = Array.prototype.slice.call(args,1); + +if (typeof importPackage == 'undefined'){ + // load compatibility script + load('nashorn:mozilla_compat.js'); +} var dir = args[0]; var foreach = function(array, func){ for (var i =0; i < array.length; i++){ @@ -142,7 +147,7 @@ for (var i = 0;i < contents.length; i++){ writeComment = false; } if (writeComment){ - println(contents[i]); + java.lang.System.out.println(contents[i]); } } diff --git a/src/docs/javascript/generateTOC.js b/src/docs/javascript/generateTOC.js index f2fe8f8..b536051 100644 --- a/src/docs/javascript/generateTOC.js +++ b/src/docs/javascript/generateTOC.js @@ -1,4 +1,4 @@ -args = args.slice(1); +args = Array.prototype.slice.call(args,1); // wph 20140105 trim not availabe in String on Mac OS. if (typeof String.prototype.trim == 'undefined'){ @@ -33,18 +33,18 @@ var createLink = function(text){ anchors[result] = 1; return result; }; -println('## Table of Contents'); +java.lang.System.out.println('## Table of Contents'); for (var i = 0; i < contents.length; i++){ line = contents[i]; if (line.match(/^##\s+/)){ var h2 = line.match(/^##\s+(.*)/)[1].trim(); var link = createLink(h2); - println (' * [' + h2 + '](#' + link + ')'); + java.lang.System.out.println (' * [' + h2 + '](#' + link + ')'); } if (line.match(/^###\s+/)){ var h3 = line.match(/^###\s+(.*)/)[1].trim(); var link = createLink(h3); - println (' * [' + h3 + '](#' + link + ')'); + java.lang.System.out.println (' * [' + h3 + '](#' + link + ')'); } } diff --git a/src/main/javascript/lib/command.js b/src/main/javascript/lib/command.js index 411aa17..af8b4d1 100644 --- a/src/main/javascript/lib/command.js +++ b/src/main/javascript/lib/command.js @@ -22,10 +22,9 @@ var executeCmd = function(args, player){ if (!intercepted) console.warn('Command %s is not recognised',name); }else{ - func = cmd.callback; var result = null; try { - result = func(args.slice(1),player); + result = cmd.callback(args.slice(1),player); }catch (e){ console.error("Error while trying to execute command: " + JSON.stringify(args)); throw e; diff --git a/src/main/javascript/modules/fireworks/fireworks.js b/src/main/javascript/modules/fireworks/fireworks.js index e3e160a..6e59003 100644 --- a/src/main/javascript/modules/fireworks/fireworks.js +++ b/src/main/javascript/modules/fireworks/fireworks.js @@ -37,9 +37,10 @@ location. For example... create a firework at the given location */ var firework = function(location){ - importPackage(org.bukkit.entity); - importPackage(org.bukkit); - + var Color = org.bukkit.Color; + var FireworkEffect = org.bukkit.FireworkEffect; + var EntityType = org.bukkit.entity.EntityType; + var randInt = function(n){ return Math.floor(Math.random() * n); }; diff --git a/src/main/javascript/plugins/minigames/NumberGuess.js b/src/main/javascript/plugins/minigames/NumberGuess.js index a3ae132..5dbac98 100644 --- a/src/main/javascript/plugins/minigames/NumberGuess.js +++ b/src/main/javascript/plugins/minigames/NumberGuess.js @@ -29,8 +29,10 @@ exports.Game_NumberGuess = { sb('players set ' + sender.name + ' NumberGuess ' + guesses); sb('objectives setdisplay sidebar NumberGuess'); - importPackage(org.bukkit.conversations); - + var Prompt = org.bukkit.conversations.Prompt; + var ConversationFactory = org.bukkit.conversations.ConversationFactory; + var ConversationPrefix = org.bukkit.conversations.ConversationPrefix; + var number = Math.ceil(Math.random() * 10); var prompt = new Prompt() From 47ea7bcab2758c4129d8e2664e78d66073cb1cc5 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 14 Jan 2014 08:37:02 +0000 Subject: [PATCH 099/456] updated version to 2.0.2 (nashorn-ready) --- build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.properties b/build.properties index 9d6409b..fc616c4 100644 --- a/build.properties +++ b/build.properties @@ -1,2 +1,2 @@ bukkit-version=1.7.2 -scriptcraft-version=2.0.1 +scriptcraft-version=2.0.2 From b2e203dd8c3aac2c4c138fcdbb253fb024c8fa15 Mon Sep 17 00:00:00 2001 From: Walter Higgins Date: Tue, 14 Jan 2014 16:00:12 +0000 Subject: [PATCH 100/456] Create .travis.yml --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..d99f43b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,5 @@ +language: java +jdk: + - oraclejdk7 + - openjdk7 + - openjdk6 From a4999745de1d28bc42fd57ab45daea097a164c13 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 14 Jan 2014 22:54:49 +0000 Subject: [PATCH 101/456] Added config.yml for plugin configuration - issue #102 --- build.xml | 35 +++++- .../scriptcraft/ScriptCraftPlugin.java | 105 +----------------- src/main/javascript/lib/events.js | 13 ++- src/main/javascript/lib/plugin.js | 4 +- src/main/javascript/lib/require.js | 31 ++---- src/main/javascript/lib/scriptcraft.js | 36 +++--- src/main/resources/boot.js | 76 +++++++++++++ src/main/resources/config.yml | 4 + 8 files changed, 161 insertions(+), 143 deletions(-) create mode 100644 src/main/resources/boot.js create mode 100644 src/main/resources/config.yml diff --git a/build.xml b/build.xml index 22c4f74..2338a24 100644 --- a/build.xml +++ b/build.xml @@ -19,6 +19,9 @@ + + + @@ -119,17 +122,39 @@ Walter Higgins - - - - + + + + + + + + + + + + + + + + + + - + + + [[version]] diff --git a/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java b/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java index 6d6b16e..8fb5a71 100644 --- a/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java +++ b/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java @@ -1,20 +1,12 @@ package net.walterhiggins.scriptcraft; -import java.io.File; -import java.io.FileReader; -import java.io.FileOutputStream; -import java.io.IOException; +import java.io.InputStreamReader; import javax.script.*; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; -import java.util.Collection; -import java.util.Arrays; import java.util.List; import java.util.ArrayList; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.command.*; -import org.bukkit.Bukkit; import org.bukkit.event.Listener; public class ScriptCraftPlugin extends JavaPlugin implements Listener @@ -23,114 +15,27 @@ public class ScriptCraftPlugin extends JavaPlugin implements Listener // need to look at possibly having context/scope per operator //protected Map playerContexts = new HashMap(); protected ScriptEngine engine = null; - private static final String JS_PLUGINS_DIR = "plugins/scriptcraft"; - private static final String JS_PLUGINS_ZIP = "scriptcraft.zip"; - - /** - * Unzips bundled javascript code. - */ - private void unzipJS() throws IOException - { - // - // does the js-plugins directory exist? - // - File jsPlugins = new File(JS_PLUGINS_DIR); - if (!jsPlugins.exists()) - { - getLogger().info("Directory " + jsPlugins.getCanonicalPath() + " does not exist."); - getLogger().info("Initializing " + jsPlugins.getCanonicalPath() + " directory with contents from plugin archive."); - try{ - jsPlugins.mkdirs(); - }catch(Exception e){ - throw new RuntimeException("Failed to create directory " + jsPlugins.getCanonicalPath() + ":" + e.getMessage()); - } - } - - ZipInputStream zis = new ZipInputStream(getResource(JS_PLUGINS_ZIP)); - ZipEntry entry; - try { - while ( ( entry = zis.getNextEntry() ) != null) - { - String filename = entry.getName(); - - File newFile = new File(jsPlugins, filename); - - //create all non exists folders - //else you will hit FileNotFoundException for compressed folder - if (entry.isDirectory()){ - newFile.mkdirs(); - }else{ - // - // only write out to file if zip entry is newer than file - // - String reason = null; - long zTime = entry.getTime(); - boolean unzip = false; - if (!newFile.exists()){ - reason = "NE"; - unzip = true; - } - else{ - long fTime = newFile.lastModified(); - if (zTime > fTime){ - reason = "" + new Long((zTime-fTime)/3600000) + "h"; - unzip = true; - } - - } - if (unzip){ - getLogger().info("Unzipping " + newFile.getCanonicalPath() + " (" + reason + ")" ); - FileOutputStream fout = new FileOutputStream(newFile); - for (int c = zis.read(); c != -1; c = zis.read()) { - fout.write(c); - } - fout.close(); - } - - } - zis.closeEntry(); - } - zis.close(); - }catch (IOException ioe){ - getLogger().warning(ioe.getMessage()); - ioe.printStackTrace(); - } - } @Override public void onEnable() { - FileReader reader = null; try{ - unzipJS(); + ScriptEngineManager factory = new ScriptEngineManager(); - File bootScript = new File(JS_PLUGINS_DIR + "/lib/scriptcraft.js"); this.engine = factory.getEngineByName("JavaScript"); - reader = new FileReader(bootScript); - this.engine.eval(reader); Invocable inv = (Invocable)this.engine; - inv.invokeFunction("__onEnable", engine, this, bootScript); - + this.engine.eval(new InputStreamReader(this.getResource("boot.js"))); + inv.invokeFunction("__scboot", this, engine); + }catch(Exception e){ e.printStackTrace(); this.getLogger().severe(e.getMessage()); - }finally { - if (reader != null){ - try { - reader.close(); - }catch(IOException ioe){ - // fail silently - } - } } } public List onTabComplete(CommandSender sender, Command cmd, String alias, String[] args) { - // - // delegate to javascript - // List result = new ArrayList(); try { Invocable inv = (Invocable)this.engine; diff --git a/src/main/javascript/lib/events.js b/src/main/javascript/lib/events.js index 651b1f5..63f8b88 100644 --- a/src/main/javascript/lib/events.js +++ b/src/main/javascript/lib/events.js @@ -93,7 +93,18 @@ exports.on = function( priority = bkEvent.EventPriority[priority]; } if (typeof eventType == "string"){ - eventType = eval('org.bukkit.event.' + eventType); + /* + Nashorn doesn't support bracket notation for accessing packages. + E.g. java.net will work but java['net'] won't. + + https://bugs.openjdk.java.net/browse/JDK-8031715 + */ + if (typeof Java != 'undefined'){ + // nashorn environment + eventType = Java.type('org.bukkit.event.' + eventType); + } else { + eventType = eval('org.bukkit.event.' + eventType); + } } var handlerList = eventType.getHandlerList(); var listener = {}; diff --git a/src/main/javascript/lib/plugin.js b/src/main/javascript/lib/plugin.js index ba9c9ab..fe493f6 100644 --- a/src/main/javascript/lib/plugin.js +++ b/src/main/javascript/lib/plugin.js @@ -49,6 +49,8 @@ exports.autoload = function(dir) { var _listSourceFiles = function(store,dir) { var files = dir.listFiles(); + if (!files) + return; for (var i = 0;i < files.length; i++) { var file = files[i]; if (file.isDirectory()){ @@ -73,8 +75,6 @@ exports.autoload = function(dir) { console.info(len + ' scriptcraft plugins found.'); for (var i = 0;i < len; i++){ var pluginPath = _canonize(sourceFiles[i]); - if (config.verbose) - console.info('Loading plugin: ' + pluginPath); var module = {}; try { module = require(pluginPath); diff --git a/src/main/javascript/lib/require.js b/src/main/javascript/lib/require.js index fa3feaf..1e3d2ba 100644 --- a/src/main/javascript/lib/require.js +++ b/src/main/javascript/lib/require.js @@ -54,12 +54,7 @@ module specification, the '.js' suffix is optional. [cjsmodules]: http://wiki.commonjs.org/wiki/Modules/1.1.1. ***/ -(function (logger, evaluator, verbose, rootDir, modulePaths) { - - if (verbose){ - logger.info("Setting up 'require' module system. Root Directory: " + rootDir); - logger.info("Module paths: " + JSON.stringify(modulePaths)); - } +(function (rootDir, modulePaths, hooks) { var File = java.io.File; @@ -152,9 +147,6 @@ When resolving module names to file paths, ScriptCraft uses the following rules. if (resolvedFile.exists()) return resolvedFile; } - if (verbose){ - logger.info("Module " + moduleName + " not found in " + modulePaths[i]); - } } } else { // it's of the form ./path @@ -192,8 +184,7 @@ When resolving module names to file paths, ScriptCraft uses the following rules. if (! ( (''+path).match(/^\./) )){ errMsg = errMsg + ' and not found in paths ' + JSON.stringify(modulePaths); } - logger.warning(errMsg); - throw new Error(errMsg); + throw errMsg; } var canonizedFilename = _canonize(file); @@ -201,9 +192,8 @@ When resolving module names to file paths, ScriptCraft uses the following rules. if (moduleInfo){ return moduleInfo; } - if (verbose){ - logger.info("loading module " + canonizedFilename); - } + if (hooks) + hooks.loading(canonizedFilename); var reader = new java.io.FileReader(file); var br = new java.io.BufferedReader(reader); var code = ""; @@ -225,10 +215,9 @@ When resolving module names to file paths, ScriptCraft uses the following rules. _loadedModules[canonizedFilename] = moduleInfo; var compiledWrapper = null; try { - compiledWrapper = evaluator.eval(code); + compiledWrapper = eval(code); }catch (e){ - logger.severe("Error:" + e + " while evaluating module " + canonizedFilename); - throw e; + throw "Error:" + e + " while evaluating module " + canonizedFilename; } var __dirname = "" + file.parentFile.canonicalPath; var parameters = [ @@ -243,12 +232,10 @@ When resolving module names to file paths, ScriptCraft uses the following rules. .apply(moduleInfo.exports, /* this */ parameters); } catch (e){ - logger.severe('Error:' + e + ' while executing module ' + canonizedFilename); - throw e; + throw 'Error:' + e + ' while executing module ' + canonizedFilename; } - if (verbose) - logger.info("loaded module " + canonizedFilename); - + if (hooks) + hooks.loaded(canonizedFilename); moduleInfo.loaded = true; return moduleInfo; }; diff --git a/src/main/javascript/lib/scriptcraft.js b/src/main/javascript/lib/scriptcraft.js index 9588338..84dad7d 100644 --- a/src/main/javascript/lib/scriptcraft.js +++ b/src/main/javascript/lib/scriptcraft.js @@ -428,6 +428,7 @@ function __onEnable (__engine, __plugin, __script) var libDir = __script.parentFile; // lib (assumes scriptcraft.js is in craftbukkit/plugins/scriptcraft/lib directory var jsPluginsRootDir = libDir.parentFile; // scriptcraft var jsPluginsRootDirName = _canonize(jsPluginsRootDir); + var logger = __plugin.logger; /* Save a javascript object to a file (saves using JSON notation) @@ -482,7 +483,7 @@ function __onEnable (__engine, __plugin, __script) result = __engine.eval(wrappedCode); // issue #103 avoid side-effects of || operator on Mac Rhino }catch (e){ - __plugin.logger.severe("Error evaluating " + canonizedFilename + ", " + e ); + logger.severe("Error evaluating " + canonizedFilename + ", " + e ); } finally { try { @@ -493,7 +494,7 @@ function __onEnable (__engine, __plugin, __script) } }else{ if (warnOnFileNotFound) - __plugin.logger.warning(canonizedFilename + " not found"); + logger.warning(canonizedFilename + " not found"); } return result; }; @@ -529,7 +530,6 @@ function __onEnable (__engine, __plugin, __script) }; var _echo = function (msg) { - __plugin.logger.info( msg ); if (typeof self == "undefined"){ return; } @@ -543,17 +543,28 @@ function __onEnable (__engine, __plugin, __script) global.addUnloadHandler = _addUnloadHandler; - var fnRequire = _load(jsPluginsRootDirName + '/lib/require.js',true); + var configRequire = _load(jsPluginsRootDirName + '/lib/require.js',true); /* setup paths to search for modules */ var modulePaths = [jsPluginsRootDirName + '/lib/', jsPluginsRootDirName + '/modules/']; - global.require = fnRequire(__plugin.logger, - __engine, - config.verbose, - jsPluginsRootDirName, - modulePaths); + + 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 ); require('js-patch')(global); global.console = require('console'); @@ -570,10 +581,10 @@ function __onEnable (__engine, __plugin, __script) var events = require('events'); events.on('server.PluginDisableEvent',function(l,e){ // save config - _save(global.config, new File(jsPluginsRootDir, "data/global-config.json" )); + _save(global.config, new File(jsPluginsRootDir, 'data/global-config.json' )); _runUnloadHandlers(); - org.bukkit.event.HandlerList["unregisterAll(org.bukkit.plugin.Plugin)"](__plugin); + 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; @@ -593,12 +604,11 @@ function __onEnable (__engine, __plugin, __script) global.self = sender; global.__engine = __engine; try { - //var jsResult = __engine["eval(java.lang.String,javax.script.Bindings)"]( fnBody, bindings ); var jsResult = __engine.eval(fnBody); if (jsResult) sender.sendMessage(jsResult); }catch (e){ - __plugin.logger.severe("Error while trying to evaluate javascript: " + fnBody + ", Error: "+ e); + logger.severe("Error while trying to evaluate javascript: " + fnBody + ", Error: "+ e); throw e; }finally{ delete global.self; diff --git a/src/main/resources/boot.js b/src/main/resources/boot.js new file mode 100644 index 0000000..5cdd1d6 --- /dev/null +++ b/src/main/resources/boot.js @@ -0,0 +1,76 @@ +/* + This file is the first and only file executed directly from the Java Plugin. +*/ +var __scboot = null; +(function(){ + var File = java.io.File + ,FileReader = java.io.FileReader + ,FileOutputStream = java.io.FileOutputStream + ,ZipInputStream = java.util.zip.ZipInputStream + ,jsPlugins = new File('plugins/scriptcraft') + ,initScript = 'lib/scriptcraft.js'; + + var unzip = function(path, logger, plugin) { + var zis = new ZipInputStream(plugin.getResource(path)) + , entry , reason = null, unzipFile = false, zTime = 0 + , fTime = 0, fout = null, c, newFile; + + while ( ( entry = zis.nextEntry ) != null ) { + + newFile = new File(jsPlugins, entry.name); + if (entry.isDirectory()){ + newFile.mkdirs(); + zis.closeEntry(); + continue; + } + reason = null; + zTime = entry.time; + unzipFile = false; + if (!newFile.exists()) { + reason = 'NE'; + unzipFile = true; + }else{ + fTime = newFile.lastModified(); + if (zTime > fTime) { + reason = ((zTime - fTime) / 3600000) + "h"; + unzipFile = true; + } + } + if (unzipFile) { + logger.info('Unzipping ' + newFile.canonicalPath + ' (' + reason + ')' ); + fout = new FileOutputStream(newFile); + for (c = zis.read(); c != -1; c = zis.read()) { + fout.write(c); + } + fout.close(); + } + zis.closeEntry(); + } + zis.close(); + }; + + __scboot = function ( plugin, engine ) + { + var logger = plugin.logger, cfg = plugin.config + ,cfgName, initScriptFile = new File(jsPlugins,initScript) + ,zips = ['lib','plugins','modules'] + ,i = 0 ,len = zips.length; + + if (!jsPlugins.exists()){ + logger.info('Directory ' + jsPlugins.canonicalPath + ' does not exist.'); + logger.info('Initializing ' + jsPlugins.canonicalPath + ' directory with contents from plugin archive.'); + jsPlugins.mkdirs(); + } + + for (i = 0; i < len;i++){ + cfgName = 'extract-js.' + zips[i]; + if (cfg.getBoolean(cfgName)){ + unzip( zips[i] + '.zip',logger,plugin); + } + } + plugin.saveDefaultConfig(); + + engine.eval(new FileReader(initScriptFile)); + __onEnable(engine, plugin, initScriptFile); + }; +})(); diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..96d7c0f --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,4 @@ +extract-js: + plugins: true + modules: true + lib: true From 963df1898cbe38539929e70ee8c40436aba835e0 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 14 Jan 2014 23:01:20 +0000 Subject: [PATCH 102/456] configuration docs for issue #102 --- README.md | 17 +++++++++++++++++ docs/release-notes.md | 15 +++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/README.md b/README.md index c4ac273..16a753f 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,23 @@ Contributing If you would like to contribute source code and/or documentation changes please [read contributing.md][contrib] +Configuration +============= +ScriptCraft is a Bukkit Plugin and uses the Bukkit Configuration +API. On first loading, ScriptCraft will create a config.yml file in +the plugins/scriptcraft/ directory. This file looks like this... + + extract-js: + plugins: true + modules: true + lib: true + +This file allows scriptcraft admins to turn on or off re-unzipping of the `modules`, +`plugins` and `lib` folders when deploying a new version of +scriptcraft. It's strongly recommended that the `lib` directory always +be set to true to get the latest core scriptcraft code . The modules +and plugins directories are optional and not part of scriptcraft core. + Further Reading =============== diff --git a/docs/release-notes.md b/docs/release-notes.md index 1232275..0fd0184 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,3 +1,18 @@ +# 2014 01 14 + +Added config.yml file for configuration options. This file allows +scriptcraft admins to turn on or off re-unzipping of the modules, +plugins and lib folders when deploying a new version of +scriptcraft. It's strongly recommended that the lib directory always +be set to true to get the latest core scriptcraft code . The modules +and plugins directories are optional and not part of scriptcraft core. +The config.yml file looks like this... + + extract-js: + plugins: true + modules: true + lib: true + # 2014 01 13 Bug Fix: Make ScriptCraft work with Nashorn javascript engine bundled with Java 8 From 5f7193d3eed198731921da20131129c51dc9f413 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 14 Jan 2014 23:25:00 +0000 Subject: [PATCH 103/456] Adding travis ci build status. --- README.md | 30 ++++++++++++++++-------------- contributing.md | 4 ++++ 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 16a753f..3023530 100644 --- a/README.md +++ b/README.md @@ -46,8 +46,8 @@ Minecraft. [cottage]: https://github.com/walterhiggins/ScriptCraft/tree/master/src/main/javascript//drone/cottage.js [bukkit]: http://dl.bukkit.org/ -Prerequisites -============= +# Prerequisites + You will need to have Java version 6 or 7 installed on your machine. Check the version by typing `java -version` at a command prompt. You will need to [install Bukkit][ib] on your machine. Bukkit @@ -55,14 +55,14 @@ is a version of Minecraft (server) that makes it easy to install plugins and customize Minecraft. You can [download the CraftBukkit server here.][cbdl] -Installation -============ +# Installation + If you don't want to compile from source, you can [download the compiled plugin here][dl] and copy it the craftbukkit's plugins directory. -Post Install -============ +# Post Install + Once installed, a new js-plugins directory is automatically created in the same directory as the plugins folder. All files in the js-plugins directory will be automatically loaded when CraftBukkit starts. *Only @@ -99,8 +99,8 @@ of the existing mods in the [homes][ho], [chat][ch], [arrows][ar] and [signs][si] directories. The chat/color.js mod is probably the simplest mod to get started with. -Additional information -====================== +# Additional information + Because the Bukkit API is open, all of the Bukkit API is accessible via javascript once the ScriptCraft plugin is loaded. There are a couple of useful Java objects exposed via javascript in the Bukkit @@ -126,13 +126,16 @@ ScriptCraft plugin... [cbdl]: http://dl.bukkit.org/downloads/craftbukkit/ [bukapi]: http://jd.bukkit.org/apidocs/ -Contributing -============ +# Contributing If you would like to contribute source code and/or documentation changes please [read contributing.md][contrib] -Configuration -============= +## Status + +[![Travis Build Status](https://api.travis-ci.org/walterhiggins/ScriptCraft.png)](http://travis-ci.org/walterhiggins/ScriptCraft) + +# Configuration + ScriptCraft is a Bukkit Plugin and uses the Bukkit Configuration API. On first loading, ScriptCraft will create a config.yml file in the plugins/scriptcraft/ directory. This file looks like this... @@ -148,8 +151,7 @@ scriptcraft. It's strongly recommended that the `lib` directory always be set to true to get the latest core scriptcraft code . The modules and plugins directories are optional and not part of scriptcraft core. -Further Reading -=============== +# Further Reading ScriptCraft has [its own website][website] with further information. diff --git a/contributing.md b/contributing.md index 10e6b15..28940e6 100644 --- a/contributing.md +++ b/contributing.md @@ -1,3 +1,7 @@ +# Status + +[![Travis Build Status](https://api.travis-ci.org/walterhiggins/ScriptCraft.png)](http://travis-ci.org/walterhiggins/ScriptCraft) + # Notes for Contributors This project uses a Maven-like directory structure... From a13f3badd923ef57565b350085e7fb589a942599 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Fri, 17 Jan 2014 23:05:36 +0000 Subject: [PATCH 104/456] Adding sc-mqtt module for comms with Arduino --- build.xml | 9 +- docs/API-Reference.md | 79 +++++++++- .../scriptcraft/ScriptCraftPlugin.java | 2 - src/main/javascript/modules/sc-mqtt.js | 137 ++++++++++++++++++ 4 files changed, 216 insertions(+), 11 deletions(-) create mode 100644 src/main/javascript/modules/sc-mqtt.js diff --git a/build.xml b/build.xml index 2338a24..4fe8977 100644 --- a/build.xml +++ b/build.xml @@ -22,6 +22,7 @@ + @@ -54,7 +55,12 @@ - + @@ -161,6 +167,7 @@ Walter Higgins + diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 42263b9..5e3fe17 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -52,6 +52,8 @@ Walter Higgins * [Examples](#examples-1) * [Http Module](#http-module) * [http.request() function](#httprequest-function) + * [sc-mqtt module](#sc-mqtt-module) + * [Usage](#usage) * [Signs Module](#signs-module) * [signs.menu() function](#signsmenu-function) * [signs.getTargetedBy() function](#signsgettargetedby-function) @@ -103,22 +105,22 @@ Walter Higgins * [Drone.hemisphere0() method](#dronehemisphere0-method) * [Drone.spiral_stairs() method](#dronespiral_stairs-method) * [Example Plugin #1 - A simple extension to Minecraft.](#example-plugin-1---a-simple-extension-to-minecraft) - * [Usage:](#usage) - * [Example Plugin #2 - Making extensions available for all players.](#example-plugin-2---making-extensions-available-for-all-players) * [Usage:](#usage-1) - * [Example Plugin #3 - Limiting use of commands to operators only.](#example-plugin-3---limiting-use-of-commands-to-operators-only) + * [Example Plugin #2 - Making extensions available for all players.](#example-plugin-2---making-extensions-available-for-all-players) * [Usage:](#usage-2) - * [Example Plugin #4 - Using parameters in commands.](#example-plugin-4---using-parameters-in-commands) + * [Example Plugin #3 - Limiting use of commands to operators only.](#example-plugin-3---limiting-use-of-commands-to-operators-only) * [Usage:](#usage-3) - * [Example Plugin #5 - Re-use - Using your own and others modules.](#example-plugin-5---re-use---using-your-own-and-others-modules) + * [Example Plugin #4 - Using parameters in commands.](#example-plugin-4---using-parameters-in-commands) * [Usage:](#usage-4) - * [Example Plugin #6 - Re-use - Using 'utils' to get Player objects.](#example-plugin-6---re-use---using-utils-to-get-player-objects) + * [Example Plugin #5 - Re-use - Using your own and others modules.](#example-plugin-5---re-use---using-your-own-and-others-modules) * [Usage:](#usage-5) + * [Example Plugin #6 - Re-use - Using 'utils' to get Player objects.](#example-plugin-6---re-use---using-utils-to-get-player-objects) + * [Usage:](#usage-6) * [Example Plugin #7 - Listening for events, Greet players when they join the game.](#example-plugin-7---listening-for-events-greet-players-when-they-join-the-game) * [Arrows Plugin](#arrows-plugin) - * [Usage:](#usage-6) + * [Usage:](#usage-7) * [Spawn Plugin](#spawn-plugin) - * [Usage](#usage-7) + * [Usage](#usage-8) * [alias Plugin](#alias-plugin) * [Examples](#examples-2) * [Classroom Plugin](#classroom-plugin) @@ -834,6 +836,67 @@ The following example illustrates how to use http.request to make a request to a var jsObj = eval("(" + responseBody + ")"); }); +## sc-mqtt module + +This module provides a simple way to communicate with devices (such as Arduino) +using the popular lightweight [MQTT protocol][mqtt]. + +### Usage + +This module can only be used if the separate `sc-mqtt.jar` file is +present in the CraftBukkit classpath. To use this module, you should +... + + 1. Download sc-mqtt.jar from + 2. Save the file to the same directory where craftbukkit.jar resides. + 3. Create a new batch file (windows-only) called + craftbukkit-sc-mqtt.bat and edit it to include the following + command... + + java -classpath sc-mqtt.jar;craftbukit.jar org.bukkit.craftbukkit.Main + + If you're using Mac OS, create a new craftbukkit-sc-mqtt.command + file and edit it (using TextWrangler or another text editor) ... + + java -classpath sc-mqtt.jar:craftbukkit.jar org.bukit.craftbukkit.Main + + 4. Execute the craftbukkit-sc-mqtt batch file / command file to start + Craftbukkit. You can now begin using this module to send and receive + messages to/from a Net-enabled Arduino or any other device which uses + the [MQTT protocol][mqtt] + + + var mqtt = require('sc-mqtt'); + + // create a new client + + var client = mqtt.client('tcp://localhost:1883', 'uniqueClientId'); + + // connect to the broker + + client.connect({ keepAliveInterval: 15 }); + + // publish a message to the broker + + client.publish('minecraft','loaded'); + + // subscribe to messages on 'arduino' topic + + client.subscribe('arduino'); + + // do something when an incoming message arrives... + + client.onMessageArrived(function(topic, message){ + console.log('Message arrived: topic=' + topic + ', message=' + message); + }); + +The `sc-mqtt` module provides a very simple minimal wrapper around the +[Eclipse Paho MQTT Version 3 Client][pahodocs] java-based MQTT +library. + +[pahodocs]: http://pic.dhe.ibm.com/infocenter/wmqv7/v7r5/index.jsp?topic=/com.ibm.mq.javadoc.doc/WMQMQxrClasses/org/eclipse/paho/client/mqttv3/package-summary.html +[mqtt]: http://mqtt.org/ + ## Signs Module The Signs Module can be used by plugin authors to create interactive diff --git a/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java b/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java index 8fb5a71..ab2082e 100644 --- a/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java +++ b/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java @@ -15,12 +15,10 @@ public class ScriptCraftPlugin extends JavaPlugin implements Listener // need to look at possibly having context/scope per operator //protected Map playerContexts = new HashMap(); protected ScriptEngine engine = null; - @Override public void onEnable() { try{ - ScriptEngineManager factory = new ScriptEngineManager(); this.engine = factory.getEngineByName("JavaScript"); Invocable inv = (Invocable)this.engine; diff --git a/src/main/javascript/modules/sc-mqtt.js b/src/main/javascript/modules/sc-mqtt.js new file mode 100644 index 0000000..82b9ee8 --- /dev/null +++ b/src/main/javascript/modules/sc-mqtt.js @@ -0,0 +1,137 @@ +/************************************************************************* +## sc-mqtt module + +This module provides a simple way to communicate with devices (such as Arduino) +using the popular lightweight [MQTT protocol][mqtt]. + +### Usage + +This module can only be used if the separate `sc-mqtt.jar` file is +present in the CraftBukkit classpath. To use this module, you should +... + + 1. Download sc-mqtt.jar from + 2. Save the file to the same directory where craftbukkit.jar resides. + 3. Create a new batch file (windows-only) called + craftbukkit-sc-mqtt.bat and edit it to include the following + command... + + java -classpath sc-mqtt.jar;craftbukit.jar org.bukkit.craftbukkit.Main + + If you're using Mac OS, create a new craftbukkit-sc-mqtt.command + file and edit it (using TextWrangler or another text editor) ... + + java -classpath sc-mqtt.jar:craftbukkit.jar org.bukit.craftbukkit.Main + + 4. Execute the craftbukkit-sc-mqtt batch file / command file to start + Craftbukkit. You can now begin using this module to send and receive + messages to/from a Net-enabled Arduino or any other device which uses + the [MQTT protocol][mqtt] + + + var mqtt = require('sc-mqtt'); + + // create a new client + + var client = mqtt.client('tcp://localhost:1883', 'uniqueClientId'); + + // connect to the broker + + client.connect({ keepAliveInterval: 15 }); + + // publish a message to the broker + + client.publish('minecraft','loaded'); + + // subscribe to messages on 'arduino' topic + + client.subscribe('arduino'); + + // do something when an incoming message arrives... + + client.onMessageArrived(function(topic, message){ + console.log('Message arrived: topic=' + topic + ', message=' + message); + }); + +The `sc-mqtt` module provides a very simple minimal wrapper around the +[Eclipse Paho MQTT Version 3 Client][pahodocs] java-based MQTT +library. + +[pahodocs]: http://pic.dhe.ibm.com/infocenter/wmqv7/v7r5/index.jsp?topic=/com.ibm.mq.javadoc.doc/WMQMQxrClasses/org/eclipse/paho/client/mqttv3/package-summary.html +[mqtt]: http://mqtt.org/ + +***/ +var MISSING_MQTT = '\nMissing class org.walterhiggins.scriptcraft.ScriptCraftMqttCallback.\n' + + 'Make sure sc-mqtt.jar is in the classpath.\n' + + 'See http://github.com/walterhiggins/scriptcraft-extras-mqtt for details.\n'; + +function Client(brokerUrl, clientId){ + + var Callback = org.walterhiggins.scriptcraft.ScriptCraftMqttCallback; + var MqttClient = org.eclipse.paho.client.mqttv3.MqttClient; + + var callback = new Callback( + function(err){ + console.log('connectionLost: ' + err); + }, + function(topic, message){ + console.log('messageArrived ' + topic + '> ' + message); + }, + function(token){ + console.log('deliveryComplete:' + token); + } + ); + + if (!brokerUrl){ + brokerUrl = 'tcp://localhost:1883'; + } + if (!clientId){ + clientId = 'scriptcraft'; + } + var client = new MqttClient(brokerUrl, clientId, null); + client.setCallback(callback); + return { + connect: function(options){ + if (typeof options === 'undefined'){ + client.connect(); + }else{ + client.connect(options); + } + }, + publish: function(topic, message, qos, retained){ + if (typeof message == 'string'){ + message = new java.lang.String(message).bytes; + } + if (typeof qos == 'undefined'){ + qos = 1; + } + if (typeof retained == 'undefined'){ + retained = false; + } + client.publish(topic, message,qos, retained); + }, + subscribe: function(topic){ + client.subscribe(topic); + }, + unsubscribe: function(topic){ + client.unsubscribe(topic); + }, + onMessageArrived: function(fn){ + callback.setMesgArrived(fn); + }, + onDeliveryComplete: function(fn){ + callback.setDeliveryComplete(fn); + }, + onConnectionLost: function(fn){ + callback.setConnLost(fn); + } + }; +}; + +exports.client = function(brokerUrl, clientId, options){ + if (typeof org.walterhiggins.scriptcraft.ScriptCraftMqttCallback != 'function'){ + throw MISSING_MQTT; + } + return new Client(brokerUrl, clientId, options); +}; + From 9a2b39b108e360806990f2a6f947096e43ce8cf4 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Fri, 17 Jan 2014 23:33:23 +0000 Subject: [PATCH 105/456] updated release notes for mqtt --- docs/release-notes.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 0fd0184..e49be22 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,3 +1,39 @@ +# 2014 01 17 + +Added support for communication with Arduino and other devices which +use the [MQTT protocol][mqtt] via a new `sc-mqtt` module. This module +requires a separate sc-mqtt.jar file downloadable from + which must be included +in the CraftBukkit classpath. If using MQTT in ScriptCraft, then +Craftbukkit should be launched like this... + + java -classpath craftbukkit.jar;sc-mqtt.jar org.bukkit.craftbukkit.Main + +You can use the new `sc-mqtt` module like this to send and receive +messages between minecraft and an Arduino. For example to send a +message to the MQTT broker whenever a player breaks a block... + + var mqtt = require('sc-mqtt'); + var client = mqtt.client('tcp://localhost:1883','scripcraft'); + client.connect(); + + events.on('block.BlockBreakEvent', function(listener, event){ + client.publish('minecraft','block-broken'); + }); + +To have minecraft react to inputs from an MQTT broker... + + var mqtt = require('sc-mqtt'); + var client = mqtt.client('tcp://localhost:1883','scripcraft'); + client.connect(); + client.onMessageArrived(function(topic, message){ + var payload = message.payload; + if (topic == 'arduino'){ + // do something with payload. + } + }); + +[mqtt]: http://mqtt.org/ # 2014 01 14 Added config.yml file for configuration options. This file allows From a098963f90bfc2d2c96ab02a87001b0864d962b6 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Wed, 22 Jan 2014 23:57:27 +0000 Subject: [PATCH 106/456] Workaround for stupid array index notation access bug in Nashorn. --- docs/API-Reference.md | 6 +- src/main/javascript/lib/command.js | 15 ++--- src/main/javascript/lib/scriptcraft.js | 9 ++- src/main/javascript/plugins/alias/alias.js | 68 +++++++++++++--------- 4 files changed, 57 insertions(+), 41 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 5e3fe17..3977782 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -2457,11 +2457,11 @@ such an alias)... /jsp alias global stormy = time 18000; weather storm -To delete an alias ... +To remove an alias ... - /jsp alias delete cw + /jsp alias remove cw -... deletes the 'cw' alias from the appropriate alias map. +... removes the 'cw' alias from the appropriate alias map. To get a list of aliases currently defined... diff --git a/src/main/javascript/lib/command.js b/src/main/javascript/lib/command.js index af8b4d1..17928af 100644 --- a/src/main/javascript/lib/command.js +++ b/src/main/javascript/lib/command.js @@ -9,10 +9,10 @@ var _cmdInterceptors = []; */ var executeCmd = function(args, player){ if (args.length === 0) - throw new Error("Usage: jsp command-name command-parameters"); + throw new Error('Usage: jsp command-name command-parameters'); var name = args[0]; var cmd = _commands[name]; - if (typeof cmd === "undefined"){ + if (typeof cmd === 'undefined'){ // it's not a global command - pass it on to interceptors var intercepted = false; for (var i = 0;i < _cmdInterceptors.length;i++){ @@ -26,7 +26,7 @@ var executeCmd = function(args, player){ try { result = cmd.callback(args.slice(1),player); }catch (e){ - console.error("Error while trying to execute command: " + JSON.stringify(args)); + console.error('Error while trying to execute command: ' + JSON.stringify(args)); throw e; } return result; @@ -36,16 +36,13 @@ var executeCmd = function(args, player){ define a new JSP command. */ var defineCmd = function(name, func, options, intercepts) { - if (typeof options == "undefined") + if (typeof options == 'undefined') options = []; _commands[name] = {callback: func, options: options}; if (intercepts) _cmdInterceptors.push(func); return func; }; -var _command = function(name, func, options, intercepts) { - return defineCmd(name, func, options, intercepts); -}; -_command.exec = executeCmd; -exports.command = _command; +exports.command = defineCmd; exports.commands = _commands; +exports.exec = executeCmd; diff --git a/src/main/javascript/lib/scriptcraft.js b/src/main/javascript/lib/scriptcraft.js index 84dad7d..929b7a7 100644 --- a/src/main/javascript/lib/scriptcraft.js +++ b/src/main/javascript/lib/scriptcraft.js @@ -573,7 +573,8 @@ function __onEnable (__engine, __plugin, __script) */ require('persistence')(jsPluginsRootDir,global); - global.command = require('command').command; + var cmdModule = require('command'); + global.command = cmdModule.command; var plugins = require('plugin'); global.__onTabComplete = require('tabcomplete'); global.plugin = plugins.plugin; @@ -593,7 +594,9 @@ function __onEnable (__engine, __plugin, __script) global.__onCommand = function( sender, cmd, label, args) { var jsArgs = []; var i = 0; - for (;i < args.length; i++) jsArgs.push('' + args[i]); + for (;i < args.length; i++) { + jsArgs.push('' + args[i]); + } var result = false; var cmdName = ('' + cmd.name).toLowerCase(); @@ -616,7 +619,7 @@ function __onEnable (__engine, __plugin, __script) } } if (cmdName == 'jsp'){ - command.exec(jsArgs, sender); + cmdModule.exec(jsArgs, sender); result = true; } return result; diff --git a/src/main/javascript/plugins/alias/alias.js b/src/main/javascript/plugins/alias/alias.js index 9c4a4ee..4941de9 100644 --- a/src/main/javascript/plugins/alias/alias.js +++ b/src/main/javascript/plugins/alias/alias.js @@ -31,11 +31,11 @@ such an alias)... /jsp alias global stormy = time 18000; weather storm -To delete an alias ... +To remove an alias ... - /jsp alias delete cw + /jsp alias remove cw -... deletes the 'cw' alias from the appropriate alias map. +... removes the 'cw' alias from the appropriate alias map. To get a list of aliases currently defined... @@ -55,7 +55,7 @@ var _usage = "\ /jsp alias set {alias} = {comand-1} ;{command-2}\n \ /jsp alias global {alias} = {command-1} ; {command-2}\n \ /jsp alias list\n \ -/jsp alias delete {alias}\n \ +/jsp alias remove {alias}\n \ Create a new alias : \n \ /jsp alias set cw = time set {1} ; weather {2}\n \ Execute the alias : \n \ @@ -81,7 +81,7 @@ var _processParams = function(params){ return { cmd: aliasCmd, aliases: aliasValue.split(/\s*;\s*/) }; }; -var _set = function(player, params){ +var _set = function(params, player){ var playerAliases = _store.players[player.name]; if (!playerAliases){ playerAliases = {}; @@ -92,11 +92,11 @@ var _set = function(player, params){ player.sendMessage("Alias '" + o.cmd + "' created."); }; -var _delete = function(player, params){ +var _remove = function(params, player){ if (_store.players[player.name] && _store.players[player.name][params[0]]){ delete _store.players[player.name][params[0]]; - player.sendMessage("Alias '" + params[0] + "' deleted."); + player.sendMessage("Alias '" + params[0] + "' removed."); } else{ player.sendMessage("Alias '" + params[0] + "' does not exist."); @@ -107,7 +107,7 @@ var _delete = function(player, params){ } }; -var _global = function(player, params){ +var _global = function(params, player){ if (!player.op){ player.sendMessage("Only operators can set global aliases. " + "You need to be an operator to perform this command."); @@ -118,7 +118,7 @@ var _global = function(player, params){ player.sendMessage("Global alias '" + o.cmd + "' created."); }; -var _list = function(player){ +var _list = function(params, player){ try { var alias = 0; if (_store.players[player.name]){ @@ -139,26 +139,42 @@ var _list = function(player){ throw e; } }; +var _help = function(params, player){ + player.sendMessage('Usage:\n' + _usage); +}; var alias = plugin('alias', { - "store": _store, - "set": _set, - "global": _global, - "delete": _delete, - "list": _list, - "help": function(player){ player.sendMessage("Usage:\n" + _usage);} + store: _store, + set: _set, + global: _global, + remove: _remove, + list: _list, + help: _help }, true ); - -var aliasCmd = command('alias', function(params,invoker){ - var operation = params[0]; +var aliasCmd = command('alias', function( params, invoker ) { + var operation = params[0], fn; if (!operation){ - invoker.sendMessage("Usage:\n" + _usage); + invoker.sendMessage('Usage:\n' + _usage); return; } - if (alias[operation]) - alias[operation](invoker, params.slice(1)); - else - invoker.sendMessage("Usage:\n" + _usage); + /* + wph 20140122 this is kind of dumb but Nashorn has some serious problems + accessing object properties by array index notation + in JRE8 alias[operation] returns null - definitely a bug in Nashorn. + */ + if (operation == 'set'){ + alias.set(params.slice(1), invoker); + }else if (operation == 'global'){ + alias.global(params.slice(1), invoker); + }else if (operation == 'remove'){ + alias.remove(params.slice(1), invoker); + }else if (operation == 'list'){ + alias.list(params.slice(1), invoker); + }else if (operation == 'help'){ + alias.help(params.slice(1), invoker); + }else { + invoker.sendMessage('Usage:\n' + _usage); + } }); var _intercept = function( msg, invoker, exec) @@ -177,7 +193,7 @@ var _intercept = function( msg, invoker, exec) if (config.verbose){ var commandObj = server.commandMap.getCommand(command); if (!commandObj) - console.info("No global alias found for command: " + command); + console.info('No global alias found for command: ' + command); } } /* @@ -192,7 +208,7 @@ var _intercept = function( msg, invoker, exec) if (config.verbose){ var commandObj = server.commandMap.getCommand(command); if (!commandObj) - console.info("No player alias found for command: " + command); + console.info('No player alias found for command: ' + command); } } for (var i = 0;i < template.length; i++) @@ -232,5 +248,5 @@ events.on('server.ServerCommandEvent', function(listener,evt){ var exec = function(cmd){ invoker.server.dispatchCommand(invoker, cmd); }; var isAlias = _intercept(''+evt.command, ''+ invoker.name, exec); if (isAlias) - evt.command = "jsp void"; + evt.command = 'jsp void'; }); From f71d1a4e7812d74228912a947186dce5c673704e Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Thu, 23 Jan 2014 23:09:41 +0000 Subject: [PATCH 107/456] made sc-mqtt module's client object fluent --- src/main/javascript/modules/sc-mqtt.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/javascript/modules/sc-mqtt.js b/src/main/javascript/modules/sc-mqtt.js index 82b9ee8..4287523 100644 --- a/src/main/javascript/modules/sc-mqtt.js +++ b/src/main/javascript/modules/sc-mqtt.js @@ -97,6 +97,7 @@ function Client(brokerUrl, clientId){ }else{ client.connect(options); } + return client; }, publish: function(topic, message, qos, retained){ if (typeof message == 'string'){ @@ -109,21 +110,27 @@ function Client(brokerUrl, clientId){ retained = false; } client.publish(topic, message,qos, retained); + return client; }, subscribe: function(topic){ client.subscribe(topic); + return client; }, unsubscribe: function(topic){ client.unsubscribe(topic); + return client; }, onMessageArrived: function(fn){ callback.setMesgArrived(fn); + return client; }, onDeliveryComplete: function(fn){ callback.setDeliveryComplete(fn); + return client; }, onConnectionLost: function(fn){ callback.setConnLost(fn); + return client; } }; }; From 8c690452e756c30bd0cbc49535ae8f1b4d44f6bb Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Fri, 24 Jan 2014 23:38:56 +0000 Subject: [PATCH 108/456] Added experimental LCDGameClock --- src/main/javascript/lib/command.js | 56 ++-- src/main/javascript/lib/plugin.js | 134 ++++----- src/main/javascript/lib/require.js | 6 +- src/main/javascript/modules/sc-mqtt.js | 156 +++++----- src/main/javascript/plugins/alias/alias.js | 272 +++++++++--------- .../plugins/drone/contrib/lcd-clock.js | 75 +++++ 6 files changed, 390 insertions(+), 309 deletions(-) create mode 100644 src/main/javascript/plugins/drone/contrib/lcd-clock.js diff --git a/src/main/javascript/lib/command.js b/src/main/javascript/lib/command.js index 17928af..61e0c60 100644 --- a/src/main/javascript/lib/command.js +++ b/src/main/javascript/lib/command.js @@ -8,40 +8,40 @@ var _cmdInterceptors = []; execute a JSP command. */ var executeCmd = function(args, player){ - if (args.length === 0) - throw new Error('Usage: jsp command-name command-parameters'); - var name = args[0]; - var cmd = _commands[name]; - if (typeof cmd === 'undefined'){ - // it's not a global command - pass it on to interceptors - var intercepted = false; - for (var i = 0;i < _cmdInterceptors.length;i++){ - if (_cmdInterceptors[i](args,player)) - intercepted = true; - } - if (!intercepted) - console.warn('Command %s is not recognised',name); - }else{ - var result = null; - try { - result = cmd.callback(args.slice(1),player); - }catch (e){ - console.error('Error while trying to execute command: ' + JSON.stringify(args)); - throw e; - } - return result; + if (args.length === 0) + throw new Error('Usage: jsp command-name command-parameters'); + var name = args[0]; + var cmd = _commands[name]; + if (typeof cmd === 'undefined'){ + // it's not a global command - pass it on to interceptors + var intercepted = false; + for (var i = 0;i < _cmdInterceptors.length;i++){ + if (_cmdInterceptors[i](args,player)) + intercepted = true; } + if (!intercepted) + console.warn('Command %s is not recognised',name); + }else{ + var result = null; + try { + result = cmd.callback(args.slice(1),player); + }catch (e){ + console.error('Error while trying to execute command: ' + JSON.stringify(args)); + throw e; + } + return result; + } }; /* define a new JSP command. */ var defineCmd = function(name, func, options, intercepts) { - if (typeof options == 'undefined') - options = []; - _commands[name] = {callback: func, options: options}; - if (intercepts) - _cmdInterceptors.push(func); - return func; + if (typeof options == 'undefined') + options = []; + _commands[name] = {callback: func, options: options}; + if (intercepts) + _cmdInterceptors.push(func); + return func; }; exports.command = defineCmd; exports.commands = _commands; diff --git a/src/main/javascript/lib/plugin.js b/src/main/javascript/lib/plugin.js index fe493f6..a85cbb3 100644 --- a/src/main/javascript/lib/plugin.js +++ b/src/main/javascript/lib/plugin.js @@ -1,8 +1,8 @@ 'use strict'; -var console = require('./console'); -var File = java.io.File; -var FileWriter = java.io.FileWriter; -var PrintWriter = java.io.PrintWriter; +var console = require('./console'), + File = java.io.File, + FileWriter = java.io.FileWriter, + PrintWriter = java.io.PrintWriter; /* plugin management */ @@ -10,22 +10,22 @@ var _plugins = {}; var _plugin = function(/* String */ moduleName, /* Object */ moduleObject, isPersistent) { - // - // don't load plugin more than once - // - if (typeof _plugins[moduleName] != "undefined") - return _plugins[moduleName].module; + // + // don't load plugin more than once + // + if (typeof _plugins[moduleName] != "undefined") + return _plugins[moduleName].module; - var pluginData = {persistent: isPersistent, module: moduleObject}; - if (typeof moduleObject.store == 'undefined') - moduleObject.store = {}; + var pluginData = {persistent: isPersistent, module: moduleObject}; + if (typeof moduleObject.store == 'undefined') + moduleObject.store = {}; - _plugins[moduleName] = pluginData; + _plugins[moduleName] = pluginData; - if (isPersistent){ - moduleObject.store = persist(moduleName, moduleObject.store); - } - return moduleObject; + if (isPersistent){ + moduleObject.store = persist(moduleName, moduleObject.store); + } + return moduleObject; }; exports.plugin = _plugin; @@ -36,59 +36,59 @@ var dataDir = null; exports.autoload = function(dir) { - scriptCraftDir = dir; - pluginDir = new File(dir, "plugins"); - dataDir = new File(dir, "data"); + scriptCraftDir = dir; + pluginDir = new File(dir, "plugins"); + dataDir = new File(dir, "data"); - var _canonize = function(file){ - return '' + file.canonicalPath.replaceAll("\\\\","/"); - }; - /* - recursively walk the given directory and return a list of all .js files - */ - var _listSourceFiles = function(store,dir) - { - var files = dir.listFiles(); - if (!files) - return; - for (var i = 0;i < files.length; i++) { - var file = files[i]; - if (file.isDirectory()){ - _listSourceFiles(store,file); - }else{ - if ( file.canonicalPath.endsWith('.js') ){ - store.push(file); - } - } + var _canonize = function(file){ + return '' + file.canonicalPath.replaceAll("\\\\","/"); + }; + /* + recursively walk the given directory and return a list of all .js files + */ + var _listSourceFiles = function(store,dir) + { + var files = dir.listFiles(); + if (!files) + return; + for (var i = 0;i < files.length; i++) { + var file = files[i]; + if (file.isDirectory()){ + _listSourceFiles(store,file); + }else{ + if ( file.canonicalPath.endsWith('.js') ){ + store.push(file); } - }; - /* - Reload all of the .js files in the given directory - */ - var _reload = function(pluginDir) - { - var sourceFiles = []; - _listSourceFiles(sourceFiles,pluginDir); + } + } + }; + /* + Reload all of the .js files in the given directory + */ + var _reload = function(pluginDir) + { + var sourceFiles = []; + _listSourceFiles(sourceFiles,pluginDir); - var len = sourceFiles.length; - if (config.verbose) - console.info(len + ' scriptcraft plugins found.'); - for (var i = 0;i < len; i++){ - var pluginPath = _canonize(sourceFiles[i]); - var module = {}; - try { - module = require(pluginPath); - for (var property in module){ - /* - all exports in plugins become global - */ - global[property] = module[property]; - } - }catch (e){ - - } + var len = sourceFiles.length; + if (config.verbose) + console.info(len + ' scriptcraft plugins found.'); + for (var i = 0;i < len; i++){ + var pluginPath = _canonize(sourceFiles[i]); + var module = {}; + try { + module = require(pluginPath); + for (var property in module){ + /* + all exports in plugins become global + */ + global[property] = module[property]; } - }; - _reload(pluginDir); + }catch (e){ + + } + } + }; + _reload(pluginDir); }; diff --git a/src/main/javascript/lib/require.js b/src/main/javascript/lib/require.js index 1e3d2ba..64e39e4 100644 --- a/src/main/javascript/lib/require.js +++ b/src/main/javascript/lib/require.js @@ -174,13 +174,13 @@ When resolving module names to file paths, ScriptCraft uses the following rules. wph 20131215 Experimental */ var _loadedModules = {}; - + var _format = java.lang.String.format; var _require = function(parentFile, path) { var file = resolveModuleToFile(path, parentFile); if (!file){ - var errMsg = '' + java.lang.String.format("require() failed to find matching file for module '%s' " + - "in working directory '%s' ", [path, parentFile.canonicalPath]); + var errMsg = '' + _format("require() failed to find matching file for module '%s' " + + "in working directory '%s' ", [path, parentFile.canonicalPath]); if (! ( (''+path).match(/^\./) )){ errMsg = errMsg + ' and not found in paths ' + JSON.stringify(modulePaths); } diff --git a/src/main/javascript/modules/sc-mqtt.js b/src/main/javascript/modules/sc-mqtt.js index 4287523..80b35ab 100644 --- a/src/main/javascript/modules/sc-mqtt.js +++ b/src/main/javascript/modules/sc-mqtt.js @@ -1,3 +1,4 @@ +'use strict'; /************************************************************************* ## sc-mqtt module @@ -62,83 +63,98 @@ library. ***/ var MISSING_MQTT = '\nMissing class org.walterhiggins.scriptcraft.ScriptCraftMqttCallback.\n' + - 'Make sure sc-mqtt.jar is in the classpath.\n' + - 'See http://github.com/walterhiggins/scriptcraft-extras-mqtt for details.\n'; + 'Make sure sc-mqtt.jar is in the classpath.\n' + + 'See http://github.com/walterhiggins/scriptcraft-extras-mqtt for details.\n'; function Client(brokerUrl, clientId){ - var Callback = org.walterhiggins.scriptcraft.ScriptCraftMqttCallback; - var MqttClient = org.eclipse.paho.client.mqttv3.MqttClient; + var Callback = org.walterhiggins.scriptcraft.ScriptCraftMqttCallback; + var MqttClient = org.eclipse.paho.client.mqttv3.MqttClient; - var callback = new Callback( - function(err){ - console.log('connectionLost: ' + err); - }, - function(topic, message){ - console.log('messageArrived ' + topic + '> ' + message); - }, - function(token){ - console.log('deliveryComplete:' + token); - } - ); - - if (!brokerUrl){ - brokerUrl = 'tcp://localhost:1883'; + var callback = new Callback( + function(err){ + console.log('connectionLost: ' + err); + }, + function(topic, message){ + console.log('messageArrived ' + topic + '> ' + message); + }, + function(token){ + console.log('deliveryComplete:' + token); } - if (!clientId){ - clientId = 'scriptcraft'; + ); + + if (!brokerUrl){ + brokerUrl = 'tcp://localhost:1883'; + } + if (!clientId){ + clientId = 'scriptcraft' + new Date().getTime(); + } + var client = new MqttClient(brokerUrl, clientId, null); + client.setCallback(callback); + return { + + connect: function(options){ + if (typeof options === 'undefined'){ + client.connect(); + }else{ + client.connect(options); + } + return client; + }, + + disconnect: function(quiesceTimeout){ + if (typeof quiesceTimeout == 'undefined') + client.disconnect(); + else + client.disconnect(quiesceTimeout); + return client; + }, + + publish: function(topic, message, qos, retained){ + if (typeof message == 'string'){ + message = new java.lang.String(message).bytes; + } + if (typeof qos == 'undefined'){ + qos = 1; + } + if (typeof retained == 'undefined'){ + retained = false; + } + client.publish(topic, message,qos, retained); + return client; + }, + + subscribe: function(topic){ + client.subscribe(topic); + return client; + }, + + unsubscribe: function(topic){ + client.unsubscribe(topic); + return client; + }, + + onMessageArrived: function(fn){ + callback.setMesgArrived(fn); + return client; + }, + + onDeliveryComplete: function(fn){ + callback.setDeliveryComplete(fn); + return client; + }, + + onConnectionLost: function(fn){ + callback.setConnLost(fn); + return client; } - var client = new MqttClient(brokerUrl, clientId, null); - client.setCallback(callback); - return { - connect: function(options){ - if (typeof options === 'undefined'){ - client.connect(); - }else{ - client.connect(options); - } - return client; - }, - publish: function(topic, message, qos, retained){ - if (typeof message == 'string'){ - message = new java.lang.String(message).bytes; - } - if (typeof qos == 'undefined'){ - qos = 1; - } - if (typeof retained == 'undefined'){ - retained = false; - } - client.publish(topic, message,qos, retained); - return client; - }, - subscribe: function(topic){ - client.subscribe(topic); - return client; - }, - unsubscribe: function(topic){ - client.unsubscribe(topic); - return client; - }, - onMessageArrived: function(fn){ - callback.setMesgArrived(fn); - return client; - }, - onDeliveryComplete: function(fn){ - callback.setDeliveryComplete(fn); - return client; - }, - onConnectionLost: function(fn){ - callback.setConnLost(fn); - return client; - } - }; -}; + }; +} exports.client = function(brokerUrl, clientId, options){ - if (typeof org.walterhiggins.scriptcraft.ScriptCraftMqttCallback != 'function'){ - throw MISSING_MQTT; - } - return new Client(brokerUrl, clientId, options); + if (typeof org.walterhiggins.scriptcraft.ScriptCraftMqttCallback != 'function'){ + throw MISSING_MQTT; + } + return new Client(brokerUrl, clientId, options); }; diff --git a/src/main/javascript/plugins/alias/alias.js b/src/main/javascript/plugins/alias/alias.js index 4941de9..e8be545 100644 --- a/src/main/javascript/plugins/alias/alias.js +++ b/src/main/javascript/plugins/alias/alias.js @@ -1,3 +1,4 @@ +'use strict'; /************************************************************************* ## alias Plugin @@ -65,8 +66,8 @@ Execute the alias : \n \ persist aliases */ var _store = { - players: {}, - global: {} + players: {}, + global: {} }; /* turns 'cw = time set {1} ; weather {2}' into {cmd: 'cw', aliases: ['time set {1}', 'weather {2}']} @@ -74,159 +75,148 @@ var _store = { used for the 'set' and 'global' options. */ var _processParams = function(params){ - var paramStr = params.join(' '); - var eqPos = paramStr.indexOf('='); - var aliasCmd = paramStr.substring(0,eqPos).trim(); - var aliasValue = paramStr.substring(eqPos+1).trim(); - return { cmd: aliasCmd, aliases: aliasValue.split(/\s*;\s*/) }; + var paramStr = params.join(' '), + eqPos = paramStr.indexOf('='), + aliasCmd = paramStr.substring(0,eqPos).trim(), + aliasValue = paramStr.substring(eqPos+1).trim(); + return { + cmd: aliasCmd, + aliases: aliasValue.split(/\s*;\s*/) + }; }; var _set = function(params, player){ - var playerAliases = _store.players[player.name]; - if (!playerAliases){ - playerAliases = {}; - } - var o = _processParams(params); - playerAliases[o.cmd] = o.aliases; - _store.players[player.name] = playerAliases; - player.sendMessage("Alias '" + o.cmd + "' created."); + var playerAliases = _store.players[player.name]; + if (!playerAliases){ + playerAliases = {}; + } + var o = _processParams(params); + playerAliases[o.cmd] = o.aliases; + _store.players[player.name] = playerAliases; + player.sendMessage("Alias '" + o.cmd + "' created."); }; var _remove = function(params, player){ - if (_store.players[player.name] && - _store.players[player.name][params[0]]){ - delete _store.players[player.name][params[0]]; - player.sendMessage("Alias '" + params[0] + "' removed."); - } - else{ - player.sendMessage("Alias '" + params[0] + "' does not exist."); - } - if (player.op){ - if (_store.global[params[0]]) - delete _store.global[params[0]]; - } + if (_store.players[player.name] && + _store.players[player.name][params[0]]){ + delete _store.players[player.name][params[0]]; + player.sendMessage("Alias '" + params[0] + "' removed."); + } + else{ + player.sendMessage("Alias '" + params[0] + "' does not exist."); + } + if (player.op){ + if (_store.global[params[0]]) + delete _store.global[params[0]]; + } }; var _global = function(params, player){ - if (!player.op){ - player.sendMessage("Only operators can set global aliases. " + - "You need to be an operator to perform this command."); - return; - } - var o = _processParams(params); - _store.global[o.cmd] = o.aliases; - player.sendMessage("Global alias '" + o.cmd + "' created."); + if (!player.op){ + player.sendMessage("Only operators can set global aliases. " + + "You need to be an operator to perform this command."); + return; + } + var o = _processParams(params); + _store.global[o.cmd] = o.aliases; + player.sendMessage("Global alias '" + o.cmd + "' created."); }; var _list = function(params, player){ - try { - var alias = 0; - if (_store.players[player.name]){ - player.sendMessage("Your aliases:"); - for (alias in _store.players[player.name]){ - player.sendMessage(alias + " = " + - JSON.stringify(_store.players[player.name][alias])); - } - }else{ - player.sendMessage("You have no player-specific aliases."); - } - player.sendMessage("Global aliases:"); - for (alias in _store.global){ - player.sendMessage(alias + " = " + JSON.stringify(_store.global[alias]) ); - } - }catch(e){ - console.error("Error in list function: " + e.message); - throw e; + var alias = 0; + try { + if (_store.players[player.name]){ + player.sendMessage("Your aliases:"); + for (alias in _store.players[player.name]){ + player.sendMessage(alias + " = " + + JSON.stringify(_store.players[player.name][alias])); + } + }else{ + player.sendMessage("You have no player-specific aliases."); } + player.sendMessage("Global aliases:"); + for (alias in _store.global){ + player.sendMessage(alias + " = " + JSON.stringify(_store.global[alias]) ); + } + }catch(e){ + console.error("Error in list function: " + e.message); + throw e; + } }; var _help = function(params, player){ - player.sendMessage('Usage:\n' + _usage); + player.sendMessage('Usage:\n' + _usage); }; + var alias = plugin('alias', { - store: _store, - set: _set, - global: _global, - remove: _remove, - list: _list, - help: _help + store: _store, + set: _set, + global: _global, + remove: _remove, + list: _list, + help: _help }, true ); var aliasCmd = command('alias', function( params, invoker ) { - var operation = params[0], fn; - if (!operation){ - invoker.sendMessage('Usage:\n' + _usage); - return; - } - /* - wph 20140122 this is kind of dumb but Nashorn has some serious problems - accessing object properties by array index notation - in JRE8 alias[operation] returns null - definitely a bug in Nashorn. - */ - if (operation == 'set'){ - alias.set(params.slice(1), invoker); - }else if (operation == 'global'){ - alias.global(params.slice(1), invoker); - }else if (operation == 'remove'){ - alias.remove(params.slice(1), invoker); - }else if (operation == 'list'){ - alias.list(params.slice(1), invoker); - }else if (operation == 'help'){ - alias.help(params.slice(1), invoker); - }else { - invoker.sendMessage('Usage:\n' + _usage); + var operation = params[0], + fn; + if (!operation){ + invoker.sendMessage('Usage:\n' + _usage); + return; + } + /* + wph 20140122 this is kind of dumb but Nashorn has some serious problems + accessing object properties by array index notation + in JRE8 alias[operation] returns null - definitely a bug in Nashorn. + */ + for (var key in alias){ + if (key == operation){ + fn = alias[key]; + fn(params.slice(1),invoker); + return; } + } + invoker.sendMessage('Usage:\n' + _usage); }); var _intercept = function( msg, invoker, exec) { - if (msg.trim().length == 0) - return false; - var msgParts = msg.split(' '); - var command = msg.match(/^\/*([^\s]+)/)[1]; - - var template = [], isAlias = false, cmds = []; - - if (_store.global[command]){ - template = _store.global[command]; - isAlias = true; - }else{ - if (config.verbose){ - var commandObj = server.commandMap.getCommand(command); - if (!commandObj) - console.info('No global alias found for command: ' + command); - } - } - /* - allows player-specific aliases to override global aliases - */ - if (_store.players[invoker] && - _store.players[invoker][command]) - { - template = _store.players[invoker][command]; - isAlias = true; - }else{ - if (config.verbose){ - var commandObj = server.commandMap.getCommand(command); - if (!commandObj) - console.info('No player alias found for command: ' + command); - } - } - for (var i = 0;i < template.length; i++) - { - var filledinCommand = template[i].replace(/{([0-9]+)}/g, function (match,index){ - index = parseInt(index,10); - if (msgParts[index]) - return msgParts[index] - else - return match; - }); - cmds.push(filledinCommand); - } - - for (var i = 0; i< cmds.length; i++){ - exec(cmds[i]); - } - return isAlias; + if (msg.trim().length == 0) + return false; + var msgParts = msg.split(' '), + command = msg.match(/^\/*([^\s]+)/)[1], + template = [], isAlias = false, cmds = [], + commandObj, + filledinCommand; + + if (_store.global[command]){ + template = _store.global[command]; + isAlias = true; + } + /* + allows player-specific aliases to override global aliases + */ + if (_store.players[invoker] && + _store.players[invoker][command]) + { + template = _store.players[invoker][command]; + isAlias = true; + } + for (var i = 0;i < template.length; i++) + { + filledinCommand = template[i].replace(/{([0-9]+)}/g, function (match,index){ + index = parseInt(index,10); + if (msgParts[index]) + return msgParts[index]; + else + return match; + }); + cmds.push(filledinCommand); + } + + for (var i = 0; i< cmds.length; i++){ + exec(cmds[i]); + } + return isAlias; }; /* @@ -234,19 +224,19 @@ var _intercept = function( msg, invoker, exec) command about to be issued matches an alias. */ events.on('player.PlayerCommandPreprocessEvent', function(listener,evt){ - var invoker = evt.player; - var exec = function(cmd){ invoker.performCommand(cmd);}; - var isAlias = _intercept(''+evt.message, ''+invoker.name, exec); - if (isAlias) - evt.cancelled = true; - + var invoker = evt.player; + var exec = function(cmd){ invoker.performCommand(cmd);}; + var isAlias = _intercept(''+evt.message, ''+invoker.name, exec); + if (isAlias) + evt.cancelled = true; }); /* define a 'void' command because ServerCommandEvent can't be canceled */ command('void',function(){}); + events.on('server.ServerCommandEvent', function(listener,evt){ - var invoker = evt.sender; - var exec = function(cmd){ invoker.server.dispatchCommand(invoker, cmd); }; - var isAlias = _intercept(''+evt.command, ''+ invoker.name, exec); - if (isAlias) - evt.command = 'jsp void'; + var invoker = evt.sender; + var exec = function(cmd){ invoker.server.dispatchCommand(invoker, cmd); }; + var isAlias = _intercept(''+evt.command, ''+ invoker.name, exec); + if (isAlias) + evt.command = 'jsp void'; }); diff --git a/src/main/javascript/plugins/drone/contrib/lcd-clock.js b/src/main/javascript/plugins/drone/contrib/lcd-clock.js new file mode 100644 index 0000000..48cf7f2 --- /dev/null +++ b/src/main/javascript/plugins/drone/contrib/lcd-clock.js @@ -0,0 +1,75 @@ +/* + Experimental: + Point at a block and issue the following ... + /js var d = new Drone(); + /js var clock = new LCDClock(d); + /js clock.start24(); + ... start the clock... + /js clock.stop24(); + ... stops the clock... +*/ +var Drone = require('../drone').Drone; +var blocktype = require('../blocktype'); + + +exports.LCDClock = function(drone, fgColor,bgColor,border) { + var lastSecs = [0,0,0,0], + world = drone.world, + intervalId = -1; + + if (typeof bgColor == 'undefined') + bgColor = '35:15'; // black wool + + if (typeof fgColor == 'undefined') + fgColor = 35 ; // white wool + + if (border){ + drone.box(border,21,9,1); + drone.up().right(); + } + drone.blocktype('00:00',fgColor,bgColor); + return { + start24: function(){ + var clock = this; + function tick(){ + var rolloverMins = 24*60; + var timeOfDayInMins = Math.floor(((world.time + 6000) % 24000) / 16.6667); + timeOfDayInMins = timeOfDayInMins % rolloverMins; + console.log('Minecraft time: ' + world.time + ' timeOfDayInMins: ' + timeOfDayInMins); + clock.update(timeOfDayInMins); + }; + intervalId = setInterval(tick, 800); + }, + stop24: function(){ + clearInterval(intervalId); + }, + update: function(secs){ + var digits = [0,0,0,0], + s = secs % 60; + m = (secs - s) / 60; + digits[3] = s%10; + digits[2] = (s-digits[3])/10; + digits[1] = m%10; + digits[0] = (m-digits[1])/10; + // + // updating all 4 digits each time is expensive + // only update digits which have changed (in most cases - just 1) + // + if (digits[3] != lastSecs[3]) + drone.right(14).blocktype(''+digits[3],fgColor,bgColor).left(14); + if (digits[2] != lastSecs[2]) + drone.right(10).blocktype(''+digits[2],fgColor,bgColor).left(10); + if (digits[1] != lastSecs[1]) + drone.right(4).blocktype(''+digits[1], fgColor, bgColor).left(4); + if (digits[0] != lastSecs[0]) + drone.blocktype(''+digits[0], fgColor, bgColor); + + lastSecs[0] = digits[0]; + lastSecs[1] = digits[1]; + lastSecs[2] = digits[2]; + lastSecs[3] = digits[3]; + + } + }; +}; + From 7a7767c83c21d07d3aac3b02b69396726b559ba2 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 25 Jan 2014 09:04:16 +0000 Subject: [PATCH 109/456] Added logging of errors during plugin autoload. --- build.xml | 14 +++++++------- src/docs/{javascript => js}/generateApiDocs.js | 0 src/docs/{javascript => js}/generateTOC.js | 0 src/main/{javascript => js}/lib/command.js | 0 src/main/{javascript => js}/lib/console.js | 0 src/main/{javascript => js}/lib/events.js | 0 src/main/{javascript => js}/lib/js-patch.js | 0 src/main/{javascript => js}/lib/json2.js | 0 src/main/{javascript => js}/lib/persistence.js | 0 src/main/{javascript => js}/lib/plugin.js | 12 ++++++------ src/main/{javascript => js}/lib/readme.md | 0 src/main/{javascript => js}/lib/require.js | 0 src/main/{javascript => js}/lib/scriptcraft.js | 2 +- src/main/{javascript => js}/lib/tabcomplete-jsp.js | 0 src/main/{javascript => js}/lib/tabcomplete.js | 0 src/main/{javascript => js}/modules/blocks.js | 0 .../modules/fireworks/fireworks.js | 0 .../modules/fireworks/package.json | 0 .../{javascript => js}/modules/http/package.json | 0 .../{javascript => js}/modules/http/request.js | 0 .../modules/minigames/scoreboard.js | 0 src/main/{javascript => js}/modules/partial.js | 0 src/main/{javascript => js}/modules/sc-mqtt.js | 0 src/main/{javascript => js}/modules/signs/menu.js | 0 .../{javascript => js}/modules/signs/package.json | 0 src/main/{javascript => js}/modules/signs/signs.js | 0 .../modules/underscore/package.json | 0 .../modules/underscore/underscore.js | 0 .../{javascript => js}/modules/utils/package.json | 0 .../modules/utils/string-exts.js | 0 src/main/{javascript => js}/modules/utils/utils.js | 0 src/main/{javascript => js}/plugins/alias/alias.js | 0 src/main/{javascript => js}/plugins/arrows.js | 0 src/main/{javascript => js}/plugins/chat/color.js | 0 .../plugins/classroom/classroom.js | 0 .../plugins/commando/commando-test.js | 0 .../plugins/commando/commando.js | 0 .../{javascript => js}/plugins/drone/blocktype.js | 0 .../plugins/drone/contrib/castle.js | 0 .../plugins/drone/contrib/chessboard.js | 0 .../plugins/drone/contrib/cottage.js | 0 .../plugins/drone/contrib/dancefloor.js | 0 .../plugins/drone/contrib/fort.js | 0 .../plugins/drone/contrib/lcd-clock.js | 0 .../plugins/drone/contrib/logo.js | 0 .../plugins/drone/contrib/rainbow.js | 0 .../plugins/drone/contrib/rboxcall.js | 0 .../plugins/drone/contrib/redstonewire.js | 0 .../plugins/drone/contrib/skyscraper-example.js | 0 .../plugins/drone/contrib/spiral_stairs.js | 0 .../plugins/drone/contrib/streamer.js | 0 .../plugins/drone/contrib/temple.js | 0 .../plugins/drone/drone-firework.js | 0 src/main/{javascript => js}/plugins/drone/drone.js | 0 .../{javascript => js}/plugins/drone/sphere.js | 0 src/main/{javascript => js}/plugins/drone/test.js | 0 .../plugins/examples/example-1-hello-module.js | 0 .../plugins/examples/example-2-hello-command.js | 0 .../plugins/examples/example-3-hello-ops-only.js | 0 .../plugins/examples/example-4-hello-parameters.js | 0 .../examples/example-5-hello-using-module.js | 0 .../plugins/examples/example-6-hello-player.js | 0 .../plugins/examples/example-7-hello-events.js | 0 src/main/{javascript => js}/plugins/homes/homes.js | 0 .../plugins/minigames/NumberGuess.js | 0 .../plugins/minigames/SnowballFight.js | 0 .../plugins/minigames/cow-clicker.js | 0 .../{javascript => js}/plugins/signs/examples.js | 0 src/main/{javascript => js}/plugins/spawn.js | 0 src/main/{javascript => js}/readme.md | 0 70 files changed, 14 insertions(+), 14 deletions(-) rename src/docs/{javascript => js}/generateApiDocs.js (100%) rename src/docs/{javascript => js}/generateTOC.js (100%) rename src/main/{javascript => js}/lib/command.js (100%) rename src/main/{javascript => js}/lib/console.js (100%) rename src/main/{javascript => js}/lib/events.js (100%) rename src/main/{javascript => js}/lib/js-patch.js (100%) rename src/main/{javascript => js}/lib/json2.js (100%) rename src/main/{javascript => js}/lib/persistence.js (100%) rename src/main/{javascript => js}/lib/plugin.js (87%) rename src/main/{javascript => js}/lib/readme.md (100%) rename src/main/{javascript => js}/lib/require.js (100%) rename src/main/{javascript => js}/lib/scriptcraft.js (97%) rename src/main/{javascript => js}/lib/tabcomplete-jsp.js (100%) rename src/main/{javascript => js}/lib/tabcomplete.js (100%) rename src/main/{javascript => js}/modules/blocks.js (100%) rename src/main/{javascript => js}/modules/fireworks/fireworks.js (100%) rename src/main/{javascript => js}/modules/fireworks/package.json (100%) rename src/main/{javascript => js}/modules/http/package.json (100%) rename src/main/{javascript => js}/modules/http/request.js (100%) rename src/main/{javascript => js}/modules/minigames/scoreboard.js (100%) rename src/main/{javascript => js}/modules/partial.js (100%) rename src/main/{javascript => js}/modules/sc-mqtt.js (100%) rename src/main/{javascript => js}/modules/signs/menu.js (100%) rename src/main/{javascript => js}/modules/signs/package.json (100%) rename src/main/{javascript => js}/modules/signs/signs.js (100%) rename src/main/{javascript => js}/modules/underscore/package.json (100%) rename src/main/{javascript => js}/modules/underscore/underscore.js (100%) rename src/main/{javascript => js}/modules/utils/package.json (100%) rename src/main/{javascript => js}/modules/utils/string-exts.js (100%) rename src/main/{javascript => js}/modules/utils/utils.js (100%) rename src/main/{javascript => js}/plugins/alias/alias.js (100%) rename src/main/{javascript => js}/plugins/arrows.js (100%) rename src/main/{javascript => js}/plugins/chat/color.js (100%) rename src/main/{javascript => js}/plugins/classroom/classroom.js (100%) rename src/main/{javascript => js}/plugins/commando/commando-test.js (100%) rename src/main/{javascript => js}/plugins/commando/commando.js (100%) rename src/main/{javascript => js}/plugins/drone/blocktype.js (100%) rename src/main/{javascript => js}/plugins/drone/contrib/castle.js (100%) rename src/main/{javascript => js}/plugins/drone/contrib/chessboard.js (100%) rename src/main/{javascript => js}/plugins/drone/contrib/cottage.js (100%) rename src/main/{javascript => js}/plugins/drone/contrib/dancefloor.js (100%) rename src/main/{javascript => js}/plugins/drone/contrib/fort.js (100%) rename src/main/{javascript => js}/plugins/drone/contrib/lcd-clock.js (100%) rename src/main/{javascript => js}/plugins/drone/contrib/logo.js (100%) rename src/main/{javascript => js}/plugins/drone/contrib/rainbow.js (100%) rename src/main/{javascript => js}/plugins/drone/contrib/rboxcall.js (100%) rename src/main/{javascript => js}/plugins/drone/contrib/redstonewire.js (100%) rename src/main/{javascript => js}/plugins/drone/contrib/skyscraper-example.js (100%) rename src/main/{javascript => js}/plugins/drone/contrib/spiral_stairs.js (100%) rename src/main/{javascript => js}/plugins/drone/contrib/streamer.js (100%) rename src/main/{javascript => js}/plugins/drone/contrib/temple.js (100%) rename src/main/{javascript => js}/plugins/drone/drone-firework.js (100%) rename src/main/{javascript => js}/plugins/drone/drone.js (100%) rename src/main/{javascript => js}/plugins/drone/sphere.js (100%) rename src/main/{javascript => js}/plugins/drone/test.js (100%) rename src/main/{javascript => js}/plugins/examples/example-1-hello-module.js (100%) rename src/main/{javascript => js}/plugins/examples/example-2-hello-command.js (100%) rename src/main/{javascript => js}/plugins/examples/example-3-hello-ops-only.js (100%) rename src/main/{javascript => js}/plugins/examples/example-4-hello-parameters.js (100%) rename src/main/{javascript => js}/plugins/examples/example-5-hello-using-module.js (100%) rename src/main/{javascript => js}/plugins/examples/example-6-hello-player.js (100%) rename src/main/{javascript => js}/plugins/examples/example-7-hello-events.js (100%) rename src/main/{javascript => js}/plugins/homes/homes.js (100%) rename src/main/{javascript => js}/plugins/minigames/NumberGuess.js (100%) rename src/main/{javascript => js}/plugins/minigames/SnowballFight.js (100%) rename src/main/{javascript => js}/plugins/minigames/cow-clicker.js (100%) rename src/main/{javascript => js}/plugins/signs/examples.js (100%) rename src/main/{javascript => js}/plugins/spawn.js (100%) rename src/main/{javascript => js}/readme.md (100%) diff --git a/build.xml b/build.xml index 4fe8977..7deaa2c 100644 --- a/build.xml +++ b/build.xml @@ -75,8 +75,8 @@ - - + + @@ -103,7 +103,7 @@ Walter Higgins - + @@ -113,7 +113,7 @@ Walter Higgins - + @@ -134,7 +134,7 @@ Walter Higgins @@ -142,7 +142,7 @@ Walter Higgins @@ -150,7 +150,7 @@ Walter Higgins diff --git a/src/docs/javascript/generateApiDocs.js b/src/docs/js/generateApiDocs.js similarity index 100% rename from src/docs/javascript/generateApiDocs.js rename to src/docs/js/generateApiDocs.js diff --git a/src/docs/javascript/generateTOC.js b/src/docs/js/generateTOC.js similarity index 100% rename from src/docs/javascript/generateTOC.js rename to src/docs/js/generateTOC.js diff --git a/src/main/javascript/lib/command.js b/src/main/js/lib/command.js similarity index 100% rename from src/main/javascript/lib/command.js rename to src/main/js/lib/command.js diff --git a/src/main/javascript/lib/console.js b/src/main/js/lib/console.js similarity index 100% rename from src/main/javascript/lib/console.js rename to src/main/js/lib/console.js diff --git a/src/main/javascript/lib/events.js b/src/main/js/lib/events.js similarity index 100% rename from src/main/javascript/lib/events.js rename to src/main/js/lib/events.js diff --git a/src/main/javascript/lib/js-patch.js b/src/main/js/lib/js-patch.js similarity index 100% rename from src/main/javascript/lib/js-patch.js rename to src/main/js/lib/js-patch.js diff --git a/src/main/javascript/lib/json2.js b/src/main/js/lib/json2.js similarity index 100% rename from src/main/javascript/lib/json2.js rename to src/main/js/lib/json2.js diff --git a/src/main/javascript/lib/persistence.js b/src/main/js/lib/persistence.js similarity index 100% rename from src/main/javascript/lib/persistence.js rename to src/main/js/lib/persistence.js diff --git a/src/main/javascript/lib/plugin.js b/src/main/js/lib/plugin.js similarity index 87% rename from src/main/javascript/lib/plugin.js rename to src/main/js/lib/plugin.js index a85cbb3..819461d 100644 --- a/src/main/javascript/lib/plugin.js +++ b/src/main/js/lib/plugin.js @@ -13,7 +13,7 @@ var _plugin = function(/* String */ moduleName, /* Object */ moduleObject, isPer // // don't load plugin more than once // - if (typeof _plugins[moduleName] != "undefined") + if (typeof _plugins[moduleName] != 'undefined') return _plugins[moduleName].module; var pluginData = {persistent: isPersistent, module: moduleObject}; @@ -34,14 +34,14 @@ var scriptCraftDir = null; var pluginDir = null; var dataDir = null; -exports.autoload = function(dir) { +exports.autoload = function(dir,logger) { scriptCraftDir = dir; - pluginDir = new File(dir, "plugins"); - dataDir = new File(dir, "data"); + pluginDir = new File(dir, 'plugins'); + dataDir = new File(dir, 'data'); var _canonize = function(file){ - return '' + file.canonicalPath.replaceAll("\\\\","/"); + return '' + file.canonicalPath.replaceAll('\\\\','/'); }; /* recursively walk the given directory and return a list of all .js files @@ -85,7 +85,7 @@ exports.autoload = function(dir) { global[property] = module[property]; } }catch (e){ - + logger.severe('Plugin ' + pluginPath + ' ' + e); } } }; diff --git a/src/main/javascript/lib/readme.md b/src/main/js/lib/readme.md similarity index 100% rename from src/main/javascript/lib/readme.md rename to src/main/js/lib/readme.md diff --git a/src/main/javascript/lib/require.js b/src/main/js/lib/require.js similarity index 100% rename from src/main/javascript/lib/require.js rename to src/main/js/lib/require.js diff --git a/src/main/javascript/lib/scriptcraft.js b/src/main/js/lib/scriptcraft.js similarity index 97% rename from src/main/javascript/lib/scriptcraft.js rename to src/main/js/lib/scriptcraft.js index 929b7a7..a8d393f 100644 --- a/src/main/javascript/lib/scriptcraft.js +++ b/src/main/js/lib/scriptcraft.js @@ -625,7 +625,7 @@ function __onEnable (__engine, __plugin, __script) return result; }; - plugins.autoload(jsPluginsRootDir); + plugins.autoload(jsPluginsRootDir,logger); /* wph 20140102 - warn if legacy 'craftbukkit/js-plugins' or 'craftbukkit/scriptcraft' directories are present */ diff --git a/src/main/javascript/lib/tabcomplete-jsp.js b/src/main/js/lib/tabcomplete-jsp.js similarity index 100% rename from src/main/javascript/lib/tabcomplete-jsp.js rename to src/main/js/lib/tabcomplete-jsp.js diff --git a/src/main/javascript/lib/tabcomplete.js b/src/main/js/lib/tabcomplete.js similarity index 100% rename from src/main/javascript/lib/tabcomplete.js rename to src/main/js/lib/tabcomplete.js diff --git a/src/main/javascript/modules/blocks.js b/src/main/js/modules/blocks.js similarity index 100% rename from src/main/javascript/modules/blocks.js rename to src/main/js/modules/blocks.js diff --git a/src/main/javascript/modules/fireworks/fireworks.js b/src/main/js/modules/fireworks/fireworks.js similarity index 100% rename from src/main/javascript/modules/fireworks/fireworks.js rename to src/main/js/modules/fireworks/fireworks.js diff --git a/src/main/javascript/modules/fireworks/package.json b/src/main/js/modules/fireworks/package.json similarity index 100% rename from src/main/javascript/modules/fireworks/package.json rename to src/main/js/modules/fireworks/package.json diff --git a/src/main/javascript/modules/http/package.json b/src/main/js/modules/http/package.json similarity index 100% rename from src/main/javascript/modules/http/package.json rename to src/main/js/modules/http/package.json diff --git a/src/main/javascript/modules/http/request.js b/src/main/js/modules/http/request.js similarity index 100% rename from src/main/javascript/modules/http/request.js rename to src/main/js/modules/http/request.js diff --git a/src/main/javascript/modules/minigames/scoreboard.js b/src/main/js/modules/minigames/scoreboard.js similarity index 100% rename from src/main/javascript/modules/minigames/scoreboard.js rename to src/main/js/modules/minigames/scoreboard.js diff --git a/src/main/javascript/modules/partial.js b/src/main/js/modules/partial.js similarity index 100% rename from src/main/javascript/modules/partial.js rename to src/main/js/modules/partial.js diff --git a/src/main/javascript/modules/sc-mqtt.js b/src/main/js/modules/sc-mqtt.js similarity index 100% rename from src/main/javascript/modules/sc-mqtt.js rename to src/main/js/modules/sc-mqtt.js diff --git a/src/main/javascript/modules/signs/menu.js b/src/main/js/modules/signs/menu.js similarity index 100% rename from src/main/javascript/modules/signs/menu.js rename to src/main/js/modules/signs/menu.js diff --git a/src/main/javascript/modules/signs/package.json b/src/main/js/modules/signs/package.json similarity index 100% rename from src/main/javascript/modules/signs/package.json rename to src/main/js/modules/signs/package.json diff --git a/src/main/javascript/modules/signs/signs.js b/src/main/js/modules/signs/signs.js similarity index 100% rename from src/main/javascript/modules/signs/signs.js rename to src/main/js/modules/signs/signs.js diff --git a/src/main/javascript/modules/underscore/package.json b/src/main/js/modules/underscore/package.json similarity index 100% rename from src/main/javascript/modules/underscore/package.json rename to src/main/js/modules/underscore/package.json diff --git a/src/main/javascript/modules/underscore/underscore.js b/src/main/js/modules/underscore/underscore.js similarity index 100% rename from src/main/javascript/modules/underscore/underscore.js rename to src/main/js/modules/underscore/underscore.js diff --git a/src/main/javascript/modules/utils/package.json b/src/main/js/modules/utils/package.json similarity index 100% rename from src/main/javascript/modules/utils/package.json rename to src/main/js/modules/utils/package.json diff --git a/src/main/javascript/modules/utils/string-exts.js b/src/main/js/modules/utils/string-exts.js similarity index 100% rename from src/main/javascript/modules/utils/string-exts.js rename to src/main/js/modules/utils/string-exts.js diff --git a/src/main/javascript/modules/utils/utils.js b/src/main/js/modules/utils/utils.js similarity index 100% rename from src/main/javascript/modules/utils/utils.js rename to src/main/js/modules/utils/utils.js diff --git a/src/main/javascript/plugins/alias/alias.js b/src/main/js/plugins/alias/alias.js similarity index 100% rename from src/main/javascript/plugins/alias/alias.js rename to src/main/js/plugins/alias/alias.js diff --git a/src/main/javascript/plugins/arrows.js b/src/main/js/plugins/arrows.js similarity index 100% rename from src/main/javascript/plugins/arrows.js rename to src/main/js/plugins/arrows.js diff --git a/src/main/javascript/plugins/chat/color.js b/src/main/js/plugins/chat/color.js similarity index 100% rename from src/main/javascript/plugins/chat/color.js rename to src/main/js/plugins/chat/color.js diff --git a/src/main/javascript/plugins/classroom/classroom.js b/src/main/js/plugins/classroom/classroom.js similarity index 100% rename from src/main/javascript/plugins/classroom/classroom.js rename to src/main/js/plugins/classroom/classroom.js diff --git a/src/main/javascript/plugins/commando/commando-test.js b/src/main/js/plugins/commando/commando-test.js similarity index 100% rename from src/main/javascript/plugins/commando/commando-test.js rename to src/main/js/plugins/commando/commando-test.js diff --git a/src/main/javascript/plugins/commando/commando.js b/src/main/js/plugins/commando/commando.js similarity index 100% rename from src/main/javascript/plugins/commando/commando.js rename to src/main/js/plugins/commando/commando.js diff --git a/src/main/javascript/plugins/drone/blocktype.js b/src/main/js/plugins/drone/blocktype.js similarity index 100% rename from src/main/javascript/plugins/drone/blocktype.js rename to src/main/js/plugins/drone/blocktype.js diff --git a/src/main/javascript/plugins/drone/contrib/castle.js b/src/main/js/plugins/drone/contrib/castle.js similarity index 100% rename from src/main/javascript/plugins/drone/contrib/castle.js rename to src/main/js/plugins/drone/contrib/castle.js diff --git a/src/main/javascript/plugins/drone/contrib/chessboard.js b/src/main/js/plugins/drone/contrib/chessboard.js similarity index 100% rename from src/main/javascript/plugins/drone/contrib/chessboard.js rename to src/main/js/plugins/drone/contrib/chessboard.js diff --git a/src/main/javascript/plugins/drone/contrib/cottage.js b/src/main/js/plugins/drone/contrib/cottage.js similarity index 100% rename from src/main/javascript/plugins/drone/contrib/cottage.js rename to src/main/js/plugins/drone/contrib/cottage.js diff --git a/src/main/javascript/plugins/drone/contrib/dancefloor.js b/src/main/js/plugins/drone/contrib/dancefloor.js similarity index 100% rename from src/main/javascript/plugins/drone/contrib/dancefloor.js rename to src/main/js/plugins/drone/contrib/dancefloor.js diff --git a/src/main/javascript/plugins/drone/contrib/fort.js b/src/main/js/plugins/drone/contrib/fort.js similarity index 100% rename from src/main/javascript/plugins/drone/contrib/fort.js rename to src/main/js/plugins/drone/contrib/fort.js diff --git a/src/main/javascript/plugins/drone/contrib/lcd-clock.js b/src/main/js/plugins/drone/contrib/lcd-clock.js similarity index 100% rename from src/main/javascript/plugins/drone/contrib/lcd-clock.js rename to src/main/js/plugins/drone/contrib/lcd-clock.js diff --git a/src/main/javascript/plugins/drone/contrib/logo.js b/src/main/js/plugins/drone/contrib/logo.js similarity index 100% rename from src/main/javascript/plugins/drone/contrib/logo.js rename to src/main/js/plugins/drone/contrib/logo.js diff --git a/src/main/javascript/plugins/drone/contrib/rainbow.js b/src/main/js/plugins/drone/contrib/rainbow.js similarity index 100% rename from src/main/javascript/plugins/drone/contrib/rainbow.js rename to src/main/js/plugins/drone/contrib/rainbow.js diff --git a/src/main/javascript/plugins/drone/contrib/rboxcall.js b/src/main/js/plugins/drone/contrib/rboxcall.js similarity index 100% rename from src/main/javascript/plugins/drone/contrib/rboxcall.js rename to src/main/js/plugins/drone/contrib/rboxcall.js diff --git a/src/main/javascript/plugins/drone/contrib/redstonewire.js b/src/main/js/plugins/drone/contrib/redstonewire.js similarity index 100% rename from src/main/javascript/plugins/drone/contrib/redstonewire.js rename to src/main/js/plugins/drone/contrib/redstonewire.js diff --git a/src/main/javascript/plugins/drone/contrib/skyscraper-example.js b/src/main/js/plugins/drone/contrib/skyscraper-example.js similarity index 100% rename from src/main/javascript/plugins/drone/contrib/skyscraper-example.js rename to src/main/js/plugins/drone/contrib/skyscraper-example.js diff --git a/src/main/javascript/plugins/drone/contrib/spiral_stairs.js b/src/main/js/plugins/drone/contrib/spiral_stairs.js similarity index 100% rename from src/main/javascript/plugins/drone/contrib/spiral_stairs.js rename to src/main/js/plugins/drone/contrib/spiral_stairs.js diff --git a/src/main/javascript/plugins/drone/contrib/streamer.js b/src/main/js/plugins/drone/contrib/streamer.js similarity index 100% rename from src/main/javascript/plugins/drone/contrib/streamer.js rename to src/main/js/plugins/drone/contrib/streamer.js diff --git a/src/main/javascript/plugins/drone/contrib/temple.js b/src/main/js/plugins/drone/contrib/temple.js similarity index 100% rename from src/main/javascript/plugins/drone/contrib/temple.js rename to src/main/js/plugins/drone/contrib/temple.js diff --git a/src/main/javascript/plugins/drone/drone-firework.js b/src/main/js/plugins/drone/drone-firework.js similarity index 100% rename from src/main/javascript/plugins/drone/drone-firework.js rename to src/main/js/plugins/drone/drone-firework.js diff --git a/src/main/javascript/plugins/drone/drone.js b/src/main/js/plugins/drone/drone.js similarity index 100% rename from src/main/javascript/plugins/drone/drone.js rename to src/main/js/plugins/drone/drone.js diff --git a/src/main/javascript/plugins/drone/sphere.js b/src/main/js/plugins/drone/sphere.js similarity index 100% rename from src/main/javascript/plugins/drone/sphere.js rename to src/main/js/plugins/drone/sphere.js diff --git a/src/main/javascript/plugins/drone/test.js b/src/main/js/plugins/drone/test.js similarity index 100% rename from src/main/javascript/plugins/drone/test.js rename to src/main/js/plugins/drone/test.js diff --git a/src/main/javascript/plugins/examples/example-1-hello-module.js b/src/main/js/plugins/examples/example-1-hello-module.js similarity index 100% rename from src/main/javascript/plugins/examples/example-1-hello-module.js rename to src/main/js/plugins/examples/example-1-hello-module.js diff --git a/src/main/javascript/plugins/examples/example-2-hello-command.js b/src/main/js/plugins/examples/example-2-hello-command.js similarity index 100% rename from src/main/javascript/plugins/examples/example-2-hello-command.js rename to src/main/js/plugins/examples/example-2-hello-command.js diff --git a/src/main/javascript/plugins/examples/example-3-hello-ops-only.js b/src/main/js/plugins/examples/example-3-hello-ops-only.js similarity index 100% rename from src/main/javascript/plugins/examples/example-3-hello-ops-only.js rename to src/main/js/plugins/examples/example-3-hello-ops-only.js diff --git a/src/main/javascript/plugins/examples/example-4-hello-parameters.js b/src/main/js/plugins/examples/example-4-hello-parameters.js similarity index 100% rename from src/main/javascript/plugins/examples/example-4-hello-parameters.js rename to src/main/js/plugins/examples/example-4-hello-parameters.js diff --git a/src/main/javascript/plugins/examples/example-5-hello-using-module.js b/src/main/js/plugins/examples/example-5-hello-using-module.js similarity index 100% rename from src/main/javascript/plugins/examples/example-5-hello-using-module.js rename to src/main/js/plugins/examples/example-5-hello-using-module.js diff --git a/src/main/javascript/plugins/examples/example-6-hello-player.js b/src/main/js/plugins/examples/example-6-hello-player.js similarity index 100% rename from src/main/javascript/plugins/examples/example-6-hello-player.js rename to src/main/js/plugins/examples/example-6-hello-player.js diff --git a/src/main/javascript/plugins/examples/example-7-hello-events.js b/src/main/js/plugins/examples/example-7-hello-events.js similarity index 100% rename from src/main/javascript/plugins/examples/example-7-hello-events.js rename to src/main/js/plugins/examples/example-7-hello-events.js diff --git a/src/main/javascript/plugins/homes/homes.js b/src/main/js/plugins/homes/homes.js similarity index 100% rename from src/main/javascript/plugins/homes/homes.js rename to src/main/js/plugins/homes/homes.js diff --git a/src/main/javascript/plugins/minigames/NumberGuess.js b/src/main/js/plugins/minigames/NumberGuess.js similarity index 100% rename from src/main/javascript/plugins/minigames/NumberGuess.js rename to src/main/js/plugins/minigames/NumberGuess.js diff --git a/src/main/javascript/plugins/minigames/SnowballFight.js b/src/main/js/plugins/minigames/SnowballFight.js similarity index 100% rename from src/main/javascript/plugins/minigames/SnowballFight.js rename to src/main/js/plugins/minigames/SnowballFight.js diff --git a/src/main/javascript/plugins/minigames/cow-clicker.js b/src/main/js/plugins/minigames/cow-clicker.js similarity index 100% rename from src/main/javascript/plugins/minigames/cow-clicker.js rename to src/main/js/plugins/minigames/cow-clicker.js diff --git a/src/main/javascript/plugins/signs/examples.js b/src/main/js/plugins/signs/examples.js similarity index 100% rename from src/main/javascript/plugins/signs/examples.js rename to src/main/js/plugins/signs/examples.js diff --git a/src/main/javascript/plugins/spawn.js b/src/main/js/plugins/spawn.js similarity index 100% rename from src/main/javascript/plugins/spawn.js rename to src/main/js/plugins/spawn.js diff --git a/src/main/javascript/readme.md b/src/main/js/readme.md similarity index 100% rename from src/main/javascript/readme.md rename to src/main/js/readme.md From 7457cd58b83bd23dedba2b26f77c1423affbf698 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Wed, 29 Jan 2014 19:49:15 +0000 Subject: [PATCH 110/456] Changed formatting to use idiomatic style. (like glasses-mode in emacs) --- docs/API-Reference.md | 110 +- src/main/js/lib/command.js | 50 +- src/main/js/lib/console.js | 50 +- src/main/js/lib/events.js | 92 +- src/main/js/lib/js-patch.js | 57 +- src/main/js/lib/persistence.js | 73 +- src/main/js/lib/plugin.js | 79 +- src/main/js/lib/require.js | 144 +- src/main/js/lib/scriptcraft.js | 471 +++-- src/main/js/lib/tabcomplete-jsp.js | 68 +- src/main/js/lib/tabcomplete.js | 305 +-- src/main/js/modules/blocks.js | 490 ++--- src/main/js/modules/fireworks/fireworks.js | 77 +- src/main/js/modules/http/request.js | 132 +- src/main/js/modules/minigames/scoreboard.js | 81 +- src/main/js/modules/partial.js | 14 - src/main/js/modules/sc-mqtt.js | 86 +- src/main/js/modules/signs/menu.js | 309 +-- src/main/js/modules/signs/signs.js | 22 +- src/main/js/modules/utils/string-exts.js | 64 +- src/main/js/modules/utils/utils.js | 256 +-- src/main/js/plugins/alias/alias.js | 155 +- src/main/js/plugins/arrows.js | 192 +- src/main/js/plugins/chat/color.js | 100 +- src/main/js/plugins/classroom/classroom.js | 88 +- src/main/js/plugins/commando/commando-test.js | 33 +- src/main/js/plugins/commando/commando.js | 64 +- src/main/js/plugins/drone/blocktype.js | 124 +- src/main/js/plugins/drone/contrib/castle.js | 86 +- .../js/plugins/drone/contrib/chessboard.js | 46 +- src/main/js/plugins/drone/contrib/cottage.js | 104 +- src/main/js/plugins/drone/contrib/fort.js | 123 +- .../js/plugins/drone/contrib/lcd-clock.js | 20 +- src/main/js/plugins/drone/contrib/rainbow.js | 45 +- src/main/js/plugins/drone/contrib/rboxcall.js | 30 +- .../drone/contrib/skyscraper-example.js | 28 +- src/main/js/plugins/drone/contrib/streamer.js | 38 +- src/main/js/plugins/drone/contrib/temple.js | 12 +- src/main/js/plugins/drone/drone-firework.js | 4 +- src/main/js/plugins/drone/drone.js | 1820 +++++++++-------- src/main/js/plugins/drone/sphere.js | 338 +-- .../examples/example-1-hello-module.js | 4 +- .../examples/example-2-hello-command.js | 4 +- .../examples/example-3-hello-ops-only.js | 18 +- .../examples/example-4-hello-parameters.js | 28 +- .../examples/example-5-hello-using-module.js | 8 +- .../examples/example-6-hello-player.js | 30 +- .../examples/example-7-hello-events.js | 16 +- src/main/js/plugins/homes/homes.js | 584 +++--- src/main/js/plugins/minigames/NumberGuess.js | 115 +- .../js/plugins/minigames/SnowballFight.js | 289 +-- src/main/js/plugins/minigames/cow-clicker.js | 256 +-- src/main/js/plugins/spawn.js | 33 +- src/main/resources/boot.js | 145 +- 54 files changed, 4161 insertions(+), 3849 deletions(-) delete mode 100644 src/main/js/modules/partial.js diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 3977782..099909e 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -787,7 +787,7 @@ To call the fireworks.firework() function directly, you must provide a location. For example... /js var fireworks = require('fireworks'); - /js fireworks.firework(self.location); + /js fireworks.firework( self.location ); ![firework example](img/firework.png) @@ -829,11 +829,14 @@ The following example illustrates how to use http.request to make a request to a ... The following example illustrates a more complex use-case POSTing parameters to a CGI process on a server... var http = require('./http/request'); - http.request({ url: "http://pixenate.com/pixenate/pxn8.pl", - method: "POST", - params: {script: "[]"} - }, function( responseCode, responseBody){ - var jsObj = eval("(" + responseBody + ")"); + http.request( + { + url: 'http://pixenate.com/pixenate/pxn8.pl', + method: 'POST', + params: {script: '[]'} + }, + function( responseCode, responseBody ) { + var jsObj = eval('(' + responseBody + ')'); }); ## sc-mqtt module @@ -870,24 +873,24 @@ present in the CraftBukkit classpath. To use this module, you should // create a new client - var client = mqtt.client('tcp://localhost:1883', 'uniqueClientId'); + var client = mqtt.client( 'tcp://localhost:1883', 'uniqueClientId' ); // connect to the broker - client.connect({ keepAliveInterval: 15 }); + client.connect( { keepAliveInterval: 15 } ); // publish a message to the broker - client.publish('minecraft','loaded'); + client.publish( 'minecraft', 'loaded' ); // subscribe to messages on 'arduino' topic - client.subscribe('arduino'); + client.subscribe( 'arduino' ); // do something when an incoming message arrives... - client.onMessageArrived(function(topic, message){ - console.log('Message arrived: topic=' + topic + ', message=' + message); + client.onMessageArrived( function( topic, message ) { + console.log( 'Message arrived: topic=' + topic + ', message=' + message ); }); The `sc-mqtt` module provides a very simple minimal wrapper around the @@ -1023,7 +1026,7 @@ Example ------- /js var boldGoldText = "Hello World".bold().gold(); - /js self.sendMessage(boldGoldText); + /js self.sendMessage( boldGoldText );

Hello World

@@ -1173,7 +1176,7 @@ package for scheduling processing of arrays. - object : Additional (optional) information passed into the foreach method. - array : The entire array. - * object (optional) : An object which may be used by the callback. + * context (optional) : An object which may be used by the callback. * delay (optional, numeric) : If a delay is specified (in ticks - 20 ticks = 1 second), then the processing will be scheduled so that each item will be processed in turn with a delay between the completion of each @@ -1273,9 +1276,9 @@ To warn players when night is approaching... utils.at( '19:00', function() { - utils.foreach( server.onlinePlayers, function(player){ - player.chat('The night is dark and full of terrors!'); - }); + utils.foreach( server.onlinePlayers, function( player ) { + player.chat( 'The night is dark and full of terrors!' ); + }); }); @@ -1410,7 +1413,7 @@ Drones can be created in any of the following ways... block is broken at the block's location you would do so like this... - events.on('block.BlockBreakEvent',function(listener,event){ + events.on('block.BlockBreakEvent',function( listener,event) { var location = event.block.location; var drone = new Drone(location); // do more stuff with the drone here... @@ -1552,7 +1555,7 @@ Markers are created and returned to using the followng two methods... // // the drone can now go off on a long excursion // - for (i = 0; i< 100; i++){ + for ( i = 0; i< 100; i++) { drone.fwd(12).box(6); } // @@ -1624,11 +1627,11 @@ arc() takes a single parameter - an object with the following named properties.. * radius - The radius of the arc. * blockType - The type of block to use - this is the block Id only (no meta). See [Data Values][dv]. * meta - The metadata value. See [Data Values][dv]. - * orientation (default: 'horizontal') - the orientation of the arc - can be 'vertical' or 'horizontal'. - * stack (default: 1) - the height or length of the arc (depending on + * orientation (default: 'horizontal' ) - the orientation of the arc - can be 'vertical' or 'horizontal'. + * stack (default: 1 ) - the height or length of the arc (depending on the orientation - if orientation is horizontal then this parameter - refers to the height, if vertical then it refers to the length). - * strokeWidth (default: 1) - the width of the stroke (how many + refers to the height, if vertical then it refers to the length ). + * strokeWidth (default: 1 ) - the width of the stroke (how many blocks) - if drawing nested arcs it's usually a good idea to set strokeWidth to at least 2 so that there are no gaps between each arc. The arc method uses a [bresenham algorithm][bres] to plot @@ -1652,7 +1655,7 @@ To draw a 1/4 circle (top right quadrant only) with a radius of 10 and stroke wi orientation: 'vertical', stack: 1, fill: false - }); + } ); ![arc example 1](img/arcex1.png) @@ -1715,7 +1718,7 @@ To create a free-standing sign... ... to create a wall mounted sign... - drone.sign(["Welcome","to","Scriptopia"], 68); + drone.sign(["Welcome","to","Scriptopia"], 68 ); ![wall sign](img/signex2.png) @@ -1730,13 +1733,13 @@ To create a free-standing sign... To create 4 trees in a row, point the cross-hairs at the ground then type `/js ` and ... - up().oak().right(8).spruce().right(8).birch().right(8).jungle(); + up( ).oak( ).right(8 ).spruce( ).right(8 ).birch( ).right(8 ).jungle( ); Trees won't always generate unless the conditions are right. You should use the tree methods when the drone is directly above the ground. Trees will usually grow if the drone's current location is occupied by Air and is directly above an area of grass (That is why -the `up()` method is called first). +the `up( )` method is called first). ![tree example](img/treeex1.png) @@ -1795,7 +1798,7 @@ pasting the copied area elsewhere... #### Example - drone.copy('somethingCool',10,5,10).right(12).paste('somethingCool'); + drone.copy('somethingCool',10,5,10 ).right(12 ).paste('somethingCool' ); ### Drone.paste() method @@ -1807,9 +1810,9 @@ To copy a 10x5x10 area (using the drone's coordinates as the starting point) into memory. the copied area can be referenced using the name 'somethingCool'. The drone moves 12 blocks right then pastes the copy. - drone.copy('somethingCool',10,5,10) - .right(12) - .paste('somethingCool'); + drone.copy('somethingCool',10,5,10 ) + .right(12 ) + .paste('somethingCool' ); ### Chaining @@ -1866,9 +1869,9 @@ Use this method to add new methods (which also become chainable global functions #### Example // submitted by [edonaldson][edonaldson] - Drone.extend('pyramid', function(block,height){ + Drone.extend('pyramid', function( block,height) { this.chkpt('pyramid'); - for (var i = height; i > 0; i -= 2) { + for ( var i = height; i > 0; i -= 2) { this.box(block, i, 1, i).up().right().fwd(); } return this.move('pyramid'); @@ -1931,7 +1934,7 @@ Say you want to do the same thing over and over. You have a couple of options... * You can use a for loop... - d = new Drone(); for (var i =0;i < 4; i++){ d.cottage().right(8); } + d = new Drone(); for ( var i =0;i < 4; i++) { d.cottage().right(8); } While this will fit on the in-game prompt, it's awkward. You need to declare a new Drone object first, then write a for loop to create the @@ -1940,7 +1943,7 @@ syntax for what should really be simple. * You can use a while loop... - d = new Drone(); var i=4; while (i--){ d.cottage().right(8); } + d = new Drone(); var i=4; while (i--) { d.cottage().right(8); } ... which is slightly shorter but still too much syntax. Each of the above statements is fine for creating a 1-dimensional array of @@ -2218,9 +2221,9 @@ This example demonstrates adding and using parameters in commands. This differs from example 3 in that the greeting can be changed from a fixed 'Hello ' to anything you like by passing a parameter. - command('hello-params', function (parameters, player) { - var salutation = parameters[0] ; - player.sendMessage( salutation + ' ' + player.name); + command( 'hello-params', function ( parameters, player ) { + var salutation = parameters[0] ; + player.sendMessage( salutation + ' ' + player.name ); }); ## Example Plugin #5 - Re-use - Using your own and others modules. @@ -2253,8 +2256,8 @@ this example, we use that module... Source Code... var greetings = require('./example-1-hello-module'); - command('hello-module', function( parameters, player ){ - greetings.hello(player); + command( 'hello-module', function( parameters, player ) { + greetings.hello( player ); }); ## Example Plugin #6 - Re-use - Using 'utils' to get Player objects. @@ -2290,13 +2293,14 @@ Source Code ... var utils = require('utils'); var greetings = require('./example-1-hello-module'); - command('hello-byname', function( parameters, sender ) { - var playerName = parameters[0]; - var recipient = utils.player(playerName); - if (recipient) - greetings.hello(recipient); - else - sender.sendMessage('Player ' + playerName + ' not found.'); + command( 'hello-byname', function( parameters, sender ) { + var playerName = parameters[0]; + var recipient = utils.player( playerName ); + if ( recipient ) { + greetings.hello( recipient ); + } else { + sender.sendMessage( 'Player ' + playerName + ' not found.' ); + } }); ## Example Plugin #7 - Listening for events, Greet players when they join the game. @@ -2379,10 +2383,10 @@ cleaner and more readable. Similarly where you see a method like [bksaf]: http://jd.bukkit.org/dev/apidocs/org/bukkit/entity/Player.html#setAllowFlight() [bkapi]: http://jd.bukkit.org/dev/apidocs/ - events.on('player.PlayerJoinEvent', function (listener, event){ - if (event.player.op) { - event.player.sendMessage('Welcome to ' + __plugin); - } + events.on( 'player.PlayerJoinEvent', function( listener, event ) { + if ( event.player.op ) { + event.player.sendMessage('Welcome to ' + __plugin); + } }); ## Arrows Plugin @@ -2506,11 +2510,11 @@ to every student in a Minecraft classroom environment. To allow all players (and any players who connect to the server) to use the `js` and `jsp` commands... - /js classroom.allowScripting(true,self) + /js classroom.allowScripting( true, self ) To disallow scripting (and prevent players who join the server from using the commands)... - /js classroom.allowScripting(false,self) + /js classroom.allowScripting( false, self ) Only ops users can run the classroom.allowScripting() function - this is so that students don't try to bar themselves and each other from scripting. diff --git a/src/main/js/lib/command.js b/src/main/js/lib/command.js index 61e0c60..6f2ae12 100644 --- a/src/main/js/lib/command.js +++ b/src/main/js/lib/command.js @@ -2,45 +2,53 @@ /* command management - allow for non-ops to execute approved javascript code. */ -var _commands = {}; -var _cmdInterceptors = []; +var _commands = {}, + _cmdInterceptors = []; /* execute a JSP command. */ -var executeCmd = function(args, player){ - if (args.length === 0) +var executeCmd = function( args, player ) { + var name, + cmd, + intercepted, + result = null; + + if ( args.length === 0 ) { throw new Error('Usage: jsp command-name command-parameters'); - var name = args[0]; - var cmd = _commands[name]; - if (typeof cmd === 'undefined'){ + } + name = args[0]; + cmd = _commands[name]; + if ( typeof cmd === 'undefined' ) { // it's not a global command - pass it on to interceptors - var intercepted = false; - for (var i = 0;i < _cmdInterceptors.length;i++){ - if (_cmdInterceptors[i](args,player)) + intercepted = false; + for ( var i = 0; i < _cmdInterceptors.length; i++ ) { + if ( _cmdInterceptors[i]( args, player ) ) intercepted = true; } - if (!intercepted) - console.warn('Command %s is not recognised',name); + if ( !intercepted ) { + console.warn( 'Command %s is not recognised', name ); + } }else{ - var result = null; try { - result = cmd.callback(args.slice(1),player); - }catch (e){ - console.error('Error while trying to execute command: ' + JSON.stringify(args)); + result = cmd.callback( args.slice(1), player ); + } catch ( e ) { + console.error( 'Error while trying to execute command: ' + JSON.stringify( args ) ); throw e; } - return result; } + return result; }; /* define a new JSP command. */ -var defineCmd = function(name, func, options, intercepts) { - if (typeof options == 'undefined') +var defineCmd = function( name, func, options, intercepts ) { + if ( typeof options == 'undefined' ) { options = []; - _commands[name] = {callback: func, options: options}; - if (intercepts) + } + _commands[name] = { callback: func, options: options }; + if ( intercepts ) { _cmdInterceptors.push(func); + } return func; }; exports.command = defineCmd; diff --git a/src/main/js/lib/console.js b/src/main/js/lib/console.js index 91c437b..75a2287 100644 --- a/src/main/js/lib/console.js +++ b/src/main/js/lib/console.js @@ -35,35 +35,39 @@ ScriptCraft uses Java's [String.format()][strfmt] so any string substitution ide [webcons]: https://developer.mozilla.org/en-US/docs/Web/API/console ***/ -var logger = __plugin.logger; -var argsToArray = function(args){ - var result = []; - for (var i =0;i < args.length; i++) - result.push(args[i]); - return result; +var logger = __plugin.logger, + logMethodName = 'log(java.util.logging.Level,java.lang.String)'; +var argsToArray = function( args ) { + var result = []; + for ( var i =0; i < args.length; i++ ) { + result.push(args[i]); + } + return result; } -var log = function(level, restOfArgs){ - var args = argsToArray(restOfArgs); - if (args.length > 1){ - var msg = java.lang.String.format(args[0],args.slice(1)); - logger['log(java.util.logging.Level,java.lang.String)'](level,msg); - }else{ - logger['log(java.util.logging.Level,java.lang.String)'](level, args[0]); - } +var log = function( level, restOfArgs ) { + var args = argsToArray( restOfArgs ); + if ( args.length > 1 ) { + var msg = java.lang.String.format( args[0], args.slice(1) ); + logger[logMethodName]( level, msg ); + } else { + logger[logMethodName]( level, args[0] ); + } }; var Level = java.util.logging.Level; -exports.log = function(){ - log(Level.INFO, arguments); +exports.log = function( ) { + log( Level.INFO, arguments ); }; -exports.info = function(){ - log(Level.INFO, arguments); -} -exports.warn = function(){ - log(Level.WARNING, arguments); +exports.info = function( ) { + log( Level.INFO, arguments ); }; -exports.error = function(){ - log(Level.SEVERE, arguments); + +exports.warn = function( ) { + log( Level.WARNING, arguments ); +}; + +exports.error = function( ) { + log( Level.SEVERE, arguments ); }; diff --git a/src/main/js/lib/events.js b/src/main/js/lib/events.js index 63f8b88..7a0fd44 100644 --- a/src/main/js/lib/events.js +++ b/src/main/js/lib/events.js @@ -75,53 +75,55 @@ To listen for events using a full class name as the `eventName` parameter... ***/ -var bkEvent = org.bukkit.event; -var bkEvtExecutor = org.bukkit.plugin.EventExecutor; -var bkRegListener = org.bukkit.plugin.RegisteredListener; +var bkEvent = org.bukkit.event, + bkEvtExecutor = org.bukkit.plugin.EventExecutor, + bkRegListener = org.bukkit.plugin.RegisteredListener; exports.on = function( - /* String or java Class */ - eventType, - /* function( registeredListener, event) */ - handler, - /* (optional) String (HIGH, HIGHEST, LOW, LOWEST, NORMAL, MONITOR), */ - priority ) { + /* String or java Class */ + eventType, + /* function( registeredListener, event) */ + handler, + /* (optional) String (HIGH, HIGHEST, LOW, LOWEST, NORMAL, MONITOR), */ + priority ) { + var handlerList, + listener = {}, + eventExecutor; - if (typeof priority == "undefined"){ - priority = bkEvent.EventPriority.HIGHEST; - }else{ - priority = bkEvent.EventPriority[priority]; + if ( typeof priority == 'undefined' ) { + priority = bkEvent.EventPriority.HIGHEST; + } else { + priority = bkEvent.EventPriority[priority]; + } + if ( typeof eventType == 'string' ) { + /* + Nashorn doesn't support bracket notation for accessing packages. + E.g. java.net will work but java['net'] won't. + + https://bugs.openjdk.java.net/browse/JDK-8031715 + */ + if ( typeof Java != 'undefined' ) { + // nashorn environment + eventType = Java.type( 'org.bukkit.event.' + eventType ); + } else { + eventType = eval( 'org.bukkit.event.' + eventType ); } - if (typeof eventType == "string"){ - /* - Nashorn doesn't support bracket notation for accessing packages. - E.g. java.net will work but java['net'] won't. - - https://bugs.openjdk.java.net/browse/JDK-8031715 - */ - if (typeof Java != 'undefined'){ - // nashorn environment - eventType = Java.type('org.bukkit.event.' + eventType); - } else { - eventType = eval('org.bukkit.event.' + eventType); - } - } - var handlerList = eventType.getHandlerList(); - var listener = {}; - var eventExecutor = new bkEvtExecutor(){ - execute: function(l,e){ - handler(listener.reg,e); - } - }; - /* - wph 20130222 issue #64 bad interaction with Essentials plugin - if another plugin tries to unregister a Listener (not a Plugin or a RegisteredListener) - then BOOM! the other plugin will throw an error because Rhino can't coerce an - equals() method from an Interface. - The workaround is to make the ScriptCraftPlugin java class a Listener. - Should only unregister() registered plugins in ScriptCraft js code. - */ - listener.reg = new bkRegListener( __plugin, eventExecutor, priority, __plugin, true); - handlerList.register(listener.reg); - return listener.reg; + } + handlerList = eventType.getHandlerList( ); + eventExecutor = new bkEvtExecutor( ) { + execute: function( l, e ) { + handler( listener.reg, e ); + } + }; + /* + wph 20130222 issue #64 bad interaction with Essentials plugin + if another plugin tries to unregister a Listener (not a Plugin or a RegisteredListener) + then BOOM! the other plugin will throw an error because Rhino can't coerce an + equals() method from an Interface. + The workaround is to make the ScriptCraftPlugin java class a Listener. + Should only unregister() registered plugins in ScriptCraft js code. + */ + listener.reg = new bkRegListener( __plugin, eventExecutor, priority, __plugin, true ); + handlerList.register( listener.reg ); + return listener.reg; }; diff --git a/src/main/js/lib/js-patch.js b/src/main/js/lib/js-patch.js index 4403bbc..e291b61 100644 --- a/src/main/js/lib/js-patch.js +++ b/src/main/js/lib/js-patch.js @@ -1,33 +1,36 @@ -module.exports = function($){ +module.exports = function( $ ) { - // wph 20140105 trim not availabe in String on Mac OS. - if (typeof String.prototype.trim == 'undefined'){ - String.prototype.trim = function(){ - return this.replace(/^\s+|\s+$/g,''); - }; - } + // wph 20140105 trim not availabe in String on Mac OS. + if ( typeof String.prototype.trim == 'undefined' ) { + String.prototype.trim = function( ) { + return this.replace( /^\s+|\s+$/g, '' ); + }; + } - $.setTimeout = function( callback, delayInMillis){ - /* - javascript programmers familiar with setTimeout know that it expects - a delay in milliseconds. However, bukkit's scheduler expects a delay in ticks - (where 1 tick = 1/20th second) - */ - var bukkitTask = server.scheduler.runTaskLater(__plugin, callback, delayInMillis/50); - return bukkitTask; - }; - $.clearTimeout = function(bukkitTask){ - bukkitTask.cancel(); - }; + $.setTimeout = function( callback, delayInMillis ) { + /* + javascript programmers familiar with setTimeout know that it expects + a delay in milliseconds. However, bukkit's scheduler expects a delay in ticks + (where 1 tick = 1/20th second) + */ + var bukkitTask = server.scheduler.runTaskLater( __plugin, callback, delayInMillis/50 ); + return bukkitTask; + }; + + $.clearTimeout = function( bukkitTask ) { + bukkitTask.cancel(); + }; - $.setInterval = function(callback, intervalInMillis){ - var delay = intervalInMillis/ 50; - var bukkitTask = server.scheduler.runTaskTimer(__plugin, callback, delay, delay); - return bukkitTask; - }; - $.clearInterval = function(bukkitTask){ - bukkitTask.cancel(); - }; + $.setInterval = function( callback, intervalInMillis ) { + var delay = intervalInMillis/ 50; + var bukkitTask = server.scheduler.runTaskTimer( __plugin, callback, delay, delay ); + return bukkitTask; + }; + + $.clearInterval = function( bukkitTask ) { + bukkitTask.cancel(); + }; + }; diff --git a/src/main/js/lib/persistence.js b/src/main/js/lib/persistence.js index cea7147..1b82231 100644 --- a/src/main/js/lib/persistence.js +++ b/src/main/js/lib/persistence.js @@ -1,37 +1,50 @@ +var _dataDir = null, + _persistentData = {}; -var _dataDir = null; -var _persistentData = {}; +module.exports = function( rootDir, $ ) { -module.exports = function( rootDir, $ ){ + var _load = function( name ) { + $.scload( _dataDir.canonicalPath + '/' + name + '-store.json' ); + }; + var _save = function( name, data ) { + $.scsave( data, _dataDir.canonicalPath + '/' + name + '-store.json' ); + }; - _dataDir = new java.io.File( rootDir, 'data'); + _dataDir = new java.io.File( rootDir, 'data' ); - $.persist = function(name, data, write){ - var i, dataFromFile; - if (typeof data == 'undefined') - data = {}; - if (typeof write == 'undefined') - write = false; - if (!write){ - dataFromFile = $.scload(_dataDir.canonicalPath + '/' + name + '-store.json'); - if (dataFromFile){ - for (i in dataFromFile){ - data[i] = dataFromFile[i]; - } - } - }else{ - // flush data to file - $.scsave(data, _dataDir.canonicalPath + '/' + name + '-store.json'); + $.persist = function( name, data, write ) { + var i, + dataFromFile; + if ( typeof data == 'undefined' ) { + data = {}; + } + if ( typeof write == 'undefined' ) { + write = false; + } + if ( !write ) { + dataFromFile = _load( name ); + if ( dataFromFile ) { + for ( i in dataFromFile ) { + data[i] = dataFromFile[i]; } - _persistentData[name] = data; - return data; - }; - - $.addUnloadHandler(function(){ - for (var name in _persistentData){ - var data = _persistentData[name]; - $.scsave(data, _dataDir.canonicalPath + '/' + name + '-store.json'); - } - }); + } + } else { + // flush data to file + _save( name, data ); + } + _persistentData[name] = data; + return data; + }; + /* + persist on shutdown + */ + $.addUnloadHandler( function( ) { + var name, + data; + for ( name in _persistentData ) { + data = _persistentData[name]; + _save( name, data ); + } + }); }; diff --git a/src/main/js/lib/plugin.js b/src/main/js/lib/plugin.js index 819461d..448d0a2 100644 --- a/src/main/js/lib/plugin.js +++ b/src/main/js/lib/plugin.js @@ -1,4 +1,5 @@ 'use strict'; + var console = require('./console'), File = java.io.File, FileWriter = java.io.FileWriter, @@ -8,22 +9,22 @@ var console = require('./console'), */ var _plugins = {}; -var _plugin = function(/* String */ moduleName, /* Object */ moduleObject, isPersistent) -{ +var _plugin = function(/* String */ moduleName, /* Object */ moduleObject, isPersistent ) { // // don't load plugin more than once // - if (typeof _plugins[moduleName] != 'undefined') + if ( typeof _plugins[moduleName] != 'undefined' ) { return _plugins[moduleName].module; + } - var pluginData = {persistent: isPersistent, module: moduleObject}; - if (typeof moduleObject.store == 'undefined') + var pluginData = { persistent: isPersistent, module: moduleObject }; + if ( typeof moduleObject.store == 'undefined' ) { moduleObject.store = {}; - + } _plugins[moduleName] = pluginData; - if (isPersistent){ - moduleObject.store = persist(moduleName, moduleObject.store); + if ( isPersistent ) { + moduleObject.store = persist( moduleName, moduleObject.store ); } return moduleObject; }; @@ -34,30 +35,31 @@ var scriptCraftDir = null; var pluginDir = null; var dataDir = null; -exports.autoload = function(dir,logger) { +exports.autoload = function( dir, logger ) { scriptCraftDir = dir; - pluginDir = new File(dir, 'plugins'); - dataDir = new File(dir, 'data'); + pluginDir = new File( dir, 'plugins' ); + dataDir = new File( dir, 'data' ); - var _canonize = function(file){ + var _canonize = function( file ) { return '' + file.canonicalPath.replaceAll('\\\\','/'); }; /* recursively walk the given directory and return a list of all .js files */ - var _listSourceFiles = function(store,dir) - { - var files = dir.listFiles(); - if (!files) + var _listSourceFiles = function( store, dir ) { + var files = dir.listFiles(), + file; + if ( !files ) { return; - for (var i = 0;i < files.length; i++) { - var file = files[i]; - if (file.isDirectory()){ - _listSourceFiles(store,file); + } + for ( var i = 0; i < files.length; i++ ) { + file = files[i]; + if ( file.isDirectory( ) ) { + _listSourceFiles( store, file ); }else{ - if ( file.canonicalPath.endsWith('.js') ){ - store.push(file); + if ( file.canonicalPath.endsWith( '.js' ) ) { + store.push( file ); } } } @@ -65,30 +67,33 @@ exports.autoload = function(dir,logger) { /* Reload all of the .js files in the given directory */ - var _reload = function(pluginDir) - { - var sourceFiles = []; - _listSourceFiles(sourceFiles,pluginDir); + (function( pluginDir ) { + var sourceFiles = [], + property, + module, + pluginPath; + _listSourceFiles( sourceFiles, pluginDir ); var len = sourceFiles.length; - if (config.verbose) - console.info(len + ' scriptcraft plugins found.'); - for (var i = 0;i < len; i++){ - var pluginPath = _canonize(sourceFiles[i]); - var module = {}; + if ( config.verbose ) { + console.info( len + ' scriptcraft plugins found.' ); + } + for ( var i = 0; i < len; i++ ) { + pluginPath = _canonize( sourceFiles[i] ); + module = {}; + try { - module = require(pluginPath); - for (var property in module){ + module = require( pluginPath ); + for ( property in module ) { /* all exports in plugins become global */ global[property] = module[property]; } - }catch (e){ - logger.severe('Plugin ' + pluginPath + ' ' + e); + } catch ( e ) { + logger.severe( 'Plugin ' + pluginPath + ' ' + e ); } } - }; - _reload(pluginDir); + }(pluginDir)); }; diff --git a/src/main/js/lib/require.js b/src/main/js/lib/require.js index 64e39e4..30d0c79 100644 --- a/src/main/js/lib/require.js +++ b/src/main/js/lib/require.js @@ -54,46 +54,45 @@ module specification, the '.js' suffix is optional. [cjsmodules]: http://wiki.commonjs.org/wiki/Modules/1.1.1. ***/ -(function (rootDir, modulePaths, hooks) { +(function ( rootDir, modulePaths, hooks ) { - var File = java.io.File; + var File = java.io.File; - var readModuleFromDirectory = function(dir){ + var readModuleFromDirectory = function( dir ) { - // look for a package.json file - var pkgJsonFile = new File(dir, './package.json'); - if (pkgJsonFile.exists()){ - var pkg = scload(pkgJsonFile); - var mainFile = new File(dir, pkg.main); - if (mainFile.exists()){ - return mainFile; - } else { - return null; - } - }else{ - // look for an index.js file - var indexJsFile = new File(dir + './index.js'); - if (indexJsFile.exists()){ - return indexJsFile; - } else { - return null; - } - } - }; + // look for a package.json file + var pkgJsonFile = new File( dir, './package.json' ); + if ( pkgJsonFile.exists() ) { + var pkg = scload( pkgJsonFile ); + var mainFile = new File( dir, pkg.main ); + if ( mainFile.exists() ) { + return mainFile; + } else { + return null; + } + } else { + // look for an index.js file + var indexJsFile = new File( dir + './index.js' ); + if ( indexJsFile.exists() ) { + return indexJsFile; + } else { + return null; + } + } + }; - var fileExists = function(file) { - if (file.isDirectory()){ - return readModuleFromDirectory(file); - }else { - return file; - } - }; + var fileExists = function( file ) { + if ( file.isDirectory() ) { + return readModuleFromDirectory( file ); + } else { + return file; + } + }; - var _canonize = function(file){ - return "" + file.canonicalPath.replaceAll("\\\\","/"); - }; + var _canonize = function(file){ + return "" + file.canonicalPath.replaceAll("\\\\","/"); + }; - var resolveModuleToFile = function(moduleName, parentDir) { /********************************************************************** ### module name resolution @@ -128,48 +127,49 @@ When resolving module names to file paths, ScriptCraft uses the following rules. 3.2 if no package.json file exists then look for an index.js file in the directory ***/ - var file = new File(moduleName); + var resolveModuleToFile = function ( moduleName, parentDir ) { + var file = new File(moduleName); - if (file.exists()){ - return fileExists(file); + if ( file.exists() ) { + return fileExists(file); + } + if ( moduleName.match( /^[^\.\/]/ ) ) { + // it's a module named like so ... 'events' , 'net/http' + // + var resolvedFile; + for (var i = 0;i < modulePaths.length; i++){ + resolvedFile = new File(modulePaths[i] + moduleName); + if (resolvedFile.exists()){ + return fileExists(resolvedFile); + }else{ + // try appending a .js to the end + resolvedFile = new File(modulePaths[i] + moduleName + '.js'); + if (resolvedFile.exists()) + return resolvedFile; } - if (moduleName.match(/^[^\.\/]/)){ - // it's a module named like so ... 'events' , 'net/http' - // - var resolvedFile; - for (var i = 0;i < modulePaths.length; i++){ - resolvedFile = new File(modulePaths[i] + moduleName); - if (resolvedFile.exists()){ - return fileExists(resolvedFile); - }else{ - // try appending a .js to the end - resolvedFile = new File(modulePaths[i] + moduleName + '.js'); - if (resolvedFile.exists()) - return resolvedFile; - } - } - } else { - // it's of the form ./path - file = new File(parentDir, moduleName); - if (file.exists()){ - return fileExists(file); - }else { + } + } else { + // it's of the form ./path + file = new File(parentDir, moduleName); + if (file.exists()){ + return fileExists(file); + }else { - // try appending a .js to the end - var pathWithJSExt = file.canonicalPath + '.js'; - file = new File( parentDir, pathWithJSExt); - if (file.exists()) - return file; - else{ - file = new File(pathWithJSExt); - if (file.exists()) - return file; - } - - } + // try appending a .js to the end + var pathWithJSExt = file.canonicalPath + '.js'; + file = new File( parentDir, pathWithJSExt); + if (file.exists()) + return file; + else{ + file = new File(pathWithJSExt); + if (file.exists()) + return file; } - return null; - }; + + } + } + return null; + }; /* wph 20131215 Experimental */ diff --git a/src/main/js/lib/scriptcraft.js b/src/main/js/lib/scriptcraft.js index a8d393f..38ca67b 100644 --- a/src/main/js/lib/scriptcraft.js +++ b/src/main/js/lib/scriptcraft.js @@ -413,238 +413,257 @@ var server = org.bukkit.Bukkit.server; /* private implementation */ -function __onEnable (__engine, __plugin, __script) +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 File = java.io.File, + FileReader = java.io.FileReader, + BufferedReader = java.io.BufferedReader, + PrintWriter = java.io.PrintWriter, + FileWriter = java.io.FileWriter; - var _canonize = function(file){ - return "" + file.getCanonicalPath().replaceAll("\\\\","/"); - }; - - var libDir = __script.parentFile; // lib (assumes scriptcraft.js is in craftbukkit/plugins/scriptcraft/lib directory - var jsPluginsRootDir = libDir.parentFile; // scriptcraft - var jsPluginsRootDirName = _canonize(jsPluginsRootDir); - var logger = __plugin.logger; + 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; - try{ - objectToStr = JSON.stringify(object,null,2); - }catch(e){ - print("ERROR: " + e.getMessage() + " while saving " + filename); - return; - } - var f = (filename instanceof File) ? filename : new File(filename); - var out = new PrintWriter(new FileWriter(f)); - out.println( objectToStr ); - out.close(); - }; - /* - make sure eval is present - */ - if (typeof eval == 'undefined'){ - global.eval = function(str){ - return __engine.eval(str); - }; + /* + 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; } - - /* - Load the contents of the file and evaluate as javascript - */ - var _load = function(filename,warnOnFileNotFound) + f = (filename instanceof File) ? filename : new File(filename); + out = new PrintWriter(new FileWriter(f)); + out.println( objectToStr ); + out.close(); + }; + /* + make sure eval is present + */ + 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; + + if ( !( filename instanceof File ) ) { + file = new File(filename); + } + var canonizedFilename = _canonize( file ); + + if ( file.exists() ) { + parent = file.getParentFile(); + 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 + } + } + } else { + if ( warnOnFileNotFound ) { + logger.warning( canonizedFilename + ' not found' ); + } + } + return result; + }; + /* + now that load is defined, use it to load a global config object + */ + var config = _load( new File(jsPluginsRootDir, 'data/global-config.json' ) ); + 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( ) { + __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 ); + + 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( l, e ) { + // 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 = []; + var i = 0; + for ( ; i < args.length ; i++ ) { + jsArgs.push( '' + args[i] ); + } + + var result = false; + var cmdName = ( '' + cmd.name ).toLowerCase(); + if (cmdName == 'js') { - var result = null - ,file = filename - ,r = undefined; - - if (!(filename instanceof File)) - file = new File(filename); - - var canonizedFilename = _canonize(file); - - if (file.exists()) { - var parent = file.getParentFile(); - var reader = new FileReader(file); - var br = new BufferedReader(reader); - var code = ""; - var wrappedCode; - 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 - } - } - }else{ - if (warnOnFileNotFound) - logger.warning(canonizedFilename + " not found"); - } - return result; - }; - /* - now that load is defined, use it to load a global config object - */ - var config = _load(new File(jsPluginsRootDir, 'data/global-config.json' )); - 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. - */ - var jsonLoaded = __engine["eval(java.io.Reader)"](new FileReader(new File(jsPluginsRootDirName + '/lib/json2.js'))); - - /* - 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.refresh = function(){ - __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; - - global.addUnloadHandler = _addUnloadHandler; - - 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)); + result = true; + var fnBody = jsArgs.join(' '); + global.self = sender; + global.__engine = __engine; + try { + var jsResult = __engine.eval(fnBody); + if ( jsResult ) { + sender.sendMessage(jsResult); + } + } catch ( e ) { + logger.severe( 'Error while trying to evaluate javascript: ' + fnBody + ', Error: '+ e ); + throw e; + } finally { + delete global.self; + delete global.__engine; + } } - 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 ); - - 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(l,e){ - // 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 = []; - var i = 0; - for (;i < args.length; i++) { - jsArgs.push('' + args[i]); - } - - var result = false; - var cmdName = ('' + cmd.name).toLowerCase(); - if (cmdName == 'js') - { - result = true; - var fnBody = jsArgs.join(' '); - global.self = sender; - global.__engine = __engine; - try { - var jsResult = __engine.eval(fnBody); - if (jsResult) - sender.sendMessage(jsResult); - }catch (e){ - logger.severe("Error while trying to evaluate javascript: " + fnBody + ", Error: "+ e); - throw e; - }finally{ - delete global.self; - delete global.__engine; - } - } - if (cmdName == 'jsp'){ - cmdModule.exec(jsArgs, sender); - result = true; - } - return result; - }; - - plugins.autoload(jsPluginsRootDir,logger); - /* - wph 20140102 - warn if legacy 'craftbukkit/js-plugins' or 'craftbukkit/scriptcraft' directories are present - */ - var cbPluginsDir = jsPluginsRootDir.parentFile; - var cbDir = new File(cbPluginsDir.canonicalPath).parentFile; - var legacyDirs = [ - new File(cbDir, 'js-plugins'), - new File(cbDir, 'scriptcraft') - ]; - var legacyExists = false; - for (var i = 0; i < legacyDirs.length; i++){ - if (legacyDirs[i].exists() && legacyDirs[i].isDirectory()){ - legacyExists = true; - console.warn('Legacy ScriptCraft directory %s was found. This directory is no longer used.', - legacyDirs[i].canonicalPath); - } + if ( cmdName == 'jsp' ) { + cmdModule.exec( jsArgs, sender ); + result = true; } - if (legacyExists){ - console.info('Please note that the working directory for %s is %s', - __plugin, jsPluginsRootDir.canonicalPath); + return result; + }; + + plugins.autoload( jsPluginsRootDir, 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() ) { + + 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 ); + } + })(); + } diff --git a/src/main/js/lib/tabcomplete-jsp.js b/src/main/js/lib/tabcomplete-jsp.js index 29490c2..3ed61cb 100644 --- a/src/main/js/lib/tabcomplete-jsp.js +++ b/src/main/js/lib/tabcomplete-jsp.js @@ -3,39 +3,45 @@ var _commands = require('command').commands; /* Tab completion for the /jsp commmand */ -var __onTabCompleteJSP = function( result, cmdSender, pluginCmd, cmdAlias, cmdArgs) { - var cmdInput = cmdArgs[0]; - var cmd = _commands[cmdInput]; - if (cmd){ - var opts = cmd.options; - var len = opts.length; - if (cmdArgs.length == 1){ - for (var i = 0;i < len; i++) - result.add(opts[i]); - }else{ - // partial e.g. /jsp chat_color dar - for (var i = 0;i < len; i++){ - if (opts[i].indexOf(cmdArgs[1]) == 0){ - result.add(opts[i]); - } - } - } - }else{ - if (cmdArgs.length == 0){ - for (var i in _commands) - result.add(i); - }else{ - // partial e.g. /jsp ho - // should tabcomplete to home - // - for (var c in _commands){ - if (c.indexOf(cmdInput) == 0){ - result.add(c); - } - } +var __onTabCompleteJSP = function( result, cmdSender, pluginCmd, cmdAlias, cmdArgs ) { + var cmdInput = cmdArgs[0], + opts, + cmd, + len, + i; + cmd = _commands[cmdInput]; + if ( cmd ) { + opts = cmd.options; + len = opts.length; + if ( cmdArgs.length == 1 ) { + for ( i = 0; i < len; i++ ) { + result.add( opts[i] ); + } + } else { + // partial e.g. /jsp chat_color dar + for ( i = 0; i < len; i++ ) { + if ( opts[i].indexOf( cmdArgs[1] ) == 0 ) { + result.add( opts[i] ); } + } } - return result; + } else { + if ( cmdArgs.length == 0 ) { + for ( i in _commands ) { + result.add( i ); + } + } else { + // partial e.g. /jsp ho + // should tabcomplete to home + // + for ( i in _commands ) { + if ( i.indexOf( cmdInput ) == 0 ) { + result.add( i ); + } + } + } + } + return result; }; module.exports = __onTabCompleteJSP; diff --git a/src/main/js/lib/tabcomplete.js b/src/main/js/lib/tabcomplete.js index 04aac7b..a1114f0 100644 --- a/src/main/js/lib/tabcomplete.js +++ b/src/main/js/lib/tabcomplete.js @@ -6,7 +6,7 @@ var tabCompleteJSP = require('tabcomplete-jsp'); var _isJavaObject = function(o){ var result = false; try { - o.hasOwnProperty("testForJava"); + o.hasOwnProperty( 'testForJava' ); }catch (e){ // java will throw an error when an attempt is made to access the // hasOwnProperty method. (it won't exist for Java objects) @@ -15,165 +15,180 @@ var _isJavaObject = function(o){ return result; }; var _javaLangObjectMethods = [ - 'equals' - ,'getClass' - ,'class' - ,'getClass' - ,'hashCode' - ,'notify' - ,'notifyAll' - ,'toString' - ,'wait' - ,'clone' - ,'finalize' + 'equals' + ,'getClass' + ,'class' + ,'getClass' + ,'hashCode' + ,'notify' + ,'notifyAll' + ,'toString' + ,'wait' + ,'clone' + ,'finalize' ]; - -var _getProperties = function(o) -{ - var result = []; - if (_isJavaObject(o)) - { - propertyLoop: - for (var i in o) - { - // - // don't include standard Object methods - // - var isObjectMethod = false; - for (var j = 0;j < _javaLangObjectMethods.length; j++) - if (_javaLangObjectMethods[j] == i) - continue propertyLoop; - var typeofProperty = null; - try { - typeofProperty = typeof o[i]; - }catch( e ){ - if (e.message == 'java.lang.IllegalStateException: Entity not leashed'){ - // wph 20131020 fail silently for Entity leashing in craftbukkit - }else{ - throw e; - } - } - if (typeofProperty == 'function' ) - result.push(i+'()'); - else - result.push(i); - } - }else{ - if (o.constructor == Array) - return result; - - for (var i in o){ - if (i.match(/^[^_]/)){ - if (typeof o[i] == 'function') - result.push(i+'()'); - else - result.push(i); - } + +var _getProperties = function( o ) { + var result = [], + i, + j, + isObjectMethod, + typeofProperty; + if ( _isJavaObject( o ) ) { + propertyLoop: + for ( i in o ) { + // + // don't include standard Object methods + // + isObjectMethod = false; + for ( j = 0; j < _javaLangObjectMethods.length; j++ ) { + if ( _javaLangObjectMethods[j] == i ) { + continue propertyLoop; + } + } + typeofProperty = null; + try { + typeofProperty = typeof o[i]; + } catch( e ) { + if ( e.message == 'java.lang.IllegalStateException: Entity not leashed' ) { + // wph 20131020 fail silently for Entity leashing in craftbukkit + } else { + throw e; } + } + if ( typeofProperty == 'function' ) { + result.push( i+'()' ); + } else { + result.push( i ); + } } - return result.sort(); + } else { + if ( o.constructor == Array ) { + return result; + } + for ( i in o ) { + if ( i.match( /^[^_]/ ) ) { + if ( typeof o[i] == 'function' ) { + result.push( i+'()' ); + } else { + result.push( i ); + } + } + } + } + return result.sort(); }; -var onTabCompleteJS = function( result, cmdSender, pluginCmd, cmdAlias, cmdArgs) { +var onTabCompleteJS = function( result, cmdSender, pluginCmd, cmdAlias, cmdArgs ) { - cmdArgs = Array.prototype.slice.call(cmdArgs, 0); + var _globalSymbols, + lastArg, + propsOfLastArg, + statement, + statementSyms, + lastSymbol, + parts, + name, + symbol, + lastGoodSymbol, + i, + objectProps, + candidate, + re, + li, + possibleCompletion; - if (pluginCmd.name == 'jsp') - return tabCompleteJSP( result, cmdSender, pluginCmd, cmdAlias, cmdArgs ); + cmdArgs = Array.prototype.slice.call( cmdArgs, 0 ); - global.self = cmdSender; // bring in self just for autocomplete + if ( pluginCmd.name == 'jsp' ) { + return tabCompleteJSP( result, cmdSender, pluginCmd, cmdAlias, cmdArgs ); + } + global.self = cmdSender; // bring in self just for autocomplete - var _globalSymbols = _getProperties(global) + _globalSymbols = _getProperties(global); - var lastArg = cmdArgs.length?cmdArgs[cmdArgs.length-1]+'':null; - var propsOfLastArg = []; - var statement = cmdArgs.join(' '); - - statement = statement.replace(/^\s+/,'').replace(/\s+$/,''); - - - if (statement.length == 0) - propsOfLastArg = _globalSymbols; - else{ - var statementSyms = statement.split(/[^\$a-zA-Z0-9_\.]/); - var lastSymbol = statementSyms[statementSyms.length-1]; - //print('DEBUG: lastSymbol=[' + lastSymbol + ']'); + lastArg = cmdArgs.length?cmdArgs[cmdArgs.length-1]+'':null; + propsOfLastArg = []; + statement = cmdArgs.join(' '); + + statement = statement.replace(/^\s+/,'').replace(/\s+$/,''); + + if ( statement.length == 0 ) { + propsOfLastArg = _globalSymbols; + } else { + statementSyms = statement.split(/[^\$a-zA-Z0-9_\.]/); + lastSymbol = statementSyms[statementSyms.length-1]; + //print('DEBUG: lastSymbol=[' + lastSymbol + ']'); + // + // try to complete the object ala java IDEs. + // + parts = lastSymbol.split(/\./); + name = parts[0]; + symbol = global[name]; + lastGoodSymbol = symbol; + if ( typeof symbol != 'undefined' ) { + for ( i = 1; i < parts.length; i++ ) { + name = parts[i]; + symbol = symbol[name]; + if ( typeof symbol == 'undefined' ) { + break; + } + lastGoodSymbol = symbol; + } + //print('debug:name['+name+']lastSymbol['+lastSymbol+']symbol['+symbol+']'); + if ( typeof symbol == 'undefined' ) { // - // try to complete the object ala java IDEs. + // look up partial matches against last good symbol // - var parts = lastSymbol.split(/\./); - var name = parts[0]; - var symbol = global[name]; - var lastGoodSymbol = symbol; - if (typeof symbol != 'undefined') - { - for (var i = 1; i < parts.length;i++){ - name = parts[i]; - symbol = symbol[name]; - if (typeof symbol == 'undefined') - break; - lastGoodSymbol = symbol; + objectProps = _getProperties( lastGoodSymbol ); + if ( name == '' ) { + // if the last symbol looks like this.. + // ScriptCraft. + // + + for ( i =0; i < objectProps.length; i++ ) { + candidate = lastSymbol + objectProps[i]; + re = new RegExp( lastSymbol + '$', 'g' ); + propsOfLastArg.push( lastArg.replace( re, candidate ) ); + } + + } else { + // it looks like this.. + // ScriptCraft.co + // + //print('debug:case Y: ScriptCraft.co'); + + li = statement.lastIndexOf(name); + for ( i = 0; i < objectProps.length; i++ ) { + if ( objectProps[i].indexOf(name) == 0 ) { + candidate = lastSymbol.substring( 0, lastSymbol.lastIndexOf( name ) ); + candidate = candidate + objectProps[i]; + re = new RegExp( lastSymbol + '$', 'g' ); + propsOfLastArg.push( lastArg.replace( re, candidate ) ); } - //print('debug:name['+name+']lastSymbol['+lastSymbol+']symbol['+symbol+']'); - if (typeof symbol == 'undefined'){ - // - // look up partial matches against last good symbol - // - var objectProps = _getProperties(lastGoodSymbol); - if (name == ''){ - // if the last symbol looks like this.. - // ScriptCraft. - // - - for (var i =0;i < objectProps.length;i++){ - var candidate = lastSymbol + objectProps[i]; - var re = new RegExp(lastSymbol + '$','g'); - propsOfLastArg.push(lastArg.replace(re,candidate)); - } - - }else{ - // it looks like this.. - // ScriptCraft.co - // - //print('debug:case Y: ScriptCraft.co'); - - var li = statement.lastIndexOf(name); - for (var i = 0; i < objectProps.length;i++){ - if (objectProps[i].indexOf(name) == 0) - { - var candidate = lastSymbol.substring(0,lastSymbol.lastIndexOf(name)); - candidate = candidate + objectProps[i]; - var re = new RegExp(lastSymbol+ '$','g'); - //print('DEBUG: re=' + re + ',lastSymbol='+lastSymbol+',lastArg=' + lastArg + ',candidate=' + candidate); - propsOfLastArg.push(lastArg.replace(re,candidate)); - } - } - - } - }else{ - //print('debug:case Z:ScriptCraft'); - var objectProps = _getProperties(symbol); - for (var i = 0; i < objectProps.length; i++){ - var re = new RegExp(lastSymbol+ '$','g'); - propsOfLastArg.push(lastArg.replace(re,lastSymbol + '.' + objectProps[i])); - } - } - }else{ - //print('debug:case AB:ScriptCr'); - // loop thru globalSymbols looking for a good match - for (var i = 0;i < _globalSymbols.length; i++){ - if (_globalSymbols[i].indexOf(lastSymbol) == 0){ - var possibleCompletion = _globalSymbols[i]; - var re = new RegExp(lastSymbol+ '$','g'); - propsOfLastArg.push(lastArg.replace(re,possibleCompletion)); - } - } - + } } + } else { + objectProps = _getProperties( symbol ); + for ( i = 0; i < objectProps.length; i++ ) { + re = new RegExp( lastSymbol+ '$', 'g' ); + propsOfLastArg.push( lastArg.replace( re, lastSymbol + '.' + objectProps[i] ) ); + } + } + } else { + for ( i = 0; i < _globalSymbols.length; i++ ) { + if ( _globalSymbols[i].indexOf(lastSymbol) == 0 ) { + possibleCompletion = _globalSymbols[i]; + re = new RegExp( lastSymbol+ '$', 'g' ); + propsOfLastArg.push( lastArg.replace( re, possibleCompletion ) ); + } + } } - for (var i = 0;i < propsOfLastArg.length; i++) - result.add(propsOfLastArg[i]); + } + for ( i = 0; i < propsOfLastArg.length; i++ ) { + result.add( propsOfLastArg[i] ); + } - delete global.self; // delete self when no longer needed for autocomplete + delete global.self; // delete self when no longer needed for autocomplete }; module.exports = onTabCompleteJS; diff --git a/src/main/js/modules/blocks.js b/src/main/js/modules/blocks.js index b16d358..ae4fcd8 100644 --- a/src/main/js/modules/blocks.js +++ b/src/main/js/modules/blocks.js @@ -21,261 +21,261 @@ The blocks module is globally exported by the Drone module. ***/ var blocks = { - air: 0, - stone: 1, - grass: 2, - dirt: 3, - cobblestone: 4, - oak: 5, - spruce: '5:1', - birch: '5:2', - jungle: '5:3', - sapling: { - oak: 6, - spruce: '6:1', - birch: '6:2', - jungle: '6:3' - }, - bedrock: 7, - water: 8, - water_still: 9, - lava: 10, - lava_still: 11, - sand: 12, - gravel: 13, - gold_ore: 14, - iron_ore: 15, - coal_ore: 16, - wood: 17, - leaves: 18, - sponge: 19, - glass: 20, - lapis_lazuli_ore: 21, - lapis_lazuli_block: 22, - dispenser: 23, - sandstone: 24, - note: 25, - bed: 26, - powered_rail: 27, - detector_rail: 28, - sticky_piston: 29, - cobweb: 30, - grass_tall: 31, - dead_bush: 32, - piston: 33, - piston_extn: 34, - wool: { - white: 35 // All other colors added below - }, - dandelion: 37, - flower_yellow: 37, - rose: 38, - flower_red: 38, - mushroom_brown: 39, - mushroom_red: 40, - gold: 41, - iron: 42, - tnt: 46, - bookshelf: 47, - moss_stone: 48, - obsidian: 49, - torch: 50, - fire: 51, - monster_spawner: 52, - stairs: { - oak: 53, - cobblestone: 67, - brick: 108, - stone: 109, - nether: 114, - sandstone: 128, - spruce: 134, - birch: 135, - jungle: 136, - quartz: 156 - }, - chest: 54, - redstone_wire: 55, - diamond_ore: 56, - diamond: 57, - crafting_table: 58, - wheat_seeds: 59, - farmland: 60, - furnace: 61, - furnace_burning: 62, - sign_post: 63, - door_wood: 64, - ladder: 65, - rail: 66, - sign: 68, - lever: 69, - pressure_plate_stone: 70, - door_iron: 71, - pressure_plate_wood: 72, - redstone_ore: 73, - redstone_ore_glowing: 74, - torch_redstone: 75, - torch_redstone_active: 76, - stone_button: 77, - ice: 79, - snow: 80, - cactus: 81, - clay: 82, - sugar_cane: 83, - jukebox: 84, - fence: 85, - pumpkin: 86, - netherrack: 87, - soulsand: 88, - glowstone: 89, - netherportal: 90, - jackolantern: 91, - cake: 92, - redstone_repeater: 93, - redeston_repeater_active: 94, - chest_locked: 95, - trapdoor: 96, - monster_egg: 97, - brick: { - stone: 98, - mossy: '98:1', - cracked: '98:2', - chiseled: '98:3', - red: 45 - }, - mushroom_brown_huge: 99, - mushroom_red_huge: 100, - iron_bars: 101, - glass_pane: 102, - melon: 103, - pumpkin_stem: 104, - melon_stem: 105, - vines: 106, - fence_gate: 107, - mycelium: 110, - lily_pad: 111, - nether: 112, - nether_fence: 113, - netherwart: 115, - table_enchantment: 116, - brewing_stand: 117, - cauldron: 118, - endportal: 119, - endportal_frame: 120, - endstone: 121, - dragon_egg: 122, - redstone_lamp: 123, - redstone_lamp_active: 124, - slab: { - snow: 78, - stone: 44, - sandstone: '44:1', - wooden: '44:2', - cobblestone: '44:3', - brick: '44:4', - stonebrick: '44:5', - netherbrick:'44:6', - quartz: '44:7', - oak: 126, - spruce: '126:1', - birch: '126:2', - jungle: '126:3', - upper: { - stone: '44:8', - sandstone: '44:9', - wooden: '44:10', - cobblestone: '44:11', - brick: '44:12', - stonebrick: '44:13', - netherbrick:'44:14', - quartz: '44:15', - oak: '126:8', - spruce: '126:9', - birch: '126:10', - jungle: '126:11', - } - }, - cocoa: 127, - emerald_ore: 129, - enderchest: 130, - tripwire_hook: 131, - tripwire: 132, - emerald: 133, - command: 137, - beacon: 138, - cobblestone_wall: 139, - flowerpot: 140, - carrots: 141, - potatoes: 142, - button_wood: 143, - mobhead: 144, - anvil: 145, - chest_trapped: 146, - pressure_plate_weighted_light: 147, - pressure_plate_weighted_heavy: 148, - redstone_comparator: 149, - redstone_comparator_active: 150, - daylight_sensor: 151, - redstone: 152, - netherquartzore: 153, - hopper: 154, - quartz: 155, - rail_activator: 157, - dropper: 158, - stained_clay: { - white: 159 // All other colors added below - }, - hay: 170, - carpet: { - white: 171 // All other colors added below - }, - hardened_clay: 172, - coal_block: 173 + air: 0, + stone: 1, + grass: 2, + dirt: 3, + cobblestone: 4, + oak: 5, + spruce: '5:1', + birch: '5:2', + jungle: '5:3', + sapling: { + oak: 6, + spruce: '6:1', + birch: '6:2', + jungle: '6:3' + }, + bedrock: 7, + water: 8, + water_still: 9, + lava: 10, + lava_still: 11, + sand: 12, + gravel: 13, + gold_ore: 14, + iron_ore: 15, + coal_ore: 16, + wood: 17, + leaves: 18, + sponge: 19, + glass: 20, + lapis_lazuli_ore: 21, + lapis_lazuli_block: 22, + dispenser: 23, + sandstone: 24, + note: 25, + bed: 26, + powered_rail: 27, + detector_rail: 28, + sticky_piston: 29, + cobweb: 30, + grass_tall: 31, + dead_bush: 32, + piston: 33, + piston_extn: 34, + wool: { + white: 35 // All other colors added below + }, + dandelion: 37, + flower_yellow: 37, + rose: 38, + flower_red: 38, + mushroom_brown: 39, + mushroom_red: 40, + gold: 41, + iron: 42, + tnt: 46, + bookshelf: 47, + moss_stone: 48, + obsidian: 49, + torch: 50, + fire: 51, + monster_spawner: 52, + stairs: { + oak: 53, + cobblestone: 67, + brick: 108, + stone: 109, + nether: 114, + sandstone: 128, + spruce: 134, + birch: 135, + jungle: 136, + quartz: 156 + }, + chest: 54, + redstone_wire: 55, + diamond_ore: 56, + diamond: 57, + crafting_table: 58, + wheat_seeds: 59, + farmland: 60, + furnace: 61, + furnace_burning: 62, + sign_post: 63, + door_wood: 64, + ladder: 65, + rail: 66, + sign: 68, + lever: 69, + pressure_plate_stone: 70, + door_iron: 71, + pressure_plate_wood: 72, + redstone_ore: 73, + redstone_ore_glowing: 74, + torch_redstone: 75, + torch_redstone_active: 76, + stone_button: 77, + ice: 79, + snow: 80, + cactus: 81, + clay: 82, + sugar_cane: 83, + jukebox: 84, + fence: 85, + pumpkin: 86, + netherrack: 87, + soulsand: 88, + glowstone: 89, + netherportal: 90, + jackolantern: 91, + cake: 92, + redstone_repeater: 93, + redeston_repeater_active: 94, + chest_locked: 95, + trapdoor: 96, + monster_egg: 97, + brick: { + stone: 98, + mossy: '98:1', + cracked: '98:2', + chiseled: '98:3', + red: 45 + }, + mushroom_brown_huge: 99, + mushroom_red_huge: 100, + iron_bars: 101, + glass_pane: 102, + melon: 103, + pumpkin_stem: 104, + melon_stem: 105, + vines: 106, + fence_gate: 107, + mycelium: 110, + lily_pad: 111, + nether: 112, + nether_fence: 113, + netherwart: 115, + table_enchantment: 116, + brewing_stand: 117, + cauldron: 118, + endportal: 119, + endportal_frame: 120, + endstone: 121, + dragon_egg: 122, + redstone_lamp: 123, + redstone_lamp_active: 124, + slab: { + snow: 78, + stone: 44, + sandstone: '44:1', + wooden: '44:2', + cobblestone: '44:3', + brick: '44:4', + stonebrick: '44:5', + netherbrick:'44:6', + quartz: '44:7', + oak: 126, + spruce: '126:1', + birch: '126:2', + jungle: '126:3', + upper: { + stone: '44:8', + sandstone: '44:9', + wooden: '44:10', + cobblestone: '44:11', + brick: '44:12', + stonebrick: '44:13', + netherbrick:'44:14', + quartz: '44:15', + oak: '126:8', + spruce: '126:9', + birch: '126:10', + jungle: '126:11' + } + }, + cocoa: 127, + emerald_ore: 129, + enderchest: 130, + tripwire_hook: 131, + tripwire: 132, + emerald: 133, + command: 137, + beacon: 138, + cobblestone_wall: 139, + flowerpot: 140, + carrots: 141, + potatoes: 142, + button_wood: 143, + mobhead: 144, + anvil: 145, + chest_trapped: 146, + pressure_plate_weighted_light: 147, + pressure_plate_weighted_heavy: 148, + redstone_comparator: 149, + redstone_comparator_active: 150, + daylight_sensor: 151, + redstone: 152, + netherquartzore: 153, + hopper: 154, + quartz: 155, + rail_activator: 157, + dropper: 158, + stained_clay: { + white: 159 // All other colors added below + }, + hay: 170, + carpet: { + white: 171 // All other colors added below + }, + hardened_clay: 172, + coal_block: 173 }; // Add all available colors to colorized block collections var colors = { - orange: ':1', - magenta: ':2', - lightblue: ':3', - yellow: ':4', - lime: ':5', - pink: ':6', - gray: ':7', - lightgray: ':8', - cyan: ':9', - purple: ':10', - blue: ':11', - brown: ':12', - green: ':13', - red: ':14', - black: ':15' + orange: ':1', + magenta: ':2', + lightblue: ':3', + yellow: ':4', + lime: ':5', + pink: ':6', + gray: ':7', + lightgray: ':8', + cyan: ':9', + purple: ':10', + blue: ':11', + brown: ':12', + green: ':13', + red: ':14', + black: ':15' }; -var colorized_blocks = ["wool", "stained_clay", "carpet"]; +var colorized_blocks = ['wool', 'stained_clay', 'carpet']; for (var i = 0, len = colorized_blocks.length; i < len; i++) { - var block = colorized_blocks[i], - data_value = blocks[block].white; - - for (var color in colors) { - blocks[block][color] = data_value + colors[color]; - } + var block = colorized_blocks[i], + data_value = blocks[block].white; + + for (var color in colors) { + blocks[block][color] = data_value + colors[color]; + } }; /* - rainbow colors - a convenience - Color aliased properties that were a direct descendant of the blocks - object are no longer used to avoid confusion with carpet and stained - clay blocks. -*/ -blocks.rainbow = [blocks.wool.red, - blocks.wool.orange, - blocks.wool.yellow, - blocks.wool.lime, - blocks.wool.lightblue, - blocks.wool.blue, - blocks.wool.purple]; - + rainbow colors - a convenience + Color aliased properties that were a direct descendant of the blocks + object are no longer used to avoid confusion with carpet and stained + clay blocks. + */ +blocks.rainbow = [ + blocks.wool.red, + blocks.wool.orange, + blocks.wool.yellow, + blocks.wool.lime, + blocks.wool.lightblue, + blocks.wool.blue, + blocks.wool.purple]; module.exports = blocks; diff --git a/src/main/js/modules/fireworks/fireworks.js b/src/main/js/modules/fireworks/fireworks.js index 6e59003..bcaffe3 100644 --- a/src/main/js/modules/fireworks/fireworks.js +++ b/src/main/js/modules/fireworks/fireworks.js @@ -27,7 +27,7 @@ To call the fireworks.firework() function directly, you must provide a location. For example... /js var fireworks = require('fireworks'); - /js fireworks.firework(self.location); + /js fireworks.firework( self.location ); ![firework example](img/firework.png) @@ -36,44 +36,45 @@ location. For example... /* create a firework at the given location */ -var firework = function(location){ - var Color = org.bukkit.Color; - var FireworkEffect = org.bukkit.FireworkEffect; - var EntityType = org.bukkit.entity.EntityType; +var firework = function( location ) { + var Color = org.bukkit.Color; + var FireworkEffect = org.bukkit.FireworkEffect; + var EntityType = org.bukkit.entity.EntityType; - var randInt = function(n){ - return Math.floor(Math.random() * n); - }; - var getColor = function(i){ - var colors = [ - Color.AQUA, Color.BLACK, Color.BLUE, Color.FUCHSIA, Color.GRAY, - Color.GREEN, Color.LIME, Color.MAROON, Color.NAVY, Color.OLIVE, - Color.ORANGE, Color.PURPLE, Color.RED, Color.SILVER, Color.TEAL, - Color.WHITE, Color.YELLOW]; - return colors[i]; - }; - var fw = location.world.spawnEntity(location, EntityType.FIREWORK); - var fwm = fw.getFireworkMeta(); - var fwTypes = [FireworkEffect.Type.BALL, - FireworkEffect.Type.BALL_LARGE, - FireworkEffect.Type.BURST, - FireworkEffect.Type.CREEPER, - FireworkEffect.Type.STAR]; - var type = fwTypes[randInt(5)]; - - var r1i = randInt(17); - var r2i = randInt(17); - var c1 = getColor(r1i); - var c2 = getColor(r2i); - var effectBuilder = FireworkEffect.builder() - .flicker(Math.round(Math.random())==0) - .withColor(c1) - .withFade(c2).trail(Math.round(Math.random())==0); - effectBuilder['with'](type); - var effect = effectBuilder.build(); - fwm.addEffect(effect); - fwm.setPower(randInt(2)+1); - fw.setFireworkMeta(fwm); + var randInt = function( n ) { + return Math.floor( Math.random() * n ); + }; + var getColor = function( i ) { + var colors = [ + Color.AQUA, Color.BLACK, Color.BLUE, Color.FUCHSIA, Color.GRAY, + Color.GREEN, Color.LIME, Color.MAROON, Color.NAVY, Color.OLIVE, + Color.ORANGE, Color.PURPLE, Color.RED, Color.SILVER, Color.TEAL, + Color.WHITE, Color.YELLOW]; + return colors[i]; + }; + var fw = location.world.spawnEntity(location, EntityType.FIREWORK); + var fwm = fw.getFireworkMeta(); + var fwTypes = [FireworkEffect.Type.BALL, + FireworkEffect.Type.BALL_LARGE, + FireworkEffect.Type.BURST, + FireworkEffect.Type.CREEPER, + FireworkEffect.Type.STAR]; + var type = fwTypes[ randInt( 5 ) ]; + + var r1i = randInt( 17 ); + var r2i = randInt( 17 ); + var c1 = getColor( r1i ); + var c2 = getColor( r2i ); + var effectBuilder = FireworkEffect.builder() + .flicker( Math.round( Math.random() ) == 0 ) + .withColor( c1 ) + .withFade( c2 ) + .trail( Math.round( Math.random() ) == 0 ); + effectBuilder['with']( type ); + var effect = effectBuilder.build(); + fwm.addEffect( effect ); + fwm.setPower( randInt( 2 ) + 1 ); + fw.setFireworkMeta( fwm ); }; exports.firework = firework; diff --git a/src/main/js/modules/http/request.js b/src/main/js/modules/http/request.js index 00d2965..713d69b 100644 --- a/src/main/js/modules/http/request.js +++ b/src/main/js/modules/http/request.js @@ -37,74 +37,76 @@ The following example illustrates how to use http.request to make a request to a ... The following example illustrates a more complex use-case POSTing parameters to a CGI process on a server... var http = require('./http/request'); - http.request({ url: "http://pixenate.com/pixenate/pxn8.pl", - method: "POST", - params: {script: "[]"} - }, function( responseCode, responseBody){ - var jsObj = eval("(" + responseBody + ")"); + http.request( + { + url: 'http://pixenate.com/pixenate/pxn8.pl', + method: 'POST', + params: {script: '[]'} + }, + function( responseCode, responseBody ) { + var jsObj = eval('(' + responseBody + ')'); }); ***/ -exports.request = function( request, callback) -{ - var paramsToString = function(params){ - var result = ""; - var paramNames = []; - for (var i in params){ - paramNames.push(i); - } - for (var i = 0;i < paramNames.length;i++){ - result += paramNames[i] + "=" + encodeURI(params[paramNames[i]]); - if (i < paramNames.length-1) - result += "&"; - } - return result; - }; - - server.scheduler.runTaskAsynchronously(__plugin,function() - { - var url, paramsAsString, conn, requestMethod; - if (typeof request === "string"){ - url = request; - requestMethod = "GET"; - }else{ - paramsAsString = paramsToString(request.params); - if (request.method) - requestMethod = request.method - else - requestMethod = "GET"; +exports.request = function( request, callback ) { + var paramsToString = function( params ) { + var result = '', + paramNames = [], + i; + for ( i in params ) { + paramNames.push( i ); + } + for ( i = 0; i < paramNames.length; i++ ) { + result += paramNames[i] + '=' + encodeURI( params[ paramNames[i] ] ); + if ( i < paramNames.length-1 ) + result += '&'; + } + return result; + }; + + server.scheduler.runTaskAsynchronously( __plugin, function() { + var url, paramsAsString, conn, requestMethod; + if (typeof request === 'string'){ + url = request; + requestMethod = 'GET'; + }else{ + paramsAsString = paramsToString( request.params ); + if ( request.method ) { + requestMethod = request.method; + } else { + requestMethod = 'GET'; + } + if ( requestMethod == 'GET' && request.params ) { + // append each parameter to the URL + url = request.url + '?' + paramsAsString; + } + } + conn = new java.net.URL( url ).openConnection(); + conn.requestMethod = requestMethod; + conn.doOutput = true; + conn.instanceFollowRedirects = false; - if (requestMethod == "GET" && request.params){ - // append each parameter to the URL - url = request.url + "?" + paramsAsString; - } - } - conn = new java.net.URL(url).openConnection(); - conn.requestMethod = requestMethod; - conn.doOutput = true; - conn.instanceFollowRedirects = false; - - if (conn.requestMethod == "POST"){ - conn.doInput = true; - // put each parameter in the outputstream - conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); - conn.setRequestProperty("charset", "utf-8"); - conn.setRequestProperty("Content-Length", "" + paramsAsString.length); - conn.useCaches =false ; - wr = new java.io.DataOutputStream(conn.getOutputStream ()); - wr.writeBytes(paramsAsString); - wr.flush(); - wr.close(); - } - var rc = conn.responseCode; - var response; - var stream; - if (rc == 200){ - stream = conn.getInputStream(); - response = new java.util.Scanner(stream).useDelimiter("\\A").next(); - } - server.scheduler.runTask(__plugin,function(){ - callback(rc,response); - }); + if ( conn.requestMethod == 'POST' ) { + conn.doInput = true; + // put each parameter in the outputstream + conn.setRequestProperty('Content-Type', 'application/x-www-form-urlencoded'); + conn.setRequestProperty('charset', 'utf-8'); + conn.setRequestProperty('Content-Length', '' + paramsAsString.length); + conn.useCaches =false ; + wr = new java.io.DataOutputStream(conn.getOutputStream ()); + wr.writeBytes(paramsAsString); + wr.flush(); + wr.close(); + } + var rc = conn.responseCode; + var response; + var stream; + if ( rc == 200 ) { + stream = conn.getInputStream(); + response = new java.util.Scanner( stream ).useDelimiter("\\A").next(); + } + server.scheduler.runTask( __plugin, function( ) { + callback( rc, response ); }); + }); }; diff --git a/src/main/js/modules/minigames/scoreboard.js b/src/main/js/modules/minigames/scoreboard.js index 20d8628..c526203 100644 --- a/src/main/js/modules/minigames/scoreboard.js +++ b/src/main/js/modules/minigames/scoreboard.js @@ -2,44 +2,49 @@ The scoreboard is a simple wrapper around the Bukkit Scoreboard API. It's only concerned with display of scores, not maintaining them - that's the game's job. */ -module.exports = function(options){ - var temp = {}; - var ccScoreboard; - var DisplaySlot = org.bukkit.scoreboard.DisplaySlot; +module.exports = function( options ) { + var temp = {}; + var ccScoreboard; + var DisplaySlot = org.bukkit.scoreboard.DisplaySlot; - return { - start: function(){ - var objective, slot; - ccScoreboard = server.scoreboardManager.getNewScoreboard(); - for (objective in options){ - var ccObj = ccScoreboard.registerNewObjective(objective,'dummy'); - for (slot in options[objective]){ - ccObj.displaySlot = DisplaySlot[slot]; - ccObj.displayName = options[objective][slot]; - } - } - }, - stop: function(){ - var objective, slot; - for (objective in options){ - ccScoreboard.getObjective(objective).unregister(); - for (slot in options[objective]){ - ccScoreboard.clearSlot(DisplaySlot[slot]); - } - } - }, - update: function(objective,player,score){ - if (player.scoreboard && player.scoreboard != ccScoreboard) - { - temp[player.name] = player.scoreboard; - player.scoreboard = ccScoreboard; - } - ccScoreboard.getObjective(objective).getScore(player).score = score; - }, - restore: function(player){ - // offlineplayers don't have a scoreboard - if (player.scoreboard) - player.scoreboard = temp[player.name]; + return { + start: function( ) { + var objective, + slot, + ccObj; + ccScoreboard = server.scoreboardManager.getNewScoreboard(); + for ( objective in options ) { + ccObj = ccScoreboard.registerNewObjective( objective, 'dummy' ); + for ( slot in options[ objective ] ) { + ccObj.displaySlot = DisplaySlot[ slot ]; + ccObj.displayName = options[ objective ][ slot ]; } - }; + } + }, + stop: function(){ + var objective, slot; + for ( objective in options ) { + ccScoreboard.getObjective(objective).unregister(); + for ( slot in options[ objective ] ) { + ccScoreboard.clearSlot( DisplaySlot[ slot ] ); + } + } + }, + update: function( objective, player, score ) { + if ( player.scoreboard && player.scoreboard != ccScoreboard ) { + temp[player.name] = player.scoreboard; + player.scoreboard = ccScoreboard; + } + ccScoreboard + .getObjective( objective ) + .getScore( player ) + .score = score; + }, + restore: function( player ) { + // offlineplayers don't have a scoreboard + if ( player.scoreboard ) { + player.scoreboard = temp[ player.name ]; + } + } + }; }; diff --git a/src/main/js/modules/partial.js b/src/main/js/modules/partial.js deleted file mode 100644 index f9c40f7..0000000 --- a/src/main/js/modules/partial.js +++ /dev/null @@ -1,14 +0,0 @@ -/** -* Create a partial function -* -* Parameters: -* func - base function -* [remaining arguments] - arguments bound to the partial function -*/ -module.exports = function (func /*, 0..n args */) { - var args = Array.prototype.slice.call(arguments, 1); - return function() { - var allArguments = args.concat(Array.prototype.slice.call(arguments)); - return func.apply(this, allArguments); - }; -} diff --git a/src/main/js/modules/sc-mqtt.js b/src/main/js/modules/sc-mqtt.js index 80b35ab..2846b49 100644 --- a/src/main/js/modules/sc-mqtt.js +++ b/src/main/js/modules/sc-mqtt.js @@ -34,24 +34,24 @@ present in the CraftBukkit classpath. To use this module, you should // create a new client - var client = mqtt.client('tcp://localhost:1883', 'uniqueClientId'); + var client = mqtt.client( 'tcp://localhost:1883', 'uniqueClientId' ); // connect to the broker - client.connect({ keepAliveInterval: 15 }); + client.connect( { keepAliveInterval: 15 } ); // publish a message to the broker - client.publish('minecraft','loaded'); + client.publish( 'minecraft', 'loaded' ); // subscribe to messages on 'arduino' topic - client.subscribe('arduino'); + client.subscribe( 'arduino' ); // do something when an incoming message arrives... - client.onMessageArrived(function(topic, message){ - console.log('Message arrived: topic=' + topic + ', message=' + message); + client.onMessageArrived( function( topic, message ) { + console.log( 'Message arrived: topic=' + topic + ', message=' + message ); }); The `sc-mqtt` module provides a very simple minimal wrapper around the @@ -66,35 +66,34 @@ var MISSING_MQTT = '\nMissing class org.walterhiggins.scriptcraft.ScriptCraftMqt 'Make sure sc-mqtt.jar is in the classpath.\n' + 'See http://github.com/walterhiggins/scriptcraft-extras-mqtt for details.\n'; -function Client(brokerUrl, clientId){ +function Client( brokerUrl, clientId ) { var Callback = org.walterhiggins.scriptcraft.ScriptCraftMqttCallback; var MqttClient = org.eclipse.paho.client.mqttv3.MqttClient; var callback = new Callback( - function(err){ - console.log('connectionLost: ' + err); + function( err ) { + console.log( 'connectionLost: ' + err ); }, - function(topic, message){ - console.log('messageArrived ' + topic + '> ' + message); + function( topic, message ) { + console.log( 'messageArrived ' + topic + '> ' + message ); }, - function(token){ - console.log('deliveryComplete:' + token); + function( token ) { + console.log( 'deliveryComplete:' + token ); } ); - if (!brokerUrl){ + if ( !brokerUrl ) { brokerUrl = 'tcp://localhost:1883'; } - if (!clientId){ + if ( !clientId ) { clientId = 'scriptcraft' + new Date().getTime(); } - var client = new MqttClient(brokerUrl, clientId, null); - client.setCallback(callback); + var client = new MqttClient( brokerUrl, clientId, null ); + client.setCallback( callback ); return { - - connect: function(options){ - if (typeof options === 'undefined'){ + connect: function( options ) { + if ( typeof options === 'undefined' ) { client.connect(); }else{ client.connect(options); @@ -102,17 +101,18 @@ function Client(brokerUrl, clientId){ return client; }, - disconnect: function(quiesceTimeout){ - if (typeof quiesceTimeout == 'undefined') + disconnect: function( quiesceTimeout ) { + if ( typeof quiesceTimeout == 'undefined' ) { client.disconnect(); - else - client.disconnect(quiesceTimeout); + } else { + client.disconnect( quiesceTimeout ); + } return client; }, - publish: function(topic, message, qos, retained){ - if (typeof message == 'string'){ - message = new java.lang.String(message).bytes; + publish: function( topic, message, qos, retained ) { + if ( typeof message == 'string' ) { + message = new java.lang.String( message ).bytes; } if (typeof qos == 'undefined'){ qos = 1; @@ -120,41 +120,43 @@ function Client(brokerUrl, clientId){ if (typeof retained == 'undefined'){ retained = false; } - client.publish(topic, message,qos, retained); + client.publish( topic, message,qos, retained ); return client; }, - subscribe: function(topic){ - client.subscribe(topic); + subscribe: function( topic ) { + client.subscribe( topic ); return client; }, - unsubscribe: function(topic){ - client.unsubscribe(topic); + unsubscribe: function( topic ) { + client.unsubscribe( topic ); return client; }, - onMessageArrived: function(fn){ - callback.setMesgArrived(fn); + onMessageArrived: function( fn ) { + callback.setMesgArrived( fn ); return client; }, - onDeliveryComplete: function(fn){ - callback.setDeliveryComplete(fn); + onDeliveryComplete: function( fn ) { + callback.setDeliveryComplete( fn ); return client; }, - onConnectionLost: function(fn){ - callback.setConnLost(fn); + onConnectionLost: function( fn ) { + callback.setConnLost( fn ); return client; } }; } - -exports.client = function(brokerUrl, clientId, options){ - if (typeof org.walterhiggins.scriptcraft.ScriptCraftMqttCallback != 'function'){ +/* + Return a new MQTT Client +*/ +exports.client = function( brokerUrl, clientId, options ) { + if ( typeof org.walterhiggins.scriptcraft.ScriptCraftMqttCallback != 'function' ) { throw MISSING_MQTT; } - return new Client(brokerUrl, clientId, options); + return new Client( brokerUrl, clientId, options ); }; diff --git a/src/main/js/modules/signs/menu.js b/src/main/js/modules/signs/menu.js index 32aba39..16c011f 100644 --- a/src/main/js/modules/signs/menu.js +++ b/src/main/js/modules/signs/menu.js @@ -8,35 +8,39 @@ var _store = {}; */ var signs = plugin("signs", { /* - construct an interactive menu which can then be attached to a Sign. - */ + construct an interactive menu which can then be attached to a Sign. + */ menu: function( - /* String */ label, - /* Array */ options, - /* Function */ onInteract, - /* Number */ defaultSelection ){}, + /* String */ label, + /* Array */ options, + /* Function */ onInteract, + /* Number */ defaultSelection ){}, store: _store -},true); + }, + true); module.exports = signs; /* redraw a menu sign */ -var _redrawMenuSign = function(p_sign,p_selectedIndex,p_displayOptions) -{ - var optLen = p_displayOptions.length; - // the offset is where the menu window begins - var offset = Math.max(0, Math.min(optLen-3, Math.floor(p_selectedIndex/3) * 3)); - for (var i = 0;i < 3; i++){ - var text = ""; - if (offset+i < optLen) - text = p_displayOptions[offset+i]; - if (offset+i == p_selectedIndex) - text = ("" + text).replace(/^ /,">"); - p_sign.setLine(i+1,text); +var _redrawMenuSign = function( p_sign, p_selectedIndex, p_displayOptions ) { + var optLen = p_displayOptions.length, + i, + text; + // the offset is where the menu window begins + var offset = Math.max( 0, Math.min( optLen-3, Math.floor( p_selectedIndex/3 ) * 3) ); + for ( i = 0;i < 3; i++ ) { + text = ""; + if ( offset+i < optLen ) { + text = p_displayOptions[offset+i]; } - p_sign.update(true); + if ( offset+i == p_selectedIndex ) { + text = ('' + text).replace(/^ /,">"); + } + p_sign.setLine( i+1, text ); + } + p_sign.update( true ); }; var _updaters = {}; @@ -44,152 +48,153 @@ var _updaters = {}; construct an interactive menu to be subsequently attached to one or more Signs. */ -signs.menu = function( - /* String */ label, - /* Array */ options, - /* Function */ callback, - /* Number */ selectedIndex -) -{ - - if (typeof selectedIndex == "undefined") - selectedIndex = 0; - // - // variables common to all instances of this menu can go here - // - var labelPadding = "---------------"; - var optionPadding = " "; - - var paddedLabel = (labelPadding+label+labelPadding).substr(((label.length+30)/2)-7,15); - var optLen = options.length; - var displayOptions = []; - for (var i =0;i < options.length;i++){ - displayOptions[i] = (" " + options[i] + optionPadding).substring(0,15); +signs.menu = function( /* String */ label, /* Array */ options, /* Function */ callback, /* Number */ selectedIndex ) { + + if ( typeof selectedIndex == "undefined" ) { + selectedIndex = 0; + } + // + // variables common to all instances of this menu can go here + // + var labelPadding = "---------------"; + var optionPadding = " "; + var i; + var paddedLabel = ( labelPadding + label + labelPadding) + .substr( ( ( label.length+30 ) / 2 ) - 7, 15 ); + var optLen = options.length; + var displayOptions = []; + for ( i = 0; i < options.length; i++ ) { + displayOptions[i] = (" " + options[i] + optionPadding).substring(0,15); + } + /* + this function is returned by signs.menu and when it is invoked it will + attach menu behaviour to an existing sign in the world. + signs.menu is for use by Plugin Authors. + The function returned by signs.menu is for use by admins/ops. + */ + var convertToMenuSign = function(/* Sign */ sign, save) { + var mouseLoc; + if (typeof save == "undefined") { + save = true; } /* - this function is returned by signs.menu and when it is invoked it will - attach menu behaviour to an existing sign in the world. - signs.menu is for use by Plugin Authors. - The function returned by signs.menu is for use by admins/ops. - */ - var convertToMenuSign = function(/* Sign */ sign, save) - { - if (typeof save == "undefined") - save = true; - /* - @deprecated start - all calls should explicitly provide a [org.bukkit.block.Sign][buksign] parameter. - */ - if (typeof sign == "undefined"){ - var mouseLoc = utils.getMousePos(); - if (mouseLoc){ - sign = mouseLoc.block.state; - if (!(sign && sign.setLine)){ - throw new Error("You must first provide a sign!"); - } - }else{ - throw new Error("You must first provide a sign!"); - } + @deprecated start + all calls should explicitly provide a [org.bukkit.block.Sign][buksign] parameter. + */ + if ( typeof sign == "undefined" ) { + mouseLoc = utils.getMousePos(); + if ( mouseLoc ) { + sign = mouseLoc.block.state; + if ( !( sign && sign.setLine ) ) { + throw new Error("You must first provide a sign!"); } - /* - @deprecated end - */ - // - // per-sign variables go here - // - var cSelectedIndex = selectedIndex; - sign.setLine(0,paddedLabel.bold()); - var _updateSign = function(p_player,p_sign) { - cSelectedIndex = (cSelectedIndex+1) % optLen; - _redrawMenuSign(p_sign,cSelectedIndex,displayOptions); - var signSelectionEvent = {player: p_player, - sign: p_sign, - text: options[cSelectedIndex], - number:cSelectedIndex}; - - callback(signSelectionEvent); - }; - - /* - get a unique ID for this particular sign instance - */ - var signLoc = sign.block.location; - var menuSignSaveData = utils.locationToJSON(signLoc); - var menuSignUID = JSON.stringify(menuSignSaveData); - /* - keep a reference to the update function for use by the event handler - */ - _updaters[menuSignUID] = _updateSign; - - // initialize the sign - _redrawMenuSign(sign,cSelectedIndex,displayOptions); - - /* - whenever a sign is placed somewhere in the world - (which is what this function does) - save its location for loading and initialization - when the server starts up again. - */ - if (save){ - if (typeof _store.menus == "undefined") - _store.menus = {}; - var signLocations = _store.menus[label]; - if (typeof signLocations == "undefined") - signLocations = _store.menus[label] = []; - signLocations.push(menuSignSaveData); - } - return sign; + } else { + throw new Error("You must first provide a sign!"); + } + } + /* + @deprecated end + */ + // + // per-sign variables go here + // + var cSelectedIndex = selectedIndex; + sign.setLine( 0, paddedLabel.bold() ); + var _updateSign = function( p_player, p_sign ) { + cSelectedIndex = ( cSelectedIndex + 1 ) % optLen; + _redrawMenuSign( p_sign, cSelectedIndex, displayOptions ); + var signSelectionEvent = { + player: p_player, + sign: p_sign, + text: options[ cSelectedIndex ], + number: cSelectedIndex + }; + callback( signSelectionEvent ); }; + /* + get a unique ID for this particular sign instance + */ + var signLoc = sign.block.location; + var menuSignSaveData = utils.locationToJSON( signLoc ); + var menuSignUID = JSON.stringify( menuSignSaveData ); /* - a new sign definition - need to store (in-memory only) - its behaviour and bring back to life other signs of the - same type in the world. Look for other static signs in the - world with this same label and make dynamic again. - */ + keep a reference to the update function for use by the event handler + */ + _updaters[ menuSignUID ] = _updateSign; - if (_store.menus && _store.menus[label]) - { - var signsOfSameLabel = _store.menus[label]; - var defragged = []; - var len = signsOfSameLabel.length; - for (var i = 0; i < len ; i++) - { - var loc = signsOfSameLabel[i]; - var world = org.bukkit.Bukkit.getWorld(loc.world); - if (!world) - continue; - var block = world.getBlockAt(loc.x,loc.y,loc.z); - if (block.state instanceof org.bukkit.block.Sign){ - convertToMenuSign(block.state,false); - defragged.push(loc); - } - } - /* - remove data for signs which no longer exist. - */ - if (defragged.length != len){ - _store.menus[label] = defragged; - } + // initialize the sign + _redrawMenuSign( sign, cSelectedIndex, displayOptions ); + + /* + whenever a sign is placed somewhere in the world + (which is what this function does) + save its location for loading and initialization + when the server starts up again. + */ + if ( save ) { + if ( typeof _store.menus == "undefined") { + _store.menus = {}; + } + var signLocations = _store.menus[label]; + if ( typeof signLocations == "undefined" ) { + signLocations = _store.menus[label] = []; + } + signLocations.push( menuSignSaveData ); } - return convertToMenuSign; + return sign; + }; + + /* + a new sign definition - need to store (in-memory only) + its behaviour and bring back to life other signs of the + same type in the world. Look for other static signs in the + world with this same label and make dynamic again. + */ + + if ( _store.menus && _store.menus[label] ) { + var signsOfSameLabel = _store.menus[ label ]; + var defragged = []; + var len = signsOfSameLabel.length; + for ( i = 0; i < len; i++ ) { + var loc = signsOfSameLabel[i]; + var world = org.bukkit.Bukkit.getWorld(loc.world); + if ( !world ) { + continue; + } + var block = world.getBlockAt( loc.x, loc.y, loc.z ); + if ( block.state instanceof org.bukkit.block.Sign ) { + convertToMenuSign( block.state, false ); + defragged.push( loc ); + } + } + /* + remove data for signs which no longer exist. + */ + if ( defragged.length != len ) { + _store.menus[label] = defragged; + } + } + return convertToMenuSign; }; // // update it every time player interacts with it. // -events.on('player.PlayerInteractEvent',function(listener, event) { - /* - look up our list of menu signs. If there's a matching location and there's - a sign, then update it. - */ +events.on( 'player.PlayerInteractEvent', function( listener, event ) { + /* + look up our list of menu signs. If there's a matching location and there's + a sign, then update it. + */ - if (! event.clickedBlock.state instanceof org.bukkit.block.Sign) - return; - var evtLocStr = utils.locationToString(event.clickedBlock.location); - var signUpdater = _updaters[evtLocStr] - if (signUpdater) - signUpdater(event.player, event.clickedBlock.state); + if ( ! event.clickedBlock.state instanceof org.bukkit.block.Sign ) { + return; + } + var evtLocStr = utils.locationToString(event.clickedBlock.location); + var signUpdater = _updaters[evtLocStr]; + if ( signUpdater ) { + signUpdater( event.player, event.clickedBlock.state ); + } }); diff --git a/src/main/js/modules/signs/signs.js b/src/main/js/modules/signs/signs.js index 686a6b1..7c5c8a6 100644 --- a/src/main/js/modules/signs/signs.js +++ b/src/main/js/modules/signs/signs.js @@ -92,16 +92,18 @@ the entity has targeted. It is a utility function for use by plugin authors. var utils = require('utils'); var menu = require('./menu'); // include all menu exports -for (var i in menu){ - exports[i] = menu[i]; +for ( var i in menu ) { + exports[i] = menu[i]; } -exports.getTargetedBy = function( livingEntity ){ - var location = utils.getMousePos( livingEntity ); - if (!location) - return null; - var state = location.block.state; - if (!(state || state.setLine)) - return null; - return state; +exports.getTargetedBy = function( livingEntity ) { + var location = utils.getMousePos( livingEntity ); + if ( !location ) { + return null; + } + var state = location.block.state; + if ( ! (state || state.setLine) ) { + return null; + } + return state; }; diff --git a/src/main/js/modules/utils/string-exts.js b/src/main/js/modules/utils/string-exts.js index bb33e23..8167636 100644 --- a/src/main/js/modules/utils/string-exts.js +++ b/src/main/js/modules/utils/string-exts.js @@ -37,43 +37,43 @@ Example ------- /js var boldGoldText = "Hello World".bold().gold(); - /js self.sendMessage(boldGoldText); + /js self.sendMessage( boldGoldText );

Hello World

***/ var c = org.bukkit.ChatColor; var formattingCodes = { - aqua: c.AQUA, - black: c.BLACK, - blue: c.BLUE, - bold: c.BOLD, - brightgreen: c.GREEN, - darkaqua: c.DARK_AQUA, - darkblue: c.DARK_BLUE, - darkgray: c.DARK_GRAY, - darkgreen: c.DARK_GREEN, - purple: c.LIGHT_PURPLE, - darkpurple: c.DARK_PURPLE, - darkred: c.DARK_RED, - gold: c.GOLD, - gray: c.GRAY, - green: c.GREEN, - italic: c.ITALIC, - lightpurple: c.LIGHT_PURPLE, - indigo: c.BLUE, - red: c.RED, - pink: c.LIGHT_PURPLE, - yellow: c.YELLOW, - white: c.WHITE, - strike: c.STRIKETHROUGH, - random: c.MAGIC, - magic: c.MAGIC, - underline: c.UNDERLINE, - reset: c.RESET + aqua: c.AQUA, + black: c.BLACK, + blue: c.BLUE, + bold: c.BOLD, + brightgreen: c.GREEN, + darkaqua: c.DARK_AQUA, + darkblue: c.DARK_BLUE, + darkgray: c.DARK_GRAY, + darkgreen: c.DARK_GREEN, + purple: c.LIGHT_PURPLE, + darkpurple: c.DARK_PURPLE, + darkred: c.DARK_RED, + gold: c.GOLD, + gray: c.GRAY, + green: c.GREEN, + italic: c.ITALIC, + lightpurple: c.LIGHT_PURPLE, + indigo: c.BLUE, + red: c.RED, + pink: c.LIGHT_PURPLE, + yellow: c.YELLOW, + white: c.WHITE, + strike: c.STRIKETHROUGH, + random: c.MAGIC, + magic: c.MAGIC, + underline: c.UNDERLINE, + reset: c.RESET }; -for (var method in formattingCodes){ - String.prototype[method] = function(c){ - return function(){return c+this;}; - }(formattingCodes[method]); +for ( var method in formattingCodes ) { + String.prototype[method] = function( c ) { + return function(){ return c + this; }; + }( formattingCodes[method] ); } diff --git a/src/main/js/modules/utils/utils.js b/src/main/js/modules/utils/utils.js index 9ed6730..6c438bc 100644 --- a/src/main/js/modules/utils/utils.js +++ b/src/main/js/modules/utils/utils.js @@ -36,18 +36,18 @@ String, then it tries to find the player with that name. ***/ var _player = function ( playerName ) { - if (typeof playerName == 'undefined'){ - if (typeof self == 'undefined'){ - return null; - } else { - return self; - } - } else { - if (typeof playerName == 'string') - return org.bukkit.Bukkit.getPlayer(playerName); - else - return playerName; // assumes it's a player object + if ( typeof playerName == 'undefined' ) { + if ( typeof self == 'undefined' ) { + return null; + } else { + return self; } + } else { + if ( typeof playerName == 'string' ) + return org.bukkit.Bukkit.getPlayer( playerName ); + else + return playerName; // assumes it's a player object + } }; /************************************************************************* ### utils.locationToJSON() function @@ -73,15 +73,15 @@ This can be useful if you write a plugin that needs to store location data since A JSON object in the above form. ***/ -var _locationToJSON = function(location){ - return { - world: ''+location.world.name, - x: location.x, - y: location.y, - z: location.z, - yaw: location.yaw, - pitch: location.pitch - }; +var _locationToJSON = function( location ) { + return { + world: ''+location.world.name, + x: location.x, + y: location.y, + z: location.z, + yaw: location.yaw, + pitch: location.pitch + }; }; /************************************************************************* ### utils.locationToString() function @@ -102,8 +102,8 @@ keys in a lookup table. lookupTable[key] = player.name; ***/ -exports.locationToString = function(location){ - return JSON.stringify(_locationToJSON(location)); +exports.locationToString = function( location ) { + return JSON.stringify( _locationToJSON( location ) ); }; exports.locationToJSON = _locationToJSON; @@ -117,21 +117,23 @@ returned by locationToJSON() and reconstructs and returns a bukkit Location object. ***/ -exports.locationFromJSON = function(json){ - if (json.constuctor == Array){ - // for support of legacy format - var world = org.bukkit.Bukkit.getWorld(json[0]); - return new org.bukkit.Location(world, json[1], json[2] , json[3]); - }else{ - var world = org.bukkit.Bukkit.getWorld(json.world); - return new org.bukkit.Location(world, json.x, json.y , json.z, json.yaw, json.pitch); - } +exports.locationFromJSON = function( json ) { + var world; + if ( json.constuctor == Array ) { + // for support of legacy format + world = org.bukkit.Bukkit.getWorld( json[0] ); + return new org.bukkit.Location( world, json[1], json[2] , json[3] ); + } else { + world = org.bukkit.Bukkit.getWorld( json.world ); + return new org.bukkit.Location( world, json.x, json.y , json.z, json.yaw, json.pitch ); + } }; exports.player = _player; -exports.getPlayerObject = function(player){ - console.warn('utils.getPlayerObject() is deprecated. Use utils.player() instead.'); - return _player(player); + +exports.getPlayerObject = function( player ) { + console.warn( 'utils.getPlayerObject() is deprecated. Use utils.player() instead.' ); + return _player(player); }; /************************************************************************* ### utils.getPlayerPos() function @@ -153,15 +155,14 @@ An [org.bukkit.Location][bkloc] object. [bksndr]: http://jd.bukkit.org/dev/apidocs/index.html?org/bukkit/command/CommandSender.html ***/ exports.getPlayerPos = function( player ) { - player = _player(player); - if (player){ - if (player instanceof org.bukkit.command.BlockCommandSender) - return player.block.location; - else - return player.location; - } + player = _player( player ); + if ( player ) { + if ( player instanceof org.bukkit.command.BlockCommandSender ) + return player.block.location; else - return null; + return player.location; + } + return null; }; /************************************************************************ ### utils.getMousePos() function @@ -186,19 +187,21 @@ The following code will strike lightning at the location the player is looking a } ***/ -exports.getMousePos = function (player) { - - player = _player(player); - if (!player) - return null; - // player might be CONSOLE or a CommandBlock - if (!player.getTargetBlock) - return null; - var targetedBlock = player.getTargetBlock(null,5); - if (targetedBlock == null || targetedBlock.isEmpty()){ - return null; - } - return targetedBlock.location; +exports.getMousePos = function( player ) { + + player = _player(player); + if ( !player ) { + return null; + } + // player might be CONSOLE or a CommandBlock + if ( !player.getTargetBlock ) { + return null; + } + var targetedBlock = player.getTargetBlock( null, 5 ); + if ( targetedBlock == null || targetedBlock.isEmpty() ) { + return null; + } + return targetedBlock.location; }; /************************************************************************ ### utils.foreach() function @@ -228,7 +231,7 @@ package for scheduling processing of arrays. - object : Additional (optional) information passed into the foreach method. - array : The entire array. - * object (optional) : An object which may be used by the callback. + * context (optional) : An object which may be used by the callback. * delay (optional, numeric) : If a delay is specified (in ticks - 20 ticks = 1 second), then the processing will be scheduled so that each item will be processed in turn with a delay between the completion of each @@ -286,20 +289,26 @@ without hogging CPU usage... utils.foreach (a, processItem, null, 10, onDone); ***/ -var _foreach = function(array, callback, object, delay, onCompletion) { - if (array instanceof java.util.Collection) - array = array.toArray(); - var i = 0; - var len = array.length; - if (delay){ - var next = function(){ callback(array[i],i,object,array); i++;}; - var hasNext = function(){return i < len;}; - _nicely(next,hasNext,onCompletion,delay); - }else{ - for (;i < len; i++){ - callback(array[i],i,object,array); - } +var _foreach = function( array, callback, context, delay, onCompletion ) { + if ( array instanceof java.util.Collection ) { + array = array.toArray(); + } + var i = 0; + var len = array.length; + if ( delay ) { + var next = function( ) { + callback(array[i], i, context, array); + i++; + }; + var hasNext = function( ) { + return i < len; + }; + _nicely( next, hasNext, onCompletion, delay ); + } else { + for ( ;i < len; i++ ) { + callback( array[i], i, context, array ); } + } }; exports.foreach = _foreach; /************************************************************************ @@ -327,16 +336,17 @@ function and the start of the next `next()` function. See the source code to utils.foreach for an example of how utils.nicely is used. ***/ -var _nicely = function(next, hasNext, onDone, delay){ - if (hasNext()){ - next(); - server.scheduler.runTaskLater(__plugin,function(){ - _nicely(next,hasNext,onDone,delay); - },delay); - }else{ - if (onDone) - onDone(); +var _nicely = function( next, hasNext, onDone, delay ) { + if ( hasNext() ){ + next(); + server.scheduler.runTaskLater( __plugin, function() { + _nicely( next, hasNext, onDone, delay ); + }, delay ); + }else{ + if ( onDone ) { + onDone(); } + } }; exports.nicely = _nicely; /************************************************************************ @@ -360,34 +370,34 @@ To warn players when night is approaching... utils.at( '19:00', function() { - utils.foreach( server.onlinePlayers, function(player){ - player.chat('The night is dark and full of terrors!'); - }); + utils.foreach( server.onlinePlayers, function( player ) { + player.chat( 'The night is dark and full of terrors!' ); + }); }); ***/ -exports.at = function(time24hr, callback, worlds) { - var forever = function(){ return true;}; - var timeParts = time24hr.split(':'); - var hrs = ((timeParts[0] * 1000) + 18000) % 24000; - var mins; - if (timeParts.length > 1) - mins = (timeParts[1] / 60) * 1000; - - var timeMc = hrs + mins; - if (typeof worlds == 'undefined'){ - worlds = server.worlds; - } - _nicely(function(){ - _foreach (worlds, function (world){ - var time = world.getTime(); - var diff = timeMc - time; - if (diff > 0 && diff < 100){ - callback(); - } - }); - },forever, null, 100); +exports.at = function( time24hr, callback, worlds ) { + var forever = function(){ return true; }; + var timeParts = time24hr.split( ':' ); + var hrs = ( (timeParts[0] * 1000) + 18000 ) % 24000; + var mins; + if ( timeParts.length > 1 ) { + mins = ( timeParts[1] / 60 ) * 1000; + } + var timeMc = hrs + mins; + if ( typeof worlds == 'undefined' ) { + worlds = server.worlds; + } + _nicely( function() { + _foreach( worlds, function ( world ) { + var time = world.getTime(); + var diff = timeMc - time; + if ( diff > 0 && diff < 100 ) { + callback(); + } + }); + }, forever, null, 100 ); }; /************************************************************************ @@ -411,25 +421,25 @@ a given directory and recursiving trawling all sub-directories. }); ***/ -exports.find = function( dir , filter){ - var result = []; - var recurse = function(dir, store){ - var files, dirfile = new java.io.File(dir); - - if (typeof filter == 'undefined') - files = dirfile.list(); - else - files = dirfile.list(filter); - - _foreach(files, function (file){ - file = new java.io.File(dir + '/' + file); - if (file.isDirectory()){ - recurse(file.canonicalPath, store); - }else{ - store.push(file.canonicalPath); - } - }); +exports.find = function( dir , filter ) { + var result = []; + var recurse = function( dir, store ) { + var files, dirfile = new java.io.File( dir ); + + if ( typeof filter == 'undefined' ) { + files = dirfile.list(); + } else { + files = dirfile.list(filter); } - recurse(dir,result); - return result; -} + _foreach( files, function( file ) { + file = new java.io.File( dir + '/' + file ); + if ( file.isDirectory() ) { + recurse( file.canonicalPath, store ); + } else { + store.push( file.canonicalPath ); + } + }); + }; + recurse( dir, result ); + return result; +}; diff --git a/src/main/js/plugins/alias/alias.js b/src/main/js/plugins/alias/alias.js index e8be545..35f58e3 100644 --- a/src/main/js/plugins/alias/alias.js +++ b/src/main/js/plugins/alias/alias.js @@ -52,7 +52,7 @@ console. Aliases will not be able to avail of command autocompletion ***/ -var _usage = "\ +var _usage = '\ /jsp alias set {alias} = {comand-1} ;{command-2}\n \ /jsp alias global {alias} = {command-1} ; {command-2}\n \ /jsp alias list\n \ @@ -61,7 +61,7 @@ Create a new alias : \n \ /jsp alias set cw = time set {1} ; weather {2}\n \ Execute the alias : \n \ /cw 4000 sun \n \ -...is the same as '/time set 4000' and '/weather sun'"; +...is the same as \'/time set 4000\' and \'/weather sun\''; /* persist aliases */ @@ -74,80 +74,80 @@ var _store = { _processParams is a private function which takes an array of parameters used for the 'set' and 'global' options. */ -var _processParams = function(params){ +var _processParams = function( params ) { var paramStr = params.join(' '), eqPos = paramStr.indexOf('='), - aliasCmd = paramStr.substring(0,eqPos).trim(), - aliasValue = paramStr.substring(eqPos+1).trim(); + aliasCmd = paramStr.substring( 0, eqPos).trim(), + aliasValue = paramStr.substring( eqPos + 1 ).trim(); return { cmd: aliasCmd, - aliases: aliasValue.split(/\s*;\s*/) + aliases: aliasValue.split( /\s*;\s*/ ) }; }; -var _set = function(params, player){ +var _set = function( params, player ) { var playerAliases = _store.players[player.name]; - if (!playerAliases){ + if (!playerAliases ) { playerAliases = {}; } - var o = _processParams(params); + var o = _processParams( params ); playerAliases[o.cmd] = o.aliases; _store.players[player.name] = playerAliases; - player.sendMessage("Alias '" + o.cmd + "' created."); + player.sendMessage( 'Alias ' + o.cmd + ' created.' ); }; -var _remove = function(params, player){ - if (_store.players[player.name] && - _store.players[player.name][params[0]]){ +var _remove = function( params, player ) { + if ( _store.players[player.name] && _store.players[player.name][ params[0] ] ) { delete _store.players[player.name][params[0]]; - player.sendMessage("Alias '" + params[0] + "' removed."); + player.sendMessage( 'Alias ' + params[0] + ' removed.' ); } else{ - player.sendMessage("Alias '" + params[0] + "' does not exist."); + player.sendMessage( 'Alias ' + params[0] + ' does not exist.' ); } - if (player.op){ - if (_store.global[params[0]]) + if ( player.op ) { + if ( _store.global[params[0]] ) { delete _store.global[params[0]]; + } } }; -var _global = function(params, player){ - if (!player.op){ - player.sendMessage("Only operators can set global aliases. " + - "You need to be an operator to perform this command."); +var _global = function( params, player ) { + if ( !player.op ) { + player.sendMessage( 'Only operators can set global aliases. ' + + 'You need to be an operator to perform this command.' ); return; } - var o = _processParams(params); + var o = _processParams( params ); _store.global[o.cmd] = o.aliases; - player.sendMessage("Global alias '" + o.cmd + "' created."); + player.sendMessage( 'Global alias ' + o.cmd + ' created.' ); }; -var _list = function(params, player){ +var _list = function( params, player ) { var alias = 0; try { - if (_store.players[player.name]){ - player.sendMessage("Your aliases:"); - for (alias in _store.players[player.name]){ - player.sendMessage(alias + " = " + - JSON.stringify(_store.players[player.name][alias])); + if ( _store.players[player.name] ) { + player.sendMessage('Your aliases:'); + for ( alias in _store.players[player.name] ) { + player.sendMessage( alias + ' = ' + + JSON.stringify( _store.players[player.name][alias] ) ); } - }else{ - player.sendMessage("You have no player-specific aliases."); + } else { + player.sendMessage( 'You have no player-specific aliases.' ); } - player.sendMessage("Global aliases:"); - for (alias in _store.global){ - player.sendMessage(alias + " = " + JSON.stringify(_store.global[alias]) ); + player.sendMessage( 'Global aliases:' ); + for ( alias in _store.global ) { + player.sendMessage( alias + ' = ' + JSON.stringify( _store.global[alias] ) ); } - }catch(e){ - console.error("Error in list function: " + e.message); + } catch( e ) { + console.error( 'Error in list function: ' + e.message ); throw e; } }; -var _help = function(params, player){ - player.sendMessage('Usage:\n' + _usage); +var _help = function( params, player ) { + player.sendMessage( 'Usage:\n' + _usage ); }; -var alias = plugin('alias', { +var alias = plugin( 'alias', { store: _store, set: _set, global: _global, @@ -156,11 +156,11 @@ var alias = plugin('alias', { help: _help }, true ); -var aliasCmd = command('alias', function( params, invoker ) { +var aliasCmd = command( 'alias', function( params, invoker ) { var operation = params[0], fn; - if (!operation){ - invoker.sendMessage('Usage:\n' + _usage); + if ( !operation ) { + invoker.sendMessage( 'Usage:\n' + _usage ); return; } /* @@ -168,53 +168,53 @@ var aliasCmd = command('alias', function( params, invoker ) { accessing object properties by array index notation in JRE8 alias[operation] returns null - definitely a bug in Nashorn. */ - for (var key in alias){ - if (key == operation){ + for ( var key in alias ) { + if ( key == operation ) { fn = alias[key]; - fn(params.slice(1),invoker); + fn( params.slice(1), invoker ); return; } } - invoker.sendMessage('Usage:\n' + _usage); + invoker.sendMessage( 'Usage:\n' + _usage ); }); -var _intercept = function( msg, invoker, exec) -{ - if (msg.trim().length == 0) +var _intercept = function( msg, invoker, exec ) { + if ( msg.trim().length == 0 ) return false; var msgParts = msg.split(' '), - command = msg.match(/^\/*([^\s]+)/)[1], - template = [], isAlias = false, cmds = [], + command = msg.match( /^\/*([^\s]+)/ )[1], + template = [], + isAlias = false, + cmds = [], commandObj, - filledinCommand; + filledinCommand, + i; - if (_store.global[command]){ + if ( _store.global[command] ) { template = _store.global[command]; isAlias = true; } /* allows player-specific aliases to override global aliases */ - if (_store.players[invoker] && - _store.players[invoker][command]) - { + if ( _store.players[invoker] && _store.players[invoker][command] ) { template = _store.players[invoker][command]; isAlias = true; } - for (var i = 0;i < template.length; i++) - { - filledinCommand = template[i].replace(/{([0-9]+)}/g, function (match,index){ - index = parseInt(index,10); - if (msgParts[index]) + for ( i = 0; i < template.length; i++) { + filledinCommand = template[i].replace( /{([0-9]+)}/g, function( match, index ) { + index = parseInt( index, 10 ); + if ( msgParts[index] ) { return msgParts[index]; - else + } else { return match; + } }); - cmds.push(filledinCommand); + cmds.push( filledinCommand ); } - for (var i = 0; i< cmds.length; i++){ - exec(cmds[i]); + for (i = 0; i< cmds.length; i++ ) { + exec( cmds[i] ); } return isAlias; @@ -223,20 +223,27 @@ var _intercept = function( msg, invoker, exec) Intercept all command processing and replace with aliased commands if the command about to be issued matches an alias. */ -events.on('player.PlayerCommandPreprocessEvent', function(listener,evt){ +events.on( 'player.PlayerCommandPreprocessEvent', function( listener, evt ) { var invoker = evt.player; - var exec = function(cmd){ invoker.performCommand(cmd);}; - var isAlias = _intercept(''+evt.message, ''+invoker.name, exec); - if (isAlias) + var exec = function( cmd ) { + invoker.performCommand(cmd); + }; + var isAlias = _intercept( ''+evt.message, ''+invoker.name, exec); + if ( isAlias ) { evt.cancelled = true; + } }); /* define a 'void' command because ServerCommandEvent can't be canceled */ -command('void',function(){}); +command('void',function( ) { +} ); -events.on('server.ServerCommandEvent', function(listener,evt){ +events.on( 'server.ServerCommandEvent', function( listener, evt ) { var invoker = evt.sender; - var exec = function(cmd){ invoker.server.dispatchCommand(invoker, cmd); }; - var isAlias = _intercept(''+evt.command, ''+ invoker.name, exec); - if (isAlias) + var exec = function( cmd ) { + invoker.server.dispatchCommand( invoker, cmd); + }; + var isAlias = _intercept( ''+evt.command, ''+ invoker.name, exec ); + if ( isAlias ) { evt.command = 'jsp void'; + } }); diff --git a/src/main/js/plugins/arrows.js b/src/main/js/plugins/arrows.js index dcac397..07cc12e 100644 --- a/src/main/js/plugins/arrows.js +++ b/src/main/js/plugins/arrows.js @@ -25,130 +25,98 @@ player23's arrows explosive. ***/ -var signs = require('signs'); -var fireworks = require('fireworks'); -var utils = require('utils'); - -var _store = {players: {}}; - -var arrows = plugin("arrows",{ - /* - turn a sign into a menu of arrow choices - */ - sign: function(sign){}, - /* - change player's arrows to normal - */ - normal: function(player){}, - /* - change player's arrows to explode on impact - */ - explosive: function(player){}, - /* - change player's arrows to teleporting - */ - teleport: function(player){}, - /* - change player's arrows to plant trees where they land - */ - flourish: function(player){}, - /* - change player's arrows to strike lightning where they land - */ - lightning: function(player){}, - - /* - launch a firework where the arrow lands - */ - explosiveYield: 2.5, - - store: _store - -},true); +var signs = require('signs'), + fireworks = require('fireworks'), + utils = require('utils'), + EXPLOSIVE_YIELD = 2.5, + _store = { players: { } }, + arrows = plugin( 'arrows', { store: _store }, true ), + i, + type, + _types = [ 'Normal', 'Explosive', 'Teleport', 'Flourish', 'Lightning', 'Firework' ]; exports.arrows = arrows; -// -// setup functions for the arrow types -// -var _types = {normal: 0, explosive: 1, teleport: 2, flourish: 3, lightning: 4, firework: 5}; -for (var type in _types) -{ - arrows[type] = (function(n){ - return function(player){ - player = utils.player(player); - if (player) - arrows.store.players[player.name] = n; - else - console.warn('arrows.' + n + ' No player ' + player); - }; - })(_types[type]); + +for ( i = 0; i < _types.length; i++ ) { + type = _types[i].toLowerCase(); + // iife (immediately-invoked function expression) + arrows[ type ] = ( function( n ) { + return function( player ) { + player = utils.player( player ); + if ( player ) { + arrows.store.players[ player.name ] = n; + } else { + console.warn('arrows.' + n + ' No player ' + player); + } + }; + } )( i ); } /* - called when the player chooses an arrow option from a menu sign -*/ -var _onMenuChoice = function(event){ - arrows.store.players[event.player.name] = event.number; + called when the player chooses an arrow option from a menu sign + */ +var _onMenuChoice = function( event ) { + arrows.store.players[ event.player.name ] = event.number; }; -var convertToArrowSign = signs.menu( - "Arrow", - ["Normal","Explosive","Teleport","Flourish","Lightning","Firework"], - _onMenuChoice); +var convertToArrowSign = signs.menu( 'Arrow', _types, _onMenuChoice ); -arrows.sign = function(cmdSender) -{ - var sign = signs.getTargetedBy(cmdSender); - if (!sign){ - throw new Error('You must first look at a sign!'); - } - return convertToArrowSign(sign,true); +/* + turn a sign into a menu of arrow choices + */ +arrows.sign = function( cmdSender ) { + var sign = signs.getTargetedBy( cmdSender ); + if ( !sign ) { + throw new Error( 'You must first look at a sign!' ); + } + return convertToArrowSign( sign, true ); }; /* - event handler called when a projectile hits something -*/ -var _onArrowHit = function(listener,event) -{ - var projectile = event.entity; - var world = projectile.world; - var shooter = projectile.shooter; - var fireworkCount = 5; - if (projectile instanceof org.bukkit.entity.Arrow && - shooter instanceof org.bukkit.entity.Player) - { - var arrowType = arrows.store.players[shooter.name]; + event handler called when a projectile hits something + */ +var _onArrowHit = function( listener, event ) { + var projectile = event.entity, + world = projectile.world, + shooter = projectile.shooter, + fireworkCount = 5, + arrowType, + TeleportCause = org.bukkit.event.player.PlayerTeleportEvent.TeleportCause, + launch = function( ) { + fireworks.firework( projectile.location ); + if ( --fireworkCount ) { + setTimeout( launch, 2000 ); + } + }; - switch (arrowType){ - case 1: - projectile.remove(); - world.createExplosion(projectile.location,arrows.explosiveYield); - break; - case 2: - projectile.remove(); - var teleportCause =org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; - shooter.teleport(projectile.location, - teleportCause.PLUGIN); - break; - case 3: - projectile.remove(); - world.generateTree(projectile.location, org.bukkit.TreeType.BIG_TREE); - break; - case 4: - projectile.remove(); - world.strikeLightning(projectile.location); - break; - case 5: - projectile.remove(); - var launch = function(){ - fireworks.firework(projectile.location); - if (--fireworkCount) - setTimeout(launch,2000); - }; - launch(); - break; - } + if (projectile instanceof org.bukkit.entity.Arrow + && shooter instanceof org.bukkit.entity.Player) { + + arrowType = arrows.store.players[ shooter.name ]; + + switch ( arrowType ) { + case 1: + projectile.remove(); + world.createExplosion( projectile.location, EXPLOSIVE_YIELD ); + break; + case 2: + projectile.remove(); + shooter.teleport( projectile.location, TeleportCause.PLUGIN ); + break; + case 3: + projectile.remove(); + world.generateTree( projectile.location, org.bukkit.TreeType.BIG_TREE ); + break; + case 4: + projectile.remove(); + world.strikeLightning( projectile.location ); + break; + case 5: + projectile.remove(); + launch(); + break; } + } }; -events.on('entity.ProjectileHitEvent',_onArrowHit); +events.on( 'entity.ProjectileHitEvent', _onArrowHit ); diff --git a/src/main/js/plugins/chat/color.js b/src/main/js/plugins/chat/color.js index 5d7ea64..a6666d0 100644 --- a/src/main/js/plugins/chat/color.js +++ b/src/main/js/plugins/chat/color.js @@ -1,54 +1,72 @@ /* TODO: Document this module */ -var _store = {players: {}}; +var _store = { players: { } }, + colorCodes = {}, + i, + colors = [ + 'black', + 'blue', + 'darkgreen', + 'darkaqua', + 'darkred', + 'purple', + 'gold', + 'gray', + 'darkgray', + 'indigo', + 'brightgreen', + 'aqua', + 'red', + 'pink', + 'yellow', + 'white' + ], + foreach = require('utils').foreach; + /* declare a new javascript plugin for changing chat text color */ -exports.chat = plugin("chat", { - /* - set the color of text for a given player - */ - setColor: function(player, color){ - _store.players[player.name] = color; - }, +exports.chat = plugin( 'chat', { + /* + set the color of text for a given player + */ + setColor: function( player, color ) { + _store.players[ player.name ] = color; + }, - store: _store + store: _store },true); -var colors = [ - "black", "blue", "darkgreen", "darkaqua", "darkred", - "purple", "gold", "gray", "darkgray", "indigo", - "brightgreen", "aqua", "red", "pink", "yellow", "white" -]; -var colorCodes = {}; -for (var i =0;i < colors.length;i++) { - var hexCode = i.toString(16); - colorCodes[colors[i]] = hexCode; -} +foreach( colors, function ( color, i ) { + colorCodes[color] = i.toString( 16 ); +} ); -events.on("player.AsyncPlayerChatEvent",function(l,e){ - var player = e.player; - var playerChatColor = _store.players[player.name]; - if (playerChatColor){ - e.message = "§" + colorCodes[playerChatColor] + e.message; - } +events.on( 'player.AsyncPlayerChatEvent', function( l, e ) { + var player = e.player; + var playerChatColor = _store.players[ player.name ]; + if ( playerChatColor ) { + e.message = '§' + colorCodes[ playerChatColor ] + e.message; + } }); -var listColors = function(params,sender){ - var colorNamesInColor = []; - for (var i = 0;i < colors.length;i++) - colorNamesInColor[i] = "§"+colorCodes[colors[i]] + colors[i]; - sender.sendMessage("valid chat colors are " + colorNamesInColor.join(", ")); -}; -command("list_colors", listColors); -command("chat_color",function(params,sender){ - var color = params[0]; - if (colorCodes[color]){ - chat.setColor(sender,color); - }else{ - sender.sendMessage(color + " is not a valid color"); - listColors(); - } -},colors); + +var listColors = function( params, sender ) { + var colorNamesInColor = []; + foreach (colors, function( color ) { + colorNamesInColor.push( '§' + colorCodes[color] + color ); + } ); + sender.sendMessage( 'valid chat colors are ' + colorNamesInColor.join( ', ') ); +}; + +command( 'list_colors', listColors ); +command( 'chat_color', function( params, sender ) { + var color = params[0]; + if ( colorCodes[color] ) { + chat.setColor( sender, color ); + } else { + sender.sendMessage( color + ' is not a valid color' ); + listColors(); + } +}, colors ); diff --git a/src/main/js/plugins/classroom/classroom.js b/src/main/js/plugins/classroom/classroom.js index d674f96..a91257a 100644 --- a/src/main/js/plugins/classroom/classroom.js +++ b/src/main/js/plugins/classroom/classroom.js @@ -1,4 +1,4 @@ -var utils = require('utils'); +var foreach = require('utils').foreach; /************************************************************************ ## Classroom Plugin @@ -32,59 +32,61 @@ to every student in a Minecraft classroom environment. To allow all players (and any players who connect to the server) to use the `js` and `jsp` commands... - /js classroom.allowScripting(true,self) + /js classroom.allowScripting( true, self ) To disallow scripting (and prevent players who join the server from using the commands)... - /js classroom.allowScripting(false,self) + /js classroom.allowScripting( false, self ) Only ops users can run the classroom.allowScripting() function - this is so that students don't try to bar themselves and each other from scripting. ***/ -var _store = {enableScripting: false}; -var classroom = plugin("classroom", { - allowScripting: function (/* boolean: true or false */ canScript, sender) { - if (typeof sender == 'undefined'){ - console.log("Attempt to set classroom scripting without credentials"); - console.log("classroom.allowScripting(boolean, sender)"); - return; - } - if (!sender.op){ - console.log("Attempt to set classroom scripting without credentials: " + sender.name); - return; - } - - /* - only operators should be allowed run this function - */ - if (!sender.isOp()) - return; - if (canScript){ - utils.foreach( server.onlinePlayers, function (player) { - player.addAttachment(__plugin, "scriptcraft.*", true); - }); - }else{ - utils.foreach( server.onlinePlayers, function(player) { - utils.foreach(player.getEffectivePermissions(), function(perm) { - if ((""+perm.permission).indexOf("scriptcraft.") == 0){ - if (perm.attachment) - perm.attachment.remove(); - } - }); - }); - } - _store.enableScripting = canScript; - }, - store: _store +var _store = { enableScripting: false }; +var classroom = plugin('classroom', { + allowScripting: function (/* boolean: true or false */ canScript, sender ) { + if ( typeof sender == 'undefined' ) { + console.log( 'Attempt to set classroom scripting without credentials' ); + console.log( 'classroom.allowScripting(boolean, sender)' ); + return; + } + if ( !sender.op ) { + console.log( 'Attempt to set classroom scripting without credentials: ' + sender.name ); + return; + } + + /* + only operators should be allowed run this function + */ + if ( !sender.isOp() ) { + return; + } + if ( canScript ) { + foreach( server.onlinePlayers, function( player ) { + player.addAttachment( __plugin, 'scriptcraft.*', true ); + }); + } else { + foreach( server.onlinePlayers, function( player ) { + foreach( player.getEffectivePermissions(), function( perm ) { + if ( (''+perm.permission).indexOf( 'scriptcraft.' ) == 0 ) { + if ( perm.attachment ) { + perm.attachment.remove(); + } + } + }); + }); + } + _store.enableScripting = canScript; + }, + store: _store }, true); exports.classroom = classroom; -events.on('player.PlayerLoginEvent', function(listener, event) { - var player = event.player; - if (_store.enableScripting){ - player.addAttachment(__plugin, "scriptcraft.*", true); - } +events.on( 'player.PlayerLoginEvent', function( listener, event ) { + var player = event.player; + if ( _store.enableScripting ) { + player.addAttachment( __plugin, 'scriptcraft.*', true ); + } }, 'HIGHEST'); diff --git a/src/main/js/plugins/commando/commando-test.js b/src/main/js/plugins/commando/commando-test.js index 56900b4..5457e11 100644 --- a/src/main/js/plugins/commando/commando-test.js +++ b/src/main/js/plugins/commando/commando-test.js @@ -1,21 +1,22 @@ /* A test of the commando plugin. - Adds a new `/scriptcrafttimeofday` command with 4 possible options: Dawn, Midday, Dusk, Midnight + Adds a new `/js-time` command with 4 possible options: Dawn, Midday, Dusk, Midnight */ -var commando = require('./commando').commando; -var times = { - Dawn: 0, - Midday: 6000, - Dusk: 12000, - Midnight: 18000 -}; -commando('scriptcrafttimeofday',function(params,sender){ - if (config.verbose){ - console.log('scriptcrafttimeofday.params=%s',JSON.stringify(params)); +var commando = require('./commando').commando, + times = ['Dawn','Midday','Dusk','Midnight']; + +commando( 'js-time' , function( params, sender ) { + var time = ''+params[0].toLowerCase(), + i = 0; + if ( sender.location ) { + for ( ; i < 4; i++ ) { + if ( times.toLowerCase() == time ) { + sender.location.world.setTime( i * 6000 ); + break; + } } - if (sender.location) - sender.location.world.setTime(times[params[0]]); - else - sender.sendMessage('This command only works in-world'); + } else { + sender.sendMessage('This command only works in-world'); + } -},['Dawn','Midday','Dusk','Midnight']); +},times); diff --git a/src/main/js/plugins/commando/commando.js b/src/main/js/plugins/commando/commando.js index 6f2694a..55fa910 100644 --- a/src/main/js/plugins/commando/commando.js +++ b/src/main/js/plugins/commando/commando.js @@ -78,37 +78,41 @@ global commands for a plugin, please let me know. ***/ var commands = {}; -exports.commando = function(name, func, options, intercepts){ - var result = command(name, func, options, intercepts); - commands[name] = result; - return result; +exports.commando = function( name, func, options, intercepts ) { + var result = command( name, func, options, intercepts ); + commands[name] = result; + return result; }; -events.on('player.PlayerCommandPreprocessEvent', function(l,e){ - var msg = '' + e.message; - var parts = msg.match(/^\/([^\s]+)/); - if (!parts) - return; - if (parts.length < 2) - return; - var command = parts[1]; - if (commands[command]){ - e.message = "/jsp " + msg.replace(/^\//,""); - } -}); -events.on('server.ServerCommandEvent', function(l,e){ - var msg = '' + e.command; - var parts = msg.match(/^\/*([^\s]+)/); - if (!parts) - return; - if (parts.length < 2) - return; - var command = parts[1]; - if (commands[command]){ - var newCmd = "jsp " + msg.replace(/^\//,""); - if (config.verbose){ - console.log('Redirecting to : %s',newCmd); - } - e.command = newCmd; +events.on( 'player.PlayerCommandPreprocessEvent', function( l, e ) { + var msg = '' + e.message; + var parts = msg.match( /^\/([^\s]+)/ ); + if ( !parts ) { + return; + } + if ( parts.length < 2 ) { + return; + } + var command = parts[1]; + if ( commands[command] ) { + e.message = '/jsp ' + msg.replace( /^\//, '' ); + } +} ); +events.on( 'server.ServerCommandEvent', function( l, e ) { + var msg = '' + e.command; + var parts = msg.match( /^\/*([^\s]+)/ ); + if ( !parts ) { + return; + } + if ( parts.length < 2 ) { + return; + } + var command = parts[1]; + if ( commands[ command ] ) { + var newCmd = 'jsp ' + msg.replace( /^\//, '' ); + if ( config.verbose ) { + console.log( 'Redirecting to : %s', newCmd ); } + e.command = newCmd; + } }); diff --git a/src/main/js/plugins/drone/blocktype.js b/src/main/js/plugins/drone/blocktype.js index 2265763..0067d55 100644 --- a/src/main/js/plugins/drone/blocktype.js +++ b/src/main/js/plugins/drone/blocktype.js @@ -307,16 +307,24 @@ var bitmaps = { /* wph 20130121 compute the width, and x,y coords of pixels ahead of time */ -for (var c in bitmaps.raw){ - var bits = bitmaps.raw[c]; - var width = bits.length/5; - var bmInfo = {"width": width,"pixels":[]} - bitmaps.computed[c] = bmInfo; - for (var j = 0; j < bits.length; j++){ - if (bits.charAt(j) != ' '){ - bmInfo.pixels.push([j%width,Math.ceil(j/width)]); - } +var c, + bits, + width, + bmInfo, + j; +for ( c in bitmaps.raw ) { + bits = bitmaps.raw[c]; + width = bits.length/5; + bmInfo = { width: width, pixels:[] }; + bitmaps.computed[c] = bmInfo; + for ( j = 0; j < bits.length; j++ ) { + if ( bits.charAt(j) != ' ' ) { + bmInfo.pixels.push( [ + j % width, + Math.ceil( j / width ) + ] ); } + } } @@ -328,46 +336,72 @@ for (var c in bitmaps.raw){ // bg // background material, optional. The negative space within the bounding box of the text. // -Drone.extend('blocktype', function(message,fg,bg){ +Drone.extend('blocktype', function( message, fg, bg ) { - this.chkpt('blocktext'); + var bmfg, + bmbg, + lines, + lineCount, + h, + line, + i, + x, + y, + ch, + bits, + charWidth, + j; - if (typeof fg == "undefined") - fg = blocks.wool.black; + this.chkpt('blocktext'); - var bmfg = this._getBlockIdAndMeta(fg); - var bmbg = null; - if (typeof bg != "undefined") - bmbg = this._getBlockIdAndMeta(bg); - var lines = message.split("\n"); - var lineCount = lines.length; - for (var h = 0;h < lineCount; h++) { - var line = lines[h]; - line = line.toLowerCase().replace(/[^0-9a-z \.\-\+\/\;\'\:\!]/g,""); - this.up(7*(lineCount-(h+1))); - - for (var i =0;i < line.length; i++) { - var ch = line.charAt(i) - var bits = bitmaps.computed[ch]; - if (typeof bits == "undefined"){ - bits = bitmaps.computed[' ']; - } - var charWidth = bits.width; - if (typeof bg != "undefined") - this.cuboidX(bmbg[0],bmbg[1],charWidth,7,1); - for (var j = 0;j < bits.pixels.length;j++){ - this.chkpt('btbl'); - var x = bits.pixels[j][0]; - var y = bits.pixels[j][1]; - this.up(6-y).right(x).cuboidX(bmfg[0],bmfg[1]); - this.move('btbl'); - } - this.right(charWidth-1); - } - this.move('blocktext'); - } + if ( typeof fg == 'undefined' ) { + fg = blocks.wool.black; + } + + bmfg = this._getBlockIdAndMeta( fg ); + bmbg = null; + if ( typeof bg != 'undefined' ) { + bmbg = this._getBlockIdAndMeta( bg ); + } + lines = message.split( '\n' ); + lineCount = lines.length; + + for ( h = 0; h < lineCount; h++) { + + line = lines[h]; + line = line.toLowerCase().replace( /[^0-9a-z \.\-\+\/\;\'\:\!]/g, '' ); + this.up( 7 * ( lineCount - ( h + 1 ) ) ); - return this.move('blocktext'); + for ( i =0; i < line.length; i++) { + + ch = line.charAt( i ); + bits = bitmaps.computed[ ch ]; + + if ( typeof bits == 'undefined' ) { + bits = bitmaps.computed[' ']; + } + charWidth = bits.width; + + if ( typeof bg != 'undefined' ) { + this.cuboidX( bmbg[0], bmbg[1], charWidth, 7, 1 ); + } + + for ( j = 0; j < bits.pixels.length; j++ ) { + + this.chkpt( 'btbl' ); + x = bits.pixels[ j ][ 0 ]; + y = bits.pixels[ j ][ 1] ; + this.up( 6 - y ).right( x ).cuboidX( bmfg[ 0 ], bmfg[ 1 ] ); + this.move( 'btbl' ); + + } + this.right( charWidth - 1 ); + + } + this.move( 'blocktext' ); + } + + return this.move( 'blocktext' ); }); diff --git a/src/main/js/plugins/drone/contrib/castle.js b/src/main/js/plugins/drone/contrib/castle.js index f7e4d15..c8ac9fc 100644 --- a/src/main/js/plugins/drone/contrib/castle.js +++ b/src/main/js/plugins/drone/contrib/castle.js @@ -3,49 +3,47 @@ var Drone = require('../drone').Drone; // // a castle is just a big wide fort with 4 taller forts at each corner // -Drone.extend('castle', function(side, height) -{ - // - // use sensible default parameter values - // if no parameters are supplied - // - if (typeof side == "undefined") - side = 24; - if (typeof height == "undefined") - height = 10; - if (height < 8 || side < 20) - throw new java.lang.RuntimeException("Castles must be at least 20 wide X 8 tall"); - // - // remember where the drone is so it can return 'home' - // - this.chkpt('castle'); - // - // how big the towers at each corner will be... - // - var towerSide = 10; - var towerHeight = height+4; +Drone.extend('castle', function( side, height ) { + // + // use sensible default parameter values + // if no parameters are supplied + // + if ( typeof side == "undefined" ) + side = 24; + if ( typeof height == "undefined" ) + height = 10; + if ( height < 8 || side < 20 ) + throw new java.lang.RuntimeException("Castles must be at least 20 wide X 8 tall"); + // + // remember where the drone is so it can return 'home' + // + this.chkpt('castle'); + // + // how big the towers at each corner will be... + // + var towerSide = 10; + var towerHeight = height+4; - // - // the main castle building will be front and right of the first tower - // - this.fwd(towerSide/2).right(towerSide/2); - // - // the castle is really just a big fort with 4 smaller 'tower' forts at each corner - // - this.fort(side,height); - // - // move back to start position - // - this.move('castle'); - // - // now place 4 towers at each corner (each tower is another fort) - // - for (var corner = 0; corner < 4; corner++) - { - // construct a 'tower' fort - this.fort(towerSide,towerHeight); - // move forward the length of the castle then turn right - this.fwd(side+towerSide-1).turn(); - } - return this.move('castle'); + // + // the main castle building will be front and right of the first tower + // + this.fwd(towerSide/2).right(towerSide/2); + // + // the castle is really just a big fort with 4 smaller 'tower' forts at each corner + // + this.fort(side,height); + // + // move back to start position + // + this.move('castle'); + // + // now place 4 towers at each corner (each tower is another fort) + // + for ( var corner = 0; corner < 4; corner++ ) { + // construct a 'tower' fort + this.fort(towerSide,towerHeight); + // move forward the length of the castle then turn right + this.fwd(side+towerSide-1).turn(); + } + return this.move('castle'); }); diff --git a/src/main/js/plugins/drone/contrib/chessboard.js b/src/main/js/plugins/drone/contrib/chessboard.js index f0c046b..6f773e4 100644 --- a/src/main/js/plugins/drone/contrib/chessboard.js +++ b/src/main/js/plugins/drone/contrib/chessboard.js @@ -10,27 +10,29 @@ var blocks = require('blocks'); * width - width of the chessboard * height - height of the chessboard */ -Drone.extend("chessboard", function(whiteBlock, blackBlock, width, depth) { - this.chkpt('chessboard-start'); - if (typeof whiteBlock == "undefined") - whiteBlock = blocks.wool.white; - if (typeof blackBlock == "undefined") - blackBlock = blocks.wool.black; - if (typeof width == "undefined") - width = 8; - if (typeof depth == "undefined") - depth = width; +Drone.extend('chessboard', function( whiteBlock, blackBlock, width, depth ) { + var i, + j, + block; - for(var i = 0; i < width; ++i) { - for(var j = 0; j < depth; ++j) { - var block = blackBlock; - if((i+j)%2 == 1) { - block = whiteBlock; - } - this.box(block); - this.right(); - } - this.move('chessboard-start').fwd(i+1); - } - return this.move('chessboard-start'); + this.chkpt('chessboard-start'); + + if ( typeof whiteBlock == 'undefined' ) { + whiteBlock = blocks.wool.white; + } + if ( typeof blackBlock == 'undefined' ) { + blackBlock = blocks.wool.black; + } + if ( typeof width == 'undefined' ) { + width = 8; + } + if ( typeof depth == 'undefined' ) { + depth = width; + } + var wb = [ blackBlock, whiteBlock ]; + for ( i = 0; i < depth; i++ ) { + this.boxa( wb, width, 1, 1).fwd(); + wb = wb.reverse(); + } + return this.move('chessboard-start'); }); diff --git a/src/main/js/plugins/drone/contrib/cottage.js b/src/main/js/plugins/drone/contrib/cottage.js index 8aeb256..8301d2c 100644 --- a/src/main/js/plugins/drone/contrib/cottage.js +++ b/src/main/js/plugins/drone/contrib/cottage.js @@ -11,69 +11,67 @@ var Drone = require('../drone').Drone; // /js drone.cottage(); // -Drone.extend('cottage',function () -{ - this.chkpt('cottage') - .box0(48,7,2,6) // 4 walls - .right(3).door() // door front and center - .up(1).left(2).box(102) // windows to left and right - .right(4).box(102) - .left(5).up().prism0(53,7,6); +Drone.extend('cottage',function ( ) { + this.chkpt('cottage') + .box0(48,7,2,6) // 4 walls + .right(3).door() // door front and center + .up(1).left(2).box(102) // windows to left and right + .right(4).box(102) + .left(5).up().prism0(53,7,6); // // put up a sign near door. // - this.down().right(4).sign(["Home","Sweet","Home"],68); + this.down().right(4) + .sign(['Home','Sweet','Home'],68); - return this.move('cottage'); + return this.move('cottage'); }); // // a more complex script that builds an tree-lined avenue with // cottages on both sides. // -Drone.extend('cottage_road', function(numberCottages) -{ - if (typeof numberCottages == "undefined"){ - numberCottages = 6; - } - var i=0, distanceBetweenTrees = 11; - // - // step 1 build the road. - // - var cottagesPerSide = Math.floor(numberCottages/2); +Drone.extend('cottage_road', function( numberCottages ) { + if (typeof numberCottages == 'undefined'){ + numberCottages = 6; + } + var i=0, distanceBetweenTrees = 11; + // + // step 1 build the road. + // + var cottagesPerSide = Math.floor(numberCottages/2); + this + .chkpt('cottage_road') // make sure the drone's state is saved. + .box(43,3,1,cottagesPerSide*(distanceBetweenTrees+1)) // build the road + .up().right() // now centered in middle of road + .chkpt('cr'); // will be returning to this position later + + // + // step 2 line the road with trees + // + for ( ; i < cottagesPerSide+1;i++ ) { this - .chkpt("cottage_road") // make sure the drone's state is saved. - .box(43,3,1,cottagesPerSide*(distanceBetweenTrees+1)) // build the road - .up().right() // now centered in middle of road - .chkpt("cr"); // will be returning to this position later + .left(5).oak() + .right(10).oak() + .left(5) // return to middle of road + .fwd(distanceBetweenTrees+1); // move forward. + } + this.move('cr').back(6); // move back 1/2 the distance between trees - // - // step 2 line the road with trees - // - for (; i < cottagesPerSide+1;i++){ - this - .left(5).oak() - .right(10).oak() - .left(5) // return to middle of road - .fwd(distanceBetweenTrees+1); // move forward. - } - this.move("cr").back(6); // move back 1/2 the distance between trees - - // this function builds a path leading to a cottage. - function pathAndCottage(d){ - return d.down().box(43,1,1,5).fwd(5).left(3).up().cottage(); - }; - // - // step 3 build cottages on each side - // - for (i = 0;i < cottagesPerSide; i++) - { - this.fwd(distanceBetweenTrees+1).chkpt("r"+i); - // build cottage on left - pathAndCottage(this.turn(3)).move("r"+i); - // build cottage on right - pathAndCottage(this.turn()).move("r"+i); - } - // return drone to where it was at start of function - return this.move("cottage_road"); + // this function builds a path leading to a cottage. + function pathAndCottage( d ) { + return d.down().box(43,1,1,5).fwd(5).left(3).up().cottage(); + }; + // + // step 3 build cottages on each side + // + for ( i = 0; i < cottagesPerSide; i++ ) { + this.fwd(distanceBetweenTrees+1).chkpt('r'+i); + // build cottage on left + pathAndCottage( this.turn(3) ).move( 'r' + i ); + // build cottage on right + pathAndCottage(this.turn()).move( 'r' + i ); + } + // return drone to where it was at start of function + return this.move('cottage_road'); }); diff --git a/src/main/js/plugins/drone/contrib/fort.js b/src/main/js/plugins/drone/contrib/fort.js index 90686c7..d0921ad 100644 --- a/src/main/js/plugins/drone/contrib/fort.js +++ b/src/main/js/plugins/drone/contrib/fort.js @@ -3,66 +3,69 @@ var Drone = require('../drone').Drone; // // constructs a medieval fort // -Drone.extend('fort', function(side, height) -{ - if (typeof side == "undefined") - side = 18; - if (typeof height == "undefined") - height = 6; - // make sure side is even - if (side%2) - side++; - if (height < 4 || side < 10) - throw new java.lang.RuntimeException("Forts must be at least 10 wide X 4 tall"); - var brick = 98; - // - // build walls. - // - this.chkpt('fort').box0(brick,side,height-1,side); - // - // build battlements - // - this.up(height-1); - for (i = 0;i <= 3;i++){ - var turret = []; - this.box(brick) // solid brick corners - .up().box('50:5').down() // light a torch on each corner - .fwd(); - turret.push('109:'+ Drone.PLAYER_STAIRS_FACING[this.dir]); - turret.push('109:'+ Drone.PLAYER_STAIRS_FACING[(this.dir+2)%4]); - try{ - this.boxa(turret,1,1,side-2).fwd(side-2).turn(); - }catch(e){ - console.log("ERROR: " + e.toString()); - } +Drone.extend('fort', function( side, height ) { + if ( typeof side == 'undefined' ) { + side = 18; + } + if ( typeof height == 'undefined' ) { + height = 6; + } + // make sure side is even + if ( side % 2 ) { + side++; + } + if ( height < 4 || side < 10 ) { + throw new java.lang.RuntimeException('Forts must be at least 10 wide X 4 tall'); + } + var brick = 98; + // + // build walls. + // + this.chkpt('fort').box0(brick,side,height-1,side); + // + // build battlements + // + this.up(height-1); + for ( i = 0; i <= 3; i++ ) { + var turret = []; + this.box(brick) // solid brick corners + .up().box('50:5').down() // light a torch on each corner + .fwd(); + turret.push('109:'+ Drone.PLAYER_STAIRS_FACING[this.dir]); + turret.push('109:'+ Drone.PLAYER_STAIRS_FACING[(this.dir+2)%4]); + try { + this.boxa(turret,1,1,side-2).fwd(side-2).turn(); + } catch( e ) { + console.log('ERROR: ' + e.toString()); } - // - // build battlement's floor - // - this.move('fort'); - this.up(height-2).fwd().right().box('126:0',side-2,1,side-2); - var battlementWidth = 3; - if (side <= 12) - battlementWidth = 2; - - this.fwd(battlementWidth).right(battlementWidth) - .box(0,side-((1+battlementWidth)*2),1,side-((1+battlementWidth)*2)); - // - // add door - // - var torch = '50:' + Drone.PLAYER_TORCH_FACING[this.dir]; - this.move('fort').right((side/2)-1).door2() // double doors - .back().left().up() - .box(torch) // left torch - .right(3) - .box(torch); // right torch - // - // add ladder up to battlements - // - var ladder = '65:' + Drone.PLAYER_SIGN_FACING[(this.dir+2)%4]; - this.move('fort').right((side/2)-3).fwd(1) // move inside fort - .box(ladder, 1,height-1,1); - return this.move('fort'); - + } + // + // build battlement's floor + // + this.move('fort'); + this.up(height-2).fwd().right().box('126:0',side-2,1,side-2); + var battlementWidth = 3; + if ( side <= 12 ) { + battlementWidth = 2; + } + this.fwd(battlementWidth).right(battlementWidth) + .box(0,side-((1+battlementWidth)*2),1,side-((1+battlementWidth)*2)); + // + // add door + // + var torch = '50:' + Drone.PLAYER_TORCH_FACING[this.dir]; + this.move('fort').right((side/2)-1).door2() // double doors + .back().left().up() + .box(torch) // left torch + .right(3) + .box(torch); // right torch + // + // add ladder up to battlements + // + var ladder = '65:' + Drone.PLAYER_SIGN_FACING[(this.dir+2)%4]; + this.move('fort').right((side/2)-3).fwd(1) // move inside fort + .box(ladder, 1,height-1,1); + return this.move('fort'); + }); diff --git a/src/main/js/plugins/drone/contrib/lcd-clock.js b/src/main/js/plugins/drone/contrib/lcd-clock.js index 48cf7f2..b6fc492 100644 --- a/src/main/js/plugins/drone/contrib/lcd-clock.js +++ b/src/main/js/plugins/drone/contrib/lcd-clock.js @@ -17,21 +17,21 @@ exports.LCDClock = function(drone, fgColor,bgColor,border) { world = drone.world, intervalId = -1; - if (typeof bgColor == 'undefined') + if ( typeof bgColor == 'undefined' ) { bgColor = '35:15'; // black wool - - if (typeof fgColor == 'undefined') + } + if ( typeof fgColor == 'undefined' ) { fgColor = 35 ; // white wool - - if (border){ + } + if ( border ) { drone.box(border,21,9,1); drone.up().right(); } drone.blocktype('00:00',fgColor,bgColor); return { - start24: function(){ + start24: function( ) { var clock = this; - function tick(){ + function tick() { var rolloverMins = 24*60; var timeOfDayInMins = Math.floor(((world.time + 6000) % 24000) / 16.6667); timeOfDayInMins = timeOfDayInMins % rolloverMins; @@ -40,10 +40,10 @@ exports.LCDClock = function(drone, fgColor,bgColor,border) { }; intervalId = setInterval(tick, 800); }, - stop24: function(){ - clearInterval(intervalId); + stop24: function() { + clearInterval( intervalId ); }, - update: function(secs){ + update: function(secs) { var digits = [0,0,0,0], s = secs % 60; m = (secs - s) / 60; diff --git a/src/main/js/plugins/drone/contrib/rainbow.js b/src/main/js/plugins/drone/contrib/rainbow.js index 31d33b5..2575a62 100644 --- a/src/main/js/plugins/drone/contrib/rainbow.js +++ b/src/main/js/plugins/drone/contrib/rainbow.js @@ -19,24 +19,29 @@ Creates a Rainbow. ***/ Drone.extend('rainbow', function(radius){ - if (typeof radius == "undefined") - radius = 18; - - this.chkpt('rainbow'); - this.down(radius); - // copy blocks.rainbow and add air at end (to compensate for strokewidth) - var colors = blocks.rainbow.slice(0); - colors.push(blocks.air); - for (var i = 0;i < colors.length; i++) { - var bm = this._getBlockIdAndMeta(colors[i]); - this.arc({ - blockType: bm[0], - meta: bm[1], - radius: radius-i, - strokeWidth: 2, - quadrants: {topright: true, - topleft: true}, - orientation: 'vertical'}).right().up(); - } - return this.move('rainbow'); + var i, + colors, + bm; + + if ( typeof radius == "undefined" ) { + radius = 18; + } + + this.chkpt('rainbow'); + this.down(radius); + // copy blocks.rainbow and add air at end (to compensate for strokewidth) + colors = blocks.rainbow.slice(0); + colors.push(blocks.air); + for ( i = 0; i < colors.length; i++ ) { + bm = this._getBlockIdAndMeta( colors[i] ); + this.arc({ + blockType: bm[0], + meta: bm[1], + radius: radius-i, + strokeWidth: 2, + quadrants: {topright: true, + topleft: true}, + orientation: 'vertical'}).right().up(); + } + return this.move('rainbow'); }); diff --git a/src/main/js/plugins/drone/contrib/rboxcall.js b/src/main/js/plugins/drone/contrib/rboxcall.js index bc709bb..222f101 100644 --- a/src/main/js/plugins/drone/contrib/rboxcall.js +++ b/src/main/js/plugins/drone/contrib/rboxcall.js @@ -12,23 +12,23 @@ var Drone = require('../drone').Drone; * depth - (Optional) depth of the cube, defaults to width */ -Drone.extend("rboxcall", function(callback, probability, width, height, depth) { - this.chkpt('rboxcall-start'); +Drone.extend("rboxcall", function( callback, probability, width, height, depth ) { + this.chkpt('rboxcall-start'); - for(var i = 0; i < width; ++i) { - this.move('rboxcall-start').right(i); - for(var j = 0; j < depth; ++j) { - this.move('rboxcall-start').right(i).fwd(j); - for(var k = 0; k < height; ++k) { - if(Math.random()*100 < probability) { - callback.call(null, new Drone(this.x, this.y, this.z)); - } - this.up(); - } + for(var i = 0; i < width; ++i) { + this.move('rboxcall-start').right(i); + for(var j = 0; j < depth; ++j) { + this.move('rboxcall-start').right(i).fwd(j); + for(var k = 0; k < height; ++k) { + if(Math.random()*100 < probability) { + callback.call(null, new Drone(this.x, this.y, this.z)); + } + this.up(); } - } + } + } - this.move('rboxcall-start'); + this.move('rboxcall-start'); - return this; + return this; }); diff --git a/src/main/js/plugins/drone/contrib/skyscraper-example.js b/src/main/js/plugins/drone/contrib/skyscraper-example.js index 2a4adb0..465aad5 100644 --- a/src/main/js/plugins/drone/contrib/skyscraper-example.js +++ b/src/main/js/plugins/drone/contrib/skyscraper-example.js @@ -1,18 +1,18 @@ var Drone = require('../drone').Drone; var blocks = require('blocks'); -Drone.extend('skyscraper',function(floors){ - - if (typeof floors == "undefined") - floors = 10; - this.chkpt('skyscraper'); - for (var i = 0;i < floors; i++) - { - this - .box(blocks.iron,20,1,20) - .up() - .box0(blocks.glass_pane,20,3,20) - .up(3); - } - return this.move('skyscraper'); +Drone.extend('skyscraper', function( floors ) { + var i = 0; + if ( typeof floors == 'undefined' ) { + floors = 10; + } + this.chkpt('skyscraper'); + for ( i = 0; i < floors; i++ ) { + this // w h d + .box( blocks.iron, 20, 1, 20) // iron floor + .up() // w h d + .box0(blocks.glass_pane, 20, 3, 20) // glass walls + .up(3); + } + return this.move('skyscraper'); }); diff --git a/src/main/js/plugins/drone/contrib/streamer.js b/src/main/js/plugins/drone/contrib/streamer.js index f0b00d9..28c8d75 100644 --- a/src/main/js/plugins/drone/contrib/streamer.js +++ b/src/main/js/plugins/drone/contrib/streamer.js @@ -7,25 +7,25 @@ var Drone = require('../drone').Drone; * dir - "up", "down", "left", "right", "fwd", "back * maxIterations - (Optional) maximum number of cubes to generate, defaults to 1000 */ -Drone.extend("streamer", function(block, dir, maxIterations) { - if (typeof maxIterations == "undefined") - maxIterations = 1000; - - var usage = "Usage: streamer({block-type}, {direction: 'up', 'down', 'fwd', 'back', 'left', 'right'}, {maximum-iterations: default 1000})\nE.g.\n" + +Drone.extend('streamer', function(block, dir, maxIterations) { + if (typeof maxIterations == 'undefined') + maxIterations = 1000; + + var usage = "Usage: streamer({block-type}, {direction: 'up', 'down', 'fwd', 'back', 'left', 'right'}, {maximum-iterations: default 1000})\nE.g.\n" + "streamer(5, 'up', 200)"; - if (typeof dir == "undefined"){ - throw new Error(usage); + if (typeof dir == 'undefined'){ + throw new Error(usage); + } + if (typeof block == 'undefined') { + throw new Error(usage); + } + for ( var i = 0; i < maxIterations || 1000; ++i ) { + this.box(block); + this[dir].call(this); + var block = this.world.getBlockAt(this.x, this.y, this.z); + if ( block.typeId != 0 && block.data != 0) { + break; } - if (typeof block == "undefined") { - throw new Error(usage); - } - for ( var i = 0; i < maxIterations||1000; ++i ) { - this.box(block); - this[dir].call(this); - var block = this.world.getBlockAt(this.x, this.y, this.z); - if ( block.typeId != 0 && block.data != 0) { - break - } - } - return this; + } + return this; }); diff --git a/src/main/js/plugins/drone/contrib/temple.js b/src/main/js/plugins/drone/contrib/temple.js index 8b9b98c..82d52c7 100644 --- a/src/main/js/plugins/drone/contrib/temple.js +++ b/src/main/js/plugins/drone/contrib/temple.js @@ -3,19 +3,19 @@ var Drone = require('../drone').Drone; // constructs a mayan temple // Drone.extend('temple', function(side) { - if (!side) { + if ( !side ) { side = 20; } var stone = '98:1'; - var stair = '109:' + Drone.PLAYER_STAIRS_FACING[this.dir]; + var stair = '109:' + Drone.PLAYER_STAIRS_FACING[ this.dir ]; this.chkpt('temple'); - while (side > 4) { - var middle = Math.round((side-2)/2); + while ( side > 4 ) { + var middle = Math.round( (side-2) / 2 ); this.chkpt('corner') - .box(stone, side, 1, side) - .right(middle).box(stair).right().box(stair) + .box( stone, side, 1, side ) + .right( middle ).box( stair ).right().box( stair ) .move('corner').up().fwd().right(); side = side - 2; } diff --git a/src/main/js/plugins/drone/drone-firework.js b/src/main/js/plugins/drone/drone-firework.js index ac07f37..8ffddbc 100644 --- a/src/main/js/plugins/drone/drone-firework.js +++ b/src/main/js/plugins/drone/drone-firework.js @@ -1,6 +1,6 @@ var fireworks = require('fireworks'); var Drone = require('./drone').Drone; -Drone.extend('firework',function() { - fireworks.firework(this.getLocation()); +Drone.extend( 'firework', function( ) { + fireworks.firework( this.getLocation() ); }); diff --git a/src/main/js/plugins/drone/drone.js b/src/main/js/plugins/drone/drone.js index 0b5913b..fe2accd 100644 --- a/src/main/js/plugins/drone/drone.js +++ b/src/main/js/plugins/drone/drone.js @@ -1,5 +1,9 @@ -var utils = require('utils'); -var blocks = require('blocks'); +var utils = require('utils'), + blocks = require('blocks'), + Location = org.bukkit.Location, + Player = org.bukkit.entity.Player, + Sign = org.bukkit.block.Sign, + TreeType = org.bukkit.TreeType; /********************************************************************* ## Drone Plugin @@ -114,7 +118,7 @@ Drones can be created in any of the following ways... block is broken at the block's location you would do so like this... - events.on('block.BlockBreakEvent',function(listener,event){ + events.on('block.BlockBreakEvent',function( listener,event) { var location = event.block.location; var drone = new Drone(location); // do more stuff with the drone here... @@ -256,7 +260,7 @@ Markers are created and returned to using the followng two methods... // // the drone can now go off on a long excursion // - for (i = 0; i< 100; i++){ + for ( i = 0; i< 100; i++) { drone.fwd(12).box(6); } // @@ -328,11 +332,11 @@ arc() takes a single parameter - an object with the following named properties.. * radius - The radius of the arc. * blockType - The type of block to use - this is the block Id only (no meta). See [Data Values][dv]. * meta - The metadata value. See [Data Values][dv]. - * orientation (default: 'horizontal') - the orientation of the arc - can be 'vertical' or 'horizontal'. - * stack (default: 1) - the height or length of the arc (depending on + * orientation (default: 'horizontal' ) - the orientation of the arc - can be 'vertical' or 'horizontal'. + * stack (default: 1 ) - the height or length of the arc (depending on the orientation - if orientation is horizontal then this parameter - refers to the height, if vertical then it refers to the length). - * strokeWidth (default: 1) - the width of the stroke (how many + refers to the height, if vertical then it refers to the length ). + * strokeWidth (default: 1 ) - the width of the stroke (how many blocks) - if drawing nested arcs it's usually a good idea to set strokeWidth to at least 2 so that there are no gaps between each arc. The arc method uses a [bresenham algorithm][bres] to plot @@ -356,7 +360,7 @@ To draw a 1/4 circle (top right quadrant only) with a radius of 10 and stroke wi orientation: 'vertical', stack: 1, fill: false - }); + } ); ![arc example 1](img/arcex1.png) @@ -419,7 +423,7 @@ To create a free-standing sign... ... to create a wall mounted sign... - drone.sign(["Welcome","to","Scriptopia"], 68); + drone.sign(["Welcome","to","Scriptopia"], 68 ); ![wall sign](img/signex2.png) @@ -434,13 +438,13 @@ To create a free-standing sign... To create 4 trees in a row, point the cross-hairs at the ground then type `/js ` and ... - up().oak().right(8).spruce().right(8).birch().right(8).jungle(); + up( ).oak( ).right(8 ).spruce( ).right(8 ).birch( ).right(8 ).jungle( ); Trees won't always generate unless the conditions are right. You should use the tree methods when the drone is directly above the ground. Trees will usually grow if the drone's current location is occupied by Air and is directly above an area of grass (That is why -the `up()` method is called first). +the `up( )` method is called first). ![tree example](img/treeex1.png) @@ -499,7 +503,7 @@ pasting the copied area elsewhere... #### Example - drone.copy('somethingCool',10,5,10).right(12).paste('somethingCool'); + drone.copy('somethingCool',10,5,10 ).right(12 ).paste('somethingCool' ); ### Drone.paste() method @@ -511,9 +515,9 @@ To copy a 10x5x10 area (using the drone's coordinates as the starting point) into memory. the copied area can be referenced using the name 'somethingCool'. The drone moves 12 blocks right then pastes the copy. - drone.copy('somethingCool',10,5,10) - .right(12) - .paste('somethingCool'); + drone.copy('somethingCool',10,5,10 ) + .right(12 ) + .paste('somethingCool' ); ### Chaining @@ -570,9 +574,9 @@ Use this method to add new methods (which also become chainable global functions #### Example // submitted by [edonaldson][edonaldson] - Drone.extend('pyramid', function(block,height){ + Drone.extend('pyramid', function( block,height) { this.chkpt('pyramid'); - for (var i = height; i > 0; i -= 2) { + for ( var i = height; i > 0; i -= 2) { this.box(block, i, 1, i).up().right().fwd(); } return this.move('pyramid'); @@ -630,121 +634,128 @@ Used when placing torches so that they face towards the drone. // There is no need to read any further unless you want to understand how the Drone object works. // -var putBlock = function(x,y,z,blockId,metadata,world){ - if (typeof metadata == "undefined") - metadata = 0; - var block = world.getBlockAt(x,y,z); - if (block.typeId != blockId || block.data != metadata) - block.setTypeIdAndData(blockId,metadata,false); +var putBlock = function( x, y, z, blockId, metadata, world ) { + if ( typeof metadata == 'undefined' ) { + metadata = 0; + } + var block = world.getBlockAt( x, y, z ); + if ( block.typeId != blockId || block.data != metadata ) { + block.setTypeIdAndData( blockId, metadata, false ); + } }; -var putSign = function(texts, x, y, z, blockId, meta, world){ - if (blockId != 63 && blockId != 68) - throw new Error("Invalid Parameter: blockId must be 63 or 68"); - putBlock(x,y,z,blockId,meta,world); - var block = world.getBlockAt(x,y,z); - var state = block.state; - if (state instanceof org.bukkit.block.Sign){ - for (var i = 0;i < texts.length; i++) - state.setLine(i%4,texts[i]); - state.update(true); +var putSign = function( texts, x, y, z, blockId, meta, world ) { + var i, + block, + state; + + if ( blockId != 63 && blockId != 68 ) { + throw new Error( 'Invalid Parameter: blockId must be 63 or 68' ); + } + putBlock( x, y, z, blockId, meta, world ); + block = world.getBlockAt( x, y, z ); + state = block.state; + if ( state instanceof Sign ) { + for ( i = 0; i < texts.length; i++ ) { + state.setLine( i % 4, texts[ i ] ); } + state.update( true ); + } }; -Drone = function(x,y,z,dir,world) -{ - this.record = false; - var usePlayerCoords = false; - var player = self; - if (x instanceof org.bukkit.entity.Player){ - player = x; +var Drone = function( x, y, z, dir, world ) { + this.record = false; + var usePlayerCoords = false; + var player = self; + if ( x instanceof Player ) { + player = x; + } + var playerPos = utils.getPlayerPos( player ); + var that = this; + var populateFromLocation = function( loc ) { + that.x = loc.x; + that.y = loc.y; + that.z = loc.z; + that.dir = _getDirFromRotation(loc.yaw); + that.world = loc.world; + }; + var mp = utils.getMousePos( player ); + if ( typeof x == 'undefined' || x instanceof Player ) { + if ( mp ) { + populateFromLocation( mp ); + if ( playerPos ) { + this.dir = _getDirFromRotation(playerPos.yaw); + } + } else { + // base it on the player's current location + usePlayerCoords = true; + // + // it's possible that drone.js could be loaded by a non-playing op + // (from the server console) + // + if ( !playerPos ) { + return null; + } + populateFromLocation( playerPos ); } - var playerPos = utils.getPlayerPos(player); - var that = this; - var populateFromLocation = function(loc){ - that.x = loc.x; - that.y = loc.y; - that.z = loc.z; - that.dir = _getDirFromRotation(loc.yaw); - that.world = loc.world; - }; - var mp = utils.getMousePos(player); - if (typeof x == "undefined" || x instanceof org.bukkit.entity.Player) - { - if (mp){ - populateFromLocation(mp); - if (playerPos) - this.dir = _getDirFromRotation(playerPos.yaw); - }else{ - // base it on the player's current location - usePlayerCoords = true; - // - // it's possible that drone.js could be loaded by a non-playing op - // (from the server console) - // - if (!playerPos){ - return null; - } - populateFromLocation(playerPos); - } - }else{ - if (arguments[0] instanceof org.bukkit.Location){ - populateFromLocation(arguments[0]); - }else{ - this.x = x; - this.y = y; - this.z = z; - if (typeof dir == "undefined"){ - this.dir = _getDirFromRotation(playerPos.yaw); - }else{ - this.dir = dir%4; - } - if (typeof world == "undefined"){ - this.world = playerPos.world; - }else{ - this.world = world; - } - } + } else { + if ( arguments[0] instanceof Location ) { + populateFromLocation( arguments[ 0 ] ); + } else { + this.x = x; + this.y = y; + this.z = z; + if ( typeof dir == 'undefined' ) { + this.dir = _getDirFromRotation( playerPos.yaw ); + } else { + this.dir = dir%4; + } + if ( typeof world == 'undefined' ) { + this.world = playerPos.world; + } else { + this.world = world; + } } + } - if (usePlayerCoords){ - this.fwd(3); - } - this.chkpt('start'); - this.record = true; - this.history = []; - return this; + if ( usePlayerCoords ) { + this.fwd( 3 ); + } + this.chkpt( 'start' ); + this.record = true; + this.history = []; + return this; }; exports.Drone = Drone; /* - because this is a plugin, any of its exports will be exported globally. - Since 'blocks' is a module not a plugin it is convenient to export it via - the Drone module. -*/ + because this is a plugin, any of its exports will be exported globally. + Since 'blocks' is a module not a plugin it is convenient to export it via + the Drone module. + */ exports.blocks = blocks; // // add custom methods to the Drone object using this function // -Drone.extend = function(name, func) -{ - Drone.prototype['_' + name] = func; - Drone.prototype[name] = function(){ - if (this.record) - this.history.push([name,arguments]); - var oldVal = this.record; - this.record = false; - this['_' + name].apply(this,arguments); - this.record = oldVal; - return this; - }; - - global[name] = function(){ - var result = new Drone(self); - result[name].apply(result,arguments); - return result; - }; +Drone.extend = function( name, func ) { + Drone.prototype[ '_' + name ] = func; + Drone.prototype[ name ] = function( ) { + if ( this.record ) { + this.history.push( [ name, arguments ] ); + } + var oldVal = this.record; + this.record = false; + this[ '_' + name ].apply( this, arguments ); + this.record = oldVal; + return this; + }; + + global[name] = function( ) { + var result = new Drone( self ); + result[name].apply( result, arguments ); + return result; + }; }; /************************************************************************** @@ -762,7 +773,7 @@ Say you want to do the same thing over and over. You have a couple of options... * You can use a for loop... - d = new Drone(); for (var i =0;i < 4; i++){ d.cottage().right(8); } + d = new Drone(); for ( var i =0;i < 4; i++) { d.cottage().right(8); } While this will fit on the in-game prompt, it's awkward. You need to declare a new Drone object first, then write a for loop to create the @@ -771,7 +782,7 @@ syntax for what should really be simple. * You can use a while loop... - d = new Drone(); var i=4; while (i--){ d.cottage().right(8); } + d = new Drone(); var i=4; while (i--) { d.cottage().right(8); } ... which is slightly shorter but still too much syntax. Each of the above statements is fine for creating a 1-dimensional array of @@ -814,751 +825,769 @@ Another example: This statement creates a row of trees 2 by 3 ... ![times example 1](img/times-trees.png) ***/ -Drone.prototype.times = function(numTimes,commands) { - if (typeof numTimes == "undefined") - numTimes = 2; - if (typeof commands == "undefined") - commands = this.history.concat(); - - this.history = [['times',[numTimes+1,commands]]]; - var oldVal = this.record; - this.record = false; - for (var j = 1; j < numTimes; j++) - { - for (var i = 0;i < commands.length; i++){ - var command = commands[i]; - var methodName = command[0]; - var args = command[1]; - print ("command=" + JSON.stringify(command) + ",methodName=" + methodName); - this[methodName].apply(this,args); - } +Drone.prototype.times = function( numTimes, commands ) { + if ( typeof numTimes == 'undefined' ) { + numTimes = 2; + } + if ( typeof commands == 'undefined' ) { + commands = this.history.concat(); + } + + this.history = [ [ 'times', [ numTimes + 1, commands ] ] ]; + var oldVal = this.record; + this.record = false; + for ( var j = 1; j < numTimes; j++ ) { + for ( var i = 0; i < commands.length; i++) { + var command = commands[i]; + var methodName = command[0]; + var args = command[1]; + print ('command=' + JSON.stringify(command ) + ',methodName=' + methodName ); + this[ methodName ].apply( this, args ); } - this.record = oldVal; - return this; + } + this.record = oldVal; + return this; }; Drone.prototype._checkpoints = {}; -Drone.extend('chkpt',function(name){ - this._checkpoints[name] = {x:this.x,y:this.y,z:this.z,dir:this.dir}; -}); +Drone.extend( 'chkpt', function( name ) { + this._checkpoints[ name ] = { x:this.x, y:this.y, z:this.z, dir:this.dir }; +} ); -Drone.extend('move', function() { - if (arguments[0] instanceof org.bukkit.Location){ - this.x = arguments[0].x; - this.y = arguments[0].y; - this.z = arguments[0].z; - this.dir = _getDirFromRotation(arguments[0].yaw); - this.world = arguments[0].world; - }else if (typeof arguments[0] === "string"){ - var coords = this._checkpoints[arguments[0]]; - if (coords){ - this.x = coords.x; - this.y = coords.y; - this.z = coords.z; - this.dir = coords.dir%4; - } - }else{ - // expect x,y,z,dir - switch(arguments.length){ - case 4: - this.dir = arguments[3]; - case 3: - this.z = arguments[2]; - case 2: - this.y = arguments[1]; - case 1:n - this.x = arguments[0]; - } +Drone.extend( 'move', function( ) { + if ( arguments[0] instanceof Location ) { + this.x = arguments[0].x; + this.y = arguments[0].y; + this.z = arguments[0].z; + this.dir = _getDirFromRotation(arguments[0].yaw ); + this.world = arguments[0].world; + } else if ( typeof arguments[0] === 'string' ) { + var coords = this._checkpoints[arguments[0]]; + if ( coords ) { + this.x = coords.x; + this.y = coords.y; + this.z = coords.z; + this.dir = coords.dir%4; + } + } else { + // expect x,y,z,dir + switch( arguments.length ) { + case 4: + this.dir = arguments[3]; + case 3: + this.z = arguments[2]; + case 2: + this.y = arguments[1]; + case 1: + this.x = arguments[0]; } + } +} ); + +Drone.extend( 'turn', function ( n ) { + if ( typeof n == 'undefined' ) { + n = 1; + } + this.dir += n; + this.dir %=4; +} ); + +Drone.extend( 'right', function( n ) { + if ( typeof n == 'undefined' ) { + n = 1; + } + _movements[ this.dir ].right( this, n ); }); -Drone.extend('turn',function(n){ - if (typeof n == "undefined") - n = 1; - this.dir += n; - this.dir %=4; +Drone.extend( 'left', function( n ) { + if ( typeof n == 'undefined') { + n = 1; + } + _movements[ this.dir ].left( this, n ); }); -Drone.extend('right',function(n){ - if (typeof n == "undefined") - n = 1; - _movements[this.dir].right(this,n); + +Drone.extend( 'fwd', function( n ) { + if ( typeof n == 'undefined' ) { + n = 1; + } + _movements[ this.dir ].fwd( this, n ); }); -Drone.extend('left',function(n){ - if (typeof n == "undefined") - n = 1; - _movements[this.dir].left(this,n); + +Drone.extend( 'back', function( n ) { + if ( typeof n == 'undefined' ) { + n = 1; + } + _movements[ this.dir ].back( this, n ); }); -Drone.extend('fwd',function(n){ - if (typeof n == "undefined") - n = 1; - _movements[this.dir].fwd(this,n); + +Drone.extend( 'up', function( n ) { + if ( typeof n == 'undefined' ) { + n = 1; + } + this.y+= n; }); -Drone.extend('back',function(n){ - if (typeof n == "undefined") - n = 1; - _movements[this.dir].back(this,n); -}); -Drone.extend('up',function(n){ - if (typeof n == "undefined") - n = 1; - this.y+= n; -}); -Drone.extend('down',function(n){ - if (typeof n == "undefined") - n = 1; - this.y-= n; + +Drone.extend( 'down', function( n ) { + if ( typeof n == 'undefined' ) { + n = 1; + } + this.y-= n; }); // // position // -Drone.prototype.getLocation = function() { - return new org.bukkit.Location(this.world, this.x, this.y, this.z); +Drone.prototype.getLocation = function( ) { + return new Location( this.world, this.x, this.y, this.z ); }; // // building // -Drone.extend('sign',function(message,block){ - if (message.constructor == Array){ - }else{ - message = [message]; - } - var bm = this._getBlockIdAndMeta(block); - block = bm[0]; - var meta = bm[1]; - if (block != 63 && block != 68){ - print("ERROR: Invalid block id for use in signs"); - return; - } - if (block == 68){ - meta = Drone.PLAYER_SIGN_FACING[this.dir%4]; - this.back(); - } - if (block == 63){ - meta = (12 + ((this.dir+2)*4)) % 16; - } - putSign(message,this.x,this.y,this.z,block,meta, this.world); - if (block == 68){ - this.fwd(); - } +Drone.extend( 'sign', function( message, block ) { + if ( message.constructor != Array ) { + message = [message]; + } + var bm = this._getBlockIdAndMeta( block ); + block = bm[0]; + var meta = bm[1]; + if ( block != 63 && block != 68 ) { + print('ERROR: Invalid block id for use in signs'); + return; + } + if ( block == 68 ) { + meta = Drone.PLAYER_SIGN_FACING[ this.dir % 4 ]; + this.back(); + } + if ( block == 63 ) { + meta = ( 12 + ( ( this.dir + 2 ) * 4 ) ) % 16; + } + putSign( message, this.x, this.y, this.z, block, meta, this.world ); + if ( block == 68 ) { + this.fwd(); + } }); -Drone.prototype.cuboida = function(/* Array */ blocks,w,h,d){ - var properBlocks = []; - var len = blocks.length; - for (var i = 0;i < len;i++){ - var bm = this._getBlockIdAndMeta(blocks[i]); - properBlocks.push([bm[0],bm[1]]); - } - if (typeof h == "undefined") - h = 1; - if (typeof d == "undefined") - d = 1; - if (typeof w == "undefined") - w = 1; - var that = this; - var dir = this.dir; - var pl = org.bukkit.entity.Player; - var cs = org.bukkit.command.BlockCommandSender; - var bi = 0; - /* - - */ - _traverse[dir].depth(that,d,function(){ - _traverseHeight(that,h,function(){ - _traverse[dir].width(that,w,function(){ - var block = that.world.getBlockAt(that.x,that.y,that.z); - var properBlock = properBlocks[bi%len]; - block.setTypeIdAndData(properBlock[0],properBlock[1],false); - bi++; - }); - }); + +Drone.prototype.cuboida = function(/* Array */ blocks, w, h, d ) { + var properBlocks = []; + var len = blocks.length; + for ( var i = 0; i < len; i++ ) { + var bm = this._getBlockIdAndMeta( blocks[ i ] ); + properBlocks.push( [ bm[0], bm[1] ] ); + } + if ( typeof h == 'undefined' ) { + h = 1; + } + if ( typeof d == 'undefined' ) { + d = 1; + } + if ( typeof w == 'undefined' ) { + w = 1; + } + var that = this; + var dir = this.dir; + var bi = 0; + /* + + */ + _traverse[dir].depth( that, d, function( ) { + _traverseHeight( that, h, function( ) { + _traverse[dir].width( that, w, function( ) { + var block = that.world.getBlockAt( that.x, that.y, that.z ); + var properBlock = properBlocks[ bi % len ]; + block.setTypeIdAndData( properBlock[0], properBlock[1], false ); + bi++; + }); }); - return this; - + }); + return this; + }; /* - faster cuboid because blockid, meta and world must be provided - use this method when you need to repeatedly place blocks -*/ -Drone.prototype.cuboidX = function(blockType, meta, w, h, d){ + faster cuboid because blockid, meta and world must be provided + use this method when you need to repeatedly place blocks + */ +Drone.prototype.cuboidX = function( blockType, meta, w, h, d ) { - if (typeof h == "undefined") - h = 1; - if (typeof d == "undefined") - d = 1; - if (typeof w == "undefined") - w = 1; - var that = this; - var dir = this.dir; + if ( typeof h == 'undefined' ) { + h = 1; + } + if ( typeof d == 'undefined' ) { + d = 1; + } + if ( typeof w == 'undefined' ) { + w = 1; + } + var that = this; + var dir = this.dir; - var depthFunc = function(){ - var block = that.world.getBlockAt(that.x,that.y,that.z); - block.setTypeIdAndData(blockType,meta,false); - // wph 20130210 - dont' know if this is a bug in bukkit but for chests, - // the metadata is ignored (defaults to 2 - south facing) - // only way to change data is to set it using property/bean. - block.data = meta; - }; - var heightFunc = function(){ - _traverse[dir].depth(that,d,depthFunc); - }; - var widthFunc = function(){ - _traverseHeight(that,h,heightFunc); - }; - - _traverse[dir].width(that,w,widthFunc); - return this; - + var depthFunc = function( ) { + var block = that.world.getBlockAt( that.x, that.y, that.z ); + block.setTypeIdAndData( blockType, meta, false ); + // wph 20130210 - dont' know if this is a bug in bukkit but for chests, + // the metadata is ignored (defaults to 2 - south facing) + // only way to change data is to set it using property/bean. + block.data = meta; + }; + var heightFunc = function( ) { + _traverse[dir].depth( that, d, depthFunc ); + }; + var widthFunc = function( ) { + _traverseHeight( that, h, heightFunc ); + }; + _traverse[dir].width( that, w, widthFunc ); + return this; + }; -Drone.prototype.cuboid = function(block,w,h,d){ - var bm = this._getBlockIdAndMeta(block); - return this.cuboidX(bm[0],bm[1], w,h,d); +Drone.prototype.cuboid = function( block, w, h, d ) { + var bm = this._getBlockIdAndMeta( block ); + return this.cuboidX( bm[0], bm[1], w, h, d ); }; -Drone.prototype.cuboid0 = function(block,w,h,d){ - this.chkpt('start_point'); - - // Front wall - this.cuboid(block, w, h, 1); - // Left wall - this.cuboid(block, 1, h, d); - // Right wall - this.right(w-1).cuboid(block, 1, h, d).left(w-1); - // Back wall - this.fwd(d-1).cuboid(block, w, h, 1); - - return this.move('start_point'); + +Drone.prototype.cuboid0 = function( block, w, h, d ) { + this.chkpt( 'start_point' ); + + // Front wall + this.cuboid( block, w, h, 1 ); + // Left wall + this.cuboid( block, 1, h, d ); + // Right wall + this.right( w - 1 ).cuboid( block, 1, h, d ).left( w - 1 ); + // Back wall + this.fwd( d - 1 ).cuboid( block, w, h, 1 ); + + return this.move( 'start_point' ); }; -Drone.extend('door',function(door){ - if (typeof door == "undefined"){ - door = 64; - }else{ - door = 71; - } - this.cuboid(door+':' + this.dir).up().cuboid(door+':8').down(); -}); -Drone.extend('door2',function(door){ - if (typeof door == "undefined"){ - door = 64; - }else{ - door = 71; - } - this - .box(door+':' + this.dir).up() - .box(door+':8').right() - .box(door+':9').down() - .box(door+':' + this.dir).left(); -}); + +Drone.extend( 'door', function( door ) { + if ( typeof door == 'undefined' ) { + door = 64; + } else { + door = 71; + } + this.cuboid( door+':' + this.dir ) + .up( ) + .cuboid( door+':8' ) + .down( ); +} ); + +Drone.extend( 'door2' , function( door ) { + if ( typeof door == 'undefined' ) { + door = 64; + } else { + door = 71; + } + this + .box( door+':' + this.dir ).up( ) + .box( door+':8' ).right( ) + .box( door+':9' ).down( ) + .box( door+':' + this.dir ).left( ); +} ); // player dirs: 0 = east, 1 = south, 2 = west, 3 = north // block dirs: 0 = east, 1 = west, 2 = south , 3 = north // sign dirs: 5 = east, 3 = south, 4 = west, 2 = north -Drone.PLAYER_STAIRS_FACING = [0,2,1,3]; +Drone.PLAYER_STAIRS_FACING = [ 0, 2, 1, 3 ]; // for blocks 68 (wall signs) 65 (ladders) 61,62 (furnaces) 23 (dispenser) and 54 (chest) -Drone.PLAYER_SIGN_FACING = [4,2,5,3]; -Drone.PLAYER_TORCH_FACING = [2,4,1,3]; +Drone.PLAYER_SIGN_FACING = [ 4, 2, 5, 3 ]; +Drone.PLAYER_TORCH_FACING = [ 2, 4, 1, 3 ]; -var _STAIRBLOCKS = {53: '5:0' // oak wood - ,67: 4 // cobblestone - ,108: 45 // brick - ,109: 98 // stone brick - ,114: 112 // nether brick - ,128: 24 // sandstone - ,134: '5:1' // spruce wood - ,135: '5:2' // birch wood - ,136: '5:3' // jungle wood - }; +var _STAIRBLOCKS = { + 53: '5:0' // oak wood + ,67: 4 // cobblestone + ,108: 45 // brick + ,109: 98 // stone brick + ,114: 112 // nether brick + ,128: 24 // sandstone + ,134: '5:1' // spruce wood + ,135: '5:2' // birch wood + ,136: '5:3' // jungle wood +}; // // prism private implementation // -var _prism = function(block,w,d) { - var stairEquiv = _STAIRBLOCKS[block]; - if (stairEquiv){ - this.fwd().prism(stairEquiv,w,d-2).back(); - var d2 = 0; - var middle = Math.floor(d/2); - var uc = 0,dc = 0; - while (d2 < d) - { - var di = (d2 < middle?this.dir:(this.dir+2)%4); - var bd = block + ':' + Drone.PLAYER_STAIRS_FACING[di]; - var putStep = true; - if (d2 == middle){ - if (d % 2 == 1){ - putStep = false; - } - } - if (putStep) - this.cuboid(bd,w); - if (d2 < middle-1){ - this.up(); - uc++; - } - var modulo = d % 2; - if (modulo == 1){ - if (d2 > middle && d2= middle && d2= 1){ - this.cuboid(block,w,1,d2); - d2 -= 2; - this.fwd().up(); - c++; + } + if ( putStep ) { + this.cuboid(bd,w ); + } + if ( d2 < middle-1 ) { + this.up( ); + uc++; + } + var modulo = d % 2; + if ( modulo == 1 ) { + if ( d2 > middle && d2= middle && d2= 1 ) { + this.cuboid(block,w,1,d2 ); + d2 -= 2; + this.fwd( ).up( ); + c++; + } + this.down(c ).back(c ); + } + return this; }; // // prism0 private implementation // -var _prism0 = function(block,w,d){ - this.prism(block,w,d) - .fwd().right() - .prism(0,w-2,d-2) - .left().back(); - var se = _STAIRBLOCKS[block]; - if (d % 2 == 1 && se){ - // top of roof will be open - need repair - var f = Math.floor(d/2); - this.fwd(f).up(f).cuboid(se,w).down(f).back(f); - } +var _prism0 = function( block,w,d ) { + this.prism(block,w,d ) + .fwd( ).right( ) + .prism(0,w-2,d-2 ) + .left( ).back( ); + var se = _STAIRBLOCKS[block]; + if ( d % 2 == 1 && se ) { + // top of roof will be open - need repair + var f = Math.floor(d/2 ); + this.fwd(f ).up(f ).cuboid(se,w ).down(f ).back(f ); + } }; -Drone.extend('prism0',_prism0); -Drone.extend('prism',_prism); -Drone.extend('box',Drone.prototype.cuboid); -Drone.extend('box0',Drone.prototype.cuboid0); -Drone.extend('boxa',Drone.prototype.cuboida); +Drone.extend('prism0',_prism0 ); +Drone.extend('prism',_prism ); +Drone.extend('box',Drone.prototype.cuboid ); +Drone.extend('box0',Drone.prototype.cuboid0 ); +Drone.extend('boxa',Drone.prototype.cuboida ); // // show the Drone's position and direction // -Drone.prototype.toString = function(){ - var dirs = ["east","south","west","north"]; - return "x: " + this.x + " y: "+this.y + " z: " + this.z + " dir: " + this.dir + " "+dirs[this.dir]; +Drone.prototype.toString = function( ) { + var dirs = ['east','south','west','north']; + return 'x: ' + this.x + ' y: '+this.y + ' z: ' + this.z + ' dir: ' + this.dir + ' '+dirs[this.dir]; }; -Drone.prototype.debug = function(){ - print(this.toString()); - return this; +Drone.prototype.debug = function( ) { + print(this.toString( ) ); + return this; }; /* - do the bresenham thing -*/ -var _bresenham = function(x0,y0,radius, setPixel, quadrants){ - // - // credit: Following code is copied almost verbatim from - // http://en.wikipedia.org/wiki/Midpoint_circle_algorithm - // Bresenham's circle algorithm - // - var f = 1 - radius; - var ddF_x = 1; - var ddF_y = -2 * radius; - var x = 0; - var y = radius; - var defaultQuadrants = {topleft: true, topright: true, bottomleft: true, bottomright: true}; - quadrants = quadrants?quadrants:defaultQuadrants; - /* - II | I - ------------ - III | IV - */ - if (quadrants.topleft || quadrants.topright) - setPixel(x0, y0 + radius); // quadrant I/II topmost - if (quadrants.bottomleft || quadrants.bottomright) - setPixel(x0, y0 - radius); // quadrant III/IV bottommost - if (quadrants.topright || quadrants.bottomright) - setPixel(x0 + radius, y0); // quadrant I/IV rightmost - if (quadrants.topleft || quadrants.bottomleft) - setPixel(x0 - radius, y0); // quadrant II/III leftmost - - while(x < y) - { - // ddF_x == 2 * x + 1; - // ddF_y == -2 * y; - // f == x*x + y*y - radius*radius + 2*x - y + 1; - if(f >= 0) - { - y--; - ddF_y += 2; - f += ddF_y; - } - x++; - ddF_x += 2; - f += ddF_x; - if (quadrants.topright){ - setPixel(x0 + x, y0 + y); // quadrant I - setPixel(x0 + y, y0 + x); // quadrant I - } - if (quadrants.topleft){ - setPixel(x0 - x, y0 + y); // quadrant II - setPixel(x0 - y, y0 + x); // quadrant II - } - if (quadrants.bottomleft){ - setPixel(x0 - x, y0 - y); // quadrant III - setPixel(x0 - y, y0 - x); // quadrant III - } - if (quadrants.bottomright){ - setPixel(x0 + x, y0 - y); // quadrant IV - setPixel(x0 + y, y0 - x); // quadrant IV - } + do the bresenham thing + */ +var _bresenham = function( x0,y0,radius, setPixel, quadrants ) { + // + // credit: Following code is copied almost verbatim from + // http://en.wikipedia.org/wiki/Midpoint_circle_algorithm + // Bresenham's circle algorithm + // + var f = 1 - radius; + var ddF_x = 1; + var ddF_y = -2 * radius; + var x = 0; + var y = radius; + var defaultQuadrants = {topleft: true, topright: true, bottomleft: true, bottomright: true}; + quadrants = quadrants?quadrants:defaultQuadrants; + /* + II | I + ------------ + III | IV + */ + if ( quadrants.topleft || quadrants.topright ) + setPixel(x0, y0 + radius ); // quadrant I/II topmost + if ( quadrants.bottomleft || quadrants.bottomright ) + setPixel(x0, y0 - radius ); // quadrant III/IV bottommost + if ( quadrants.topright || quadrants.bottomright ) + setPixel(x0 + radius, y0 ); // quadrant I/IV rightmost + if ( quadrants.topleft || quadrants.bottomleft ) + setPixel(x0 - radius, y0 ); // quadrant II/III leftmost + + while ( x < y ) { + if(f >= 0 ) { + y--; + ddF_y += 2; + f += ddF_y; } + x++; + ddF_x += 2; + f += ddF_x; + if ( quadrants.topright ) { + setPixel(x0 + x, y0 + y ); // quadrant I + setPixel(x0 + y, y0 + x ); // quadrant I + } + if ( quadrants.topleft ) { + setPixel(x0 - x, y0 + y ); // quadrant II + setPixel(x0 - y, y0 + x ); // quadrant II + } + if ( quadrants.bottomleft ) { + setPixel(x0 - x, y0 - y ); // quadrant III + setPixel(x0 - y, y0 - x ); // quadrant III + } + if ( quadrants.bottomright ) { + setPixel(x0 + x, y0 - y ); // quadrant IV + setPixel(x0 + y, y0 - x ); // quadrant IV + } + } }; -var _getStrokeDir = function(x,y){ - var absY = Math.abs(y); - var absX = Math.abs(x); - var strokeDir = 0; - if (y > 0 && absY >= absX) - strokeDir = 0 ; //down - else if (y < 0 && absY >= absX) - strokeDir = 1 ; // up - else if (x > 0 && absX >= absY) - strokeDir = 2 ; // left - else if (x < 0 && absX >= absY) - strokeDir = 3 ; // right - return strokeDir; +var _getStrokeDir = function( x,y ) { + var absY = Math.abs(y ); + var absX = Math.abs(x ); + var strokeDir = 0; + if ( y > 0 && absY >= absX ) + strokeDir = 0 ; //down + else if ( y < 0 && absY >= absX ) + strokeDir = 1 ; // up + else if ( x > 0 && absX >= absY ) + strokeDir = 2 ; // left + else if ( x < 0 && absX >= absY ) + strokeDir = 3 ; // right + return strokeDir; }; /* - The daddy of all arc-related API calls - - if you're drawing anything that bends it ends up here. -*/ -var _arc2 = function( params ) { + The daddy of all arc-related API calls - + if you're drawing anything that bends it ends up here. + */ +var _arc2 = function( params ) { - var drone = params.drone; - var orientation = params.orientation?params.orientation:"horizontal"; - var quadrants = params.quadrants?params.quadrants:{ - topright:1, - topleft:2, - bottomleft:3, - bottomright:4 - }; - var stack = params.stack?params.stack:1; - var radius = params.radius; - var strokeWidth = params.strokeWidth?params.strokeWidth:1; - drone.chkpt('arc2'); - var x0, y0, gotoxy,setPixel; - - if (orientation == "horizontal"){ - gotoxy = function(x,y){ return drone.right(x).fwd(y);}; - drone.right(radius).fwd(radius).chkpt('center'); - switch (drone.dir) { - case 0: // east - case 2: // west - x0 = drone.z; - y0 = drone.x; - break; - case 1: // south - case 3: // north - x0 = drone.x; - y0 = drone.z; + var drone = params.drone; + var orientation = params.orientation?params.orientation:'horizontal'; + var quadrants = params.quadrants?params.quadrants:{ + topright:1, + topleft:2, + bottomleft:3, + bottomright:4 + }; + var stack = params.stack?params.stack:1; + var radius = params.radius; + var strokeWidth = params.strokeWidth?params.strokeWidth:1; + drone.chkpt('arc2' ); + var x0, y0, gotoxy,setPixel; + + if ( orientation == 'horizontal' ) { + gotoxy = function( x,y ) { return drone.right(x ).fwd(y );}; + drone.right(radius ).fwd(radius ).chkpt('center' ); + switch ( drone.dir ) { + case 0: // east + case 2: // west + x0 = drone.z; + y0 = drone.x; + break; + case 1: // south + case 3: // north + x0 = drone.x; + y0 = drone.z; + } + setPixel = function( x,y ) { + x = (x-x0 ); + y = (y-y0 ); + if ( params.fill ) { + // wph 20130114 more efficient esp. for large cylinders/spheres + if ( y < 0 ) { + drone + .fwd(y ).right(x ) + .cuboidX(params.blockType,params.meta,1,stack,Math.abs(y*2 )+1 ) + .back(y ).left(x ); } - setPixel = function(x,y) { - x = (x-x0); - y = (y-y0); - if (params.fill){ - // wph 20130114 more efficient esp. for large cylinders/spheres - if (y < 0){ - drone - .fwd(y).right(x) - .cuboidX(params.blockType,params.meta,1,stack,Math.abs(y*2)+1) - .back(y).left(x); - } - }else{ - if (strokeWidth == 1){ - gotoxy(x,y) - .cuboidX(params.blockType, - params.meta, - 1, // width - stack, // height - strokeWidth // depth - ) - .move('center'); - } else { - var strokeDir = _getStrokeDir(x,y); - var width = 1, depth = 1; - switch (strokeDir){ - case 0: // down - y = y-(strokeWidth-1); - depth = strokeWidth; - break; - case 1: // up - depth = strokeWidth; - break; - case 2: // left - width = strokeWidth; - x = x-(strokeWidth-1); - break; - case 3: // right - width = strokeWidth; - break; - } - gotoxy(x,y) - .cuboidX(params.blockType, params.meta, width, stack, depth) - .move('center'); - - } - } - }; - }else{ - // vertical - gotoxy = function(x,y){ return drone.right(x).up(y);}; - drone.right(radius).up(radius).chkpt('center'); - switch (drone.dir) { - case 0: // east - case 2: // west - x0 = drone.z; - y0 = drone.y; + }else{ + if ( strokeWidth == 1 ) { + gotoxy(x,y ) + .cuboidX(params.blockType, + params.meta, + 1, // width + stack, // height + strokeWidth // depth + ) + .move('center' ); + } else { + var strokeDir = _getStrokeDir(x,y ); + var width = 1, depth = 1; + switch ( strokeDir ) { + case 0: // down + y = y-(strokeWidth-1 ); + depth = strokeWidth; break; - case 1: // south - case 3: // north - x0 = drone.x; - y0 = drone.y; + case 1: // up + depth = strokeWidth; + break; + case 2: // left + width = strokeWidth; + x = x-(strokeWidth-1 ); + break; + case 3: // right + width = strokeWidth; + break; + } + gotoxy(x,y ) + .cuboidX(params.blockType, params.meta, width, stack, depth ) + .move('center' ); + } - setPixel = function(x,y) { - x = (x-x0); - y = (y-y0); - if (params.fill){ - // wph 20130114 more efficient esp. for large cylinders/spheres - if (y < 0){ - drone - .up(y).right(x) - .cuboidX(params.blockType,params.meta,1,Math.abs(y*2)+1,stack) - .down(y).left(x); - } - }else{ - if (strokeWidth == 1){ - gotoxy(x,y) - .cuboidX(params.blockType,params.meta,strokeWidth,1,stack) - .move('center'); - }else{ - var strokeDir = _getStrokeDir(x,y); - var width = 1, height = 1; - switch (strokeDir){ - case 0: // down - y = y-(strokeWidth-1); - height = strokeWidth; - break; - case 1: // up - height = strokeWidth; - break; - case 2: // left - width = strokeWidth; - x = x-(strokeWidth-1); - break; - case 3: // right - width = strokeWidth; - break; - } - gotoxy(x,y) - .cuboidX(params.blockType, params.meta, width, height, stack) - .move('center'); - - } - } - }; - } - /* - setPixel assumes a 2D plane - need to put a block along appropriate plane - */ - _bresenham(x0,y0,radius,setPixel,quadrants); - - params.drone.move('arc2'); -}; - - -Drone.extend('arc',function(params) { - params.drone = this; - _arc2(params); -}); - -var _cylinder0 = function(block,radius,height,exactParams){ - var arcParams = { - radius: radius, - fill: false, - orientation: 'horizontal', - stack: height, + } }; - - if (exactParams){ - arcParams.blockType = exactParams.blockType; - arcParams.meta = exactParams.meta; - }else{ - var md = this._getBlockIdAndMeta(block); - arcParams.blockType = md[0]; - arcParams.meta = md[1]; + }else{ + // vertical + gotoxy = function( x,y ) { return drone.right(x ).up(y );}; + drone.right(radius ).up(radius ).chkpt('center' ); + switch ( drone.dir ) { + case 0: // east + case 2: // west + x0 = drone.z; + y0 = drone.y; + break; + case 1: // south + case 3: // north + x0 = drone.x; + y0 = drone.y; } - return this.arc(arcParams); -}; -var _cylinder1 = function(block,radius,height,exactParams){ - var arcParams = { - radius: radius, - fill: true, - orientation: 'horizontal', - stack: height, + setPixel = function( x,y ) { + x = (x-x0 ); + y = (y-y0 ); + if ( params.fill ) { + // wph 20130114 more efficient esp. for large cylinders/spheres + if ( y < 0 ) { + drone + .up(y ).right(x ) + .cuboidX(params.blockType,params.meta,1,Math.abs(y*2 )+1,stack ) + .down(y ).left(x ); + } + }else{ + if ( strokeWidth == 1 ) { + gotoxy(x,y ) + .cuboidX(params.blockType,params.meta,strokeWidth,1,stack ) + .move('center' ); + }else{ + var strokeDir = _getStrokeDir(x,y ); + var width = 1, height = 1; + switch ( strokeDir ) { + case 0: // down + y = y-(strokeWidth-1 ); + height = strokeWidth; + break; + case 1: // up + height = strokeWidth; + break; + case 2: // left + width = strokeWidth; + x = x-(strokeWidth-1 ); + break; + case 3: // right + width = strokeWidth; + break; + } + gotoxy(x,y ) + .cuboidX(params.blockType, params.meta, width, height, stack ) + .move('center' ); + + } + } }; - - if (exactParams){ - arcParams.blockType = exactParams.blockType; - arcParams.meta = exactParams.meta; - }else{ - var md = this._getBlockIdAndMeta(block); - arcParams.blockType = md[0]; - arcParams.meta = md[1]; - } - return this.arc(arcParams); + } + /* + setPixel assumes a 2D plane - need to put a block along appropriate plane + */ + _bresenham(x0,y0,radius,setPixel,quadrants ); + + params.drone.move('arc2' ); }; -var _paste = function(name) + + +Drone.extend('arc',function( params ) { + params.drone = this; + _arc2(params ); +} ); + +var _cylinder0 = function( block,radius,height,exactParams ) { + var arcParams = { + radius: radius, + fill: false, + orientation: 'horizontal', + stack: height, + }; + + if ( exactParams ) { + arcParams.blockType = exactParams.blockType; + arcParams.meta = exactParams.meta; + }else{ + var md = this._getBlockIdAndMeta(block ); + arcParams.blockType = md[0]; + arcParams.meta = md[1]; + } + return this.arc(arcParams ); +}; +var _cylinder1 = function( block,radius,height,exactParams ) { + var arcParams = { + radius: radius, + fill: true, + orientation: 'horizontal', + stack: height, + }; + + if ( exactParams ) { + arcParams.blockType = exactParams.blockType; + arcParams.meta = exactParams.meta; + }else{ + var md = this._getBlockIdAndMeta(block ); + arcParams.blockType = md[0]; + arcParams.meta = md[1]; + } + return this.arc(arcParams ); +}; +var _paste = function( name ) { - var ccContent = Drone.clipBoard[name]; - var srcBlocks = ccContent.blocks; - var srcDir = ccContent.dir; // direction player was facing when copied. - var dirOffset = (4 + (this.dir - srcDir)) %4; - var that = this; + var ccContent = Drone.clipBoard[name]; + var srcBlocks = ccContent.blocks; + var srcDir = ccContent.dir; // direction player was facing when copied. + var dirOffset = (4 + (this.dir - srcDir ) ) %4; + var that = this; - _traverse[this.dir].width(that,srcBlocks.length,function(ww){ - var h = srcBlocks[ww].length; - _traverseHeight(that,h,function(hh){ - var d = srcBlocks[ww][hh].length; - _traverse[that.dir].depth(that,d,function(dd){ - var b = srcBlocks[ww][hh][dd]; - var bm = that._getBlockIdAndMeta(b); - var cb = bm[0]; - var md = bm[1]; - // - // need to adjust blocks which face a direction - // - switch (cb) - { - // - // doors - // - case 64: // wood - case 71: // iron - // top half of door doesn't need to change - if (md < 8) { - md = (md + dirOffset) % 4; - } - break; - // - // stairs - // - case 53: // oak - case 67: // cobblestone - case 108: // red brick - case 109: // stone brick - case 114: // nether brick - case 128: // sandstone - case 134: // spruce - case 135: // birch - case 136: // junglewood - var dir = md & 0x3; - var a = Drone.PLAYER_STAIRS_FACING; - var len = a.length; - for (var c=0;c < len;c++){ - if (a[c] == dir){ - break; - } - } - c = (c + dirOffset) %4; - var newDir = a[c]; - md = (md >>2<<2) + newDir; - break; - // - // signs , ladders etc - // - case 23: // dispenser - case 54: // chest - case 61: // furnace - case 62: // burning furnace - case 65: // ladder - case 68: // wall sign - var a = Drone.PLAYER_SIGN_FACING; - var len = a.length; - for (var c=0;c < len;c++){ - if (a[c] == md){ - break; - } - } - c = (c + dirOffset) %4; - var newDir = a[c]; - md = newDir; - break; - } - putBlock(that.x,that.y,that.z,cb,md,that.world); - }); - }); - }); -}; -var _getDirFromRotation = function(r){ - // 0 = east, 1 = south, 2 = west, 3 = north - // 46 to 135 = west - // 136 to 225 = north - // 226 to 315 = east - // 316 to 45 = south - - r = (r + 360) % 360; // east could be 270 or -90 - - if (r > 45 && r <= 135) - return 2; // west - if (r > 135 && r <= 225) - return 3; // north - if (r > 225 && r <= 315) - return 0; // east - if (r > 315 || r < 45) - return 1; // south -}; -var _getBlockIdAndMeta = function(b){ - var defaultMeta = 0; - if (typeof b == 'string'){ - var bs = b; - var sp = bs.indexOf(':'); - if (sp == -1){ - b = parseInt(bs); - // wph 20130414 - use sensible defaults for certain blocks e.g. stairs - // should face the drone. - for (var i in blocks.stairs){ - if (blocks.stairs[i] === b){ - defaultMeta = Drone.PLAYER_STAIRS_FACING[this.dir]; - break; - } + _traverse[this.dir].width(that,srcBlocks.length,function( ww ) { + var h = srcBlocks[ww].length; + _traverseHeight(that,h,function( hh ) { + var d = srcBlocks[ww][hh].length; + _traverse[that.dir].depth(that,d,function( dd ) { + var b = srcBlocks[ww][hh][dd]; + var bm = that._getBlockIdAndMeta(b ); + var cb = bm[0]; + var md = bm[1]; + // + // need to adjust blocks which face a direction + // + switch ( cb ) { + // + // doors + // + case 64: // wood + case 71: // iron + // top half of door doesn't need to change + if ( md < 8 ) { + md = (md + dirOffset ) % 4; + } + break; + // + // stairs + // + case 53: // oak + case 67: // cobblestone + case 108: // red brick + case 109: // stone brick + case 114: // nether brick + case 128: // sandstone + case 134: // spruce + case 135: // birch + case 136: // junglewood + var dir = md & 0x3; + var a = Drone.PLAYER_STAIRS_FACING; + var len = a.length; + for ( var c=0;c < len;c++ ) { + if ( a[c] == dir ) { + break; } - return [b,defaultMeta]; - } - b = parseInt(bs.substring(0,sp)); - var md = parseInt(bs.substring(sp+1,bs.length)); - return [b,md]; - }else{ - // wph 20130414 - use sensible defaults for certain blocks e.g. stairs - // should face the drone. - for (var i in blocks.stairs){ - if (blocks.stairs[i] === b){ - defaultMeta = Drone.PLAYER_STAIRS_FACING[this.dir]; - break; + } + c = (c + dirOffset ) %4; + var newDir = a[c]; + md = (md >>2<<2 ) + newDir; + break; + // + // signs , ladders etc + // + case 23: // dispenser + case 54: // chest + case 61: // furnace + case 62: // burning furnace + case 65: // ladder + case 68: // wall sign + var a = Drone.PLAYER_SIGN_FACING; + var len = a.length; + for ( var c=0;c < len;c++ ) { + if ( a[c] == md ) { + break; } + } + c = (c + dirOffset ) %4; + var newDir = a[c]; + md = newDir; + break; } - return [b,defaultMeta]; + putBlock(that.x,that.y,that.z,cb,md,that.world ); + } ); + } ); + } ); +}; +var _getDirFromRotation = function( r ) { + // 0 = east, 1 = south, 2 = west, 3 = north + // 46 to 135 = west + // 136 to 225 = north + // 226 to 315 = east + // 316 to 45 = south + + r = (r + 360 ) % 360; // east could be 270 or -90 + + if ( r > 45 && r <= 135 ) + return 2; // west + if ( r > 135 && r <= 225 ) + return 3; // north + if ( r > 225 && r <= 315 ) + return 0; // east + if ( r > 315 || r < 45 ) + return 1; // south +}; +var _getBlockIdAndMeta = function(b ) { + var defaultMeta = 0; + if ( typeof b == 'string' ) { + var bs = b; + var sp = bs.indexOf(':' ); + if ( sp == -1 ) { + b = parseInt(bs ); + // wph 20130414 - use sensible defaults for certain blocks e.g. stairs + // should face the drone. + for ( var i in blocks.stairs ) { + if ( blocks.stairs[i] === b ) { + defaultMeta = Drone.PLAYER_STAIRS_FACING[this.dir]; + break; + } + } + return [b,defaultMeta]; } + b = parseInt(bs.substring(0,sp ) ); + var md = parseInt(bs.substring(sp+1,bs.length ) ); + return [b,md]; + }else{ + // wph 20130414 - use sensible defaults for certain blocks e.g. stairs + // should face the drone. + for ( var i in blocks.stairs ) { + if ( blocks.stairs[i] === b ) { + defaultMeta = Drone.PLAYER_STAIRS_FACING[this.dir]; + break; + } + } + return [b,defaultMeta]; + } }; // // movement // var _movements = [{},{},{},{}]; // east -_movements[0].right = function(that,n){ that.z +=n; return that;}; -_movements[0].left = function(that,n){ that.z -=n; return that;}; -_movements[0].fwd = function(that,n){ that.x +=n; return that;}; -_movements[0].back = function(that,n){ that.x -= n; return that;}; +_movements[0].right = function( that,n ) { that.z +=n; return that;}; +_movements[0].left = function( that,n ) { that.z -=n; return that;}; +_movements[0].fwd = function( that,n ) { that.x +=n; return that;}; +_movements[0].back = function( that,n ) { that.x -= n; return that;}; // south _movements[1].right = _movements[0].back; _movements[1].left = _movements[0].fwd; @@ -1576,145 +1605,144 @@ _movements[3].fwd = _movements[0].left; _movements[3].back = _movements[0].right; var _traverse = [{},{},{},{}]; // east -_traverse[0].width = function(that,n,callback){ - var s = that.z, e = s + n; - for (; that.z < e; that.z++){ - callback(that.z-s); - } - that.z = s; +_traverse[0].width = function( that,n,callback ) { + var s = that.z, e = s + n; + for ( ; that.z < e; that.z++ ) { + callback(that.z-s ); + } + that.z = s; }; -_traverse[0].depth = function(that,n,callback){ - var s = that.x, e = s+n; - for (;that.x < e;that.x++){ - callback(that.x-s); - } - that.x = s; +_traverse[0].depth = function( that,n,callback ) { + var s = that.x, e = s+n; + for ( ;that.x < e;that.x++ ) { + callback(that.x-s ); + } + that.x = s; }; // south -_traverse[1].width = function(that,n,callback){ - var s = that.x, e = s-n; - for (;that.x > e;that.x--){ - callback(s-that.x); - } - that.x = s; +_traverse[1].width = function( that,n,callback ) { + var s = that.x, e = s-n; + for ( ;that.x > e;that.x-- ) { + callback(s-that.x ); + } + that.x = s; }; _traverse[1].depth = _traverse[0].width; // west -_traverse[2].width = function(that,n,callback){ - var s = that.z, e = s-n; - for (;that.z > e;that.z--){ - callback(s-that.z); - } - that.z = s; +_traverse[2].width = function( that,n,callback ) { + var s = that.z, e = s-n; + for ( ;that.z > e;that.z-- ) { + callback(s-that.z ); + } + that.z = s; }; _traverse[2].depth = _traverse[1].width; // north _traverse[3].width = _traverse[0].depth; _traverse[3].depth = _traverse[2].width; -var _traverseHeight = function(that,n,callback){ - var s = that.y, e = s + n; - for (; that.y < e; that.y++){ - callback(that.y-s); - } - that.y = s; +var _traverseHeight = function( that,n,callback ) { + var s = that.y, e = s + n; + for ( ; that.y < e; that.y++ ) { + callback(that.y-s ); + } + that.y = s; }; // // standard fisher-yates shuffle algorithm // -var _fisherYates = function( myArray ) { - var i = myArray.length; - if ( i == 0 ) return false; - while ( --i ) { - var j = Math.floor( Math.random() * ( i + 1 ) ); - var tempi = myArray[i]; - var tempj = myArray[j]; - myArray[i] = tempj; - myArray[j] = tempi; - } +var _fisherYates = function( myArray ) { + var i = myArray.length; + if ( i == 0 ) return false; + while ( --i ) { + var j = Math.floor( Math.random( ) * ( i + 1 ) ); + var tempi = myArray[i]; + var tempj = myArray[j]; + myArray[i] = tempj; + myArray[j] = tempi; + } }; -var _copy = function(name, w, h, d) { - var that = this; - var ccContent = []; - _traverse[this.dir].width(that,w,function(ww){ - ccContent.push([]); - _traverseHeight(that,h,function(hh){ - ccContent[ww].push([]); - _traverse[that.dir].depth(that,d,function(dd){ - var b = that.world.getBlockAt(that.x,that.y,that.z); - ccContent[ww][hh][dd] = b; - }); - }); - }); - Drone.clipBoard[name] = {dir: this.dir, blocks: ccContent}; +var _copy = function( name, w, h, d ) { + var that = this; + var ccContent = []; + _traverse[this.dir].width(that,w,function( ww ) { + ccContent.push([] ); + _traverseHeight(that,h,function( hh ) { + ccContent[ww].push([] ); + _traverse[that.dir].depth(that,d,function( dd ) { + var b = that.world.getBlockAt(that.x,that.y,that.z ); + ccContent[ww][hh][dd] = b; + } ); + } ); + } ); + Drone.clipBoard[name] = {dir: this.dir, blocks: ccContent}; }; -var _garden = function(w,d) { - // make sure grass is present first - this.down().box(2,w,1,d).up(); - - // make flowers more common than long grass - var dist = {37: 3, // red flower - 38: 3, // yellow flower - '31:1': 2, // long grass - 0: 1 - }; - - return this.rand(dist,w,1,d); +var _garden = function( w,d ) { + // make sure grass is present first + this.down( ).box(2,w,1,d ).up( ); + + // make flowers more common than long grass + var dist = {37: 3, // red flower + 38: 3, // yellow flower + '31:1': 2, // long grass + 0: 1 + }; + + return this.rand(dist,w,1,d ); }; -var _rand = function(blockDistribution){ - if (!(blockDistribution.constructor == Array)){ - var a = []; - for (var p in blockDistribution){ - var n = blockDistribution[p]; - for (var i = 0;i < n;i++){ - a.push(p); - } - } - blockDistribution = a; +var _rand = function( blockDistribution ) { + if ( !(blockDistribution.constructor == Array ) ) { + var a = []; + for ( var p in blockDistribution ) { + var n = blockDistribution[p]; + for ( var i = 0;i < n;i++ ) { + a.push(p ); + } } - while (blockDistribution.length < 1000){ - // make array bigger so that it's more random - blockDistribution = blockDistribution.concat(blockDistribution); - } - _fisherYates(blockDistribution); - return blockDistribution; + blockDistribution = a; + } + while ( blockDistribution.length < 1000 ) { + // make array bigger so that it's more random + blockDistribution = blockDistribution.concat(blockDistribution ); + } + _fisherYates(blockDistribution ); + return blockDistribution; }; -Drone.extend('rand',function(dist,w,h,d){ - var randomized = _rand(dist); - this.boxa(randomized,w,h,d); -}); +Drone.extend('rand',function( dist,w,h,d ) { + var randomized = _rand(dist ); + this.boxa(randomized,w,h,d ); +} ); var _trees = { - oak: org.bukkit.TreeType.BIG_TREE , - birch: org.bukkit.TreeType.BIRCH , - jungle: org.bukkit.TreeType.JUNGLE, - spruce: org.bukkit.TreeType.REDWOOD + oak: TreeType.BIG_TREE , + birch: TreeType.BIRCH , + jungle: TreeType.JUNGLE, + spruce: TreeType.REDWOOD }; -for (var p in _trees) -{ - Drone.extend(p, function(v) { - return function() { - var block = this.world.getBlockAt(this.x,this.y,this.z); - if (block.typeId == 2){ - this.up(); - } - var treeLoc = new org.bukkit.Location(this.world,this.x,this.y,this.z); - var successful = treeLoc.world.generateTree(treeLoc,v); - if (block.typeId == 2){ - this.down(); - } - }; - }(_trees[p])); +for ( var p in _trees ) { + Drone.extend(p, function( v ) { + return function( ) { + var block = this.world.getBlockAt(this.x,this.y,this.z ); + if ( block.typeId == 2 ) { + this.up( ); + } + var treeLoc = new Location(this.world,this.x,this.y,this.z ); + var successful = treeLoc.world.generateTree(treeLoc,v ); + if ( block.typeId == 2 ) { + this.down( ); + } + }; + }(_trees[p] ) ); } // // Drone's clipboard // Drone.clipBoard = {}; -Drone.extend('garden',_garden); -Drone.extend('copy', _copy); -Drone.extend('paste',_paste); -Drone.extend('cylinder0',_cylinder0); -Drone.extend('cylinder', _cylinder1); +Drone.extend('garden',_garden ); +Drone.extend('copy', _copy ); +Drone.extend('paste',_paste ); +Drone.extend('cylinder0',_cylinder0 ); +Drone.extend('cylinder', _cylinder1 ); // // wph 20130130 - make this a method - extensions can use it. // diff --git a/src/main/js/plugins/drone/sphere.js b/src/main/js/plugins/drone/sphere.js index 3ba43eb..7b91a4f 100644 --- a/src/main/js/plugins/drone/sphere.js +++ b/src/main/js/plugins/drone/sphere.js @@ -22,49 +22,64 @@ Spheres are time-consuming to make. You *can* make large spheres (250 radius) bu server to be very busy for a couple of minutes while doing so. ***/ -Drone.extend('sphere', function(block,radius) -{ - var lastRadius = radius; - var slices = [[radius,0]]; - var diameter = radius*2; - var bm = this._getBlockIdAndMeta(block); +Drone.extend( 'sphere', function( block, radius ) { + var lastRadius = radius, + slices = [ [ radius , 0 ] ], + diameter = radius * 2, + bm = this._getBlockIdAndMeta( block ), + r2 = radius * radius, + i = 0, + newRadius, + yOffset, + sr, + sh, + v, + h; - var r2 = radius*radius; - for (var i = 0; i <= radius;i++){ - var newRadius = Math.round(Math.sqrt(r2 - i*i)); - if (newRadius == lastRadius) - slices[slices.length-1][1]++; - else - slices.push([newRadius,1]); - lastRadius = newRadius; + for ( i = 0; i <= radius; i++ ) { + newRadius = Math.round( Math.sqrt( r2 - i * i ) ); + if ( newRadius == lastRadius ) { + slices[ slices.length - 1 ][ 1 ]++; + } else { + slices.push( [ newRadius , 1 ] ); } - this.chkpt('sphere'); - // - // mid section - // - this.up(radius - slices[0][1]) - .cylinder(block,radius,(slices[0][1]*2)-1,{blockType: bm[0],meta: bm[1]}) - .down(radius-slices[0][1]); + lastRadius = newRadius; + } + this.chkpt( 'sphere' ); + // + // mid section + // + this.up( radius - slices[0][1] ) + .cylinder( block, radius, ( slices[0][1]*2 ) - 1, { blockType: bm[0], meta: bm[1] } ) + .down( radius - slices[0][1] ); + + yOffset = -1; + for ( i = 1; i < slices.length; i++ ) { + yOffset += slices[i-1][1]; + sr = slices[i][0]; + sh = slices[i][1]; + v = radius + yOffset; + h = radius - sr; + // northern hemisphere + this.up( v ) + .fwd( h ) + .right( h ) + .cylinder( block, sr, sh, { blockType: bm[0], meta: bm[1] } ) + .left( h ) + .back( h ) + .down( v ); - var yOffset = -1; - for (var i = 1; i < slices.length;i++) - { - yOffset += slices[i-1][1]; - var sr = slices[i][0]; - var sh = slices[i][1]; - var v = radius + yOffset, h = radius-sr; - // northern hemisphere - this.up(v).fwd(h).right(h) - .cylinder(block,sr,sh,{blockType: bm[0],meta: bm[1]}) - .left(h).back(h).down(v); - - // southern hemisphere - v = radius - (yOffset+sh+1); - this.up(v).fwd(h).right(h) - .cylinder(block,sr,sh,{blockType: bm[0],meta: bm[1]}) - .left(h).back(h). down(v); - } - return this.move('sphere'); + // southern hemisphere + v = radius - ( yOffset + sh + 1 ); + this.up( v ) + .fwd( h ) + .right( h ) + .cylinder( block, sr, sh, { blockType: bm[0], meta: bm[1] } ) + .left( h ) + .back( h ) + .down( v ); + } + return this.move( 'sphere' ); }); /************************************************************************ ### Drone.sphere0() method @@ -88,81 +103,80 @@ server to be very busy for a couple of minutes while doing so. ***/ Drone.extend('sphere0', function(block,radius) { -/* - this.sphere(block,radius) - .fwd().right().up() - .sphere(0,radius-1) - .back().left().down(); + var lastRadius = radius, + slices = [ [ radius, 0 ] ], + diameter = radius * 2, + bm = this._getBlockIdAndMeta( block ), + r2 = radius*radius, + i, + newRadius, + sr, + sh, + v, + h, + len, + yOffset; -*/ - - var lastRadius = radius; - var slices = [[radius,0]]; - var diameter = radius*2; - var bm = this._getBlockIdAndMeta(block); - - var r2 = radius*radius; - for (var i = 0; i <= radius;i++){ - var newRadius = Math.round(Math.sqrt(r2 - i*i)); - if (newRadius == lastRadius) - slices[slices.length-1][1]++; - else - slices.push([newRadius,1]); - lastRadius = newRadius; + for ( i = 0; i <= radius; i++ ) { + newRadius = Math.round( Math.sqrt( r2 - i * i ) ); + if ( newRadius == lastRadius ) { + slices[ slices.length - 1 ][ 1 ]++; + } else { + slices.push( [ newRadius, 1 ] ); } - this.chkpt('sphere0'); - // - // mid section - // - //.cylinder(block,radius,(slices[0][1]*2)-1,{blockType: bm[0],meta: bm[1]}) - this.up(radius - slices[0][1]) - .arc({blockType: bm[0], - meta: bm[1], - radius: radius, - strokeWidth: 2, - stack: (slices[0][1]*2)-1, - fill: false - }) - .down(radius-slices[0][1]); + lastRadius = newRadius; + } + this.chkpt( 'sphere0' ); + // + // mid section + // + this.up( radius - slices[0][1] ) + .arc({ blockType: bm[0], + meta: bm[1], + radius: radius, + strokeWidth: 2, + stack: (slices[0][1]*2)-1, + fill: false + }) + .down( radius - slices[0][1] ); + + yOffset = -1; + len = slices.length; + for ( i = 1; i < len; i++ ) { + yOffset += slices[i-1][1]; + sr = slices[i][0]; + sh = slices[i][1]; + v = radius + yOffset; + h = radius-sr; + // northern hemisphere + // .cylinder(block,sr,sh,{blockType: bm[0],meta: bm[1]}) + this.up( v ).fwd( h ).right( h ) + .arc({ + blockType: bm[0], + meta: bm[1], + radius: sr, + stack: sh, + fill: false, + strokeWidth: i < len - 1 ? 1 + ( sr - slices[ i + 1 ][ 0 ] ) : 1 + }) + .left( h ).back( h ).down( v ); - var yOffset = -1; - var len = slices.length; - for (var i = 1; i < len;i++) - { - yOffset += slices[i-1][1]; - var sr = slices[i][0]; - var sh = slices[i][1]; - var v = radius + yOffset, h = radius-sr; - // northern hemisphere - // .cylinder(block,sr,sh,{blockType: bm[0],meta: bm[1]}) - this.up(v).fwd(h).right(h) - .arc({ - blockType: bm[0], - meta: bm[1], - radius: sr, - stack: sh, - fill: false, - strokeWidth: i : Go to player's home", - "/jsp home set : Set your current location as home", - "/jsp home delete : Delete your home location", +var homes = plugin( 'homes', { - /* social */ - "/jsp home list : List homes you can visit", - "/jsp home ilist : List players who can visit your home", - "/jsp home invite : Invite to your home", - "/jsp home uninvite : Uninvite to your home", - "/jsp home public : Open your home to all players", - "/jsp home private : Make your home private", + help: function( ) { + return [ + /* basic functions */ + '/jsp home : Return to your own home', + '/jsp home : Go to player home', + '/jsp home set : Set your current location as home', + '/jsp home delete : Delete your home location', - /* administration */ - "/jsp home listall : Show all houses (ops only)", - "/jsp home clear : Clears player's home location (ops only)" - ]; - }, - /* ======================================================================== - basic functions - ======================================================================== */ + /* social */ + '/jsp home list : List homes you can visit', + '/jsp home ilist : List players who can visit your home', + '/jsp home invite : Invite to your home', + '/jsp home uninvite : Uninvite to your home', + '/jsp home public : Open your home to all players', + '/jsp home private : Make your home private', - go: function(guest, host){ - if (typeof host == "undefined") - host = guest; - guest = utils.player(guest); - host = utils.player(host); - var loc = _store.houses[host.name]; - if (!loc){ - guest.sendMessage(host.name + " has no home"); - return; - } - if (!this._canVisit(guest,host)){ - guest.sendMessage("You can't visit " + host.name + "'s home yet"); - return; - } - var teleportCause = org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; - var homeLoc = utils.locationFromJSON(loc); - guest.teleport(homeLoc, teleportCause.PLUGIN); - }, - /* - determine whether a guest is allow visit a host's home - */ - _canVisit: function(guest, host){ - if (guest == host) - return true; - if (_store.openHouses[host.name]) - return true; - var invitations = _store.invites[host.name]; - if (invitations) - for (var i = 0;i < invitations.length;i++) - if (invitations[i] == guest.name) - return true; - return false; - }, - set: function(player){ - player = utils.player(player); - var loc = player.location; - _store.houses[player.name] = utils.locationToJSON(loc); - }, - remove: function(player){ - player = utils.player(player); - delete _store.houses[player.name]; - }, - /* ======================================================================== - social functions - ======================================================================== */ - - /* - list homes which the player can visit - */ - list: function(player){ - var result = []; - for (var ohp in _store.openHouses) - result.push(ohp); - player = utils.player(player); - for (var host in _store.invites){ - var guests = _store.invites[host]; - for (var i = 0;i < guests.length; i++) - if (guests[i] == player.name) - result.push(host); - } - return result; - }, - /* - list who can visit the player's home - */ - ilist: function(player){ - player = utils.player(player); - var result = []; - // if home is public - all players - if (_store.openHouses[player.name]){ - var online = org.bukkit.Bukkit.getOnlinePlayers(); - for (var i = 0;i < online.length; i++) - if (online[i].name != player.name) - result.push(online[i].name); - }else{ - if (_store.invites[player.name]) - result = _store.invites[player.name]; - else - result = []; - } - return result; - }, - /* - Invite a player to the home - */ - invite: function(host, guest){ - host = utils.player(host); - guest = utils.player(guest); - var invitations = []; - if (_store.invites[host.name]) - invitations = _store.invites[host.name]; - invitations.push(guest.name); - _store.invites[host.name] = invitations; - guest.sendMessage(host.name + " has invited you to their home."); - guest.sendMessage("type '/jsp home " + host.name + "' to accept"); - }, - /* - Uninvite someone to the home - */ - uninvite: function(host, guest){ - host = utils.player(host); - guest = utils.player(guest); - var invitations = _store.invites[host.name]; - if (!invitations) - return; - var revisedInvites = []; - for (var i =0;i < invitations.length; i++) - if (invitations[i] != guest.name) - revisedInvites.push(invitations[i]); - _store.invites[host.name] = revisedInvites; - }, - /* - make the player's house public - */ - open: function(player, optionalMsg){ - player = utils.player(player); - _store.openHouses[player.name] = true; - if (typeof optionalMsg != "undefined") - __plugin.server.broadcastMessage(optionalMsg); - }, - /* - make the player's house private - */ - close: function(player){ - player = utils.player(player); - delete _store.openHouses[player.name]; - }, - /* ======================================================================== - admin functions - ======================================================================== */ - listall: function(){ - var result = []; - for (var home in _store.houses) - result.push(home); - return result; - }, - clear: function(player){ - player = utils.player(player); - delete _store.houses[player.name]; - delete _store.openHouses[player.name]; - }, - store: _store -}, true); + /* administration */ + '/jsp home listall : Show all houses (ops only)', + '/jsp home clear : Clears player home location (ops only)' + ]; + }, + /* ======================================================================== + basic functions + ======================================================================== */ + + go: function( guest, host ) { + var loc, + homeLoc; + if ( typeof host == 'undefined' ) { + host = guest; + } + guest = utils.player( guest ); + host = utils.player( host ); + loc = _store.houses[ host.name ]; + if ( !loc ) { + guest.sendMessage( host.name + ' has no home' ); + return; + } + if ( !this._canVisit( guest, host ) ) { + guest.sendMessage( 'You can not visit ' + host.name + "'s home yet" ); + return; + } + homeLoc = utils.locationFromJSON( loc ); + guest.teleport(homeLoc, TeleportCause.PLUGIN); + }, + /* + determine whether a guest is allow visit a host's home + */ + _canVisit: function( guest, host ) { + var invitations, + i; + if ( guest == host ) { + return true; + } + if ( _store.openHouses[ host.name ] ) { + return true; + } + invitations = _store.invites[ host.name ]; + if ( invitations ) { + for ( i = 0; i < invitations.length; i++ ) { + if ( invitations[i] == guest.name ) { + return true; + } + } + } + return false; + }, + + set: function( player ) { + player = utils.player( player ); + var loc = player.location; + _store.houses[player.name] = utils.locationToJSON( loc ); + }, + + remove: function( player ) { + player = utils.player( player ); + delete _store.houses[ player.name ]; + }, + /* ======================================================================== + social functions + ======================================================================== */ + + /* + list homes which the player can visit + */ + list: function( player ) { + var result = [], + ohp, + host, + guests, + i; + for ( ohp in _store.openHouses ) { + result.push(ohp); + } + player = utils.player(player); + for ( host in _store.invites ) { + guests = _store.invites[host]; + for ( i = 0; i < guests.length; i++ ) { + if ( guests[i] == player.name ) { + result.push(host); + } + } + } + return result; + }, + /* + list who can visit the player home + */ + ilist: function( player ) { + var result = [], + onlinePlayers, + i; + player = utils.player( player ); + // if home is public - all players + if ( _store.openHouses[player.name] ) { + onlinePlayers = org.bukkit.Bukkit.getOnlinePlayers(); + for ( i = 0; i < onlinePlayers.length; i++ ) { + if ( onlinePlayers[i].name != player.name) { + result.push( onlinePlayers[i].name ); + } + } + } else { + if ( _store.invites[player.name] ) { + result = _store.invites[ player.name ]; + } else { + result = []; + } + } + return result; + }, + /* + Invite a player to the home + */ + invite: function( host, guest ) { + host = utils.player( host ); + guest = utils.player( guest ); + var invitations = []; + if ( _store.invites[host.name] ) { + invitations = _store.invites[host.name]; + } + invitations.push( guest.name ); + _store.invites[host.name] = invitations; + guest.sendMessage( host.name + ' has invited you to their home.' ); + guest.sendMessage( 'type "/jsp home ' + host.name + '" to accept' ); + }, + /* + Uninvite someone to the home + */ + uninvite: function( host, guest ) { + var invitations, + revisedInvites, + i; + host = utils.player( host ); + guest = utils.player( guest ); + invitations = _store.invites[ host.name ]; + if ( !invitations ) { + return; + } + revisedInvites = []; + for ( i = 0; i < invitations.length; i++ ) { + if ( invitations[i] != guest.name ) { + revisedInvites.push( invitations[i] ); + } + } + _store.invites[host.name] = revisedInvites; + }, + /* + make the player house public + */ + open: function( player, optionalMsg ) { + player = utils.player( player ); + _store.openHouses[ player.name ] = true; + if ( typeof optionalMsg != 'undefined' ) { + __plugin.server.broadcastMessage( optionalMsg ); + } + }, + + /* + make the player house private + */ + close: function( player ) { + player = utils.player( player ); + delete _store.openHouses[ player.name ]; + }, + /* ======================================================================== + admin functions + ======================================================================== */ + listall: function( ) { + var result = []; + for ( var home in _store.houses ) { + result.push(home); + } + return result; + }, + + clear: function( player ) { + player = utils.player( player ); + delete _store.houses[ player.name ]; + delete _store.openHouses[ player.name ]; + }, + store: _store +}, true ); exports.homes = homes; /* - define a set of command options that can be used by players -*/ + define a set of command options that can be used by players + */ var options = { - 'set': function(params, sender){ homes.set(sender); }, - 'delete': function(params, sender ){ homes.remove(sender);}, - 'help': function(params, sender){ sender.sendMessage(homes.help());}, - 'list': function(params, sender){ - var visitable = homes.list(); - if (visitable.length == 0){ - sender.sendMessage("There are no homes to visit"); - return; - }else{ - sender.sendMessage([ - "You can visit any of these " + visitable.length + " homes" - ,visitable.join(", ") - ]); - } - }, - 'ilist': function(params, sender){ - var potentialVisitors = homes.ilist(); - if (potentialVisitors.length == 0) - sender.sendMessage("No one can visit your home"); - else - sender.sendMessage([ - "These " + potentialVisitors.length + "players can visit your home", - potentialVisitors.join(", ")]); - }, - 'invite': function(params,sender){ - if (params.length == 1){ - sender.sendMessage("You must provide a player's name"); - return; - } - var playerName = params[1]; - var guest = utils.player(playerName); - if (!guest) - sender.sendMessage(playerName + " is not here"); - else - homes.invite(sender,guest); - }, - 'uninvite': function(params,sender){ - if (params.length == 1){ - sender.sendMessage("You must provide a player's name"); - return; - } - var playerName = params[1]; - var guest = utils.player(playerName); - if (!guest) - sender.sendMessage(playerName + " is not here"); - else - homes.uninvite(sender,guest); - }, - 'public': function(params,sender){ - homes.open(sender,params.slice(1).join(' ')); - sender.sendMessage("Your home is open to the public"); - }, - 'private': function(params, sender){ - homes.close(sender); - sender.sendMessage("Your home is closed to the public"); - }, - 'listall': function(params, sender){ - if (!sender.isOp()) - sender.sendMessage("Only operators can do this"); - else - sender.sendMessage(homes.listall().join(", ")); - }, - 'clear': function(params,sender){ - if (!sender.isOp()) - sender.sendMessage("Only operators can do this"); - else - homes.clear(params[1], sender); + + 'set': function( params, sender ) { + homes.set( sender ); + }, + + 'delete': function( params, sender ) { + homes.remove( sender ); + }, + + 'help': function( params, sender ) { + sender.sendMessage( homes.help() ); + }, + + 'list': function( params, sender ) { + var visitable = homes.list(); + if ( visitable.length == 0 ) { + sender.sendMessage( 'There are no homes to visit' ); + return; + } else { + sender.sendMessage([ + 'You can visit any of these ' + visitable.length + ' homes' + ,visitable.join(', ') + ]); } + }, + + 'ilist': function( params, sender ) { + var potentialVisitors = homes.ilist(); + if ( potentialVisitors.length == 0 ) { + sender.sendMessage('No one can visit your home'); + } else { + sender.sendMessage([ + 'These ' + potentialVisitors.length + 'players can visit your home', + potentialVisitors.join(', ')]); + } + }, + + 'invite': function( params, sender ) { + if ( params.length == 1 ) { + sender.sendMessage( 'You must provide a player name' ); + return; + } + var playerName = params[1]; + var guest = utils.player( playerName ); + if ( !guest ) { + sender.sendMessage( playerName + ' is not here' ); + } else { + homes.invite( sender, guest ); + } + }, + + 'uninvite': function( params, sender ) { + if ( params.length == 1 ) { + sender.sendMessage( 'You must provide a player name' ); + return; + } + var playerName = params[1]; + var guest = utils.player( playerName ); + if ( !guest ) { + sender.sendMessage( playerName + ' is not here' ); + } else { + homes.uninvite( sender, guest ); + } + }, + + 'public': function( params, sender ) { + homes.open( sender, params.slice( 1 ).join(' ') ); + sender.sendMessage( 'Your home is open to the public' ); + }, + + 'private': function( params, sender ) { + homes.close( sender ); + sender.sendMessage( 'Your home is closed to the public' ); + }, + + 'listall': function( params, sender ) { + if ( !sender.isOp() ) { + sender.sendMessage( 'Only operators can do this' ); + } else { + sender.sendMessage( homes.listall().join(', ') ); + } + }, + + 'clear': function( params, sender ) { + if ( !sender.isOp() ) { + sender.sendMessage( 'Only operators can do this' ); + } else { + homes.clear( params[1], sender ); + } + } }; + var optionList = []; -for (var o in options) - optionList.push(o); +for ( var o in options ) { + optionList.push(o); +} + /* - Expose a set of commands that players can use at the in-game command prompt -*/ -command("home", function ( params , sender) { - if (params.length == 0){ - homes.go(sender,sender); - return; + Expose a set of commands that players can use at the in-game command prompt + */ +command( 'home', function ( params , sender) { + var option, + host; + if ( params.length == 0 ) { + homes.go( sender, sender ); + return; + } + option = options[ params[0] ]; + if ( option ) { + option( params, sender ); + } else { + host = utils.player( params[0] ); + if ( !host ) { + sender.sendMessage( params[0] + ' is not here' ); + } else { + homes.go( sender, host ); } - var option = options[params[0]]; - if (option) - option(params,sender); - else{ - var host = utils.player(params[0]); - if (!host) - sender.sendMessage(params[0] + " is not here"); - else - homes.go(sender,host); - } -},optionList); + } +}, optionList ); diff --git a/src/main/js/plugins/minigames/NumberGuess.js b/src/main/js/plugins/minigames/NumberGuess.js index 5dbac98..6fb0938 100644 --- a/src/main/js/plugins/minigames/NumberGuess.js +++ b/src/main/js/plugins/minigames/NumberGuess.js @@ -15,64 +15,73 @@ Once the game begins, guess a number by typing the `/` character followed by a number between 1 and 10. ***/ +var Prompt = org.bukkit.conversations.Prompt, + ConversationFactory = org.bukkit.conversations.ConversationFactory, + ConversationPrefix = org.bukkit.conversations.ConversationPrefix; -var sb = function(cmd){ - org.bukkit.Bukkit.dispatchCommand(server.consoleSender, 'scoreboard ' + cmd) +var sb = function( cmd ) { + org.bukkit.Bukkit.dispatchCommand( server.consoleSender, 'scoreboard ' + cmd ) ; }; exports.Game_NumberGuess = { - start: function(sender) { + start: function( sender ) { - var guesses = 0; + var guesses = 0; - sb('objectives add NumberGuess dummy Guesses'); - sb('players set ' + sender.name + ' NumberGuess ' + guesses); - sb('objectives setdisplay sidebar NumberGuess'); - - var Prompt = org.bukkit.conversations.Prompt; - var ConversationFactory = org.bukkit.conversations.ConversationFactory; - var ConversationPrefix = org.bukkit.conversations.ConversationPrefix; + sb( 'objectives add NumberGuess dummy Guesses' ); + sb( 'players set ' + sender.name + ' NumberGuess ' + guesses ); + sb( 'objectives setdisplay sidebar NumberGuess' ); - var number = Math.ceil(Math.random() * 10); - - var prompt = new Prompt() - { - getPromptText: function(ctx){ - var hint = ""; - var h = ctx.getSessionData("hint"); - if (h){ - hint = h; - } - return hint + "Think of a number between 1 and 10"; - }, - acceptInput: function(ctx, s) - { - s = s.replace(/^[^0-9]+/,""); // strip leading non-numeric characters (e.g. '/' ) - s = parseInt(s); - if (s == number){ - setTimeout(function(){ - ctx.forWhom.sendRawMessage("You guessed Correct!"); - sb('objectives remove NumberGuess'); - },100); - return null; - }else{ - if (s < number) - ctx.setSessionData("hint","too low\n"); - if (s > number) - ctx.setSessionData("hint","too high\n"); - guesses++; - sb('players set ' + sender.name + ' NumberGuess ' + guesses); - - return prompt; - } - }, - blocksForInput: function(ctx){ return true; } - }; - var cf = new ConversationFactory(__plugin); - var conv = cf.withModality(true) - .withFirstPrompt(prompt) - .withPrefix(new ConversationPrefix(){ getPrefix: function(ctx){ return "[1-10] ";} }) - .buildConversation(sender); - conv.begin(); - } + var number = Math.ceil( Math.random() * 10 ); + + var prompt = new Prompt( ) { + + getPromptText: function( ctx ) { + var hint = ''; + var h = ctx.getSessionData( 'hint' ); + if ( h ) { + hint = h; + } + return hint + 'Think of a number between 1 and 10'; + }, + + acceptInput: function( ctx, s ) { + s = s.replace( /^[^0-9]+/, '' ); // strip leading non-numeric characters (e.g. '/' ) + s = parseInt( s ); + if ( s == number ) { + setTimeout(function( ) { + ctx.forWhom.sendRawMessage( 'You guessed Correct!' ); + sb( 'objectives remove NumberGuess' ); + }, 100 ); + return null; + } else { + if ( s < number ) { + ctx.setSessionData( 'hint', 'too low\n' ); + } + if ( s > number ) { + ctx.setSessionData( 'hint', 'too high\n' ); + } + guesses++; + sb( 'players set ' + sender.name + ' NumberGuess ' + guesses ); + + return prompt; + } + }, + + blocksForInput: function( ctx ) { + return true; + } + }; + var convPrefix = new ConversationPrefix( ) { + getPrefix: function( ctx ) { + return '[1-10] '; + } + }; + new ConversationFactory( __plugin ) + .withModality( true ) + .withFirstPrompt( prompt ) + .withPrefix( convPrefix ) + .buildConversation( sender ) + .begin( ); + } }; diff --git a/src/main/js/plugins/minigames/SnowballFight.js b/src/main/js/plugins/minigames/SnowballFight.js index d7ea67a..afa17a9 100644 --- a/src/main/js/plugins/minigames/SnowballFight.js +++ b/src/main/js/plugins/minigames/SnowballFight.js @@ -42,141 +42,170 @@ cover to make the game more fun. ***/ -var _startGame = function(gameState){ - // don't let game start if already in progress (wait for game to finish) - if (gameState.inProgress){ - return; - } - gameState.inProgress = true; - // reset timer - gameState.duration = gameState.originalDuration; - // put all players in survival mode and give them each 200 snowballs - // 64 snowballs for every 30 seconds should be more than enough - for (var i = 10;i < gameState.duration;i+=10) - gameState.ammo.push(gameState.ammo[0]); - - for (var teamName in gameState.teams) - { - gameState.teamScores[teamName] = 0; - var team = gameState.teams[teamName]; - for (var i = 0;i < team.length;i++) { - var player = server.getPlayer(team[i]); - gameState.savedModes[player.name] = player.gameMode; - player.gameMode = org.bukkit.GameMode.SURVIVAL; - player.inventory.addItem(gameState.ammo); - } - } -}; -/* - end the game -*/ -var _endGame = function(gameState){ - var scores = []; - - var leaderBoard = []; - for (var tn in gameState.teamScores){ - leaderBoard.push([tn,gameState.teamScores[tn]]); - } - leaderBoard.sort(function(a,b){ return b[1] - a[1];}); - - for (var i = 0;i < leaderBoard.length; i++){ - scores.push("Team " + leaderBoard[i][0] + " scored " + leaderBoard[i][1]); - } - - for (var teamName in gameState.teams) { - var team = gameState.teams[teamName]; - for (var i = 0;i < team.length;i++) { - // restore player's previous game mode and take back snowballs - var player = server.getPlayer(team[i]); - player.gameMode = gameState.savedModes[player.name]; - player.inventory.removeItem(gameState.ammo); - player.sendMessage("GAME OVER."); - player.sendMessage(scores); - } - } - var handlerList = org.bukkit.event.entity.EntityDamageByEntityEvent.getHandlerList(); - handlerList.unregister(gameState.listener); - gameState.inProgress = false; -}; -/* - get the team the player belongs to -*/ -var _getTeam = function(player,pteams) { - for (var teamName in pteams) { - var team = pteams[teamName]; - for (var i = 0;i < team.length; i++) - if (team[i] == player.name) - return teamName; - } - return null; -}; -/* - construct a new game -*/ -var createGame = function(duration, teams) { - - var _snowBalls = new org.bukkit.inventory.ItemStack(org.bukkit.Material.SNOW_BALL, 64); +var GameMode = org.bukkit.GameMode, + EntityDamageByEntityEvent = org.bukkit.event.entity.EntityDamageByEntityEvent, + ItemStack = org.bukkit.inventory.ItemStack, + Material = org.bukkit.Material, + Snowball = org.bukkit.entity.Snowball; - var _gameState = { - teams: teams, - duration: duration, - originalDuration: duration, - inProgress: false, - teamScores: {}, - listener: null, - savedModes: {}, - ammo: [_snowBalls] - }; - if (typeof duration == "undefined"){ - duration = 60; +var _startGame = function( gameState ) { + var i, + teamName, + team, + player; + + // don't let game start if already in progress (wait for game to finish) + if ( gameState.inProgress ) { + return; + } + gameState.inProgress = true; + // reset timer + gameState.duration = gameState.originalDuration; + // put all players in survival mode and give them each 200 snowballs + // 64 snowballs for every 30 seconds should be more than enough + for ( i = 10; i < gameState.duration; i += 10 ) { + gameState.ammo.push( gameState.ammo[ 0 ] ); + } + + for ( teamName in gameState.teams ) { + gameState.teamScores[teamName] = 0; + team = gameState.teams[ teamName ]; + for ( i = 0; i < team.length; i++ ) { + player = server.getPlayer( team[i] ); + gameState.savedModes[ player.name ] = player.gameMode; + player.gameMode = GameMode.SURVIVAL; + player.inventory.addItem( gameState.ammo ); } - if (typeof teams == "undefined"){ - /* - wph 20130511 use all players - */ - teams = []; - var players = server.onlinePlayers; - for (var i = 0;i < players.length; i++){ - teams.push(players[i].name); - } + } +}; +/* + end the game + */ +var _endGame = function( gameState ) { + var scores = [], + leaderBoard = [], + tn, + i, + teamName, + team, + player, + handlerList; + + leaderBoard = []; + for ( tn in gameState.teamScores){ + leaderBoard.push([tn,gameState.teamScores[tn]]); + } + leaderBoard.sort(function(a,b){ return b[1] - a[1];}); + + for ( i = 0; i < leaderBoard.length; i++ ) { + scores.push( 'Team ' + leaderBoard[i][0] + ' scored ' + leaderBoard[i][1] ); + } + + for ( teamName in gameState.teams ) { + team = gameState.teams[teamName]; + for ( i = 0; i < team.length; i++ ) { + // restore player's previous game mode and take back snowballs + player = server.getPlayer( team[i] ); + player.gameMode = gameState.savedModes[ player.name ]; + player.inventory.removeItem( gameState.ammo ); + player.sendMessage( 'GAME OVER.' ); + player.sendMessage( scores ); } - // - // allow for teams param to be either {red:['player1','player2'],blue:['player3']} or - // ['player1','player2','player3'] if all players are against each other (no teams) - // - if (teams instanceof Array){ - _gameState.teams = {}; - for (var i = 0;i < teams.length; i++) - _gameState.teams[teams[i]] = [teams[i]]; + } + handlerList = EntityDamageByEntityEvent.getHandlerList(); + handlerList.unregister( gameState.listener ); + gameState.inProgress = false; +}; +/* + get the team the player belongs to + */ +var _getTeam = function( player, pteams ) { + var teamName, + team, + i; + for ( teamName in pteams ) { + team = pteams[ teamName ]; + for ( i = 0; i < team.length; i++ ) { + if ( team[i] == player.name ) { + return teamName; + } } + } + return null; +}; +/* + construct a new game + */ +var createGame = function( duration, teams ) { + var players, + i, + _snowBalls = new ItemStack( Material.SNOW_BALL, 64 ); + + var _gameState = { + teams: teams, + duration: duration, + originalDuration: duration, + inProgress: false, + teamScores: {}, + listener: null, + savedModes: {}, + ammo: [ _snowBalls ] + }; + if ( typeof duration == 'undefined' ) { + duration = 60; + } + if ( typeof teams == 'undefined' ) { /* - this function is called every time a player is damaged by another entity/player - */ - var _onSnowballHit = function(l,event){ - var snowball = event.damager; - if (!snowball || !(snowball instanceof org.bukkit.entity.Snowball)) - return; - var throwersTeam = _getTeam(snowball.shooter,_gameState.teams); - var damageeTeam = _getTeam(event.entity,_gameState.teams); - if (!throwersTeam || !damageeTeam) - return; // thrower/damagee wasn't in game - if (throwersTeam != damageeTeam) - _gameState.teamScores[throwersTeam]++; - else - _gameState.teamScores[throwersTeam]--; - }; - - return { - start: function() { - _startGame(_gameState); - _gameState.listener = events.on('entity.EntityDamageByEntityEvent',_onSnowballHit); - new java.lang.Thread(function(){ - while (_gameState.duration--) - java.lang.Thread.sleep(1000); // sleep 1,000 millisecs (1 second) - _endGame(_gameState); - }).start(); - } - }; + wph 20130511 use all players + */ + teams = []; + players = server.onlinePlayers; + for ( i = 0; i < players.length; i++ ) { + teams.push( players[i].name ); + } + } + // + // allow for teams param to be either {red:['player1','player2'],blue:['player3']} or + // ['player1','player2','player3'] if all players are against each other (no teams) + // + if ( teams instanceof Array ) { + _gameState.teams = {}; + for ( i = 0;i < teams.length; i++ ) { + _gameState.teams[ teams[i] ] = [ teams[i] ]; + } + } + /* + this function is called every time a player is damaged by another entity/player + */ + var _onSnowballHit = function( l, event ) { + var snowball = event.damager; + if ( !snowball || !( snowball instanceof Snowball ) ) { + return; + } + var throwersTeam = _getTeam( snowball.shooter, _gameState.teams ); + var damageeTeam = _getTeam( event.entity, _gameState.teams); + if ( !throwersTeam || !damageeTeam ) { + return; // thrower/damagee wasn't in game + } + if ( throwersTeam != damageeTeam ) { + _gameState.teamScores[ throwersTeam ]++; + } else { + _gameState.teamScores[ throwersTeam ]--; + } + }; + + return { + start: function( ) { + _startGame( _gameState ); + _gameState.listener = events.on('entity.EntityDamageByEntityEvent',_onSnowballHit); + new java.lang.Thread( function( ) { + while ( _gameState.duration-- ) { + java.lang.Thread.sleep( 1000 ); // sleep 1,000 millisecs (1 second) + } + _endGame(_gameState); + } ).start( ); + } + }; }; exports.Game_SnowballFight = createGame; diff --git a/src/main/js/plugins/minigames/cow-clicker.js b/src/main/js/plugins/minigames/cow-clicker.js index 16615ae..d03027d 100644 --- a/src/main/js/plugins/minigames/cow-clicker.js +++ b/src/main/js/plugins/minigames/cow-clicker.js @@ -41,139 +41,161 @@ your own mini-game... ***/ -var store = {}; - -var scoreboardConfig = { - cowclicker: {SIDEBAR: 'Cows Clicked'} -}; +var store = {}, + Bukkit = org.bukkit.Bukkit, + Cow = org.bukkit.entity.Cow, + Sound = org.bukkit.Sound, + OfflinePlayer = org.bukkit.OfflinePlayer, + scoreboardConfig = { + cowclicker: { + SIDEBAR: 'Cows Clicked' + } + }; var scoreboard = require('minigames/scoreboard')(scoreboardConfig); -var _onPlayerInteract = function(listener, event){ - var player = event.player; - var Bukkit = org.bukkit.Bukkit; +var _onPlayerInteract = function( listener, event ) { + var player = event.player, + clickedEntity = event.rightClicked, + loc = clickedEntity.location; + + if ( !store[ player.name ] ) { + return; + } + + var sound = function( snd, vol, pitch ) { + loc.world.playSound( loc, snd, vol, pitch ); + }; + + if ( clickedEntity instanceof Cow) { + store[ player.name ].score++; + scoreboard.update( 'cowclicker', player, store[ player.name ].score ); - if (!store[player.name]) - return; - - var clickedEntity = event.rightClicked; - var loc = clickedEntity.location; - var sound = function(snd,vol,pitch){ - loc.world.playSound(loc,snd,vol,pitch); - }; - if (clickedEntity instanceof org.bukkit.entity.Cow) - { - store[player.name].score++; - scoreboard.update('cowclicker', player, store[player.name].score); - - Bukkit.dispatchCommand(player, 'me clicked a cow!'); - sound(org.bukkit.Sound.CLICK,1,1); - setTimeout(function(){ - sound(org.bukkit.Sound.COW_HURT,10,0.85) - },200); + Bukkit.dispatchCommand( player, 'me clicked a cow!' ); + sound( Sound.CLICK, 1, 1 ); + setTimeout( function( ) { + sound( Sound.COW_HURT, 10, 0.85 ) ; + }, 200 ); + } +}; +var _onPlayerQuit = function( listener, event ) { + _removePlayer( event.player ); +}; +var _onPlayerJoin = function( listener, event ) { + var gamePlayer = store[event.player.name]; + if ( gamePlayer ) { + _addPlayer( event.player, gamePlayer.score ); + } +}; + +var _startGame = function( ) { + var p, + player; + if ( config.verbose ) { + console.log('Staring game: Cow Clicker'); + } + + events.on( 'player.PlayerQuitEvent', _onPlayerQuit ); + events.on( 'player.PlayerJoinEvent', _onPlayerJoin ); + events.on( 'player.PlayerInteractEntityEvent', _onPlayerInteract ); + + scoreboard.start(); + + store = persist( 'cowclicker', store ); + for ( p in store ) { + player = server.getPlayer( p ); + if ( player ) { + /* + only add online players + */ + var score = store[p].score; + _addPlayer( player, score ); } -}; -var _onPlayerQuit = function(listener, event){ - _removePlayer(event.player) -}; -var _onPlayerJoin = function(listener, event){ - var gamePlayer = store[event.player.name]; - if (gamePlayer) - _addPlayer(event.player, gamePlayer.score); + } }; -var _startGame = function(){ - if (config.verbose) - console.log('Staring game: Cow Clicker'); - - events.on('player.PlayerQuitEvent', _onPlayerQuit); - events.on('player.PlayerJoinEvent', _onPlayerJoin); - events.on('player.PlayerInteractEntityEvent', _onPlayerInteract); +var _addPlayer = function( player, score ) { + if ( config.verbose ) { + console.log( 'Adding player %s to Cow Clicker game', player ); + } + if ( typeof score == 'undefined' ) { + score = 0; + } + store[ player.name ] = { score: score }; + scoreboard.update( 'cowclicker', player, store[ player.name ].score); + + player.sendMessage( 'Go forth and click some cows!' ); +}; - scoreboard.start(); +var _removePlayer = function( player, notify ) { - store = persist('cowclicker',store); - for (var p in store){ - var player = server.getPlayer(p); - if (player){ - /* - only add online players - */ - var score = store[p].score; - _addPlayer(player, score); - } + if ( player instanceof OfflinePlayer && player.player ) { + player = player.player; + } + + if ( !store[player.name] ) { + return; + } + if ( config.verbose ) { + console.log( 'Removing player %s from Cow Clicker', player ); + } + + var playerScore = store[ player.name ].score; + + scoreboard.restore( player ); + + delete store[ player.name ]; + if ( notify && player ) { + player.sendMessage( 'You clicked ' + playerScore + ' cows! ' + + 'You must be tired after all that clicking.' ); + } +}; + +var _removeAllPlayers = function( notify ) { + if ( typeof notify == 'undefined' ) { + notify = false; + } + for ( var p in store ) { + var player = server.getOfflinePlayer( p ); + if ( player ) { + _removePlayer( player, notify ); } -}; -var _addPlayer = function(player,score){ - if (config.verbose) - console.log('Adding player %s to Cow Clicker game',player); - - if (typeof score == 'undefined') - score = 0; - store[player.name] = {score: score}; - scoreboard.update('cowclicker', player,store[player.name].score); - - player.sendMessage('Go forth and click some cows!'); + delete store[p]; + } }; -var _removePlayer = function(player,notify){ - - if (player instanceof org.bukkit.OfflinePlayer && player.player) - player = player.player; - - if (!store[player.name]) - return; - if (config.verbose) - console.log('Removing player %s from Cow Clicker', player); - - var playerScore = store[player.name].score; - - scoreboard.restore(player); - - delete store[player.name]; - if (notify && player){ - player.sendMessage('You clicked ' + playerScore + ' cows! ' + - 'You must be tired after all that clicking.'); - } -}; -var _removeAllPlayers = function(notify){ - if (typeof notify == 'undefined') - notify = false; - for (var p in store){ - var player = server.getOfflinePlayer(p); - if (player) - _removePlayer(player,notify); - delete store[p]; - } -} -var _stopGame = function(removePlayers){ - if (typeof removePlayers == 'undefined') - removePlayers = true; - if (config.verbose) - console.log('Stopping game: Cow Clicker'); - scoreboard.stop(); - if (!removePlayers) - return; - _removeAllPlayers(false); - persist('cowclicker',store.pers,'w'); +var _stopGame = function( removePlayers ) { + if ( typeof removePlayers == 'undefined' ) { + removePlayers = true; + } + if ( config.verbose ) { + console.log( 'Stopping game: Cow Clicker' ); + } + scoreboard.stop(); + if ( !removePlayers ) { + return; + } + _removeAllPlayers( false ); + persist( 'cowclicker', store.pers, 'w' ); }; /* - start the game automatically when this module is loaded. -*/ + start the game automatically when this module is loaded. + */ _startGame(); /* - players can join and leave the game by typing `jsp cowclicker` -*/ -command('cowclicker', function(params, sender){ - if (!store[sender.name]) - _addPlayer(sender); - else - _removePlayer(sender); + players can join and leave the game by typing `jsp cowclicker` + */ +command( 'cowclicker', function( params, sender ) { + if ( !store[sender.name] ) { + _addPlayer( sender ); + } else { + _removePlayer( sender ); + } }); /* - stop the game when ScriptCraft is unloaded. -*/ -addUnloadHandler(function(){ - _stopGame(false); -}); + stop the game when ScriptCraft is unloaded. + */ +addUnloadHandler( function( ) { + _stopGame( false ); +} ); diff --git a/src/main/js/plugins/spawn.js b/src/main/js/plugins/spawn.js index 975de56..97e4c4c 100644 --- a/src/main/js/plugins/spawn.js +++ b/src/main/js/plugins/spawn.js @@ -16,20 +16,21 @@ press TAB. Visit for a list of possible entities (creatures) which can be spawned. ***/ -var entities = []; -var Types = org.bukkit.entity.EntityType -for (var t in Types){ - if (Types[t] && Types[t].ordinal){ - entities.push(t); - } +var entities = [], + EntityType = org.bukkit.entity.EntityType; + +for ( var t in EntityType ) { + if ( EntityType[t] && EntityType[t].ordinal ) { + entities.push(t); + } } -command('spawn', function(parameters, sender){ - if (!sender.op){ - sender.sendMessage('Only operators can perform this command'); - return; - } - var location = sender.location; - var world = location.world; - var type = ('' + parameters[0]).toUpperCase(); - world.spawnEntity(location, org.bukkit.entity.EntityType[type]); -},entities); +command( 'spawn', function( parameters, sender ) { + if ( !sender.op ) { + sender.sendMessage( 'Only operators can perform this command' ); + return; + } + var location = sender.location; + var world = location.world; + var type = ('' + parameters[0]).toUpperCase(); + world.spawnEntity( location, EntityType[type] ); +}, entities ); diff --git a/src/main/resources/boot.js b/src/main/resources/boot.js index 5cdd1d6..aed8412 100644 --- a/src/main/resources/boot.js +++ b/src/main/resources/boot.js @@ -1,76 +1,87 @@ /* - This file is the first and only file executed directly from the Java Plugin. -*/ + This file is the first and only file executed directly from the Java Plugin. + */ var __scboot = null; (function(){ - var File = java.io.File - ,FileReader = java.io.FileReader - ,FileOutputStream = java.io.FileOutputStream - ,ZipInputStream = java.util.zip.ZipInputStream - ,jsPlugins = new File('plugins/scriptcraft') - ,initScript = 'lib/scriptcraft.js'; + var File = java.io.File, + FileReader = java.io.FileReader, + FileOutputStream = java.io.FileOutputStream, + ZipInputStream = java.util.zip.ZipInputStream, + jsPlugins = new File('plugins/scriptcraft'), + initScript = 'lib/scriptcraft.js'; - var unzip = function(path, logger, plugin) { - var zis = new ZipInputStream(plugin.getResource(path)) - , entry , reason = null, unzipFile = false, zTime = 0 - , fTime = 0, fout = null, c, newFile; + var unzip = function(path, logger, plugin) { + var zis = new ZipInputStream(plugin.getResource(path)), + entry, + reason = null, + unzipFile = false, + zTime = 0, + fTime = 0, + fout = null, + c, + newFile; - while ( ( entry = zis.nextEntry ) != null ) { + while ( ( entry = zis.nextEntry ) != null ) { - newFile = new File(jsPlugins, entry.name); - if (entry.isDirectory()){ - newFile.mkdirs(); - zis.closeEntry(); - continue; - } - reason = null; - zTime = entry.time; - unzipFile = false; - if (!newFile.exists()) { - reason = 'NE'; - unzipFile = true; - }else{ - fTime = newFile.lastModified(); - if (zTime > fTime) { - reason = ((zTime - fTime) / 3600000) + "h"; - unzipFile = true; - } - } - if (unzipFile) { - logger.info('Unzipping ' + newFile.canonicalPath + ' (' + reason + ')' ); - fout = new FileOutputStream(newFile); - for (c = zis.read(); c != -1; c = zis.read()) { - fout.write(c); - } - fout.close(); - } - zis.closeEntry(); + newFile = new File(jsPlugins, entry.name); + if (entry.isDirectory()){ + newFile.mkdirs(); + zis.closeEntry(); + continue; + } + reason = null; + zTime = entry.time; + unzipFile = false; + if (!newFile.exists()) { + reason = 'NE'; + unzipFile = true; + }else{ + fTime = newFile.lastModified(); + if (zTime > fTime) { + reason = ((zTime - fTime) / 3600000) + "h"; + unzipFile = true; } - zis.close(); - }; + } + if (unzipFile) { + logger.info('Unzipping ' + newFile.canonicalPath + ' (' + reason + ')' ); + fout = new FileOutputStream(newFile); + for (c = zis.read(); c != -1; c = zis.read()) { + fout.write(c); + } + fout.close(); + } + zis.closeEntry(); + } + zis.close(); + }; + /* + Called from Java plugin + */ + __scboot = function ( plugin, engine ) + { + var logger = plugin.logger, + cfg = plugin.config, + cfgName, + initScriptFile = new File(jsPlugins,initScript), + zips = ['lib','plugins','modules'], + i = 0, + len = zips.length; + + if (!jsPlugins.exists()){ + logger.info('Directory ' + jsPlugins.canonicalPath + ' does not exist.'); + logger.info('Initializing ' + jsPlugins.canonicalPath + ' directory with contents from plugin archive.'); + jsPlugins.mkdirs(); + } + + for (i = 0; i < len;i++){ + cfgName = 'extract-js.' + zips[i]; + if (cfg.getBoolean(cfgName)){ + unzip( zips[i] + '.zip',logger,plugin); + } + } + plugin.saveDefaultConfig(); - __scboot = function ( plugin, engine ) - { - var logger = plugin.logger, cfg = plugin.config - ,cfgName, initScriptFile = new File(jsPlugins,initScript) - ,zips = ['lib','plugins','modules'] - ,i = 0 ,len = zips.length; - - if (!jsPlugins.exists()){ - logger.info('Directory ' + jsPlugins.canonicalPath + ' does not exist.'); - logger.info('Initializing ' + jsPlugins.canonicalPath + ' directory with contents from plugin archive.'); - jsPlugins.mkdirs(); - } - - for (i = 0; i < len;i++){ - cfgName = 'extract-js.' + zips[i]; - if (cfg.getBoolean(cfgName)){ - unzip( zips[i] + '.zip',logger,plugin); - } - } - plugin.saveDefaultConfig(); - - engine.eval(new FileReader(initScriptFile)); - __onEnable(engine, plugin, initScriptFile); - }; + engine.eval(new FileReader(initScriptFile)); + __onEnable(engine, plugin, initScriptFile); + }; })(); From 7ab34980e41a07cee7adfcfc607c91e130e08d7e Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Wed, 29 Jan 2014 20:11:47 +0000 Subject: [PATCH 111/456] fix issue #113 --- src/main/js/lib/require.js | 160 ++++++++++++++++++++----------------- 1 file changed, 85 insertions(+), 75 deletions(-) diff --git a/src/main/js/lib/require.js b/src/main/js/lib/require.js index 30d0c79..6c85142 100644 --- a/src/main/js/lib/require.js +++ b/src/main/js/lib/require.js @@ -56,7 +56,9 @@ module specification, the '.js' suffix is optional. ***/ (function ( rootDir, modulePaths, hooks ) { - var File = java.io.File; + var File = java.io.File, + FileReader = java.io.FileReader, + BufferedReader = java.io.BufferedReader; var readModuleFromDirectory = function( dir ) { @@ -170,82 +172,90 @@ When resolving module names to file paths, ScriptCraft uses the following rules. } return null; }; - /* - wph 20131215 Experimental - */ - var _loadedModules = {}; - var _format = java.lang.String.format; - var _require = function(parentFile, path) - { - var file = resolveModuleToFile(path, parentFile); - if (!file){ - var errMsg = '' + _format("require() failed to find matching file for module '%s' " + - "in working directory '%s' ", [path, parentFile.canonicalPath]); - if (! ( (''+path).match(/^\./) )){ - errMsg = errMsg + ' and not found in paths ' + JSON.stringify(modulePaths); - } - throw errMsg; - } - var canonizedFilename = _canonize(file); - - var moduleInfo = _loadedModules[canonizedFilename]; - if (moduleInfo){ - return moduleInfo; - } - if (hooks) - hooks.loading(canonizedFilename); - var reader = new java.io.FileReader(file); - var br = new java.io.BufferedReader(reader); - var code = ""; - var r = null; - while ((r = br.readLine()) !== null) - code += r + "\n"; + /* + wph 20131215 Experimental + */ + var _loadedModules = {}; + var _format = java.lang.String.format; + /* + require() function implementation + */ + var _require = function( parentFile, path ) { + var file, + canonizedFilename, + moduleInfo, + buffered, + head = '(function(exports,module,require,__filename,__dirname){ ', + code = '', + line = null; + + file = resolveModuleToFile(path, parentFile); + if ( !file ) { + var errMsg = '' + _format("require() failed to find matching file for module '%s' " + + "in working directory '%s' ", [path, parentFile.canonicalPath]); + if (! ( (''+path).match( /^\./ ) ) ) { + errMsg = errMsg + ' and not found in paths ' + JSON.stringify(modulePaths); + } + throw errMsg; + } + canonizedFilename = _canonize(file); + + moduleInfo = _loadedModules[canonizedFilename]; + if ( moduleInfo ) { + return moduleInfo; + } + if ( hooks ) { + hooks.loading( canonizedFilename ); + } + buffered = new BufferedReader(new FileReader(file)); + while ( (line = buffered.readLine()) !== null ) { + code += line + '\n'; + } + buffered.close(); // close the stream so there's no file locks - var head = "(function(exports,module,require,__filename,__dirname){ "; - - moduleInfo = { - loaded: false, - id: canonizedFilename, - exports: {}, - require: _requireClosure(file.parentFile) - }; - var tail = "})"; - code = head + code + tail; - - _loadedModules[canonizedFilename] = moduleInfo; - var compiledWrapper = null; - try { - compiledWrapper = eval(code); - }catch (e){ - throw "Error:" + e + " while evaluating module " + canonizedFilename; - } - var __dirname = "" + file.parentFile.canonicalPath; - var parameters = [ - moduleInfo.exports, /* exports */ - moduleInfo, /* module */ - moduleInfo.require, /* require */ - canonizedFilename, /* __filename */ - __dirname /* __dirname */ - ]; - try { - compiledWrapper - .apply(moduleInfo.exports, /* this */ - parameters); - } catch (e){ - throw 'Error:' + e + ' while executing module ' + canonizedFilename; - } - if (hooks) - hooks.loaded(canonizedFilename); - moduleInfo.loaded = true; - return moduleInfo; + moduleInfo = { + loaded: false, + id: canonizedFilename, + exports: {}, + require: _requireClosure(file.parentFile) }; + var tail = '})'; + code = head + code + tail; - var _requireClosure = function(parent){ - return function(path){ - var module = _require(parent, path); - return module.exports; - }; + _loadedModules[canonizedFilename] = moduleInfo; + var compiledWrapper = null; + try { + compiledWrapper = eval(code); + } catch (e) { + throw 'Error:' + e + ' while evaluating module ' + canonizedFilename; + } + var __dirname = '' + file.parentFile.canonicalPath; + var parameters = [ + moduleInfo.exports, /* exports */ + moduleInfo, /* module */ + moduleInfo.require, /* require */ + canonizedFilename, /* __filename */ + __dirname /* __dirname */ + ]; + try { + compiledWrapper + .apply(moduleInfo.exports, /* this */ + parameters); + } catch (e) { + throw 'Error:' + e + ' while executing module ' + canonizedFilename; + } + if ( hooks ) { + hooks.loaded( canonizedFilename ); + } + moduleInfo.loaded = true; + return moduleInfo; + }; + + var _requireClosure = function( parent ) { + return function( path ) { + var module = _require( parent, path ); + return module.exports; }; - return _requireClosure(new java.io.File(rootDir)); + }; + return _requireClosure( new java.io.File(rootDir) ); }) - From 621245adacf0bd28e6a0dcbe7d68b9b5d5cf11a3 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Wed, 29 Jan 2014 23:11:40 +0000 Subject: [PATCH 112/456] Removed underscore from source (it's downloaded) and fixed spawn plugin. --- build.xml | 28 +- src/main/js/modules/underscore/package.json | 4 - src/main/js/modules/underscore/underscore.js | 1314 ------------------ src/main/js/plugins/spawn.js | 13 +- 4 files changed, 32 insertions(+), 1327 deletions(-) delete mode 100644 src/main/js/modules/underscore/package.json delete mode 100644 src/main/js/modules/underscore/underscore.js diff --git a/build.xml b/build.xml index 7deaa2c..453775d 100644 --- a/build.xml +++ b/build.xml @@ -17,12 +17,14 @@ + + @@ -131,26 +133,39 @@ Walter Higgins - + + + + + + + - + + + + + + - + @@ -169,6 +184,7 @@ Walter Higgins + diff --git a/src/main/js/modules/underscore/package.json b/src/main/js/modules/underscore/package.json deleted file mode 100644 index 8c4e2b2..0000000 --- a/src/main/js/modules/underscore/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - name: 'underscore', - main: './underscore.js' -} diff --git a/src/main/js/modules/underscore/underscore.js b/src/main/js/modules/underscore/underscore.js deleted file mode 100644 index 6440a36..0000000 --- a/src/main/js/modules/underscore/underscore.js +++ /dev/null @@ -1,1314 +0,0 @@ -// Underscore.js 1.5.2 -// http://underscorejs.org -// (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors -// Underscore may be freely distributed under the MIT license. - -(function() { - - // Baseline setup - // -------------- - - // Establish the root object, `window` in the browser, or `exports` on the server. - var root = this; - - // Save the previous value of the `_` variable. - var previousUnderscore = root._; - - // Establish the object that gets returned to break out of a loop iteration. - var breaker = {}; - - // Save bytes in the minified (but not gzipped) version: - var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; - - //use the faster Date.now if available. - var getTime = (Date.now || function() { - return new Date().getTime(); - }); - - // Create quick reference variables for speed access to core prototypes. - var - push = ArrayProto.push, - slice = ArrayProto.slice, - concat = ArrayProto.concat, - toString = ObjProto.toString, - hasOwnProperty = ObjProto.hasOwnProperty; - - // All **ECMAScript 5** native function implementations that we hope to use - // are declared here. - var - nativeForEach = ArrayProto.forEach, - nativeMap = ArrayProto.map, - nativeReduce = ArrayProto.reduce, - nativeReduceRight = ArrayProto.reduceRight, - nativeFilter = ArrayProto.filter, - nativeEvery = ArrayProto.every, - nativeSome = ArrayProto.some, - nativeIndexOf = ArrayProto.indexOf, - nativeLastIndexOf = ArrayProto.lastIndexOf, - nativeIsArray = Array.isArray, - nativeKeys = Object.keys, - nativeBind = FuncProto.bind; - - // Create a safe reference to the Underscore object for use below. - var _ = function(obj) { - if (obj instanceof _) return obj; - if (!(this instanceof _)) return new _(obj); - this._wrapped = obj; - }; - - // Export the Underscore object for **Node.js**, with - // backwards-compatibility for the old `require()` API. If we're in - // the browser, add `_` as a global object via a string identifier, - // for Closure Compiler "advanced" mode. - if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) { - exports = module.exports = _; - } - exports._ = _; - } else { - root._ = _; - } - - // Current version. - _.VERSION = '1.5.2'; - - // Collection Functions - // -------------------- - - // The cornerstone, an `each` implementation, aka `forEach`. - // Handles objects with the built-in `forEach`, arrays, and raw objects. - // Delegates to **ECMAScript 5**'s native `forEach` if available. - var each = _.each = _.forEach = function(obj, iterator, context) { - if (obj == null) return; - if (nativeForEach && obj.forEach === nativeForEach) { - obj.forEach(iterator, context); - } else if (obj.length === +obj.length) { - for (var i = 0, length = obj.length; i < length; i++) { - if (iterator.call(context, obj[i], i, obj) === breaker) return; - } - } else { - var keys = _.keys(obj); - for (var i = 0, length = keys.length; i < length; i++) { - if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return; - } - } - }; - - // Return the results of applying the iterator to each element. - // Delegates to **ECMAScript 5**'s native `map` if available. - _.map = _.collect = function(obj, iterator, context) { - var results = []; - if (obj == null) return results; - if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); - each(obj, function(value, index, list) { - results.push(iterator.call(context, value, index, list)); - }); - return results; - }; - - var reduceError = 'Reduce of empty array with no initial value'; - - // **Reduce** builds up a single result from a list of values, aka `inject`, - // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. - _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { - var initial = arguments.length > 2; - if (obj == null) obj = []; - if (nativeReduce && obj.reduce === nativeReduce) { - if (context) iterator = _.bind(iterator, context); - return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); - } - each(obj, function(value, index, list) { - if (!initial) { - memo = value; - initial = true; - } else { - memo = iterator.call(context, memo, value, index, list); - } - }); - if (!initial) throw new TypeError(reduceError); - return memo; - }; - - // The right-associative version of reduce, also known as `foldr`. - // Delegates to **ECMAScript 5**'s native `reduceRight` if available. - _.reduceRight = _.foldr = function(obj, iterator, memo, context) { - var initial = arguments.length > 2; - if (obj == null) obj = []; - if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { - if (context) iterator = _.bind(iterator, context); - return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); - } - var length = obj.length; - if (length !== +length) { - var keys = _.keys(obj); - length = keys.length; - } - each(obj, function(value, index, list) { - index = keys ? keys[--length] : --length; - if (!initial) { - memo = obj[index]; - initial = true; - } else { - memo = iterator.call(context, memo, obj[index], index, list); - } - }); - if (!initial) throw new TypeError(reduceError); - return memo; - }; - - // Return the first value which passes a truth test. Aliased as `detect`. - _.find = _.detect = function(obj, iterator, context) { - var result; - any(obj, function(value, index, list) { - if (iterator.call(context, value, index, list)) { - result = value; - return true; - } - }); - return result; - }; - - // Return all the elements that pass a truth test. - // Delegates to **ECMAScript 5**'s native `filter` if available. - // Aliased as `select`. - _.filter = _.select = function(obj, iterator, context) { - var results = []; - if (obj == null) return results; - if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); - each(obj, function(value, index, list) { - if (iterator.call(context, value, index, list)) results.push(value); - }); - return results; - }; - - // Return all the elements for which a truth test fails. - _.reject = function(obj, iterator, context) { - return _.filter(obj, function(value, index, list) { - return !iterator.call(context, value, index, list); - }, context); - }; - - // Determine whether all of the elements match a truth test. - // Delegates to **ECMAScript 5**'s native `every` if available. - // Aliased as `all`. - _.every = _.all = function(obj, iterator, context) { - iterator || (iterator = _.identity); - var result = true; - if (obj == null) return result; - if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); - each(obj, function(value, index, list) { - if (!(result = result && iterator.call(context, value, index, list))) return breaker; - }); - return !!result; - }; - - // Determine if at least one element in the object matches a truth test. - // Delegates to **ECMAScript 5**'s native `some` if available. - // Aliased as `any`. - var any = _.some = _.any = function(obj, iterator, context) { - iterator || (iterator = _.identity); - var result = false; - if (obj == null) return result; - if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); - each(obj, function(value, index, list) { - if (result || (result = iterator.call(context, value, index, list))) return breaker; - }); - return !!result; - }; - - // Determine if the array or object contains a given value (using `===`). - // Aliased as `include`. - _.contains = _.include = function(obj, target) { - if (obj == null) return false; - if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; - return any(obj, function(value) { - return value === target; - }); - }; - - // Invoke a method (with arguments) on every item in a collection. - _.invoke = function(obj, method) { - var args = slice.call(arguments, 2); - var isFunc = _.isFunction(method); - return _.map(obj, function(value) { - return (isFunc ? method : value[method]).apply(value, args); - }); - }; - - // Convenience version of a common use case of `map`: fetching a property. - _.pluck = function(obj, key) { - return _.map(obj, _.property(key)); - }; - - // Convenience version of a common use case of `filter`: selecting only objects - // containing specific `key:value` pairs. - _.where = function(obj, attrs, first) { - if (_.isEmpty(attrs)) return first ? void 0 : []; - return _[first ? 'find' : 'filter'](obj, function(value) { - for (var key in attrs) { - if (attrs[key] !== value[key]) return false; - } - return true; - }); - }; - - // Convenience version of a common use case of `find`: getting the first object - // containing specific `key:value` pairs. - _.findWhere = function(obj, attrs) { - return _.where(obj, attrs, true); - }; - - // Return the maximum element or (element-based computation). - // Can't optimize arrays of integers longer than 65,535 elements. - // See [WebKit Bug 80797](https://bugs.webkit.org/show_bug.cgi?id=80797) - _.max = function(obj, iterator, context) { - if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { - return Math.max.apply(Math, obj); - } - if (!iterator && _.isEmpty(obj)) return -Infinity; - var result = {computed : -Infinity, value: -Infinity}; - each(obj, function(value, index, list) { - var computed = iterator ? iterator.call(context, value, index, list) : value; - computed > result.computed && (result = {value : value, computed : computed}); - }); - return result.value; - }; - - // Return the minimum element (or element-based computation). - _.min = function(obj, iterator, context) { - if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { - return Math.min.apply(Math, obj); - } - if (!iterator && _.isEmpty(obj)) return Infinity; - var result = {computed : Infinity, value: Infinity}; - each(obj, function(value, index, list) { - var computed = iterator ? iterator.call(context, value, index, list) : value; - computed < result.computed && (result = {value : value, computed : computed}); - }); - return result.value; - }; - - // Shuffle an array, using the modern version of the - // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle). - _.shuffle = function(obj) { - var rand; - var index = 0; - var shuffled = []; - each(obj, function(value) { - rand = _.random(index++); - shuffled[index - 1] = shuffled[rand]; - shuffled[rand] = value; - }); - return shuffled; - }; - - // Sample **n** random values from a collection. - // If **n** is not specified, returns a single random element. - // The internal `guard` argument allows it to work with `map`. - _.sample = function(obj, n, guard) { - if (n == null || guard) { - if (obj.length !== +obj.length) obj = _.values(obj); - return obj[_.random(obj.length - 1)]; - } - return _.shuffle(obj).slice(0, Math.max(0, n)); - }; - - // An internal function to generate lookup iterators. - var lookupIterator = function(value) { - if (value == null) return _.identity; - if (_.isFunction(value)) return value; - return _.property(value); - }; - - // Sort the object's values by a criterion produced by an iterator. - _.sortBy = function(obj, iterator, context) { - iterator = lookupIterator(iterator); - return _.pluck(_.map(obj, function(value, index, list) { - return { - value: value, - index: index, - criteria: iterator.call(context, value, index, list) - }; - }).sort(function(left, right) { - var a = left.criteria; - var b = right.criteria; - if (a !== b) { - if (a > b || a === void 0) return 1; - if (a < b || b === void 0) return -1; - } - return left.index - right.index; - }), 'value'); - }; - - // An internal function used for aggregate "group by" operations. - var group = function(behavior) { - return function(obj, iterator, context) { - var result = {}; - iterator = lookupIterator(iterator); - each(obj, function(value, index) { - var key = iterator.call(context, value, index, obj); - behavior(result, key, value); - }); - return result; - }; - }; - - // Groups the object's values by a criterion. Pass either a string attribute - // to group by, or a function that returns the criterion. - _.groupBy = group(function(result, key, value) { - (_.has(result, key) ? result[key] : (result[key] = [])).push(value); - }); - - // Indexes the object's values by a criterion, similar to `groupBy`, but for - // when you know that your index values will be unique. - _.indexBy = group(function(result, key, value) { - result[key] = value; - }); - - // Counts instances of an object that group by a certain criterion. Pass - // either a string attribute to count by, or a function that returns the - // criterion. - _.countBy = group(function(result, key) { - _.has(result, key) ? result[key]++ : result[key] = 1; - }); - - // Use a comparator function to figure out the smallest index at which - // an object should be inserted so as to maintain order. Uses binary search. - _.sortedIndex = function(array, obj, iterator, context) { - iterator = lookupIterator(iterator); - var value = iterator.call(context, obj); - var low = 0, high = array.length; - while (low < high) { - var mid = (low + high) >>> 1; - iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid; - } - return low; - }; - - // Safely create a real, live array from anything iterable. - _.toArray = function(obj) { - if (!obj) return []; - if (_.isArray(obj)) return slice.call(obj); - if (obj.length === +obj.length) return _.map(obj, _.identity); - return _.values(obj); - }; - - // Return the number of elements in an object. - _.size = function(obj) { - if (obj == null) return 0; - return (obj.length === +obj.length) ? obj.length : _.keys(obj).length; - }; - - // Array Functions - // --------------- - - // Get the first element of an array. Passing **n** will return the first N - // values in the array. Aliased as `head` and `take`. The **guard** check - // allows it to work with `_.map`. - _.first = _.head = _.take = function(array, n, guard) { - if (array == null) return void 0; - if ((n == null) || guard) return array[0]; - if (n < 0) return []; - return slice.call(array, 0, n); - }; - - // Returns everything but the last entry of the array. Especially useful on - // the arguments object. Passing **n** will return all the values in - // the array, excluding the last N. The **guard** check allows it to work with - // `_.map`. - _.initial = function(array, n, guard) { - return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); - }; - - // Get the last element of an array. Passing **n** will return the last N - // values in the array. The **guard** check allows it to work with `_.map`. - _.last = function(array, n, guard) { - if (array == null) return void 0; - if ((n == null) || guard) return array[array.length - 1]; - return slice.call(array, Math.max(array.length - n, 0)); - }; - - // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. - // Especially useful on the arguments object. Passing an **n** will return - // the rest N values in the array. The **guard** - // check allows it to work with `_.map`. - _.rest = _.tail = _.drop = function(array, n, guard) { - return slice.call(array, (n == null) || guard ? 1 : n); - }; - - // Trim out all falsy values from an array. - _.compact = function(array) { - return _.filter(array, _.identity); - }; - - // Internal implementation of a recursive `flatten` function. - var flatten = function(input, shallow, output) { - if (shallow && _.every(input, _.isArray)) { - return concat.apply(output, input); - } - each(input, function(value) { - if (_.isArray(value) || _.isArguments(value)) { - shallow ? push.apply(output, value) : flatten(value, shallow, output); - } else { - output.push(value); - } - }); - return output; - }; - - // Flatten out an array, either recursively (by default), or just one level. - _.flatten = function(array, shallow) { - return flatten(array, shallow, []); - }; - - // Return a version of the array that does not contain the specified value(s). - _.without = function(array) { - return _.difference(array, slice.call(arguments, 1)); - }; - - // Produce a duplicate-free version of the array. If the array has already - // been sorted, you have the option of using a faster algorithm. - // Aliased as `unique`. - _.uniq = _.unique = function(array, isSorted, iterator, context) { - if (_.isFunction(isSorted)) { - context = iterator; - iterator = isSorted; - isSorted = false; - } - var initial = iterator ? _.map(array, iterator, context) : array; - var results = []; - var seen = []; - each(initial, function(value, index) { - if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) { - seen.push(value); - results.push(array[index]); - } - }); - return results; - }; - - // Produce an array that contains the union: each distinct element from all of - // the passed-in arrays. - _.union = function() { - return _.uniq(_.flatten(arguments, true)); - }; - - // Produce an array that contains every item shared between all the - // passed-in arrays. - _.intersection = function(array) { - var rest = slice.call(arguments, 1); - return _.filter(_.uniq(array), function(item) { - return _.every(rest, function(other) { - return _.indexOf(other, item) >= 0; - }); - }); - }; - - // Take the difference between one array and a number of other arrays. - // Only the elements present in just the first array will remain. - _.difference = function(array) { - var rest = concat.apply(ArrayProto, slice.call(arguments, 1)); - return _.filter(array, function(value){ return !_.contains(rest, value); }); - }; - - // Zip together multiple lists into a single array -- elements that share - // an index go together. - _.zip = function() { - var length = _.max(_.pluck(arguments, "length").concat(0)); - var results = new Array(length); - for (var i = 0; i < length; i++) { - results[i] = _.pluck(arguments, '' + i); - } - return results; - }; - - // Converts lists into objects. Pass either a single array of `[key, value]` - // pairs, or two parallel arrays of the same length -- one of keys, and one of - // the corresponding values. - _.object = function(list, values) { - if (list == null) return {}; - var result = {}; - for (var i = 0, length = list.length; i < length; i++) { - if (values) { - result[list[i]] = values[i]; - } else { - result[list[i][0]] = list[i][1]; - } - } - return result; - }; - - // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), - // we need this function. Return the position of the first occurrence of an - // item in an array, or -1 if the item is not included in the array. - // Delegates to **ECMAScript 5**'s native `indexOf` if available. - // If the array is large and already in sort order, pass `true` - // for **isSorted** to use binary search. - _.indexOf = function(array, item, isSorted) { - if (array == null) return -1; - var i = 0, length = array.length; - if (isSorted) { - if (typeof isSorted == 'number') { - i = (isSorted < 0 ? Math.max(0, length + isSorted) : isSorted); - } else { - i = _.sortedIndex(array, item); - return array[i] === item ? i : -1; - } - } - if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted); - for (; i < length; i++) if (array[i] === item) return i; - return -1; - }; - - // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. - _.lastIndexOf = function(array, item, from) { - if (array == null) return -1; - var hasIndex = from != null; - if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) { - return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item); - } - var i = (hasIndex ? from : array.length); - while (i--) if (array[i] === item) return i; - return -1; - }; - - // Generate an integer Array containing an arithmetic progression. A port of - // the native Python `range()` function. See - // [the Python documentation](http://docs.python.org/library/functions.html#range). - _.range = function(start, stop, step) { - if (arguments.length <= 1) { - stop = start || 0; - start = 0; - } - step = arguments[2] || 1; - - var length = Math.max(Math.ceil((stop - start) / step), 0); - var idx = 0; - var range = new Array(length); - - while(idx < length) { - range[idx++] = start; - start += step; - } - - return range; - }; - - // Function (ahem) Functions - // ------------------ - - // Reusable constructor function for prototype setting. - var ctor = function(){}; - - // Create a function bound to a given object (assigning `this`, and arguments, - // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if - // available. - _.bind = function(func, context) { - var args, bound; - if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); - if (!_.isFunction(func)) throw new TypeError; - args = slice.call(arguments, 2); - return bound = function() { - if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); - ctor.prototype = func.prototype; - var self = new ctor; - ctor.prototype = null; - var result = func.apply(self, args.concat(slice.call(arguments))); - if (Object(result) === result) return result; - return self; - }; - }; - - // Partially apply a function by creating a version that has had some of its - // arguments pre-filled, without changing its dynamic `this` context. - _.partial = function(func) { - var args = slice.call(arguments, 1); - return function() { - return func.apply(this, args.concat(slice.call(arguments))); - }; - }; - - // Bind a number of an object's methods to that object. Remaining arguments - // are the method names to be bound. Useful for ensuring that all callbacks - // defined on an object belong to it. - _.bindAll = function(obj) { - var funcs = slice.call(arguments, 1); - if (funcs.length === 0) throw new Error("bindAll must be passed function names"); - each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); - return obj; - }; - - // Memoize an expensive function by storing its results. - _.memoize = function(func, hasher) { - var memo = {}; - hasher || (hasher = _.identity); - return function() { - var key = hasher.apply(this, arguments); - return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); - }; - }; - - // Delays a function for the given number of milliseconds, and then calls - // it with the arguments supplied. - _.delay = function(func, wait) { - var args = slice.call(arguments, 2); - return setTimeout(function(){ return func.apply(null, args); }, wait); - }; - - // Defers a function, scheduling it to run after the current call stack has - // cleared. - _.defer = function(func) { - return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); - }; - - // Returns a function, that, when invoked, will only be triggered at most once - // during a given window of time. Normally, the throttled function will run - // as much as it can, without ever going more than once per `wait` duration; - // but if you'd like to disable the execution on the leading edge, pass - // `{leading: false}`. To disable execution on the trailing edge, ditto. - _.throttle = function(func, wait, options) { - var context, args, result; - var timeout = null; - var previous = 0; - options || (options = {}); - var later = function() { - previous = options.leading === false ? 0 : getTime(); - timeout = null; - result = func.apply(context, args); - context = args = null; - }; - return function() { - var now = getTime(); - if (!previous && options.leading === false) previous = now; - var remaining = wait - (now - previous); - context = this; - args = arguments; - if (remaining <= 0) { - clearTimeout(timeout); - timeout = null; - previous = now; - result = func.apply(context, args); - context = args = null; - } else if (!timeout && options.trailing !== false) { - timeout = setTimeout(later, remaining); - } - return result; - }; - }; - - // Returns a function, that, as long as it continues to be invoked, will not - // be triggered. The function will be called after it stops being called for - // N milliseconds. If `immediate` is passed, trigger the function on the - // leading edge, instead of the trailing. - _.debounce = function(func, wait, immediate) { - var timeout, args, context, timestamp, result; - return function() { - context = this; - args = arguments; - timestamp = getTime(); - var later = function() { - var last = getTime() - timestamp; - if (last < wait) { - timeout = setTimeout(later, wait - last); - } else { - timeout = null; - if (!immediate) { - result = func.apply(context, args); - context = args = null; - } - } - }; - var callNow = immediate && !timeout; - if (!timeout) { - timeout = setTimeout(later, wait); - } - if (callNow) { - result = func.apply(context, args); - context = args = null; - } - - return result; - }; - }; - - // Returns a function that will be executed at most one time, no matter how - // often you call it. Useful for lazy initialization. - _.once = function(func) { - var ran = false, memo; - return function() { - if (ran) return memo; - ran = true; - memo = func.apply(this, arguments); - func = null; - return memo; - }; - }; - - // Returns the first function passed as an argument to the second, - // allowing you to adjust arguments, run code before and after, and - // conditionally execute the original function. - _.wrap = function(func, wrapper) { - return _.partial(wrapper, func); - }; - - // Returns a function that is the composition of a list of functions, each - // consuming the return value of the function that follows. - _.compose = function() { - var funcs = arguments; - return function() { - var args = arguments; - for (var i = funcs.length - 1; i >= 0; i--) { - args = [funcs[i].apply(this, args)]; - } - return args[0]; - }; - }; - - // Returns a function that will only be executed after being called N times. - _.after = function(times, func) { - return function() { - if (--times < 1) { - return func.apply(this, arguments); - } - }; - }; - - // Object Functions - // ---------------- - - // Retrieve the names of an object's properties. - // Delegates to **ECMAScript 5**'s native `Object.keys` - _.keys = nativeKeys || function(obj) { - if (obj !== Object(obj)) throw new TypeError('Invalid object'); - var keys = []; - for (var key in obj) if (_.has(obj, key)) keys.push(key); - return keys; - }; - - // Retrieve the values of an object's properties. - _.values = function(obj) { - var keys = _.keys(obj); - var length = keys.length; - var values = new Array(length); - for (var i = 0; i < length; i++) { - values[i] = obj[keys[i]]; - } - return values; - }; - - // Convert an object into a list of `[key, value]` pairs. - _.pairs = function(obj) { - var keys = _.keys(obj); - var length = keys.length; - var pairs = new Array(length); - for (var i = 0; i < length; i++) { - pairs[i] = [keys[i], obj[keys[i]]]; - } - return pairs; - }; - - // Invert the keys and values of an object. The values must be serializable. - _.invert = function(obj) { - var result = {}; - var keys = _.keys(obj); - for (var i = 0, length = keys.length; i < length; i++) { - result[obj[keys[i]]] = keys[i]; - } - return result; - }; - - // Return a sorted list of the function names available on the object. - // Aliased as `methods` - _.functions = _.methods = function(obj) { - var names = []; - for (var key in obj) { - if (_.isFunction(obj[key])) names.push(key); - } - return names.sort(); - }; - - // Extend a given object with all the properties in passed-in object(s). - _.extend = function(obj) { - each(slice.call(arguments, 1), function(source) { - if (source) { - for (var prop in source) { - obj[prop] = source[prop]; - } - } - }); - return obj; - }; - - // Return a copy of the object only containing the whitelisted properties. - _.pick = function(obj) { - var copy = {}; - var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); - each(keys, function(key) { - if (key in obj) copy[key] = obj[key]; - }); - return copy; - }; - - // Return a copy of the object without the blacklisted properties. - _.omit = function(obj) { - var copy = {}; - var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); - for (var key in obj) { - if (!_.contains(keys, key)) copy[key] = obj[key]; - } - return copy; - }; - - // Fill in a given object with default properties. - _.defaults = function(obj) { - each(slice.call(arguments, 1), function(source) { - if (source) { - for (var prop in source) { - if (obj[prop] === void 0) obj[prop] = source[prop]; - } - } - }); - return obj; - }; - - // Create a (shallow-cloned) duplicate of an object. - _.clone = function(obj) { - if (!_.isObject(obj)) return obj; - return _.isArray(obj) ? obj.slice() : _.extend({}, obj); - }; - - // Invokes interceptor with the obj, and then returns obj. - // The primary purpose of this method is to "tap into" a method chain, in - // order to perform operations on intermediate results within the chain. - _.tap = function(obj, interceptor) { - interceptor(obj); - return obj; - }; - - // Internal recursive comparison function for `isEqual`. - var eq = function(a, b, aStack, bStack) { - // Identical objects are equal. `0 === -0`, but they aren't identical. - // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). - if (a === b) return a !== 0 || 1 / a == 1 / b; - // A strict comparison is necessary because `null == undefined`. - if (a == null || b == null) return a === b; - // Unwrap any wrapped objects. - if (a instanceof _) a = a._wrapped; - if (b instanceof _) b = b._wrapped; - // Compare `[[Class]]` names. - var className = toString.call(a); - if (className != toString.call(b)) return false; - switch (className) { - // Strings, numbers, dates, and booleans are compared by value. - case '[object String]': - // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is - // equivalent to `new String("5")`. - return a == String(b); - case '[object Number]': - // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for - // other numeric values. - return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); - case '[object Date]': - case '[object Boolean]': - // Coerce dates and booleans to numeric primitive values. Dates are compared by their - // millisecond representations. Note that invalid dates with millisecond representations - // of `NaN` are not equivalent. - return +a == +b; - // RegExps are compared by their source patterns and flags. - case '[object RegExp]': - return a.source == b.source && - a.global == b.global && - a.multiline == b.multiline && - a.ignoreCase == b.ignoreCase; - } - if (typeof a != 'object' || typeof b != 'object') return false; - // Assume equality for cyclic structures. The algorithm for detecting cyclic - // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. - var length = aStack.length; - while (length--) { - // Linear search. Performance is inversely proportional to the number of - // unique nested structures. - if (aStack[length] == a) return bStack[length] == b; - } - // Objects with different constructors are not equivalent, but `Object`s - // from different frames are. - var aCtor = a.constructor, bCtor = b.constructor; - if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) && - _.isFunction(bCtor) && (bCtor instanceof bCtor)) - && ('constructor' in a && 'constructor' in b)) { - return false; - } - // Add the first object to the stack of traversed objects. - aStack.push(a); - bStack.push(b); - var size = 0, result = true; - // Recursively compare objects and arrays. - if (className == '[object Array]') { - // Compare array lengths to determine if a deep comparison is necessary. - size = a.length; - result = size == b.length; - if (result) { - // Deep compare the contents, ignoring non-numeric properties. - while (size--) { - if (!(result = eq(a[size], b[size], aStack, bStack))) break; - } - } - } else { - // Deep compare objects. - for (var key in a) { - if (_.has(a, key)) { - // Count the expected number of properties. - size++; - // Deep compare each member. - if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break; - } - } - // Ensure that both objects contain the same number of properties. - if (result) { - for (key in b) { - if (_.has(b, key) && !(size--)) break; - } - result = !size; - } - } - // Remove the first object from the stack of traversed objects. - aStack.pop(); - bStack.pop(); - return result; - }; - - // Perform a deep comparison to check if two objects are equal. - _.isEqual = function(a, b) { - return eq(a, b, [], []); - }; - - // Is a given array, string, or object empty? - // An "empty" object has no enumerable own-properties. - _.isEmpty = function(obj) { - if (obj == null) return true; - if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; - for (var key in obj) if (_.has(obj, key)) return false; - return true; - }; - - // Is a given value a DOM element? - _.isElement = function(obj) { - return !!(obj && obj.nodeType === 1); - }; - - // Is a given value an array? - // Delegates to ECMA5's native Array.isArray - _.isArray = nativeIsArray || function(obj) { - return toString.call(obj) == '[object Array]'; - }; - - // Is a given variable an object? - _.isObject = function(obj) { - return obj === Object(obj); - }; - - // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. - each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) { - _['is' + name] = function(obj) { - return toString.call(obj) == '[object ' + name + ']'; - }; - }); - - // Define a fallback version of the method in browsers (ahem, IE), where - // there isn't any inspectable "Arguments" type. - if (!_.isArguments(arguments)) { - _.isArguments = function(obj) { - return !!(obj && _.has(obj, 'callee')); - }; - } - - // Optimize `isFunction` if appropriate. - if (typeof (/./) !== 'function') { - _.isFunction = function(obj) { - return typeof obj === 'function'; - }; - } - - // Is a given object a finite number? - _.isFinite = function(obj) { - return isFinite(obj) && !isNaN(parseFloat(obj)); - }; - - // Is the given value `NaN`? (NaN is the only number which does not equal itself). - _.isNaN = function(obj) { - return _.isNumber(obj) && obj != +obj; - }; - - // Is a given value a boolean? - _.isBoolean = function(obj) { - return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; - }; - - // Is a given value equal to null? - _.isNull = function(obj) { - return obj === null; - }; - - // Is a given variable undefined? - _.isUndefined = function(obj) { - return obj === void 0; - }; - - // Shortcut function for checking if an object has a given property directly - // on itself (in other words, not on a prototype). - _.has = function(obj, key) { - return hasOwnProperty.call(obj, key); - }; - - // Utility Functions - // ----------------- - - // Run Underscore.js in *noConflict* mode, returning the `_` variable to its - // previous owner. Returns a reference to the Underscore object. - _.noConflict = function() { - root._ = previousUnderscore; - return this; - }; - - // Keep the identity function around for default iterators. - _.identity = function(value) { - return value; - }; - - _.constant = function(value) { - return function () { - return value; - }; - }; - - _.property = function(key) { - return function(obj) { - return obj[key]; - }; - }; - - // Run a function **n** times. - _.times = function(n, iterator, context) { - var accum = Array(Math.max(0, n)); - for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i); - return accum; - }; - - // Return a random integer between min and max (inclusive). - _.random = function(min, max) { - if (max == null) { - max = min; - min = 0; - } - return min + Math.floor(Math.random() * (max - min + 1)); - }; - - // List of HTML entities for escaping. - var entityMap = { - escape: { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' - } - }; - entityMap.unescape = _.invert(entityMap.escape); - - // Regexes containing the keys and values listed immediately above. - var entityRegexes = { - escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'), - unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g') - }; - - // Functions for escaping and unescaping strings to/from HTML interpolation. - _.each(['escape', 'unescape'], function(method) { - _[method] = function(string) { - if (string == null) return ''; - return ('' + string).replace(entityRegexes[method], function(match) { - return entityMap[method][match]; - }); - }; - }); - - // If the value of the named `property` is a function then invoke it with the - // `object` as context; otherwise, return it. - _.result = function(object, property) { - if (object == null) return void 0; - var value = object[property]; - return _.isFunction(value) ? value.call(object) : value; - }; - - // Add your own custom functions to the Underscore object. - _.mixin = function(obj) { - each(_.functions(obj), function(name) { - var func = _[name] = obj[name]; - _.prototype[name] = function() { - var args = [this._wrapped]; - push.apply(args, arguments); - return result.call(this, func.apply(_, args)); - }; - }); - }; - - // Generate a unique integer id (unique within the entire client session). - // Useful for temporary DOM ids. - var idCounter = 0; - _.uniqueId = function(prefix) { - var id = ++idCounter + ''; - return prefix ? prefix + id : id; - }; - - // By default, Underscore uses ERB-style template delimiters, change the - // following template settings to use alternative delimiters. - _.templateSettings = { - evaluate : /<%([\s\S]+?)%>/g, - interpolate : /<%=([\s\S]+?)%>/g, - escape : /<%-([\s\S]+?)%>/g - }; - - // When customizing `templateSettings`, if you don't want to define an - // interpolation, evaluation or escaping regex, we need one that is - // guaranteed not to match. - var noMatch = /(.)^/; - - // Certain characters need to be escaped so that they can be put into a - // string literal. - var escapes = { - "'": "'", - '\\': '\\', - '\r': 'r', - '\n': 'n', - '\t': 't', - '\u2028': 'u2028', - '\u2029': 'u2029' - }; - - var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; - - // JavaScript micro-templating, similar to John Resig's implementation. - // Underscore templating handles arbitrary delimiters, preserves whitespace, - // and correctly escapes quotes within interpolated code. - _.template = function(text, data, settings) { - var render; - settings = _.defaults({}, settings, _.templateSettings); - - // Combine delimiters into one regular expression via alternation. - var matcher = new RegExp([ - (settings.escape || noMatch).source, - (settings.interpolate || noMatch).source, - (settings.evaluate || noMatch).source - ].join('|') + '|$', 'g'); - - // Compile the template source, escaping string literals appropriately. - var index = 0; - var source = "__p+='"; - text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { - source += text.slice(index, offset) - .replace(escaper, function(match) { return '\\' + escapes[match]; }); - - if (escape) { - source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; - } - if (interpolate) { - source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; - } - if (evaluate) { - source += "';\n" + evaluate + "\n__p+='"; - } - index = offset + match.length; - return match; - }); - source += "';\n"; - - // If a variable is not specified, place data values in local scope. - if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; - - source = "var __t,__p='',__j=Array.prototype.join," + - "print=function(){__p+=__j.call(arguments,'');};\n" + - source + "return __p;\n"; - - try { - render = new Function(settings.variable || 'obj', '_', source); - } catch (e) { - e.source = source; - throw e; - } - - if (data) return render(data, _); - var template = function(data) { - return render.call(this, data, _); - }; - - // Provide the compiled function source as a convenience for precompilation. - template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; - - return template; - }; - - // Add a "chain" function, which will delegate to the wrapper. - _.chain = function(obj) { - return _(obj).chain(); - }; - - // OOP - // --------------- - // If Underscore is called as a function, it returns a wrapped object that - // can be used OO-style. This wrapper holds altered versions of all the - // underscore functions. Wrapped objects may be chained. - - // Helper function to continue chaining intermediate results. - var result = function(obj) { - return this._chain ? _(obj).chain() : obj; - }; - - // Add all of the Underscore functions to the wrapper object. - _.mixin(_); - - // Add all mutator Array functions to the wrapper. - each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { - var method = ArrayProto[name]; - _.prototype[name] = function() { - var obj = this._wrapped; - method.apply(obj, arguments); - if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0]; - return result.call(this, obj); - }; - }); - - // Add all accessor Array functions to the wrapper. - each(['concat', 'join', 'slice'], function(name) { - var method = ArrayProto[name]; - _.prototype[name] = function() { - return result.call(this, method.apply(this._wrapped, arguments)); - }; - }); - - _.extend(_.prototype, { - - // Start chaining a wrapped Underscore object. - chain: function() { - this._chain = true; - return this; - }, - - // Extracts the result from a wrapped and chained object. - value: function() { - return this._wrapped; - } - - }); - - // AMD registration happens at the end for compatibility with AMD loaders - // that may not enforce next-turn semantics on modules. Even though general - // practice for AMD registration is to be anonymous, underscore registers - // as a named module because, like jQuery, it is a base library that is - // popular enough to be bundled in a third party lib, but not be part of - // an AMD load request. Those cases could generate an error when an - // anonymous define() is called outside of a loader request. - if (typeof define === 'function' && define.amd) { - define('underscore', [], function() { - return _; - }); - } -}).call(this); diff --git a/src/main/js/plugins/spawn.js b/src/main/js/plugins/spawn.js index 97e4c4c..ce0c571 100644 --- a/src/main/js/plugins/spawn.js +++ b/src/main/js/plugins/spawn.js @@ -19,9 +19,12 @@ for a list of possible entities (creatures) which can be spawned. var entities = [], EntityType = org.bukkit.entity.EntityType; -for ( var t in EntityType ) { - if ( EntityType[t] && EntityType[t].ordinal ) { - entities.push(t); +var MaterialEnum = Packages.MaterialEnum; + +var entitytypes = EntityType.values(); +for ( var t in entitytypes ) { + if ( entitytypes[t] && entitytypes[t].ordinal ) { + entities.push(entitytypes[t].name()); } } command( 'spawn', function( parameters, sender ) { @@ -30,6 +33,10 @@ command( 'spawn', function( parameters, sender ) { return; } var location = sender.location; + if ( !location ) { + sender.sendMessage( 'You have no location. This command only works in-game.' ); + return; + } var world = location.world; var type = ('' + parameters[0]).toUpperCase(); world.spawnEntity( location, EntityType[type] ); From e52b665583a129abc132a2b45b7d0e90825a29ce Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Thu, 30 Jan 2014 00:03:07 +0000 Subject: [PATCH 113/456] updated contributing.md to refer to idiomatic js guide --- contributing.md | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/contributing.md b/contributing.md index 28940e6..aad163c 100644 --- a/contributing.md +++ b/contributing.md @@ -14,16 +14,16 @@ This project uses a Maven-like directory structure... scriptcraft + ScriptCraftPlugin.java - javascript + - lib + - (core javascript code goes here. Modules in this directory - should not be 'require'd by plugin or module authors) - modules + - (this is where module authors should put modules for - use by others) - plugins + - (this is where plugins - scriptcraft extensions for use by - operators and players should go) + js + + lib + + (core javascript code goes here. Modules in this directory + should not be 'require'd by plugin or module authors) + modules + + (this is where module authors should put modules for + use by others) + plugins + + (this is where plugins - scriptcraft extensions for use by + operators and players should go) resources + plugin.yml docs + @@ -71,6 +71,14 @@ called `greet.js` located in the plugins folder... global. Anyone with operator privileges can type `/js greet(self)` at the in-game command prompt to execute the function. +## Coding Conventions + +See for a recommended +approach to writing javascript code for ScriptCraft. ScriptCraft is +aimed at younger programmers so readability is important - moreso than +cleverness. If submitting new code for inclusion in ScriptCraft please +ensure it is documented using the guidelines below... + ## Documentation contributions The Young persons guide to programming source file is located at From ca46607a472d2cd53da231a615f415f8482dce43 Mon Sep 17 00:00:00 2001 From: Walter Higgins Date: Thu, 30 Jan 2014 00:41:59 +0000 Subject: [PATCH 114/456] Update README.md --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3023530..0290848 100644 --- a/README.md +++ b/README.md @@ -14,14 +14,16 @@ files in a directory. This is a simple mod in a file called greet.js in the scriptcraft/plugins directory... - exports.greet = function( player ) { - player.sendMessage('Hello ' + player.name ); - }; +```javascript +exports.greet = function( player ) { + player.sendMessage('Hello ' + player.name ); +}; +``` At the in-game prompt, type... /js greet(self) - + ... to see the greeting. Anything you can do using CraftBukkit's API in Java, you can do using ScriptCraft in Javascript. # Description From 13ee0d0e9c82fe971cb101e137e284d4498f19f6 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Fri, 31 Jan 2014 00:36:28 +0000 Subject: [PATCH 115/456] Fixes #114 --- .../scriptcraft/ScriptCraftPlugin.java | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java b/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java index ab2082e..7a0681c 100644 --- a/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java +++ b/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java @@ -14,6 +14,7 @@ public class ScriptCraftPlugin extends JavaPlugin implements Listener // right now all ops share the same JS context/scope // need to look at possibly having context/scope per operator //protected Map playerContexts = new HashMap(); + private String NO_JAVASCRIPT_MESSAGE = "No JavaScript Engine available. ScriptCraft will not work without Javascript."; protected ScriptEngine engine = null; @Override public void onEnable() @@ -21,10 +22,13 @@ public class ScriptCraftPlugin extends JavaPlugin implements Listener try{ ScriptEngineManager factory = new ScriptEngineManager(); this.engine = factory.getEngineByName("JavaScript"); - Invocable inv = (Invocable)this.engine; - this.engine.eval(new InputStreamReader(this.getResource("boot.js"))); - inv.invokeFunction("__scboot", this, engine); - + if (this.engine == null){ + this.getLogger().severe(NO_JAVASCRIPT_MESSAGE); + } else { + Invocable inv = (Invocable)this.engine; + this.engine.eval(new InputStreamReader(this.getResource("boot.js"))); + inv.invokeFunction("__scboot", this, engine); + } }catch(Exception e){ e.printStackTrace(); this.getLogger().severe(e.getMessage()); @@ -35,6 +39,10 @@ public class ScriptCraftPlugin extends JavaPlugin implements Listener String[] args) { List result = new ArrayList(); + if (this.engine == null){ + this.getLogger().severe(NO_JAVASCRIPT_MESSAGE); + return null; + } try { Invocable inv = (Invocable)this.engine; inv.invokeFunction("__onTabComplete", result, sender, cmd, alias, args); @@ -50,6 +58,10 @@ public class ScriptCraftPlugin extends JavaPlugin implements Listener boolean result = false; String javascriptCode = ""; Object jsResult = null; + if (this.engine == null){ + this.getLogger().severe(NO_JAVASCRIPT_MESSAGE); + return false; + } try { jsResult = ((Invocable)this.engine).invokeFunction("__onCommand", sender, cmd, label, args); }catch (Exception se){ From 5868b9099a444b372032d8985d4b48ca74b7f18b Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Fri, 31 Jan 2014 00:42:05 +0000 Subject: [PATCH 116/456] Round up setInterval and setTimeout to match bukkit's min 50ms tick. --- src/main/js/lib/js-patch.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/js/lib/js-patch.js b/src/main/js/lib/js-patch.js index e291b61..db6cfb3 100644 --- a/src/main/js/lib/js-patch.js +++ b/src/main/js/lib/js-patch.js @@ -14,7 +14,7 @@ module.exports = function( $ ) { a delay in milliseconds. However, bukkit's scheduler expects a delay in ticks (where 1 tick = 1/20th second) */ - var bukkitTask = server.scheduler.runTaskLater( __plugin, callback, delayInMillis/50 ); + var bukkitTask = server.scheduler.runTaskLater( __plugin, callback, Math.ceil( delayInMillis / 50 ) ); return bukkitTask; }; @@ -23,7 +23,7 @@ module.exports = function( $ ) { }; $.setInterval = function( callback, intervalInMillis ) { - var delay = intervalInMillis/ 50; + var delay = Math.ceil( intervalInMillis / 50); var bukkitTask = server.scheduler.runTaskTimer( __plugin, callback, delay, delay ); return bukkitTask; }; From b3eaec4cf287c0b7e4022ffc72041064050c5153 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Fri, 31 Jan 2014 01:38:27 +0000 Subject: [PATCH 117/456] Add debug flag for compile. --- build.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/build.xml b/build.xml index 453775d..85858c5 100644 --- a/build.xml +++ b/build.xml @@ -62,6 +62,7 @@ source="1.6" target="1.6" destdir="${build}" + debug="true" classpath="${minecraft.dir}/craftbukkit.jar" /> From 29eb6c1975de92e9488960ad4ce6732cd7e0a207 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 4 Feb 2014 08:38:32 +0000 Subject: [PATCH 118/456] added craftbukkit link to guide --- docs/YoungPersonsGuideToProgrammingMinecraft.md | 7 +++++-- src/docs/templates/ypgpm.md | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/YoungPersonsGuideToProgrammingMinecraft.md b/docs/YoungPersonsGuideToProgrammingMinecraft.md index 2c2ba4b..532d32f 100644 --- a/docs/YoungPersonsGuideToProgrammingMinecraft.md +++ b/docs/YoungPersonsGuideToProgrammingMinecraft.md @@ -61,8 +61,9 @@ easy addition of 'Mods' and extensions to Minecraft. ScriptCraft is a difficult but CraftBukkit makes it easy. Follow these steps to Install ScriptCraft on your computer... -1. [Download and install CraftBukkit][dlbuk]. Then follow the [Bukkit Installation Instructions][bii]. -[bii]: http://wiki.bukkit.org/Setting_up_a_server +1. [Download and install CraftBukkit][dlbuk]. Then follow the [Bukkit + Installation Instructions][bii]. (Tip: You can grab the very latest + version of bukkit from the [alternative versions list][dlbuk2]) 2. Start the CraftBukkit server, then once it has started up, stop it by typing 'stop'. If you go to the craftbukkit folder (see step 1) you @@ -1207,6 +1208,8 @@ different objects and methods available for use by ScriptCraft. [buk]: http://wiki.bukkit.org/Setting_up_a_server [dlbuk]: http://dl.bukkit.org/ +[dlbuk2]: http://dl.bukkit.org/downloads/craftbukkit/ +[bii]: http://wiki.bukkit.org/Setting_up_a_server [sc-plugin]: http://scriptcraftjs.org/download/ [ce]: http://www.codecademy.com/ [mcdv]: http://www.minecraftwiki.net/wiki/Data_values diff --git a/src/docs/templates/ypgpm.md b/src/docs/templates/ypgpm.md index 46a04f8..2ecd292 100644 --- a/src/docs/templates/ypgpm.md +++ b/src/docs/templates/ypgpm.md @@ -26,8 +26,9 @@ easy addition of 'Mods' and extensions to Minecraft. ScriptCraft is a difficult but CraftBukkit makes it easy. Follow these steps to Install ScriptCraft on your computer... -1. [Download and install CraftBukkit][dlbuk]. Then follow the [Bukkit Installation Instructions][bii]. -[bii]: http://wiki.bukkit.org/Setting_up_a_server +1. [Download and install CraftBukkit][dlbuk]. Then follow the [Bukkit + Installation Instructions][bii]. (Tip: You can grab the very latest + version of bukkit from the [alternative versions list][dlbuk2]) 2. Start the CraftBukkit server, then once it has started up, stop it by typing 'stop'. If you go to the craftbukkit folder (see step 1) you @@ -1172,6 +1173,8 @@ different objects and methods available for use by ScriptCraft. [buk]: http://wiki.bukkit.org/Setting_up_a_server [dlbuk]: http://dl.bukkit.org/ +[dlbuk2]: http://dl.bukkit.org/downloads/craftbukkit/ +[bii]: http://wiki.bukkit.org/Setting_up_a_server [sc-plugin]: http://scriptcraftjs.org/download/ [ce]: http://www.codecademy.com/ [mcdv]: http://www.minecraftwiki.net/wiki/Data_values From 349c2f17cf76d57ba15346a60516f5e35328de85 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 4 Feb 2014 20:53:59 +0000 Subject: [PATCH 119/456] added docs for chat/color plugn --- docs/API-Reference.md | 42 ++++++++++++++++++++++++++---- src/main/js/plugins/chat/color.js | 36 ++++++++++++++++++++++--- src/main/js/plugins/homes/homes.js | 18 ++++++------- 3 files changed, 79 insertions(+), 17 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 099909e..74b6398 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -123,6 +123,7 @@ Walter Higgins * [Usage](#usage-8) * [alias Plugin](#alias-plugin) * [Examples](#examples-2) + * [chat Plugin](#chat-plugin) * [Classroom Plugin](#classroom-plugin) * [classroom.allowScripting() function](#classroomallowscripting-function) * [Commando Plugin](#commando-plugin) @@ -2479,6 +2480,37 @@ Aliases can be used at the in-game prompt by players or in the server console. Aliases will not be able to avail of command autocompletion (pressing the TAB key will have no effect). +## chat Plugin + +This plugin lets players choose a text color to use when chatting. Players can list colors by typing... + + /jsp list_colors + +... and can set the color to use when chatting by typing... + + /jsp chat_color {color} + +... where {color} is one of the following colors... + + * black + * blue + * darkgreen + * darkaqua + * darkred + * purple + * gold + * gray + * darkgray + * indigo + * brightgreen + * aqua + * red + * pink + * yellow + * white + +This plugin's source code is useful to study because it is short and demonstrates use of the `plugin()`, and `command()` functions, persistence and event handling. + ## Classroom Plugin The `classroom` object contains a couple of utility functions for use @@ -2624,8 +2656,8 @@ The `jsp home` command has the following options... * `/jsp home` ..command will return you to your home, if you have set one. - * `/jsp home ` Will take you to the home of (where - is the name of the player whose home you wish to visit. + * `/jsp home {player}` Will take you to the home of {player} (where + {player} is the name of the player whose home you wish to visit. * `/jsp home delete` Deletes your home location from the location database. This does not actually remove the home from the world or @@ -2640,8 +2672,8 @@ visit. * `/jsp home list` Lists home which you can visit. * `/jsp home ilist` Lists players who can visit your home. - * `/jsp home invite ` Invites the named player to your home. - * `/jsp home uninvite ` Uninvites (revokes invitation) the named player to your home. + * `/jsp home invite {player}` Invites the named player to your home. + * `/jsp home uninvite {player}` Uninvites (revokes invitation) the named player to your home. * `/jsp home public` Opens your home to all players (all players can visit your home). * `/jsp home private` Makes your home private (no longer visitable by all). @@ -2649,7 +2681,7 @@ visit. The following administration options can only be used by server operators... * `/jsp home listall` List all of the homes - * `/jsp home clear ` Removes the player's home + * `/jsp home clear {player}` Removes the player's home location. Again, this command does not destroy any structures in the world, it simply removes the location from the database. No blocks are destroyed by this command. diff --git a/src/main/js/plugins/chat/color.js b/src/main/js/plugins/chat/color.js index a6666d0..eb85f6c 100644 --- a/src/main/js/plugins/chat/color.js +++ b/src/main/js/plugins/chat/color.js @@ -1,6 +1,36 @@ -/* - TODO: Document this module -*/ +/************************************************************************* +## chat Plugin + +This plugin lets players choose a text color to use when chatting. Players can list colors by typing... + + /jsp list_colors + +... and can set the color to use when chatting by typing... + + /jsp chat_color {color} + +... where {color} is one of the following colors... + + * black + * blue + * darkgreen + * darkaqua + * darkred + * purple + * gold + * gray + * darkgray + * indigo + * brightgreen + * aqua + * red + * pink + * yellow + * white + +This plugin's source code is useful to study because it is short and demonstrates use of the `plugin()`, and `command()` functions, persistence and event handling. + +***/ var _store = { players: { } }, colorCodes = {}, i, diff --git a/src/main/js/plugins/homes/homes.js b/src/main/js/plugins/homes/homes.js index f859531..94fb22e 100644 --- a/src/main/js/plugins/homes/homes.js +++ b/src/main/js/plugins/homes/homes.js @@ -28,8 +28,8 @@ The `jsp home` command has the following options... * `/jsp home` ..command will return you to your home, if you have set one. - * `/jsp home ` Will take you to the home of (where - is the name of the player whose home you wish to visit. + * `/jsp home {player}` Will take you to the home of {player} (where + {player} is the name of the player whose home you wish to visit. * `/jsp home delete` Deletes your home location from the location database. This does not actually remove the home from the world or @@ -44,8 +44,8 @@ visit. * `/jsp home list` Lists home which you can visit. * `/jsp home ilist` Lists players who can visit your home. - * `/jsp home invite ` Invites the named player to your home. - * `/jsp home uninvite ` Uninvites (revokes invitation) the named player to your home. + * `/jsp home invite {player}` Invites the named player to your home. + * `/jsp home uninvite {player}` Uninvites (revokes invitation) the named player to your home. * `/jsp home public` Opens your home to all players (all players can visit your home). * `/jsp home private` Makes your home private (no longer visitable by all). @@ -53,7 +53,7 @@ visit. The following administration options can only be used by server operators... * `/jsp home listall` List all of the homes - * `/jsp home clear ` Removes the player's home + * `/jsp home clear {player}` Removes the player's home location. Again, this command does not destroy any structures in the world, it simply removes the location from the database. No blocks are destroyed by this command. @@ -74,21 +74,21 @@ var homes = plugin( 'homes', { return [ /* basic functions */ '/jsp home : Return to your own home', - '/jsp home : Go to player home', + '/jsp home {player} : Go to player home', '/jsp home set : Set your current location as home', '/jsp home delete : Delete your home location', /* social */ '/jsp home list : List homes you can visit', '/jsp home ilist : List players who can visit your home', - '/jsp home invite : Invite to your home', - '/jsp home uninvite : Uninvite to your home', + '/jsp home invite {player} : Invite {player} to your home', + '/jsp home uninvite {player} : Uninvite {player} to your home', '/jsp home public : Open your home to all players', '/jsp home private : Make your home private', /* administration */ '/jsp home listall : Show all houses (ops only)', - '/jsp home clear : Clears player home location (ops only)' + '/jsp home clear {player} : Clears player home location (ops only)' ]; }, /* ======================================================================== From fe62f61883a8fd861d8b7d955c4e17c3a3a67168 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 4 Feb 2014 21:04:12 +0000 Subject: [PATCH 120/456] fix code in sc-mqtt module --- docs/API-Reference.md | 37 ++++++++++++---------------------- src/main/js/modules/sc-mqtt.js | 37 ++++++++++++---------------------- 2 files changed, 26 insertions(+), 48 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 74b6398..87596b1 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -869,30 +869,19 @@ present in the CraftBukkit classpath. To use this module, you should messages to/from a Net-enabled Arduino or any other device which uses the [MQTT protocol][mqtt] - - var mqtt = require('sc-mqtt'); - - // create a new client - - var client = mqtt.client( 'tcp://localhost:1883', 'uniqueClientId' ); - - // connect to the broker - - client.connect( { keepAliveInterval: 15 } ); - - // publish a message to the broker - - client.publish( 'minecraft', 'loaded' ); - - // subscribe to messages on 'arduino' topic - - client.subscribe( 'arduino' ); - - // do something when an incoming message arrives... - - client.onMessageArrived( function( topic, message ) { - console.log( 'Message arrived: topic=' + topic + ', message=' + message ); - }); + var mqtt = require('sc-mqtt'); + // create a new client + var client = mqtt.client( 'tcp://localhost:1883', 'uniqueClientId' ); + // connect to the broker + client.connect( { keepAliveInterval: 15 } ); + // publish a message to the broker + client.publish( 'minecraft', 'loaded' ); + // subscribe to messages on 'arduino' topic + client.subscribe( 'arduino' ); + // do something when an incoming message arrives... + client.onMessageArrived( function( topic, message ) { + console.log( 'Message arrived: topic=' + topic + ', message=' + message ); + }); The `sc-mqtt` module provides a very simple minimal wrapper around the [Eclipse Paho MQTT Version 3 Client][pahodocs] java-based MQTT diff --git a/src/main/js/modules/sc-mqtt.js b/src/main/js/modules/sc-mqtt.js index 2846b49..99069ab 100644 --- a/src/main/js/modules/sc-mqtt.js +++ b/src/main/js/modules/sc-mqtt.js @@ -29,30 +29,19 @@ present in the CraftBukkit classpath. To use this module, you should messages to/from a Net-enabled Arduino or any other device which uses the [MQTT protocol][mqtt] - - var mqtt = require('sc-mqtt'); - - // create a new client - - var client = mqtt.client( 'tcp://localhost:1883', 'uniqueClientId' ); - - // connect to the broker - - client.connect( { keepAliveInterval: 15 } ); - - // publish a message to the broker - - client.publish( 'minecraft', 'loaded' ); - - // subscribe to messages on 'arduino' topic - - client.subscribe( 'arduino' ); - - // do something when an incoming message arrives... - - client.onMessageArrived( function( topic, message ) { - console.log( 'Message arrived: topic=' + topic + ', message=' + message ); - }); + var mqtt = require('sc-mqtt'); + // create a new client + var client = mqtt.client( 'tcp://localhost:1883', 'uniqueClientId' ); + // connect to the broker + client.connect( { keepAliveInterval: 15 } ); + // publish a message to the broker + client.publish( 'minecraft', 'loaded' ); + // subscribe to messages on 'arduino' topic + client.subscribe( 'arduino' ); + // do something when an incoming message arrives... + client.onMessageArrived( function( topic, message ) { + console.log( 'Message arrived: topic=' + topic + ', message=' + message ); + }); The `sc-mqtt` module provides a very simple minimal wrapper around the [Eclipse Paho MQTT Version 3 Client][pahodocs] java-based MQTT From f1925efd87edb5a217aba271babc157f7bb50170 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 4 Feb 2014 21:36:00 +0000 Subject: [PATCH 121/456] added syntax-highlighting to code samples --- docs/API-Reference.md | 249 ++++++++------- ...YoungPersonsGuideToProgrammingMinecraft.md | 299 ++++++++++-------- src/docs/templates/ypgpm.md | 299 ++++++++++-------- src/main/js/lib/require.js | 28 +- src/main/js/lib/scriptcraft.js | 61 ++-- src/main/js/modules/sc-mqtt.js | 39 ++- src/main/js/modules/utils/utils.js | 122 +++---- 7 files changed, 614 insertions(+), 483 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 87596b1..9bbba5a 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -156,21 +156,23 @@ loads the module circle.js in the same directory. The contents of foo.js: - var circle = require('./circle.js'); - console.log( 'The area of a circle of radius 4 is ' - + circle.area(4)); +```javascript +var circle = require('./circle.js'); +console.log( 'The area of a circle of radius 4 is ' + + circle.area(4)); +``` The contents of circle.js: - var PI = Math.PI; - - exports.area = function (r) { - return PI * r * r; - }; - - exports.circumference = function (r) { - return 2 * PI * r; - }; +```javascript +var PI = Math.PI; +exports.area = function (r) { + return PI * r * r; +}; +exports.circumference = function (r) { + return 2 * PI * r; +}; +``` The module circle.js has exported the functions area() and circumference(). To add functions and objects to the root of your @@ -210,9 +212,11 @@ module in the `plugins` directory exports becomes a global variable. For example, if you have a module greeting.js in the plugins directory.... - exports.greet = function(player) { - player.sendMessage('Hello ' + player.name); - }; +```javascript +exports.greet = function(player) { + player.sendMessage('Hello ' + player.name); +}; +``` ... then `greet` becomes a global function and can be used at the in-game (or server) command prompt like so... @@ -400,10 +404,12 @@ restored using the `scload()` function. #### Example - var myObject = { name: 'John Doe', - aliases: ['John Ray', 'John Mee'], - date_of_birth: '1982/01/31' }; - scsave(myObject, 'johndoe.json'); +```javascript +var myObject = { name: 'John Doe', + aliases: ['John Ray', 'John Mee'], + date_of_birth: '1982/01/31' }; +scsave(myObject, 'johndoe.json'); +``` ##### johndoe.json contents... @@ -490,13 +496,15 @@ If Node.js supports setTimeout() then it's probably good for ScriptCraft to supp #### Example - // - // start a storm in 5 seconds - // - setTimeout( function() { - var world = server.worlds.get(0); - world.setStorm(true); - }, 5000); +```javascript +// +// start a storm in 5 seconds +// +setTimeout( function() { + var world = server.worlds.get(0); + world.setStorm(true); +}, 5000); +``` ### clearTimeout() function @@ -566,23 +574,29 @@ For example imagine you have 3 files program.js, inc.js and math.js ... ### math.js - exports.add = function(a,b){ - return a + b; - } +```javascript +exports.add = function(a,b){ + return a + b; +} +``` ### inc.js - var math = require('./math'); - exports.increment = function(n){ - return math.add(n, 1); - } +```javascript +var math = require('./math'); +exports.increment = function(n){ + return math.add(n, 1); +} +``` ### program.js - var inc = require('./inc').increment; - var a = 7; - a = inc(a); - print(a); +```javascript +var inc = require('./inc').increment; +var a = 7; +a = inc(a); +print(a); +``` You can see from the above sample code that programs can use modules and modules themeselves can use other modules. Modules have full @@ -857,31 +871,38 @@ present in the CraftBukkit classpath. To use this module, you should craftbukkit-sc-mqtt.bat and edit it to include the following command... - java -classpath sc-mqtt.jar;craftbukit.jar org.bukkit.craftbukkit.Main - + ```sh + java -classpath sc-mqtt.jar;craftbukit.jar org.bukkit.craftbukkit.Main + ``` + If you're using Mac OS, create a new craftbukkit-sc-mqtt.command file and edit it (using TextWrangler or another text editor) ... - java -classpath sc-mqtt.jar:craftbukkit.jar org.bukit.craftbukkit.Main + ```sh + java -classpath sc-mqtt.jar:craftbukkit.jar org.bukit.craftbukkit.Main + ``` 4. Execute the craftbukkit-sc-mqtt batch file / command file to start Craftbukkit. You can now begin using this module to send and receive messages to/from a Net-enabled Arduino or any other device which uses the [MQTT protocol][mqtt] - var mqtt = require('sc-mqtt'); - // create a new client - var client = mqtt.client( 'tcp://localhost:1883', 'uniqueClientId' ); - // connect to the broker - client.connect( { keepAliveInterval: 15 } ); - // publish a message to the broker - client.publish( 'minecraft', 'loaded' ); - // subscribe to messages on 'arduino' topic - client.subscribe( 'arduino' ); - // do something when an incoming message arrives... - client.onMessageArrived( function( topic, message ) { - console.log( 'Message arrived: topic=' + topic + ', message=' + message ); - }); + ```javascript + var mqtt = require('sc-mqtt'); + // create a new client + var client = mqtt.client( 'tcp://localhost:1883', 'uniqueClientId' ); + // connect to the broker + client.connect( { keepAliveInterval: 15 } ); + // publish a message to the broker + client.publish( 'minecraft', 'loaded' ); + // subscribe to messages on 'arduino' topic + client.subscribe( 'arduino' ); + // do something when an incoming message arrives... + client.onMessageArrived( function( topic, message ) { + console.log( 'Message arrived: topic=' + topic + ', message=' + message ); + }); + + ``` The `sc-mqtt` module provides a very simple minimal wrapper around the [Eclipse Paho MQTT Version 3 Client][pahodocs] java-based MQTT @@ -1042,14 +1063,16 @@ String, then it tries to find the player with that name. #### Example - var utils = require('utils'); - var name = 'walterh'; - var player = utils.player(name); - if (player) { - player.sendMessage('Got ' + name); - }else{ - console.log('No player named ' + name); - } +```javascript +var utils = require('utils'); +var name = 'walterh'; +var player = utils.player(name); +if ( player ) { + player.sendMessage('Got ' + name); +} else { + console.log('No player named ' + name); +} +``` [bkpl]: http://jd.bukkit.org/dev/apidocs/org/bukkit/entity/Player.html [bkloc]: http://jd.bukkit.org/dev/apidocs/org/bukkit/Location.html @@ -1087,11 +1110,13 @@ The utils.locationToString() function returns a keys in a lookup table. #### Example - - var utils = require('utils'); - ... - var key = utils.locationToString(player.location); - lookupTable[key] = player.name; + +```javascript +var utils = require('utils'); +... +var key = utils.locationToString(player.location); +lookupTable[key] = player.name; +``` ### utils.locationFromJSON() function @@ -1132,12 +1157,14 @@ is the location of the block the player is looking at (targeting). The following code will strike lightning at the location the player is looking at... - var utils = require('utils'); - var playerName = 'walterh'; - var targetPos = utils.getMousePos(playerName); - if (targetPos){ - targetPos.world.strikeLightning(targetPos); - } +```javascript +var utils = require('utils'); +var playerName = 'walterh'; +var targetPos = utils.getMousePos(playerName); +if (targetPos){ + targetPos.world.strikeLightning(targetPos); +} +``` ### utils.foreach() function @@ -1186,42 +1213,48 @@ and put the code there. The following example illustrates how to use foreach for immediate processing of an array... - var utils = require('utils'); - var players = ['moe', 'larry', 'curly']; - utils.foreach (players, function(item){ - server.getPlayer(item).sendMessage('Hi ' + item); - }); +```javascript +var utils = require('utils'); +var players = ['moe', 'larry', 'curly']; +utils.foreach (players, function(item){ + server.getPlayer(item).sendMessage('Hi ' + item); +}); +``` ... The `utils.foreach()` function can work with Arrays or any Java-style collection. This is important because many objects in the Bukkit API use Java-style collections... - utils.foreach( server.onlinePlayers, function(player){ - player.chat('Hello!'); - }); +```javascript +utils.foreach( server.onlinePlayers, function(player){ + player.chat('Hello!'); +}); +``` ... the above code sends a 'Hello!' to every online player. The following example is a more complex use case - The need to build an enormous structure without hogging CPU usage... - // build a structure 200 wide x 200 tall x 200 long - // (That's 8 Million Blocks - enough to tax any machine!) - var utils = require('utils'); +```javascript +// build a structure 200 wide x 200 tall x 200 long +// (That's 8 Million Blocks - enough to tax any machine!) +var utils = require('utils'); - var a = []; - a.length = 200; - var drone = new Drone(); - var processItem = function(item, index, object, array){ - // build a box 200 wide by 200 long then move up - drone.box(blocks.wood, 200, 1, 200).up(); - }; - // by the time the job's done 'self' might be someone else - // assume this code is within a function/closure - var player = self; - var onDone = function(){ - player.sendMessage('Job Done!'); - }; - utils.foreach (a, processItem, null, 10, onDone); +var a = []; +a.length = 200; +var drone = new Drone(); +var processItem = function(item, index, object, array){ + // build a box 200 wide by 200 long then move up + drone.box(blocks.wood, 200, 1, 200).up(); +}; +// by the time the job's done 'self' might be someone else +// assume this code is within a function/closure +var player = self; +var onDone = function(){ + player.sendMessage('Job Done!'); +}; +utils.foreach (a, processItem, null, 10, onDone); +``` ### utils.nicely() function @@ -1262,15 +1295,17 @@ The utils.at() function will perform a given task at a given time every To warn players when night is approaching... - var utils = require('utils'); +```javascript +var utils = require('utils'); - utils.at( '19:00', function() { +utils.at( '19:00', function() { - utils.foreach( server.onlinePlayers, function( player ) { + utils.foreach( server.onlinePlayers, function( player ) { player.chat( 'The night is dark and full of terrors!' ); - }); - }); + +}); +``` ### utils.find() function @@ -1286,10 +1321,12 @@ a given directory and recursiving trawling all sub-directories. #### Example - var utils = require('utils'); - var jsFiles = utils.find('./', function(dir,name){ - return name.match(/\.js$/); - }); +```javascript +var utils = require('utils'); +var jsFiles = utils.find('./', function(dir,name){ + return name.match(/\.js$/); +}); +``` ## Drone Plugin diff --git a/docs/YoungPersonsGuideToProgrammingMinecraft.md b/docs/YoungPersonsGuideToProgrammingMinecraft.md index 532d32f..2c704da 100644 --- a/docs/YoungPersonsGuideToProgrammingMinecraft.md +++ b/docs/YoungPersonsGuideToProgrammingMinecraft.md @@ -474,9 +474,11 @@ object can do, let's use that knowledge to create a Minecraft Mod! Once you've installed Notepad++, Launch it, create a new file and type the following... - exports.greet = function(player){ - player.sendMessage('Hi ' + player.name); - } +```javascript +exports.greet = function( player ) { + player.sendMessage('Hi ' + player.name); +} +``` ... then save the file in a new directory `craftbukkit/plugins/scriptcraft/plugins/{your_name}` (replace @@ -514,12 +516,14 @@ one or more functions, objects or variables. For example... #### thrower.js - exports.egg = function(player){ - player.throwEgg(); - } - exports.snowball = function(player){ - player.throwSnowball(); - } +```javascript +exports.egg = function(player){ + player.throwEgg(); +} +exports.snowball = function(player){ + player.throwSnowball(); +} +``` ... is a plugin which provides 2 javascript functions called `egg()` and `snowball()` which can be invoked from the in-game prompt like @@ -536,9 +540,11 @@ differently each time it is called. Change the `greet()` function so that it looks like this... - exports.greet = function ( greeting , player) { - player.sendMessage( greeting + player.name ); - } +```javascript +exports.greet = function ( greeting , player) { + player.sendMessage( greeting + player.name ); +} +``` ... Save your greet.js file and issue the `/js refresh()` command in minecraft. Now enter the following command in Minecraft... @@ -696,16 +702,20 @@ At the in-game command prompt type the following then hit Enter... statements on a single line at the in-game command prompt but the statements could be written like this... - var players = server.onlinePlayers; - for (var i = 0; i < players.length; i++) { - var player = players[i]; - player.sendMessage('Hi!'); - } +```javascript +var players = server.onlinePlayers; +var player; +var i; +for ( i = 0; i < players.length; i++ ) { + player = players[i]; + player.sendMessage( 'Hi!' ); +} +``` ... On the first line, a new variable `players` is created from the server object's onlinePlayers property. `players` is more concise and easier to type than the long-winded `server.onlinePlayers`. On the -second line, the for loop is declared, a counter variable `i` is set +fourth line, the for loop is declared, a counter variable `i` is set to 0 (zero - arrays in javascript start at 0 not 1) and each time around the loop is tested to see if it's less than the number of players online. At the end of each run around the loop the `i` @@ -729,13 +739,17 @@ function. Open the `hi.js` file you created earlier (using NotePad++ , TextWrangler or your editor of choice) and add the following code at the bottom of the file... - exports.hiAll = function (){ - var players = server.onlinePlayers; - for (var i = 0; i < players.length; i++) { - var player = players[i]; - player.sendMessage('Hi!'); - } +```javascript +exports.hiAll = function () { + var players = server.onlinePlayers, + player, + i; + for ( i = 0; i < players.length; i++) { + player = players[i]; + player.sendMessage( 'Hi!' ); } +} +``` ... save the file, at the in-game command prompt type `reload` and then type `/js hiAll()`. This will send the message `Hi!` to all of @@ -749,11 +763,13 @@ use `for` loops and Arrays to get things done. Another way to repeat things over and over is to use a `while` loop. The following `while` loop counts to 100... - var i = 1; - while (i <= 100){ - console.log( i ); - i = i + 1; - } +```javascript +var i = 1; +while (i <= 100){ + console.log( i ); + i = i + 1; +} +``` A `while` loop will repeat until its condition is `false` - the condition in the above example is `i <= 100` so while i is less than @@ -775,12 +791,14 @@ Just like `for` loops, `while` loops can be also be used to loop through arrays. The following loop prints out all of the players on the server... - var players = server.onlinePlayers; - var i = 0; - while ( i < players.length ) { - console.log( players[i] ); - i = i + 1; - } +```javascript +var players = server.onlinePlayers; +var i = 0; +while ( i < players.length ) { + console.log( players[i] ); + i = i + 1; +} +``` ... whether you chose to use a `for` loop or a `while` loop is largely a matter of personal taste, `for` loops are more commonly used with @@ -814,31 +832,30 @@ above example uses a named function which already exists ( `console.log` ), you can also create new functions on-the-fly and pass them to the utils.foreach() function... - /* - give every player the ability to fly. - */ - var utils = require('utils'); - utils.foreach( server.onlinePlayers, - function (player) { - player.setAllowFlight(true); - } - ); +```javascript +/* + give every player the ability to fly. +*/ +var utils = require('utils'); +utils.foreach( server.onlinePlayers, function( player ) { + player.setAllowFlight(true); +} ); +``` ... Another example, this time each player will hear a Cat's Meow... - /* - Play a Cat's Meow sound for each player. - */ - var utils = require('utils'); - utils.foreach( server.onlinePlayers, - function (player) { - player.playSound(player.location, - org.bukkit.Sound.CAT_MEOW, - 1, - 1); - - } - ); +```javascript +/* + Play a Cat's Meow sound for each player. +*/ +var utils = require('utils'); +utils.foreach( server.onlinePlayers, function( player ) { + player.playSound(player.location, + org.bukkit.Sound.CAT_MEOW, + 1, + 1); +} ); +``` ### Exercise Try changing the above function so that different sounds are played @@ -873,23 +890,24 @@ loops come in. Open your favorite text editor and create a new file in your scriptcraft/plugins/{your-name} directory, name the file `myskyscraper.js`, then type the following... - var myskyscraper = function(floors) { - if (typeof floors == 'undefined'){ - floors = 10; - } - this.chkpt('myskyscraper'); // saves the drone position so it can return there later - for (var i = 0; i < floors; i++) - { - this.box(blocks.iron,20,1,20) - .up() - .box0(blocks.glass_pane,20,3,20) - .up(3); - } - return this.move('myskyscraper'); // return to where we started - }; - - var Drone = require('../drone/drone.js').Drone; - Drone.extend('myskyscraper',myskyscraper); +```javascript +var myskyscraper = function(floors) { + if (typeof floors == 'undefined'){ + floors = 10; + } + this.chkpt('myskyscraper'); // saves the drone position so it can return there later + for (var i = 0; i < floors; i++) + { + this.box(blocks.iron,20,1,20) + .up() + .box0(blocks.glass_pane,20,3,20) + .up(3); + } + return this.move('myskyscraper'); // return to where we started +}; +var Drone = require('../drone/drone.js').Drone; +Drone.extend('myskyscraper',myskyscraper); +``` ... so this takes a little explaining. First I create a new function called myskyscraper that will take a single parameter `floors` so that @@ -969,17 +987,15 @@ flying or not? This is where the `if - else` construct comes in handy. Open your favorite editor and type the following code into a new file in your scriptcraft/plugins directory... - function flightStatus(player) - { - if ( player.flying ) - { - player.sendMessage( 'Hey, You are flying!' ); - } - else - { - player.sendMessage( 'You are not flying.' ); - } - } +```javascript +function flightStatus( player ) { + if ( player.flying ) { + player.sendMessage( 'Hey, You are flying!' ); + } else { + player.sendMessage( 'You are not flying.' ); + } +} +``` ... now type `/reload` at the in-game prompt then type `/js flightStatus(self)` and an appropriate message will appear based on @@ -1009,10 +1025,12 @@ of event occurs, it's probably best to illustrate this by example. The following code sends a message to any player who breaks a block in the game... - events.on('block.BlockBreakEvent', function (listener, event) { - var breaker = event.player; - breaker.sendMessage('You broke a block'); - }); +```javascript +events.on('block.BlockBreakEvent', function ( listener, event ) { + var breaker = event.player; + breaker.sendMessage('You broke a block'); +} ); +``` The `events.on()` function is how you *register* a function which you want to be called whenever a particular type of event occurs. In the @@ -1063,11 +1081,13 @@ just specify the fully qualified class name instead. E.g. ... If you want an event handler to only execute once, you can remove the handler like this... - events.on('block.BlockBreakEvent', function(listener, evt) { - var breaker = evt.player; - breaker.sendMessage('You broke a block'); - evt.handlers.unregister( listener ); - }); +```javascript +events.on('block.BlockBreakEvent', function( listener, evt ) { + var breaker = evt.player; + breaker.sendMessage('You broke a block'); + evt.handlers.unregister( listener ); +} ); +``` The `evt.handlers.unregister( listener );` statement will remove this function from the list of listeners for this event. @@ -1121,47 +1141,55 @@ the name column until I find 'jane' then look *across* to get her score. In Javascript, an object which stored such a table would look like this... - var scoreboard = { - walter: 5, - tom: 6, - jane: 8, - bart: 7 - }; +```javascript +var scoreboard = { + walter: 5, + tom: 6, + jane: 8, + bart: 7 +}; +``` ... and if I wanted to write a function which took a player name as a parameter and returned their score, I'd do it like this... - function getScore(player){ - return scoreboard[ player ]; - } +```javascript +function getScore(player){ + return scoreboard[ player ]; +} +``` ... I might call such a function like this... - var janesScore = getScore('jane'); // returns 8 +```javascript +var janesScore = getScore('jane'); // returns 8 +``` ... putting it all together, a hypothetical scoreboard.js mdoule might look something like this... - var utils = require('utils'); - var scores = {}; +```javascript +var utils = require('utils'); +var scores = {}; - exports.initialise = function(names){ - scores = {}; - utils.foreach(names, function(name){ - scores[name] = 0; - }); - }; +exports.initialise = function(names){ + scores = {}; + utils.foreach(names, function(name){ + scores[name] = 0; + }); +}; - /* changes score by diff e.g. to add 6 to the player's current score - updateScore('walter',6); // walter's new score = 5 + 6 = 11. - */ - exports.updateScore = function(name, diff){ - scores[name] += diff; - }; +/* changes score by diff e.g. to add 6 to the player's current score + updateScore('walter',6); // walter's new score = 5 + 6 = 11. +*/ +exports.updateScore = function(name, diff){ + scores[name] += diff; +}; - exports.getScore = function(name){ - return scores[name]; - }; +exports.getScore = function(name){ + return scores[name]; +}; +``` ## Counting block break events for each player @@ -1170,20 +1198,21 @@ keep a count of how many blocks each player has broken ... #### block-break-counter.js - var breaks = {}; - // every time a player joins the game reset their block-break-count to 0 - events.on('player.PlayerJoinEvent', function(listener, event){ - breaks[event.player] = 0; - }); - events.on('block.BlockBreakEvent', function(listener, event){ - var breaker = event.player; - var breakCount = breaks[breaker.name]; - breakCount++; // increment the count. - breaks[breaker.name] = breakCount; - - breaker.sendMessage('You broke ' + breakCount + ' blocks'); - - }); +```javascript +var breaks = {}; +// every time a player joins the game reset their block-break-count to 0 +events.on('player.PlayerJoinEvent', function(listener, event){ + breaks[event.player] = 0; +}); +events.on('block.BlockBreakEvent', function(listener, event){ + var breaker = event.player; + var breakCount = breaks[breaker.name]; + breakCount++; // increment the count. + breaks[breaker.name] = breakCount; + + breaker.sendMessage('You broke ' + breakCount + ' blocks'); +}); +``` With a little more work, you could turn this into a game where players compete against each other to break as many blocks as possible within diff --git a/src/docs/templates/ypgpm.md b/src/docs/templates/ypgpm.md index 2ecd292..98349a4 100644 --- a/src/docs/templates/ypgpm.md +++ b/src/docs/templates/ypgpm.md @@ -439,9 +439,11 @@ object can do, let's use that knowledge to create a Minecraft Mod! Once you've installed Notepad++, Launch it, create a new file and type the following... - exports.greet = function(player){ - player.sendMessage('Hi ' + player.name); - } +```javascript +exports.greet = function( player ) { + player.sendMessage('Hi ' + player.name); +} +``` ... then save the file in a new directory `craftbukkit/plugins/scriptcraft/plugins/{your_name}` (replace @@ -479,12 +481,14 @@ one or more functions, objects or variables. For example... #### thrower.js - exports.egg = function(player){ - player.throwEgg(); - } - exports.snowball = function(player){ - player.throwSnowball(); - } +```javascript +exports.egg = function(player){ + player.throwEgg(); +} +exports.snowball = function(player){ + player.throwSnowball(); +} +``` ... is a plugin which provides 2 javascript functions called `egg()` and `snowball()` which can be invoked from the in-game prompt like @@ -501,9 +505,11 @@ differently each time it is called. Change the `greet()` function so that it looks like this... - exports.greet = function ( greeting , player) { - player.sendMessage( greeting + player.name ); - } +```javascript +exports.greet = function ( greeting , player) { + player.sendMessage( greeting + player.name ); +} +``` ... Save your greet.js file and issue the `/js refresh()` command in minecraft. Now enter the following command in Minecraft... @@ -661,16 +667,20 @@ At the in-game command prompt type the following then hit Enter... statements on a single line at the in-game command prompt but the statements could be written like this... - var players = server.onlinePlayers; - for (var i = 0; i < players.length; i++) { - var player = players[i]; - player.sendMessage('Hi!'); - } +```javascript +var players = server.onlinePlayers; +var player; +var i; +for ( i = 0; i < players.length; i++ ) { + player = players[i]; + player.sendMessage( 'Hi!' ); +} +``` ... On the first line, a new variable `players` is created from the server object's onlinePlayers property. `players` is more concise and easier to type than the long-winded `server.onlinePlayers`. On the -second line, the for loop is declared, a counter variable `i` is set +fourth line, the for loop is declared, a counter variable `i` is set to 0 (zero - arrays in javascript start at 0 not 1) and each time around the loop is tested to see if it's less than the number of players online. At the end of each run around the loop the `i` @@ -694,13 +704,17 @@ function. Open the `hi.js` file you created earlier (using NotePad++ , TextWrangler or your editor of choice) and add the following code at the bottom of the file... - exports.hiAll = function (){ - var players = server.onlinePlayers; - for (var i = 0; i < players.length; i++) { - var player = players[i]; - player.sendMessage('Hi!'); - } +```javascript +exports.hiAll = function () { + var players = server.onlinePlayers, + player, + i; + for ( i = 0; i < players.length; i++) { + player = players[i]; + player.sendMessage( 'Hi!' ); } +} +``` ... save the file, at the in-game command prompt type `reload` and then type `/js hiAll()`. This will send the message `Hi!` to all of @@ -714,11 +728,13 @@ use `for` loops and Arrays to get things done. Another way to repeat things over and over is to use a `while` loop. The following `while` loop counts to 100... - var i = 1; - while (i <= 100){ - console.log( i ); - i = i + 1; - } +```javascript +var i = 1; +while (i <= 100){ + console.log( i ); + i = i + 1; +} +``` A `while` loop will repeat until its condition is `false` - the condition in the above example is `i <= 100` so while i is less than @@ -740,12 +756,14 @@ Just like `for` loops, `while` loops can be also be used to loop through arrays. The following loop prints out all of the players on the server... - var players = server.onlinePlayers; - var i = 0; - while ( i < players.length ) { - console.log( players[i] ); - i = i + 1; - } +```javascript +var players = server.onlinePlayers; +var i = 0; +while ( i < players.length ) { + console.log( players[i] ); + i = i + 1; +} +``` ... whether you chose to use a `for` loop or a `while` loop is largely a matter of personal taste, `for` loops are more commonly used with @@ -779,31 +797,30 @@ above example uses a named function which already exists ( `console.log` ), you can also create new functions on-the-fly and pass them to the utils.foreach() function... - /* - give every player the ability to fly. - */ - var utils = require('utils'); - utils.foreach( server.onlinePlayers, - function (player) { - player.setAllowFlight(true); - } - ); +```javascript +/* + give every player the ability to fly. +*/ +var utils = require('utils'); +utils.foreach( server.onlinePlayers, function( player ) { + player.setAllowFlight(true); +} ); +``` ... Another example, this time each player will hear a Cat's Meow... - /* - Play a Cat's Meow sound for each player. - */ - var utils = require('utils'); - utils.foreach( server.onlinePlayers, - function (player) { - player.playSound(player.location, - org.bukkit.Sound.CAT_MEOW, - 1, - 1); - - } - ); +```javascript +/* + Play a Cat's Meow sound for each player. +*/ +var utils = require('utils'); +utils.foreach( server.onlinePlayers, function( player ) { + player.playSound(player.location, + org.bukkit.Sound.CAT_MEOW, + 1, + 1); +} ); +``` ### Exercise Try changing the above function so that different sounds are played @@ -838,23 +855,24 @@ loops come in. Open your favorite text editor and create a new file in your scriptcraft/plugins/{your-name} directory, name the file `myskyscraper.js`, then type the following... - var myskyscraper = function(floors) { - if (typeof floors == 'undefined'){ - floors = 10; - } - this.chkpt('myskyscraper'); // saves the drone position so it can return there later - for (var i = 0; i < floors; i++) - { - this.box(blocks.iron,20,1,20) - .up() - .box0(blocks.glass_pane,20,3,20) - .up(3); - } - return this.move('myskyscraper'); // return to where we started - }; - - var Drone = require('../drone/drone.js').Drone; - Drone.extend('myskyscraper',myskyscraper); +```javascript +var myskyscraper = function(floors) { + if (typeof floors == 'undefined'){ + floors = 10; + } + this.chkpt('myskyscraper'); // saves the drone position so it can return there later + for (var i = 0; i < floors; i++) + { + this.box(blocks.iron,20,1,20) + .up() + .box0(blocks.glass_pane,20,3,20) + .up(3); + } + return this.move('myskyscraper'); // return to where we started +}; +var Drone = require('../drone/drone.js').Drone; +Drone.extend('myskyscraper',myskyscraper); +``` ... so this takes a little explaining. First I create a new function called myskyscraper that will take a single parameter `floors` so that @@ -934,17 +952,15 @@ flying or not? This is where the `if - else` construct comes in handy. Open your favorite editor and type the following code into a new file in your scriptcraft/plugins directory... - function flightStatus(player) - { - if ( player.flying ) - { - player.sendMessage( 'Hey, You are flying!' ); - } - else - { - player.sendMessage( 'You are not flying.' ); - } - } +```javascript +function flightStatus( player ) { + if ( player.flying ) { + player.sendMessage( 'Hey, You are flying!' ); + } else { + player.sendMessage( 'You are not flying.' ); + } +} +``` ... now type `/reload` at the in-game prompt then type `/js flightStatus(self)` and an appropriate message will appear based on @@ -974,10 +990,12 @@ of event occurs, it's probably best to illustrate this by example. The following code sends a message to any player who breaks a block in the game... - events.on('block.BlockBreakEvent', function (listener, event) { - var breaker = event.player; - breaker.sendMessage('You broke a block'); - }); +```javascript +events.on('block.BlockBreakEvent', function ( listener, event ) { + var breaker = event.player; + breaker.sendMessage('You broke a block'); +} ); +``` The `events.on()` function is how you *register* a function which you want to be called whenever a particular type of event occurs. In the @@ -1028,11 +1046,13 @@ just specify the fully qualified class name instead. E.g. ... If you want an event handler to only execute once, you can remove the handler like this... - events.on('block.BlockBreakEvent', function(listener, evt) { - var breaker = evt.player; - breaker.sendMessage('You broke a block'); - evt.handlers.unregister( listener ); - }); +```javascript +events.on('block.BlockBreakEvent', function( listener, evt ) { + var breaker = evt.player; + breaker.sendMessage('You broke a block'); + evt.handlers.unregister( listener ); +} ); +``` The `evt.handlers.unregister( listener );` statement will remove this function from the list of listeners for this event. @@ -1086,47 +1106,55 @@ the name column until I find 'jane' then look *across* to get her score. In Javascript, an object which stored such a table would look like this... - var scoreboard = { - walter: 5, - tom: 6, - jane: 8, - bart: 7 - }; +```javascript +var scoreboard = { + walter: 5, + tom: 6, + jane: 8, + bart: 7 +}; +``` ... and if I wanted to write a function which took a player name as a parameter and returned their score, I'd do it like this... - function getScore(player){ - return scoreboard[ player ]; - } +```javascript +function getScore(player){ + return scoreboard[ player ]; +} +``` ... I might call such a function like this... - var janesScore = getScore('jane'); // returns 8 +```javascript +var janesScore = getScore('jane'); // returns 8 +``` ... putting it all together, a hypothetical scoreboard.js mdoule might look something like this... - var utils = require('utils'); - var scores = {}; +```javascript +var utils = require('utils'); +var scores = {}; - exports.initialise = function(names){ - scores = {}; - utils.foreach(names, function(name){ - scores[name] = 0; - }); - }; +exports.initialise = function(names){ + scores = {}; + utils.foreach(names, function(name){ + scores[name] = 0; + }); +}; - /* changes score by diff e.g. to add 6 to the player's current score - updateScore('walter',6); // walter's new score = 5 + 6 = 11. - */ - exports.updateScore = function(name, diff){ - scores[name] += diff; - }; +/* changes score by diff e.g. to add 6 to the player's current score + updateScore('walter',6); // walter's new score = 5 + 6 = 11. +*/ +exports.updateScore = function(name, diff){ + scores[name] += diff; +}; - exports.getScore = function(name){ - return scores[name]; - }; +exports.getScore = function(name){ + return scores[name]; +}; +``` ## Counting block break events for each player @@ -1135,20 +1163,21 @@ keep a count of how many blocks each player has broken ... #### block-break-counter.js - var breaks = {}; - // every time a player joins the game reset their block-break-count to 0 - events.on('player.PlayerJoinEvent', function(listener, event){ - breaks[event.player] = 0; - }); - events.on('block.BlockBreakEvent', function(listener, event){ - var breaker = event.player; - var breakCount = breaks[breaker.name]; - breakCount++; // increment the count. - breaks[breaker.name] = breakCount; - - breaker.sendMessage('You broke ' + breakCount + ' blocks'); - - }); +```javascript +var breaks = {}; +// every time a player joins the game reset their block-break-count to 0 +events.on('player.PlayerJoinEvent', function(listener, event){ + breaks[event.player] = 0; +}); +events.on('block.BlockBreakEvent', function(listener, event){ + var breaker = event.player; + var breakCount = breaks[breaker.name]; + breakCount++; // increment the count. + breaks[breaker.name] = breakCount; + + breaker.sendMessage('You broke ' + breakCount + ' blocks'); +}); +``` With a little more work, you could turn this into a game where players compete against each other to break as many blocks as possible within diff --git a/src/main/js/lib/require.js b/src/main/js/lib/require.js index 6c85142..fa1b91e 100644 --- a/src/main/js/lib/require.js +++ b/src/main/js/lib/require.js @@ -18,23 +18,29 @@ For example imagine you have 3 files program.js, inc.js and math.js ... ### math.js - exports.add = function(a,b){ - return a + b; - } +```javascript +exports.add = function(a,b){ + return a + b; +} +``` ### inc.js - var math = require('./math'); - exports.increment = function(n){ - return math.add(n, 1); - } +```javascript +var math = require('./math'); +exports.increment = function(n){ + return math.add(n, 1); +} +``` ### program.js - var inc = require('./inc').increment; - var a = 7; - a = inc(a); - print(a); +```javascript +var inc = require('./inc').increment; +var a = 7; +a = inc(a); +print(a); +``` You can see from the above sample code that programs can use modules and modules themeselves can use other modules. Modules have full diff --git a/src/main/js/lib/scriptcraft.js b/src/main/js/lib/scriptcraft.js index 38ca67b..4d80c78 100644 --- a/src/main/js/lib/scriptcraft.js +++ b/src/main/js/lib/scriptcraft.js @@ -12,21 +12,23 @@ loads the module circle.js in the same directory. The contents of foo.js: - var circle = require('./circle.js'); - console.log( 'The area of a circle of radius 4 is ' - + circle.area(4)); +```javascript +var circle = require('./circle.js'); +console.log( 'The area of a circle of radius 4 is ' + + circle.area(4)); +``` The contents of circle.js: - var PI = Math.PI; - - exports.area = function (r) { - return PI * r * r; - }; - - exports.circumference = function (r) { - return 2 * PI * r; - }; +```javascript +var PI = Math.PI; +exports.area = function (r) { + return PI * r * r; +}; +exports.circumference = function (r) { + return 2 * PI * r; +}; +``` The module circle.js has exported the functions area() and circumference(). To add functions and objects to the root of your @@ -66,9 +68,11 @@ module in the `plugins` directory exports becomes a global variable. For example, if you have a module greeting.js in the plugins directory.... - exports.greet = function(player) { - player.sendMessage('Hello ' + player.name); - }; +```javascript +exports.greet = function(player) { + player.sendMessage('Hello ' + player.name); +}; +``` ... then `greet` becomes a global function and can be used at the in-game (or server) command prompt like so... @@ -256,10 +260,12 @@ restored using the `scload()` function. #### Example - var myObject = { name: 'John Doe', - aliases: ['John Ray', 'John Mee'], - date_of_birth: '1982/01/31' }; - scsave(myObject, 'johndoe.json'); +```javascript +var myObject = { name: 'John Doe', + aliases: ['John Ray', 'John Mee'], + date_of_birth: '1982/01/31' }; +scsave(myObject, 'johndoe.json'); +``` ##### johndoe.json contents... @@ -346,13 +352,15 @@ If Node.js supports setTimeout() then it's probably good for ScriptCraft to supp #### Example - // - // start a storm in 5 seconds - // - setTimeout( function() { - var world = server.worlds.get(0); - world.setStorm(true); - }, 5000); +```javascript +// +// start a storm in 5 seconds +// +setTimeout( function() { + var world = server.worlds.get(0); + world.setStorm(true); +}, 5000); +``` ### clearTimeout() function @@ -477,7 +485,6 @@ function __onEnable ( __engine, __plugin, __script ) var canonizedFilename = _canonize( file ); if ( file.exists() ) { - parent = file.getParentFile(); reader = new FileReader( file ); br = new BufferedReader( reader ); code = ''; diff --git a/src/main/js/modules/sc-mqtt.js b/src/main/js/modules/sc-mqtt.js index 99069ab..96197ab 100644 --- a/src/main/js/modules/sc-mqtt.js +++ b/src/main/js/modules/sc-mqtt.js @@ -17,31 +17,38 @@ present in the CraftBukkit classpath. To use this module, you should craftbukkit-sc-mqtt.bat and edit it to include the following command... - java -classpath sc-mqtt.jar;craftbukit.jar org.bukkit.craftbukkit.Main - + ```sh + java -classpath sc-mqtt.jar;craftbukit.jar org.bukkit.craftbukkit.Main + ``` + If you're using Mac OS, create a new craftbukkit-sc-mqtt.command file and edit it (using TextWrangler or another text editor) ... - java -classpath sc-mqtt.jar:craftbukkit.jar org.bukit.craftbukkit.Main + ```sh + java -classpath sc-mqtt.jar:craftbukkit.jar org.bukit.craftbukkit.Main + ``` 4. Execute the craftbukkit-sc-mqtt batch file / command file to start Craftbukkit. You can now begin using this module to send and receive messages to/from a Net-enabled Arduino or any other device which uses the [MQTT protocol][mqtt] - var mqtt = require('sc-mqtt'); - // create a new client - var client = mqtt.client( 'tcp://localhost:1883', 'uniqueClientId' ); - // connect to the broker - client.connect( { keepAliveInterval: 15 } ); - // publish a message to the broker - client.publish( 'minecraft', 'loaded' ); - // subscribe to messages on 'arduino' topic - client.subscribe( 'arduino' ); - // do something when an incoming message arrives... - client.onMessageArrived( function( topic, message ) { - console.log( 'Message arrived: topic=' + topic + ', message=' + message ); - }); + ```javascript + var mqtt = require('sc-mqtt'); + // create a new client + var client = mqtt.client( 'tcp://localhost:1883', 'uniqueClientId' ); + // connect to the broker + client.connect( { keepAliveInterval: 15 } ); + // publish a message to the broker + client.publish( 'minecraft', 'loaded' ); + // subscribe to messages on 'arduino' topic + client.subscribe( 'arduino' ); + // do something when an incoming message arrives... + client.onMessageArrived( function( topic, message ) { + console.log( 'Message arrived: topic=' + topic + ', message=' + message ); + }); + + ``` The `sc-mqtt` module provides a very simple minimal wrapper around the [Eclipse Paho MQTT Version 3 Client][pahodocs] java-based MQTT diff --git a/src/main/js/modules/utils/utils.js b/src/main/js/modules/utils/utils.js index 6c438bc..20f27de 100644 --- a/src/main/js/modules/utils/utils.js +++ b/src/main/js/modules/utils/utils.js @@ -22,14 +22,16 @@ String, then it tries to find the player with that name. #### Example - var utils = require('utils'); - var name = 'walterh'; - var player = utils.player(name); - if (player) { - player.sendMessage('Got ' + name); - }else{ - console.log('No player named ' + name); - } +```javascript +var utils = require('utils'); +var name = 'walterh'; +var player = utils.player(name); +if ( player ) { + player.sendMessage('Got ' + name); +} else { + console.log('No player named ' + name); +} +``` [bkpl]: http://jd.bukkit.org/dev/apidocs/org/bukkit/entity/Player.html [bkloc]: http://jd.bukkit.org/dev/apidocs/org/bukkit/Location.html @@ -95,11 +97,13 @@ The utils.locationToString() function returns a keys in a lookup table. #### Example - - var utils = require('utils'); - ... - var key = utils.locationToString(player.location); - lookupTable[key] = player.name; + +```javascript +var utils = require('utils'); +... +var key = utils.locationToString(player.location); +lookupTable[key] = player.name; +``` ***/ exports.locationToString = function( location ) { @@ -179,12 +183,14 @@ is the location of the block the player is looking at (targeting). The following code will strike lightning at the location the player is looking at... - var utils = require('utils'); - var playerName = 'walterh'; - var targetPos = utils.getMousePos(playerName); - if (targetPos){ - targetPos.world.strikeLightning(targetPos); - } +```javascript +var utils = require('utils'); +var playerName = 'walterh'; +var targetPos = utils.getMousePos(playerName); +if (targetPos){ + targetPos.world.strikeLightning(targetPos); +} +``` ***/ exports.getMousePos = function( player ) { @@ -251,42 +257,48 @@ and put the code there. The following example illustrates how to use foreach for immediate processing of an array... - var utils = require('utils'); - var players = ['moe', 'larry', 'curly']; - utils.foreach (players, function(item){ - server.getPlayer(item).sendMessage('Hi ' + item); - }); +```javascript +var utils = require('utils'); +var players = ['moe', 'larry', 'curly']; +utils.foreach (players, function(item){ + server.getPlayer(item).sendMessage('Hi ' + item); +}); +``` ... The `utils.foreach()` function can work with Arrays or any Java-style collection. This is important because many objects in the Bukkit API use Java-style collections... - utils.foreach( server.onlinePlayers, function(player){ - player.chat('Hello!'); - }); +```javascript +utils.foreach( server.onlinePlayers, function(player){ + player.chat('Hello!'); +}); +``` ... the above code sends a 'Hello!' to every online player. The following example is a more complex use case - The need to build an enormous structure without hogging CPU usage... - // build a structure 200 wide x 200 tall x 200 long - // (That's 8 Million Blocks - enough to tax any machine!) - var utils = require('utils'); +```javascript +// build a structure 200 wide x 200 tall x 200 long +// (That's 8 Million Blocks - enough to tax any machine!) +var utils = require('utils'); - var a = []; - a.length = 200; - var drone = new Drone(); - var processItem = function(item, index, object, array){ - // build a box 200 wide by 200 long then move up - drone.box(blocks.wood, 200, 1, 200).up(); - }; - // by the time the job's done 'self' might be someone else - // assume this code is within a function/closure - var player = self; - var onDone = function(){ - player.sendMessage('Job Done!'); - }; - utils.foreach (a, processItem, null, 10, onDone); +var a = []; +a.length = 200; +var drone = new Drone(); +var processItem = function(item, index, object, array){ + // build a box 200 wide by 200 long then move up + drone.box(blocks.wood, 200, 1, 200).up(); +}; +// by the time the job's done 'self' might be someone else +// assume this code is within a function/closure +var player = self; +var onDone = function(){ + player.sendMessage('Job Done!'); +}; +utils.foreach (a, processItem, null, 10, onDone); +``` ***/ var _foreach = function( array, callback, context, delay, onCompletion ) { @@ -366,15 +378,17 @@ The utils.at() function will perform a given task at a given time every To warn players when night is approaching... - var utils = require('utils'); +```javascript +var utils = require('utils'); - utils.at( '19:00', function() { +utils.at( '19:00', function() { - utils.foreach( server.onlinePlayers, function( player ) { + utils.foreach( server.onlinePlayers, function( player ) { player.chat( 'The night is dark and full of terrors!' ); - }); - }); + +}); +``` ***/ exports.at = function( time24hr, callback, worlds ) { @@ -415,10 +429,12 @@ a given directory and recursiving trawling all sub-directories. #### Example - var utils = require('utils'); - var jsFiles = utils.find('./', function(dir,name){ - return name.match(/\.js$/); - }); +```javascript +var utils = require('utils'); +var jsFiles = utils.find('./', function(dir,name){ + return name.match(/\.js$/); +}); +``` ***/ exports.find = function( dir , filter ) { From 39b459ab7f7a5ad772cdd572f4958f09f64f08b5 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 4 Feb 2014 21:49:12 +0000 Subject: [PATCH 122/456] further syntax-highlighting and ignore netbeans folder. --- .gitignore | 2 + docs/API-Reference.md | 96 ++++++++++--------- ...YoungPersonsGuideToProgrammingMinecraft.md | 8 +- src/docs/templates/ypgpm.md | 8 +- src/main/js/lib/events.js | 37 ++++--- src/main/js/modules/signs/signs.js | 59 ++++++------ 6 files changed, 116 insertions(+), 94 deletions(-) diff --git a/.gitignore b/.gitignore index 663489b..330fd3b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ target build.local.properties /src/main/javascript/lib/.#tabcomplete.js /src/main/javascript/plugins/.#example-1.js +/nbproject/private/private.xml +/nbproject/project.xml diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 9bbba5a..ce30c32 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -693,29 +693,36 @@ function each time the event is fired. The following code will print a message on screen every time a block is broken in the game - events.on('block.BlockBreakEvent', function(listener, evt){ - evt.player.sendMessage( evt.player.name + ' broke a block!'); - }); +```javascript +events.on( 'block.BlockBreakEvent', function( listener, evt ) { + evt.player.sendMessage( evt.player.name + ' broke a block!'); +} ); +``` To handle an event only once and unregister from further events... - - events.on('block.BlockBreakEvent', function(listener, evt){ - evt.player.sendMessage( evt.player.name + ' broke a block!'); - evt.handlers.unregister(listener); - }); + +```javascript +events.on( 'block.BlockBreakEvent', function( listener, evt ) { + evt.player.sendMessage( evt.player.name + ' broke a block!'); + evt.handlers.unregister( listener ); +} ); To unregister a listener *outside* of the listener function... - var myBlockBreakListener = events.on('block.BlockBreakEvent',function(l,e){ ... }); - ... - var handlers = org.bukkit.event.block.BlockBreakEvent.getHandlerList(); - handlers.unregister(myBlockBreakListener); +```javascript +var myBlockBreakListener = events.on( 'block.BlockBreakEvent', function( l, e ) { ... } ); +... +var handlers = org.bukkit.event.block.BlockBreakEvent.getHandlerList(); +handlers.unregister(myBlockBreakListener); +``` To listen for events using a full class name as the `eventName` parameter... - events.on(org.bukkit.event.block.BlockBreakEvent, function(listener, evt){ - evt.player.sendMessage( evt.player.name + ' broke a block!'); - }); +```javascript +events.on( org.bukkit.event.block.BlockBreakEvent, function( listener, evt ) { + evt.player.sendMessage( evt.player.name + ' broke a block!'); +} ); +``` [buk2]: http://wiki.bukkit.org/Event_API_Reference [buk]: http://jd.bukkit.org/dev/apidocs/index.html?org/bukkit/event/Event.html @@ -949,30 +956,31 @@ an interactive sign. #### Example: Create a sign which changes the time of day. ##### plugins/signs/time-of-day.js - - var utils = require('utils'), - signs = require('signs'); + +```javascript +var utils = require('utils'), + signs = require('signs'); - var onTimeChoice = function(event){ - var selectedIndex = event.number; - // convert to Minecraft time 0 = Dawn, 6000 = midday, 12000 = dusk, 18000 = midnight - var time = selectedIndex * 6000; - event.player.location.world.setTime(time); - }; +var onTimeChoice = function(event){ + var selectedIndex = event.number; + // convert to Minecraft time 0 = Dawn, 6000 = midday, 12000 = dusk, 18000 = midnight + var time = selectedIndex * 6000; + event.player.location.world.setTime(time); +}; - // signs.menu returns a function which can be called for one or more signs in the game. - var convertToTimeMenu = signs.menu('Time of Day', - ['Dawn', 'Midday', 'Dusk', 'Midnight'], - onTimeChoice); +// signs.menu returns a function which can be called for one or more signs in the game. +var convertToTimeMenu = signs.menu('Time of Day', + ['Dawn', 'Midday', 'Dusk', 'Midnight'], + onTimeChoice); - exports.time_sign = function( player ){ - - var sign = signs.getTargetedBy(player); - if (!sign){ - throw new Error('You must look at a sign'); - } - convertToTimeMenu(sign); - }; +exports.time_sign = function( player ){ + var sign = signs.getTargetedBy(player); + if ( !sign ) { + throw new Error('You must look at a sign'); + } + convertToTimeMenu(sign); +}; +``` To use the above function at the in-game prompt, look at an existing sign and type... @@ -990,13 +998,15 @@ the entity has targeted. It is a utility function for use by plugin authors. #### Example - var signs = require('signs'), - utils = require('utils'); - var player = utils.player('tom1234'); - var sign = signs.getTargetedBy( player ); - if (!sign){ - player.sendMessage('Not looking at a sign'); - } +```javascript +var signs = require('signs'), + utils = require('utils'); +var player = utils.player('tom1234'); +var sign = signs.getTargetedBy( player ); +if ( !sign ) { + player.sendMessage('Not looking at a sign'); +} +``` [buksign]: http://jd.bukkit.org/dev/apidocs/org/bukkit/block/Sign.html diff --git a/docs/YoungPersonsGuideToProgrammingMinecraft.md b/docs/YoungPersonsGuideToProgrammingMinecraft.md index 2c704da..b142f3a 100644 --- a/docs/YoungPersonsGuideToProgrammingMinecraft.md +++ b/docs/YoungPersonsGuideToProgrammingMinecraft.md @@ -765,7 +765,7 @@ loop. The following `while` loop counts to 100... ```javascript var i = 1; -while (i <= 100){ +while ( i <= 100 ) { console.log( i ); i = i + 1; } @@ -892,12 +892,12 @@ type the following... ```javascript var myskyscraper = function(floors) { - if (typeof floors == 'undefined'){ + var i ; + if ( typeof floors == 'undefined' ) { floors = 10; } this.chkpt('myskyscraper'); // saves the drone position so it can return there later - for (var i = 0; i < floors; i++) - { + for ( i = 0; i < floors; i++ ) { this.box(blocks.iron,20,1,20) .up() .box0(blocks.glass_pane,20,3,20) diff --git a/src/docs/templates/ypgpm.md b/src/docs/templates/ypgpm.md index 98349a4..7a21f53 100644 --- a/src/docs/templates/ypgpm.md +++ b/src/docs/templates/ypgpm.md @@ -730,7 +730,7 @@ loop. The following `while` loop counts to 100... ```javascript var i = 1; -while (i <= 100){ +while ( i <= 100 ) { console.log( i ); i = i + 1; } @@ -857,12 +857,12 @@ type the following... ```javascript var myskyscraper = function(floors) { - if (typeof floors == 'undefined'){ + var i ; + if ( typeof floors == 'undefined' ) { floors = 10; } this.chkpt('myskyscraper'); // saves the drone position so it can return there later - for (var i = 0; i < floors; i++) - { + for ( i = 0; i < floors; i++ ) { this.box(blocks.iron,20,1,20) .up() .box0(blocks.glass_pane,20,3,20) diff --git a/src/main/js/lib/events.js b/src/main/js/lib/events.js index 7a0fd44..d9a92ed 100644 --- a/src/main/js/lib/events.js +++ b/src/main/js/lib/events.js @@ -46,29 +46,36 @@ function each time the event is fired. The following code will print a message on screen every time a block is broken in the game - events.on('block.BlockBreakEvent', function(listener, evt){ - evt.player.sendMessage( evt.player.name + ' broke a block!'); - }); +```javascript +events.on( 'block.BlockBreakEvent', function( listener, evt ) { + evt.player.sendMessage( evt.player.name + ' broke a block!'); +} ); +``` To handle an event only once and unregister from further events... - - events.on('block.BlockBreakEvent', function(listener, evt){ - evt.player.sendMessage( evt.player.name + ' broke a block!'); - evt.handlers.unregister(listener); - }); + +```javascript +events.on( 'block.BlockBreakEvent', function( listener, evt ) { + evt.player.sendMessage( evt.player.name + ' broke a block!'); + evt.handlers.unregister( listener ); +} ); To unregister a listener *outside* of the listener function... - var myBlockBreakListener = events.on('block.BlockBreakEvent',function(l,e){ ... }); - ... - var handlers = org.bukkit.event.block.BlockBreakEvent.getHandlerList(); - handlers.unregister(myBlockBreakListener); +```javascript +var myBlockBreakListener = events.on( 'block.BlockBreakEvent', function( l, e ) { ... } ); +... +var handlers = org.bukkit.event.block.BlockBreakEvent.getHandlerList(); +handlers.unregister(myBlockBreakListener); +``` To listen for events using a full class name as the `eventName` parameter... - events.on(org.bukkit.event.block.BlockBreakEvent, function(listener, evt){ - evt.player.sendMessage( evt.player.name + ' broke a block!'); - }); +```javascript +events.on( org.bukkit.event.block.BlockBreakEvent, function( listener, evt ) { + evt.player.sendMessage( evt.player.name + ' broke a block!'); +} ); +``` [buk2]: http://wiki.bukkit.org/Event_API_Reference [buk]: http://jd.bukkit.org/dev/apidocs/index.html?org/bukkit/event/Event.html diff --git a/src/main/js/modules/signs/signs.js b/src/main/js/modules/signs/signs.js index 7c5c8a6..8851d83 100644 --- a/src/main/js/modules/signs/signs.js +++ b/src/main/js/modules/signs/signs.js @@ -37,30 +37,31 @@ an interactive sign. #### Example: Create a sign which changes the time of day. ##### plugins/signs/time-of-day.js - - var utils = require('utils'), - signs = require('signs'); + +```javascript +var utils = require('utils'), + signs = require('signs'); - var onTimeChoice = function(event){ - var selectedIndex = event.number; - // convert to Minecraft time 0 = Dawn, 6000 = midday, 12000 = dusk, 18000 = midnight - var time = selectedIndex * 6000; - event.player.location.world.setTime(time); - }; +var onTimeChoice = function(event){ + var selectedIndex = event.number; + // convert to Minecraft time 0 = Dawn, 6000 = midday, 12000 = dusk, 18000 = midnight + var time = selectedIndex * 6000; + event.player.location.world.setTime(time); +}; - // signs.menu returns a function which can be called for one or more signs in the game. - var convertToTimeMenu = signs.menu('Time of Day', - ['Dawn', 'Midday', 'Dusk', 'Midnight'], - onTimeChoice); +// signs.menu returns a function which can be called for one or more signs in the game. +var convertToTimeMenu = signs.menu('Time of Day', + ['Dawn', 'Midday', 'Dusk', 'Midnight'], + onTimeChoice); - exports.time_sign = function( player ){ - - var sign = signs.getTargetedBy(player); - if (!sign){ - throw new Error('You must look at a sign'); - } - convertToTimeMenu(sign); - }; +exports.time_sign = function( player ){ + var sign = signs.getTargetedBy(player); + if ( !sign ) { + throw new Error('You must look at a sign'); + } + convertToTimeMenu(sign); +}; +``` To use the above function at the in-game prompt, look at an existing sign and type... @@ -78,13 +79,15 @@ the entity has targeted. It is a utility function for use by plugin authors. #### Example - var signs = require('signs'), - utils = require('utils'); - var player = utils.player('tom1234'); - var sign = signs.getTargetedBy( player ); - if (!sign){ - player.sendMessage('Not looking at a sign'); - } +```javascript +var signs = require('signs'), + utils = require('utils'); +var player = utils.player('tom1234'); +var sign = signs.getTargetedBy( player ); +if ( !sign ) { + player.sendMessage('Not looking at a sign'); +} +``` [buksign]: http://jd.bukkit.org/dev/apidocs/org/bukkit/block/Sign.html From 8453525da63f7f42a0c255133249b000941fd872 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Mon, 10 Feb 2014 20:55:32 +0000 Subject: [PATCH 123/456] Improvements to classroom.js module (added players/ directory into which players can drop their custom scripts when classroom.allowScripting(true) is called. --- docs/API-Reference.md | 75 +++++++++++- src/main/js/lib/plugin.js | 24 ++-- src/main/js/lib/require.js | 61 ++++++---- src/main/js/lib/scriptcraft.js | 51 ++++++--- src/main/js/modules/utils/utils.js | 76 ++++++++++++ src/main/js/plugins/classroom/classroom.js | 127 ++++++++++++++++----- 6 files changed, 327 insertions(+), 87 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index ce30c32..80d387d 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -68,6 +68,8 @@ Walter Higgins * [utils.nicely() function](#utilsnicely-function) * [utils.at() function](#utilsat-function) * [utils.find() function](#utilsfind-function) + * [utils.serverAddress() function](#utilsserveraddress-function) + * [utils.watchFile() function](#utilswatchfile-function) * [Drone Plugin](#drone-plugin) * [TLDNR; (Just read this if you're impatient)](#tldnr-just-read-this-if-youre-impatient) * [Constructing a Drone Object](#constructing-a-drone-object) @@ -1338,6 +1340,34 @@ var jsFiles = utils.find('./', function(dir,name){ }); ``` +### utils.serverAddress() function + +The utils.serverAddress() function returns the IP(v4) address of the server. + +```javascript +var utils = require('utils'); +var serverAddress = utils.serverAddress(); +console.log(serverAddress); +``` +### utils.watchFile() function + +Watches for changes to the given file or directory and calls the function provided +when the file changes. + +#### Parameters + + * File - the file to watch (can be a file or directory) + * Callback - The callback to invoke when the file has changed. The callback takes the + changed file as a parameter. + +#### Example + +```javascript +var utils = require('utils'); +utils.watchFile( 'test.txt', function( file ) { + console.log( file + ' has changed'); +}); +``` ## Drone Plugin The Drone is a convenience class for building. It can be used for... @@ -2560,8 +2590,44 @@ quickly realise how to grant themselves and others operator privileges once they have access to ScriptCraft). The goal of this module is not so much to enforce restrictions -(security or otherwise) but to make it easier for tutors to setup a shared server -so students can learn Javascript. +(security or otherwise) but to make it easier for tutors to setup a +shared server so students can learn Javascript. When scripting is +turned on, every player who joins the server will have a dedicated +directory into which they can save scripts. All scripts in such +directories are automatically watched and loaded into a global +variable named after the player. + +So for example, if player 'walterh' joins the server, a `walterh` +global variable is created. If a file `greet.js` with the following +content is dropped into the `plugins/scriptcraft/players/walterh` +directory... + +```javascript +exports.hi = function( player ){ + player.sendMessage('Hi ' + player.name); +}; +``` + +... then it can be invoked like this: `/js walterh.hi( self )` . This +lets every player/student create their own functions without having +naming collisions. + +It's strongly recommended that the +`craftbukkit/plugins/scriptcraft/players/` directory is shared so that +others can connect to it and drop .js files into their student +directories. On Ubuntu, select the folder in Nautilus (the default +file browser) then right-click and choose *Sharing Options*, check the +*Share this folder* checkbox and the *Allow others to create and +delete files* and *Guest access* checkboxes. Click *Create Share* +button to close the sharing options dialog. Students can then access +the shared folder as follows... + + * Windows: Open Explorer, Go to \\{serverAddress}\players\ + * Macintosh: Open Finder, Go to smb://{serverAddress}/players/ + * Linux: Open Nautilus, Go to smb://{serverAddress}/players/ + +... where {serverAddress} is the ip address of the server (this is +displayed to whoever invokes the classroom.allowScripting() function.) ### classroom.allowScripting() function @@ -2569,6 +2635,11 @@ Allow or disallow anyone who connects to the server (or is already connected) to use ScriptCraft. This function is preferable to granting 'ops' privileges to every student in a Minecraft classroom environment. +Whenever any file is added/edited or removed from any of the players/ +directories the contents are automatically reloaded. This is to +facilitate quick turnaround time for students getting to grips with +Javascript. + #### Parameters * canScript : true or false diff --git a/src/main/js/lib/plugin.js b/src/main/js/lib/plugin.js index 448d0a2..5b7bec7 100644 --- a/src/main/js/lib/plugin.js +++ b/src/main/js/lib/plugin.js @@ -31,15 +31,7 @@ var _plugin = function(/* String */ moduleName, /* Object */ moduleObject, isPer exports.plugin = _plugin; -var scriptCraftDir = null; -var pluginDir = null; -var dataDir = null; - -exports.autoload = function( dir, logger ) { - - scriptCraftDir = dir; - pluginDir = new File( dir, 'plugins' ); - dataDir = new File( dir, 'data' ); +exports.autoload = function( context, pluginDir, logger, options ) { var _canonize = function( file ) { return '' + file.canonicalPath.replaceAll('\\\\','/'); @@ -76,22 +68,26 @@ exports.autoload = function( dir, logger ) { var len = sourceFiles.length; if ( config.verbose ) { - console.info( len + ' scriptcraft plugins found.' ); + console.info( len + ' scriptcraft plugins found in ' + pluginDir ); } for ( var i = 0; i < len; i++ ) { pluginPath = _canonize( sourceFiles[i] ); module = {}; try { - module = require( pluginPath ); + module = require( pluginPath , options); for ( property in module ) { /* - all exports in plugins become global + all exports in plugins become members of context object */ - global[property] = module[property]; + context[property] = module[property]; } } catch ( e ) { - logger.severe( 'Plugin ' + pluginPath + ' ' + e ); + if ( typeof logger != 'undefined' ) { + logger.severe( 'Plugin ' + pluginPath + ' ' + e ); + } else { + java.lang.System.out.println( 'Error: Plugin ' + pluginPath + ' ' + e ); + } } } }(pluginDir)); diff --git a/src/main/js/lib/require.js b/src/main/js/lib/require.js index fa1b91e..d23298a 100644 --- a/src/main/js/lib/require.js +++ b/src/main/js/lib/require.js @@ -136,57 +136,56 @@ When resolving module names to file paths, ScriptCraft uses the following rules. ***/ var resolveModuleToFile = function ( moduleName, parentDir ) { - var file = new File(moduleName); - + var file = new File(moduleName), + i = 0, + pathWithJSExt, + resolvedFile; if ( file.exists() ) { return fileExists(file); } if ( moduleName.match( /^[^\.\/]/ ) ) { // it's a module named like so ... 'events' , 'net/http' // - var resolvedFile; - for (var i = 0;i < modulePaths.length; i++){ + for ( ; i < modulePaths.length; i++ ) { resolvedFile = new File(modulePaths[i] + moduleName); - if (resolvedFile.exists()){ + if ( resolvedFile.exists() ) { return fileExists(resolvedFile); - }else{ + } else { // try appending a .js to the end resolvedFile = new File(modulePaths[i] + moduleName + '.js'); - if (resolvedFile.exists()) + if ( resolvedFile.exists() ) { return resolvedFile; + } } } } else { // it's of the form ./path file = new File(parentDir, moduleName); - if (file.exists()){ + if ( file.exists() ) { return fileExists(file); - }else { - + } else { // try appending a .js to the end - var pathWithJSExt = file.canonicalPath + '.js'; - file = new File( parentDir, pathWithJSExt); - if (file.exists()) + pathWithJSExt = file.canonicalPath + '.js'; + file = new File( parentDir, pathWithJSExt ); + if (file.exists()) { return file; - else{ + } else { file = new File(pathWithJSExt); - if (file.exists()) + if ( file.exists() ) { return file; + } } } } return null; }; - /* - wph 20131215 Experimental - */ var _loadedModules = {}; var _format = java.lang.String.format; /* require() function implementation */ - var _require = function( parentFile, path ) { + var _require = function( parentFile, path, options ) { var file, canonizedFilename, moduleInfo, @@ -194,7 +193,15 @@ When resolving module names to file paths, ScriptCraft uses the following rules. head = '(function(exports,module,require,__filename,__dirname){ ', code = '', line = null; - + + if ( typeof options == 'undefined' ) { + options = { cache: true }; + } else { + if ( typeof options.cache == 'undefined' ) { + options.cache = true; + } + } + file = resolveModuleToFile(path, parentFile); if ( !file ) { var errMsg = '' + _format("require() failed to find matching file for module '%s' " + @@ -205,10 +212,12 @@ When resolving module names to file paths, ScriptCraft uses the following rules. throw errMsg; } canonizedFilename = _canonize(file); - + moduleInfo = _loadedModules[canonizedFilename]; if ( moduleInfo ) { - return moduleInfo; + if ( options.cache ) { + return moduleInfo; + } } if ( hooks ) { hooks.loading( canonizedFilename ); @@ -228,7 +237,9 @@ When resolving module names to file paths, ScriptCraft uses the following rules. var tail = '})'; code = head + code + tail; - _loadedModules[canonizedFilename] = moduleInfo; + if ( options.cache ) { + _loadedModules[canonizedFilename] = moduleInfo; + } var compiledWrapper = null; try { compiledWrapper = eval(code); @@ -258,8 +269,8 @@ When resolving module names to file paths, ScriptCraft uses the following rules. }; var _requireClosure = function( parent ) { - return function( path ) { - var module = _require( parent, path ); + return function( path, options ) { + var module = _require( parent, path , options); return module.exports; }; }; diff --git a/src/main/js/lib/scriptcraft.js b/src/main/js/lib/scriptcraft.js index 4d80c78..a3a7ec0 100644 --- a/src/main/js/lib/scriptcraft.js +++ b/src/main/js/lib/scriptcraft.js @@ -491,7 +491,7 @@ function __onEnable ( __engine, __plugin, __script ) 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 @@ -566,7 +566,7 @@ function __onEnable ( __engine, __plugin, __script ) setup paths to search for modules */ var modulePaths = [ jsPluginsRootDirName + '/lib/', - jsPluginsRootDirName + '/modules/' ]; + jsPluginsRootDirName + '/modules/' ]; if ( config.verbose ) { logger.info( 'Setting up CommonJS-style module system. Root Directory: ' + jsPluginsRootDirName ); @@ -612,27 +612,44 @@ function __onEnable ( __engine, __plugin, __script ) global.__onCommand = function( sender, cmd, label, args) { - var jsArgs = []; - var i = 0; + var jsArgs = [], + i = 0, + jsResult, + result, + cmdName, + fnBody; for ( ; i < args.length ; i++ ) { jsArgs.push( '' + args[i] ); } - var result = false; - var cmdName = ( '' + cmd.name ).toLowerCase(); + result = false; + cmdName = ( '' + cmd.name ).toLowerCase(); if (cmdName == 'js') { result = true; - var fnBody = jsArgs.join(' '); + fnBody = jsArgs.join(' '); global.self = sender; global.__engine = __engine; try { - var jsResult = __engine.eval(fnBody); - if ( jsResult ) { - sender.sendMessage(jsResult); - } + if ( typeof eval == 'undefined' ) { + jsResult = __engine.eval(fnBody); + } else { + /* + nashorn + https://bugs.openjdk.java.net/browse/JDK-8034055 + */ + jsResult = 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 { delete global.self; @@ -646,7 +663,7 @@ function __onEnable ( __engine, __plugin, __script ) return result; }; - plugins.autoload( jsPluginsRootDir, logger ); + plugins.autoload( global, new File(jsPluginsRootDir,'plugins'), logger ); /* wph 20140102 - warn if legacy 'craftbukkit/js-plugins' or 'craftbukkit/scriptcraft' directories are present */ @@ -655,21 +672,21 @@ function __onEnable ( __engine, __plugin, __script ) cbDir = new File(cbPluginsDir.canonicalPath).parentFile, legacyExists = false, legacyDirs = [new File( cbDir, 'js-plugins' ), - new File( cbDir, 'scriptcraft' )]; + new File( cbDir, 'scriptcraft' )]; for ( var i = 0; i < legacyDirs.length; i++ ) { if ( legacyDirs[i].exists() - && legacyDirs[i].isDirectory() ) { + && legacyDirs[i].isDirectory() ) { - legacyExists = true; + legacyExists = true; - console.warn('Legacy ScriptCraft directory %s was found. This directory is no longer used.', + 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 ); + __plugin, jsPluginsRootDir.canonicalPath ); } })(); diff --git a/src/main/js/modules/utils/utils.js b/src/main/js/modules/utils/utils.js index 20f27de..d55d7dd 100644 --- a/src/main/js/modules/utils/utils.js +++ b/src/main/js/modules/utils/utils.js @@ -459,3 +459,79 @@ exports.find = function( dir , filter ) { recurse( dir, result ); return result; }; +/************************************************************************ +### utils.serverAddress() function + +The utils.serverAddress() function returns the IP(v4) address of the server. + +```javascript +var utils = require('utils'); +var serverAddress = utils.serverAddress(); +console.log(serverAddress); +``` +***/ +exports.serverAddress = function() { + var interfaces = java.net.NetworkInterface.getNetworkInterfaces(); + var current, + addresses, + current_addr; + while ( interfaces.hasMoreElements() ) { + current = interfaces.nextElement(); + if ( ! current.isUp() || current.isLoopback() || current.isVirtual() ) { + continue; + } + addresses = current.getInetAddresses(); + while (addresses.hasMoreElements()) { + current_addr = addresses.nextElement(); + if ( current_addr.isLoopbackAddress() ) + continue; + if ( current_addr instanceof java.net.Inet4Address) + return current_addr.getHostAddress(); + } + } + return null; +}; +/************************************************************************ +### utils.watchFile() function + +Watches for changes to the given file or directory and calls the function provided +when the file changes. + +#### Parameters + + * File - the file to watch (can be a file or directory) + * Callback - The callback to invoke when the file has changed. The callback takes the + changed file as a parameter. + +#### Example + +```javascript +var utils = require('utils'); +utils.watchFile( 'test.txt', function( file ) { + console.log( file + ' has changed'); +}); +``` +***/ +var filesWatched = {}; +exports.watchFile = function( file, callback ) { + var File = java.io.File; + if ( typeof file == 'string' ) { + file = new File(file); + } + filesWatched[file.canonicalPath] = { + callback: callback, + lastModified: file.lastModified() + }; +}; +function fileWatcher() { + for (var file in filesWatched) { + var fileObject = new File(file); + var lm = fileObject.lastModified(); + if ( lm != filesWatched[file].lastModified ) { + filesWatched[file].lastModified = lm; + filesWatched[file].callback(fileObject); + } + } + setTimeout( fileWatcher, 5000 ); +}; +setTimeout( fileWatcher, 5000 ); diff --git a/src/main/js/plugins/classroom/classroom.js b/src/main/js/plugins/classroom/classroom.js index a91257a..729c1f7 100644 --- a/src/main/js/plugins/classroom/classroom.js +++ b/src/main/js/plugins/classroom/classroom.js @@ -1,4 +1,10 @@ -var foreach = require('utils').foreach; +var utils = require('utils'), + autoload = require('plugin').autoload, + logger = __plugin.logger, + foreach = utils.foreach, + watchFile = utils.watchFile, + playersDir = __dirname + '/../../players/', + serverAddress = utils.serverAddress(); /************************************************************************ ## Classroom Plugin @@ -14,8 +20,44 @@ quickly realise how to grant themselves and others operator privileges once they have access to ScriptCraft). The goal of this module is not so much to enforce restrictions -(security or otherwise) but to make it easier for tutors to setup a shared server -so students can learn Javascript. +(security or otherwise) but to make it easier for tutors to setup a +shared server so students can learn Javascript. When scripting is +turned on, every player who joins the server will have a dedicated +directory into which they can save scripts. All scripts in such +directories are automatically watched and loaded into a global +variable named after the player. + +So for example, if player 'walterh' joins the server, a `walterh` +global variable is created. If a file `greet.js` with the following +content is dropped into the `plugins/scriptcraft/players/walterh` +directory... + +```javascript +exports.hi = function( player ){ + player.sendMessage('Hi ' + player.name); +}; +``` + +... then it can be invoked like this: `/js walterh.hi( self )` . This +lets every player/student create their own functions without having +naming collisions. + +It's strongly recommended that the +`craftbukkit/plugins/scriptcraft/players/` directory is shared so that +others can connect to it and drop .js files into their student +directories. On Ubuntu, select the folder in Nautilus (the default +file browser) then right-click and choose *Sharing Options*, check the +*Share this folder* checkbox and the *Allow others to create and +delete files* and *Guest access* checkboxes. Click *Create Share* +button to close the sharing options dialog. Students can then access +the shared folder as follows... + + * Windows: Open Explorer, Go to \\{serverAddress}\players\ + * Macintosh: Open Finder, Go to smb://{serverAddress}/players/ + * Linux: Open Nautilus, Go to smb://{serverAddress}/players/ + +... where {serverAddress} is the ip address of the server (this is +displayed to whoever invokes the classroom.allowScripting() function.) ### classroom.allowScripting() function @@ -23,6 +65,11 @@ Allow or disallow anyone who connects to the server (or is already connected) to use ScriptCraft. This function is preferable to granting 'ops' privileges to every student in a Minecraft classroom environment. +Whenever any file is added/edited or removed from any of the players/ +directories the contents are automatically reloaded. This is to +facilitate quick turnaround time for students getting to grips with +Javascript. + #### Parameters * canScript : true or false @@ -42,51 +89,73 @@ Only ops users can run the classroom.allowScripting() function - this is so that don't try to bar themselves and each other from scripting. ***/ -var _store = { enableScripting: false }; +var _store = { enableScripting: false }, + File = java.io.File; + +function revokeScripting ( player ) { + foreach( player.getEffectivePermissions(), function( perm ) { + if ( (''+perm.permission).indexOf( 'scriptcraft.' ) == 0 ) { + if ( perm.attachment ) { + perm.attachment.remove(); + } + } + }); +} + +function grantScripting( player ) { + console.log('Enabling scripting for player ' + player.name); + var playerName = '' + player.name; + playerName = playerName.replace(/[^a-zA-Z0-9_\-]/g,''); + var playerDir = new File( playersDir + playerName ); + playerDir.mkdirs(); + player.addAttachment( __plugin, 'scriptcraft.*', true ); + var playerContext = {}; + autoload( playerContext, playerDir, logger, { cache: false }); + global[playerName] = playerContext; + + watchFile( playerDir, function( changedDir ){ + autoload(playerContext, playerDir, logger, { cache: false }); + }); + +/* + player.sendMessage('Create your own minecraft mods by adding javascript (.js) files'); + player.sendMessage(' Windows: Open Explorer, go to \\\\' + serverAddress + '\\players\\' + player.name); + player.sendMessage(' Macintosh: Open Finder, Go to smb://' + serverAddress + '/players/' + player.name); + player.sendMessage(' Linux: Open Nautilus, Go to smb://' + serverAddress + '/players/' + player.name); +*/ + +} + var classroom = plugin('classroom', { allowScripting: function (/* boolean: true or false */ canScript, sender ) { - if ( typeof sender == 'undefined' ) { + sender = utils.player(sender); + if ( !sender ) { console.log( 'Attempt to set classroom scripting without credentials' ); console.log( 'classroom.allowScripting(boolean, sender)' ); return; } - if ( !sender.op ) { - console.log( 'Attempt to set classroom scripting without credentials: ' + sender.name ); - return; - } - /* only operators should be allowed run this function */ - if ( !sender.isOp() ) { + if ( !sender.op ) { + console.log( 'Attempt to set classroom scripting without credentials: ' + sender.name ); + sender.sendMessage('Only operators can use this function'); return; } - if ( canScript ) { - foreach( server.onlinePlayers, function( player ) { - player.addAttachment( __plugin, 'scriptcraft.*', true ); - }); - } else { - foreach( server.onlinePlayers, function( player ) { - foreach( player.getEffectivePermissions(), function( perm ) { - if ( (''+perm.permission).indexOf( 'scriptcraft.' ) == 0 ) { - if ( perm.attachment ) { - perm.attachment.remove(); - } - } - }); - }); - } + foreach( server.onlinePlayers, canScript ? grantScripting : revokeScripting); _store.enableScripting = canScript; + + sender.sendMessage('Scripting turned ' + ( canScript ? 'on' : 'off' ) + + ' for all players on server ' + serverAddress); }, store: _store }, true); exports.classroom = classroom; -events.on( 'player.PlayerLoginEvent', function( listener, event ) { - var player = event.player; +events.on( 'player.PlayerJoinEvent', function( listener, event ) { if ( _store.enableScripting ) { - player.addAttachment( __plugin, 'scriptcraft.*', true ); + grantScripting(event.player); } }, 'HIGHEST'); From 3c7d3a2bb493467b999b2e15658b03f6b2546fc3 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 11 Feb 2014 20:58:45 +0000 Subject: [PATCH 124/456] Added section on configuration (server.properties) to young persons guide --- ...YoungPersonsGuideToProgrammingMinecraft.md | 20 +++++++++++++++++++ docs/readme.md | 16 +++++++++++---- src/docs/templates/ypgpm.md | 19 ++++++++++++++++++ 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/docs/YoungPersonsGuideToProgrammingMinecraft.md b/docs/YoungPersonsGuideToProgrammingMinecraft.md index b142f3a..1e93e47 100644 --- a/docs/YoungPersonsGuideToProgrammingMinecraft.md +++ b/docs/YoungPersonsGuideToProgrammingMinecraft.md @@ -2,6 +2,7 @@ ## Table of Contents * [Introduction](#introduction) * [Installation](#installation) + * [Configuring your Server (optional)](#configuring-your-server-optional) * [Learning Javascript](#learning-javascript) * [First Steps](#first-steps) * [Variables](#variables) @@ -95,6 +96,25 @@ Minecraft your way using Javascript. Javascript is easier to learn than Java but it's also more flexible and powerful and is used for creating interactive web sites and many other applications. +## Configuring your Server (optional) + +Once you've installed CraftBukkit, depending on your specific needs, +you might want to consider setting the following properties in the +`server.properties` file... + + # completely flat worlds are best for building from scratch + level-type=FLAT + generate-structures=false + + # creative mode + gamemode=1 + pvp=false + + # turns off authentication (for classroom environments) + online-mode=false + spawn-npcs=false + spawn-monsters=false + ## Learning Javascript To begin creating cool stuff in Minecraft using ScriptCraft, you don't diff --git a/docs/readme.md b/docs/readme.md index a480fbf..b79fc56 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -1,16 +1,24 @@ # Let's begin... -I created ScriptCraft to make it easier for children (and anyone curious about programming) to create their own Minecraft Mods. ScriptCraft makes it easier for new programmers to create Minecraft mods. Mods are written using the Javascript programming language and once the ScriptCraft mod is installed, you can add your own new Mods by adding Javascript (.js) files in a directory. +I created ScriptCraft to make it easier for children (and anyone +curious about programming) to create their own Minecraft +Mods. ScriptCraft makes it easier for new programmers to create +Minecraft mods. Mods are written using the Javascript programming +language and once the ScriptCraft mod is installed, you can add your +own new Mods by adding Javascript (.js) files in a directory. * If you're new to programming and want to start modding Minecraft, then [Start Here][ypgpm]. * If you've already used [Scratch][scr], have attended a few [CoderDojo][cd] sessions, or have already dabbled with Javascript, then [Start Here][cda]. # Additional Resources -CoderDojo Athenry have some [excellent tutorials][cda] for younger programmers who have used [Scratch][scr] and are interested in Modding Minecraft using Javascript. -In particular, they have an excellent [Scratch - to - Javascript][sj] tutorial which explains Scratch programs and how to do the same thing in Javascript. +CoderDojo Athenry have some [excellent tutorials][cda] for younger +programmers who have used [Scratch][scr] and are interested in Modding +Minecraft using Javascript. In particular, they have an excellent +[Scratch - to - Javascript][sj] tutorial which explains Scratch +programs and how to do the same thing in Javascript. -I highly recommend the series of tutorials provided by CoderDojo Athenry. +I highly recommend the series of [tutorials provided by CoderDojo Athenry][cda]. Developer Chris Cacciatore has created some interesting tools using Scriptcraft... diff --git a/src/docs/templates/ypgpm.md b/src/docs/templates/ypgpm.md index 7a21f53..9121243 100644 --- a/src/docs/templates/ypgpm.md +++ b/src/docs/templates/ypgpm.md @@ -60,6 +60,25 @@ Minecraft your way using Javascript. Javascript is easier to learn than Java but it's also more flexible and powerful and is used for creating interactive web sites and many other applications. +## Configuring your Server (optional) + +Once you've installed CraftBukkit, depending on your specific needs, +you might want to consider setting the following properties in the +`server.properties` file... + + # completely flat worlds are best for building from scratch + level-type=FLAT + generate-structures=false + + # creative mode + gamemode=1 + pvp=false + + # turns off authentication (for classroom environments) + online-mode=false + spawn-npcs=false + spawn-monsters=false + ## Learning Javascript To begin creating cool stuff in Minecraft using ScriptCraft, you don't From 2e4516bf69cdfa583faf7ffd41e9bc5322dffcf8 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 11 Feb 2014 21:10:56 +0000 Subject: [PATCH 125/456] Added 'utils.unwatchFile()' function to fix #117 --- docs/API-Reference.md | 11 +++++++++++ src/main/js/modules/utils/utils.js | 23 ++++++++++++++++++++-- src/main/js/plugins/classroom/classroom.js | 5 +++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 80d387d..3beffca 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -70,6 +70,7 @@ Walter Higgins * [utils.find() function](#utilsfind-function) * [utils.serverAddress() function](#utilsserveraddress-function) * [utils.watchFile() function](#utilswatchfile-function) + * [utils.unwatchFile() function](#utilsunwatchfile-function) * [Drone Plugin](#drone-plugin) * [TLDNR; (Just read this if you're impatient)](#tldnr-just-read-this-if-youre-impatient) * [Constructing a Drone Object](#constructing-a-drone-object) @@ -1368,6 +1369,16 @@ utils.watchFile( 'test.txt', function( file ) { console.log( file + ' has changed'); }); ``` +### utils.unwatchFile() function + +Removes a file from the watch list. + +#### Example +```javascript +var utils = require('utils'); +utils.unwatchFile( 'test.txt'); +``` + ## Drone Plugin The Drone is a convenience class for building. It can be used for... diff --git a/src/main/js/modules/utils/utils.js b/src/main/js/modules/utils/utils.js index d55d7dd..0244fda 100644 --- a/src/main/js/modules/utils/utils.js +++ b/src/main/js/modules/utils/utils.js @@ -1,4 +1,5 @@ 'use strict'; +var File = java.io.File; /************************************************************************ ## Utilities Module @@ -440,7 +441,7 @@ var jsFiles = utils.find('./', function(dir,name){ exports.find = function( dir , filter ) { var result = []; var recurse = function( dir, store ) { - var files, dirfile = new java.io.File( dir ); + var files, dirfile = new File( dir ); if ( typeof filter == 'undefined' ) { files = dirfile.list(); @@ -514,7 +515,6 @@ utils.watchFile( 'test.txt', function( file ) { ***/ var filesWatched = {}; exports.watchFile = function( file, callback ) { - var File = java.io.File; if ( typeof file == 'string' ) { file = new File(file); } @@ -523,6 +523,25 @@ exports.watchFile = function( file, callback ) { lastModified: file.lastModified() }; }; +/************************************************************************ +### utils.unwatchFile() function + +Removes a file from the watch list. + +#### Example +```javascript +var utils = require('utils'); +utils.unwatchFile( 'test.txt'); +``` + +***/ +exports.unwatchFile = function( file, callback ) { + if ( typeof file == 'string' ) { + file = new File(file); + } + delete filesWatched[file.canonicalPath]; +}; + function fileWatcher() { for (var file in filesWatched) { var fileObject = new File(file); diff --git a/src/main/js/plugins/classroom/classroom.js b/src/main/js/plugins/classroom/classroom.js index 729c1f7..af240b3 100644 --- a/src/main/js/plugins/classroom/classroom.js +++ b/src/main/js/plugins/classroom/classroom.js @@ -3,6 +3,7 @@ var utils = require('utils'), logger = __plugin.logger, foreach = utils.foreach, watchFile = utils.watchFile, + unwatchFile = utils.unwatchFile, playersDir = __dirname + '/../../players/', serverAddress = utils.serverAddress(); @@ -100,6 +101,10 @@ function revokeScripting ( player ) { } } }); + var playerName = '' + player.name; + playerName = playerName.replace(/[^a-zA-Z0-9_\-]/g,''); + var playerDir = new File( playersDir + playerName ); + unwatchFile( playerDir ); } function grantScripting( player ) { From 5bec21d381709e87efae79d521a11310a49d948f Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sun, 16 Feb 2014 18:29:15 +0000 Subject: [PATCH 126/456] Fix bug in reload - data dir was not present. fix #119 --- docs/release-notes.md | 6 ++++++ src/main/js/lib/scriptcraft.js | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index e49be22..ad86ae8 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,3 +1,9 @@ +# 2014 02 11 + +## Version 2.0.4 + +Various bug fixes, enhanced classroom module and improved error logging on startup. + # 2014 01 17 Added support for communication with Arduino and other devices which diff --git a/src/main/js/lib/scriptcraft.js b/src/main/js/lib/scriptcraft.js index a3a7ec0..8d57efa 100644 --- a/src/main/js/lib/scriptcraft.js +++ b/src/main/js/lib/scriptcraft.js @@ -515,7 +515,10 @@ function __onEnable ( __engine, __plugin, __script ) /* now that load is defined, use it to load a global config object */ - var config = _load( new File(jsPluginsRootDir, 'data/global-config.json' ) ); + var configFile = new File(jsPluginsRootDir, 'data/'); + configFile.mkdirs(); + configFile = new File(configFile,'global-config.json'); + var config = _load( configFile ); if ( !config ) { config = { verbose: false }; } From 7e03c6d3d7d721dbc38790bc3f00471ab1b1bf5f Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sun, 16 Feb 2014 18:29:36 +0000 Subject: [PATCH 127/456] More efficient fort building. --- src/main/js/plugins/drone/contrib/fort.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/main/js/plugins/drone/contrib/fort.js b/src/main/js/plugins/drone/contrib/fort.js index d0921ad..ec05630 100644 --- a/src/main/js/plugins/drone/contrib/fort.js +++ b/src/main/js/plugins/drone/contrib/fort.js @@ -14,6 +14,10 @@ Drone.extend('fort', function( side, height ) { if ( side % 2 ) { side++; } + var battlementWidth = 3; + if ( side <= 12 ) { + battlementWidth = 2; + } if ( height < 4 || side < 10 ) { throw new java.lang.RuntimeException('Forts must be at least 10 wide X 4 tall'); } @@ -43,13 +47,15 @@ Drone.extend('fort', function( side, height ) { // build battlement's floor // this.move('fort'); - this.up(height-2).fwd().right().box('126:0',side-2,1,side-2); - var battlementWidth = 3; - if ( side <= 12 ) { - battlementWidth = 2; + this.up(height-2).fwd().right(); + for ( var i = 0; i < battlementWidth; i++ ) { + this.box0('126:0', side - ( 2 + (i * 2) ), 1, side - ( 2 + ( i * 2) )); + this.fwd().right(); } - this.fwd(battlementWidth).right(battlementWidth) - .box(0,side-((1+battlementWidth)*2),1,side-((1+battlementWidth)*2)); +// .box('126:0',side-2,1,side-2); + +// this.fwd(battlementWidth).right(battlementWidth) +// .box(0,side-((1+battlementWidth)*2),1,side-((1+battlementWidth)*2)); // // add door // From 9fdfb45b6cee501e05449d17f129e3076d09e036 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sun, 16 Feb 2014 18:30:30 +0000 Subject: [PATCH 128/456] Remove commented out code for inefficient battlements building. --- src/main/js/plugins/drone/contrib/fort.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/js/plugins/drone/contrib/fort.js b/src/main/js/plugins/drone/contrib/fort.js index ec05630..4a2bc04 100644 --- a/src/main/js/plugins/drone/contrib/fort.js +++ b/src/main/js/plugins/drone/contrib/fort.js @@ -52,10 +52,6 @@ Drone.extend('fort', function( side, height ) { this.box0('126:0', side - ( 2 + (i * 2) ), 1, side - ( 2 + ( i * 2) )); this.fwd().right(); } -// .box('126:0',side-2,1,side-2); - -// this.fwd(battlementWidth).right(battlementWidth) -// .box(0,side-((1+battlementWidth)*2),1,side-((1+battlementWidth)*2)); // // add door // From de113db48c896aa36c18bc4ae1d58be6aef78207 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sun, 16 Feb 2014 18:31:09 +0000 Subject: [PATCH 129/456] Adding new hangtorch() drone function. --- .../js/plugins/drone/contrib/hangtorch.js | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/main/js/plugins/drone/contrib/hangtorch.js diff --git a/src/main/js/plugins/drone/contrib/hangtorch.js b/src/main/js/plugins/drone/contrib/hangtorch.js new file mode 100644 index 0000000..6f4c011 --- /dev/null +++ b/src/main/js/plugins/drone/contrib/hangtorch.js @@ -0,0 +1,29 @@ +var Drone = require('../drone').Drone; +var Material = org.bukkit.Material; +function canHang(material){ + if (material.equals(Material.AIR) || + material.equals(Material.VINE) ) { + return true; + } else { + return false; + } +} +Drone.extend('hangtorch', function () { + var torch = '50:' + Drone.PLAYER_TORCH_FACING[this.dir]; + var moves = 0; + var block = this.world.getBlockAt(this.x, this.y, this.z); + + while ( !canHang(block.type) ){ + + moves++; + this.back(); + if (moves == 10){ + this.fwd(moves); + console.log('no air to hang torch'); + return; + } + block = this.world.getBlockAt(this.x, this.y, this.z); + } + this.box(torch); + this.fwd(moves); +}); From ef028856137eea1e3a203cab379beb80fb841f6b Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sun, 16 Feb 2014 18:37:51 +0000 Subject: [PATCH 130/456] Drone.cuboidX() - foundation of most Drone building is now async. --- src/main/js/plugins/drone/drone.js | 60 ++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/src/main/js/plugins/drone/drone.js b/src/main/js/plugins/drone/drone.js index fe2accd..98f922e 100644 --- a/src/main/js/plugins/drone/drone.js +++ b/src/main/js/plugins/drone/drone.js @@ -735,6 +735,34 @@ exports.Drone = Drone; */ exports.blocks = blocks; +Drone.queue = []; +Drone.processingQueue = false; +Drone.MAX_QUEUE_SIZE = 100000; +Drone.tick = setInterval( function() { + if ( Drone.processingQueue ) { + return; + } + var maxOpsPerTick = Math.floor(Math.max(10,Math.sqrt(Drone.queue.length))) ; + var op; + Drone.processingQueue = true; + while ( maxOpsPerTick > 0 ) { + op = Drone.queue.shift(); + if (!op){ + Drone.processingQueue = false; + return; + } + var block = op.world.getBlockAt( op.x, op.y, op.z ); + block.setTypeIdAndData( op.typeid, op.meta, false ); + // wph 20130210 - dont' know if this is a bug in bukkit but for chests, + // the metadata is ignored (defaults to 2 - south facing) + // only way to change data is to set it using property/bean. + block.data = op.meta; + maxOpsPerTick--; + } + Drone.processingQueue = false; + return; +}, 1); + // // add custom methods to the Drone object using this function // @@ -1021,12 +1049,19 @@ Drone.prototype.cuboidX = function( blockType, meta, w, h, d ) { var dir = this.dir; var depthFunc = function( ) { - var block = that.world.getBlockAt( that.x, that.y, that.z ); - block.setTypeIdAndData( blockType, meta, false ); - // wph 20130210 - dont' know if this is a bug in bukkit but for chests, - // the metadata is ignored (defaults to 2 - south facing) - // only way to change data is to set it using property/bean. - block.data = meta; + var len = Drone.queue.length; + if ( len < Drone.MAX_QUEUE_SIZE ) { + Drone.queue.push({ + world: that.world, + x: that.x, + y: that.y, + z:that.z, + typeid: blockType, + meta: meta + }); + } else { + throw new Error('Drone is too busy!'); + } }; var heightFunc = function( ) { _traverse[dir].depth( that, d, depthFunc ); @@ -1059,15 +1094,16 @@ Drone.prototype.cuboid0 = function( block, w, h, d ) { return this.move( 'start_point' ); }; + Drone.extend( 'door', function( door ) { if ( typeof door == 'undefined' ) { door = 64; } else { door = 71; } - this.cuboid( door+':' + this.dir ) + this.cuboidX( door, this.dir ) .up( ) - .cuboid( door+':8' ) + .cuboidX( door, 8 ) .down( ); } ); @@ -1078,10 +1114,10 @@ Drone.extend( 'door2' , function( door ) { door = 71; } this - .box( door+':' + this.dir ).up( ) - .box( door+':8' ).right( ) - .box( door+':9' ).down( ) - .box( door+':' + this.dir ).left( ); + .cuboidX( door, this.dir ).up( ) + .cuboidX( door, 8 ).right( ) + .cuboidX( door, 9 ).down( ) + .cuboidX( door, this.dir ).left( ); } ); // player dirs: 0 = east, 1 = south, 2 = west, 3 = north // block dirs: 0 = east, 1 = west, 2 = south , 3 = north From d976563adf65a25d111b3634ed595b55d3774a63 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Wed, 19 Feb 2014 00:15:44 +0000 Subject: [PATCH 131/456] Drone.garden() is now non-destructive - won't destroy existing blocks. --- docs/API-Reference.md | 20 +++- src/main/js/plugins/drone/contrib/fort.js | 70 ++++++++------ .../js/plugins/drone/contrib/hangtorch.js | 6 +- src/main/js/plugins/drone/drone.js | 92 +++++++++++++++---- 4 files changed, 139 insertions(+), 49 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 3beffca..aa45d48 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -86,7 +86,9 @@ Walter Higgins * [Drone.cylinder0() method](#dronecylinder0-method) * [Drone.arc() method](#dronearc-method) * [Drone.door() method](#dronedoor-method) + * [Drone.door_iron() method](#dronedoor_iron-method) * [Drone.door2() method](#dronedoor2-method) + * [Drone.door2_iron() method](#dronedoor2_iron-method) * [Drone.sign() method](#dronesign-method) * [Drone Trees methods](#drone-trees-methods) * [Drone.garden() method](#dronegarden-method) @@ -1760,7 +1762,11 @@ To create an iron door... drone.door( blocks.door_iron ); ![iron door](img/doorex1.png) - + +### Drone.door_iron() method + +create an Iron door. + ### Drone.door2() method Create double doors (left and right side) @@ -1777,6 +1783,11 @@ To create double-doors at the cross-hairs/drone's location... ![double doors](img/door2ex1.png) +### Drone.door2_iron() method + +Create double iron doors + + ### Drone.sign() method Signs must use block 63 (stand-alone signs) or 68 (signs on walls) @@ -1854,7 +1865,12 @@ place random blocks stone, mossy stone and cracked stone (each block has the sam to place random blocks stone has a 50% chance of being picked, - rand({blocks.brick.stone: 5, blocks.brick.mossy: 3, blocks.brick.cracked: 2},w,d,h) + var distribution = {}; + distribution[ blocks.brick.stone ] = 5; + distribution[ blocks.brick.mossy ] = 3; + distribution[ blocks.brick.cracked ] = 2; + + rand( distribution, width, height, depth) regular stone has a 50% chance, mossy stone has a 30% chance and cracked stone has just a 20% chance of being picked. diff --git a/src/main/js/plugins/drone/contrib/fort.js b/src/main/js/plugins/drone/contrib/fort.js index 4a2bc04..42a0f2c 100644 --- a/src/main/js/plugins/drone/contrib/fort.js +++ b/src/main/js/plugins/drone/contrib/fort.js @@ -4,6 +4,12 @@ var Drone = require('../drone').Drone; // constructs a medieval fort // Drone.extend('fort', function( side, height ) { + var brick = 98, + turret, + i, + torch, + ladder; + if ( typeof side == 'undefined' ) { side = 18; } @@ -21,53 +27,65 @@ Drone.extend('fort', function( side, height ) { if ( height < 4 || side < 10 ) { throw new java.lang.RuntimeException('Forts must be at least 10 wide X 4 tall'); } - var brick = 98; // // build walls. // - this.chkpt('fort').box0(brick,side,height-1,side); + this.chkpt('fort') + .box0(brick,side,height-1,side) + .up(height-1); // // build battlements // - this.up(height-1); for ( i = 0; i <= 3; i++ ) { - var turret = []; + + turret = [ + '109:'+ Drone.PLAYER_STAIRS_FACING[this.dir], + '109:'+ Drone.PLAYER_STAIRS_FACING[(this.dir+2)%4] + ]; this.box(brick) // solid brick corners - .up().box('50:5').down() // light a torch on each corner - .fwd(); - turret.push('109:'+ Drone.PLAYER_STAIRS_FACING[this.dir]); - turret.push('109:'+ Drone.PLAYER_STAIRS_FACING[(this.dir+2)%4]); - try { - this.boxa(turret,1,1,side-2).fwd(side-2).turn(); - } catch( e ) { - console.log('ERROR: ' + e.toString()); - } + .up() + .box('50:5') + .down() // light a torch on each corner + .fwd() + .boxa(turret,1,1,side-2) + .fwd(side-2) + .turn(); } // // build battlement's floor // - this.move('fort'); - this.up(height-2).fwd().right(); - for ( var i = 0; i < battlementWidth; i++ ) { - this.box0('126:0', side - ( 2 + (i * 2) ), 1, side - ( 2 + ( i * 2) )); - this.fwd().right(); + this.move('fort') + .up(height-2) + .fwd() + .right(); + + for ( i = 0; i < battlementWidth; i++ ) { + + this.box0('126:0', side - ( 2 + (i * 2) ), 1, side - ( 2 + ( i * 2) )) + .fwd() + .right(); } // // add door // - var torch = '50:' + Drone.PLAYER_TORCH_FACING[this.dir]; - this.move('fort').right((side/2)-1).door2() // double doors - .back().left().up() + torch = '50:' + Drone.PLAYER_TORCH_FACING[this.dir]; + this.move('fort') + .right((side/2)-1) + .door2() // double doors + .back() + .left() + .up() .box(torch) // left torch .right(3) .box(torch); // right torch // // add ladder up to battlements // - var ladder = '65:' + Drone.PLAYER_SIGN_FACING[(this.dir+2)%4]; - this.move('fort').right((side/2)-3).fwd(1) // move inside fort - .box(ladder, 1,height-1,1); - return this.move('fort'); - + ladder = '65:' + Drone.PLAYER_SIGN_FACING[(this.dir+2)%4]; + this.move('fort') + .right((side/2)-3) + .fwd(1) // move inside fort + .box(ladder, 1,height-1,1) + .move('fort'); }); diff --git a/src/main/js/plugins/drone/contrib/hangtorch.js b/src/main/js/plugins/drone/contrib/hangtorch.js index 6f4c011..6523592 100644 --- a/src/main/js/plugins/drone/contrib/hangtorch.js +++ b/src/main/js/plugins/drone/contrib/hangtorch.js @@ -19,11 +19,11 @@ Drone.extend('hangtorch', function () { this.back(); if (moves == 10){ this.fwd(moves); - console.log('no air to hang torch'); + console.log('nowhere to hang torch'); return; } block = this.world.getBlockAt(this.x, this.y, this.z); } - this.box(torch); - this.fwd(moves); + this.box(torch) + .fwd(moves); }); diff --git a/src/main/js/plugins/drone/drone.js b/src/main/js/plugins/drone/drone.js index 98f922e..ce9a65f 100644 --- a/src/main/js/plugins/drone/drone.js +++ b/src/main/js/plugins/drone/drone.js @@ -3,8 +3,8 @@ var utils = require('utils'), Location = org.bukkit.Location, Player = org.bukkit.entity.Player, Sign = org.bukkit.block.Sign, - TreeType = org.bukkit.TreeType; - + TreeType = org.bukkit.TreeType, + Material = org.bukkit.Material; /********************************************************************* ## Drone Plugin @@ -387,7 +387,11 @@ To create an iron door... drone.door( blocks.door_iron ); ![iron door](img/doorex1.png) - + +### Drone.door_iron() method + +create an Iron door. + ### Drone.door2() method Create double doors (left and right side) @@ -404,6 +408,11 @@ To create double-doors at the cross-hairs/drone's location... ![double doors](img/door2ex1.png) +### Drone.door2_iron() method + +Create double iron doors + + ### Drone.sign() method Signs must use block 63 (stand-alone signs) or 68 (signs on walls) @@ -481,7 +490,12 @@ place random blocks stone, mossy stone and cracked stone (each block has the sam to place random blocks stone has a 50% chance of being picked, - rand({blocks.brick.stone: 5, blocks.brick.mossy: 3, blocks.brick.cracked: 2},w,d,h) + var distribution = {}; + distribution[ blocks.brick.stone ] = 5; + distribution[ blocks.brick.mossy ] = 3; + distribution[ blocks.brick.cracked ] = 2; + + rand( distribution, width, height, depth) regular stone has a 50% chance, mossy stone has a 30% chance and cracked stone has just a 20% chance of being picked. @@ -995,7 +1009,11 @@ Drone.extend( 'sign', function( message, block ) { } }); -Drone.prototype.cuboida = function(/* Array */ blocks, w, h, d ) { +Drone.prototype.cuboida = function(/* Array */ blocks, w, h, d, overwrite ) { + + if ( typeof overwrite == 'undefined' ) { + overwrite = true; + } var properBlocks = []; var len = blocks.length; for ( var i = 0; i < len; i++ ) { @@ -1022,7 +1040,9 @@ Drone.prototype.cuboida = function(/* Array */ blocks, w, h, d ) { _traverse[dir].width( that, w, function( ) { var block = that.world.getBlockAt( that.x, that.y, that.z ); var properBlock = properBlocks[ bi % len ]; - block.setTypeIdAndData( properBlock[0], properBlock[1], false ); + if (overwrite || block.type.equals(Material.AIR) ) { + block.setTypeIdAndData( properBlock[0], properBlock[1], false ); + } bi++; }); }); @@ -1107,6 +1127,14 @@ Drone.extend( 'door', function( door ) { .down( ); } ); +Drone.extend( 'door_iron', function( ) { + var door = 71; + this.cuboidX( door, this.dir ) + .up( ) + .cuboidX( door, 8 ) + .down( ); +} ); + Drone.extend( 'door2' , function( door ) { if ( typeof door == 'undefined' ) { door = 64; @@ -1119,6 +1147,15 @@ Drone.extend( 'door2' , function( door ) { .cuboidX( door, 9 ).down( ) .cuboidX( door, this.dir ).left( ); } ); +Drone.extend( 'door2_iron' , function( door ) { + var door = 71; + this + .cuboidX( door, this.dir ).up( ) + .cuboidX( door, 8 ).right( ) + .cuboidX( door, 9 ).down( ) + .cuboidX( door, this.dir ).left( ); +} ); + // player dirs: 0 = east, 1 = south, 2 = west, 3 = north // block dirs: 0 = east, 1 = west, 2 = south , 3 = north // sign dirs: 5 = east, 3 = south, 4 = west, 2 = north @@ -1712,18 +1749,32 @@ var _copy = function( name, w, h, d ) { } ); Drone.clipBoard[name] = {dir: this.dir, blocks: ccContent}; }; -var _garden = function( w,d ) { +var _garden = function( width, depth ) { + if ( typeof width == 'undefined' ) { + width = 10; + } + if ( typeof depth == 'undefined' ) { + depth = width; + } + var grass = 2, + red = 37, + yellow = 38, + longgrass = '31:1', + air = 0; + // make sure grass is present first - this.down( ).box(2,w,1,d ).up( ); + this.down() + .box( grass, width, 1, depth ) + .up( ); // make flowers more common than long grass - var dist = {37: 3, // red flower - 38: 3, // yellow flower - '31:1': 2, // long grass - 0: 1 - }; - - return this.rand(dist,w,1,d ); + var dist = { }; + dist[red] = 3; + dist[yellow] = 3; + dist[longgrass] = 2; + dist[air] = 1; + + return this.rand( dist, width, 1, depth, false /* don't overwrite */ ); }; var _rand = function( blockDistribution ) { @@ -1744,10 +1795,15 @@ var _rand = function( blockDistribution ) { _fisherYates(blockDistribution ); return blockDistribution; }; -Drone.extend('rand',function( dist,w,h,d ) { - var randomized = _rand(dist ); - this.boxa(randomized,w,h,d ); + +Drone.extend( 'rand', function( dist, width, height, depth, overwrite ) { + if ( typeof overwrite == 'undefined' ) { + overwrite = true; + } + var randomized = _rand( dist ); + this.boxa( randomized, width, height, depth, overwrite); } ); + var _trees = { oak: TreeType.BIG_TREE , birch: TreeType.BIRCH , From 96b1a54896abccbb2ebd6d012f7bc8f475835e38 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Wed, 19 Feb 2014 22:17:19 +0000 Subject: [PATCH 132/456] Making bukkit objects easier to identify by name (bk prefix) --- docs/release-notes.md | 6 ++++ src/main/js/lib/events.js | 19 ++++++----- src/main/js/modules/fireworks/fireworks.js | 30 ++++++++-------- src/main/js/modules/minigames/scoreboard.js | 6 ++-- src/main/js/modules/signs/menu.js | 15 ++++---- src/main/js/modules/utils/utils.js | 17 ++++++---- src/main/js/plugins/arrows.js | 13 ++++--- .../js/plugins/drone/contrib/hangtorch.js | 10 +++--- src/main/js/plugins/drone/drone.js | 34 +++++++++---------- src/main/js/plugins/homes/homes.js | 7 ++-- src/main/js/plugins/minigames/NumberGuess.js | 15 ++++---- .../js/plugins/minigames/SnowballFight.js | 16 ++++----- src/main/js/plugins/minigames/cow-clicker.js | 18 +++++----- src/main/js/plugins/spawn.js | 6 ++-- 14 files changed, 116 insertions(+), 96 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index ad86ae8..e89d0a6 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,3 +1,9 @@ +# 2014 02 19 + +## Version 2.0.5 + +Asynchronous building. Drone now builds asynchronously. + # 2014 02 11 ## Version 2.0.4 diff --git a/src/main/js/lib/events.js b/src/main/js/lib/events.js index d9a92ed..0da3004 100644 --- a/src/main/js/lib/events.js +++ b/src/main/js/lib/events.js @@ -82,9 +82,10 @@ events.on( org.bukkit.event.block.BlockBreakEvent, function( listener, evt ) { ***/ -var bkEvent = org.bukkit.event, - bkEvtExecutor = org.bukkit.plugin.EventExecutor, - bkRegListener = org.bukkit.plugin.RegisteredListener; +var bkEventPriority = org.bukkit.event.EventPriority, + bkEventExecutor = org.bukkit.plugin.EventExecutor, + bkRegisteredListener = org.bukkit.plugin.RegisteredListener, + bkEventPackage = 'org.bukkit.event.'; exports.on = function( /* String or java Class */ @@ -98,9 +99,9 @@ exports.on = function( eventExecutor; if ( typeof priority == 'undefined' ) { - priority = bkEvent.EventPriority.HIGHEST; + priority = bkEventPriority.HIGHEST; } else { - priority = bkEvent.EventPriority[priority]; + priority = bkEventPriority[priority.toUpperCase()]; } if ( typeof eventType == 'string' ) { /* @@ -111,13 +112,13 @@ exports.on = function( */ if ( typeof Java != 'undefined' ) { // nashorn environment - eventType = Java.type( 'org.bukkit.event.' + eventType ); + eventType = Java.type( bkEventPackage + eventType ); } else { - eventType = eval( 'org.bukkit.event.' + eventType ); + eventType = eval( bkEventPackage + eventType ); } } handlerList = eventType.getHandlerList( ); - eventExecutor = new bkEvtExecutor( ) { + eventExecutor = new bkEventExecutor( ) { execute: function( l, e ) { handler( listener.reg, e ); } @@ -130,7 +131,7 @@ exports.on = function( The workaround is to make the ScriptCraftPlugin java class a Listener. Should only unregister() registered plugins in ScriptCraft js code. */ - listener.reg = new bkRegListener( __plugin, eventExecutor, priority, __plugin, true ); + listener.reg = new bkRegisteredListener( __plugin, eventExecutor, priority, __plugin, true ); handlerList.register( listener.reg ); return listener.reg; }; diff --git a/src/main/js/modules/fireworks/fireworks.js b/src/main/js/modules/fireworks/fireworks.js index bcaffe3..345e96e 100644 --- a/src/main/js/modules/fireworks/fireworks.js +++ b/src/main/js/modules/fireworks/fireworks.js @@ -37,35 +37,37 @@ location. For example... create a firework at the given location */ var firework = function( location ) { - var Color = org.bukkit.Color; - var FireworkEffect = org.bukkit.FireworkEffect; - var EntityType = org.bukkit.entity.EntityType; + var bkColor = org.bukkit.Color; + var bkFireworkEffect = org.bukkit.FireworkEffect; + var bkEntityType = org.bukkit.entity.EntityType; var randInt = function( n ) { return Math.floor( Math.random() * n ); }; var getColor = function( i ) { var colors = [ - Color.AQUA, Color.BLACK, Color.BLUE, Color.FUCHSIA, Color.GRAY, - Color.GREEN, Color.LIME, Color.MAROON, Color.NAVY, Color.OLIVE, - Color.ORANGE, Color.PURPLE, Color.RED, Color.SILVER, Color.TEAL, - Color.WHITE, Color.YELLOW]; + bkColor.AQUA, bkColor.BLACK, bkColor.BLUE, bkColor.FUCHSIA, bkColor.GRAY, + bkColor.GREEN, bkColor.LIME, bkColor.MAROON, bkColor.NAVY, bkColor.OLIVE, + bkColor.ORANGE, bkColor.PURPLE, bkColor.RED, bkColor.SILVER, bkColor.TEAL, + bkColor.WHITE, bkColor.YELLOW]; return colors[i]; }; - var fw = location.world.spawnEntity(location, EntityType.FIREWORK); + var fw = location.world.spawnEntity(location, bkEntityType.FIREWORK); var fwm = fw.getFireworkMeta(); - var fwTypes = [FireworkEffect.Type.BALL, - FireworkEffect.Type.BALL_LARGE, - FireworkEffect.Type.BURST, - FireworkEffect.Type.CREEPER, - FireworkEffect.Type.STAR]; + var fwTypes = [ + bkFireworkEffect.Type.BALL, + bkFireworkEffect.Type.BALL_LARGE, + bkFireworkEffect.Type.BURST, + bkFireworkEffect.Type.CREEPER, + bkFireworkEffect.Type.STAR + ]; var type = fwTypes[ randInt( 5 ) ]; var r1i = randInt( 17 ); var r2i = randInt( 17 ); var c1 = getColor( r1i ); var c2 = getColor( r2i ); - var effectBuilder = FireworkEffect.builder() + var effectBuilder = bkFireworkEffect.builder() .flicker( Math.round( Math.random() ) == 0 ) .withColor( c1 ) .withFade( c2 ) diff --git a/src/main/js/modules/minigames/scoreboard.js b/src/main/js/modules/minigames/scoreboard.js index c526203..7fc4cae 100644 --- a/src/main/js/modules/minigames/scoreboard.js +++ b/src/main/js/modules/minigames/scoreboard.js @@ -1,3 +1,4 @@ +var bkDisplaySlot = org.bukkit.scoreboard.DisplaySlot; /* The scoreboard is a simple wrapper around the Bukkit Scoreboard API. It's only concerned with display of scores, not maintaining them - that's the game's job. @@ -5,7 +6,6 @@ module.exports = function( options ) { var temp = {}; var ccScoreboard; - var DisplaySlot = org.bukkit.scoreboard.DisplaySlot; return { start: function( ) { @@ -16,7 +16,7 @@ module.exports = function( options ) { for ( objective in options ) { ccObj = ccScoreboard.registerNewObjective( objective, 'dummy' ); for ( slot in options[ objective ] ) { - ccObj.displaySlot = DisplaySlot[ slot ]; + ccObj.displaySlot = bkDisplaySlot[ slot ]; ccObj.displayName = options[ objective ][ slot ]; } } @@ -26,7 +26,7 @@ module.exports = function( options ) { for ( objective in options ) { ccScoreboard.getObjective(objective).unregister(); for ( slot in options[ objective ] ) { - ccScoreboard.clearSlot( DisplaySlot[ slot ] ); + ccScoreboard.clearSlot( bkDisplaySlot[ slot ] ); } } }, diff --git a/src/main/js/modules/signs/menu.js b/src/main/js/modules/signs/menu.js index 16c011f..4d10c1d 100644 --- a/src/main/js/modules/signs/menu.js +++ b/src/main/js/modules/signs/menu.js @@ -1,6 +1,9 @@ -var utils = require('utils'); -var stringExt = require('utils/string-exts'); -var _store = {}; +var utils = require('utils'), + stringExt = require('utils/string-exts'), + _store = {}, + bkBukkit = org.bukkit.Bukkit, + bkSign = org.bukkit.block.Sign; + /* Define the signs module - signs are persistent (that is - a menu sign will still be a menu after the @@ -158,12 +161,12 @@ signs.menu = function( /* String */ label, /* Array */ options, /* Function */ c var len = signsOfSameLabel.length; for ( i = 0; i < len; i++ ) { var loc = signsOfSameLabel[i]; - var world = org.bukkit.Bukkit.getWorld(loc.world); + var world = bkBukkit.getWorld(loc.world); if ( !world ) { continue; } var block = world.getBlockAt( loc.x, loc.y, loc.z ); - if ( block.state instanceof org.bukkit.block.Sign ) { + if ( block.state instanceof bkSign ) { convertToMenuSign( block.state, false ); defragged.push( loc ); } @@ -187,7 +190,7 @@ events.on( 'player.PlayerInteractEvent', function( listener, event ) { a sign, then update it. */ - if ( ! event.clickedBlock.state instanceof org.bukkit.block.Sign ) { + if ( ! event.clickedBlock.state instanceof bkSign ) { return; } var evtLocStr = utils.locationToString(event.clickedBlock.location); diff --git a/src/main/js/modules/utils/utils.js b/src/main/js/modules/utils/utils.js index 0244fda..ac86768 100644 --- a/src/main/js/modules/utils/utils.js +++ b/src/main/js/modules/utils/utils.js @@ -1,5 +1,8 @@ 'use strict'; -var File = java.io.File; +var File = java.io.File, + bkBukkit = org.bukkit.Bukkit, + bkLocation = org.bukkit.Location, + bkBlockCommandSender = org.bukkit.command.BlockCommandSender; /************************************************************************ ## Utilities Module @@ -47,7 +50,7 @@ var _player = function ( playerName ) { } } else { if ( typeof playerName == 'string' ) - return org.bukkit.Bukkit.getPlayer( playerName ); + return bkBukkit.getPlayer( playerName ); else return playerName; // assumes it's a player object } @@ -126,11 +129,11 @@ exports.locationFromJSON = function( json ) { var world; if ( json.constuctor == Array ) { // for support of legacy format - world = org.bukkit.Bukkit.getWorld( json[0] ); - return new org.bukkit.Location( world, json[1], json[2] , json[3] ); + world = bkBukkit.getWorld( json[0] ); + return new bkLocation( world, json[1], json[2] , json[3] ); } else { - world = org.bukkit.Bukkit.getWorld( json.world ); - return new org.bukkit.Location( world, json.x, json.y , json.z, json.yaw, json.pitch ); + world = bkBukkit.getWorld( json.world ); + return new bkLocation( world, json.x, json.y , json.z, json.yaw, json.pitch ); } }; @@ -162,7 +165,7 @@ An [org.bukkit.Location][bkloc] object. exports.getPlayerPos = function( player ) { player = _player( player ); if ( player ) { - if ( player instanceof org.bukkit.command.BlockCommandSender ) + if ( player instanceof bkBlockCommandSender ) return player.block.location; else return player.location; diff --git a/src/main/js/plugins/arrows.js b/src/main/js/plugins/arrows.js index 07cc12e..4c2c879 100644 --- a/src/main/js/plugins/arrows.js +++ b/src/main/js/plugins/arrows.js @@ -28,6 +28,10 @@ player23's arrows explosive. var signs = require('signs'), fireworks = require('fireworks'), utils = require('utils'), + bkTeleportCause = org.bukkit.event.player.PlayerTeleportEvent.TeleportCause, + bkArrow = org.bukkit.entity.Arrow, + bkPlayer = org.bukkit.entity.Player, + bkTreeType = org.bukkit.TreeType, EXPLOSIVE_YIELD = 2.5, _store = { players: { } }, arrows = plugin( 'arrows', { store: _store }, true ), @@ -81,7 +85,6 @@ var _onArrowHit = function( listener, event ) { shooter = projectile.shooter, fireworkCount = 5, arrowType, - TeleportCause = org.bukkit.event.player.PlayerTeleportEvent.TeleportCause, launch = function( ) { fireworks.firework( projectile.location ); if ( --fireworkCount ) { @@ -89,8 +92,8 @@ var _onArrowHit = function( listener, event ) { } }; - if (projectile instanceof org.bukkit.entity.Arrow - && shooter instanceof org.bukkit.entity.Player) { + if (projectile instanceof bkArrow + && shooter instanceof bkPlayer) { arrowType = arrows.store.players[ shooter.name ]; @@ -101,11 +104,11 @@ var _onArrowHit = function( listener, event ) { break; case 2: projectile.remove(); - shooter.teleport( projectile.location, TeleportCause.PLUGIN ); + shooter.teleport( projectile.location, bkTeleportCause.PLUGIN ); break; case 3: projectile.remove(); - world.generateTree( projectile.location, org.bukkit.TreeType.BIG_TREE ); + world.generateTree( projectile.location, bkTreeType.BIG_TREE ); break; case 4: projectile.remove(); diff --git a/src/main/js/plugins/drone/contrib/hangtorch.js b/src/main/js/plugins/drone/contrib/hangtorch.js index 6523592..aa15c57 100644 --- a/src/main/js/plugins/drone/contrib/hangtorch.js +++ b/src/main/js/plugins/drone/contrib/hangtorch.js @@ -1,8 +1,10 @@ var Drone = require('../drone').Drone; -var Material = org.bukkit.Material; -function canHang(material){ - if (material.equals(Material.AIR) || - material.equals(Material.VINE) ) { +var bkMaterial = org.bukkit.Material; + +function canHang( material ) { + + if ( material.equals(bkMaterial.AIR) || + material.equals(bkMaterial.VINE) ) { return true; } else { return false; diff --git a/src/main/js/plugins/drone/drone.js b/src/main/js/plugins/drone/drone.js index ce9a65f..e44e3de 100644 --- a/src/main/js/plugins/drone/drone.js +++ b/src/main/js/plugins/drone/drone.js @@ -1,10 +1,10 @@ var utils = require('utils'), blocks = require('blocks'), - Location = org.bukkit.Location, - Player = org.bukkit.entity.Player, - Sign = org.bukkit.block.Sign, - TreeType = org.bukkit.TreeType, - Material = org.bukkit.Material; + bkLocation = org.bukkit.Location, + bkPlayer = org.bukkit.entity.Player, + bkSign = org.bukkit.block.Sign, + bkTreeType = org.bukkit.TreeType, + bkMaterial = org.bukkit.Material; /********************************************************************* ## Drone Plugin @@ -669,7 +669,7 @@ var putSign = function( texts, x, y, z, blockId, meta, world ) { putBlock( x, y, z, blockId, meta, world ); block = world.getBlockAt( x, y, z ); state = block.state; - if ( state instanceof Sign ) { + if ( state instanceof bkSign ) { for ( i = 0; i < texts.length; i++ ) { state.setLine( i % 4, texts[ i ] ); } @@ -681,7 +681,7 @@ var Drone = function( x, y, z, dir, world ) { this.record = false; var usePlayerCoords = false; var player = self; - if ( x instanceof Player ) { + if ( x instanceof bkPlayer ) { player = x; } var playerPos = utils.getPlayerPos( player ); @@ -694,7 +694,7 @@ var Drone = function( x, y, z, dir, world ) { that.world = loc.world; }; var mp = utils.getMousePos( player ); - if ( typeof x == 'undefined' || x instanceof Player ) { + if ( typeof x == 'undefined' || x instanceof bkPlayer ) { if ( mp ) { populateFromLocation( mp ); if ( playerPos ) { @@ -713,7 +713,7 @@ var Drone = function( x, y, z, dir, world ) { populateFromLocation( playerPos ); } } else { - if ( arguments[0] instanceof Location ) { + if ( arguments[0] instanceof bkLocation ) { populateFromLocation( arguments[ 0 ] ); } else { this.x = x; @@ -898,7 +898,7 @@ Drone.extend( 'chkpt', function( name ) { } ); Drone.extend( 'move', function( ) { - if ( arguments[0] instanceof Location ) { + if ( arguments[0] instanceof bkLocation ) { this.x = arguments[0].x; this.y = arguments[0].y; this.z = arguments[0].z; @@ -980,7 +980,7 @@ Drone.extend( 'down', function( n ) { // position // Drone.prototype.getLocation = function( ) { - return new Location( this.world, this.x, this.y, this.z ); + return new bkLocation( this.world, this.x, this.y, this.z ); }; // // building @@ -1040,7 +1040,7 @@ Drone.prototype.cuboida = function(/* Array */ blocks, w, h, d, overwrite ) { _traverse[dir].width( that, w, function( ) { var block = that.world.getBlockAt( that.x, that.y, that.z ); var properBlock = properBlocks[ bi % len ]; - if (overwrite || block.type.equals(Material.AIR) ) { + if (overwrite || block.type.equals(bkMaterial.AIR) ) { block.setTypeIdAndData( properBlock[0], properBlock[1], false ); } bi++; @@ -1805,10 +1805,10 @@ Drone.extend( 'rand', function( dist, width, height, depth, overwrite ) { } ); var _trees = { - oak: TreeType.BIG_TREE , - birch: TreeType.BIRCH , - jungle: TreeType.JUNGLE, - spruce: TreeType.REDWOOD + oak: bkTreeType.BIG_TREE , + birch: bkTreeType.BIRCH , + jungle: bkTreeType.JUNGLE, + spruce: bkTreeType.REDWOOD }; for ( var p in _trees ) { Drone.extend(p, function( v ) { @@ -1817,7 +1817,7 @@ for ( var p in _trees ) { if ( block.typeId == 2 ) { this.up( ); } - var treeLoc = new Location(this.world,this.x,this.y,this.z ); + var treeLoc = new bkLocation(this.world,this.x,this.y,this.z ); var successful = treeLoc.world.generateTree(treeLoc,v ); if ( block.typeId == 2 ) { this.down( ); diff --git a/src/main/js/plugins/homes/homes.js b/src/main/js/plugins/homes/homes.js index 94fb22e..a4b0af7 100644 --- a/src/main/js/plugins/homes/homes.js +++ b/src/main/js/plugins/homes/homes.js @@ -60,7 +60,8 @@ The following administration options can only be used by server operators... ***/ var utils = require('utils'), - TeleportCause = org.bukkit.event.player.PlayerTeleportEvent.TeleportCause, + bkTeleportCause = org.bukkit.event.player.PlayerTeleportEvent.TeleportCause, + bkBukkit = org.bukkit.Bukkit, _store = { houses: { }, openHouses: { }, @@ -113,7 +114,7 @@ var homes = plugin( 'homes', { return; } homeLoc = utils.locationFromJSON( loc ); - guest.teleport(homeLoc, TeleportCause.PLUGIN); + guest.teleport(homeLoc, bkTeleportCause.PLUGIN); }, /* determine whether a guest is allow visit a host's home @@ -185,7 +186,7 @@ var homes = plugin( 'homes', { player = utils.player( player ); // if home is public - all players if ( _store.openHouses[player.name] ) { - onlinePlayers = org.bukkit.Bukkit.getOnlinePlayers(); + onlinePlayers = bkBukkit.getOnlinePlayers(); for ( i = 0; i < onlinePlayers.length; i++ ) { if ( onlinePlayers[i].name != player.name) { result.push( onlinePlayers[i].name ); diff --git a/src/main/js/plugins/minigames/NumberGuess.js b/src/main/js/plugins/minigames/NumberGuess.js index 6fb0938..56770b0 100644 --- a/src/main/js/plugins/minigames/NumberGuess.js +++ b/src/main/js/plugins/minigames/NumberGuess.js @@ -15,12 +15,13 @@ Once the game begins, guess a number by typing the `/` character followed by a number between 1 and 10. ***/ -var Prompt = org.bukkit.conversations.Prompt, - ConversationFactory = org.bukkit.conversations.ConversationFactory, - ConversationPrefix = org.bukkit.conversations.ConversationPrefix; +var bkPrompt = org.bukkit.conversations.Prompt, + bkConversationFactory = org.bukkit.conversations.ConversationFactory, + bkConversationPrefix = org.bukkit.conversations.ConversationPrefix, + bkBukkit = org.bukkit.Bukkit; var sb = function( cmd ) { - org.bukkit.Bukkit.dispatchCommand( server.consoleSender, 'scoreboard ' + cmd ) ; + bkBukkit.dispatchCommand( server.consoleSender, 'scoreboard ' + cmd ) ; }; exports.Game_NumberGuess = { @@ -34,7 +35,7 @@ exports.Game_NumberGuess = { var number = Math.ceil( Math.random() * 10 ); - var prompt = new Prompt( ) { + var prompt = new bkPrompt( ) { getPromptText: function( ctx ) { var hint = ''; @@ -72,12 +73,12 @@ exports.Game_NumberGuess = { return true; } }; - var convPrefix = new ConversationPrefix( ) { + var convPrefix = new bkConversationPrefix( ) { getPrefix: function( ctx ) { return '[1-10] '; } }; - new ConversationFactory( __plugin ) + new bkConversationFactory( __plugin ) .withModality( true ) .withFirstPrompt( prompt ) .withPrefix( convPrefix ) diff --git a/src/main/js/plugins/minigames/SnowballFight.js b/src/main/js/plugins/minigames/SnowballFight.js index afa17a9..b72913f 100644 --- a/src/main/js/plugins/minigames/SnowballFight.js +++ b/src/main/js/plugins/minigames/SnowballFight.js @@ -42,11 +42,11 @@ cover to make the game more fun. ***/ -var GameMode = org.bukkit.GameMode, - EntityDamageByEntityEvent = org.bukkit.event.entity.EntityDamageByEntityEvent, - ItemStack = org.bukkit.inventory.ItemStack, - Material = org.bukkit.Material, - Snowball = org.bukkit.entity.Snowball; +var bkGameMode = org.bukkit.GameMode, + bkEntityDamageByEntityEvent = org.bukkit.event.entity.EntityDamageByEntityEvent, + bkItemStack = org.bukkit.inventory.ItemStack, + bkMaterial = org.bukkit.Material, + bkSnowball = org.bukkit.entity.Snowball; var _startGame = function( gameState ) { var i, @@ -73,7 +73,7 @@ var _startGame = function( gameState ) { for ( i = 0; i < team.length; i++ ) { player = server.getPlayer( team[i] ); gameState.savedModes[ player.name ] = player.gameMode; - player.gameMode = GameMode.SURVIVAL; + player.gameMode = bkGameMode.SURVIVAL; player.inventory.addItem( gameState.ammo ); } } @@ -112,7 +112,7 @@ var _endGame = function( gameState ) { player.sendMessage( scores ); } } - handlerList = EntityDamageByEntityEvent.getHandlerList(); + handlerList = bkEntityDamageByEntityEvent.getHandlerList(); handlerList.unregister( gameState.listener ); gameState.inProgress = false; }; @@ -197,7 +197,7 @@ var createGame = function( duration, teams ) { return { start: function( ) { _startGame( _gameState ); - _gameState.listener = events.on('entity.EntityDamageByEntityEvent',_onSnowballHit); + _gameState.listener = events.on(bkEntityDamageByEntityEvent,_onSnowballHit); new java.lang.Thread( function( ) { while ( _gameState.duration-- ) { java.lang.Thread.sleep( 1000 ); // sleep 1,000 millisecs (1 second) diff --git a/src/main/js/plugins/minigames/cow-clicker.js b/src/main/js/plugins/minigames/cow-clicker.js index d03027d..df83b0f 100644 --- a/src/main/js/plugins/minigames/cow-clicker.js +++ b/src/main/js/plugins/minigames/cow-clicker.js @@ -42,10 +42,10 @@ your own mini-game... ***/ var store = {}, - Bukkit = org.bukkit.Bukkit, - Cow = org.bukkit.entity.Cow, - Sound = org.bukkit.Sound, - OfflinePlayer = org.bukkit.OfflinePlayer, + bkBukkit = org.bukkit.Bukkit, + bkCow = org.bukkit.entity.Cow, + bkSound = org.bukkit.Sound, + bkOfflinePlayer = org.bukkit.OfflinePlayer, scoreboardConfig = { cowclicker: { SIDEBAR: 'Cows Clicked' @@ -66,14 +66,14 @@ var _onPlayerInteract = function( listener, event ) { loc.world.playSound( loc, snd, vol, pitch ); }; - if ( clickedEntity instanceof Cow) { + if ( clickedEntity instanceof bkCow) { store[ player.name ].score++; scoreboard.update( 'cowclicker', player, store[ player.name ].score ); - Bukkit.dispatchCommand( player, 'me clicked a cow!' ); - sound( Sound.CLICK, 1, 1 ); + bkBukkit.dispatchCommand( player, 'me clicked a cow!' ); + sound( bkSound.CLICK, 1, 1 ); setTimeout( function( ) { - sound( Sound.COW_HURT, 10, 0.85 ) ; + sound( bkSound.COW_HURT, 10, 0.85 ) ; }, 200 ); } }; @@ -128,7 +128,7 @@ var _addPlayer = function( player, score ) { var _removePlayer = function( player, notify ) { - if ( player instanceof OfflinePlayer && player.player ) { + if ( player instanceof bkOfflinePlayer && player.player ) { player = player.player; } diff --git a/src/main/js/plugins/spawn.js b/src/main/js/plugins/spawn.js index ce0c571..30803c3 100644 --- a/src/main/js/plugins/spawn.js +++ b/src/main/js/plugins/spawn.js @@ -17,11 +17,9 @@ for a list of possible entities (creatures) which can be spawned. ***/ var entities = [], - EntityType = org.bukkit.entity.EntityType; + bkEntityType = org.bukkit.entity.EntityType; -var MaterialEnum = Packages.MaterialEnum; - -var entitytypes = EntityType.values(); +var entitytypes = bkEntityType.values(); for ( var t in entitytypes ) { if ( entitytypes[t] && entitytypes[t].ordinal ) { entities.push(entitytypes[t].name()); From d97936ed1049f5aa76f097025b430f6fc4e911b3 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Wed, 19 Feb 2014 22:20:06 +0000 Subject: [PATCH 133/456] updated release notes for 2.0.5 release --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index e89d0a6..3c18d1a 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -3,6 +3,7 @@ ## Version 2.0.5 Asynchronous building. Drone now builds asynchronously. +Fixed Issue #119 (exceptions on reload/stop) # 2014 02 11 From 6b3314fa275381bc821be268b4c1396d8186bdc1 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Fri, 28 Feb 2014 10:10:05 +0000 Subject: [PATCH 134/456] fix typos in comments --- src/main/js/modules/sc-mqtt.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/js/modules/sc-mqtt.js b/src/main/js/modules/sc-mqtt.js index 96197ab..da1e135 100644 --- a/src/main/js/modules/sc-mqtt.js +++ b/src/main/js/modules/sc-mqtt.js @@ -18,14 +18,14 @@ present in the CraftBukkit classpath. To use this module, you should command... ```sh - java -classpath sc-mqtt.jar;craftbukit.jar org.bukkit.craftbukkit.Main + java -classpath sc-mqtt.jar;craftbukkit.jar org.bukkit.craftbukkit.Main ``` If you're using Mac OS, create a new craftbukkit-sc-mqtt.command file and edit it (using TextWrangler or another text editor) ... ```sh - java -classpath sc-mqtt.jar:craftbukkit.jar org.bukit.craftbukkit.Main + java -classpath sc-mqtt.jar:craftbukkit.jar org.bukkit.craftbukkit.Main ``` 4. Execute the craftbukkit-sc-mqtt batch file / command file to start From cd52379b5ca486bf8b79fe22ded1a51f674f4448 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Fri, 28 Feb 2014 10:11:28 +0000 Subject: [PATCH 135/456] indentation --- src/main/js/plugins/arrows.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/js/plugins/arrows.js b/src/main/js/plugins/arrows.js index 4c2c879..82a9fd9 100644 --- a/src/main/js/plugins/arrows.js +++ b/src/main/js/plugins/arrows.js @@ -88,7 +88,7 @@ var _onArrowHit = function( listener, event ) { launch = function( ) { fireworks.firework( projectile.location ); if ( --fireworkCount ) { - setTimeout( launch, 2000 ); + setTimeout( launch, 2000 ); } }; From 4b0fb7565a7898a8ebca6c92e8d6497e42c72b77 Mon Sep 17 00:00:00 2001 From: Walter Higgins Date: Thu, 6 Mar 2014 13:17:14 +0000 Subject: [PATCH 136/456] Fix issue #122 --- src/main/js/plugins/minigames/SnowballFight.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/js/plugins/minigames/SnowballFight.js b/src/main/js/plugins/minigames/SnowballFight.js index b72913f..9dccae6 100644 --- a/src/main/js/plugins/minigames/SnowballFight.js +++ b/src/main/js/plugins/minigames/SnowballFight.js @@ -139,7 +139,7 @@ var _getTeam = function( player, pteams ) { var createGame = function( duration, teams ) { var players, i, - _snowBalls = new ItemStack( Material.SNOW_BALL, 64 ); + _snowBalls = new bkItemStack( bkMaterial.SNOW_BALL, 64 ); var _gameState = { teams: teams, @@ -179,7 +179,7 @@ var createGame = function( duration, teams ) { */ var _onSnowballHit = function( l, event ) { var snowball = event.damager; - if ( !snowball || !( snowball instanceof Snowball ) ) { + if ( !snowball || !( snowball instanceof bkSnowball ) ) { return; } var throwersTeam = _getTeam( snowball.shooter, _gameState.teams ); From 64913338a73fbc4fa90a1ecb47cffd371350d210 Mon Sep 17 00:00:00 2001 From: Walter Higgins Date: Thu, 6 Mar 2014 17:59:10 +0000 Subject: [PATCH 137/456] fixes issue #123 --- src/main/js/plugins/spawn.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/js/plugins/spawn.js b/src/main/js/plugins/spawn.js index 30803c3..1cfd566 100644 --- a/src/main/js/plugins/spawn.js +++ b/src/main/js/plugins/spawn.js @@ -37,5 +37,5 @@ command( 'spawn', function( parameters, sender ) { } var world = location.world; var type = ('' + parameters[0]).toUpperCase(); - world.spawnEntity( location, EntityType[type] ); + world.spawnEntity( location, bkEntityType[type] ); }, entities ); From fae2b6aac739b24dc3d3c19d39242e9e3042363e Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 8 Mar 2014 21:01:25 +0000 Subject: [PATCH 138/456] Improved Drone background-processing. --- docs/API-Reference.md | 4 +- docs/release-notes.md | 8 +++ src/main/js/lib/scriptcraft.js | 6 ++ src/main/js/plugins/drone/drone.js | 92 +++++++++++++++-------------- src/main/js/plugins/drone/sphere.js | 2 +- 5 files changed, 65 insertions(+), 47 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index aa45d48..a4b230b 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -884,14 +884,14 @@ present in the CraftBukkit classpath. To use this module, you should command... ```sh - java -classpath sc-mqtt.jar;craftbukit.jar org.bukkit.craftbukkit.Main + java -classpath sc-mqtt.jar;craftbukkit.jar org.bukkit.craftbukkit.Main ``` If you're using Mac OS, create a new craftbukkit-sc-mqtt.command file and edit it (using TextWrangler or another text editor) ... ```sh - java -classpath sc-mqtt.jar:craftbukkit.jar org.bukit.craftbukkit.Main + java -classpath sc-mqtt.jar:craftbukkit.jar org.bukkit.craftbukkit.Main ``` 4. Execute the craftbukkit-sc-mqtt batch file / command file to start diff --git a/docs/release-notes.md b/docs/release-notes.md index 3c18d1a..82b80a3 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,3 +1,11 @@ +# 2014 03 08 + +## Version 2.0.6 + +Fixed issues #122 #123 + +Improved background processing of Drone build commands. + # 2014 02 19 ## Version 2.0.5 diff --git a/src/main/js/lib/scriptcraft.js b/src/main/js/lib/scriptcraft.js index 8d57efa..6b0081f 100644 --- a/src/main/js/lib/scriptcraft.js +++ b/src/main/js/lib/scriptcraft.js @@ -548,6 +548,12 @@ function __onEnable ( __engine, __plugin, __script ) global.refresh = function( ) { + if ( typeof self !== 'undefined' ) { + if ( !self.op ) { + self.sendMessage('Only operators can refresh()'); + return; + } + } __plugin.pluginLoader.disablePlugin( __plugin ); __plugin.pluginLoader.enablePlugin( __plugin ); }; diff --git a/src/main/js/plugins/drone/drone.js b/src/main/js/plugins/drone/drone.js index e44e3de..432ff6e 100644 --- a/src/main/js/plugins/drone/drone.js +++ b/src/main/js/plugins/drone/drone.js @@ -750,33 +750,25 @@ exports.Drone = Drone; exports.blocks = blocks; Drone.queue = []; -Drone.processingQueue = false; -Drone.MAX_QUEUE_SIZE = 100000; -Drone.tick = setInterval( function() { - if ( Drone.processingQueue ) { - return; +Drone.opsPerSec = 10; +Drone.processQueue = function(){ + var process = Drone.queue.shift(); + if (process){ + try { + process(); + } catch( e ) { + console.log('Drone build error: %s', e); + } } - var maxOpsPerTick = Math.floor(Math.max(10,Math.sqrt(Drone.queue.length))) ; - var op; - Drone.processingQueue = true; - while ( maxOpsPerTick > 0 ) { - op = Drone.queue.shift(); - if (!op){ - Drone.processingQueue = false; - return; - } - var block = op.world.getBlockAt( op.x, op.y, op.z ); - block.setTypeIdAndData( op.typeid, op.meta, false ); - // wph 20130210 - dont' know if this is a bug in bukkit but for chests, - // the metadata is ignored (defaults to 2 - south facing) - // only way to change data is to set it using property/bean. - block.data = op.meta; - maxOpsPerTick--; + setTimeout(Drone.processQueue,1000/Drone.opsPerSec); +}; +setTimeout(Drone.processQueue,1000/Drone.opsPerSec); +addUnloadHandler(function(){ + var pendingBuildOps = Drone.queue.length; + if (pendingBuildOps > 0){ + console.warn('There were ' + pendingBuildOps + ' pending build operations which were cancelled'); } - Drone.processingQueue = false; - return; -}, 1); - +}); // // add custom methods to the Drone object using this function // @@ -1054,7 +1046,7 @@ Drone.prototype.cuboida = function(/* Array */ blocks, w, h, d, overwrite ) { faster cuboid because blockid, meta and world must be provided use this method when you need to repeatedly place blocks */ -Drone.prototype.cuboidX = function( blockType, meta, w, h, d ) { +Drone.prototype.cuboidX = function( blockType, meta, w, h, d, immediate ) { if ( typeof h == 'undefined' ) { h = 1; @@ -1065,23 +1057,32 @@ Drone.prototype.cuboidX = function( blockType, meta, w, h, d ) { if ( typeof w == 'undefined' ) { w = 1; } + if ( ( w * h * d ) >= 1000000 ) { + this.sign([ + 'Build too Big!', + 'width:' + w, + 'height:' + h, + 'depth:' + d + ], 68); + console.warn('Build too big! ' + w + ' X ' + h + ' X ' + d); + return this; + } var that = this; var dir = this.dir; + if ( !immediate ) { + var clone = Drone.clone(this); + Drone.queue.push(this.cuboidX.bind(clone, blockType, meta, w, h, d, true));; + return this; + } var depthFunc = function( ) { - var len = Drone.queue.length; - if ( len < Drone.MAX_QUEUE_SIZE ) { - Drone.queue.push({ - world: that.world, - x: that.x, - y: that.y, - z:that.z, - typeid: blockType, - meta: meta - }); - } else { - throw new Error('Drone is too busy!'); - } + + var block = that.world.getBlockAt( that.x, that.y, that.z ); + block.setTypeIdAndData( blockType, meta, false ); + // wph 20130210 - dont' know if this is a bug in bukkit but for chests, + // the metadata is ignored (defaults to 2 - south facing) + // only way to change data is to set it using property/bean. + block.data = meta; }; var heightFunc = function( ) { _traverse[dir].depth( that, d, depthFunc ); @@ -1337,7 +1338,6 @@ var _getStrokeDir = function( x,y ) { if you're drawing anything that bends it ends up here. */ var _arc2 = function( params ) { - var drone = params.drone; var orientation = params.orientation?params.orientation:'horizontal'; var quadrants = params.quadrants?params.quadrants:{ @@ -1490,12 +1490,13 @@ var _cylinder0 = function( block,radius,height,exactParams ) { radius: radius, fill: false, orientation: 'horizontal', - stack: height, + stack: height }; if ( exactParams ) { - arcParams.blockType = exactParams.blockType; - arcParams.meta = exactParams.meta; + for ( var p in exactParams ) { + arcParams[p] = exactParams[p]; + } }else{ var md = this._getBlockIdAndMeta(block ); arcParams.blockType = md[0]; @@ -1825,7 +1826,10 @@ for ( var p in _trees ) { }; }(_trees[p] ) ); } - +Drone.clone = function(origin) { + var result = {x: origin.x, y: origin.y, z: origin.z, world: origin.world, dir: origin.dir}; + return result; +}; // // Drone's clipboard // diff --git a/src/main/js/plugins/drone/sphere.js b/src/main/js/plugins/drone/sphere.js index 7b91a4f..a4c27b6 100644 --- a/src/main/js/plugins/drone/sphere.js +++ b/src/main/js/plugins/drone/sphere.js @@ -74,7 +74,7 @@ Drone.extend( 'sphere', function( block, radius ) { this.up( v ) .fwd( h ) .right( h ) - .cylinder( block, sr, sh, { blockType: bm[0], meta: bm[1] } ) + .cylinder( block, sr, sh, { blockType: bm[0], meta: bm[1]} ) .left( h ) .back( h ) .down( v ); From 1a1837cc7e27592de2857e096aaa30f04010c215 Mon Sep 17 00:00:00 2001 From: Robert Storlind Date: Mon, 10 Mar 2014 14:10:54 +0100 Subject: [PATCH 139/456] Maze generation using Drone Point at the ground. Try /js amazing(5,7) --- src/main/js/plugins/drone/contrib/mazegen.js | 108 +++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 src/main/js/plugins/drone/contrib/mazegen.js diff --git a/src/main/js/plugins/drone/contrib/mazegen.js b/src/main/js/plugins/drone/contrib/mazegen.js new file mode 100644 index 0000000..d5ecdcb --- /dev/null +++ b/src/main/js/plugins/drone/contrib/mazegen.js @@ -0,0 +1,108 @@ +// Maze generation based on http://rosettacode.org/wiki/Maze_generation#JavaScript + +var Drone = require('../drone').Drone; + +function maze_make(x,y) { + var n=x*y-1; + if (n<0) { + echo("illegal maze dimensions"); + return ({x: 0, y: 0}); + } + var horiz=[]; for (var j= 0; j0 && j0 && (j != here[0]+1 || k != here[1]+1)); + } + while (00 && m.verti[j/2-1][Math.floor(k/4)]) + line[k]= ' '; + else + line[k]= '-'; + else + for (var k=0; k0 && m.horiz[(j-1)/2][k/4-1]) + line[k]= ' '; + else + line[k]= '|'; + else + line[k]= ' '; + if (0 == j) line[1]= line[2]= line[3]= ' '; + if (m.x*2-1 == j) line[4*m.y]= ' '; + text.push(line.join('')+' \r\n'); // TWEAKED: space added to get an even number of columns + } + return text.join(''); +} + +// ScriptCraft stuff starts here +// Helper function to parse the ASCII art into Drone actions +// You might also consider creating a new maze_display but for now this will do the work +function maze_draw(maze_string, d) { + // d is the Drone to use + d.up().chkpt('maze-start'); + for (var j = 0; j < maze_string.length; j += 2) { + switch(maze_string.substr(j, 2)) { + case ' ': + d.box(0).fwd(); // Make sure to empty this position + break; + case '\r\n': + d.move('maze-start'); + d.right().chkpt('maze-start'); + break; + default: // '+ ', '+-', '--', '| ' + if (j == 0) { + d.box(blocks.glowstone); // highlight the maze entry and exit + } else if (j == maze_string.length - 4) { + d.box(blocks.glass); + } else { + d.box(blocks.oak); + } + d.fwd(); + } + } +} + +// User-facing code starts here +// Example: Try /js amazing(5,7) +Drone.extend('amazing', function(size_x, size_y) { + m = maze_make(size_x, size_y); + if (m.x > 0 && m.y > 0) { + maze_draw(maze_display(m), this); + } +}); + From e40786d727a4fe56cbb4f773042ddfacc7ea668b Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Mon, 10 Mar 2014 23:17:58 +0000 Subject: [PATCH 140/456] Fix issue #115 --- src/main/js/lib/tabcomplete.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/main/js/lib/tabcomplete.js b/src/main/js/lib/tabcomplete.js index a1114f0..3cbdec2 100644 --- a/src/main/js/lib/tabcomplete.js +++ b/src/main/js/lib/tabcomplete.js @@ -7,7 +7,7 @@ var _isJavaObject = function(o){ var result = false; try { o.hasOwnProperty( 'testForJava' ); - }catch (e){ + } catch (e) { // java will throw an error when an attempt is made to access the // hasOwnProperty method. (it won't exist for Java objects) result = true; @@ -34,6 +34,7 @@ var _getProperties = function( o ) { j, isObjectMethod, typeofProperty; + if ( _isJavaObject( o ) ) { propertyLoop: for ( i in o ) { @@ -103,11 +104,12 @@ var onTabCompleteJS = function( result, cmdSender, pluginCmd, cmdAlias, cmdArgs if ( pluginCmd.name == 'jsp' ) { return tabCompleteJSP( result, cmdSender, pluginCmd, cmdAlias, cmdArgs ); } + global.self = cmdSender; // bring in self just for autocomplete _globalSymbols = _getProperties(global); - lastArg = cmdArgs.length?cmdArgs[cmdArgs.length-1]+'':null; + lastArg = cmdArgs.length ? cmdArgs[ cmdArgs.length - 1 ] + '' : null; propsOfLastArg = []; statement = cmdArgs.join(' '); @@ -117,6 +119,7 @@ var onTabCompleteJS = function( result, cmdSender, pluginCmd, cmdAlias, cmdArgs propsOfLastArg = _globalSymbols; } else { statementSyms = statement.split(/[^\$a-zA-Z0-9_\.]/); + lastSymbol = statementSyms[statementSyms.length-1]; //print('DEBUG: lastSymbol=[' + lastSymbol + ']'); // @@ -124,18 +127,22 @@ var onTabCompleteJS = function( result, cmdSender, pluginCmd, cmdAlias, cmdArgs // parts = lastSymbol.split(/\./); name = parts[0]; + symbol = global[name]; + lastGoodSymbol = symbol; - if ( typeof symbol != 'undefined' ) { + if ( typeof symbol !== 'undefined' ) { for ( i = 1; i < parts.length; i++ ) { name = parts[i]; - symbol = symbol[name]; + if ( !name ) { // fix issue #115 + break; + } + symbol = symbol[name]; // this causes problem in jre8 if name is '' if ( typeof symbol == 'undefined' ) { break; } lastGoodSymbol = symbol; } - //print('debug:name['+name+']lastSymbol['+lastSymbol+']symbol['+symbol+']'); if ( typeof symbol == 'undefined' ) { // // look up partial matches against last good symbol @@ -146,7 +153,7 @@ var onTabCompleteJS = function( result, cmdSender, pluginCmd, cmdAlias, cmdArgs // ScriptCraft. // - for ( i =0; i < objectProps.length; i++ ) { + for ( i = 0; i < objectProps.length; i++ ) { candidate = lastSymbol + objectProps[i]; re = new RegExp( lastSymbol + '$', 'g' ); propsOfLastArg.push( lastArg.replace( re, candidate ) ); From 30d1d89e9143eaa9dc17ce1c7516053474be9f57 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Mon, 10 Mar 2014 23:18:33 +0000 Subject: [PATCH 141/456] Make putsign use drone queue and add informative messages for senders. --- src/main/js/plugins/drone/drone.js | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/main/js/plugins/drone/drone.js b/src/main/js/plugins/drone/drone.js index 432ff6e..b2d82fe 100644 --- a/src/main/js/plugins/drone/drone.js +++ b/src/main/js/plugins/drone/drone.js @@ -658,11 +658,15 @@ var putBlock = function( x, y, z, blockId, metadata, world ) { } }; -var putSign = function( texts, x, y, z, blockId, meta, world ) { +var putSign = function( texts, x, y, z, blockId, meta, world, immediate ) { var i, block, state; + if ( !immediate ) { + Drone.queue.push(function(){ putSign(texts, x, y, z, blockId, meta, world, true); }); + return; + } if ( blockId != 63 && blockId != 68 ) { throw new Error( 'Invalid Parameter: blockId must be 63 or 68' ); } @@ -738,6 +742,7 @@ var Drone = function( x, y, z, dir, world ) { this.chkpt( 'start' ); this.record = true; this.history = []; + this.player = player; return this; }; @@ -780,14 +785,22 @@ Drone.extend = function( name, func ) { } var oldVal = this.record; this.record = false; - this[ '_' + name ].apply( this, arguments ); + this[ '_' + name ].apply( this, arguments ); this.record = oldVal; return this; }; global[name] = function( ) { var result = new Drone( self ); + var len = Drone.queue.length; result[name].apply( result, arguments ); + var newLen = Drone.queue.length; + if ( len > (3 * Drone.opsPerSec) || (newLen - len) > (3 * Drone.opsPerSec)) { + if ( result.player && !result.playerNotifiedPending ) { + result.player.sendMessage('Build queue will complete in ' + Math.ceil( newLen / Drone.opsPerSec ) + ' seconds (approx.)'); + result.playerNotifiedPending = true; + } + } return result; }; }; @@ -1522,8 +1535,13 @@ var _cylinder1 = function( block,radius,height,exactParams ) { } return this.arc(arcParams ); }; -var _paste = function( name ) +var _paste = function( name, immediate ) { + + if ( !immediate ) { + Drone.queue.push(function(){ _paste(name, true);}); + return; + } var ccContent = Drone.clipBoard[name]; var srcBlocks = ccContent.blocks; var srcDir = ccContent.dir; // direction player was facing when copied. From 360b7df75becda0c5be0d6dd6d9e2e4637f51d98 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 11 Mar 2014 19:57:40 +0000 Subject: [PATCH 142/456] Fix issue #115 --- docs/release-notes.md | 2 +- src/main/js/lib/scriptcraft.js | 19 ++++++++----------- src/main/js/lib/tabcomplete.js | 29 ++++++++++++++++++++++------- 3 files changed, 31 insertions(+), 19 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 82b80a3..98fdb85 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,7 +2,7 @@ ## Version 2.0.6 -Fixed issues #122 #123 +Fixed issues #115 #122 #123 Improved background processing of Drone build commands. diff --git a/src/main/js/lib/scriptcraft.js b/src/main/js/lib/scriptcraft.js index 6b0081f..88f7546 100644 --- a/src/main/js/lib/scriptcraft.js +++ b/src/main/js/lib/scriptcraft.js @@ -640,15 +640,7 @@ function __onEnable ( __engine, __plugin, __script ) global.self = sender; global.__engine = __engine; try { - if ( typeof eval == 'undefined' ) { - jsResult = __engine.eval(fnBody); - } else { - /* - nashorn - https://bugs.openjdk.java.net/browse/JDK-8034055 - */ - jsResult = eval( fnBody ); - } + jsResult = __engine.eval(fnBody); if ( typeof jsResult != 'undefined' ) { if ( jsResult == null) { sender.sendMessage('(null)'); @@ -661,8 +653,13 @@ function __onEnable ( __engine, __plugin, __script ) sender.sendMessage( 'Error while trying to evaluate javascript: ' + fnBody + ', Error: '+ e ); throw e; } finally { - delete global.self; - delete global.__engine; + /* + 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' ) { diff --git a/src/main/js/lib/tabcomplete.js b/src/main/js/lib/tabcomplete.js index 3cbdec2..37c0774 100644 --- a/src/main/js/lib/tabcomplete.js +++ b/src/main/js/lib/tabcomplete.js @@ -36,6 +36,15 @@ var _getProperties = function( o ) { typeofProperty; if ( _isJavaObject( o ) ) { + /* + fix for issue #115 - java objects are not iterable + see: http://mail.openjdk.java.net/pipermail/nashorn-dev/2014-March/002790.html + */ + if ( typeof Object.bindProperties === 'function' ) { + var placeholder = {}; + Object.bindProperties(placeholder, o); + o = placeholder; + } propertyLoop: for ( i in o ) { // @@ -45,7 +54,7 @@ var _getProperties = function( o ) { for ( j = 0; j < _javaLangObjectMethods.length; j++ ) { if ( _javaLangObjectMethods[j] == i ) { continue propertyLoop; - } + } } typeofProperty = null; try { @@ -71,9 +80,9 @@ var _getProperties = function( o ) { if ( i.match( /^[^_]/ ) ) { if ( typeof o[i] == 'function' ) { result.push( i+'()' ); - } else { + } else { result.push( i ); - } + } } } } @@ -130,6 +139,8 @@ var onTabCompleteJS = function( result, cmdSender, pluginCmd, cmdAlias, cmdArgs symbol = global[name]; + //print('DEBUG: name=' + name + ',symbol=' + symbol); + lastGoodSymbol = symbol; if ( typeof symbol !== 'undefined' ) { for ( i = 1; i < parts.length; i++ ) { @@ -140,10 +151,14 @@ var onTabCompleteJS = function( result, cmdSender, pluginCmd, cmdAlias, cmdArgs symbol = symbol[name]; // this causes problem in jre8 if name is '' if ( typeof symbol == 'undefined' ) { break; - } + } + // nashorn - object[missingProperty] returns null not undefined + if ( symbol == null ) { + break; + } lastGoodSymbol = symbol; } - if ( typeof symbol == 'undefined' ) { + if ( typeof symbol == 'undefined' || symbol === null) { // // look up partial matches against last good symbol // @@ -165,8 +180,8 @@ var onTabCompleteJS = function( result, cmdSender, pluginCmd, cmdAlias, cmdArgs // //print('debug:case Y: ScriptCraft.co'); - li = statement.lastIndexOf(name); - for ( i = 0; i < objectProps.length; i++ ) { + li = statement.lastIndexOf(name); + for ( i = 0; i < objectProps.length; i++ ) { if ( objectProps[i].indexOf(name) == 0 ) { candidate = lastSymbol.substring( 0, lastSymbol.lastIndexOf( name ) ); candidate = candidate + objectProps[i]; From 7977dc0fb5fcb6a09ceeb314e27669017b9223cf Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 11 Mar 2014 20:06:29 +0000 Subject: [PATCH 143/456] fix issue #124 --- docs/YoungPersonsGuideToProgrammingMinecraft.md | 2 +- src/docs/templates/ypgpm.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/YoungPersonsGuideToProgrammingMinecraft.md b/docs/YoungPersonsGuideToProgrammingMinecraft.md index 1e93e47..6e5e762 100644 --- a/docs/YoungPersonsGuideToProgrammingMinecraft.md +++ b/docs/YoungPersonsGuideToProgrammingMinecraft.md @@ -925,7 +925,7 @@ var myskyscraper = function(floors) { } return this.move('myskyscraper'); // return to where we started }; -var Drone = require('../drone/drone.js').Drone; +var Drone = require('../drone/drone').Drone; Drone.extend('myskyscraper',myskyscraper); ``` diff --git a/src/docs/templates/ypgpm.md b/src/docs/templates/ypgpm.md index 9121243..5d5467d 100644 --- a/src/docs/templates/ypgpm.md +++ b/src/docs/templates/ypgpm.md @@ -889,7 +889,7 @@ var myskyscraper = function(floors) { } return this.move('myskyscraper'); // return to where we started }; -var Drone = require('../drone/drone.js').Drone; +var Drone = require('../drone/drone').Drone; Drone.extend('myskyscraper',myskyscraper); ``` From 7e435be5654d738a4f0fdbe35f271d6c3e441925 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 11 Mar 2014 21:35:59 +0000 Subject: [PATCH 144/456] make Drone.cuboida() async. --- src/main/js/plugins/drone/drone.js | 48 +++++++++++++++++------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/src/main/js/plugins/drone/drone.js b/src/main/js/plugins/drone/drone.js index b2d82fe..a43954b 100644 --- a/src/main/js/plugins/drone/drone.js +++ b/src/main/js/plugins/drone/drone.js @@ -1014,17 +1014,22 @@ Drone.extend( 'sign', function( message, block ) { } }); -Drone.prototype.cuboida = function(/* Array */ blocks, w, h, d, overwrite ) { +Drone.prototype.cuboida = function(/* Array */ blocks, w, h, d, overwrite, immediate ) { + + var len = blocks.length, + i = 0; + if ( !immediate ) { + for ( ; i < len; i++ ) { + blocks[i] = this._getBlockIdAndMeta( blocks[ i ] ); + } + var clone = Drone.clone(this); + Drone.queue.push(this.cuboida.bind(clone, blocks, w, h, d, overwrite, true) ); + return this; + } if ( typeof overwrite == 'undefined' ) { overwrite = true; } - var properBlocks = []; - var len = blocks.length; - for ( var i = 0; i < len; i++ ) { - var bm = this._getBlockIdAndMeta( blocks[ i ] ); - properBlocks.push( [ bm[0], bm[1] ] ); - } if ( typeof h == 'undefined' ) { h = 1; } @@ -1037,14 +1042,11 @@ Drone.prototype.cuboida = function(/* Array */ blocks, w, h, d, overwrite ) { var that = this; var dir = this.dir; var bi = 0; - /* - - */ _traverse[dir].depth( that, d, function( ) { _traverseHeight( that, h, function( ) { _traverse[dir].width( that, w, function( ) { var block = that.world.getBlockAt( that.x, that.y, that.z ); - var properBlock = properBlocks[ bi % len ]; + var properBlock = blocks[ bi % len ]; if (overwrite || block.type.equals(bkMaterial.AIR) ) { block.setTypeIdAndData( properBlock[0], properBlock[1], false ); } @@ -1085,7 +1087,7 @@ Drone.prototype.cuboidX = function( blockType, meta, w, h, d, immediate ) { if ( !immediate ) { var clone = Drone.clone(this); - Drone.queue.push(this.cuboidX.bind(clone, blockType, meta, w, h, d, true));; + Drone.queue.push(this.cuboidX.bind(clone, blockType, meta, w, h, d, true)); return this; } var depthFunc = function( ) { @@ -1639,36 +1641,40 @@ var _getDirFromRotation = function( r ) { if ( r > 315 || r < 45 ) return 1; // south }; -var _getBlockIdAndMeta = function(b ) { - var defaultMeta = 0; +var _getBlockIdAndMeta = function( b ) { + var defaultMeta = 0, + i = 0, + bs, + md, + sp; if ( typeof b == 'string' ) { - var bs = b; - var sp = bs.indexOf(':' ); + bs = b; + sp = bs.indexOf(':' ); if ( sp == -1 ) { b = parseInt(bs ); // wph 20130414 - use sensible defaults for certain blocks e.g. stairs // should face the drone. - for ( var i in blocks.stairs ) { + for ( i in blocks.stairs ) { if ( blocks.stairs[i] === b ) { defaultMeta = Drone.PLAYER_STAIRS_FACING[this.dir]; break; } } - return [b,defaultMeta]; + return [ b, defaultMeta ]; } b = parseInt(bs.substring(0,sp ) ); - var md = parseInt(bs.substring(sp+1,bs.length ) ); + md = parseInt(bs.substring(sp+1,bs.length ) ); return [b,md]; }else{ // wph 20130414 - use sensible defaults for certain blocks e.g. stairs // should face the drone. - for ( var i in blocks.stairs ) { + for ( i in blocks.stairs ) { if ( blocks.stairs[i] === b ) { defaultMeta = Drone.PLAYER_STAIRS_FACING[this.dir]; break; } } - return [b,defaultMeta]; + return [ b, defaultMeta ]; } }; // From 5bec691575b4926cc73f067c7c82c15919fce3bb Mon Sep 17 00:00:00 2001 From: Jason Kohles Date: Thu, 13 Mar 2014 12:10:51 -0400 Subject: [PATCH 145/456] Fix require() to work with index.js --- src/main/js/lib/require.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/js/lib/require.js b/src/main/js/lib/require.js index d23298a..90830dd 100644 --- a/src/main/js/lib/require.js +++ b/src/main/js/lib/require.js @@ -80,7 +80,7 @@ module specification, the '.js' suffix is optional. } } else { // look for an index.js file - var indexJsFile = new File( dir + './index.js' ); + var indexJsFile = new File( dir, './index.js' ); if ( indexJsFile.exists() ) { return indexJsFile; } else { From 45217a0953d005cbe1645f110f8bab799197a707 Mon Sep 17 00:00:00 2001 From: Jason Kohles Date: Thu, 13 Mar 2014 14:18:45 -0400 Subject: [PATCH 146/456] Make error messages from require() more useful --- src/main/js/lib/require.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/js/lib/require.js b/src/main/js/lib/require.js index 90830dd..35d8212 100644 --- a/src/main/js/lib/require.js +++ b/src/main/js/lib/require.js @@ -244,7 +244,9 @@ When resolving module names to file paths, ScriptCraft uses the following rules. try { compiledWrapper = eval(code); } catch (e) { - throw 'Error:' + e + ' while evaluating module ' + canonizedFilename; + throw new Error( "Error evaluating module " + path + + " at " + canonizedFilename + " line #" + e.lineNumber + + ". Error was: " + e.message, canonizedFilename, e.lineNumber ); } var __dirname = '' + file.parentFile.canonicalPath; var parameters = [ @@ -259,7 +261,9 @@ When resolving module names to file paths, ScriptCraft uses the following rules. .apply(moduleInfo.exports, /* this */ parameters); } catch (e) { - throw 'Error:' + e + ' while executing module ' + canonizedFilename; + throw new Error( "Error executing module " + path + + " at " + canonizedFilename + " line #" + e.lineNumber + + ". Error was: " + e.message, canonizedFilename, e.lineNumber ) } if ( hooks ) { hooks.loaded( canonizedFilename ); From 0a506f80ed3d7296c6da710473a9f7a12fcbce2a Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Thu, 13 Mar 2014 19:23:32 +0000 Subject: [PATCH 147/456] Adding Drone.MAX_VOLUME and Drone.MAX_SIDE properties --- docs/release-notes.md | 7 ++++++- src/main/js/plugins/drone/drone.js | 11 ++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 98fdb85..5f85f05 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,7 +1,12 @@ -# 2014 03 08 +# 2014 03 12 ## Version 2.0.6 +Added Drone.MAX_VOLUME and Drone.MAX_SIDE properties to specify limits on size of Drone ops. +This is to stop individual players from hogging the CPU in a classrom environment. + +# 2014 03 08 + Fixed issues #115 #122 #123 Improved background processing of Drone build commands. diff --git a/src/main/js/plugins/drone/drone.js b/src/main/js/plugins/drone/drone.js index a43954b..4a5aead 100644 --- a/src/main/js/plugins/drone/drone.js +++ b/src/main/js/plugins/drone/drone.js @@ -1057,6 +1057,15 @@ Drone.prototype.cuboida = function(/* Array */ blocks, w, h, d, overwrite, immed return this; }; +Drone.MAX_VOLUME = 1000000; +Drone.MAX_SIDE = 1000; + +var tooBig = function(w, h, d ) { + return ( w * h * d ) >= Drone.MAX_VOLUME || + ( w >= Drone.MAX_SIDE ) || + ( h >= Drone.MAX_SIDE ) || + ( d >= Drone.MAX_SIDE ); +}; /* faster cuboid because blockid, meta and world must be provided use this method when you need to repeatedly place blocks @@ -1072,7 +1081,7 @@ Drone.prototype.cuboidX = function( blockType, meta, w, h, d, immediate ) { if ( typeof w == 'undefined' ) { w = 1; } - if ( ( w * h * d ) >= 1000000 ) { + if ( tooBig( w, h, d ) ) { this.sign([ 'Build too Big!', 'width:' + w, From fce28567e6d1185c31e7da679b19b93f78c6a36c Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Thu, 13 Mar 2014 22:11:01 +0000 Subject: [PATCH 148/456] Make error messages more concise (don't repeat file name) --- src/main/js/lib/require.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/js/lib/require.js b/src/main/js/lib/require.js index 35d8212..89a8179 100644 --- a/src/main/js/lib/require.js +++ b/src/main/js/lib/require.js @@ -245,8 +245,8 @@ When resolving module names to file paths, ScriptCraft uses the following rules. compiledWrapper = eval(code); } catch (e) { throw new Error( "Error evaluating module " + path - + " at " + canonizedFilename + " line #" + e.lineNumber - + ". Error was: " + e.message, canonizedFilename, e.lineNumber ); + + " line #" + e.lineNumber + + " : " + e.message, canonizedFilename, e.lineNumber ); } var __dirname = '' + file.parentFile.canonicalPath; var parameters = [ @@ -262,8 +262,8 @@ When resolving module names to file paths, ScriptCraft uses the following rules. parameters); } catch (e) { throw new Error( "Error executing module " + path - + " at " + canonizedFilename + " line #" + e.lineNumber - + ". Error was: " + e.message, canonizedFilename, e.lineNumber ) + + " line #" + e.lineNumber + + " : " + e.message, canonizedFilename, e.lineNumber ) } if ( hooks ) { hooks.loaded( canonizedFilename ); From b7352ed96245bdacd39000abff0e8ad19110cb04 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Thu, 13 Mar 2014 22:49:03 +0000 Subject: [PATCH 149/456] Show correct lineNumber in require() errors on Nashorn (JRE8) --- src/main/js/lib/require.js | 26 +++++++++++++++----------- src/main/js/lib/scriptcraft.js | 18 +++++++++++++----- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/main/js/lib/require.js b/src/main/js/lib/require.js index 89a8179..2769d83 100644 --- a/src/main/js/lib/require.js +++ b/src/main/js/lib/require.js @@ -60,7 +60,7 @@ module specification, the '.js' suffix is optional. [cjsmodules]: http://wiki.commonjs.org/wiki/Modules/1.1.1. ***/ -(function ( rootDir, modulePaths, hooks ) { +(function ( rootDir, modulePaths, hooks, evaluate ) { var File = java.io.File, FileReader = java.io.FileReader, @@ -155,7 +155,7 @@ When resolving module names to file paths, ScriptCraft uses the following rules. resolvedFile = new File(modulePaths[i] + moduleName + '.js'); if ( resolvedFile.exists() ) { return resolvedFile; - } + } } } } else { @@ -173,7 +173,7 @@ When resolving module names to file paths, ScriptCraft uses the following rules. file = new File(pathWithJSExt); if ( file.exists() ) { return file; - } + } } } @@ -187,18 +187,18 @@ When resolving module names to file paths, ScriptCraft uses the following rules. */ var _require = function( parentFile, path, options ) { var file, - canonizedFilename, - moduleInfo, - buffered, + canonizedFilename, + moduleInfo, + buffered, head = '(function(exports,module,require,__filename,__dirname){ ', - code = '', - line = null; + code = '', + line = null; if ( typeof options == 'undefined' ) { options = { cache: true }; } else { if ( typeof options.cache == 'undefined' ) { - options.cache = true; + options.cache = true; } } @@ -216,7 +216,7 @@ When resolving module names to file paths, ScriptCraft uses the following rules. moduleInfo = _loadedModules[canonizedFilename]; if ( moduleInfo ) { if ( options.cache ) { - return moduleInfo; + return moduleInfo; } } if ( hooks ) { @@ -242,8 +242,12 @@ When resolving module names to file paths, ScriptCraft uses the following rules. } var compiledWrapper = null; try { - compiledWrapper = eval(code); + compiledWrapper = evaluate(code); } catch (e) { + /* + wph 20140313 JRE8 (nashorn) gives misleading linenumber of evaluating code not evaluated code. + This can be fixed by instead using __engine.eval + */ throw new Error( "Error evaluating module " + path + " line #" + e.lineNumber + " : " + e.message, canonizedFilename, e.lineNumber ); diff --git a/src/main/js/lib/scriptcraft.js b/src/main/js/lib/scriptcraft.js index 88f7546..6d935b9 100644 --- a/src/main/js/lib/scriptcraft.js +++ b/src/main/js/lib/scriptcraft.js @@ -428,7 +428,9 @@ function __onEnable ( __engine, __plugin, __script ) 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( '\\\\', '/' ); }; @@ -457,14 +459,13 @@ function __onEnable ( __engine, __plugin, __script ) out.close(); }; /* - make sure eval is present + 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 */ @@ -593,7 +594,14 @@ function __onEnable ( __engine, __plugin, __script ) } } }; - global.require = configRequire( jsPluginsRootDirName, modulePaths, requireHooks ); + global.require = configRequire( + jsPluginsRootDirName, + modulePaths, + requireHooks, + function(code){ + return __engine.eval(code); + } + ); require('js-patch')( global ); global.console = require('console'); From 38e312c93e9c5187071cd6adf90c82e0f5235014 Mon Sep 17 00:00:00 2001 From: Robert Storlind Date: Fri, 14 Mar 2014 09:25:43 +0100 Subject: [PATCH 150/456] Indentation according to style guide --- src/main/js/plugins/drone/contrib/mazegen.js | 175 +++++++++---------- 1 file changed, 87 insertions(+), 88 deletions(-) diff --git a/src/main/js/plugins/drone/contrib/mazegen.js b/src/main/js/plugins/drone/contrib/mazegen.js index d5ecdcb..1531504 100644 --- a/src/main/js/plugins/drone/contrib/mazegen.js +++ b/src/main/js/plugins/drone/contrib/mazegen.js @@ -3,106 +3,105 @@ var Drone = require('../drone').Drone; function maze_make(x,y) { - var n=x*y-1; - if (n<0) { - echo("illegal maze dimensions"); - return ({x: 0, y: 0}); - } - var horiz=[]; for (var j= 0; j0 && j0 && (j != here[0]+1 || k != here[1]+1)); - } - while (00 && j0 && (j != here[0]+1 || k != here[1]+1)); + } + while (00 && m.verti[j/2-1][Math.floor(k/4)]) - line[k]= ' '; - else - line[k]= '-'; - else - for (var k=0; k0 && m.horiz[(j-1)/2][k/4-1]) - line[k]= ' '; - else - line[k]= '|'; - else - line[k]= ' '; - if (0 == j) line[1]= line[2]= line[3]= ' '; - if (m.x*2-1 == j) line[4*m.y]= ' '; - text.push(line.join('')+' \r\n'); // TWEAKED: space added to get an even number of columns - } - return text.join(''); + var text= []; + for (var j= 0; j0 && m.verti[j/2-1][Math.floor(k/4)]) + line[k]= ' '; + else + line[k]= '-'; + else + for (var k=0; k0 && m.horiz[(j-1)/2][k/4-1]) + line[k]= ' '; + else + line[k]= '|'; + else + line[k]= ' '; + if (0 == j) line[1]= line[2]= line[3]= ' '; + if (m.x*2-1 == j) line[4*m.y]= ' '; + text.push(line.join('')+' \r\n'); // TWEAKED: space added to get an even number of columns + } + return text.join(''); } // ScriptCraft stuff starts here // Helper function to parse the ASCII art into Drone actions // You might also consider creating a new maze_display but for now this will do the work function maze_draw(maze_string, d) { - // d is the Drone to use - d.up().chkpt('maze-start'); - for (var j = 0; j < maze_string.length; j += 2) { - switch(maze_string.substr(j, 2)) { - case ' ': - d.box(0).fwd(); // Make sure to empty this position - break; - case '\r\n': - d.move('maze-start'); - d.right().chkpt('maze-start'); - break; - default: // '+ ', '+-', '--', '| ' - if (j == 0) { - d.box(blocks.glowstone); // highlight the maze entry and exit - } else if (j == maze_string.length - 4) { - d.box(blocks.glass); - } else { - d.box(blocks.oak); - } - d.fwd(); - } - } + // d is the Drone to use + d.up().chkpt('maze-start'); + for (var j = 0; j < maze_string.length; j += 2) { + switch(maze_string.substr(j, 2)) { + case ' ': + d.box(0).fwd(); // Make sure to empty this position + break; + case '\r\n': + d.move('maze-start'); + d.right().chkpt('maze-start'); + break; + default: // '+ ', '+-', '--', '| ' + if (j == 0) { + d.box(blocks.glowstone); // highlight the maze entry and exit + } else if (j == maze_string.length - 4) { + d.box(blocks.glass); + } else { + d.box(blocks.oak); + } + d.fwd(); + } + } } // User-facing code starts here // Example: Try /js amazing(5,7) Drone.extend('amazing', function(size_x, size_y) { - m = maze_make(size_x, size_y); - if (m.x > 0 && m.y > 0) { - maze_draw(maze_display(m), this); - } + m = maze_make(size_x, size_y); + if (m.x > 0 && m.y > 0) { + maze_draw(maze_display(m), this); + } }); - From 9d406dbb447ece2b20f5840b91fe5a299568f1fe Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Fri, 14 Mar 2014 22:23:35 +0000 Subject: [PATCH 151/456] Made maze code place blocks 2 high --- src/main/js/lib/scriptcraft.js | 4 ++-- src/main/js/plugins/drone/contrib/mazegen.js | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/js/lib/scriptcraft.js b/src/main/js/lib/scriptcraft.js index 6d935b9..f35b09b 100644 --- a/src/main/js/lib/scriptcraft.js +++ b/src/main/js/lib/scriptcraft.js @@ -598,8 +598,8 @@ function __onEnable ( __engine, __plugin, __script ) jsPluginsRootDirName, modulePaths, requireHooks, - function(code){ - return __engine.eval(code); + function( code ) { + return __engine.eval( code ); } ); diff --git a/src/main/js/plugins/drone/contrib/mazegen.js b/src/main/js/plugins/drone/contrib/mazegen.js index 1531504..b02c22c 100644 --- a/src/main/js/plugins/drone/contrib/mazegen.js +++ b/src/main/js/plugins/drone/contrib/mazegen.js @@ -74,7 +74,7 @@ function maze_display(m) { // You might also consider creating a new maze_display but for now this will do the work function maze_draw(maze_string, d) { // d is the Drone to use - d.up().chkpt('maze-start'); + d.chkpt('maze-start'); for (var j = 0; j < maze_string.length; j += 2) { switch(maze_string.substr(j, 2)) { case ' ': @@ -86,11 +86,11 @@ function maze_draw(maze_string, d) { break; default: // '+ ', '+-', '--', '| ' if (j == 0) { - d.box(blocks.glowstone); // highlight the maze entry and exit + d.box(blocks.glowstone,1,2,1); // highlight the maze entry and exit } else if (j == maze_string.length - 4) { - d.box(blocks.glass); + d.box(blocks.glass,1,2,1); } else { - d.box(blocks.oak); + d.box(blocks.oak,1,2,1); } d.fwd(); } From f82d88cb3fad2939af2e89d4e07785173c2aea9f Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 15 Mar 2014 18:06:23 +0000 Subject: [PATCH 152/456] Event handling rework. Simplified event handling and unregistering. --- docs/API-Reference.md | 35 +++++++++--------- ...YoungPersonsGuideToProgrammingMinecraft.md | 34 ++++++++++++------ src/docs/templates/ypgpm.md | 34 ++++++++++++------ src/main/js/lib/events.js | 36 +++++++++++-------- src/main/js/lib/scriptcraft.js | 2 +- src/main/js/modules/signs/menu.js | 2 +- src/main/js/plugins/alias/alias.js | 4 +-- src/main/js/plugins/arrows.js | 2 +- src/main/js/plugins/classroom/classroom.js | 2 +- src/main/js/plugins/commando/commando.js | 12 +++---- src/main/js/plugins/drone/drone.js | 2 +- .../examples/example-7-hello-events.js | 10 +++--- .../js/plugins/minigames/SnowballFight.js | 5 ++- src/main/js/plugins/minigames/cow-clicker.js | 6 ++-- 14 files changed, 108 insertions(+), 78 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index a4b230b..75da1d1 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -679,8 +679,7 @@ This method is used to register event listeners. enclosing quotes). * callback - A function which will be called whenever the event - fires. The callback should take 2 parameters, listener (the Bukkit - registered listener for this callback) and event (the event fired). + fires. The callback should take a single parameter, event (the event fired). * priority (optional - default: "HIGHEST") - The priority the listener/callback takes over other listeners to the same @@ -690,16 +689,14 @@ This method is used to register event listeners. #### Returns -An org.bukkit.plugin.RegisteredListener object which can be used to -unregister the listener. This same object is passed to the callback -function each time the event is fired. +An object which can be used to unregister the listener. #### Example: The following code will print a message on screen every time a block is broken in the game ```javascript -events.on( 'block.BlockBreakEvent', function( listener, evt ) { +events.on( 'block.BlockBreakEvent', function( evt ) { evt.player.sendMessage( evt.player.name + ' broke a block!'); } ); ``` @@ -707,24 +704,28 @@ events.on( 'block.BlockBreakEvent', function( listener, evt ) { To handle an event only once and unregister from further events... ```javascript -events.on( 'block.BlockBreakEvent', function( listener, evt ) { +events.on( 'block.BlockBreakEvent', function( evt ) { evt.player.sendMessage( evt.player.name + ' broke a block!'); - evt.handlers.unregister( listener ); + this.unregister(); } ); +The `this` keyword when used inside the callback function refers to +the Listener object created by ScriptCraft. It has a single method +`unregister()` which can be used to stop listening. This is the same +object which is returned by the `events.on()` function. + To unregister a listener *outside* of the listener function... ```javascript -var myBlockBreakListener = events.on( 'block.BlockBreakEvent', function( l, e ) { ... } ); +var myBlockBreakListener = events.on( 'block.BlockBreakEvent', function( evt ) { ... } ); ... -var handlers = org.bukkit.event.block.BlockBreakEvent.getHandlerList(); -handlers.unregister(myBlockBreakListener); +myBlockBreakListener.unregister(); ``` To listen for events using a full class name as the `eventName` parameter... ```javascript -events.on( org.bukkit.event.block.BlockBreakEvent, function( listener, evt ) { +events.on( org.bukkit.event.block.BlockBreakEvent, function( evt ) { evt.player.sendMessage( evt.player.name + ' broke a block!'); } ); ``` @@ -1493,7 +1494,7 @@ Drones can be created in any of the following ways... block is broken at the block's location you would do so like this... - events.on('block.BlockBreakEvent',function( listener,event) { + events.on('block.BlockBreakEvent',function( event) { var location = event.block.location; var drone = new Drone(location); // do more stuff with the drone here... @@ -2429,9 +2430,9 @@ parameters... Package' and 'Previous Package' links to browse). 2. The event handling function (also sometimes refered to as a - 'callback'). In ScriptCraft, this function takes 2 parameters, a - listener object and an event object. All of the information about - the event is in the event object. + 'callback'). In ScriptCraft, this function takes a single + parameter, an event object. All of the information about the event + is in the event object. In the example below, if a player joins the server and is an operator, then the ScriptCraft plugin information will be displayed to that @@ -2477,7 +2478,7 @@ cleaner and more readable. Similarly where you see a method like [bksaf]: http://jd.bukkit.org/dev/apidocs/org/bukkit/entity/Player.html#setAllowFlight() [bkapi]: http://jd.bukkit.org/dev/apidocs/ - events.on( 'player.PlayerJoinEvent', function( listener, event ) { + events.on( 'player.PlayerJoinEvent', function( event ) { if ( event.player.op ) { event.player.sendMessage('Welcome to ' + __plugin); } diff --git a/docs/YoungPersonsGuideToProgrammingMinecraft.md b/docs/YoungPersonsGuideToProgrammingMinecraft.md index 6e5e762..1ad8abd 100644 --- a/docs/YoungPersonsGuideToProgrammingMinecraft.md +++ b/docs/YoungPersonsGuideToProgrammingMinecraft.md @@ -1046,7 +1046,7 @@ following code sends a message to any player who breaks a block in the game... ```javascript -events.on('block.BlockBreakEvent', function ( listener, event ) { +events.on('block.BlockBreakEvent', function ( event ) { var breaker = event.player; breaker.sendMessage('You broke a block'); } ); @@ -1057,7 +1057,7 @@ want to be called whenever a particular type of event occurs. In the above code the first parameter `'block.BlockBreakEvent'` is the type of event I want to listen for and the second parameter is the function I want to be called when that event occurs. The function I want called -in turn takes 2 parameters. The `event` object has all the information +in turn takes 1 parameter. The `event` object has all the information about the event which just occurred. I can tell who broke the block and send a message to the player. The important thing to note is that the function defined above will not be called until a player breaks a @@ -1076,13 +1076,13 @@ It's important to note that when browsing the Bukkit API's `events.on()` you can listen to such an event using either the fully qualified Class name... - events.on(org.bukkit.events.entity.EntityShootBowEvent, function( listener, event) { + events.on(org.bukkit.events.entity.EntityShootBowEvent, function( event ) { ... }); or an abbreviated name in string form... - events.on('entity.EntityShootBowEvent', function( listener, event) { + events.on('entity.EntityShootBowEvent', function( event ) { ... }); @@ -1093,7 +1093,7 @@ prepending the 'org.bukkit.events' package. For custom events (events which aren't in the org.bukkit.event tree) just specify the fully qualified class name instead. E.g. ... - events.on ( net.yourdomain.events.YourEvent, function(listener, event ) { + events.on ( net.yourdomain.events.YourEvent, function( event ) { ... }); @@ -1102,15 +1102,27 @@ just specify the fully qualified class name instead. E.g. ... If you want an event handler to only execute once, you can remove the handler like this... ```javascript -events.on('block.BlockBreakEvent', function( listener, evt ) { +events.on('block.BlockBreakEvent', function( evt ) { var breaker = evt.player; breaker.sendMessage('You broke a block'); - evt.handlers.unregister( listener ); + this.unregister(); } ); ``` -The `evt.handlers.unregister( listener );` statement will remove this -function from the list of listeners for this event. +The `this.unregister();` statement will remove this function from the +list of listeners for the event. The `this` keyword when used inside +an event handling function refers to a Listener object provided by +ScriptCraft, it has a single method `unregister()` which can be used +to stop listening for events. + +To unregister a listener *outside* of the listener function... + +```javascript +var myBlockBreakListener = events.on( 'block.BlockBreakEvent', function( evt ) { ... } ); +... +myBlockBreakListener.unregister(); +``` + ## Keeping Score - Lookup tables in Javascript @@ -1221,10 +1233,10 @@ keep a count of how many blocks each player has broken ... ```javascript var breaks = {}; // every time a player joins the game reset their block-break-count to 0 -events.on('player.PlayerJoinEvent', function(listener, event){ +events.on('player.PlayerJoinEvent', function( event ) { breaks[event.player] = 0; }); -events.on('block.BlockBreakEvent', function(listener, event){ +events.on('block.BlockBreakEvent', function( event ) { var breaker = event.player; var breakCount = breaks[breaker.name]; breakCount++; // increment the count. diff --git a/src/docs/templates/ypgpm.md b/src/docs/templates/ypgpm.md index 5d5467d..38d46c3 100644 --- a/src/docs/templates/ypgpm.md +++ b/src/docs/templates/ypgpm.md @@ -1010,7 +1010,7 @@ following code sends a message to any player who breaks a block in the game... ```javascript -events.on('block.BlockBreakEvent', function ( listener, event ) { +events.on('block.BlockBreakEvent', function ( event ) { var breaker = event.player; breaker.sendMessage('You broke a block'); } ); @@ -1021,7 +1021,7 @@ want to be called whenever a particular type of event occurs. In the above code the first parameter `'block.BlockBreakEvent'` is the type of event I want to listen for and the second parameter is the function I want to be called when that event occurs. The function I want called -in turn takes 2 parameters. The `event` object has all the information +in turn takes 1 parameter. The `event` object has all the information about the event which just occurred. I can tell who broke the block and send a message to the player. The important thing to note is that the function defined above will not be called until a player breaks a @@ -1040,13 +1040,13 @@ It's important to note that when browsing the Bukkit API's `events.on()` you can listen to such an event using either the fully qualified Class name... - events.on(org.bukkit.events.entity.EntityShootBowEvent, function( listener, event) { + events.on(org.bukkit.events.entity.EntityShootBowEvent, function( event ) { ... }); or an abbreviated name in string form... - events.on('entity.EntityShootBowEvent', function( listener, event) { + events.on('entity.EntityShootBowEvent', function( event ) { ... }); @@ -1057,7 +1057,7 @@ prepending the 'org.bukkit.events' package. For custom events (events which aren't in the org.bukkit.event tree) just specify the fully qualified class name instead. E.g. ... - events.on ( net.yourdomain.events.YourEvent, function(listener, event ) { + events.on ( net.yourdomain.events.YourEvent, function( event ) { ... }); @@ -1066,15 +1066,27 @@ just specify the fully qualified class name instead. E.g. ... If you want an event handler to only execute once, you can remove the handler like this... ```javascript -events.on('block.BlockBreakEvent', function( listener, evt ) { +events.on('block.BlockBreakEvent', function( evt ) { var breaker = evt.player; breaker.sendMessage('You broke a block'); - evt.handlers.unregister( listener ); + this.unregister(); } ); ``` -The `evt.handlers.unregister( listener );` statement will remove this -function from the list of listeners for this event. +The `this.unregister();` statement will remove this function from the +list of listeners for the event. The `this` keyword when used inside +an event handling function refers to a Listener object provided by +ScriptCraft, it has a single method `unregister()` which can be used +to stop listening for events. + +To unregister a listener *outside* of the listener function... + +```javascript +var myBlockBreakListener = events.on( 'block.BlockBreakEvent', function( evt ) { ... } ); +... +myBlockBreakListener.unregister(); +``` + ## Keeping Score - Lookup tables in Javascript @@ -1185,10 +1197,10 @@ keep a count of how many blocks each player has broken ... ```javascript var breaks = {}; // every time a player joins the game reset their block-break-count to 0 -events.on('player.PlayerJoinEvent', function(listener, event){ +events.on('player.PlayerJoinEvent', function( event ) { breaks[event.player] = 0; }); -events.on('block.BlockBreakEvent', function(listener, event){ +events.on('block.BlockBreakEvent', function( event ) { var breaker = event.player; var breakCount = breaks[breaker.name]; breakCount++; // increment the count. diff --git a/src/main/js/lib/events.js b/src/main/js/lib/events.js index 0da3004..34dfde0 100644 --- a/src/main/js/lib/events.js +++ b/src/main/js/lib/events.js @@ -27,8 +27,7 @@ This method is used to register event listeners. enclosing quotes). * callback - A function which will be called whenever the event - fires. The callback should take 2 parameters, listener (the Bukkit - registered listener for this callback) and event (the event fired). + fires. The callback should take a single parameter, event (the event fired). * priority (optional - default: "HIGHEST") - The priority the listener/callback takes over other listeners to the same @@ -38,16 +37,14 @@ This method is used to register event listeners. #### Returns -An org.bukkit.plugin.RegisteredListener object which can be used to -unregister the listener. This same object is passed to the callback -function each time the event is fired. +An object which can be used to unregister the listener. #### Example: The following code will print a message on screen every time a block is broken in the game ```javascript -events.on( 'block.BlockBreakEvent', function( listener, evt ) { +events.on( 'block.BlockBreakEvent', function( evt ) { evt.player.sendMessage( evt.player.name + ' broke a block!'); } ); ``` @@ -55,24 +52,28 @@ events.on( 'block.BlockBreakEvent', function( listener, evt ) { To handle an event only once and unregister from further events... ```javascript -events.on( 'block.BlockBreakEvent', function( listener, evt ) { +events.on( 'block.BlockBreakEvent', function( evt ) { evt.player.sendMessage( evt.player.name + ' broke a block!'); - evt.handlers.unregister( listener ); + this.unregister(); } ); +The `this` keyword when used inside the callback function refers to +the Listener object created by ScriptCraft. It has a single method +`unregister()` which can be used to stop listening. This is the same +object which is returned by the `events.on()` function. + To unregister a listener *outside* of the listener function... ```javascript -var myBlockBreakListener = events.on( 'block.BlockBreakEvent', function( l, e ) { ... } ); +var myBlockBreakListener = events.on( 'block.BlockBreakEvent', function( evt ) { ... } ); ... -var handlers = org.bukkit.event.block.BlockBreakEvent.getHandlerList(); -handlers.unregister(myBlockBreakListener); +myBlockBreakListener.unregister(); ``` To listen for events using a full class name as the `eventName` parameter... ```javascript -events.on( org.bukkit.event.block.BlockBreakEvent, function( listener, evt ) { +events.on( org.bukkit.event.block.BlockBreakEvent, function( evt ) { evt.player.sendMessage( evt.player.name + ' broke a block!'); } ); ``` @@ -118,9 +119,11 @@ exports.on = function( } } handlerList = eventType.getHandlerList( ); + + var result = { }; eventExecutor = new bkEventExecutor( ) { - execute: function( l, e ) { - handler( listener.reg, e ); + execute: function( l, evt ) { + handler.call( result, evt ); } }; /* @@ -133,5 +136,8 @@ exports.on = function( */ listener.reg = new bkRegisteredListener( __plugin, eventExecutor, priority, __plugin, true ); handlerList.register( listener.reg ); - return listener.reg; + result.unregister = function(){ + handlerList.unregister( listener.reg ); + }; + return result; }; diff --git a/src/main/js/lib/scriptcraft.js b/src/main/js/lib/scriptcraft.js index f35b09b..de0fb97 100644 --- a/src/main/js/lib/scriptcraft.js +++ b/src/main/js/lib/scriptcraft.js @@ -617,7 +617,7 @@ function __onEnable ( __engine, __plugin, __script ) global.plugin = plugins.plugin; var events = require('events'); - events.on( 'server.PluginDisableEvent', function( l, e ) { + events.on( 'server.PluginDisableEvent', function( evt ) { // save config _save( global.config, new File( jsPluginsRootDir, 'data/global-config.json' ) ); diff --git a/src/main/js/modules/signs/menu.js b/src/main/js/modules/signs/menu.js index 4d10c1d..df1c7af 100644 --- a/src/main/js/modules/signs/menu.js +++ b/src/main/js/modules/signs/menu.js @@ -184,7 +184,7 @@ signs.menu = function( /* String */ label, /* Array */ options, /* Function */ c // // update it every time player interacts with it. // -events.on( 'player.PlayerInteractEvent', function( listener, event ) { +events.on( 'player.PlayerInteractEvent', function( event ) { /* look up our list of menu signs. If there's a matching location and there's a sign, then update it. diff --git a/src/main/js/plugins/alias/alias.js b/src/main/js/plugins/alias/alias.js index 35f58e3..0626fc9 100644 --- a/src/main/js/plugins/alias/alias.js +++ b/src/main/js/plugins/alias/alias.js @@ -223,7 +223,7 @@ var _intercept = function( msg, invoker, exec ) { Intercept all command processing and replace with aliased commands if the command about to be issued matches an alias. */ -events.on( 'player.PlayerCommandPreprocessEvent', function( listener, evt ) { +events.on( 'player.PlayerCommandPreprocessEvent', function( evt ) { var invoker = evt.player; var exec = function( cmd ) { invoker.performCommand(cmd); @@ -237,7 +237,7 @@ events.on( 'player.PlayerCommandPreprocessEvent', function( listener, evt ) { command('void',function( ) { } ); -events.on( 'server.ServerCommandEvent', function( listener, evt ) { +events.on( 'server.ServerCommandEvent', function( evt ) { var invoker = evt.sender; var exec = function( cmd ) { invoker.server.dispatchCommand( invoker, cmd); diff --git a/src/main/js/plugins/arrows.js b/src/main/js/plugins/arrows.js index 82a9fd9..4e2c8b5 100644 --- a/src/main/js/plugins/arrows.js +++ b/src/main/js/plugins/arrows.js @@ -79,7 +79,7 @@ arrows.sign = function( cmdSender ) { /* event handler called when a projectile hits something */ -var _onArrowHit = function( listener, event ) { +var _onArrowHit = function( event ) { var projectile = event.entity, world = projectile.world, shooter = projectile.shooter, diff --git a/src/main/js/plugins/classroom/classroom.js b/src/main/js/plugins/classroom/classroom.js index af240b3..2f74caf 100644 --- a/src/main/js/plugins/classroom/classroom.js +++ b/src/main/js/plugins/classroom/classroom.js @@ -158,7 +158,7 @@ var classroom = plugin('classroom', { exports.classroom = classroom; -events.on( 'player.PlayerJoinEvent', function( listener, event ) { +events.on( 'player.PlayerJoinEvent', function( event ) { if ( _store.enableScripting ) { grantScripting(event.player); } diff --git a/src/main/js/plugins/commando/commando.js b/src/main/js/plugins/commando/commando.js index 55fa910..64437d6 100644 --- a/src/main/js/plugins/commando/commando.js +++ b/src/main/js/plugins/commando/commando.js @@ -84,8 +84,8 @@ exports.commando = function( name, func, options, intercepts ) { return result; }; -events.on( 'player.PlayerCommandPreprocessEvent', function( l, e ) { - var msg = '' + e.message; +events.on( 'player.PlayerCommandPreprocessEvent', function( evt ) { + var msg = '' + evt.message; var parts = msg.match( /^\/([^\s]+)/ ); if ( !parts ) { return; @@ -95,11 +95,11 @@ events.on( 'player.PlayerCommandPreprocessEvent', function( l, e ) { } var command = parts[1]; if ( commands[command] ) { - e.message = '/jsp ' + msg.replace( /^\//, '' ); + evt.message = '/jsp ' + msg.replace( /^\//, '' ); } } ); -events.on( 'server.ServerCommandEvent', function( l, e ) { - var msg = '' + e.command; +events.on( 'server.ServerCommandEvent', function( evt ) { + var msg = '' + evt.command; var parts = msg.match( /^\/*([^\s]+)/ ); if ( !parts ) { return; @@ -113,6 +113,6 @@ events.on( 'server.ServerCommandEvent', function( l, e ) { if ( config.verbose ) { console.log( 'Redirecting to : %s', newCmd ); } - e.command = newCmd; + evt.command = newCmd; } }); diff --git a/src/main/js/plugins/drone/drone.js b/src/main/js/plugins/drone/drone.js index 4a5aead..8af954c 100644 --- a/src/main/js/plugins/drone/drone.js +++ b/src/main/js/plugins/drone/drone.js @@ -118,7 +118,7 @@ Drones can be created in any of the following ways... block is broken at the block's location you would do so like this... - events.on('block.BlockBreakEvent',function( listener,event) { + events.on('block.BlockBreakEvent',function( event) { var location = event.block.location; var drone = new Drone(location); // do more stuff with the drone here... diff --git a/src/main/js/plugins/examples/example-7-hello-events.js b/src/main/js/plugins/examples/example-7-hello-events.js index b4d7d87..8723c91 100644 --- a/src/main/js/plugins/examples/example-7-hello-events.js +++ b/src/main/js/plugins/examples/example-7-hello-events.js @@ -31,9 +31,9 @@ parameters... Package' and 'Previous Package' links to browse). 2. The event handling function (also sometimes refered to as a - 'callback'). In ScriptCraft, this function takes 2 parameters, a - listener object and an event object. All of the information about - the event is in the event object. + 'callback'). In ScriptCraft, this function takes a single + parameter, an event object. All of the information about the event + is in the event object. In the example below, if a player joins the server and is an operator, then the ScriptCraft plugin information will be displayed to that @@ -79,14 +79,14 @@ cleaner and more readable. Similarly where you see a method like [bksaf]: http://jd.bukkit.org/dev/apidocs/org/bukkit/entity/Player.html#setAllowFlight() [bkapi]: http://jd.bukkit.org/dev/apidocs/ - events.on( 'player.PlayerJoinEvent', function( listener, event ) { + events.on( 'player.PlayerJoinEvent', function( event ) { if ( event.player.op ) { event.player.sendMessage('Welcome to ' + __plugin); } }); ***/ -events.on( 'player.PlayerJoinEvent', function( listener, event ) { +events.on( 'player.PlayerJoinEvent', function( event ) { if ( event.player.op ) { event.player.sendMessage( 'Welcome to ' + __plugin ); } diff --git a/src/main/js/plugins/minigames/SnowballFight.js b/src/main/js/plugins/minigames/SnowballFight.js index 9dccae6..5116caf 100644 --- a/src/main/js/plugins/minigames/SnowballFight.js +++ b/src/main/js/plugins/minigames/SnowballFight.js @@ -112,8 +112,7 @@ var _endGame = function( gameState ) { player.sendMessage( scores ); } } - handlerList = bkEntityDamageByEntityEvent.getHandlerList(); - handlerList.unregister( gameState.listener ); + gameState.listener.unregister(); gameState.inProgress = false; }; /* @@ -177,7 +176,7 @@ var createGame = function( duration, teams ) { /* this function is called every time a player is damaged by another entity/player */ - var _onSnowballHit = function( l, event ) { + var _onSnowballHit = function( event ) { var snowball = event.damager; if ( !snowball || !( snowball instanceof bkSnowball ) ) { return; diff --git a/src/main/js/plugins/minigames/cow-clicker.js b/src/main/js/plugins/minigames/cow-clicker.js index df83b0f..124b037 100644 --- a/src/main/js/plugins/minigames/cow-clicker.js +++ b/src/main/js/plugins/minigames/cow-clicker.js @@ -53,7 +53,7 @@ var store = {}, }; var scoreboard = require('minigames/scoreboard')(scoreboardConfig); -var _onPlayerInteract = function( listener, event ) { +var _onPlayerInteract = function( event ) { var player = event.player, clickedEntity = event.rightClicked, loc = clickedEntity.location; @@ -77,10 +77,10 @@ var _onPlayerInteract = function( listener, event ) { }, 200 ); } }; -var _onPlayerQuit = function( listener, event ) { +var _onPlayerQuit = function( event ) { _removePlayer( event.player ); }; -var _onPlayerJoin = function( listener, event ) { +var _onPlayerJoin = function( event ) { var gamePlayer = store[event.player.name]; if ( gamePlayer ) { _addPlayer( event.player, gamePlayer.score ); From c3db7a431448a83c2d37b38caa7528e185d1dc4b Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 15 Mar 2014 18:12:34 +0000 Subject: [PATCH 153/456] Updated to reflect events rework. --- docs/Anatomy-of-a-Plugin.md | 38 +++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/docs/Anatomy-of-a-Plugin.md b/docs/Anatomy-of-a-Plugin.md index c03a99c..281bf62 100644 --- a/docs/Anatomy-of-a-Plugin.md +++ b/docs/Anatomy-of-a-Plugin.md @@ -46,11 +46,11 @@ chosen color... var colorCodes = {}; for (var i =0;i < colors.length;i++) colorCodes[colors[i]] = i.toString(16); - events.on('player.AsyncPlayerChatEvent',function(l,e){ - var player = e.player; - var playerChatColor = _store.players[player.name]; - if (playerChatColor){ - e.message = '§' + colorCodes[playerChatColor] + e.message; + events.on( 'player.AsyncPlayerChatEvent', function( evt ) { + var player = evt.player; + var playerChatColor = _store.players[ player.name ]; + if ( playerChatColor ) { + evt.message = '§' + colorCodes[ playerChatColor ] + e.message; } }); @@ -71,7 +71,7 @@ choose their text color? If you've written a javascript function and want players to be able to use that function, you expose it using the new `command()` function like so... - command('chat_color',function(params,sender){ + command( 'chat_color', function( params, sender ) { var color = params[0]; if (colorCodes[color]){ chat.setColor(sender,color); @@ -95,7 +95,7 @@ players use to change their chat color setting. The full plugin source code is just a couple of lines of code but is a fully working plugin... // declare a new javascript plugin - var _store = {players: {}} // private variable + var _store = { players: {} } ; // private variable exports.chat = plugin('chat', { setColor: function(player, color){ _store.players[player.name] = color; @@ -108,24 +108,26 @@ code is just a couple of lines of code but is a fully working plugin... 'brightgreen', 'aqua', 'red', 'pink', 'yellow', 'white']; var colorCodes = {}; - for (var i =0;i < colors.length;i++) colorCodes[colors[i]] = i.toString(16); + for ( var i =0; i < colors.length; i++ ) { + colorCodes[ colors[i] ] = i.toString(16); + } - events.on('player.AsyncPlayerChatEvent',function(l,e){ - var player = e.player; + events.on( 'player.AsyncPlayerChatEvent', function( evt ) { + var player = evt.player; var playerChatColor = _store.players[player.name]; - if (playerChatColor){ - e.message = '§' + colorCodes[playerChatColor] + e.message; + if ( playerChatColor ) { + evt.message = '§' + colorCodes[playerChatColor] + e.message; } }); - command('chat_color',function(params,sender){ + command( 'chat_color', function( params, sender ) { var color = params[0]; - if (colorCodes[color]){ - chat.setColor(sender,color); + if ( colorCodes[ color ] ) { + chat.setColor( sender, color ); }else{ - sender.sendMessage(color + ' is not a valid color'); - sender.sendMessage(colors.join(',')); + sender.sendMessage( color + ' is not a valid color' ); + sender.sendMessage( colors.join(',') ); } - },colors); + }, colors ); ![Chat Color plugin][1] From d59b0682de84787d277954c78356c0aea4bb5081 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 15 Mar 2014 18:21:20 +0000 Subject: [PATCH 154/456] added comment to leave semicolon off of last line. --- src/main/js/lib/require.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/js/lib/require.js b/src/main/js/lib/require.js index 2769d83..4d7398f 100644 --- a/src/main/js/lib/require.js +++ b/src/main/js/lib/require.js @@ -267,7 +267,7 @@ When resolving module names to file paths, ScriptCraft uses the following rules. } catch (e) { throw new Error( "Error executing module " + path + " line #" + e.lineNumber - + " : " + e.message, canonizedFilename, e.lineNumber ) + + " : " + e.message, canonizedFilename, e.lineNumber ); } if ( hooks ) { hooks.loaded( canonizedFilename ); @@ -283,4 +283,5 @@ When resolving module names to file paths, ScriptCraft uses the following rules. }; }; return _requireClosure( new java.io.File(rootDir) ); + // last line deliberately has no semicolon! }) From aa58d490fe56fc9b5e96b3f90524ea5ace1ee084 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 15 Mar 2014 18:33:03 +0000 Subject: [PATCH 155/456] release notes for simplified event handling --- docs/release-notes.md | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 5f85f05..52f64f7 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,7 +1,33 @@ -# 2014 03 12 +# 2014 03 15 ## Version 2.0.6 +## Simplified Event handling code. + +The callback function for event handling now only takes 1 single +parameter, the event which triggered the callback. The listener object +is bound to the callback function so within the callback function +`this` refers to the listener object. Unregistering listeners has also +been greatly simplified. You can have an event handler which fires +only once by unregistering itself within the callback like this... + + events.on('player.PlayerJoinEvent', function( event ) { + + // do something + event.player.sendMessage( "You're the first player to join" ); + + // unregister so this function is called only once ever. + this.unregister(); + + }); + +The `events.on()` function also returns the same listener object as +`this` refered to inside the callback. The listener object has a +single method `unregister()` which can be called to stop listening for +the event. + +# 2014 03 12 + Added Drone.MAX_VOLUME and Drone.MAX_SIDE properties to specify limits on size of Drone ops. This is to stop individual players from hogging the CPU in a classrom environment. From 9b0ac86de2ba17f72d31eba56674333648e703c2 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sun, 16 Mar 2014 18:57:59 +0000 Subject: [PATCH 156/456] Fixes #130 --- build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.properties b/build.properties index fc616c4..eacfba2 100644 --- a/build.properties +++ b/build.properties @@ -1,2 +1,2 @@ bukkit-version=1.7.2 -scriptcraft-version=2.0.2 +scriptcraft-version=2.0.6 From 76c6d6f23f4790ca57139aedcb8e4272ce9df14f Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sun, 16 Mar 2014 18:58:19 +0000 Subject: [PATCH 157/456] Fixes #132 --- src/main/js/lib/js-patch.js | 38 ++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/main/js/lib/js-patch.js b/src/main/js/lib/js-patch.js index db6cfb3..ea8b7c4 100644 --- a/src/main/js/lib/js-patch.js +++ b/src/main/js/lib/js-patch.js @@ -7,7 +7,43 @@ module.exports = function( $ ) { return this.replace( /^\s+|\s+$/g, '' ); }; } - + + // wph 20140316 Java 1.6.0_65 on mac does not have Function.prototype.bind + // code from http://webreflection.blogspot.ie/2010/02/functionprototypebind.html + if (typeof Function.prototype.bind == 'undefined' ) { + Function.prototype.bind = (function (slice){ + // (C) WebReflection - Mit Style License + function bind(context) { + var self = this; // "trapped" function reference + // only if there is more than an argument + // we are interested into more complex operations + // this will speed up common bind creation + // avoiding useless slices over arguments + if (1 < arguments.length) { + // extra arguments to send by default + var $arguments = slice.call(arguments, 1); + return function () { + return self.apply( + context, + // thanks @kangax for this suggestion + arguments.length ? + // concat arguments with those received + $arguments.concat(slice.call(arguments)) : + // send just arguments, no concat, no slice + $arguments + ); + }; + } + // optimized callback + return function () { + // speed up when function is called without arguments + return arguments.length ? self.apply(context, arguments) : self.call(context); + }; + } + // the named function + return bind; + }(Array.prototype.slice)); + } $.setTimeout = function( callback, delayInMillis ) { /* javascript programmers familiar with setTimeout know that it expects From d6d1a906b5ef370e515640486381a11c1e782eea Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sun, 16 Mar 2014 19:50:18 +0000 Subject: [PATCH 158/456] Fixes #129 --- src/main/js/lib/tabcomplete.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/js/lib/tabcomplete.js b/src/main/js/lib/tabcomplete.js index 37c0774..1f0a52c 100644 --- a/src/main/js/lib/tabcomplete.js +++ b/src/main/js/lib/tabcomplete.js @@ -101,6 +101,7 @@ var onTabCompleteJS = function( result, cmdSender, pluginCmd, cmdAlias, cmdArgs name, symbol, lastGoodSymbol, + lastArgProp, i, objectProps, candidate, @@ -165,8 +166,9 @@ var onTabCompleteJS = function( result, cmdSender, pluginCmd, cmdAlias, cmdArgs objectProps = _getProperties( lastGoodSymbol ); if ( name == '' ) { // if the last symbol looks like this.. - // ScriptCraft. + // server. // + //print('debug:case Y1: server.'); for ( i = 0; i < objectProps.length; i++ ) { candidate = lastSymbol + objectProps[i]; @@ -176,9 +178,9 @@ var onTabCompleteJS = function( result, cmdSender, pluginCmd, cmdAlias, cmdArgs } else { // it looks like this.. - // ScriptCraft.co + // server.wo // - //print('debug:case Y: ScriptCraft.co'); + //print('debug:case Y2: server.wo'); li = statement.lastIndexOf(name); for ( i = 0; i < objectProps.length; i++ ) { @@ -191,10 +193,13 @@ var onTabCompleteJS = function( result, cmdSender, pluginCmd, cmdAlias, cmdArgs } } } else { + //print('debug:case Y3: server'); objectProps = _getProperties( symbol ); for ( i = 0; i < objectProps.length; i++ ) { re = new RegExp( lastSymbol+ '$', 'g' ); - propsOfLastArg.push( lastArg.replace( re, lastSymbol + '.' + objectProps[i] ) ); + lastArgProp = lastArg.replace( re, lastSymbol + '.' + objectProps[i] ) ; + lastArgProp = lastArgProp.replace(/\.\./g,'.'); + propsOfLastArg.push( lastArgProp ); } } } else { From ebf2e031c614fb2cc827923979026cb3a79dd859 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sun, 16 Mar 2014 21:24:26 +0000 Subject: [PATCH 159/456] Fixes #131 --- src/main/js/plugins/drone/sphere.js | 120 ++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/src/main/js/plugins/drone/sphere.js b/src/main/js/plugins/drone/sphere.js index a4c27b6..4af3907 100644 --- a/src/main/js/plugins/drone/sphere.js +++ b/src/main/js/plugins/drone/sphere.js @@ -36,6 +36,9 @@ Drone.extend( 'sphere', function( block, radius ) { v, h; + if ( radius > 127 ) { + throw new Error('Sphere radius must be less than 128 blocks'); + } for ( i = 0; i <= radius; i++ ) { newRadius = Math.round( Math.sqrt( r2 - i * i ) ); if ( newRadius == lastRadius ) { @@ -117,6 +120,10 @@ Drone.extend('sphere0', function(block,radius) len, yOffset; + if ( radius > 127 ) { + throw new Error('Sphere radius must be less than 128 blocks'); + } + for ( i = 0; i <= radius; i++ ) { newRadius = Math.round( Math.sqrt( r2 - i * i ) ); if ( newRadius == lastRadius ) { @@ -207,6 +214,11 @@ Drone.extend( 'hemisphere', function( block, radius, northSouth ) { r2 = radius * radius, i = 0, newRadius; + + if ( radius > 255 ) { + throw new Error('Hemisphere radius must be less than 256 blocks'); + } + for ( i = 0; i <= radius; i++ ) { newRadius = Math.round( Math.sqrt( r2 - i * i ) ); if ( newRadius == lastRadius ) { @@ -270,8 +282,116 @@ To create a glass 'north' hemisphere with a radius of 20 blocks... ***/ Drone.extend( 'hemisphere0', function( block, radius, northSouth ) { + + if ( radius > 255 ) { + throw new Error('Hemisphere radius must be less than 256 blocks'); + } + +/* return this.hemisphere( block, radius, northSouth) .fwd().right().up( northSouth == 'north' ? 0 : 1 ) .hemisphere( 0, radius-1, northSouth ) .back().left().down( northSouth == 'north' ? 0 : 1 ); +*/ + var lastRadius = radius, + slices = [ [ radius, 0 ] ], + diameter = radius * 2, + bm = this._getBlockIdAndMeta(block), + r2 = radius * radius, + i = 0, + len, + newRadius; + + if ( radius > 255 ) { + throw new Error('Hemisphere radius must be less than 256 blocks'); + } + + + for ( i = 0; i <= radius; i++ ) { + newRadius = Math.round( Math.sqrt( r2 - i * i ) ); + if ( newRadius == lastRadius ) { + slices[ slices.length - 1 ][ 1 ]++; + } else { + slices.push( [ newRadius, 1 ] ); + } + lastRadius = newRadius; + } + this.chkpt( 'hsphere0' ); + // + // mid section + // + if ( northSouth == 'north' ) { + //this.cylinder( block, radius, slices[0][1], { blockType: bm[0], meta: bm[1] } ); + this.arc({ + blockType: bm[0], + meta: bm[1], + radius: radius, + strokeWidth: 1, + stack: slices[0][1], + fill: false + }); + } else { + this.up( radius - slices[0][1] ); + this.arc({ + blockType: bm[0], + meta: bm[1], + radius: radius, + strokeWidth: 1, + stack: slices[0][1], + fill: false + }); + //this.cylinder( block, radius, slices[0][1], { blockType: bm[0], meta: bm[1] } ) + this.down( radius - slices[0][1] ); + } + + var yOffset = -1; + len = slices.length; + for ( i = 1; i < slices.length; i++ ) { + yOffset += slices[i-1][1]; + var sr = slices[i][0]; + var sh = slices[i][1]; + var v = yOffset, h = radius-sr; + if ( northSouth == 'north' ) { + // northern hemisphere + this.up( v ) + .fwd( h ) + .right( h ); + + //this.cylinder( block, sr, sh, { blockType: bm[0], meta: bm[1] } ); + this.arc( { + blockType: bm[0], + meta: bm[1], + radius: sr, + stack: sh, + fill: false, + strokeWidth: i < len - 1 ? 1 + ( sr - slices[ i + 1 ][ 0 ] ) : 1 + } ); + + this.left( h ) + .back( h ) + .down( v ); + } else { + // southern hemisphere + v = radius - ( yOffset + sh + 1 ); + this.up( v ) + .fwd( h ) + .right( h ); + + //this.cylinder( block, sr, sh, { blockType: bm[0], meta: bm[1] } ); + this.arc({ + blockType: bm[0], + meta: bm[1], + radius: sr, + stack: sh, + fill: false, + strokeWidth: i < len - 1 ? 1 + ( sr - slices[ i + 1 ][ 0 ] ) : 1 + }); + + this.left( h ) + .back( h ) + .down( v ); + } + } + return this.move( 'hsphere0' ); + }); From d64128705fde660f09a3bde634bc6769a4c84e29 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sun, 16 Mar 2014 21:25:37 +0000 Subject: [PATCH 160/456] Fixes #131 --- src/main/js/plugins/drone/sphere.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/js/plugins/drone/sphere.js b/src/main/js/plugins/drone/sphere.js index 4af3907..7c99735 100644 --- a/src/main/js/plugins/drone/sphere.js +++ b/src/main/js/plugins/drone/sphere.js @@ -354,28 +354,28 @@ Drone.extend( 'hemisphere0', function( block, radius, northSouth ) { if ( northSouth == 'north' ) { // northern hemisphere this.up( v ) - .fwd( h ) - .right( h ); + .fwd( h ) + .right( h ); //this.cylinder( block, sr, sh, { blockType: bm[0], meta: bm[1] } ); this.arc( { - blockType: bm[0], - meta: bm[1], - radius: sr, - stack: sh, - fill: false, - strokeWidth: i < len - 1 ? 1 + ( sr - slices[ i + 1 ][ 0 ] ) : 1 + blockType: bm[0], + meta: bm[1], + radius: sr, + stack: sh, + fill: false, + strokeWidth: i < len - 1 ? 1 + ( sr - slices[ i + 1 ][ 0 ] ) : 1 } ); this.left( h ) - .back( h ) - .down( v ); + .back( h ) + .down( v ); } else { // southern hemisphere v = radius - ( yOffset + sh + 1 ); this.up( v ) - .fwd( h ) - .right( h ); + .fwd( h ) + .right( h ); //this.cylinder( block, sr, sh, { blockType: bm[0], meta: bm[1] } ); this.arc({ @@ -388,8 +388,8 @@ Drone.extend( 'hemisphere0', function( block, radius, northSouth ) { }); this.left( h ) - .back( h ) - .down( v ); + .back( h ) + .down( v ); } } return this.move( 'hsphere0' ); From 2adf0e3792aee8df6c10e63284581eff96c550b9 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sun, 16 Mar 2014 21:26:29 +0000 Subject: [PATCH 161/456] removed commented code. --- src/main/js/plugins/drone/sphere.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/js/plugins/drone/sphere.js b/src/main/js/plugins/drone/sphere.js index 7c99735..bffea0a 100644 --- a/src/main/js/plugins/drone/sphere.js +++ b/src/main/js/plugins/drone/sphere.js @@ -321,7 +321,7 @@ Drone.extend( 'hemisphere0', function( block, radius, northSouth ) { // mid section // if ( northSouth == 'north' ) { - //this.cylinder( block, radius, slices[0][1], { blockType: bm[0], meta: bm[1] } ); + this.arc({ blockType: bm[0], meta: bm[1], @@ -332,6 +332,7 @@ Drone.extend( 'hemisphere0', function( block, radius, northSouth ) { }); } else { this.up( radius - slices[0][1] ); + this.arc({ blockType: bm[0], meta: bm[1], @@ -340,7 +341,7 @@ Drone.extend( 'hemisphere0', function( block, radius, northSouth ) { stack: slices[0][1], fill: false }); - //this.cylinder( block, radius, slices[0][1], { blockType: bm[0], meta: bm[1] } ) + this.down( radius - slices[0][1] ); } @@ -357,7 +358,6 @@ Drone.extend( 'hemisphere0', function( block, radius, northSouth ) { .fwd( h ) .right( h ); - //this.cylinder( block, sr, sh, { blockType: bm[0], meta: bm[1] } ); this.arc( { blockType: bm[0], meta: bm[1], @@ -377,7 +377,6 @@ Drone.extend( 'hemisphere0', function( block, radius, northSouth ) { .fwd( h ) .right( h ); - //this.cylinder( block, sr, sh, { blockType: bm[0], meta: bm[1] } ); this.arc({ blockType: bm[0], meta: bm[1], From dcef5a0b9c7532a2474800c0e12eb241631cb79c Mon Sep 17 00:00:00 2001 From: Marco Aurelio Gerosa Date: Sun, 6 Apr 2014 21:32:34 -0700 Subject: [PATCH 162/456] Update YoungPersonsGuideToProgrammingMinecraft.md --- docs/YoungPersonsGuideToProgrammingMinecraft.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/YoungPersonsGuideToProgrammingMinecraft.md b/docs/YoungPersonsGuideToProgrammingMinecraft.md index 1ad8abd..1cf05df 100644 --- a/docs/YoungPersonsGuideToProgrammingMinecraft.md +++ b/docs/YoungPersonsGuideToProgrammingMinecraft.md @@ -140,6 +140,7 @@ Minecraft server and are ready to connect ... address field. `localhost` is a special internet address that points to your own computer. 5. Click 'Join Server' to join the craftbukkit server. +If you get a message of client disconnected, check your client version. If it's 1.7.2, you need to edit your profile and select 1.6.4 to match craftbukkit. 6. Once you've joined the game, press the `/` key located at the bottom right of your keyboard. A prompt will appear. Type the following then press enter: `js 1 + 1` The number 2 should be displayed. From 35d67fe6e417e487638f0a5c84aeb91b9a267fa2 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Mon, 7 Apr 2014 20:43:23 +0100 Subject: [PATCH 163/456] Fixes issue #133 --- src/main/js/plugins/chat/color.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/js/plugins/chat/color.js b/src/main/js/plugins/chat/color.js index eb85f6c..533b557 100644 --- a/src/main/js/plugins/chat/color.js +++ b/src/main/js/plugins/chat/color.js @@ -73,11 +73,11 @@ foreach( colors, function ( color, i ) { colorCodes[color] = i.toString( 16 ); } ); -events.on( 'player.AsyncPlayerChatEvent', function( l, e ) { - var player = e.player; +events.on( 'player.AsyncPlayerChatEvent', function( event ) { + var player = event.player; var playerChatColor = _store.players[ player.name ]; if ( playerChatColor ) { - e.message = '§' + colorCodes[ playerChatColor ] + e.message; + event.message = '§' + colorCodes[ playerChatColor ] + event.message; } }); From 7b7d8cb35c4b3a720b83c282b3c6442e22104652 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Mon, 7 Apr 2014 20:59:09 +0100 Subject: [PATCH 164/456] Fix issue #135 --- src/main/js/plugins/drone/drone.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/js/plugins/drone/drone.js b/src/main/js/plugins/drone/drone.js index 8af954c..7d3f3b7 100644 --- a/src/main/js/plugins/drone/drone.js +++ b/src/main/js/plugins/drone/drone.js @@ -684,7 +684,7 @@ var putSign = function( texts, x, y, z, blockId, meta, world, immediate ) { var Drone = function( x, y, z, dir, world ) { this.record = false; var usePlayerCoords = false; - var player = self; + var player = (typeof self !== 'undefined' ? self : null); if ( x instanceof bkPlayer ) { player = x; } From 8c81b37bb322393be04a02016a77d3023f9145ae Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sun, 13 Apr 2014 23:25:02 +0100 Subject: [PATCH 165/456] Added asynchronous player input (async prompt) function. --- docs/API-Reference.md | 57 ++++++++++++++++++++++++ docs/release-notes.md | 4 ++ src/main/js/modules/input.js | 86 ++++++++++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+) create mode 100644 src/main/js/modules/input.js diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 75da1d1..81c458b 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -50,6 +50,7 @@ Walter Higgins * [Examples](#examples) * [Fireworks Module](#fireworks-module) * [Examples](#examples-1) + * [Asynchronous Input Module](#asynchronous-input-module) * [Http Module](#http-module) * [http.request() function](#httprequest-function) * [sc-mqtt module](#sc-mqtt-module) @@ -819,6 +820,62 @@ location. For example... ![firework example](img/firework.png) +## Asynchronous Input Module + +The `input` module provides a simple way to prompt players for input at the +in-game prompt. In Javascript browser environments the `prompt()` function provides +a way to block execution and ask the user for input. Execution is blocked until the user +provides input using the modal dialog and clicks OK. Unfortunately Minecraft provides no +equivalent modal dialog which can be used to gather player text input. The only way to gather text +input from the player in Minecraft is to do so asynchronously. That is - a prompt message can be +sent to the player but the player is not obliged to provide input immediately, nor does the program +execution block until the player does so. + +So ScriptCraft has no `prompt()` implementation because `prompt()` is a synchronous function and +Minecraft's API provides no equivalent functions or classes which can be used to implement this synchronously. +The Minecraft API does however have a 'Conversation' API which allows for prompting of the player and asynchronously gathering text input from the player. + +This new `input()` function is best illustrated by example. The following code is for a number-guessing game: + +```javascript +var input = require('input'); +exports.numberguess = function(player){ + var randomNumber = Math.ceil(Math.random() * 10); + input( player, 'Think of a number between 1 and 10 (q to quit)', function( guess, repeat ) { + if ( guess == 'q'){ + return; + } + if ( +guess !== randomNumber ) { + if (+guess < randomNumber ) { + player.sendMessage('Too low - guess again'); + } + if (+guess > randomNumber ) { + player.sendMessage('Too high - guess again'); + } + repeat(); + } else { + player.sendMessage('You guessed correctly'); + } + }); +}; +``` + +The `input()` function takes 3 parameters, the player, a prompt message and a callback which will be invoked when the player has entered some text at the in-game command prompt. +The callback is bound to an object which has the following properties: + + * sender : The player who input the text + * value : The value of the text which has been input. + * message: The message prompt. + * repeat: A function which when invoked will repeat the original prompt. (this is for flow control) + +The callback function as well as being bound to an object with the above properties (so you can use this.value inside your callback to get the value which has just been input), can also take the following parameters (in exact order): + + * value + * repeat + * sender + +The `value` parameter will be the same as `this.value`, the `repeat` parameter will be the same as `this.repeat` and so on. + ## Http Module For handling http requests. Not to be confused with the more robust diff --git a/docs/release-notes.md b/docs/release-notes.md index 52f64f7..4e52909 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,3 +1,7 @@ +# 2014 04 13 + +Added asynchronous `input()` function module. + # 2014 03 15 ## Version 2.0.6 diff --git a/src/main/js/modules/input.js b/src/main/js/modules/input.js new file mode 100644 index 0000000..4941085 --- /dev/null +++ b/src/main/js/modules/input.js @@ -0,0 +1,86 @@ +/************************************************************************* +## Asynchronous Input Module + +The `input` module provides a simple way to prompt players for input at the +in-game prompt. In Javascript browser environments the `prompt()` function provides +a way to block execution and ask the user for input. Execution is blocked until the user +provides input using the modal dialog and clicks OK. Unfortunately Minecraft provides no +equivalent modal dialog which can be used to gather player text input. The only way to gather text +input from the player in Minecraft is to do so asynchronously. That is - a prompt message can be +sent to the player but the player is not obliged to provide input immediately, nor does the program +execution block until the player does so. + +So ScriptCraft has no `prompt()` implementation because `prompt()` is a synchronous function and +Minecraft's API provides no equivalent functions or classes which can be used to implement this synchronously. +The Minecraft API does however have a 'Conversation' API which allows for prompting of the player and asynchronously gathering text input from the player. + +This new `input()` function is best illustrated by example. The following code is for a number-guessing game: + +```javascript +var input = require('input'); +exports.numberguess = function(player){ + var randomNumber = Math.ceil(Math.random() * 10); + input( player, 'Think of a number between 1 and 10 (q to quit)', function( guess, repeat ) { + if ( guess == 'q'){ + return; + } + if ( +guess !== randomNumber ) { + if (+guess < randomNumber ) { + player.sendMessage('Too low - guess again'); + } + if (+guess > randomNumber ) { + player.sendMessage('Too high - guess again'); + } + repeat(); + } else { + player.sendMessage('You guessed correctly'); + } + }); +}; +``` + +The `input()` function takes 3 parameters, the player, a prompt message and a callback which will be invoked when the player has entered some text at the in-game command prompt. +The callback is bound to an object which has the following properties: + + * sender : The player who input the text + * value : The value of the text which has been input. + * message: The message prompt. + * repeat: A function which when invoked will repeat the original prompt. (this is for flow control) + +The callback function as well as being bound to an object with the above properties (so you can use this.value inside your callback to get the value which has just been input), can also take the following parameters (in exact order): + + * value + * repeat + * sender + +The `value` parameter will be the same as `this.value`, the `repeat` parameter will be the same as `this.repeat` and so on. + +***/ + +var bkPrompt = org.bukkit.conversations.Prompt, + bkConversationFactory = org.bukkit.conversations.ConversationFactory; + +function asyncInput( sender, promptMesg, callback) { + var repeat = function(){ + asyncInput( sender, promptMesg, callback); + }; + var prompt = new bkPrompt( ) { + getPromptText: function( ctx ) { + return promptMesg; + }, + acceptInput: function( ctx, value ) { + callback.apply( { repeat: repeat, sender: sender, message: promptMesg, value: value }, + [value, repeat, sender]); + return null; + }, + blocksForInput: function( ctx ) { + return true; + } + }; + new bkConversationFactory( __plugin ) + .withModality( true ) + .withFirstPrompt( prompt ) + .buildConversation( sender ) + .begin( ); +} +module.exports = asyncInput; From 3c7f8ae94e0cf00ae0d41551b67e00889ea24abc Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 19 Apr 2014 17:28:43 +0100 Subject: [PATCH 166/456] Make drone build processing more evenly distributed for multiple players --- .gitignore | 2 + ...YoungPersonsGuideToProgrammingMinecraft.md | 12 ++- nbproject/ide-targets.xml | 14 ++++ src/docs/templates/ypgpm.md | 11 ++- src/main/js/plugins/drone/contrib/cottage.js | 59 +++++++++----- src/main/js/plugins/drone/drone.js | 79 +++++++++++++------ 6 files changed, 124 insertions(+), 53 deletions(-) create mode 100644 nbproject/ide-targets.xml diff --git a/.gitignore b/.gitignore index 330fd3b..86d431e 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ build.local.properties /src/main/javascript/plugins/.#example-1.js /nbproject/private/private.xml /nbproject/project.xml +/src/docs/nbproject/private/ +/src/docs/build/ \ No newline at end of file diff --git a/docs/YoungPersonsGuideToProgrammingMinecraft.md b/docs/YoungPersonsGuideToProgrammingMinecraft.md index 1cf05df..24c465e 100644 --- a/docs/YoungPersonsGuideToProgrammingMinecraft.md +++ b/docs/YoungPersonsGuideToProgrammingMinecraft.md @@ -62,7 +62,7 @@ easy addition of 'Mods' and extensions to Minecraft. ScriptCraft is a difficult but CraftBukkit makes it easy. Follow these steps to Install ScriptCraft on your computer... -1. [Download and install CraftBukkit][dlbuk]. Then follow the [Bukkit +1. [Download and install CraftBukkit][dlbuk2] (choose either Recommended, Beta or Development) . Then follow the [Bukkit Installation Instructions][bii]. (Tip: You can grab the very latest version of bukkit from the [alternative versions list][dlbuk2]) @@ -139,8 +139,13 @@ Minecraft server and are ready to connect ... 4. Type any name you like in the name field then type `localhost` in the address field. `localhost` is a special internet address that points to your own computer. -5. Click 'Join Server' to join the craftbukkit server. -If you get a message of client disconnected, check your client version. If it's 1.7.2, you need to edit your profile and select 1.6.4 to match craftbukkit. +5. Click 'Join Server' to join the craftbukkit server. If the version +of Minecraft is incompatible with the version of CraftBukkit you will +not be able to connect to the server. To fix this, you can create a +Minecraft profile in your client. Profiles let you decide which +version of Minecraft client you want to run so that your client and +server are compatible. + 6. Once you've joined the game, press the `/` key located at the bottom right of your keyboard. A prompt will appear. Type the following then press enter: `js 1 + 1` The number 2 should be displayed. @@ -1269,7 +1274,6 @@ different objects and methods available for use by ScriptCraft. [buk]: http://wiki.bukkit.org/Setting_up_a_server -[dlbuk]: http://dl.bukkit.org/ [dlbuk2]: http://dl.bukkit.org/downloads/craftbukkit/ [bii]: http://wiki.bukkit.org/Setting_up_a_server [sc-plugin]: http://scriptcraftjs.org/download/ diff --git a/nbproject/ide-targets.xml b/nbproject/ide-targets.xml new file mode 100644 index 0000000..efb4aa5 --- /dev/null +++ b/nbproject/ide-targets.xml @@ -0,0 +1,14 @@ + + + + + + + Starting Bukkit with ScriptCraft + + + + + + + diff --git a/src/docs/templates/ypgpm.md b/src/docs/templates/ypgpm.md index 38d46c3..39b30a2 100644 --- a/src/docs/templates/ypgpm.md +++ b/src/docs/templates/ypgpm.md @@ -26,7 +26,7 @@ easy addition of 'Mods' and extensions to Minecraft. ScriptCraft is a difficult but CraftBukkit makes it easy. Follow these steps to Install ScriptCraft on your computer... -1. [Download and install CraftBukkit][dlbuk]. Then follow the [Bukkit +1. [Download and install CraftBukkit][dlbuk2] (choose either Recommended, Beta or Development) . Then follow the [Bukkit Installation Instructions][bii]. (Tip: You can grab the very latest version of bukkit from the [alternative versions list][dlbuk2]) @@ -103,7 +103,13 @@ Minecraft server and are ready to connect ... 4. Type any name you like in the name field then type `localhost` in the address field. `localhost` is a special internet address that points to your own computer. -5. Click 'Join Server' to join the craftbukkit server. +5. Click 'Join Server' to join the craftbukkit server. If the version +of Minecraft is incompatible with the version of CraftBukkit you will +not be able to connect to the server. To fix this, you can create a +Minecraft profile in your client. Profiles let you decide which +version of Minecraft client you want to run so that your client and +server are compatible. + 6. Once you've joined the game, press the `/` key located at the bottom right of your keyboard. A prompt will appear. Type the following then press enter: `js 1 + 1` The number 2 should be displayed. @@ -1232,7 +1238,6 @@ different objects and methods available for use by ScriptCraft. [buk]: http://wiki.bukkit.org/Setting_up_a_server -[dlbuk]: http://dl.bukkit.org/ [dlbuk2]: http://dl.bukkit.org/downloads/craftbukkit/ [bii]: http://wiki.bukkit.org/Setting_up_a_server [sc-plugin]: http://scriptcraftjs.org/download/ diff --git a/src/main/js/plugins/drone/contrib/cottage.js b/src/main/js/plugins/drone/contrib/cottage.js index 8301d2c..b9c2272 100644 --- a/src/main/js/plugins/drone/contrib/cottage.js +++ b/src/main/js/plugins/drone/contrib/cottage.js @@ -14,17 +14,20 @@ var Drone = require('../drone').Drone; Drone.extend('cottage',function ( ) { this.chkpt('cottage') .box0(48,7,2,6) // 4 walls - .right(3).door() // door front and center - .up(1).left(2).box(102) // windows to left and right - .right(4).box(102) - .left(5).up().prism0(53,7,6); - // - // put up a sign near door. - // - this.down().right(4) - .sign(['Home','Sweet','Home'],68); - - return this.move('cottage'); + .right(3) + .door() // door front and center + .up(1) + .left(2) + .box(102) // windows to left and right + .right(4) + .box(102) + .left(5) + .up() + .prism0(53,7,6) + .down() + .right(4) + .sign(['Home','Sweet','Home'],68) + .move('cottage'); }); // // a more complex script that builds an tree-lined avenue with @@ -41,8 +44,9 @@ Drone.extend('cottage_road', function( numberCottages ) { var cottagesPerSide = Math.floor(numberCottages/2); this .chkpt('cottage_road') // make sure the drone's state is saved. - .box(43,3,1,cottagesPerSide*(distanceBetweenTrees+1)) // build the road - .up().right() // now centered in middle of road + .box( 43, 3, 1, cottagesPerSide * ( distanceBetweenTrees + 1 ) ) // build the road + .up() + .right() // now centered in middle of road .chkpt('cr'); // will be returning to this position later // @@ -50,26 +54,39 @@ Drone.extend('cottage_road', function( numberCottages ) { // for ( ; i < cottagesPerSide+1;i++ ) { this - .left(5).oak() - .right(10).oak() + .left(5) + .oak() + .right(10) + .oak() .left(5) // return to middle of road - .fwd(distanceBetweenTrees+1); // move forward. + .fwd( distanceBetweenTrees + 1 ); // move forward. } - this.move('cr').back(6); // move back 1/2 the distance between trees + this + .move('cr') + .back(6); // move back 1/2 the distance between trees // this function builds a path leading to a cottage. - function pathAndCottage( d ) { - return d.down().box(43,1,1,5).fwd(5).left(3).up().cottage(); + function pathAndCottage( drone ) { + drone + .down() + .box(43,1,1,5) + .fwd(5) + .left(3) + .up() + .cottage(); + return drone; }; // // step 3 build cottages on each side // for ( i = 0; i < cottagesPerSide; i++ ) { - this.fwd(distanceBetweenTrees+1).chkpt('r'+i); + this + .fwd( distanceBetweenTrees + 1 ) + .chkpt('r'+i); // build cottage on left pathAndCottage( this.turn(3) ).move( 'r' + i ); // build cottage on right - pathAndCottage(this.turn()).move( 'r' + i ); + pathAndCottage( this.turn() ).move( 'r' + i ); } // return drone to where it was at start of function return this.move('cottage_road'); diff --git a/src/main/js/plugins/drone/drone.js b/src/main/js/plugins/drone/drone.js index 7d3f3b7..8ff8f00 100644 --- a/src/main/js/plugins/drone/drone.js +++ b/src/main/js/plugins/drone/drone.js @@ -658,13 +658,13 @@ var putBlock = function( x, y, z, blockId, metadata, world ) { } }; -var putSign = function( texts, x, y, z, blockId, meta, world, immediate ) { +var putSign = function( drone, x, y, z, world, texts, blockId, meta, immediate ) { var i, block, state; if ( !immediate ) { - Drone.queue.push(function(){ putSign(texts, x, y, z, blockId, meta, world, true); }); + getQueue(drone).push(function(){ putSign( drone, x, y, z, world, texts, blockId, meta, true); }); return; } if ( blockId != 63 && blockId != 68 ) { @@ -755,21 +755,33 @@ exports.Drone = Drone; exports.blocks = blocks; Drone.queue = []; + Drone.opsPerSec = 10; Drone.processQueue = function(){ - var process = Drone.queue.shift(); - if (process){ - try { - process(); - } catch( e ) { - console.log('Drone build error: %s', e); - } + var process, + i = 0, + queues = getAllQueues(); + + for ( ; i < queues.length; i++ ) { + process = queues[i].shift(); + if (process){ + try { + process(); + } catch( e ) { + console.log('Drone build error: %s', e); + } + } } - setTimeout(Drone.processQueue,1000/Drone.opsPerSec); + setTimeout( Drone.processQueue, 1000 / Drone.opsPerSec ); }; -setTimeout(Drone.processQueue,1000/Drone.opsPerSec); -addUnloadHandler(function(){ - var pendingBuildOps = Drone.queue.length; +setTimeout( Drone.processQueue, 1000 / Drone.opsPerSec ); + +addUnloadHandler( function() { + var pendingBuildOps = 0; + var allQueues = getAllQueues(); + for (var i = 0; i < allQueues.length; i++){ + pendingBuildOps += allQueues[i].length; + } if (pendingBuildOps > 0){ console.warn('There were ' + pendingBuildOps + ' pending build operations which were cancelled'); } @@ -792,15 +804,7 @@ Drone.extend = function( name, func ) { global[name] = function( ) { var result = new Drone( self ); - var len = Drone.queue.length; result[name].apply( result, arguments ); - var newLen = Drone.queue.length; - if ( len > (3 * Drone.opsPerSec) || (newLen - len) > (3 * Drone.opsPerSec)) { - if ( result.player && !result.playerNotifiedPending ) { - result.player.sendMessage('Build queue will complete in ' + Math.ceil( newLen / Drone.opsPerSec ) + ' seconds (approx.)'); - result.playerNotifiedPending = true; - } - } return result; }; }; @@ -1008,12 +1012,37 @@ Drone.extend( 'sign', function( message, block ) { if ( block == 63 ) { meta = ( 12 + ( ( this.dir + 2 ) * 4 ) ) % 16; } - putSign( message, this.x, this.y, this.z, block, meta, this.world ); + putSign( this, this.x, this.y, this.z, this.world, message, block, meta); if ( block == 68 ) { this.fwd(); } }); +var playerQueues = {}; +/* + if the drone has an associated player, then use that player's queue otherwise + use the global queue. +*/ +function getQueue( drone ){ + if ( drone.player ) { + var playerName = ''+drone.player.name; + var result = playerQueues[playerName]; + if (result === undefined){ + playerQueues[playerName] = []; + return playerQueues[playerName]; + } + return result; + } else { + return Drone.queue; + } +} +function getAllQueues() { + var result = [ Drone.queue ]; + for (var pq in playerQueues) { + result.push(playerQueues[pq]) ; + } + return result; +} Drone.prototype.cuboida = function(/* Array */ blocks, w, h, d, overwrite, immediate ) { var len = blocks.length, @@ -1024,7 +1053,7 @@ Drone.prototype.cuboida = function(/* Array */ blocks, w, h, d, overwrite, immed blocks[i] = this._getBlockIdAndMeta( blocks[ i ] ); } var clone = Drone.clone(this); - Drone.queue.push(this.cuboida.bind(clone, blocks, w, h, d, overwrite, true) ); + getQueue(this).push(this.cuboida.bind(clone, blocks, w, h, d, overwrite, true) ); return this; } if ( typeof overwrite == 'undefined' ) { @@ -1096,7 +1125,7 @@ Drone.prototype.cuboidX = function( blockType, meta, w, h, d, immediate ) { if ( !immediate ) { var clone = Drone.clone(this); - Drone.queue.push(this.cuboidX.bind(clone, blockType, meta, w, h, d, true)); + getQueue(this).push(this.cuboidX.bind(clone, blockType, meta, w, h, d, true)); return this; } var depthFunc = function( ) { @@ -1550,7 +1579,7 @@ var _paste = function( name, immediate ) { if ( !immediate ) { - Drone.queue.push(function(){ _paste(name, true);}); + getQueue(this).push(function(){ _paste(name, true);}); return; } var ccContent = Drone.clipBoard[name]; From 6ec85d5a8fb098b96d04612abf6619624ebff127 Mon Sep 17 00:00:00 2001 From: Ivan Kay Date: Thu, 24 Apr 2014 19:51:11 -0500 Subject: [PATCH 167/456] Improve bkEventExecutor interface implementation What was done before isn't valid JS; This does the same thing, while keeping the JS syntax valid. --- src/main/js/lib/events.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/js/lib/events.js b/src/main/js/lib/events.js index 34dfde0..13d6d0b 100644 --- a/src/main/js/lib/events.js +++ b/src/main/js/lib/events.js @@ -121,11 +121,11 @@ exports.on = function( handlerList = eventType.getHandlerList( ); var result = { }; - eventExecutor = new bkEventExecutor( ) { + eventExecutor = new bkEventExecutor( { execute: function( l, evt ) { handler.call( result, evt ); } - }; + } ); /* wph 20130222 issue #64 bad interaction with Essentials plugin if another plugin tries to unregister a Listener (not a Plugin or a RegisteredListener) From 9d2df863b6116e160805cd2de14d70c20f89a239 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Fri, 25 Apr 2014 20:51:15 +0100 Subject: [PATCH 168/456] spacing --- docs/API-Reference.md | 2 +- src/main/js/plugins/alias/alias.js | 6 +- src/main/js/plugins/drone/drone.js | 108 ++++++++++++++--------------- 3 files changed, 58 insertions(+), 58 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 81c458b..d9c45ff 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -2006,7 +2006,7 @@ commands in a new script file and load it using /js load() ### Extending Drone -The Drone object can be easily extended - new buidling recipes/blue-prints can be added and can +The Drone object can be easily extended - new buidling recipes/blueprints can be added and can become part of a Drone's chain using the *static* method `Drone.extend`. ### Drone.extend() static method diff --git a/src/main/js/plugins/alias/alias.js b/src/main/js/plugins/alias/alias.js index 0626fc9..3ed7d30 100644 --- a/src/main/js/plugins/alias/alias.js +++ b/src/main/js/plugins/alias/alias.js @@ -178,7 +178,7 @@ var aliasCmd = command( 'alias', function( params, invoker ) { invoker.sendMessage( 'Usage:\n' + _usage ); }); -var _intercept = function( msg, invoker, exec ) { +var _intercept = function( msg, invoker, exec ) { if ( msg.trim().length == 0 ) return false; var msgParts = msg.split(' '), @@ -228,7 +228,7 @@ events.on( 'player.PlayerCommandPreprocessEvent', function( evt ) { var exec = function( cmd ) { invoker.performCommand(cmd); }; - var isAlias = _intercept( ''+evt.message, ''+invoker.name, exec); + var isAlias = _intercept( (''+evt.message).trim(), ''+invoker.name, exec); if ( isAlias ) { evt.cancelled = true; } @@ -242,7 +242,7 @@ events.on( 'server.ServerCommandEvent', function( evt ) { var exec = function( cmd ) { invoker.server.dispatchCommand( invoker, cmd); }; - var isAlias = _intercept( ''+evt.command, ''+ invoker.name, exec ); + var isAlias = _intercept( (''+evt.command).trim(), ''+ invoker.name, exec ); if ( isAlias ) { evt.command = 'jsp void'; } diff --git a/src/main/js/plugins/drone/drone.js b/src/main/js/plugins/drone/drone.js index 8ff8f00..a18f594 100644 --- a/src/main/js/plugins/drone/drone.js +++ b/src/main/js/plugins/drone/drone.js @@ -573,7 +573,7 @@ commands in a new script file and load it using /js load() ### Extending Drone -The Drone object can be easily extended - new buidling recipes/blue-prints can be added and can +The Drone object can be easily extended - new buidling recipes/blueprints can be added and can become part of a Drone's chain using the *static* method `Drone.extend`. ### Drone.extend() static method @@ -892,7 +892,6 @@ Drone.prototype.times = function( numTimes, commands ) { var command = commands[i]; var methodName = command[0]; var args = command[1]; - print ('command=' + JSON.stringify(command ) + ',methodName=' + methodName ); this[ methodName ].apply( this, args ); } } @@ -1002,7 +1001,11 @@ Drone.extend( 'sign', function( message, block ) { block = bm[0]; var meta = bm[1]; if ( block != 63 && block != 68 ) { - print('ERROR: Invalid block id for use in signs'); + var usage = 'Usage: sign("message", "63:1") or sign("message","68:1")'; + if ( this.player ) { + this.player.sendMessage(usage); + } + console.error(usage); return; } if ( block == 68 ) { @@ -1169,45 +1172,43 @@ Drone.prototype.cuboid0 = function( block, w, h, d ) { }; -Drone.extend( 'door', function( door ) { - if ( typeof door == 'undefined' ) { - door = 64; +Drone.extend( 'door', function( doorMaterial ) { + if ( typeof doorMaterial == 'undefined' ) { + doorMaterial = 64; // wood } else { - door = 71; + doorMaterial = 71; // iron } - this.cuboidX( door, this.dir ) + this.cuboidX( doorMaterial, this.dir ) .up( ) - .cuboidX( door, 8 ) + .cuboidX( doorMaterial, 8 ) .down( ); } ); Drone.extend( 'door_iron', function( ) { - var door = 71; - this.cuboidX( door, this.dir ) + this.cuboidX( 71, this.dir ) .up( ) - .cuboidX( door, 8 ) + .cuboidX( 71, 8 ) .down( ); } ); -Drone.extend( 'door2' , function( door ) { - if ( typeof door == 'undefined' ) { - door = 64; +Drone.extend( 'door2' , function( doorMaterial ) { + if ( typeof doorMaterial == 'undefined' ) { + doorMaterial = 64; } else { - door = 71; + doorMaterial = 71; } this - .cuboidX( door, this.dir ).up( ) - .cuboidX( door, 8 ).right( ) - .cuboidX( door, 9 ).down( ) - .cuboidX( door, this.dir ).left( ); + .cuboidX( doorMaterial, this.dir ).up( ) + .cuboidX( doorMaterial, 8 ).right( ) + .cuboidX( doorMaterial, 9 ).down( ) + .cuboidX( doorMaterial, this.dir ).left( ); } ); -Drone.extend( 'door2_iron' , function( door ) { - var door = 71; +Drone.extend( 'door2_iron' , function( ) { this - .cuboidX( door, this.dir ).up( ) - .cuboidX( door, 8 ).right( ) - .cuboidX( door, 9 ).down( ) - .cuboidX( door, this.dir ).left( ); + .cuboidX( 71, this.dir ).up( ) + .cuboidX( 71, 8 ).right( ) + .cuboidX( 71, 9 ).down( ) + .cuboidX( 71, this.dir ).left( ); } ); // player dirs: 0 = east, 1 = south, 2 = west, 3 = north @@ -1312,7 +1313,7 @@ Drone.prototype.toString = function( ) { return 'x: ' + this.x + ' y: '+this.y + ' z: ' + this.z + ' dir: ' + this.dir + ' '+dirs[this.dir]; }; Drone.prototype.debug = function( ) { - print(this.toString( ) ); + console.log(this.toString( ) ); return this; }; /* @@ -1390,7 +1391,7 @@ var _getStrokeDir = function( x,y ) { The daddy of all arc-related API calls - if you're drawing anything that bends it ends up here. */ -var _arc2 = function( params ) { +var _arc2 = function( params ) { var drone = params.drone; var orientation = params.orientation?params.orientation:'horizontal'; var quadrants = params.quadrants?params.quadrants:{ @@ -1419,33 +1420,32 @@ var _arc2 = function( params ) { x0 = drone.x; y0 = drone.z; } - setPixel = function( x,y ) { - x = (x-x0 ); - y = (y-y0 ); + setPixel = function( x, y ) { + x = ( x-x0 ); + y = ( y-y0 ); if ( params.fill ) { // wph 20130114 more efficient esp. for large cylinders/spheres if ( y < 0 ) { drone - .fwd(y ).right(x ) - .cuboidX(params.blockType,params.meta,1,stack,Math.abs(y*2 )+1 ) - .back(y ).left(x ); + .fwd( y ).right( x ) + .cuboidX( params.blockType, params.meta, 1, stack, Math.abs( y * 2 ) + 1 ) + .back( y ).left( x ); } }else{ if ( strokeWidth == 1 ) { - gotoxy(x,y ) - .cuboidX(params.blockType, - params.meta, + gotoxy(x,y ) + .cuboidX( params.blockType, params.meta, 1, // width stack, // height strokeWidth // depth ) .move('center' ); } else { - var strokeDir = _getStrokeDir(x,y ); + var strokeDir = _getStrokeDir( x, y ); var width = 1, depth = 1; switch ( strokeDir ) { case 0: // down - y = y-(strokeWidth-1 ); + y = y-( strokeWidth - 1 ); depth = strokeWidth; break; case 1: // up @@ -1459,9 +1459,9 @@ var _arc2 = function( params ) { width = strokeWidth; break; } - gotoxy(x,y ) - .cuboidX(params.blockType, params.meta, width, stack, depth ) - .move('center' ); + gotoxy( x, y ) + .cuboidX( params.blockType, params.meta, width, stack, depth ) + .move( 'center' ); } } @@ -1481,28 +1481,28 @@ var _arc2 = function( params ) { x0 = drone.x; y0 = drone.y; } - setPixel = function( x,y ) { - x = (x-x0 ); - y = (y-y0 ); + setPixel = function( x, y ) { + x = ( x - x0 ); + y = ( y - y0 ); if ( params.fill ) { // wph 20130114 more efficient esp. for large cylinders/spheres if ( y < 0 ) { drone - .up(y ).right(x ) - .cuboidX(params.blockType,params.meta,1,Math.abs(y*2 )+1,stack ) - .down(y ).left(x ); + .up( y ).right( x ) + .cuboidX( params.blockType, params.meta, 1, Math.abs( y * 2 ) + 1, stack ) + .down( y ).left( x ); } }else{ if ( strokeWidth == 1 ) { - gotoxy(x,y ) - .cuboidX(params.blockType,params.meta,strokeWidth,1,stack ) - .move('center' ); + gotoxy( x, y ) + .cuboidX( params.blockType, params.meta, strokeWidth, 1, stack ) + .move( 'center' ); }else{ - var strokeDir = _getStrokeDir(x,y ); + var strokeDir = _getStrokeDir( x,y ); var width = 1, height = 1; switch ( strokeDir ) { case 0: // down - y = y-(strokeWidth-1 ); + y = y - ( strokeWidth - 1 ); height = strokeWidth; break; case 1: // up @@ -1510,7 +1510,7 @@ var _arc2 = function( params ) { break; case 2: // left width = strokeWidth; - x = x-(strokeWidth-1 ); + x = x - ( strokeWidth - 1 ); break; case 3: // right width = strokeWidth; From a4968d8dc612383713503072ef0dd2c20ece1b32 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 26 Apr 2014 16:56:57 +0100 Subject: [PATCH 169/456] Events handling changes. Added new convenience functions to events module to make event handling easier still for newbies --- build.xml | 24 +- docs/API-Reference.md | 911 ++++++++++++++++++++++++++++++++- src/generateEventsHelper.js | 60 +++ src/main/js/lib/events.js | 5 +- src/main/js/lib/java-utils.js | 3 + src/main/js/lib/scriptcraft.js | 19 +- src/main/js/lib/tabcomplete.js | 17 +- 7 files changed, 1006 insertions(+), 33 deletions(-) create mode 100644 src/generateEventsHelper.js create mode 100644 src/main/js/lib/java-utils.js diff --git a/build.xml b/build.xml index 85858c5..50340c2 100644 --- a/build.xml +++ b/build.xml @@ -73,17 +73,27 @@ - + - + + + + + + + + + + + @@ -134,13 +144,13 @@ Walter Higgins - + - + - + - + - + Date: Sat, 26 Apr 2014 17:03:08 +0100 Subject: [PATCH 170/456] fixing TOC issues in API ref --- docs/API-Reference.md | 696 ++++++++++++++++++------------------ src/generateEventsHelper.js | 2 +- 2 files changed, 349 insertions(+), 349 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 0afb6aa..43dc93e 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -48,180 +48,180 @@ Walter Higgins * [Using string substitutions](#using-string-substitutions) * [Events Helper Module](#events-helper-module) * [Usage](#usage) - * [events.hanging(callback)](#eventshangingcallback) - * [events.world(callback)](#eventsworldcallback) - * [events.chunk(callback)](#eventschunkcallback) - * [events.worldUnload(callback)](#eventsworldunloadcallback) - * [events.worldLoad(callback)](#eventsworldloadcallback) - * [events.chunkLoad(callback)](#eventschunkloadcallback) - * [events.chunkPopulate(callback)](#eventschunkpopulatecallback) - * [events.portalCreate(callback)](#eventsportalcreatecallback) - * [events.spawnChange(callback)](#eventsspawnchangecallback) - * [events.chunkUnload(callback)](#eventschunkunloadcallback) - * [events.worldInit(callback)](#eventsworldinitcallback) - * [events.entity(callback)](#eventsentitycallback) - * [events.horseJump(callback)](#eventshorsejumpcallback) - * [events.entityCombust(callback)](#eventsentitycombustcallback) - * [events.entityRegainHealth(callback)](#eventsentityregainhealthcallback) - * [events.entityCombustByBlock(callback)](#eventsentitycombustbyblockcallback) - * [events.entityCombustByEntity(callback)](#eventsentitycombustbyentitycallback) - * [events.playerLeashEntity(callback)](#eventsplayerleashentitycallback) - * [events.pigZap(callback)](#eventspigzapcallback) - * [events.itemDespawn(callback)](#eventsitemdespawncallback) - * [events.entityTarget(callback)](#eventsentitytargetcallback) - * [events.slimeSplit(callback)](#eventsslimesplitcallback) - * [events.entityChangeBlock(callback)](#eventsentitychangeblockcallback) - * [events.entityPortalEnter(callback)](#eventsentityportalentercallback) - * [events.creeperPower(callback)](#eventscreeperpowercallback) - * [events.entityDeath(callback)](#eventsentitydeathcallback) - * [events.projectileHit(callback)](#eventsprojectilehitcallback) - * [events.entityTame(callback)](#eventsentitytamecallback) - * [events.potionSplash(callback)](#eventspotionsplashcallback) - * [events.expBottle(callback)](#eventsexpbottlecallback) - * [events.entityExplode(callback)](#eventsentityexplodecallback) - * [events.creatureSpawn(callback)](#eventscreaturespawncallback) - * [events.foodLevelChange(callback)](#eventsfoodlevelchangecallback) - * [events.entityInteract(callback)](#eventsentityinteractcallback) - * [events.entityBreakDoor(callback)](#eventsentitybreakdoorcallback) - * [events.entityCreatePortal(callback)](#eventsentitycreateportalcallback) - * [events.sheepRegrowWool(callback)](#eventssheepregrowwoolcallback) - * [events.explosionPrime(callback)](#eventsexplosionprimecallback) - * [events.entityUnleash(callback)](#eventsentityunleashcallback) - * [events.entityShootBow(callback)](#eventsentityshootbowcallback) - * [events.projectileLaunch(callback)](#eventsprojectilelaunchcallback) - * [events.itemSpawn(callback)](#eventsitemspawncallback) - * [events.sheepDyeWool(callback)](#eventssheepdyewoolcallback) - * [events.entityTeleport(callback)](#eventsentityteleportcallback) - * [events.block(callback)](#eventsblockcallback) - * [events.blockFade(callback)](#eventsblockfadecallback) - * [events.blockDamage(callback)](#eventsblockdamagecallback) - * [events.blockPiston(callback)](#eventsblockpistoncallback) - * [events.blockPistonExtend(callback)](#eventsblockpistonextendcallback) - * [events.blockExp(callback)](#eventsblockexpcallback) - * [events.blockGrow(callback)](#eventsblockgrowcallback) - * [events.blockPistonRetract(callback)](#eventsblockpistonretractcallback) - * [events.blockDispense(callback)](#eventsblockdispensecallback) - * [events.blockBreak(callback)](#eventsblockbreakcallback) - * [events.painting(callback)](#eventspaintingcallback) - * [events.paintingPlace(callback)](#eventspaintingplacecallback) - * [events.weather(callback)](#eventsweathercallback) - * [events.lightningStrike(callback)](#eventslightningstrikecallback) - * [events.vehicle(callback)](#eventsvehiclecallback) - * [events.vehicleEnter(callback)](#eventsvehicleentercallback) - * [events.vehicleMove(callback)](#eventsvehiclemovecallback) - * [events.vehicleCollision(callback)](#eventsvehiclecollisioncallback) - * [events.vehicleCreate(callback)](#eventsvehiclecreatecallback) - * [events.asyncPlayerPreLogin(callback)](#eventsasyncplayerprelogincallback) - * [events.playerUnleashEntity(callback)](#eventsplayerunleashentitycallback) - * [events.playerPreLogin(callback)](#eventsplayerprelogincallback) - * [events.player(callback)](#eventsplayercallback) - * [events.server(callback)](#eventsservercallback) - * [events.inventoryPickupItem(callback)](#eventsinventorypickupitemcallback) - * [events.inventoryMoveItem(callback)](#eventsinventorymoveitemcallback) - * [events.furnaceBurn(callback)](#eventsfurnaceburncallback) - * [events.inventory(callback)](#eventsinventorycallback) - * [events.brew(callback)](#eventsbrewcallback) - * [events.furnaceExtract(callback)](#eventsfurnaceextractcallback) - * [events.furnaceSmelt(callback)](#eventsfurnacesmeltcallback) - * [events.inventoryInteract(callback)](#eventsinventoryinteractcallback) - * [events.inventoryClose(callback)](#eventsinventoryclosecallback) - * [events.inventoryDrag(callback)](#eventsinventorydragcallback) - * [events.inventoryClick(callback)](#eventsinventoryclickcallback) - * [events.inventoryCreative(callback)](#eventsinventorycreativecallback) - * [events.hangingPlace(callback)](#eventshangingplacecallback) - * [events.hangingBreak(callback)](#eventshangingbreakcallback) - * [events.worldSave(callback)](#eventsworldsavecallback) - * [events.structureGrow(callback)](#eventsstructuregrowcallback) - * [events.entityDamage(callback)](#eventsentitydamagecallback) - * [events.entityTargetLivingEntity(callback)](#eventsentitytargetlivingentitycallback) - * [events.playerDeath(callback)](#eventsplayerdeathcallback) - * [events.entityDamageByBlock(callback)](#eventsentitydamagebyblockcallback) - * [events.entityDamageByEntity(callback)](#eventsentitydamagebyentitycallback) - * [events.entityPortal(callback)](#eventsentityportalcallback) - * [events.entityPortalExit(callback)](#eventsentityportalexitcallback) - * [events.signChange(callback)](#eventssignchangecallback) - * [events.leavesDecay(callback)](#eventsleavesdecaycallback) - * [events.blockRedstone(callback)](#eventsblockredstonecallback) - * [events.blockCanBuild(callback)](#eventsblockcanbuildcallback) - * [events.blockBurn(callback)](#eventsblockburncallback) - * [events.blockPhysics(callback)](#eventsblockphysicscallback) - * [events.blockIgnite(callback)](#eventsblockignitecallback) - * [events.notePlay(callback)](#eventsnoteplaycallback) - * [events.blockPlace(callback)](#eventsblockplacecallback) - * [events.blockFromTo(callback)](#eventsblockfromtocallback) - * [events.blockForm(callback)](#eventsblockformcallback) - * [events.blockSpread(callback)](#eventsblockspreadcallback) - * [events.enchantItem(callback)](#eventsenchantitemcallback) - * [events.prepareItemEnchant(callback)](#eventsprepareitemenchantcallback) - * [events.paintingBreak(callback)](#eventspaintingbreakcallback) - * [events.paintingBreakByEntity(callback)](#eventspaintingbreakbyentitycallback) - * [events.weatherChange(callback)](#eventsweatherchangecallback) - * [events.thunderChange(callback)](#eventsthunderchangecallback) - * [events.vehicleEntityCollision(callback)](#eventsvehicleentitycollisioncallback) - * [events.vehicleBlockCollision(callback)](#eventsvehicleblockcollisioncallback) - * [events.vehicleExit(callback)](#eventsvehicleexitcallback) - * [events.vehicleUpdate(callback)](#eventsvehicleupdatecallback) - * [events.vehicleDamage(callback)](#eventsvehicledamagecallback) - * [events.vehicleDestroy(callback)](#eventsvehicledestroycallback) - * [events.playerExpChange(callback)](#eventsplayerexpchangecallback) - * [events.playerRespawn(callback)](#eventsplayerrespawncallback) - * [events.playerCommandPreprocess(callback)](#eventsplayercommandpreprocesscallback) - * [events.playerPickupItem(callback)](#eventsplayerpickupitemcallback) - * [events.playerInventory(callback)](#eventsplayerinventorycallback) - * [events.playerFish(callback)](#eventsplayerfishcallback) - * [events.playerBedEnter(callback)](#eventsplayerbedentercallback) - * [events.playerLogin(callback)](#eventsplayerlogincallback) - * [events.playerDropItem(callback)](#eventsplayerdropitemcallback) - * [events.playerLevelChange(callback)](#eventsplayerlevelchangecallback) - * [events.playerVelocity(callback)](#eventsplayervelocitycallback) - * [events.playerInteract(callback)](#eventsplayerinteractcallback) - * [events.playerQuit(callback)](#eventsplayerquitcallback) - * [events.playerChatTabComplete(callback)](#eventsplayerchattabcompletecallback) - * [events.playerEggThrow(callback)](#eventsplayereggthrowcallback) - * [events.playerChat(callback)](#eventsplayerchatcallback) - * [events.playerAchievementAwarded(callback)](#eventsplayerachievementawardedcallback) - * [events.playerBedLeave(callback)](#eventsplayerbedleavecallback) - * [events.playerChannel(callback)](#eventsplayerchannelcallback) - * [events.playerStatisticIncrement(callback)](#eventsplayerstatisticincrementcallback) - * [events.playerToggleSprint(callback)](#eventsplayertogglesprintcallback) - * [events.playerInteractEntity(callback)](#eventsplayerinteractentitycallback) - * [events.playerEditBook(callback)](#eventsplayereditbookcallback) - * [events.playerKick(callback)](#eventsplayerkickcallback) - * [events.playerItemHeld(callback)](#eventsplayeritemheldcallback) - * [events.playerItemConsume(callback)](#eventsplayeritemconsumecallback) - * [events.playerGameModeChange(callback)](#eventsplayergamemodechangecallback) - * [events.playerItemBreak(callback)](#eventsplayeritembreakcallback) - * [events.playerToggleFlight(callback)](#eventsplayertoggleflightcallback) - * [events.playerAnimation(callback)](#eventsplayeranimationcallback) - * [events.asyncPlayerChat(callback)](#eventsasyncplayerchatcallback) - * [events.playerBucket(callback)](#eventsplayerbucketcallback) - * [events.playerRegisterChannel(callback)](#eventsplayerregisterchannelcallback) - * [events.playerMove(callback)](#eventsplayermovecallback) - * [events.playerTeleport(callback)](#eventsplayerteleportcallback) - * [events.playerBucketFill(callback)](#eventsplayerbucketfillcallback) - * [events.playerJoin(callback)](#eventsplayerjoincallback) - * [events.playerShearEntity(callback)](#eventsplayershearentitycallback) - * [events.playerToggleSneak(callback)](#eventsplayertogglesneakcallback) - * [events.playerChangedWorld(callback)](#eventsplayerchangedworldcallback) - * [events.serverCommand(callback)](#eventsservercommandcallback) - * [events.remoteServerCommand(callback)](#eventsremoteservercommandcallback) - * [events.mapInitialize(callback)](#eventsmapinitializecallback) - * [events.service(callback)](#eventsservicecallback) - * [events.plugin(callback)](#eventsplugincallback) - * [events.serviceRegister(callback)](#eventsserviceregistercallback) - * [events.serverListPing(callback)](#eventsserverlistpingcallback) - * [events.serviceUnregister(callback)](#eventsserviceunregistercallback) - * [events.prepareItemCraft(callback)](#eventsprepareitemcraftcallback) - * [events.inventoryOpen(callback)](#eventsinventoryopencallback) - * [events.craftItem(callback)](#eventscraftitemcallback) - * [events.hangingBreakByEntity(callback)](#eventshangingbreakbyentitycallback) - * [events.blockMultiPlace(callback)](#eventsblockmultiplacecallback) - * [events.entityBlockForm(callback)](#eventsentityblockformcallback) - * [events.playerBucketEmpty(callback)](#eventsplayerbucketemptycallback) - * [events.playerPortal(callback)](#eventsplayerportalcallback) - * [events.playerUnregisterChannel(callback)](#eventsplayerunregisterchannelcallback) - * [events.pluginDisable(callback)](#eventsplugindisablecallback) - * [events.pluginEnable(callback)](#eventspluginenablecallback) + * [events.hanging()](#eventshanging) + * [events.world()](#eventsworld) + * [events.chunk()](#eventschunk) + * [events.worldUnload()](#eventsworldunload) + * [events.worldLoad()](#eventsworldload) + * [events.chunkLoad()](#eventschunkload) + * [events.chunkPopulate()](#eventschunkpopulate) + * [events.portalCreate()](#eventsportalcreate) + * [events.spawnChange()](#eventsspawnchange) + * [events.chunkUnload()](#eventschunkunload) + * [events.worldInit()](#eventsworldinit) + * [events.entity()](#eventsentity) + * [events.horseJump()](#eventshorsejump) + * [events.entityCombust()](#eventsentitycombust) + * [events.entityRegainHealth()](#eventsentityregainhealth) + * [events.entityCombustByBlock()](#eventsentitycombustbyblock) + * [events.entityCombustByEntity()](#eventsentitycombustbyentity) + * [events.playerLeashEntity()](#eventsplayerleashentity) + * [events.pigZap()](#eventspigzap) + * [events.itemDespawn()](#eventsitemdespawn) + * [events.entityTarget()](#eventsentitytarget) + * [events.slimeSplit()](#eventsslimesplit) + * [events.entityChangeBlock()](#eventsentitychangeblock) + * [events.entityPortalEnter()](#eventsentityportalenter) + * [events.creeperPower()](#eventscreeperpower) + * [events.entityDeath()](#eventsentitydeath) + * [events.projectileHit()](#eventsprojectilehit) + * [events.entityTame()](#eventsentitytame) + * [events.potionSplash()](#eventspotionsplash) + * [events.expBottle()](#eventsexpbottle) + * [events.entityExplode()](#eventsentityexplode) + * [events.creatureSpawn()](#eventscreaturespawn) + * [events.foodLevelChange()](#eventsfoodlevelchange) + * [events.entityInteract()](#eventsentityinteract) + * [events.entityBreakDoor()](#eventsentitybreakdoor) + * [events.entityCreatePortal()](#eventsentitycreateportal) + * [events.sheepRegrowWool()](#eventssheepregrowwool) + * [events.explosionPrime()](#eventsexplosionprime) + * [events.entityUnleash()](#eventsentityunleash) + * [events.entityShootBow()](#eventsentityshootbow) + * [events.projectileLaunch()](#eventsprojectilelaunch) + * [events.itemSpawn()](#eventsitemspawn) + * [events.sheepDyeWool()](#eventssheepdyewool) + * [events.entityTeleport()](#eventsentityteleport) + * [events.block()](#eventsblock) + * [events.blockFade()](#eventsblockfade) + * [events.blockDamage()](#eventsblockdamage) + * [events.blockPiston()](#eventsblockpiston) + * [events.blockPistonExtend()](#eventsblockpistonextend) + * [events.blockExp()](#eventsblockexp) + * [events.blockGrow()](#eventsblockgrow) + * [events.blockPistonRetract()](#eventsblockpistonretract) + * [events.blockDispense()](#eventsblockdispense) + * [events.blockBreak()](#eventsblockbreak) + * [events.painting()](#eventspainting) + * [events.paintingPlace()](#eventspaintingplace) + * [events.weather()](#eventsweather) + * [events.lightningStrike()](#eventslightningstrike) + * [events.vehicle()](#eventsvehicle) + * [events.vehicleEnter()](#eventsvehicleenter) + * [events.vehicleMove()](#eventsvehiclemove) + * [events.vehicleCollision()](#eventsvehiclecollision) + * [events.vehicleCreate()](#eventsvehiclecreate) + * [events.asyncPlayerPreLogin()](#eventsasyncplayerprelogin) + * [events.playerUnleashEntity()](#eventsplayerunleashentity) + * [events.playerPreLogin()](#eventsplayerprelogin) + * [events.player()](#eventsplayer) + * [events.server()](#eventsserver) + * [events.inventoryPickupItem()](#eventsinventorypickupitem) + * [events.inventoryMoveItem()](#eventsinventorymoveitem) + * [events.furnaceBurn()](#eventsfurnaceburn) + * [events.inventory()](#eventsinventory) + * [events.brew()](#eventsbrew) + * [events.furnaceExtract()](#eventsfurnaceextract) + * [events.furnaceSmelt()](#eventsfurnacesmelt) + * [events.inventoryInteract()](#eventsinventoryinteract) + * [events.inventoryClose()](#eventsinventoryclose) + * [events.inventoryDrag()](#eventsinventorydrag) + * [events.inventoryClick()](#eventsinventoryclick) + * [events.inventoryCreative()](#eventsinventorycreative) + * [events.hangingPlace()](#eventshangingplace) + * [events.hangingBreak()](#eventshangingbreak) + * [events.worldSave()](#eventsworldsave) + * [events.structureGrow()](#eventsstructuregrow) + * [events.entityDamage()](#eventsentitydamage) + * [events.entityTargetLivingEntity()](#eventsentitytargetlivingentity) + * [events.playerDeath()](#eventsplayerdeath) + * [events.entityDamageByBlock()](#eventsentitydamagebyblock) + * [events.entityDamageByEntity()](#eventsentitydamagebyentity) + * [events.entityPortal()](#eventsentityportal) + * [events.entityPortalExit()](#eventsentityportalexit) + * [events.signChange()](#eventssignchange) + * [events.leavesDecay()](#eventsleavesdecay) + * [events.blockRedstone()](#eventsblockredstone) + * [events.blockCanBuild()](#eventsblockcanbuild) + * [events.blockBurn()](#eventsblockburn) + * [events.blockPhysics()](#eventsblockphysics) + * [events.blockIgnite()](#eventsblockignite) + * [events.notePlay()](#eventsnoteplay) + * [events.blockPlace()](#eventsblockplace) + * [events.blockFromTo()](#eventsblockfromto) + * [events.blockForm()](#eventsblockform) + * [events.blockSpread()](#eventsblockspread) + * [events.enchantItem()](#eventsenchantitem) + * [events.prepareItemEnchant()](#eventsprepareitemenchant) + * [events.paintingBreak()](#eventspaintingbreak) + * [events.paintingBreakByEntity()](#eventspaintingbreakbyentity) + * [events.weatherChange()](#eventsweatherchange) + * [events.thunderChange()](#eventsthunderchange) + * [events.vehicleEntityCollision()](#eventsvehicleentitycollision) + * [events.vehicleBlockCollision()](#eventsvehicleblockcollision) + * [events.vehicleExit()](#eventsvehicleexit) + * [events.vehicleUpdate()](#eventsvehicleupdate) + * [events.vehicleDamage()](#eventsvehicledamage) + * [events.vehicleDestroy()](#eventsvehicledestroy) + * [events.playerExpChange()](#eventsplayerexpchange) + * [events.playerRespawn()](#eventsplayerrespawn) + * [events.playerCommandPreprocess()](#eventsplayercommandpreprocess) + * [events.playerPickupItem()](#eventsplayerpickupitem) + * [events.playerInventory()](#eventsplayerinventory) + * [events.playerFish()](#eventsplayerfish) + * [events.playerBedEnter()](#eventsplayerbedenter) + * [events.playerLogin()](#eventsplayerlogin) + * [events.playerDropItem()](#eventsplayerdropitem) + * [events.playerLevelChange()](#eventsplayerlevelchange) + * [events.playerVelocity()](#eventsplayervelocity) + * [events.playerInteract()](#eventsplayerinteract) + * [events.playerQuit()](#eventsplayerquit) + * [events.playerChatTabComplete()](#eventsplayerchattabcomplete) + * [events.playerEggThrow()](#eventsplayereggthrow) + * [events.playerChat()](#eventsplayerchat) + * [events.playerAchievementAwarded()](#eventsplayerachievementawarded) + * [events.playerBedLeave()](#eventsplayerbedleave) + * [events.playerChannel()](#eventsplayerchannel) + * [events.playerStatisticIncrement()](#eventsplayerstatisticincrement) + * [events.playerToggleSprint()](#eventsplayertogglesprint) + * [events.playerInteractEntity()](#eventsplayerinteractentity) + * [events.playerEditBook()](#eventsplayereditbook) + * [events.playerKick()](#eventsplayerkick) + * [events.playerItemHeld()](#eventsplayeritemheld) + * [events.playerItemConsume()](#eventsplayeritemconsume) + * [events.playerGameModeChange()](#eventsplayergamemodechange) + * [events.playerItemBreak()](#eventsplayeritembreak) + * [events.playerToggleFlight()](#eventsplayertoggleflight) + * [events.playerAnimation()](#eventsplayeranimation) + * [events.asyncPlayerChat()](#eventsasyncplayerchat) + * [events.playerBucket()](#eventsplayerbucket) + * [events.playerRegisterChannel()](#eventsplayerregisterchannel) + * [events.playerMove()](#eventsplayermove) + * [events.playerTeleport()](#eventsplayerteleport) + * [events.playerBucketFill()](#eventsplayerbucketfill) + * [events.playerJoin()](#eventsplayerjoin) + * [events.playerShearEntity()](#eventsplayershearentity) + * [events.playerToggleSneak()](#eventsplayertogglesneak) + * [events.playerChangedWorld()](#eventsplayerchangedworld) + * [events.serverCommand()](#eventsservercommand) + * [events.remoteServerCommand()](#eventsremoteservercommand) + * [events.mapInitialize()](#eventsmapinitialize) + * [events.service()](#eventsservice) + * [events.plugin()](#eventsplugin) + * [events.serviceRegister()](#eventsserviceregister) + * [events.serverListPing()](#eventsserverlistping) + * [events.serviceUnregister()](#eventsserviceunregister) + * [events.prepareItemCraft()](#eventsprepareitemcraft) + * [events.inventoryOpen()](#eventsinventoryopen) + * [events.craftItem()](#eventscraftitem) + * [events.hangingBreakByEntity()](#eventshangingbreakbyentity) + * [events.blockMultiPlace()](#eventsblockmultiplace) + * [events.entityBlockForm()](#eventsentityblockform) + * [events.playerBucketEmpty()](#eventsplayerbucketempty) + * [events.playerPortal()](#eventsplayerportal) + * [events.playerUnregisterChannel()](#eventsplayerunregisterchannel) + * [events.pluginDisable()](#eventsplugindisable) + * [events.pluginEnable()](#eventspluginenable) * [Blocks Module](#blocks-module) * [Examples](#examples) * [Fireworks Module](#fireworks-module) @@ -963,699 +963,699 @@ The crucial difference is that the events module will have functions for each of the built-in events so tab-completion will help beginning programmers to explore the events at the server console window. -### events.hanging(callback) +### events.hanging() #### Parameters * callback - A function which is called whenever the hanging.HangingEvent event is fired see events.on() for more information -### events.world(callback) +### events.world() #### Parameters * callback - A function which is called whenever the world.WorldEvent event is fired see events.on() for more information -### events.chunk(callback) +### events.chunk() #### Parameters * callback - A function which is called whenever the world.ChunkEvent event is fired see events.on() for more information -### events.worldUnload(callback) +### events.worldUnload() #### Parameters * callback - A function which is called whenever the world.WorldUnloadEvent event is fired see events.on() for more information -### events.worldLoad(callback) +### events.worldLoad() #### Parameters * callback - A function which is called whenever the world.WorldLoadEvent event is fired see events.on() for more information -### events.chunkLoad(callback) +### events.chunkLoad() #### Parameters * callback - A function which is called whenever the world.ChunkLoadEvent event is fired see events.on() for more information -### events.chunkPopulate(callback) +### events.chunkPopulate() #### Parameters * callback - A function which is called whenever the world.ChunkPopulateEvent event is fired see events.on() for more information -### events.portalCreate(callback) +### events.portalCreate() #### Parameters * callback - A function which is called whenever the world.PortalCreateEvent event is fired see events.on() for more information -### events.spawnChange(callback) +### events.spawnChange() #### Parameters * callback - A function which is called whenever the world.SpawnChangeEvent event is fired see events.on() for more information -### events.chunkUnload(callback) +### events.chunkUnload() #### Parameters * callback - A function which is called whenever the world.ChunkUnloadEvent event is fired see events.on() for more information -### events.worldInit(callback) +### events.worldInit() #### Parameters * callback - A function which is called whenever the world.WorldInitEvent event is fired see events.on() for more information -### events.entity(callback) +### events.entity() #### Parameters * callback - A function which is called whenever the entity.EntityEvent event is fired see events.on() for more information -### events.horseJump(callback) +### events.horseJump() #### Parameters * callback - A function which is called whenever the entity.HorseJumpEvent event is fired see events.on() for more information -### events.entityCombust(callback) +### events.entityCombust() #### Parameters * callback - A function which is called whenever the entity.EntityCombustEvent event is fired see events.on() for more information -### events.entityRegainHealth(callback) +### events.entityRegainHealth() #### Parameters * callback - A function which is called whenever the entity.EntityRegainHealthEvent event is fired see events.on() for more information -### events.entityCombustByBlock(callback) +### events.entityCombustByBlock() #### Parameters * callback - A function which is called whenever the entity.EntityCombustByBlockEvent event is fired see events.on() for more information -### events.entityCombustByEntity(callback) +### events.entityCombustByEntity() #### Parameters * callback - A function which is called whenever the entity.EntityCombustByEntityEvent event is fired see events.on() for more information -### events.playerLeashEntity(callback) +### events.playerLeashEntity() #### Parameters * callback - A function which is called whenever the entity.PlayerLeashEntityEvent event is fired see events.on() for more information -### events.pigZap(callback) +### events.pigZap() #### Parameters * callback - A function which is called whenever the entity.PigZapEvent event is fired see events.on() for more information -### events.itemDespawn(callback) +### events.itemDespawn() #### Parameters * callback - A function which is called whenever the entity.ItemDespawnEvent event is fired see events.on() for more information -### events.entityTarget(callback) +### events.entityTarget() #### Parameters * callback - A function which is called whenever the entity.EntityTargetEvent event is fired see events.on() for more information -### events.slimeSplit(callback) +### events.slimeSplit() #### Parameters * callback - A function which is called whenever the entity.SlimeSplitEvent event is fired see events.on() for more information -### events.entityChangeBlock(callback) +### events.entityChangeBlock() #### Parameters * callback - A function which is called whenever the entity.EntityChangeBlockEvent event is fired see events.on() for more information -### events.entityPortalEnter(callback) +### events.entityPortalEnter() #### Parameters * callback - A function which is called whenever the entity.EntityPortalEnterEvent event is fired see events.on() for more information -### events.creeperPower(callback) +### events.creeperPower() #### Parameters * callback - A function which is called whenever the entity.CreeperPowerEvent event is fired see events.on() for more information -### events.entityDeath(callback) +### events.entityDeath() #### Parameters * callback - A function which is called whenever the entity.EntityDeathEvent event is fired see events.on() for more information -### events.projectileHit(callback) +### events.projectileHit() #### Parameters * callback - A function which is called whenever the entity.ProjectileHitEvent event is fired see events.on() for more information -### events.entityTame(callback) +### events.entityTame() #### Parameters * callback - A function which is called whenever the entity.EntityTameEvent event is fired see events.on() for more information -### events.potionSplash(callback) +### events.potionSplash() #### Parameters * callback - A function which is called whenever the entity.PotionSplashEvent event is fired see events.on() for more information -### events.expBottle(callback) +### events.expBottle() #### Parameters * callback - A function which is called whenever the entity.ExpBottleEvent event is fired see events.on() for more information -### events.entityExplode(callback) +### events.entityExplode() #### Parameters * callback - A function which is called whenever the entity.EntityExplodeEvent event is fired see events.on() for more information -### events.creatureSpawn(callback) +### events.creatureSpawn() #### Parameters * callback - A function which is called whenever the entity.CreatureSpawnEvent event is fired see events.on() for more information -### events.foodLevelChange(callback) +### events.foodLevelChange() #### Parameters * callback - A function which is called whenever the entity.FoodLevelChangeEvent event is fired see events.on() for more information -### events.entityInteract(callback) +### events.entityInteract() #### Parameters * callback - A function which is called whenever the entity.EntityInteractEvent event is fired see events.on() for more information -### events.entityBreakDoor(callback) +### events.entityBreakDoor() #### Parameters * callback - A function which is called whenever the entity.EntityBreakDoorEvent event is fired see events.on() for more information -### events.entityCreatePortal(callback) +### events.entityCreatePortal() #### Parameters * callback - A function which is called whenever the entity.EntityCreatePortalEvent event is fired see events.on() for more information -### events.sheepRegrowWool(callback) +### events.sheepRegrowWool() #### Parameters * callback - A function which is called whenever the entity.SheepRegrowWoolEvent event is fired see events.on() for more information -### events.explosionPrime(callback) +### events.explosionPrime() #### Parameters * callback - A function which is called whenever the entity.ExplosionPrimeEvent event is fired see events.on() for more information -### events.entityUnleash(callback) +### events.entityUnleash() #### Parameters * callback - A function which is called whenever the entity.EntityUnleashEvent event is fired see events.on() for more information -### events.entityShootBow(callback) +### events.entityShootBow() #### Parameters * callback - A function which is called whenever the entity.EntityShootBowEvent event is fired see events.on() for more information -### events.projectileLaunch(callback) +### events.projectileLaunch() #### Parameters * callback - A function which is called whenever the entity.ProjectileLaunchEvent event is fired see events.on() for more information -### events.itemSpawn(callback) +### events.itemSpawn() #### Parameters * callback - A function which is called whenever the entity.ItemSpawnEvent event is fired see events.on() for more information -### events.sheepDyeWool(callback) +### events.sheepDyeWool() #### Parameters * callback - A function which is called whenever the entity.SheepDyeWoolEvent event is fired see events.on() for more information -### events.entityTeleport(callback) +### events.entityTeleport() #### Parameters * callback - A function which is called whenever the entity.EntityTeleportEvent event is fired see events.on() for more information -### events.block(callback) +### events.block() #### Parameters * callback - A function which is called whenever the block.BlockEvent event is fired see events.on() for more information -### events.blockFade(callback) +### events.blockFade() #### Parameters * callback - A function which is called whenever the block.BlockFadeEvent event is fired see events.on() for more information -### events.blockDamage(callback) +### events.blockDamage() #### Parameters * callback - A function which is called whenever the block.BlockDamageEvent event is fired see events.on() for more information -### events.blockPiston(callback) +### events.blockPiston() #### Parameters * callback - A function which is called whenever the block.BlockPistonEvent event is fired see events.on() for more information -### events.blockPistonExtend(callback) +### events.blockPistonExtend() #### Parameters * callback - A function which is called whenever the block.BlockPistonExtendEvent event is fired see events.on() for more information -### events.blockExp(callback) +### events.blockExp() #### Parameters * callback - A function which is called whenever the block.BlockExpEvent event is fired see events.on() for more information -### events.blockGrow(callback) +### events.blockGrow() #### Parameters * callback - A function which is called whenever the block.BlockGrowEvent event is fired see events.on() for more information -### events.blockPistonRetract(callback) +### events.blockPistonRetract() #### Parameters * callback - A function which is called whenever the block.BlockPistonRetractEvent event is fired see events.on() for more information -### events.blockDispense(callback) +### events.blockDispense() #### Parameters * callback - A function which is called whenever the block.BlockDispenseEvent event is fired see events.on() for more information -### events.blockBreak(callback) +### events.blockBreak() #### Parameters * callback - A function which is called whenever the block.BlockBreakEvent event is fired see events.on() for more information -### events.painting(callback) +### events.painting() #### Parameters * callback - A function which is called whenever the painting.PaintingEvent event is fired see events.on() for more information -### events.paintingPlace(callback) +### events.paintingPlace() #### Parameters * callback - A function which is called whenever the painting.PaintingPlaceEvent event is fired see events.on() for more information -### events.weather(callback) +### events.weather() #### Parameters * callback - A function which is called whenever the weather.WeatherEvent event is fired see events.on() for more information -### events.lightningStrike(callback) +### events.lightningStrike() #### Parameters * callback - A function which is called whenever the weather.LightningStrikeEvent event is fired see events.on() for more information -### events.vehicle(callback) +### events.vehicle() #### Parameters * callback - A function which is called whenever the vehicle.VehicleEvent event is fired see events.on() for more information -### events.vehicleEnter(callback) +### events.vehicleEnter() #### Parameters * callback - A function which is called whenever the vehicle.VehicleEnterEvent event is fired see events.on() for more information -### events.vehicleMove(callback) +### events.vehicleMove() #### Parameters * callback - A function which is called whenever the vehicle.VehicleMoveEvent event is fired see events.on() for more information -### events.vehicleCollision(callback) +### events.vehicleCollision() #### Parameters * callback - A function which is called whenever the vehicle.VehicleCollisionEvent event is fired see events.on() for more information -### events.vehicleCreate(callback) +### events.vehicleCreate() #### Parameters * callback - A function which is called whenever the vehicle.VehicleCreateEvent event is fired see events.on() for more information -### events.asyncPlayerPreLogin(callback) +### events.asyncPlayerPreLogin() #### Parameters * callback - A function which is called whenever the player.AsyncPlayerPreLoginEvent event is fired see events.on() for more information -### events.playerUnleashEntity(callback) +### events.playerUnleashEntity() #### Parameters * callback - A function which is called whenever the player.PlayerUnleashEntityEvent event is fired see events.on() for more information -### events.playerPreLogin(callback) +### events.playerPreLogin() #### Parameters * callback - A function which is called whenever the player.PlayerPreLoginEvent event is fired see events.on() for more information -### events.player(callback) +### events.player() #### Parameters * callback - A function which is called whenever the player.PlayerEvent event is fired see events.on() for more information -### events.server(callback) +### events.server() #### Parameters * callback - A function which is called whenever the server.ServerEvent event is fired see events.on() for more information -### events.inventoryPickupItem(callback) +### events.inventoryPickupItem() #### Parameters * callback - A function which is called whenever the inventory.InventoryPickupItemEvent event is fired see events.on() for more information -### events.inventoryMoveItem(callback) +### events.inventoryMoveItem() #### Parameters * callback - A function which is called whenever the inventory.InventoryMoveItemEvent event is fired see events.on() for more information -### events.furnaceBurn(callback) +### events.furnaceBurn() #### Parameters * callback - A function which is called whenever the inventory.FurnaceBurnEvent event is fired see events.on() for more information -### events.inventory(callback) +### events.inventory() #### Parameters * callback - A function which is called whenever the inventory.InventoryEvent event is fired see events.on() for more information -### events.brew(callback) +### events.brew() #### Parameters * callback - A function which is called whenever the inventory.BrewEvent event is fired see events.on() for more information -### events.furnaceExtract(callback) +### events.furnaceExtract() #### Parameters * callback - A function which is called whenever the inventory.FurnaceExtractEvent event is fired see events.on() for more information -### events.furnaceSmelt(callback) +### events.furnaceSmelt() #### Parameters * callback - A function which is called whenever the inventory.FurnaceSmeltEvent event is fired see events.on() for more information -### events.inventoryInteract(callback) +### events.inventoryInteract() #### Parameters * callback - A function which is called whenever the inventory.InventoryInteractEvent event is fired see events.on() for more information -### events.inventoryClose(callback) +### events.inventoryClose() #### Parameters * callback - A function which is called whenever the inventory.InventoryCloseEvent event is fired see events.on() for more information -### events.inventoryDrag(callback) +### events.inventoryDrag() #### Parameters * callback - A function which is called whenever the inventory.InventoryDragEvent event is fired see events.on() for more information -### events.inventoryClick(callback) +### events.inventoryClick() #### Parameters * callback - A function which is called whenever the inventory.InventoryClickEvent event is fired see events.on() for more information -### events.inventoryCreative(callback) +### events.inventoryCreative() #### Parameters * callback - A function which is called whenever the inventory.InventoryCreativeEvent event is fired see events.on() for more information -### events.hangingPlace(callback) +### events.hangingPlace() #### Parameters * callback - A function which is called whenever the hanging.HangingPlaceEvent event is fired see events.on() for more information -### events.hangingBreak(callback) +### events.hangingBreak() #### Parameters * callback - A function which is called whenever the hanging.HangingBreakEvent event is fired see events.on() for more information -### events.worldSave(callback) +### events.worldSave() #### Parameters * callback - A function which is called whenever the world.WorldSaveEvent event is fired see events.on() for more information -### events.structureGrow(callback) +### events.structureGrow() #### Parameters * callback - A function which is called whenever the world.StructureGrowEvent event is fired see events.on() for more information -### events.entityDamage(callback) +### events.entityDamage() #### Parameters * callback - A function which is called whenever the entity.EntityDamageEvent event is fired see events.on() for more information -### events.entityTargetLivingEntity(callback) +### events.entityTargetLivingEntity() #### Parameters * callback - A function which is called whenever the entity.EntityTargetLivingEntityEvent event is fired see events.on() for more information -### events.playerDeath(callback) +### events.playerDeath() #### Parameters * callback - A function which is called whenever the entity.PlayerDeathEvent event is fired see events.on() for more information -### events.entityDamageByBlock(callback) +### events.entityDamageByBlock() #### Parameters * callback - A function which is called whenever the entity.EntityDamageByBlockEvent event is fired see events.on() for more information -### events.entityDamageByEntity(callback) +### events.entityDamageByEntity() #### Parameters * callback - A function which is called whenever the entity.EntityDamageByEntityEvent event is fired see events.on() for more information -### events.entityPortal(callback) +### events.entityPortal() #### Parameters * callback - A function which is called whenever the entity.EntityPortalEvent event is fired see events.on() for more information -### events.entityPortalExit(callback) +### events.entityPortalExit() #### Parameters * callback - A function which is called whenever the entity.EntityPortalExitEvent event is fired see events.on() for more information -### events.signChange(callback) +### events.signChange() #### Parameters * callback - A function which is called whenever the block.SignChangeEvent event is fired see events.on() for more information -### events.leavesDecay(callback) +### events.leavesDecay() #### Parameters * callback - A function which is called whenever the block.LeavesDecayEvent event is fired see events.on() for more information -### events.blockRedstone(callback) +### events.blockRedstone() #### Parameters * callback - A function which is called whenever the block.BlockRedstoneEvent event is fired see events.on() for more information -### events.blockCanBuild(callback) +### events.blockCanBuild() #### Parameters * callback - A function which is called whenever the block.BlockCanBuildEvent event is fired see events.on() for more information -### events.blockBurn(callback) +### events.blockBurn() #### Parameters * callback - A function which is called whenever the block.BlockBurnEvent event is fired see events.on() for more information -### events.blockPhysics(callback) +### events.blockPhysics() #### Parameters * callback - A function which is called whenever the block.BlockPhysicsEvent event is fired see events.on() for more information -### events.blockIgnite(callback) +### events.blockIgnite() #### Parameters * callback - A function which is called whenever the block.BlockIgniteEvent event is fired see events.on() for more information -### events.notePlay(callback) +### events.notePlay() #### Parameters * callback - A function which is called whenever the block.NotePlayEvent event is fired see events.on() for more information -### events.blockPlace(callback) +### events.blockPlace() #### Parameters * callback - A function which is called whenever the block.BlockPlaceEvent event is fired see events.on() for more information -### events.blockFromTo(callback) +### events.blockFromTo() #### Parameters * callback - A function which is called whenever the block.BlockFromToEvent event is fired see events.on() for more information -### events.blockForm(callback) +### events.blockForm() #### Parameters * callback - A function which is called whenever the block.BlockFormEvent event is fired see events.on() for more information -### events.blockSpread(callback) +### events.blockSpread() #### Parameters * callback - A function which is called whenever the block.BlockSpreadEvent event is fired see events.on() for more information -### events.enchantItem(callback) +### events.enchantItem() #### Parameters * callback - A function which is called whenever the enchantment.EnchantItemEvent event is fired see events.on() for more information -### events.prepareItemEnchant(callback) +### events.prepareItemEnchant() #### Parameters * callback - A function which is called whenever the enchantment.PrepareItemEnchantEvent event is fired see events.on() for more information -### events.paintingBreak(callback) +### events.paintingBreak() #### Parameters * callback - A function which is called whenever the painting.PaintingBreakEvent event is fired see events.on() for more information -### events.paintingBreakByEntity(callback) +### events.paintingBreakByEntity() #### Parameters * callback - A function which is called whenever the painting.PaintingBreakByEntityEvent event is fired see events.on() for more information -### events.weatherChange(callback) +### events.weatherChange() #### Parameters * callback - A function which is called whenever the weather.WeatherChangeEvent event is fired see events.on() for more information -### events.thunderChange(callback) +### events.thunderChange() #### Parameters * callback - A function which is called whenever the weather.ThunderChangeEvent event is fired see events.on() for more information -### events.vehicleEntityCollision(callback) +### events.vehicleEntityCollision() #### Parameters * callback - A function which is called whenever the vehicle.VehicleEntityCollisionEvent event is fired see events.on() for more information -### events.vehicleBlockCollision(callback) +### events.vehicleBlockCollision() #### Parameters * callback - A function which is called whenever the vehicle.VehicleBlockCollisionEvent event is fired see events.on() for more information -### events.vehicleExit(callback) +### events.vehicleExit() #### Parameters * callback - A function which is called whenever the vehicle.VehicleExitEvent event is fired see events.on() for more information -### events.vehicleUpdate(callback) +### events.vehicleUpdate() #### Parameters * callback - A function which is called whenever the vehicle.VehicleUpdateEvent event is fired see events.on() for more information -### events.vehicleDamage(callback) +### events.vehicleDamage() #### Parameters * callback - A function which is called whenever the vehicle.VehicleDamageEvent event is fired see events.on() for more information -### events.vehicleDestroy(callback) +### events.vehicleDestroy() #### Parameters * callback - A function which is called whenever the vehicle.VehicleDestroyEvent event is fired see events.on() for more information -### events.playerExpChange(callback) +### events.playerExpChange() #### Parameters * callback - A function which is called whenever the player.PlayerExpChangeEvent event is fired see events.on() for more information -### events.playerRespawn(callback) +### events.playerRespawn() #### Parameters * callback - A function which is called whenever the player.PlayerRespawnEvent event is fired see events.on() for more information -### events.playerCommandPreprocess(callback) +### events.playerCommandPreprocess() #### Parameters * callback - A function which is called whenever the player.PlayerCommandPreprocessEvent event is fired see events.on() for more information -### events.playerPickupItem(callback) +### events.playerPickupItem() #### Parameters * callback - A function which is called whenever the player.PlayerPickupItemEvent event is fired see events.on() for more information -### events.playerInventory(callback) +### events.playerInventory() #### Parameters * callback - A function which is called whenever the player.PlayerInventoryEvent event is fired see events.on() for more information -### events.playerFish(callback) +### events.playerFish() #### Parameters * callback - A function which is called whenever the player.PlayerFishEvent event is fired see events.on() for more information -### events.playerBedEnter(callback) +### events.playerBedEnter() #### Parameters * callback - A function which is called whenever the player.PlayerBedEnterEvent event is fired see events.on() for more information -### events.playerLogin(callback) +### events.playerLogin() #### Parameters * callback - A function which is called whenever the player.PlayerLoginEvent event is fired see events.on() for more information -### events.playerDropItem(callback) +### events.playerDropItem() #### Parameters * callback - A function which is called whenever the player.PlayerDropItemEvent event is fired see events.on() for more information -### events.playerLevelChange(callback) +### events.playerLevelChange() #### Parameters * callback - A function which is called whenever the player.PlayerLevelChangeEvent event is fired see events.on() for more information -### events.playerVelocity(callback) +### events.playerVelocity() #### Parameters * callback - A function which is called whenever the player.PlayerVelocityEvent event is fired see events.on() for more information -### events.playerInteract(callback) +### events.playerInteract() #### Parameters * callback - A function which is called whenever the player.PlayerInteractEvent event is fired see events.on() for more information -### events.playerQuit(callback) +### events.playerQuit() #### Parameters * callback - A function which is called whenever the player.PlayerQuitEvent event is fired see events.on() for more information -### events.playerChatTabComplete(callback) +### events.playerChatTabComplete() #### Parameters * callback - A function which is called whenever the player.PlayerChatTabCompleteEvent event is fired see events.on() for more information -### events.playerEggThrow(callback) +### events.playerEggThrow() #### Parameters * callback - A function which is called whenever the player.PlayerEggThrowEvent event is fired see events.on() for more information -### events.playerChat(callback) +### events.playerChat() #### Parameters * callback - A function which is called whenever the player.PlayerChatEvent event is fired see events.on() for more information -### events.playerAchievementAwarded(callback) +### events.playerAchievementAwarded() #### Parameters * callback - A function which is called whenever the player.PlayerAchievementAwardedEvent event is fired see events.on() for more information -### events.playerBedLeave(callback) +### events.playerBedLeave() #### Parameters * callback - A function which is called whenever the player.PlayerBedLeaveEvent event is fired see events.on() for more information -### events.playerChannel(callback) +### events.playerChannel() #### Parameters * callback - A function which is called whenever the player.PlayerChannelEvent event is fired see events.on() for more information -### events.playerStatisticIncrement(callback) +### events.playerStatisticIncrement() #### Parameters * callback - A function which is called whenever the player.PlayerStatisticIncrementEvent event is fired see events.on() for more information -### events.playerToggleSprint(callback) +### events.playerToggleSprint() #### Parameters * callback - A function which is called whenever the player.PlayerToggleSprintEvent event is fired see events.on() for more information -### events.playerInteractEntity(callback) +### events.playerInteractEntity() #### Parameters * callback - A function which is called whenever the player.PlayerInteractEntityEvent event is fired see events.on() for more information -### events.playerEditBook(callback) +### events.playerEditBook() #### Parameters * callback - A function which is called whenever the player.PlayerEditBookEvent event is fired see events.on() for more information -### events.playerKick(callback) +### events.playerKick() #### Parameters * callback - A function which is called whenever the player.PlayerKickEvent event is fired see events.on() for more information -### events.playerItemHeld(callback) +### events.playerItemHeld() #### Parameters * callback - A function which is called whenever the player.PlayerItemHeldEvent event is fired see events.on() for more information -### events.playerItemConsume(callback) +### events.playerItemConsume() #### Parameters * callback - A function which is called whenever the player.PlayerItemConsumeEvent event is fired see events.on() for more information -### events.playerGameModeChange(callback) +### events.playerGameModeChange() #### Parameters * callback - A function which is called whenever the player.PlayerGameModeChangeEvent event is fired see events.on() for more information -### events.playerItemBreak(callback) +### events.playerItemBreak() #### Parameters * callback - A function which is called whenever the player.PlayerItemBreakEvent event is fired see events.on() for more information -### events.playerToggleFlight(callback) +### events.playerToggleFlight() #### Parameters * callback - A function which is called whenever the player.PlayerToggleFlightEvent event is fired see events.on() for more information -### events.playerAnimation(callback) +### events.playerAnimation() #### Parameters * callback - A function which is called whenever the player.PlayerAnimationEvent event is fired see events.on() for more information -### events.asyncPlayerChat(callback) +### events.asyncPlayerChat() #### Parameters * callback - A function which is called whenever the player.AsyncPlayerChatEvent event is fired see events.on() for more information -### events.playerBucket(callback) +### events.playerBucket() #### Parameters * callback - A function which is called whenever the player.PlayerBucketEvent event is fired see events.on() for more information -### events.playerRegisterChannel(callback) +### events.playerRegisterChannel() #### Parameters * callback - A function which is called whenever the player.PlayerRegisterChannelEvent event is fired see events.on() for more information -### events.playerMove(callback) +### events.playerMove() #### Parameters * callback - A function which is called whenever the player.PlayerMoveEvent event is fired see events.on() for more information -### events.playerTeleport(callback) +### events.playerTeleport() #### Parameters * callback - A function which is called whenever the player.PlayerTeleportEvent event is fired see events.on() for more information -### events.playerBucketFill(callback) +### events.playerBucketFill() #### Parameters * callback - A function which is called whenever the player.PlayerBucketFillEvent event is fired see events.on() for more information -### events.playerJoin(callback) +### events.playerJoin() #### Parameters * callback - A function which is called whenever the player.PlayerJoinEvent event is fired see events.on() for more information -### events.playerShearEntity(callback) +### events.playerShearEntity() #### Parameters * callback - A function which is called whenever the player.PlayerShearEntityEvent event is fired see events.on() for more information -### events.playerToggleSneak(callback) +### events.playerToggleSneak() #### Parameters * callback - A function which is called whenever the player.PlayerToggleSneakEvent event is fired see events.on() for more information -### events.playerChangedWorld(callback) +### events.playerChangedWorld() #### Parameters * callback - A function which is called whenever the player.PlayerChangedWorldEvent event is fired see events.on() for more information -### events.serverCommand(callback) +### events.serverCommand() #### Parameters * callback - A function which is called whenever the server.ServerCommandEvent event is fired see events.on() for more information -### events.remoteServerCommand(callback) +### events.remoteServerCommand() #### Parameters * callback - A function which is called whenever the server.RemoteServerCommandEvent event is fired see events.on() for more information -### events.mapInitialize(callback) +### events.mapInitialize() #### Parameters * callback - A function which is called whenever the server.MapInitializeEvent event is fired see events.on() for more information -### events.service(callback) +### events.service() #### Parameters * callback - A function which is called whenever the server.ServiceEvent event is fired see events.on() for more information -### events.plugin(callback) +### events.plugin() #### Parameters * callback - A function which is called whenever the server.PluginEvent event is fired see events.on() for more information -### events.serviceRegister(callback) +### events.serviceRegister() #### Parameters * callback - A function which is called whenever the server.ServiceRegisterEvent event is fired see events.on() for more information -### events.serverListPing(callback) +### events.serverListPing() #### Parameters * callback - A function which is called whenever the server.ServerListPingEvent event is fired see events.on() for more information -### events.serviceUnregister(callback) +### events.serviceUnregister() #### Parameters * callback - A function which is called whenever the server.ServiceUnregisterEvent event is fired see events.on() for more information -### events.prepareItemCraft(callback) +### events.prepareItemCraft() #### Parameters * callback - A function which is called whenever the inventory.PrepareItemCraftEvent event is fired see events.on() for more information -### events.inventoryOpen(callback) +### events.inventoryOpen() #### Parameters * callback - A function which is called whenever the inventory.InventoryOpenEvent event is fired see events.on() for more information -### events.craftItem(callback) +### events.craftItem() #### Parameters * callback - A function which is called whenever the inventory.CraftItemEvent event is fired see events.on() for more information -### events.hangingBreakByEntity(callback) +### events.hangingBreakByEntity() #### Parameters * callback - A function which is called whenever the hanging.HangingBreakByEntityEvent event is fired see events.on() for more information -### events.blockMultiPlace(callback) +### events.blockMultiPlace() #### Parameters * callback - A function which is called whenever the block.BlockMultiPlaceEvent event is fired see events.on() for more information -### events.entityBlockForm(callback) +### events.entityBlockForm() #### Parameters * callback - A function which is called whenever the block.EntityBlockFormEvent event is fired see events.on() for more information -### events.playerBucketEmpty(callback) +### events.playerBucketEmpty() #### Parameters * callback - A function which is called whenever the player.PlayerBucketEmptyEvent event is fired see events.on() for more information -### events.playerPortal(callback) +### events.playerPortal() #### Parameters * callback - A function which is called whenever the player.PlayerPortalEvent event is fired see events.on() for more information -### events.playerUnregisterChannel(callback) +### events.playerUnregisterChannel() #### Parameters * callback - A function which is called whenever the player.PlayerUnregisterChannelEvent event is fired see events.on() for more information -### events.pluginDisable(callback) +### events.pluginDisable() #### Parameters * callback - A function which is called whenever the server.PluginDisableEvent event is fired see events.on() for more information -### events.pluginEnable(callback) +### events.pluginEnable() #### Parameters * callback - A function which is called whenever the server.PluginEnableEvent event is fired see events.on() for more information diff --git a/src/generateEventsHelper.js b/src/generateEventsHelper.js index 302860a..433d589 100644 --- a/src/generateEventsHelper.js +++ b/src/generateEventsHelper.js @@ -41,7 +41,7 @@ while ( ( entry = zis.nextEntry) != null) { var comment = [ '/*********************', - '### events.' + fname + '(callback)', + '### events.' + fname + '()', '#### Parameters ', ' * callback - A function which is called whenever the ' + shortName + ' event is fired', 'see events.on() for more information', From d8cfcb4572e8c444bc99a95115228128995eb55d Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 26 Apr 2014 17:14:32 +0100 Subject: [PATCH 171/456] fixing toc issue --- docs/API-Reference.md | 1044 +++++++++++++++++++++++++++++------ src/generateEventsHelper.js | 9 +- 2 files changed, 877 insertions(+), 176 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 43dc93e..25301b4 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -964,701 +964,1397 @@ of the built-in events so tab-completion will help beginning programmers to explore the events at the server console window. ### events.hanging() + #### Parameters + * callback - A function which is called whenever the hanging.HangingEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.world() + #### Parameters + * callback - A function which is called whenever the world.WorldEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.chunk() + #### Parameters + * callback - A function which is called whenever the world.ChunkEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.worldUnload() + #### Parameters + * callback - A function which is called whenever the world.WorldUnloadEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.worldLoad() + #### Parameters + * callback - A function which is called whenever the world.WorldLoadEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.chunkLoad() + #### Parameters + * callback - A function which is called whenever the world.ChunkLoadEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.chunkPopulate() + #### Parameters + * callback - A function which is called whenever the world.ChunkPopulateEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.portalCreate() + #### Parameters + * callback - A function which is called whenever the world.PortalCreateEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.spawnChange() + #### Parameters + * callback - A function which is called whenever the world.SpawnChangeEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.chunkUnload() + #### Parameters + * callback - A function which is called whenever the world.ChunkUnloadEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.worldInit() + #### Parameters + * callback - A function which is called whenever the world.WorldInitEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.entity() + #### Parameters + * callback - A function which is called whenever the entity.EntityEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.horseJump() + #### Parameters + * callback - A function which is called whenever the entity.HorseJumpEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.entityCombust() + #### Parameters + * callback - A function which is called whenever the entity.EntityCombustEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.entityRegainHealth() + #### Parameters + * callback - A function which is called whenever the entity.EntityRegainHealthEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.entityCombustByBlock() + #### Parameters + * callback - A function which is called whenever the entity.EntityCombustByBlockEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.entityCombustByEntity() + #### Parameters + * callback - A function which is called whenever the entity.EntityCombustByEntityEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerLeashEntity() + #### Parameters + * callback - A function which is called whenever the entity.PlayerLeashEntityEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.pigZap() + #### Parameters + * callback - A function which is called whenever the entity.PigZapEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.itemDespawn() + #### Parameters + * callback - A function which is called whenever the entity.ItemDespawnEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.entityTarget() + #### Parameters + * callback - A function which is called whenever the entity.EntityTargetEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.slimeSplit() + #### Parameters + * callback - A function which is called whenever the entity.SlimeSplitEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.entityChangeBlock() + #### Parameters + * callback - A function which is called whenever the entity.EntityChangeBlockEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.entityPortalEnter() + #### Parameters + * callback - A function which is called whenever the entity.EntityPortalEnterEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.creeperPower() + #### Parameters + * callback - A function which is called whenever the entity.CreeperPowerEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.entityDeath() + #### Parameters + * callback - A function which is called whenever the entity.EntityDeathEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.projectileHit() + #### Parameters + * callback - A function which is called whenever the entity.ProjectileHitEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.entityTame() + #### Parameters + * callback - A function which is called whenever the entity.EntityTameEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.potionSplash() + #### Parameters + * callback - A function which is called whenever the entity.PotionSplashEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.expBottle() + #### Parameters + * callback - A function which is called whenever the entity.ExpBottleEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.entityExplode() + #### Parameters + * callback - A function which is called whenever the entity.EntityExplodeEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.creatureSpawn() + #### Parameters + * callback - A function which is called whenever the entity.CreatureSpawnEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.foodLevelChange() + #### Parameters + * callback - A function which is called whenever the entity.FoodLevelChangeEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.entityInteract() + #### Parameters + * callback - A function which is called whenever the entity.EntityInteractEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.entityBreakDoor() + #### Parameters + * callback - A function which is called whenever the entity.EntityBreakDoorEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.entityCreatePortal() + #### Parameters + * callback - A function which is called whenever the entity.EntityCreatePortalEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.sheepRegrowWool() + #### Parameters + * callback - A function which is called whenever the entity.SheepRegrowWoolEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.explosionPrime() + #### Parameters + * callback - A function which is called whenever the entity.ExplosionPrimeEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.entityUnleash() + #### Parameters + * callback - A function which is called whenever the entity.EntityUnleashEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.entityShootBow() + #### Parameters + * callback - A function which is called whenever the entity.EntityShootBowEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.projectileLaunch() + #### Parameters + * callback - A function which is called whenever the entity.ProjectileLaunchEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.itemSpawn() + #### Parameters + * callback - A function which is called whenever the entity.ItemSpawnEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.sheepDyeWool() + #### Parameters + * callback - A function which is called whenever the entity.SheepDyeWoolEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.entityTeleport() + #### Parameters + * callback - A function which is called whenever the entity.EntityTeleportEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.block() + #### Parameters + * callback - A function which is called whenever the block.BlockEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.blockFade() + #### Parameters + * callback - A function which is called whenever the block.BlockFadeEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.blockDamage() + #### Parameters + * callback - A function which is called whenever the block.BlockDamageEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.blockPiston() + #### Parameters + * callback - A function which is called whenever the block.BlockPistonEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.blockPistonExtend() + #### Parameters + * callback - A function which is called whenever the block.BlockPistonExtendEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.blockExp() + #### Parameters + * callback - A function which is called whenever the block.BlockExpEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.blockGrow() + #### Parameters + * callback - A function which is called whenever the block.BlockGrowEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.blockPistonRetract() + #### Parameters + * callback - A function which is called whenever the block.BlockPistonRetractEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.blockDispense() + #### Parameters + * callback - A function which is called whenever the block.BlockDispenseEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.blockBreak() + #### Parameters + * callback - A function which is called whenever the block.BlockBreakEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.painting() + #### Parameters + * callback - A function which is called whenever the painting.PaintingEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.paintingPlace() + #### Parameters + * callback - A function which is called whenever the painting.PaintingPlaceEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.weather() + #### Parameters + * callback - A function which is called whenever the weather.WeatherEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.lightningStrike() + #### Parameters + * callback - A function which is called whenever the weather.LightningStrikeEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.vehicle() + #### Parameters + * callback - A function which is called whenever the vehicle.VehicleEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.vehicleEnter() + #### Parameters + * callback - A function which is called whenever the vehicle.VehicleEnterEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.vehicleMove() + #### Parameters + * callback - A function which is called whenever the vehicle.VehicleMoveEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.vehicleCollision() + #### Parameters + * callback - A function which is called whenever the vehicle.VehicleCollisionEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.vehicleCreate() + #### Parameters + * callback - A function which is called whenever the vehicle.VehicleCreateEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.asyncPlayerPreLogin() + #### Parameters + * callback - A function which is called whenever the player.AsyncPlayerPreLoginEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerUnleashEntity() + #### Parameters + * callback - A function which is called whenever the player.PlayerUnleashEntityEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerPreLogin() + #### Parameters + * callback - A function which is called whenever the player.PlayerPreLoginEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.player() + #### Parameters + * callback - A function which is called whenever the player.PlayerEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.server() + #### Parameters + * callback - A function which is called whenever the server.ServerEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.inventoryPickupItem() + #### Parameters + * callback - A function which is called whenever the inventory.InventoryPickupItemEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.inventoryMoveItem() + #### Parameters + * callback - A function which is called whenever the inventory.InventoryMoveItemEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.furnaceBurn() + #### Parameters + * callback - A function which is called whenever the inventory.FurnaceBurnEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.inventory() + #### Parameters + * callback - A function which is called whenever the inventory.InventoryEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.brew() + #### Parameters + * callback - A function which is called whenever the inventory.BrewEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.furnaceExtract() + #### Parameters + * callback - A function which is called whenever the inventory.FurnaceExtractEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.furnaceSmelt() + #### Parameters + * callback - A function which is called whenever the inventory.FurnaceSmeltEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.inventoryInteract() + #### Parameters + * callback - A function which is called whenever the inventory.InventoryInteractEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.inventoryClose() + #### Parameters + * callback - A function which is called whenever the inventory.InventoryCloseEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.inventoryDrag() + #### Parameters + * callback - A function which is called whenever the inventory.InventoryDragEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.inventoryClick() + #### Parameters + * callback - A function which is called whenever the inventory.InventoryClickEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.inventoryCreative() + #### Parameters + * callback - A function which is called whenever the inventory.InventoryCreativeEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.hangingPlace() + #### Parameters + * callback - A function which is called whenever the hanging.HangingPlaceEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.hangingBreak() + #### Parameters + * callback - A function which is called whenever the hanging.HangingBreakEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.worldSave() + #### Parameters + * callback - A function which is called whenever the world.WorldSaveEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.structureGrow() + #### Parameters + * callback - A function which is called whenever the world.StructureGrowEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.entityDamage() + #### Parameters + * callback - A function which is called whenever the entity.EntityDamageEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.entityTargetLivingEntity() + #### Parameters + * callback - A function which is called whenever the entity.EntityTargetLivingEntityEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerDeath() + #### Parameters + * callback - A function which is called whenever the entity.PlayerDeathEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.entityDamageByBlock() + #### Parameters + * callback - A function which is called whenever the entity.EntityDamageByBlockEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.entityDamageByEntity() + #### Parameters + * callback - A function which is called whenever the entity.EntityDamageByEntityEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.entityPortal() + #### Parameters + * callback - A function which is called whenever the entity.EntityPortalEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.entityPortalExit() + #### Parameters + * callback - A function which is called whenever the entity.EntityPortalExitEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.signChange() + #### Parameters + * callback - A function which is called whenever the block.SignChangeEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.leavesDecay() + #### Parameters + * callback - A function which is called whenever the block.LeavesDecayEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.blockRedstone() + #### Parameters + * callback - A function which is called whenever the block.BlockRedstoneEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.blockCanBuild() + #### Parameters + * callback - A function which is called whenever the block.BlockCanBuildEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.blockBurn() + #### Parameters + * callback - A function which is called whenever the block.BlockBurnEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.blockPhysics() + #### Parameters + * callback - A function which is called whenever the block.BlockPhysicsEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.blockIgnite() + #### Parameters + * callback - A function which is called whenever the block.BlockIgniteEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.notePlay() + #### Parameters + * callback - A function which is called whenever the block.NotePlayEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.blockPlace() + #### Parameters + * callback - A function which is called whenever the block.BlockPlaceEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.blockFromTo() + #### Parameters + * callback - A function which is called whenever the block.BlockFromToEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.blockForm() + #### Parameters + * callback - A function which is called whenever the block.BlockFormEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.blockSpread() + #### Parameters + * callback - A function which is called whenever the block.BlockSpreadEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.enchantItem() + #### Parameters + * callback - A function which is called whenever the enchantment.EnchantItemEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.prepareItemEnchant() + #### Parameters + * callback - A function which is called whenever the enchantment.PrepareItemEnchantEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.paintingBreak() + #### Parameters + * callback - A function which is called whenever the painting.PaintingBreakEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.paintingBreakByEntity() + #### Parameters + * callback - A function which is called whenever the painting.PaintingBreakByEntityEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.weatherChange() + #### Parameters + * callback - A function which is called whenever the weather.WeatherChangeEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.thunderChange() + #### Parameters + * callback - A function which is called whenever the weather.ThunderChangeEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.vehicleEntityCollision() + #### Parameters + * callback - A function which is called whenever the vehicle.VehicleEntityCollisionEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.vehicleBlockCollision() + #### Parameters + * callback - A function which is called whenever the vehicle.VehicleBlockCollisionEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.vehicleExit() + #### Parameters + * callback - A function which is called whenever the vehicle.VehicleExitEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.vehicleUpdate() + #### Parameters + * callback - A function which is called whenever the vehicle.VehicleUpdateEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.vehicleDamage() + #### Parameters + * callback - A function which is called whenever the vehicle.VehicleDamageEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.vehicleDestroy() + #### Parameters + * callback - A function which is called whenever the vehicle.VehicleDestroyEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerExpChange() + #### Parameters + * callback - A function which is called whenever the player.PlayerExpChangeEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerRespawn() + #### Parameters + * callback - A function which is called whenever the player.PlayerRespawnEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerCommandPreprocess() + #### Parameters + * callback - A function which is called whenever the player.PlayerCommandPreprocessEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerPickupItem() + #### Parameters + * callback - A function which is called whenever the player.PlayerPickupItemEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerInventory() + #### Parameters + * callback - A function which is called whenever the player.PlayerInventoryEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerFish() + #### Parameters + * callback - A function which is called whenever the player.PlayerFishEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerBedEnter() + #### Parameters + * callback - A function which is called whenever the player.PlayerBedEnterEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerLogin() + #### Parameters + * callback - A function which is called whenever the player.PlayerLoginEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerDropItem() + #### Parameters + * callback - A function which is called whenever the player.PlayerDropItemEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerLevelChange() + #### Parameters + * callback - A function which is called whenever the player.PlayerLevelChangeEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerVelocity() + #### Parameters + * callback - A function which is called whenever the player.PlayerVelocityEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerInteract() + #### Parameters + * callback - A function which is called whenever the player.PlayerInteractEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerQuit() + #### Parameters + * callback - A function which is called whenever the player.PlayerQuitEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerChatTabComplete() + #### Parameters + * callback - A function which is called whenever the player.PlayerChatTabCompleteEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerEggThrow() + #### Parameters + * callback - A function which is called whenever the player.PlayerEggThrowEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerChat() + #### Parameters + * callback - A function which is called whenever the player.PlayerChatEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerAchievementAwarded() + #### Parameters + * callback - A function which is called whenever the player.PlayerAchievementAwardedEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerBedLeave() + #### Parameters + * callback - A function which is called whenever the player.PlayerBedLeaveEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerChannel() + #### Parameters + * callback - A function which is called whenever the player.PlayerChannelEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerStatisticIncrement() + #### Parameters + * callback - A function which is called whenever the player.PlayerStatisticIncrementEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerToggleSprint() + #### Parameters + * callback - A function which is called whenever the player.PlayerToggleSprintEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerInteractEntity() + #### Parameters + * callback - A function which is called whenever the player.PlayerInteractEntityEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerEditBook() + #### Parameters + * callback - A function which is called whenever the player.PlayerEditBookEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerKick() + #### Parameters + * callback - A function which is called whenever the player.PlayerKickEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerItemHeld() + #### Parameters + * callback - A function which is called whenever the player.PlayerItemHeldEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerItemConsume() + #### Parameters + * callback - A function which is called whenever the player.PlayerItemConsumeEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerGameModeChange() + #### Parameters + * callback - A function which is called whenever the player.PlayerGameModeChangeEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerItemBreak() + #### Parameters + * callback - A function which is called whenever the player.PlayerItemBreakEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerToggleFlight() + #### Parameters + * callback - A function which is called whenever the player.PlayerToggleFlightEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerAnimation() + #### Parameters + * callback - A function which is called whenever the player.PlayerAnimationEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.asyncPlayerChat() + #### Parameters + * callback - A function which is called whenever the player.AsyncPlayerChatEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerBucket() + #### Parameters + * callback - A function which is called whenever the player.PlayerBucketEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerRegisterChannel() + #### Parameters + * callback - A function which is called whenever the player.PlayerRegisterChannelEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerMove() + #### Parameters + * callback - A function which is called whenever the player.PlayerMoveEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerTeleport() + #### Parameters + * callback - A function which is called whenever the player.PlayerTeleportEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerBucketFill() + #### Parameters + * callback - A function which is called whenever the player.PlayerBucketFillEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerJoin() + #### Parameters + * callback - A function which is called whenever the player.PlayerJoinEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerShearEntity() + #### Parameters + * callback - A function which is called whenever the player.PlayerShearEntityEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerToggleSneak() + #### Parameters + * callback - A function which is called whenever the player.PlayerToggleSneakEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerChangedWorld() + #### Parameters + * callback - A function which is called whenever the player.PlayerChangedWorldEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.serverCommand() + #### Parameters + * callback - A function which is called whenever the server.ServerCommandEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.remoteServerCommand() + #### Parameters + * callback - A function which is called whenever the server.RemoteServerCommandEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.mapInitialize() + #### Parameters + * callback - A function which is called whenever the server.MapInitializeEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.service() + #### Parameters + * callback - A function which is called whenever the server.ServiceEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.plugin() + #### Parameters + * callback - A function which is called whenever the server.PluginEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.serviceRegister() + #### Parameters + * callback - A function which is called whenever the server.ServiceRegisterEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.serverListPing() + #### Parameters + * callback - A function which is called whenever the server.ServerListPingEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.serviceUnregister() + #### Parameters + * callback - A function which is called whenever the server.ServiceUnregisterEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.prepareItemCraft() + #### Parameters + * callback - A function which is called whenever the inventory.PrepareItemCraftEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.inventoryOpen() + #### Parameters + * callback - A function which is called whenever the inventory.InventoryOpenEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.craftItem() + #### Parameters + * callback - A function which is called whenever the inventory.CraftItemEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.hangingBreakByEntity() + #### Parameters + * callback - A function which is called whenever the hanging.HangingBreakByEntityEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.blockMultiPlace() + #### Parameters + * callback - A function which is called whenever the block.BlockMultiPlaceEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.entityBlockForm() + #### Parameters + * callback - A function which is called whenever the block.EntityBlockFormEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerBucketEmpty() + #### Parameters + * callback - A function which is called whenever the player.PlayerBucketEmptyEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerPortal() + #### Parameters + * callback - A function which is called whenever the player.PlayerPortalEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.playerUnregisterChannel() + #### Parameters + * callback - A function which is called whenever the player.PlayerUnregisterChannelEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.pluginDisable() + #### Parameters + * callback - A function which is called whenever the server.PluginDisableEvent event is fired -see events.on() for more information + +see events.on() for more information. + ### events.pluginEnable() + #### Parameters + * callback - A function which is called whenever the server.PluginEnableEvent event is fired -see events.on() for more information + +see events.on() for more information. + ## Blocks Module You hate having to lookup [Data Values][dv] when you use ScriptCraft's diff --git a/src/generateEventsHelper.js b/src/generateEventsHelper.js index 433d589..9bc6b9e 100644 --- a/src/generateEventsHelper.js +++ b/src/generateEventsHelper.js @@ -26,7 +26,8 @@ var content = [ 'The crucial difference is that the events module will have functions for each ', 'of the built-in events so tab-completion will help ', 'beginning programmers to explore the events at the server console window.', - '' + '', + '***/' ]; for (var i = 0; i< content.length; i++){ out.println(content[i]); @@ -42,9 +43,13 @@ while ( ( entry = zis.nextEntry) != null) { var comment = [ '/*********************', '### events.' + fname + '()', + '', '#### Parameters ', + '', ' * callback - A function which is called whenever the ' + shortName + ' event is fired', - 'see events.on() for more information', + '', + 'see events.on() for more information.', + '', '***/' ]; for (var i = 0; i < comment.length; i++){ From 2ba3335a16c43ff13d634361c23f2ac078768e35 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 26 Apr 2014 20:20:10 +0100 Subject: [PATCH 172/456] Omit abstract event classes from events-helper. Improved tab completion for jre8 --- build.properties | 4 +- build.xml | 1 + docs/API-Reference.md | 146 ++---------------- src/generateEventsHelper.js | 7 + src/main/js/lib/java-utils.js | 3 + src/main/js/lib/scriptcraft.js | 7 +- src/main/js/lib/tabcomplete.js | 10 +- src/main/js/modules/signs/menu.js | 2 +- src/main/js/plugins/alias/alias.js | 4 +- src/main/js/plugins/arrows.js | 2 +- src/main/js/plugins/chat/color.js | 2 +- src/main/js/plugins/classroom/classroom.js | 2 +- src/main/js/plugins/commando/commando.js | 4 +- src/main/js/plugins/drone/drone.js | 2 +- .../examples/example-7-hello-events.js | 9 +- .../js/plugins/minigames/SnowballFight.js | 3 +- src/main/js/plugins/minigames/cow-clicker.js | 6 +- 17 files changed, 55 insertions(+), 159 deletions(-) diff --git a/build.properties b/build.properties index eacfba2..254474c 100644 --- a/build.properties +++ b/build.properties @@ -1,2 +1,2 @@ -bukkit-version=1.7.2 -scriptcraft-version=2.0.6 +bukkit-version=1.7.9 +scriptcraft-version=2.0.8 diff --git a/build.xml b/build.xml index 50340c2..8a1fe2c 100644 --- a/build.xml +++ b/build.xml @@ -89,6 +89,7 @@ + diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 25301b4..0049729 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -48,9 +48,6 @@ Walter Higgins * [Using string substitutions](#using-string-substitutions) * [Events Helper Module](#events-helper-module) * [Usage](#usage) - * [events.hanging()](#eventshanging) - * [events.world()](#eventsworld) - * [events.chunk()](#eventschunk) * [events.worldUnload()](#eventsworldunload) * [events.worldLoad()](#eventsworldload) * [events.chunkLoad()](#eventschunkload) @@ -59,7 +56,6 @@ Walter Higgins * [events.spawnChange()](#eventsspawnchange) * [events.chunkUnload()](#eventschunkunload) * [events.worldInit()](#eventsworldinit) - * [events.entity()](#eventsentity) * [events.horseJump()](#eventshorsejump) * [events.entityCombust()](#eventsentitycombust) * [events.entityRegainHealth()](#eventsentityregainhealth) @@ -92,30 +88,22 @@ Walter Higgins * [events.itemSpawn()](#eventsitemspawn) * [events.sheepDyeWool()](#eventssheepdyewool) * [events.entityTeleport()](#eventsentityteleport) - * [events.block()](#eventsblock) * [events.blockFade()](#eventsblockfade) * [events.blockDamage()](#eventsblockdamage) - * [events.blockPiston()](#eventsblockpiston) * [events.blockPistonExtend()](#eventsblockpistonextend) * [events.blockExp()](#eventsblockexp) * [events.blockGrow()](#eventsblockgrow) * [events.blockPistonRetract()](#eventsblockpistonretract) * [events.blockDispense()](#eventsblockdispense) * [events.blockBreak()](#eventsblockbreak) - * [events.painting()](#eventspainting) * [events.paintingPlace()](#eventspaintingplace) - * [events.weather()](#eventsweather) * [events.lightningStrike()](#eventslightningstrike) - * [events.vehicle()](#eventsvehicle) * [events.vehicleEnter()](#eventsvehicleenter) * [events.vehicleMove()](#eventsvehiclemove) - * [events.vehicleCollision()](#eventsvehiclecollision) * [events.vehicleCreate()](#eventsvehiclecreate) * [events.asyncPlayerPreLogin()](#eventsasyncplayerprelogin) * [events.playerUnleashEntity()](#eventsplayerunleashentity) * [events.playerPreLogin()](#eventsplayerprelogin) - * [events.player()](#eventsplayer) - * [events.server()](#eventsserver) * [events.inventoryPickupItem()](#eventsinventorypickupitem) * [events.inventoryMoveItem()](#eventsinventorymoveitem) * [events.furnaceBurn()](#eventsfurnaceburn) @@ -194,7 +182,6 @@ Walter Higgins * [events.playerToggleFlight()](#eventsplayertoggleflight) * [events.playerAnimation()](#eventsplayeranimation) * [events.asyncPlayerChat()](#eventsasyncplayerchat) - * [events.playerBucket()](#eventsplayerbucket) * [events.playerRegisterChannel()](#eventsplayerregisterchannel) * [events.playerMove()](#eventsplayermove) * [events.playerTeleport()](#eventsplayerteleport) @@ -206,8 +193,6 @@ Walter Higgins * [events.serverCommand()](#eventsservercommand) * [events.remoteServerCommand()](#eventsremoteservercommand) * [events.mapInitialize()](#eventsmapinitialize) - * [events.service()](#eventsservice) - * [events.plugin()](#eventsplugin) * [events.serviceRegister()](#eventsserviceregister) * [events.serverListPing()](#eventsserverlistping) * [events.serviceUnregister()](#eventsserviceunregister) @@ -963,30 +948,6 @@ The crucial difference is that the events module will have functions for each of the built-in events so tab-completion will help beginning programmers to explore the events at the server console window. -### events.hanging() - -#### Parameters - - * callback - A function which is called whenever the hanging.HangingEvent event is fired - -see events.on() for more information. - -### events.world() - -#### Parameters - - * callback - A function which is called whenever the world.WorldEvent event is fired - -see events.on() for more information. - -### events.chunk() - -#### Parameters - - * callback - A function which is called whenever the world.ChunkEvent event is fired - -see events.on() for more information. - ### events.worldUnload() #### Parameters @@ -1051,14 +1012,6 @@ see events.on() for more information. see events.on() for more information. -### events.entity() - -#### Parameters - - * callback - A function which is called whenever the entity.EntityEvent event is fired - -see events.on() for more information. - ### events.horseJump() #### Parameters @@ -1315,14 +1268,6 @@ see events.on() for more information. see events.on() for more information. -### events.block() - -#### Parameters - - * callback - A function which is called whenever the block.BlockEvent event is fired - -see events.on() for more information. - ### events.blockFade() #### Parameters @@ -1339,14 +1284,6 @@ see events.on() for more information. see events.on() for more information. -### events.blockPiston() - -#### Parameters - - * callback - A function which is called whenever the block.BlockPistonEvent event is fired - -see events.on() for more information. - ### events.blockPistonExtend() #### Parameters @@ -1395,14 +1332,6 @@ see events.on() for more information. see events.on() for more information. -### events.painting() - -#### Parameters - - * callback - A function which is called whenever the painting.PaintingEvent event is fired - -see events.on() for more information. - ### events.paintingPlace() #### Parameters @@ -1411,14 +1340,6 @@ see events.on() for more information. see events.on() for more information. -### events.weather() - -#### Parameters - - * callback - A function which is called whenever the weather.WeatherEvent event is fired - -see events.on() for more information. - ### events.lightningStrike() #### Parameters @@ -1427,14 +1348,6 @@ see events.on() for more information. see events.on() for more information. -### events.vehicle() - -#### Parameters - - * callback - A function which is called whenever the vehicle.VehicleEvent event is fired - -see events.on() for more information. - ### events.vehicleEnter() #### Parameters @@ -1451,14 +1364,6 @@ see events.on() for more information. see events.on() for more information. -### events.vehicleCollision() - -#### Parameters - - * callback - A function which is called whenever the vehicle.VehicleCollisionEvent event is fired - -see events.on() for more information. - ### events.vehicleCreate() #### Parameters @@ -1491,22 +1396,6 @@ see events.on() for more information. see events.on() for more information. -### events.player() - -#### Parameters - - * callback - A function which is called whenever the player.PlayerEvent event is fired - -see events.on() for more information. - -### events.server() - -#### Parameters - - * callback - A function which is called whenever the server.ServerEvent event is fired - -see events.on() for more information. - ### events.inventoryPickupItem() #### Parameters @@ -2131,14 +2020,6 @@ see events.on() for more information. see events.on() for more information. -### events.playerBucket() - -#### Parameters - - * callback - A function which is called whenever the player.PlayerBucketEvent event is fired - -see events.on() for more information. - ### events.playerRegisterChannel() #### Parameters @@ -2227,22 +2108,6 @@ see events.on() for more information. see events.on() for more information. -### events.service() - -#### Parameters - - * callback - A function which is called whenever the server.ServiceEvent event is fired - -see events.on() for more information. - -### events.plugin() - -#### Parameters - - * callback - A function which is called whenever the server.PluginEvent event is fired - -see events.on() for more information. - ### events.serviceRegister() #### Parameters @@ -3138,7 +3003,7 @@ Drones can be created in any of the following ways... block is broken at the block's location you would do so like this... - events.on('block.BlockBreakEvent',function( event) { + events.blockBreak( function( event) { var location = event.block.location; var drone = new Drone(location); // do more stuff with the drone here... @@ -4046,7 +3911,6 @@ Source Code ... A simple event-driven minecraft plugin. How to handle Events. - This example demonstrates event-driven programming. The code below will display the version of ScriptCraft every time an operator joins the game. This module is notable from previous modules for the @@ -4128,6 +3992,14 @@ cleaner and more readable. Similarly where you see a method like } }); +Update: Since version 2.0.8 the above code can be replaced by the more succinct: + + events.playerJoin( function( event ) { + if ( event.player.op ) { + event.player.sendMessage('Welcome to ' + __plugin); + } + }); + ## Arrows Plugin The arrows mod adds fancy arrows to the game. Arrows which... diff --git a/src/generateEventsHelper.js b/src/generateEventsHelper.js index 9bc6b9e..920c65a 100644 --- a/src/generateEventsHelper.js +++ b/src/generateEventsHelper.js @@ -36,6 +36,13 @@ while ( ( entry = zis.nextEntry) != null) { var name = '' + entry.name; if (name.match(/org\/bukkit\/event\/.+Event\.class$/)){ name = name.replace(/\//g,'.').replace('.class',''); + + // abstract events don't have a static getHandlerList method so + // shouldn't be added to this module + var hasHandlerList = eval(name + '.getHandlerList'); + if ( !hasHandlerList ) { + continue; + } var parts = name.split('.'); var shortName = name.replace('org.bukkit.event.',''); var fname = parts.reverse().shift().replace(/^(.)/,function(a){ return a.toLowerCase()}).replace(/Event$/,''); diff --git a/src/main/js/lib/java-utils.js b/src/main/js/lib/java-utils.js index d6cf812..6ed83f5 100644 --- a/src/main/js/lib/java-utils.js +++ b/src/main/js/lib/java-utils.js @@ -1,3 +1,6 @@ exports.isJavaObject = function( o ) { + if (o === global){ + return false; + } return o instanceof java.lang.Object; }; diff --git a/src/main/js/lib/scriptcraft.js b/src/main/js/lib/scriptcraft.js index 9edf6b8..e0350d7 100644 --- a/src/main/js/lib/scriptcraft.js +++ b/src/main/js/lib/scriptcraft.js @@ -619,15 +619,16 @@ function __onEnable ( __engine, __plugin, __script ) global.plugin = plugins.plugin; var events = require('events'); - events.on( 'server.PluginDisableEvent', function( evt ) { + // wph 20131226 - make events global as it is used by many plugins/modules + global.events = events; + + events.pluginDisable(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) { diff --git a/src/main/js/lib/tabcomplete.js b/src/main/js/lib/tabcomplete.js index 810155b..514e7ba 100644 --- a/src/main/js/lib/tabcomplete.js +++ b/src/main/js/lib/tabcomplete.js @@ -24,6 +24,7 @@ var _getProperties = function( o ) { i, j, isObjectMethod, + propValue, typeofProperty; if ( isJavaObject( o ) ) { @@ -49,12 +50,14 @@ var _getProperties = function( o ) { } typeofProperty = null; try { - typeofProperty = typeof o[i]; + propValue = o[i]; + typeofProperty = typeof propValue; } catch( e ) { if ( e.message == 'java.lang.IllegalStateException: Entity not leashed' ) { // wph 20131020 fail silently for Entity leashing in craftbukkit } else { - throw e; + // don't throw an error during tab completion just make a best effort to + // do the job. } } if ( typeofProperty == 'function' ) { @@ -119,6 +122,9 @@ var onTabCompleteJS = function( result, cmdSender, pluginCmd, cmdAlias, cmdArgs if ( statement.length == 0 ) { propsOfLastArg = _globalSymbols; } else { + if (statement.match(/\)$/)){ + return; + } statementSyms = statement.split(/[^\$a-zA-Z0-9_\.]/); lastSymbol = statementSyms[statementSyms.length-1]; diff --git a/src/main/js/modules/signs/menu.js b/src/main/js/modules/signs/menu.js index df1c7af..a792a76 100644 --- a/src/main/js/modules/signs/menu.js +++ b/src/main/js/modules/signs/menu.js @@ -184,7 +184,7 @@ signs.menu = function( /* String */ label, /* Array */ options, /* Function */ c // // update it every time player interacts with it. // -events.on( 'player.PlayerInteractEvent', function( event ) { +events.playerInteract( function( event ) { /* look up our list of menu signs. If there's a matching location and there's a sign, then update it. diff --git a/src/main/js/plugins/alias/alias.js b/src/main/js/plugins/alias/alias.js index 3ed7d30..6ffe4af 100644 --- a/src/main/js/plugins/alias/alias.js +++ b/src/main/js/plugins/alias/alias.js @@ -223,7 +223,7 @@ var _intercept = function( msg, invoker, exec ) { Intercept all command processing and replace with aliased commands if the command about to be issued matches an alias. */ -events.on( 'player.PlayerCommandPreprocessEvent', function( evt ) { +events.playerCommandPreprocess( function( evt ) { var invoker = evt.player; var exec = function( cmd ) { invoker.performCommand(cmd); @@ -237,7 +237,7 @@ events.on( 'player.PlayerCommandPreprocessEvent', function( evt ) { command('void',function( ) { } ); -events.on( 'server.ServerCommandEvent', function( evt ) { +events.serverCommand( function( evt ) { var invoker = evt.sender; var exec = function( cmd ) { invoker.server.dispatchCommand( invoker, cmd); diff --git a/src/main/js/plugins/arrows.js b/src/main/js/plugins/arrows.js index 4e2c8b5..2aa54e0 100644 --- a/src/main/js/plugins/arrows.js +++ b/src/main/js/plugins/arrows.js @@ -121,5 +121,5 @@ var _onArrowHit = function( event ) { } } }; -events.on( 'entity.ProjectileHitEvent', _onArrowHit ); +events.projectileHit( _onArrowHit ); diff --git a/src/main/js/plugins/chat/color.js b/src/main/js/plugins/chat/color.js index 533b557..482caca 100644 --- a/src/main/js/plugins/chat/color.js +++ b/src/main/js/plugins/chat/color.js @@ -73,7 +73,7 @@ foreach( colors, function ( color, i ) { colorCodes[color] = i.toString( 16 ); } ); -events.on( 'player.AsyncPlayerChatEvent', function( event ) { +events.asyncPlayerChat( function( event ) { var player = event.player; var playerChatColor = _store.players[ player.name ]; if ( playerChatColor ) { diff --git a/src/main/js/plugins/classroom/classroom.js b/src/main/js/plugins/classroom/classroom.js index 2f74caf..fa4edfc 100644 --- a/src/main/js/plugins/classroom/classroom.js +++ b/src/main/js/plugins/classroom/classroom.js @@ -158,7 +158,7 @@ var classroom = plugin('classroom', { exports.classroom = classroom; -events.on( 'player.PlayerJoinEvent', function( event ) { +events.playerJoin( function( event ) { if ( _store.enableScripting ) { grantScripting(event.player); } diff --git a/src/main/js/plugins/commando/commando.js b/src/main/js/plugins/commando/commando.js index 64437d6..30f2707 100644 --- a/src/main/js/plugins/commando/commando.js +++ b/src/main/js/plugins/commando/commando.js @@ -84,7 +84,7 @@ exports.commando = function( name, func, options, intercepts ) { return result; }; -events.on( 'player.PlayerCommandPreprocessEvent', function( evt ) { +events.playerCommandPreprocess( function( evt ) { var msg = '' + evt.message; var parts = msg.match( /^\/([^\s]+)/ ); if ( !parts ) { @@ -98,7 +98,7 @@ events.on( 'player.PlayerCommandPreprocessEvent', function( evt ) { evt.message = '/jsp ' + msg.replace( /^\//, '' ); } } ); -events.on( 'server.ServerCommandEvent', function( evt ) { +events.serverCommand( function( evt ) { var msg = '' + evt.command; var parts = msg.match( /^\/*([^\s]+)/ ); if ( !parts ) { diff --git a/src/main/js/plugins/drone/drone.js b/src/main/js/plugins/drone/drone.js index a18f594..b478cb2 100644 --- a/src/main/js/plugins/drone/drone.js +++ b/src/main/js/plugins/drone/drone.js @@ -118,7 +118,7 @@ Drones can be created in any of the following ways... block is broken at the block's location you would do so like this... - events.on('block.BlockBreakEvent',function( event) { + events.blockBreak( function( event) { var location = event.block.location; var drone = new Drone(location); // do more stuff with the drone here... diff --git a/src/main/js/plugins/examples/example-7-hello-events.js b/src/main/js/plugins/examples/example-7-hello-events.js index 8723c91..58b62b5 100644 --- a/src/main/js/plugins/examples/example-7-hello-events.js +++ b/src/main/js/plugins/examples/example-7-hello-events.js @@ -3,7 +3,6 @@ A simple event-driven minecraft plugin. How to handle Events. - This example demonstrates event-driven programming. The code below will display the version of ScriptCraft every time an operator joins the game. This module is notable from previous modules for the @@ -85,6 +84,14 @@ cleaner and more readable. Similarly where you see a method like } }); +Update: Since version 2.0.8 the above code can be replaced by the more succinct: + + events.playerJoin( function( event ) { + if ( event.player.op ) { + event.player.sendMessage('Welcome to ' + __plugin); + } + }); + ***/ events.on( 'player.PlayerJoinEvent', function( event ) { if ( event.player.op ) { diff --git a/src/main/js/plugins/minigames/SnowballFight.js b/src/main/js/plugins/minigames/SnowballFight.js index 5116caf..8d1dfbd 100644 --- a/src/main/js/plugins/minigames/SnowballFight.js +++ b/src/main/js/plugins/minigames/SnowballFight.js @@ -43,7 +43,6 @@ cover to make the game more fun. ***/ var bkGameMode = org.bukkit.GameMode, - bkEntityDamageByEntityEvent = org.bukkit.event.entity.EntityDamageByEntityEvent, bkItemStack = org.bukkit.inventory.ItemStack, bkMaterial = org.bukkit.Material, bkSnowball = org.bukkit.entity.Snowball; @@ -196,7 +195,7 @@ var createGame = function( duration, teams ) { return { start: function( ) { _startGame( _gameState ); - _gameState.listener = events.on(bkEntityDamageByEntityEvent,_onSnowballHit); + _gameState.listener = events.entityDamageByEntity( _onSnowballHit ); new java.lang.Thread( function( ) { while ( _gameState.duration-- ) { java.lang.Thread.sleep( 1000 ); // sleep 1,000 millisecs (1 second) diff --git a/src/main/js/plugins/minigames/cow-clicker.js b/src/main/js/plugins/minigames/cow-clicker.js index 124b037..6ed4250 100644 --- a/src/main/js/plugins/minigames/cow-clicker.js +++ b/src/main/js/plugins/minigames/cow-clicker.js @@ -94,9 +94,9 @@ var _startGame = function( ) { console.log('Staring game: Cow Clicker'); } - events.on( 'player.PlayerQuitEvent', _onPlayerQuit ); - events.on( 'player.PlayerJoinEvent', _onPlayerJoin ); - events.on( 'player.PlayerInteractEntityEvent', _onPlayerInteract ); + events.playerQuit( _onPlayerQuit ); + events.playerJoin( _onPlayerJoin ); + events.playerInteractEntity( _onPlayerInteract ); scoreboard.start(); From 1cfa6ff76768025586ec1fff02d09c5007962e6e Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 26 Apr 2014 20:24:45 +0100 Subject: [PATCH 173/456] added priority optional param to docs --- docs/API-Reference.md | 318 ++++++++++++++++++------------------ src/generateEventsHelper.js | 2 +- 2 files changed, 160 insertions(+), 160 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 0049729..898bb6b 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -954,7 +954,7 @@ beginning programmers to explore the events at the server console window. * callback - A function which is called whenever the world.WorldUnloadEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.worldLoad() @@ -962,7 +962,7 @@ see events.on() for more information. * callback - A function which is called whenever the world.WorldLoadEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.chunkLoad() @@ -970,7 +970,7 @@ see events.on() for more information. * callback - A function which is called whenever the world.ChunkLoadEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.chunkPopulate() @@ -978,7 +978,7 @@ see events.on() for more information. * callback - A function which is called whenever the world.ChunkPopulateEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.portalCreate() @@ -986,7 +986,7 @@ see events.on() for more information. * callback - A function which is called whenever the world.PortalCreateEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.spawnChange() @@ -994,7 +994,7 @@ see events.on() for more information. * callback - A function which is called whenever the world.SpawnChangeEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.chunkUnload() @@ -1002,7 +1002,7 @@ see events.on() for more information. * callback - A function which is called whenever the world.ChunkUnloadEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.worldInit() @@ -1010,7 +1010,7 @@ see events.on() for more information. * callback - A function which is called whenever the world.WorldInitEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.horseJump() @@ -1018,7 +1018,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.HorseJumpEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.entityCombust() @@ -1026,7 +1026,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.EntityCombustEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.entityRegainHealth() @@ -1034,7 +1034,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.EntityRegainHealthEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.entityCombustByBlock() @@ -1042,7 +1042,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.EntityCombustByBlockEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.entityCombustByEntity() @@ -1050,7 +1050,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.EntityCombustByEntityEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerLeashEntity() @@ -1058,7 +1058,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.PlayerLeashEntityEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.pigZap() @@ -1066,7 +1066,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.PigZapEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.itemDespawn() @@ -1074,7 +1074,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.ItemDespawnEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.entityTarget() @@ -1082,7 +1082,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.EntityTargetEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.slimeSplit() @@ -1090,7 +1090,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.SlimeSplitEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.entityChangeBlock() @@ -1098,7 +1098,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.EntityChangeBlockEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.entityPortalEnter() @@ -1106,7 +1106,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.EntityPortalEnterEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.creeperPower() @@ -1114,7 +1114,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.CreeperPowerEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.entityDeath() @@ -1122,7 +1122,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.EntityDeathEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.projectileHit() @@ -1130,7 +1130,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.ProjectileHitEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.entityTame() @@ -1138,7 +1138,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.EntityTameEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.potionSplash() @@ -1146,7 +1146,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.PotionSplashEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.expBottle() @@ -1154,7 +1154,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.ExpBottleEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.entityExplode() @@ -1162,7 +1162,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.EntityExplodeEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.creatureSpawn() @@ -1170,7 +1170,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.CreatureSpawnEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.foodLevelChange() @@ -1178,7 +1178,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.FoodLevelChangeEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.entityInteract() @@ -1186,7 +1186,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.EntityInteractEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.entityBreakDoor() @@ -1194,7 +1194,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.EntityBreakDoorEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.entityCreatePortal() @@ -1202,7 +1202,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.EntityCreatePortalEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.sheepRegrowWool() @@ -1210,7 +1210,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.SheepRegrowWoolEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.explosionPrime() @@ -1218,7 +1218,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.ExplosionPrimeEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.entityUnleash() @@ -1226,7 +1226,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.EntityUnleashEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.entityShootBow() @@ -1234,7 +1234,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.EntityShootBowEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.projectileLaunch() @@ -1242,7 +1242,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.ProjectileLaunchEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.itemSpawn() @@ -1250,7 +1250,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.ItemSpawnEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.sheepDyeWool() @@ -1258,7 +1258,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.SheepDyeWoolEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.entityTeleport() @@ -1266,7 +1266,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.EntityTeleportEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.blockFade() @@ -1274,7 +1274,7 @@ see events.on() for more information. * callback - A function which is called whenever the block.BlockFadeEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.blockDamage() @@ -1282,7 +1282,7 @@ see events.on() for more information. * callback - A function which is called whenever the block.BlockDamageEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.blockPistonExtend() @@ -1290,7 +1290,7 @@ see events.on() for more information. * callback - A function which is called whenever the block.BlockPistonExtendEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.blockExp() @@ -1298,7 +1298,7 @@ see events.on() for more information. * callback - A function which is called whenever the block.BlockExpEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.blockGrow() @@ -1306,7 +1306,7 @@ see events.on() for more information. * callback - A function which is called whenever the block.BlockGrowEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.blockPistonRetract() @@ -1314,7 +1314,7 @@ see events.on() for more information. * callback - A function which is called whenever the block.BlockPistonRetractEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.blockDispense() @@ -1322,7 +1322,7 @@ see events.on() for more information. * callback - A function which is called whenever the block.BlockDispenseEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.blockBreak() @@ -1330,7 +1330,7 @@ see events.on() for more information. * callback - A function which is called whenever the block.BlockBreakEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.paintingPlace() @@ -1338,7 +1338,7 @@ see events.on() for more information. * callback - A function which is called whenever the painting.PaintingPlaceEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.lightningStrike() @@ -1346,7 +1346,7 @@ see events.on() for more information. * callback - A function which is called whenever the weather.LightningStrikeEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.vehicleEnter() @@ -1354,7 +1354,7 @@ see events.on() for more information. * callback - A function which is called whenever the vehicle.VehicleEnterEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.vehicleMove() @@ -1362,7 +1362,7 @@ see events.on() for more information. * callback - A function which is called whenever the vehicle.VehicleMoveEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.vehicleCreate() @@ -1370,7 +1370,7 @@ see events.on() for more information. * callback - A function which is called whenever the vehicle.VehicleCreateEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.asyncPlayerPreLogin() @@ -1378,7 +1378,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.AsyncPlayerPreLoginEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerUnleashEntity() @@ -1386,7 +1386,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerUnleashEntityEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerPreLogin() @@ -1394,7 +1394,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerPreLoginEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.inventoryPickupItem() @@ -1402,7 +1402,7 @@ see events.on() for more information. * callback - A function which is called whenever the inventory.InventoryPickupItemEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.inventoryMoveItem() @@ -1410,7 +1410,7 @@ see events.on() for more information. * callback - A function which is called whenever the inventory.InventoryMoveItemEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.furnaceBurn() @@ -1418,7 +1418,7 @@ see events.on() for more information. * callback - A function which is called whenever the inventory.FurnaceBurnEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.inventory() @@ -1426,7 +1426,7 @@ see events.on() for more information. * callback - A function which is called whenever the inventory.InventoryEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.brew() @@ -1434,7 +1434,7 @@ see events.on() for more information. * callback - A function which is called whenever the inventory.BrewEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.furnaceExtract() @@ -1442,7 +1442,7 @@ see events.on() for more information. * callback - A function which is called whenever the inventory.FurnaceExtractEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.furnaceSmelt() @@ -1450,7 +1450,7 @@ see events.on() for more information. * callback - A function which is called whenever the inventory.FurnaceSmeltEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.inventoryInteract() @@ -1458,7 +1458,7 @@ see events.on() for more information. * callback - A function which is called whenever the inventory.InventoryInteractEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.inventoryClose() @@ -1466,7 +1466,7 @@ see events.on() for more information. * callback - A function which is called whenever the inventory.InventoryCloseEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.inventoryDrag() @@ -1474,7 +1474,7 @@ see events.on() for more information. * callback - A function which is called whenever the inventory.InventoryDragEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.inventoryClick() @@ -1482,7 +1482,7 @@ see events.on() for more information. * callback - A function which is called whenever the inventory.InventoryClickEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.inventoryCreative() @@ -1490,7 +1490,7 @@ see events.on() for more information. * callback - A function which is called whenever the inventory.InventoryCreativeEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.hangingPlace() @@ -1498,7 +1498,7 @@ see events.on() for more information. * callback - A function which is called whenever the hanging.HangingPlaceEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.hangingBreak() @@ -1506,7 +1506,7 @@ see events.on() for more information. * callback - A function which is called whenever the hanging.HangingBreakEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.worldSave() @@ -1514,7 +1514,7 @@ see events.on() for more information. * callback - A function which is called whenever the world.WorldSaveEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.structureGrow() @@ -1522,7 +1522,7 @@ see events.on() for more information. * callback - A function which is called whenever the world.StructureGrowEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.entityDamage() @@ -1530,7 +1530,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.EntityDamageEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.entityTargetLivingEntity() @@ -1538,7 +1538,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.EntityTargetLivingEntityEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerDeath() @@ -1546,7 +1546,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.PlayerDeathEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.entityDamageByBlock() @@ -1554,7 +1554,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.EntityDamageByBlockEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.entityDamageByEntity() @@ -1562,7 +1562,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.EntityDamageByEntityEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.entityPortal() @@ -1570,7 +1570,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.EntityPortalEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.entityPortalExit() @@ -1578,7 +1578,7 @@ see events.on() for more information. * callback - A function which is called whenever the entity.EntityPortalExitEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.signChange() @@ -1586,7 +1586,7 @@ see events.on() for more information. * callback - A function which is called whenever the block.SignChangeEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.leavesDecay() @@ -1594,7 +1594,7 @@ see events.on() for more information. * callback - A function which is called whenever the block.LeavesDecayEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.blockRedstone() @@ -1602,7 +1602,7 @@ see events.on() for more information. * callback - A function which is called whenever the block.BlockRedstoneEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.blockCanBuild() @@ -1610,7 +1610,7 @@ see events.on() for more information. * callback - A function which is called whenever the block.BlockCanBuildEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.blockBurn() @@ -1618,7 +1618,7 @@ see events.on() for more information. * callback - A function which is called whenever the block.BlockBurnEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.blockPhysics() @@ -1626,7 +1626,7 @@ see events.on() for more information. * callback - A function which is called whenever the block.BlockPhysicsEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.blockIgnite() @@ -1634,7 +1634,7 @@ see events.on() for more information. * callback - A function which is called whenever the block.BlockIgniteEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.notePlay() @@ -1642,7 +1642,7 @@ see events.on() for more information. * callback - A function which is called whenever the block.NotePlayEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.blockPlace() @@ -1650,7 +1650,7 @@ see events.on() for more information. * callback - A function which is called whenever the block.BlockPlaceEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.blockFromTo() @@ -1658,7 +1658,7 @@ see events.on() for more information. * callback - A function which is called whenever the block.BlockFromToEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.blockForm() @@ -1666,7 +1666,7 @@ see events.on() for more information. * callback - A function which is called whenever the block.BlockFormEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.blockSpread() @@ -1674,7 +1674,7 @@ see events.on() for more information. * callback - A function which is called whenever the block.BlockSpreadEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.enchantItem() @@ -1682,7 +1682,7 @@ see events.on() for more information. * callback - A function which is called whenever the enchantment.EnchantItemEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.prepareItemEnchant() @@ -1690,7 +1690,7 @@ see events.on() for more information. * callback - A function which is called whenever the enchantment.PrepareItemEnchantEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.paintingBreak() @@ -1698,7 +1698,7 @@ see events.on() for more information. * callback - A function which is called whenever the painting.PaintingBreakEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.paintingBreakByEntity() @@ -1706,7 +1706,7 @@ see events.on() for more information. * callback - A function which is called whenever the painting.PaintingBreakByEntityEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.weatherChange() @@ -1714,7 +1714,7 @@ see events.on() for more information. * callback - A function which is called whenever the weather.WeatherChangeEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.thunderChange() @@ -1722,7 +1722,7 @@ see events.on() for more information. * callback - A function which is called whenever the weather.ThunderChangeEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.vehicleEntityCollision() @@ -1730,7 +1730,7 @@ see events.on() for more information. * callback - A function which is called whenever the vehicle.VehicleEntityCollisionEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.vehicleBlockCollision() @@ -1738,7 +1738,7 @@ see events.on() for more information. * callback - A function which is called whenever the vehicle.VehicleBlockCollisionEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.vehicleExit() @@ -1746,7 +1746,7 @@ see events.on() for more information. * callback - A function which is called whenever the vehicle.VehicleExitEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.vehicleUpdate() @@ -1754,7 +1754,7 @@ see events.on() for more information. * callback - A function which is called whenever the vehicle.VehicleUpdateEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.vehicleDamage() @@ -1762,7 +1762,7 @@ see events.on() for more information. * callback - A function which is called whenever the vehicle.VehicleDamageEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.vehicleDestroy() @@ -1770,7 +1770,7 @@ see events.on() for more information. * callback - A function which is called whenever the vehicle.VehicleDestroyEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerExpChange() @@ -1778,7 +1778,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerExpChangeEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerRespawn() @@ -1786,7 +1786,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerRespawnEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerCommandPreprocess() @@ -1794,7 +1794,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerCommandPreprocessEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerPickupItem() @@ -1802,7 +1802,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerPickupItemEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerInventory() @@ -1810,7 +1810,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerInventoryEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerFish() @@ -1818,7 +1818,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerFishEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerBedEnter() @@ -1826,7 +1826,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerBedEnterEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerLogin() @@ -1834,7 +1834,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerLoginEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerDropItem() @@ -1842,7 +1842,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerDropItemEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerLevelChange() @@ -1850,7 +1850,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerLevelChangeEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerVelocity() @@ -1858,7 +1858,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerVelocityEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerInteract() @@ -1866,7 +1866,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerInteractEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerQuit() @@ -1874,7 +1874,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerQuitEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerChatTabComplete() @@ -1882,7 +1882,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerChatTabCompleteEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerEggThrow() @@ -1890,7 +1890,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerEggThrowEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerChat() @@ -1898,7 +1898,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerChatEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerAchievementAwarded() @@ -1906,7 +1906,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerAchievementAwardedEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerBedLeave() @@ -1914,7 +1914,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerBedLeaveEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerChannel() @@ -1922,7 +1922,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerChannelEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerStatisticIncrement() @@ -1930,7 +1930,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerStatisticIncrementEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerToggleSprint() @@ -1938,7 +1938,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerToggleSprintEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerInteractEntity() @@ -1946,7 +1946,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerInteractEntityEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerEditBook() @@ -1954,7 +1954,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerEditBookEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerKick() @@ -1962,7 +1962,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerKickEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerItemHeld() @@ -1970,7 +1970,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerItemHeldEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerItemConsume() @@ -1978,7 +1978,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerItemConsumeEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerGameModeChange() @@ -1986,7 +1986,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerGameModeChangeEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerItemBreak() @@ -1994,7 +1994,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerItemBreakEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerToggleFlight() @@ -2002,7 +2002,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerToggleFlightEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerAnimation() @@ -2010,7 +2010,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerAnimationEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.asyncPlayerChat() @@ -2018,7 +2018,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.AsyncPlayerChatEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerRegisterChannel() @@ -2026,7 +2026,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerRegisterChannelEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerMove() @@ -2034,7 +2034,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerMoveEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerTeleport() @@ -2042,7 +2042,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerTeleportEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerBucketFill() @@ -2050,7 +2050,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerBucketFillEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerJoin() @@ -2058,7 +2058,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerJoinEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerShearEntity() @@ -2066,7 +2066,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerShearEntityEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerToggleSneak() @@ -2074,7 +2074,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerToggleSneakEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerChangedWorld() @@ -2082,7 +2082,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerChangedWorldEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.serverCommand() @@ -2090,7 +2090,7 @@ see events.on() for more information. * callback - A function which is called whenever the server.ServerCommandEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.remoteServerCommand() @@ -2098,7 +2098,7 @@ see events.on() for more information. * callback - A function which is called whenever the server.RemoteServerCommandEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.mapInitialize() @@ -2106,7 +2106,7 @@ see events.on() for more information. * callback - A function which is called whenever the server.MapInitializeEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.serviceRegister() @@ -2114,7 +2114,7 @@ see events.on() for more information. * callback - A function which is called whenever the server.ServiceRegisterEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.serverListPing() @@ -2122,7 +2122,7 @@ see events.on() for more information. * callback - A function which is called whenever the server.ServerListPingEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.serviceUnregister() @@ -2130,7 +2130,7 @@ see events.on() for more information. * callback - A function which is called whenever the server.ServiceUnregisterEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.prepareItemCraft() @@ -2138,7 +2138,7 @@ see events.on() for more information. * callback - A function which is called whenever the inventory.PrepareItemCraftEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.inventoryOpen() @@ -2146,7 +2146,7 @@ see events.on() for more information. * callback - A function which is called whenever the inventory.InventoryOpenEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.craftItem() @@ -2154,7 +2154,7 @@ see events.on() for more information. * callback - A function which is called whenever the inventory.CraftItemEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.hangingBreakByEntity() @@ -2162,7 +2162,7 @@ see events.on() for more information. * callback - A function which is called whenever the hanging.HangingBreakByEntityEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.blockMultiPlace() @@ -2170,7 +2170,7 @@ see events.on() for more information. * callback - A function which is called whenever the block.BlockMultiPlaceEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.entityBlockForm() @@ -2178,7 +2178,7 @@ see events.on() for more information. * callback - A function which is called whenever the block.EntityBlockFormEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerBucketEmpty() @@ -2186,7 +2186,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerBucketEmptyEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerPortal() @@ -2194,7 +2194,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerPortalEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.playerUnregisterChannel() @@ -2202,7 +2202,7 @@ see events.on() for more information. * callback - A function which is called whenever the player.PlayerUnregisterChannelEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.pluginDisable() @@ -2210,7 +2210,7 @@ see events.on() for more information. * callback - A function which is called whenever the server.PluginDisableEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ### events.pluginEnable() @@ -2218,7 +2218,7 @@ see events.on() for more information. * callback - A function which is called whenever the server.PluginEnableEvent event is fired -see events.on() for more information. + * priority - optional - see events.on() for more information. ## Blocks Module diff --git a/src/generateEventsHelper.js b/src/generateEventsHelper.js index 920c65a..86fe1b0 100644 --- a/src/generateEventsHelper.js +++ b/src/generateEventsHelper.js @@ -55,7 +55,7 @@ while ( ( entry = zis.nextEntry) != null) { '', ' * callback - A function which is called whenever the ' + shortName + ' event is fired', '', - 'see events.on() for more information.', + ' * priority - optional - see events.on() for more information.', '', '***/' ]; From a314bf849f1bb949e6008df63e68274007121140 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 26 Apr 2014 20:31:52 +0100 Subject: [PATCH 174/456] tidy up docs on events-helper module. --- docs/API-Reference.md | 15 +++++++++------ src/generateEventsHelper.js | 15 +++++++++------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 898bb6b..d351a77 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -929,23 +929,26 @@ ScriptCraft uses Java's [String.format()][strfmt] so any string substitution ide [webcons]: https://developer.mozilla.org/en-US/docs/Web/API/console ## Events Helper Module -The Events helper module provides a suite of functions one for each possible event. -For example, the blockBreak function in turn calls events.on(org.bukkit.event.block.BlockBreakEvent, callback, priority) -This module is a convenience wrapper for easily add new event handlers. +The Events helper module provides a suite of functions - one for each possible event. +For example, the events.blockBreak() function is just a wrapper function which calls events.on(org.bukkit.event.block.BlockBreakEvent, callback, priority) +This module is a convenience wrapper for easily adding new event handling functions in Javascript. +At the in-game or server-console prompt, players/admins can type `events.` and use TAB completion +to choose from any of the approx. 160 different event types to listen to. + ### Usage events.blockBreak(function(evt){ evt.player.sendMessage("You broke a block!"); }); -... which is just a shorter way of writing ... +... which is just a shorter and less error-prone way of writing ... events.on("block.BlockBreakEvent",function(evt){ evt.player.sendMessage("You broke a block!"); }); -The crucial difference is that the events module will have functions for each -of the built-in events so tab-completion will help +The crucial difference is that the events module now has functions for each +of the built-in events. The functions are accessible via tab-completion so will help beginning programmers to explore the events at the server console window. ### events.worldUnload() diff --git a/src/generateEventsHelper.js b/src/generateEventsHelper.js index 86fe1b0..3c398ef 100644 --- a/src/generateEventsHelper.js +++ b/src/generateEventsHelper.js @@ -8,23 +8,26 @@ var File = java.io.File, var content = [ '/*********************', '## Events Helper Module', - 'The Events helper module provides a suite of functions one for each possible event.', - 'For example, the blockBreak function in turn calls events.on(org.bukkit.event.block.BlockBreakEvent, callback, priority)', - 'This module is a convenience wrapper for easily add new event handlers.', + 'The Events helper module provides a suite of functions - one for each possible event.', + 'For example, the events.blockBreak() function is just a wrapper function which calls events.on(org.bukkit.event.block.BlockBreakEvent, callback, priority)', + 'This module is a convenience wrapper for easily adding new event handling functions in Javascript. ', + 'At the in-game or server-console prompt, players/admins can type `events.` and use TAB completion ', + 'to choose from any of the approx. 160 different event types to listen to.', + '', '### Usage', '', ' events.blockBreak(function(evt){ ', ' evt.player.sendMessage("You broke a block!"); ', ' });', '', - '... which is just a shorter way of writing ...', + '... which is just a shorter and less error-prone way of writing ...', '', ' events.on("block.BlockBreakEvent",function(evt){ ', ' evt.player.sendMessage("You broke a block!");', ' });', '', - 'The crucial difference is that the events module will have functions for each ', - 'of the built-in events so tab-completion will help ', + 'The crucial difference is that the events module now has functions for each ', + 'of the built-in events. The functions are accessible via tab-completion so will help ', 'beginning programmers to explore the events at the server console window.', '', '***/' From e0d5abb5f6c15c934c9fe28bbc039c971647e06c Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 26 Apr 2014 20:34:45 +0100 Subject: [PATCH 175/456] fix docs for events.js --- docs/API-Reference.md | 1 + src/main/js/lib/events.js | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index d351a77..24f0e79 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -869,6 +869,7 @@ events.on( 'block.BlockBreakEvent', function( evt ) { evt.player.sendMessage( evt.player.name + ' broke a block!'); this.unregister(); } ); +``` The `this` keyword when used inside the callback function refers to the Listener object created by ScriptCraft. It has a single method diff --git a/src/main/js/lib/events.js b/src/main/js/lib/events.js index ed019ed..4278269 100644 --- a/src/main/js/lib/events.js +++ b/src/main/js/lib/events.js @@ -55,6 +55,7 @@ events.on( 'block.BlockBreakEvent', function( evt ) { evt.player.sendMessage( evt.player.name + ' broke a block!'); this.unregister(); } ); +``` The `this` keyword when used inside the callback function refers to the Listener object created by ScriptCraft. It has a single method From f1a960680a19e0119ed8324b4814cd413ab5de6b Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 26 Apr 2014 21:08:18 +0100 Subject: [PATCH 176/456] changed generateEventsHelper to use engine.eval instead of eval (fix travis build problem) --- src/generateEventsHelper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/generateEventsHelper.js b/src/generateEventsHelper.js index 3c398ef..f6f7571 100644 --- a/src/generateEventsHelper.js +++ b/src/generateEventsHelper.js @@ -42,7 +42,7 @@ while ( ( entry = zis.nextEntry) != null) { // abstract events don't have a static getHandlerList method so // shouldn't be added to this module - var hasHandlerList = eval(name + '.getHandlerList'); + var hasHandlerList = engine.eval(name + '.getHandlerList'); if ( !hasHandlerList ) { continue; } From 03ebf347b1fa48cb85df10c01d5a1bd6583ca52c Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 26 Apr 2014 22:09:54 +0100 Subject: [PATCH 177/456] fix travis issues. --- build.xml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/build.xml b/build.xml index 8a1fe2c..0c270ab 100644 --- a/build.xml +++ b/build.xml @@ -70,13 +70,16 @@ - + - + @@ -84,11 +87,11 @@ - + - + @@ -115,7 +118,7 @@ Walter Higgins - + @@ -125,7 +128,7 @@ Walter Higgins - + From 3700c11223e61da0d48ad12ade90019c7f4038b3 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 26 Apr 2014 22:21:03 +0100 Subject: [PATCH 178/456] fix travis issues (finally?) --- src/generateEventsHelper.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/generateEventsHelper.js b/src/generateEventsHelper.js index f6f7571..26ed1de 100644 --- a/src/generateEventsHelper.js +++ b/src/generateEventsHelper.js @@ -42,7 +42,13 @@ while ( ( entry = zis.nextEntry) != null) { // abstract events don't have a static getHandlerList method so // shouldn't be added to this module - var hasHandlerList = engine.eval(name + '.getHandlerList'); + var hasHandlerList = false; + try { + hasHandlerList = engine.eval(name + '.getHandlerList'); + } catch ( ex ) { + // exception is thrown for JRE7 + continue; + } if ( !hasHandlerList ) { continue; } From dc812c934c33d49c960db01b914f71b4d377296c Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Mon, 28 Apr 2014 23:29:53 +0100 Subject: [PATCH 179/456] don't echo result if undefined/null --- src/main/js/lib/java-utils.js | 3 +++ src/main/js/lib/scriptcraft.js | 33 ++++++++++++++++++++------------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/main/js/lib/java-utils.js b/src/main/js/lib/java-utils.js index 6ed83f5..e9405e1 100644 --- a/src/main/js/lib/java-utils.js +++ b/src/main/js/lib/java-utils.js @@ -2,5 +2,8 @@ exports.isJavaObject = function( o ) { if (o === global){ return false; } + if (o !== undefined && o !== null){ + return o.getClass ? true : false; + } return o instanceof java.lang.Object; }; diff --git a/src/main/js/lib/scriptcraft.js b/src/main/js/lib/scriptcraft.js index e0350d7..bdb9848 100644 --- a/src/main/js/lib/scriptcraft.js +++ b/src/main/js/lib/scriptcraft.js @@ -651,25 +651,32 @@ function __onEnable ( __engine, __plugin, __script ) global.self = sender; global.__engine = __engine; try { - + // cannot rely on native eval in jre7 and jre8 + // because ... + // js var hearts + // js hearts + // ... throws an execption ('hearts' is not defined). vars are not sticky in native eval . + // jsResult = __engine.eval( fnBody ); if ( typeof jsResult != 'undefined' ) { if ( jsResult == null) { - sender.sendMessage('(null)'); + // engine eval will return null even if the result should be undefined + // this can be confusing so I think it's better to omit output for this case + // sender.sendMessage('(null)'); } else { - try { - if ( isJavaObject(jsResult) || typeof jsResult === 'function') { - sender.sendMessage(jsResult); - } else { - var replacer = function replacer(key, value){ - return this[key] instanceof java.lang.Object ? '' + this[key] : value; - }; - sender.sendMessage( JSON.stringify( jsResult, replacer, 2) ); - } - } catch ( displayError ) { + try { + if ( isJavaObject(jsResult) || typeof jsResult === 'function') { + sender.sendMessage(jsResult); + } else { + var replacer = function replacer(key, value){ + return this[key] instanceof java.lang.Object ? '' + this[key] : value; + }; + sender.sendMessage( JSON.stringify( jsResult, replacer, 2) ); + } + } catch ( displayError ) { logger.severe( 'Error while trying to display result: ' + jsResult + ', Error: '+ displayError ); - } + } } } } catch ( e ) { From 4d230bd5145a59a4fa34097491f61a9c2d66e358 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 10 May 2014 18:45:48 +0100 Subject: [PATCH 180/456] Added new sounds module --- .gitignore | 8 +- build.properties | 2 +- docs/API-Reference.md | 202 ++++------------------------------ docs/release-notes.md | 12 ++ src/main/js/modules/sounds.js | 48 ++++++++ 5 files changed, 91 insertions(+), 181 deletions(-) create mode 100644 src/main/js/modules/sounds.js diff --git a/.gitignore b/.gitignore index 86d431e..749abfc 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,10 @@ build.local.properties /nbproject/private/private.xml /nbproject/project.xml /src/docs/nbproject/private/ -/src/docs/build/ \ No newline at end of file +/src/docs/build//#build.xml# +/#build.xml# +/src/docs/build/classes/generateApiDocs.js +/src/docs/build/classes/generateTOC.js +/src/docs/build/classes/hello.js +/src/docs/build/classes/.netbeans_automatic_build +/src/docs/build/classes/.netbeans_update_resources diff --git a/build.properties b/build.properties index 254474c..e3d8fee 100644 --- a/build.properties +++ b/build.properties @@ -1,2 +1,2 @@ bukkit-version=1.7.9 -scriptcraft-version=2.0.8 +scriptcraft-version=2.1.0 diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 24f0e79..5c85010 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -59,8 +59,6 @@ Walter Higgins * [events.horseJump()](#eventshorsejump) * [events.entityCombust()](#eventsentitycombust) * [events.entityRegainHealth()](#eventsentityregainhealth) - * [events.entityCombustByBlock()](#eventsentitycombustbyblock) - * [events.entityCombustByEntity()](#eventsentitycombustbyentity) * [events.playerLeashEntity()](#eventsplayerleashentity) * [events.pigZap()](#eventspigzap) * [events.itemDespawn()](#eventsitemdespawn) @@ -78,7 +76,6 @@ Walter Higgins * [events.creatureSpawn()](#eventscreaturespawn) * [events.foodLevelChange()](#eventsfoodlevelchange) * [events.entityInteract()](#eventsentityinteract) - * [events.entityBreakDoor()](#eventsentitybreakdoor) * [events.entityCreatePortal()](#eventsentitycreateportal) * [events.sheepRegrowWool()](#eventssheepregrowwool) * [events.explosionPrime()](#eventsexplosionprime) @@ -95,36 +92,27 @@ Walter Higgins * [events.blockGrow()](#eventsblockgrow) * [events.blockPistonRetract()](#eventsblockpistonretract) * [events.blockDispense()](#eventsblockdispense) - * [events.blockBreak()](#eventsblockbreak) * [events.paintingPlace()](#eventspaintingplace) * [events.lightningStrike()](#eventslightningstrike) * [events.vehicleEnter()](#eventsvehicleenter) * [events.vehicleMove()](#eventsvehiclemove) * [events.vehicleCreate()](#eventsvehiclecreate) * [events.asyncPlayerPreLogin()](#eventsasyncplayerprelogin) - * [events.playerUnleashEntity()](#eventsplayerunleashentity) * [events.playerPreLogin()](#eventsplayerprelogin) * [events.inventoryPickupItem()](#eventsinventorypickupitem) * [events.inventoryMoveItem()](#eventsinventorymoveitem) * [events.furnaceBurn()](#eventsfurnaceburn) * [events.inventory()](#eventsinventory) * [events.brew()](#eventsbrew) - * [events.furnaceExtract()](#eventsfurnaceextract) * [events.furnaceSmelt()](#eventsfurnacesmelt) - * [events.inventoryInteract()](#eventsinventoryinteract) * [events.inventoryClose()](#eventsinventoryclose) * [events.inventoryDrag()](#eventsinventorydrag) * [events.inventoryClick()](#eventsinventoryclick) - * [events.inventoryCreative()](#eventsinventorycreative) * [events.hangingPlace()](#eventshangingplace) * [events.hangingBreak()](#eventshangingbreak) * [events.worldSave()](#eventsworldsave) * [events.structureGrow()](#eventsstructuregrow) * [events.entityDamage()](#eventsentitydamage) - * [events.entityTargetLivingEntity()](#eventsentitytargetlivingentity) - * [events.playerDeath()](#eventsplayerdeath) - * [events.entityDamageByBlock()](#eventsentitydamagebyblock) - * [events.entityDamageByEntity()](#eventsentitydamagebyentity) * [events.entityPortal()](#eventsentityportal) * [events.entityPortalExit()](#eventsentityportalexit) * [events.signChange()](#eventssignchange) @@ -142,7 +130,6 @@ Walter Higgins * [events.enchantItem()](#eventsenchantitem) * [events.prepareItemEnchant()](#eventsprepareitemenchant) * [events.paintingBreak()](#eventspaintingbreak) - * [events.paintingBreakByEntity()](#eventspaintingbreakbyentity) * [events.weatherChange()](#eventsweatherchange) * [events.thunderChange()](#eventsthunderchange) * [events.vehicleEntityCollision()](#eventsvehicleentitycollision) @@ -182,7 +169,6 @@ Walter Higgins * [events.playerToggleFlight()](#eventsplayertoggleflight) * [events.playerAnimation()](#eventsplayeranimation) * [events.asyncPlayerChat()](#eventsasyncplayerchat) - * [events.playerRegisterChannel()](#eventsplayerregisterchannel) * [events.playerMove()](#eventsplayermove) * [events.playerTeleport()](#eventsplayerteleport) * [events.playerBucketFill()](#eventsplayerbucketfill) @@ -198,13 +184,8 @@ Walter Higgins * [events.serviceUnregister()](#eventsserviceunregister) * [events.prepareItemCraft()](#eventsprepareitemcraft) * [events.inventoryOpen()](#eventsinventoryopen) - * [events.craftItem()](#eventscraftitem) - * [events.hangingBreakByEntity()](#eventshangingbreakbyentity) - * [events.blockMultiPlace()](#eventsblockmultiplace) - * [events.entityBlockForm()](#eventsentityblockform) * [events.playerBucketEmpty()](#eventsplayerbucketempty) * [events.playerPortal()](#eventsplayerportal) - * [events.playerUnregisterChannel()](#eventsplayerunregisterchannel) * [events.pluginDisable()](#eventsplugindisable) * [events.pluginEnable()](#eventspluginenable) * [Blocks Module](#blocks-module) @@ -219,6 +200,8 @@ Walter Higgins * [Signs Module](#signs-module) * [signs.menu() function](#signsmenu-function) * [signs.getTargetedBy() function](#signsgettargetedby-function) + * [Sounds Module](#sounds-module) + * [Usage:](#usage-2) * [Utilities Module](#utilities-module) * [utils.player() function](#utilsplayer-function) * [utils.locationToJSON() function](#utilslocationtojson-function) @@ -272,22 +255,22 @@ Walter Higgins * [Drone.hemisphere0() method](#dronehemisphere0-method) * [Drone.spiral_stairs() method](#dronespiral_stairs-method) * [Example Plugin #1 - A simple extension to Minecraft.](#example-plugin-1---a-simple-extension-to-minecraft) - * [Usage:](#usage-2) - * [Example Plugin #2 - Making extensions available for all players.](#example-plugin-2---making-extensions-available-for-all-players) * [Usage:](#usage-3) - * [Example Plugin #3 - Limiting use of commands to operators only.](#example-plugin-3---limiting-use-of-commands-to-operators-only) + * [Example Plugin #2 - Making extensions available for all players.](#example-plugin-2---making-extensions-available-for-all-players) * [Usage:](#usage-4) - * [Example Plugin #4 - Using parameters in commands.](#example-plugin-4---using-parameters-in-commands) + * [Example Plugin #3 - Limiting use of commands to operators only.](#example-plugin-3---limiting-use-of-commands-to-operators-only) * [Usage:](#usage-5) - * [Example Plugin #5 - Re-use - Using your own and others modules.](#example-plugin-5---re-use---using-your-own-and-others-modules) + * [Example Plugin #4 - Using parameters in commands.](#example-plugin-4---using-parameters-in-commands) * [Usage:](#usage-6) - * [Example Plugin #6 - Re-use - Using 'utils' to get Player objects.](#example-plugin-6---re-use---using-utils-to-get-player-objects) + * [Example Plugin #5 - Re-use - Using your own and others modules.](#example-plugin-5---re-use---using-your-own-and-others-modules) * [Usage:](#usage-7) + * [Example Plugin #6 - Re-use - Using 'utils' to get Player objects.](#example-plugin-6---re-use---using-utils-to-get-player-objects) + * [Usage:](#usage-8) * [Example Plugin #7 - Listening for events, Greet players when they join the game.](#example-plugin-7---listening-for-events-greet-players-when-they-join-the-game) * [Arrows Plugin](#arrows-plugin) - * [Usage:](#usage-8) + * [Usage:](#usage-9) * [Spawn Plugin](#spawn-plugin) - * [Usage](#usage-9) + * [Usage](#usage-10) * [alias Plugin](#alias-plugin) * [Examples](#examples-2) * [chat Plugin](#chat-plugin) @@ -1040,22 +1023,6 @@ beginning programmers to explore the events at the server console window. * priority - optional - see events.on() for more information. -### events.entityCombustByBlock() - -#### Parameters - - * callback - A function which is called whenever the entity.EntityCombustByBlockEvent event is fired - - * priority - optional - see events.on() for more information. - -### events.entityCombustByEntity() - -#### Parameters - - * callback - A function which is called whenever the entity.EntityCombustByEntityEvent event is fired - - * priority - optional - see events.on() for more information. - ### events.playerLeashEntity() #### Parameters @@ -1192,14 +1159,6 @@ beginning programmers to explore the events at the server console window. * priority - optional - see events.on() for more information. -### events.entityBreakDoor() - -#### Parameters - - * callback - A function which is called whenever the entity.EntityBreakDoorEvent event is fired - - * priority - optional - see events.on() for more information. - ### events.entityCreatePortal() #### Parameters @@ -1328,14 +1287,6 @@ beginning programmers to explore the events at the server console window. * priority - optional - see events.on() for more information. -### events.blockBreak() - -#### Parameters - - * callback - A function which is called whenever the block.BlockBreakEvent event is fired - - * priority - optional - see events.on() for more information. - ### events.paintingPlace() #### Parameters @@ -1384,14 +1335,6 @@ beginning programmers to explore the events at the server console window. * priority - optional - see events.on() for more information. -### events.playerUnleashEntity() - -#### Parameters - - * callback - A function which is called whenever the player.PlayerUnleashEntityEvent event is fired - - * priority - optional - see events.on() for more information. - ### events.playerPreLogin() #### Parameters @@ -1440,14 +1383,6 @@ beginning programmers to explore the events at the server console window. * priority - optional - see events.on() for more information. -### events.furnaceExtract() - -#### Parameters - - * callback - A function which is called whenever the inventory.FurnaceExtractEvent event is fired - - * priority - optional - see events.on() for more information. - ### events.furnaceSmelt() #### Parameters @@ -1456,14 +1391,6 @@ beginning programmers to explore the events at the server console window. * priority - optional - see events.on() for more information. -### events.inventoryInteract() - -#### Parameters - - * callback - A function which is called whenever the inventory.InventoryInteractEvent event is fired - - * priority - optional - see events.on() for more information. - ### events.inventoryClose() #### Parameters @@ -1488,14 +1415,6 @@ beginning programmers to explore the events at the server console window. * priority - optional - see events.on() for more information. -### events.inventoryCreative() - -#### Parameters - - * callback - A function which is called whenever the inventory.InventoryCreativeEvent event is fired - - * priority - optional - see events.on() for more information. - ### events.hangingPlace() #### Parameters @@ -1536,38 +1455,6 @@ beginning programmers to explore the events at the server console window. * priority - optional - see events.on() for more information. -### events.entityTargetLivingEntity() - -#### Parameters - - * callback - A function which is called whenever the entity.EntityTargetLivingEntityEvent event is fired - - * priority - optional - see events.on() for more information. - -### events.playerDeath() - -#### Parameters - - * callback - A function which is called whenever the entity.PlayerDeathEvent event is fired - - * priority - optional - see events.on() for more information. - -### events.entityDamageByBlock() - -#### Parameters - - * callback - A function which is called whenever the entity.EntityDamageByBlockEvent event is fired - - * priority - optional - see events.on() for more information. - -### events.entityDamageByEntity() - -#### Parameters - - * callback - A function which is called whenever the entity.EntityDamageByEntityEvent event is fired - - * priority - optional - see events.on() for more information. - ### events.entityPortal() #### Parameters @@ -1704,14 +1591,6 @@ beginning programmers to explore the events at the server console window. * priority - optional - see events.on() for more information. -### events.paintingBreakByEntity() - -#### Parameters - - * callback - A function which is called whenever the painting.PaintingBreakByEntityEvent event is fired - - * priority - optional - see events.on() for more information. - ### events.weatherChange() #### Parameters @@ -2024,14 +1903,6 @@ beginning programmers to explore the events at the server console window. * priority - optional - see events.on() for more information. -### events.playerRegisterChannel() - -#### Parameters - - * callback - A function which is called whenever the player.PlayerRegisterChannelEvent event is fired - - * priority - optional - see events.on() for more information. - ### events.playerMove() #### Parameters @@ -2152,38 +2023,6 @@ beginning programmers to explore the events at the server console window. * priority - optional - see events.on() for more information. -### events.craftItem() - -#### Parameters - - * callback - A function which is called whenever the inventory.CraftItemEvent event is fired - - * priority - optional - see events.on() for more information. - -### events.hangingBreakByEntity() - -#### Parameters - - * callback - A function which is called whenever the hanging.HangingBreakByEntityEvent event is fired - - * priority - optional - see events.on() for more information. - -### events.blockMultiPlace() - -#### Parameters - - * callback - A function which is called whenever the block.BlockMultiPlaceEvent event is fired - - * priority - optional - see events.on() for more information. - -### events.entityBlockForm() - -#### Parameters - - * callback - A function which is called whenever the block.EntityBlockFormEvent event is fired - - * priority - optional - see events.on() for more information. - ### events.playerBucketEmpty() #### Parameters @@ -2200,14 +2039,6 @@ beginning programmers to explore the events at the server console window. * priority - optional - see events.on() for more information. -### events.playerUnregisterChannel() - -#### Parameters - - * callback - A function which is called whenever the player.PlayerUnregisterChannelEvent event is fired - - * priority - optional - see events.on() for more information. - ### events.pluginDisable() #### Parameters @@ -2529,6 +2360,19 @@ if ( !sign ) { [buksign]: http://jd.bukkit.org/dev/apidocs/org/bukkit/block/Sign.html +## Sounds Module + +This module is a simple wrapper around the Bukkit Sound class and provides +a simpler way to play sounds. All of the org.bukkit.Sound Enum values are attached. + +### Usage: + + var sounds = require('sounds'); + sounds.play( self, sounds.VILLAGER_NO , 1, 0); // plays VILLAGER_NO sound at full volume and medium pitch + sounds.play( self, sounds.VILLAGER_NO ); // same as previous statement + +The play() function takes either a Location object or any object which has a location. +The volume parameter is in the range 0 to 1 and the pitch parameter is in the range 0 to 4. String class extensions ----------------------- The following chat-formatting methods are added to the javascript String class.. diff --git a/docs/release-notes.md b/docs/release-notes.md index 4e52909..2bd662c 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,3 +1,15 @@ +# 2014 05 10 + +Further simplification of events handling. The events.on() function can still be used but additional functions are now provided for each type of event. +For example, to register a custom player-join event handler... + + events.playerJoin(function(event){ + event.player.sendMessage('welcome!'); + }); + +Added new sounds module for simpler sounds playback and in-game tab completion. +All of the org.bukkit.Sound enum values are attached to the sounds module. + # 2014 04 13 Added asynchronous `input()` function module. diff --git a/src/main/js/modules/sounds.js b/src/main/js/modules/sounds.js new file mode 100644 index 0000000..fca5972 --- /dev/null +++ b/src/main/js/modules/sounds.js @@ -0,0 +1,48 @@ +var bkSound = org.bukkit.Sound, + bkLocation = org.bukkit.Location, + i = 0, + allSounds = bkSound.values(), + len = allSounds.length, + sound, + soundName; + +for ( ; i < len; i++ ) { + sound = allSounds[i]; + soundName = '' + sound.name(); + exports[soundName] = sound; +} +/************************************************************************* +## Sounds Module + +This module is a simple wrapper around the Bukkit Sound class and provides +a simpler way to play sounds. All of the org.bukkit.Sound Enum values are attached. + +### Usage: + + var sounds = require('sounds'); + sounds.play( self, sounds.VILLAGER_NO , 1, 0); // plays VILLAGER_NO sound at full volume and medium pitch + sounds.play( self, sounds.VILLAGER_NO ); // same as previous statement + +The play() function takes either a Location object or any object which has a location. +The volume parameter is in the range 0 to 1 and the pitch parameter is in the range 0 to 4. +***/ +exports.play = function(locationOrHasLocation, sound, volume, pitch) { + var location = null; + if (!locationOrHasLocation) + return; + if (locationOrHasLocation instanceof bkLocation){ + location = locationOrHasLocation; + } else { + locationOrHasLocation = locationOrHasLocation.location; + if (locationOrHasLocation && locationOrHasLocation instanceof bkLocation ){ + location = locationOrHasLocation; + } + } + if (!location) + return; + if (!volume) + volume = 1; + if (!pitch) + pitch = 0; + location.world.playSound(location, sound, volume, pitch); +}; From e3078804abb2549147cd4ce73d91a13c398bada2 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sun, 18 May 2014 00:35:26 +0100 Subject: [PATCH 181/456] Fixed events-helper to include all non-abstract handlers. Added items.js module and fixed sounds.js (more work needed on items.js) --- build.xml | 2 +- docs/API-Reference.md | 197 ++++++++++++++++-- ...YoungPersonsGuideToProgrammingMinecraft.md | 2 +- docs/release-notes.md | 7 + src/docs/templates/ypgpm.md | 2 +- src/generateEventsHelper.js | 16 +- src/main/js/modules/input.js | 19 +- src/main/js/modules/items.js | 27 +++ src/main/js/modules/sounds.js | 57 ++++- 9 files changed, 282 insertions(+), 47 deletions(-) create mode 100644 src/main/js/modules/items.js diff --git a/build.xml b/build.xml index 0c270ab..6b4e694 100644 --- a/build.xml +++ b/build.xml @@ -89,7 +89,7 @@ - + diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 5c85010..8c8c39e 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -59,6 +59,8 @@ Walter Higgins * [events.horseJump()](#eventshorsejump) * [events.entityCombust()](#eventsentitycombust) * [events.entityRegainHealth()](#eventsentityregainhealth) + * [events.entityCombustByBlock()](#eventsentitycombustbyblock) + * [events.entityCombustByEntity()](#eventsentitycombustbyentity) * [events.playerLeashEntity()](#eventsplayerleashentity) * [events.pigZap()](#eventspigzap) * [events.itemDespawn()](#eventsitemdespawn) @@ -76,6 +78,7 @@ Walter Higgins * [events.creatureSpawn()](#eventscreaturespawn) * [events.foodLevelChange()](#eventsfoodlevelchange) * [events.entityInteract()](#eventsentityinteract) + * [events.entityBreakDoor()](#eventsentitybreakdoor) * [events.entityCreatePortal()](#eventsentitycreateportal) * [events.sheepRegrowWool()](#eventssheepregrowwool) * [events.explosionPrime()](#eventsexplosionprime) @@ -92,27 +95,35 @@ Walter Higgins * [events.blockGrow()](#eventsblockgrow) * [events.blockPistonRetract()](#eventsblockpistonretract) * [events.blockDispense()](#eventsblockdispense) + * [events.blockBreak()](#eventsblockbreak) * [events.paintingPlace()](#eventspaintingplace) * [events.lightningStrike()](#eventslightningstrike) * [events.vehicleEnter()](#eventsvehicleenter) * [events.vehicleMove()](#eventsvehiclemove) * [events.vehicleCreate()](#eventsvehiclecreate) * [events.asyncPlayerPreLogin()](#eventsasyncplayerprelogin) + * [events.playerUnleashEntity()](#eventsplayerunleashentity) * [events.playerPreLogin()](#eventsplayerprelogin) * [events.inventoryPickupItem()](#eventsinventorypickupitem) * [events.inventoryMoveItem()](#eventsinventorymoveitem) * [events.furnaceBurn()](#eventsfurnaceburn) * [events.inventory()](#eventsinventory) * [events.brew()](#eventsbrew) + * [events.furnaceExtract()](#eventsfurnaceextract) * [events.furnaceSmelt()](#eventsfurnacesmelt) * [events.inventoryClose()](#eventsinventoryclose) * [events.inventoryDrag()](#eventsinventorydrag) * [events.inventoryClick()](#eventsinventoryclick) + * [events.inventoryCreative()](#eventsinventorycreative) * [events.hangingPlace()](#eventshangingplace) * [events.hangingBreak()](#eventshangingbreak) * [events.worldSave()](#eventsworldsave) * [events.structureGrow()](#eventsstructuregrow) * [events.entityDamage()](#eventsentitydamage) + * [events.entityTargetLivingEntity()](#eventsentitytargetlivingentity) + * [events.playerDeath()](#eventsplayerdeath) + * [events.entityDamageByBlock()](#eventsentitydamagebyblock) + * [events.entityDamageByEntity()](#eventsentitydamagebyentity) * [events.entityPortal()](#eventsentityportal) * [events.entityPortalExit()](#eventsentityportalexit) * [events.signChange()](#eventssignchange) @@ -130,6 +141,7 @@ Walter Higgins * [events.enchantItem()](#eventsenchantitem) * [events.prepareItemEnchant()](#eventsprepareitemenchant) * [events.paintingBreak()](#eventspaintingbreak) + * [events.paintingBreakByEntity()](#eventspaintingbreakbyentity) * [events.weatherChange()](#eventsweatherchange) * [events.thunderChange()](#eventsthunderchange) * [events.vehicleEntityCollision()](#eventsvehicleentitycollision) @@ -156,7 +168,6 @@ Walter Higgins * [events.playerChat()](#eventsplayerchat) * [events.playerAchievementAwarded()](#eventsplayerachievementawarded) * [events.playerBedLeave()](#eventsplayerbedleave) - * [events.playerChannel()](#eventsplayerchannel) * [events.playerStatisticIncrement()](#eventsplayerstatisticincrement) * [events.playerToggleSprint()](#eventsplayertogglesprint) * [events.playerInteractEntity()](#eventsplayerinteractentity) @@ -169,6 +180,7 @@ Walter Higgins * [events.playerToggleFlight()](#eventsplayertoggleflight) * [events.playerAnimation()](#eventsplayeranimation) * [events.asyncPlayerChat()](#eventsasyncplayerchat) + * [events.playerRegisterChannel()](#eventsplayerregisterchannel) * [events.playerMove()](#eventsplayermove) * [events.playerTeleport()](#eventsplayerteleport) * [events.playerBucketFill()](#eventsplayerbucketfill) @@ -184,8 +196,13 @@ Walter Higgins * [events.serviceUnregister()](#eventsserviceunregister) * [events.prepareItemCraft()](#eventsprepareitemcraft) * [events.inventoryOpen()](#eventsinventoryopen) + * [events.craftItem()](#eventscraftitem) + * [events.hangingBreakByEntity()](#eventshangingbreakbyentity) + * [events.blockMultiPlace()](#eventsblockmultiplace) + * [events.entityBlockForm()](#eventsentityblockform) * [events.playerBucketEmpty()](#eventsplayerbucketempty) * [events.playerPortal()](#eventsplayerportal) + * [events.playerUnregisterChannel()](#eventsplayerunregisterchannel) * [events.pluginDisable()](#eventsplugindisable) * [events.pluginEnable()](#eventspluginenable) * [Blocks Module](#blocks-module) @@ -1023,6 +1040,22 @@ beginning programmers to explore the events at the server console window. * priority - optional - see events.on() for more information. +### events.entityCombustByBlock() + +#### Parameters + + * callback - A function which is called whenever the entity.EntityCombustByBlockEvent event is fired + + * priority - optional - see events.on() for more information. + +### events.entityCombustByEntity() + +#### Parameters + + * callback - A function which is called whenever the entity.EntityCombustByEntityEvent event is fired + + * priority - optional - see events.on() for more information. + ### events.playerLeashEntity() #### Parameters @@ -1159,6 +1192,14 @@ beginning programmers to explore the events at the server console window. * priority - optional - see events.on() for more information. +### events.entityBreakDoor() + +#### Parameters + + * callback - A function which is called whenever the entity.EntityBreakDoorEvent event is fired + + * priority - optional - see events.on() for more information. + ### events.entityCreatePortal() #### Parameters @@ -1287,6 +1328,14 @@ beginning programmers to explore the events at the server console window. * priority - optional - see events.on() for more information. +### events.blockBreak() + +#### Parameters + + * callback - A function which is called whenever the block.BlockBreakEvent event is fired + + * priority - optional - see events.on() for more information. + ### events.paintingPlace() #### Parameters @@ -1335,6 +1384,14 @@ beginning programmers to explore the events at the server console window. * priority - optional - see events.on() for more information. +### events.playerUnleashEntity() + +#### Parameters + + * callback - A function which is called whenever the player.PlayerUnleashEntityEvent event is fired + + * priority - optional - see events.on() for more information. + ### events.playerPreLogin() #### Parameters @@ -1383,6 +1440,14 @@ beginning programmers to explore the events at the server console window. * priority - optional - see events.on() for more information. +### events.furnaceExtract() + +#### Parameters + + * callback - A function which is called whenever the inventory.FurnaceExtractEvent event is fired + + * priority - optional - see events.on() for more information. + ### events.furnaceSmelt() #### Parameters @@ -1415,6 +1480,14 @@ beginning programmers to explore the events at the server console window. * priority - optional - see events.on() for more information. +### events.inventoryCreative() + +#### Parameters + + * callback - A function which is called whenever the inventory.InventoryCreativeEvent event is fired + + * priority - optional - see events.on() for more information. + ### events.hangingPlace() #### Parameters @@ -1455,6 +1528,38 @@ beginning programmers to explore the events at the server console window. * priority - optional - see events.on() for more information. +### events.entityTargetLivingEntity() + +#### Parameters + + * callback - A function which is called whenever the entity.EntityTargetLivingEntityEvent event is fired + + * priority - optional - see events.on() for more information. + +### events.playerDeath() + +#### Parameters + + * callback - A function which is called whenever the entity.PlayerDeathEvent event is fired + + * priority - optional - see events.on() for more information. + +### events.entityDamageByBlock() + +#### Parameters + + * callback - A function which is called whenever the entity.EntityDamageByBlockEvent event is fired + + * priority - optional - see events.on() for more information. + +### events.entityDamageByEntity() + +#### Parameters + + * callback - A function which is called whenever the entity.EntityDamageByEntityEvent event is fired + + * priority - optional - see events.on() for more information. + ### events.entityPortal() #### Parameters @@ -1591,6 +1696,14 @@ beginning programmers to explore the events at the server console window. * priority - optional - see events.on() for more information. +### events.paintingBreakByEntity() + +#### Parameters + + * callback - A function which is called whenever the painting.PaintingBreakByEntityEvent event is fired + + * priority - optional - see events.on() for more information. + ### events.weatherChange() #### Parameters @@ -1799,14 +1912,6 @@ beginning programmers to explore the events at the server console window. * priority - optional - see events.on() for more information. -### events.playerChannel() - -#### Parameters - - * callback - A function which is called whenever the player.PlayerChannelEvent event is fired - - * priority - optional - see events.on() for more information. - ### events.playerStatisticIncrement() #### Parameters @@ -1903,6 +2008,14 @@ beginning programmers to explore the events at the server console window. * priority - optional - see events.on() for more information. +### events.playerRegisterChannel() + +#### Parameters + + * callback - A function which is called whenever the player.PlayerRegisterChannelEvent event is fired + + * priority - optional - see events.on() for more information. + ### events.playerMove() #### Parameters @@ -2023,6 +2136,38 @@ beginning programmers to explore the events at the server console window. * priority - optional - see events.on() for more information. +### events.craftItem() + +#### Parameters + + * callback - A function which is called whenever the inventory.CraftItemEvent event is fired + + * priority - optional - see events.on() for more information. + +### events.hangingBreakByEntity() + +#### Parameters + + * callback - A function which is called whenever the hanging.HangingBreakByEntityEvent event is fired + + * priority - optional - see events.on() for more information. + +### events.blockMultiPlace() + +#### Parameters + + * callback - A function which is called whenever the block.BlockMultiPlaceEvent event is fired + + * priority - optional - see events.on() for more information. + +### events.entityBlockForm() + +#### Parameters + + * callback - A function which is called whenever the block.EntityBlockFormEvent event is fired + + * priority - optional - see events.on() for more information. + ### events.playerBucketEmpty() #### Parameters @@ -2039,6 +2184,14 @@ beginning programmers to explore the events at the server console window. * priority - optional - see events.on() for more information. +### events.playerUnregisterChannel() + +#### Parameters + + * callback - A function which is called whenever the player.PlayerUnregisterChannelEvent event is fired + + * priority - optional - see events.on() for more information. + ### events.pluginDisable() #### Parameters @@ -2128,20 +2281,20 @@ This new `input()` function is best illustrated by example. The following code i var input = require('input'); exports.numberguess = function(player){ var randomNumber = Math.ceil(Math.random() * 10); - input( player, 'Think of a number between 1 and 10 (q to quit)', function( guess, repeat ) { + input( player, 'Think of a number between 1 and 10 (q to quit)', function( guess, guesser, repeat ) { if ( guess == 'q'){ return; } if ( +guess !== randomNumber ) { if (+guess < randomNumber ) { - player.sendMessage('Too low - guess again'); + guesser.sendMessage('Too low - guess again'); } if (+guess > randomNumber ) { - player.sendMessage('Too high - guess again'); + guesser.sendMessage('Too high - guess again'); } repeat(); } else { - player.sendMessage('You guessed correctly'); + guesser.sendMessage('You guessed correctly'); } }); }; @@ -2158,8 +2311,8 @@ The callback is bound to an object which has the following properties: The callback function as well as being bound to an object with the above properties (so you can use this.value inside your callback to get the value which has just been input), can also take the following parameters (in exact order): * value - * repeat * sender + * repeat The `value` parameter will be the same as `this.value`, the `repeat` parameter will be the same as `this.repeat` and so on. @@ -2368,11 +2521,23 @@ a simpler way to play sounds. All of the org.bukkit.Sound Enum values are attach ### Usage: var sounds = require('sounds'); - sounds.play( self, sounds.VILLAGER_NO , 1, 0); // plays VILLAGER_NO sound at full volume and medium pitch - sounds.play( self, sounds.VILLAGER_NO ); // same as previous statement + sounds.play( org.bukkit.Sound.VILLAGER_NO , self, 1, 0); // plays VILLAGER_NO sound at full volume and medium pitch + sounds.play( org.bukkit.Sound.VILLAGER_NO , self ); // same as previous statement The play() function takes either a Location object or any object which has a location. The volume parameter is in the range 0 to 1 and the pitch parameter is in the range 0 to 4. + +In addition, a play function is provided for each possible sound using the following rules: + +1. The sound is converted from ALL_CAPS_UNDERSCORE to camelCase so for example there is a sounds.villagerNo() function which will play the VILLAGER_NO sound. +2. Each such function can take 3 parameters: location (which can be either an actual Location object or an object which has a location), volume and pitch +3. Or... each such function can be called without parameters meaning the sound will be played for all online players to hear. + + sounds.villagerNo(self, 1, 0); // plays VILLAGER_NO sound at full volume and medium pitch at invoker's location + + sounds.villagerNo(); // plays VILLAGER_NO sound for all players online. + +These methods are provided for convenience to help beginners explore sounds using TAB completion. String class extensions ----------------------- The following chat-formatting methods are added to the javascript String class.. diff --git a/docs/YoungPersonsGuideToProgrammingMinecraft.md b/docs/YoungPersonsGuideToProgrammingMinecraft.md index 24c465e..443d214 100644 --- a/docs/YoungPersonsGuideToProgrammingMinecraft.md +++ b/docs/YoungPersonsGuideToProgrammingMinecraft.md @@ -1014,7 +1014,7 @@ Open your favorite editor and type the following code into a new file in your scriptcraft/plugins directory... ```javascript -function flightStatus( player ) { +exports.flightStatus = function( player ) { if ( player.flying ) { player.sendMessage( 'Hey, You are flying!' ); } else { diff --git a/docs/release-notes.md b/docs/release-notes.md index 2bd662c..8d05fc5 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,3 +1,10 @@ +# 2014 05 12 + +Turn off modality for conversations which are started via the 'input' module. +(with modality on, player.sendMessage() is suppressed but player.sendRawMessage() isn't. + turning modality off as devs would expect player.sendMessage() to work - I did anyway) + + # 2014 05 10 Further simplification of events handling. The events.on() function can still be used but additional functions are now provided for each type of event. diff --git a/src/docs/templates/ypgpm.md b/src/docs/templates/ypgpm.md index 39b30a2..82f3433 100644 --- a/src/docs/templates/ypgpm.md +++ b/src/docs/templates/ypgpm.md @@ -978,7 +978,7 @@ Open your favorite editor and type the following code into a new file in your scriptcraft/plugins directory... ```javascript -function flightStatus( player ) { +exports.flightStatus = function( player ) { if ( player.flying ) { player.sendMessage( 'Hey, You are flying!' ); } else { diff --git a/src/generateEventsHelper.js b/src/generateEventsHelper.js index 26ed1de..370a8df 100644 --- a/src/generateEventsHelper.js +++ b/src/generateEventsHelper.js @@ -2,6 +2,8 @@ var File = java.io.File, FileReader = java.io.FileReader, FileInputStream = java.io.FileInputStream, out = java.lang.System.out, + err = java.lang.System.err, + Modifier = java.lang.reflect.Modifier, ZipInputStream = java.util.zip.ZipInputStream, zis = new ZipInputStream(new FileInputStream('./target/minecraft/craftbukkit.jar')), entry = null; @@ -39,17 +41,9 @@ while ( ( entry = zis.nextEntry) != null) { var name = '' + entry.name; if (name.match(/org\/bukkit\/event\/.+Event\.class$/)){ name = name.replace(/\//g,'.').replace('.class',''); - - // abstract events don't have a static getHandlerList method so - // shouldn't be added to this module - var hasHandlerList = false; - try { - hasHandlerList = engine.eval(name + '.getHandlerList'); - } catch ( ex ) { - // exception is thrown for JRE7 - continue; - } - if ( !hasHandlerList ) { + var clz = java.lang.Class.forName(name); + var isAbstract = Modifier.isAbstract(clz.getModifiers()); + if ( isAbstract ) { continue; } var parts = name.split('.'); diff --git a/src/main/js/modules/input.js b/src/main/js/modules/input.js index 4941085..c3755af 100644 --- a/src/main/js/modules/input.js +++ b/src/main/js/modules/input.js @@ -20,20 +20,20 @@ This new `input()` function is best illustrated by example. The following code i var input = require('input'); exports.numberguess = function(player){ var randomNumber = Math.ceil(Math.random() * 10); - input( player, 'Think of a number between 1 and 10 (q to quit)', function( guess, repeat ) { + input( player, 'Think of a number between 1 and 10 (q to quit)', function( guess, guesser, repeat ) { if ( guess == 'q'){ return; } if ( +guess !== randomNumber ) { if (+guess < randomNumber ) { - player.sendMessage('Too low - guess again'); + guesser.sendMessage('Too low - guess again'); } if (+guess > randomNumber ) { - player.sendMessage('Too high - guess again'); + guesser.sendMessage('Too high - guess again'); } repeat(); } else { - player.sendMessage('You guessed correctly'); + guesser.sendMessage('You guessed correctly'); } }); }; @@ -50,8 +50,8 @@ The callback is bound to an object which has the following properties: The callback function as well as being bound to an object with the above properties (so you can use this.value inside your callback to get the value which has just been input), can also take the following parameters (in exact order): * value - * repeat * sender + * repeat The `value` parameter will be the same as `this.value`, the `repeat` parameter will be the same as `this.repeat` and so on. @@ -64,21 +64,22 @@ function asyncInput( sender, promptMesg, callback) { var repeat = function(){ asyncInput( sender, promptMesg, callback); }; - var prompt = new bkPrompt( ) { + var prompt = new bkPrompt( { getPromptText: function( ctx ) { return promptMesg; }, acceptInput: function( ctx, value ) { callback.apply( { repeat: repeat, sender: sender, message: promptMesg, value: value }, - [value, repeat, sender]); + [value, sender, repeat]); return null; }, blocksForInput: function( ctx ) { return true; } - }; + }); + new bkConversationFactory( __plugin ) - .withModality( true ) + .withModality( false ) .withFirstPrompt( prompt ) .buildConversation( sender ) .begin( ); diff --git a/src/main/js/modules/items.js b/src/main/js/modules/items.js new file mode 100644 index 0000000..da485a6 --- /dev/null +++ b/src/main/js/modules/items.js @@ -0,0 +1,27 @@ +var bkItemStack = org.bukkit.inventory.ItemStack, + bkMaterial = org.bukkit.Material +var items = function(material, amount){ + material = material.toUpperCase(); + return new bkItemStack(bkMaterial[material],amount); +}; +module.exports = items; + +var materials = bkMaterial.values(); + +for (var i = 0;i < materials.length; i++ ){ + var name = (''+materials[i].name()).toLowerCase(); + name = name.replace(/(_.)/g,function(a){ return a.replace(/_/,'').toUpperCase(); }); + + items[name] = (function(material){ + return function(amount){ + if (typeof amount == 'undefined'){ + amount = 1; + } + if (typeof amount == 'number'){ + return new bkItemStack(material, amount); + } else { + return amount == material; + } + }; + })(materials[i]); +} diff --git a/src/main/js/modules/sounds.js b/src/main/js/modules/sounds.js index fca5972..84114fb 100644 --- a/src/main/js/modules/sounds.js +++ b/src/main/js/modules/sounds.js @@ -1,6 +1,7 @@ var bkSound = org.bukkit.Sound, bkLocation = org.bukkit.Location, i = 0, + foreach = require('utils').foreach, allSounds = bkSound.values(), len = allSounds.length, sound, @@ -9,7 +10,33 @@ var bkSound = org.bukkit.Sound, for ( ; i < len; i++ ) { sound = allSounds[i]; soundName = '' + sound.name(); - exports[soundName] = sound; + var methodName = soundName.toLowerCase().replace(/_(.)/g,function(a,b){ return b.toUpperCase();}); + exports[methodName] = (function(sound){ + return function() + { + switch (arguments.length) { + case 3: + exports.play(sound, arguments[0], arguments[1], arguments[2]); + break; + case 2: + // TODO: possible combinations: + // location, volume, + // volume pitch + exports.play(sound, arguments[0],arguments[1]); + break; + case 1: + exports.play(sound, arguments[0]); + break; + case 0: + // play the sound at full vol, medium pitch for all players + // + foreach(server.onlinePlayers,function(player){ + exports.play(sound, player, 1, 0); + }); + default: + } + }; + })(sound); } /************************************************************************* ## Sounds Module @@ -20,13 +47,25 @@ a simpler way to play sounds. All of the org.bukkit.Sound Enum values are attach ### Usage: var sounds = require('sounds'); - sounds.play( self, sounds.VILLAGER_NO , 1, 0); // plays VILLAGER_NO sound at full volume and medium pitch - sounds.play( self, sounds.VILLAGER_NO ); // same as previous statement + sounds.play( org.bukkit.Sound.VILLAGER_NO , self, 1, 0); // plays VILLAGER_NO sound at full volume and medium pitch + sounds.play( org.bukkit.Sound.VILLAGER_NO , self ); // same as previous statement The play() function takes either a Location object or any object which has a location. The volume parameter is in the range 0 to 1 and the pitch parameter is in the range 0 to 4. + +In addition, a play function is provided for each possible sound using the following rules: + +1. The sound is converted from ALL_CAPS_UNDERSCORE to camelCase so for example there is a sounds.villagerNo() function which will play the VILLAGER_NO sound. +2. Each such function can take 3 parameters: location (which can be either an actual Location object or an object which has a location), volume and pitch +3. Or... each such function can be called without parameters meaning the sound will be played for all online players to hear. + + sounds.villagerNo(self, 1, 0); // plays VILLAGER_NO sound at full volume and medium pitch at invoker's location + + sounds.villagerNo(); // plays VILLAGER_NO sound for all players online. + +These methods are provided for convenience to help beginners explore sounds using TAB completion. ***/ -exports.play = function(locationOrHasLocation, sound, volume, pitch) { +exports.play = function(sound, locationOrHasLocation, volume, pitch) { var location = null; if (!locationOrHasLocation) return; @@ -38,11 +77,13 @@ exports.play = function(locationOrHasLocation, sound, volume, pitch) { location = locationOrHasLocation; } } - if (!location) + if (!location){ + console.warn('sounds.play() needs a location'); return; - if (!volume) + } + if (typeof volume == 'undefined') volume = 1; - if (!pitch) - pitch = 0; + if (typeof pitch == 'undefined') + pitch = 1; location.world.playSound(location, sound, volume, pitch); }; From c324adf269bdc68c4f6fd4b5dfb67eb83f49bc03 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Tue, 20 May 2014 00:05:38 +0100 Subject: [PATCH 182/456] Improved Tab completion to work with Java Enums on JRE7 and JRE8. Added bukkit namespace. --- docs/API-Reference.md | 11 ++++++++++ docs/release-notes.md | 3 +++ src/main/js/lib/bukkit.js | 26 +++++++++++++++++++++++ src/main/js/lib/java-utils.js | 22 ++++++++++++++++++- src/main/js/lib/scriptcraft.js | 1 + src/main/js/lib/tabcomplete.js | 22 ++++++++++++++++--- src/main/js/modules/items.js | 2 +- src/main/js/modules/utils/utils.js | 34 ++++++++++++++++++++++++++++++ 8 files changed, 116 insertions(+), 5 deletions(-) create mode 100644 src/main/js/lib/bukkit.js diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 8c8c39e..d707964 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -233,6 +233,7 @@ Walter Higgins * [utils.serverAddress() function](#utilsserveraddress-function) * [utils.watchFile() function](#utilswatchfile-function) * [utils.unwatchFile() function](#utilsunwatchfile-function) + * [utils.array() function](#utilsarray-function) * [Drone Plugin](#drone-plugin) * [TLDNR; (Just read this if you're impatient)](#tldnr-just-read-this-if-youre-impatient) * [Constructing a Drone Object](#constructing-a-drone-object) @@ -2904,6 +2905,16 @@ var utils = require('utils'); utils.unwatchFile( 'test.txt'); ``` +### utils.array() function + +Converts Java collection objects to type Javascript array so they can avail of +all of Javascript's Array goodness. + +#### Example + + var utils = require('utils'); + var worlds = utils.array(server.worlds); + ## Drone Plugin The Drone is a convenience class for building. It can be used for... diff --git a/docs/release-notes.md b/docs/release-notes.md index 8d05fc5..378b472 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,3 +1,6 @@ +# 2014 05 19 +Improved Tab Completion to work with Java Enums too. + # 2014 05 12 Turn off modality for conversations which are started via the 'input' module. diff --git a/src/main/js/lib/bukkit.js b/src/main/js/lib/bukkit.js new file mode 100644 index 0000000..bbbf532 --- /dev/null +++ b/src/main/js/lib/bukkit.js @@ -0,0 +1,26 @@ +var bukkit = { + stat: org.bukkit.Statistic, + stats: org.bukkit.Statistic, + material: org.bukkit.Material, + art: org.bukkit.Art, + mode: org.bukkit.GameMode, + sound: org.bukkit.Sound, + players: function(){ + var result = []; + for (var i = 0; i < server.onlinePlayers.length; i++){ + result.push(server.onlinePlayers[i]); + } + return result; + }, + worlds: function(){ + var result = []; + var lWorlds = server.worlds; + for (var i = 0; i < lWorlds.size(); i++){ + result.push(lWorlds.get(i)); + } + return result; + } +}; +module.exports = function( container ){ + container.bukkit = bukkit; +}; diff --git a/src/main/js/lib/java-utils.js b/src/main/js/lib/java-utils.js index e9405e1..aaa065e 100644 --- a/src/main/js/lib/java-utils.js +++ b/src/main/js/lib/java-utils.js @@ -3,7 +3,27 @@ exports.isJavaObject = function( o ) { return false; } if (o !== undefined && o !== null){ - return o.getClass ? true : false; + try { + // this throws error for java objects in jre7 + if (typeof o.constructor === 'function'){ + return false; + } + } catch (e){ + return true; + } + try { + var result = o.getClass ? true : false; // throws error for Enums/Class in jre7 + if (result == true){ + return result; + } + }catch (e2){ + // fail silently and move on to next test + } + // java classes don't have a getClass so just because .getClass isn't present + // doesn't mean it's not a Java Enum or Class (.getClass only works for object instances?) + if (o instanceof java.lang.Object){ + return true; + } } return o instanceof java.lang.Object; }; diff --git a/src/main/js/lib/scriptcraft.js b/src/main/js/lib/scriptcraft.js index bdb9848..d901f73 100644 --- a/src/main/js/lib/scriptcraft.js +++ b/src/main/js/lib/scriptcraft.js @@ -630,6 +630,7 @@ function __onEnable ( __engine, __plugin, __script ) org.bukkit.event.HandlerList['unregisterAll(org.bukkit.plugin.Plugin)'](__plugin); }); + require('bukkit')( global ); global.__onCommand = function( sender, cmd, label, args) { var jsArgs = [], diff --git a/src/main/js/lib/tabcomplete.js b/src/main/js/lib/tabcomplete.js index 514e7ba..97ac135 100644 --- a/src/main/js/lib/tabcomplete.js +++ b/src/main/js/lib/tabcomplete.js @@ -72,8 +72,18 @@ var _getProperties = function( o ) { } for ( i in o ) { if ( i.match( /^[^_]/ ) ) { - if ( typeof o[i] == 'function' ) { - result.push( i+'()' ); + if ( typeof o[i] == 'function'){ + if ( ! (o[i] instanceof java.lang.Object) ) { + try { + if (o[i].constructor){} // throws error for java objects in jre7 + result.push(i + '()'); + } catch (e ){ + result.push(i); + } + + }else { + result.push( i ); + } } else { result.push( i ); } @@ -146,7 +156,13 @@ var onTabCompleteJS = function( result, cmdSender, pluginCmd, cmdAlias, cmdArgs if ( !name ) { // fix issue #115 break; } - symbol = symbol[name]; // this causes problem in jre8 if name is '' + try { + // this causes problems in jre if symbol is an enum and name is partial-match + symbol = symbol[name]; // this causes problem in jre8 if name is '' + } catch (e){ + symbol = null; + break; + } if ( typeof symbol == 'undefined' ) { break; } diff --git a/src/main/js/modules/items.js b/src/main/js/modules/items.js index da485a6..080a9a6 100644 --- a/src/main/js/modules/items.js +++ b/src/main/js/modules/items.js @@ -15,7 +15,7 @@ for (var i = 0;i < materials.length; i++ ){ items[name] = (function(material){ return function(amount){ if (typeof amount == 'undefined'){ - amount = 1; + return material; } if (typeof amount == 'number'){ return new bkItemStack(material, amount); diff --git a/src/main/js/modules/utils/utils.js b/src/main/js/modules/utils/utils.js index ac86768..a47f387 100644 --- a/src/main/js/modules/utils/utils.js +++ b/src/main/js/modules/utils/utils.js @@ -557,3 +557,37 @@ function fileWatcher() { setTimeout( fileWatcher, 5000 ); }; setTimeout( fileWatcher, 5000 ); +/************************************************************************** +### utils.array() function + +Converts Java collection objects to type Javascript array so they can avail of +all of Javascript's Array goodness. + +#### Example + + var utils = require('utils'); + var worlds = utils.array(server.worlds); + +***/ +exports.array = function( ){ + var result = [], + javaArray = null, + i = 0; + if (arguments[0] instanceof java.util.Collection){ + // it's a java collection + javaArray = arguments[0].toArray(); + for ( ;i < javaArray.length; i++) { + result.push(javaArray[i]); + } + } else if (arguments[0].constructor === Array){ + // it's a javascript array + return arguments[0]; + } else if (arguments[0].length) { + // it's a java array + javaArray = arguments[0]; + for ( ;i < javaArray.length; i++) { + result.push(javaArray[i]); + } + } + return result; +}; From 2f2db3c76f84b15ee57ce966d7e76328b0fa481f Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 24 May 2014 10:55:27 +0100 Subject: [PATCH 183/456] Added new blocks. and changed Drone.extend to support single param. --- src/main/js/modules/blocks.js | 4 +++- src/main/js/modules/items.js | 5 +++-- src/main/js/plugins/drone/contrib/temple.js | 12 +++++++++--- src/main/js/plugins/drone/drone.js | 21 +++++++++++++++++++-- 4 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/main/js/modules/blocks.js b/src/main/js/modules/blocks.js index ae4fcd8..5b96cf3 100644 --- a/src/main/js/modules/blocks.js +++ b/src/main/js/modules/blocks.js @@ -230,7 +230,9 @@ var blocks = { white: 171 // All other colors added below }, hardened_clay: 172, - coal_block: 173 + coal_block: 173, + packed_ice: 174, + double_plant: 175 }; // Add all available colors to colorized block collections diff --git a/src/main/js/modules/items.js b/src/main/js/modules/items.js index 080a9a6..a3b60cd 100644 --- a/src/main/js/modules/items.js +++ b/src/main/js/modules/items.js @@ -1,6 +1,7 @@ var bkItemStack = org.bukkit.inventory.ItemStack, - bkMaterial = org.bukkit.Material -var items = function(material, amount){ + bkMaterial = org.bukkit.Material; + +var items = function( material, amount ) { material = material.toUpperCase(); return new bkItemStack(bkMaterial[material],amount); }; diff --git a/src/main/js/plugins/drone/contrib/temple.js b/src/main/js/plugins/drone/contrib/temple.js index 82d52c7..8545e60 100644 --- a/src/main/js/plugins/drone/contrib/temple.js +++ b/src/main/js/plugins/drone/contrib/temple.js @@ -15,10 +15,16 @@ Drone.extend('temple', function(side) { var middle = Math.round( (side-2) / 2 ); this.chkpt('corner') .box( stone, side, 1, side ) - .right( middle ).box( stair ).right().box( stair ) - .move('corner').up().fwd().right(); + .right( middle ) + .box( stair ) + .right() + .box( stair ) + .move('corner') + .up() + .fwd() + .right(); side = side - 2; } - return this.move('temple'); + this.move('temple'); }); diff --git a/src/main/js/plugins/drone/drone.js b/src/main/js/plugins/drone/drone.js index b478cb2..bd04253 100644 --- a/src/main/js/plugins/drone/drone.js +++ b/src/main/js/plugins/drone/drone.js @@ -582,10 +582,13 @@ Use this method to add new methods (which also become chainable global functions #### Parameters - * name - The name of the new method e.g. 'pyramid' + * name - The name of the new method e.g. 'pyramid'. * function - The method body. -#### Example +Alternatively if you provide just a function as a parameter, then the function name will be used as the new method name. For example the following two approaches are both valid. + + +#### Example 1 Using name and function as parameters // submitted by [edonaldson][edonaldson] Drone.extend('pyramid', function( block,height) { @@ -596,6 +599,16 @@ Use this method to add new methods (which also become chainable global functions return this.move('pyramid'); }); +#### Example 2 Using just a named function as a parameter + + Drone.extend(function pyramid( block,height) { + this.chkpt('pyramid'); + for ( var i = height; i > 0; i -= 2) { + this.box(block, i, 1, i).up().right().fwd(); + } + return this.move('pyramid'); + }); + Once the method is defined (it can be defined in a new pyramid.js file) it can be used like so... var d = new Drone(); @@ -790,6 +803,10 @@ addUnloadHandler( function() { // add custom methods to the Drone object using this function // Drone.extend = function( name, func ) { + if (arguments.length == 1){ + func = name; + name = func.name; + } Drone.prototype[ '_' + name ] = func; Drone.prototype[ name ] = function( ) { if ( this.record ) { From fa64f07c3828acb53a29102b4783e20a726a1869 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Mon, 26 May 2014 21:03:47 +0100 Subject: [PATCH 184/456] doc changes - change ref of js-plugins to scriptcraft/plugins --- README.md | 6 +++--- docs/API-Reference.md | 32 ++++++++++++++---------------- src/main/js/lib/scriptcraft.js | 4 ++-- src/main/js/plugins/drone/drone.js | 14 ------------- 4 files changed, 20 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 0290848..14009cf 100644 --- a/README.md +++ b/README.md @@ -65,8 +65,8 @@ directory. # Post Install -Once installed, a new js-plugins directory is automatically created in -the same directory as the plugins folder. All files in the js-plugins +Once installed, a new scriptcraft/plugins directory is automatically created in +the same directory as the plugins folder. All files in the scriptcraft/plugins directory will be automatically loaded when CraftBukkit starts. *Only players who are ops can use this plugin.* You can grant a player `op` privileges by typing 'op ' at the server console prompt or @@ -94,7 +94,7 @@ javascript plugin for Minecraft. [si]: blob/master/src/main/javascript/modules/signs/menu.js A Javascript mod for minecraft is just a javascript source file (.js) -located in the craftbukkit/js-plugins directory. All .js files in this +located in the craftbukkit/plugins/scriptcraft/plugins directory. All .js files in this directory will be automatically loaded when the craftbukkit server starts. To get started writing your own mod, first take a look at some of the existing mods in the [homes][ho], [chat][ch], [arrows][ar] and diff --git a/docs/API-Reference.md b/docs/API-Reference.md index d707964..3bd5148 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -235,7 +235,6 @@ Walter Higgins * [utils.unwatchFile() function](#utilsunwatchfile-function) * [utils.array() function](#utilsarray-function) * [Drone Plugin](#drone-plugin) - * [TLDNR; (Just read this if you're impatient)](#tldnr-just-read-this-if-youre-impatient) * [Constructing a Drone Object](#constructing-a-drone-object) * [Drone.box() method](#dronebox-method) * [Drone.box0() method](#dronebox0-method) @@ -2928,20 +2927,6 @@ be chained together like so... var theDrone = new Drone(); theDrone.up().left().box(blocks.oak).down().fwd(3).cylinder0(blocks.lava,8); -### TLDNR; (Just read this if you're impatient) - -At the in-game command prompt type... - - /js box( blocks.oak ) - -... creates a single wooden block at the cross-hairs or player location - - /js box( blocks.oak ).right(2).box( blocks.wool.black, 4, 9, 1) - -... creates a single wooden block and a 2001 black obelisk that is 4 -wide x 9 tall x 1 long in size. If you want to see what else -ScriptCraft's Drone can do, read on... - ### Constructing a Drone Object Drones can be created in any of the following ways... @@ -3491,10 +3476,13 @@ Use this method to add new methods (which also become chainable global functions #### Parameters - * name - The name of the new method e.g. 'pyramid' + * name - The name of the new method e.g. 'pyramid'. * function - The method body. -#### Example +Alternatively if you provide just a function as a parameter, then the function name will be used as the new method name. For example the following two approaches are both valid. + + +#### Example 1 Using name and function as parameters // submitted by [edonaldson][edonaldson] Drone.extend('pyramid', function( block,height) { @@ -3505,6 +3493,16 @@ Use this method to add new methods (which also become chainable global functions return this.move('pyramid'); }); +#### Example 2 Using just a named function as a parameter + + Drone.extend(function pyramid( block,height) { + this.chkpt('pyramid'); + for ( var i = height; i > 0; i -= 2) { + this.box(block, i, 1, i).up().right().fwd(); + } + return this.move('pyramid'); + }); + Once the method is defined (it can be defined in a new pyramid.js file) it can be used like so... var d = new Drone(); diff --git a/src/main/js/lib/scriptcraft.js b/src/main/js/lib/scriptcraft.js index d901f73..7d68738 100644 --- a/src/main/js/lib/scriptcraft.js +++ b/src/main/js/lib/scriptcraft.js @@ -718,8 +718,8 @@ function __onEnable ( __engine, __plugin, __script ) legacyExists = true; - console.warn('Legacy ScriptCraft directory %s was found. This directory is no longer used.', - legacyDirs[i].canonicalPath); + console.warn('Legacy ScriptCraft directory %s was found. This directory is no longer used.',legacyDirs[i].canonicalPath); + console.warn('Please put plugins in the plugins/scriptcraft/plugins directory'); } } if ( legacyExists ) { diff --git a/src/main/js/plugins/drone/drone.js b/src/main/js/plugins/drone/drone.js index bd04253..2568d61 100644 --- a/src/main/js/plugins/drone/drone.js +++ b/src/main/js/plugins/drone/drone.js @@ -19,20 +19,6 @@ be chained together like so... var theDrone = new Drone(); theDrone.up().left().box(blocks.oak).down().fwd(3).cylinder0(blocks.lava,8); -### TLDNR; (Just read this if you're impatient) - -At the in-game command prompt type... - - /js box( blocks.oak ) - -... creates a single wooden block at the cross-hairs or player location - - /js box( blocks.oak ).right(2).box( blocks.wool.black, 4, 9, 1) - -... creates a single wooden block and a 2001 black obelisk that is 4 -wide x 9 tall x 1 long in size. If you want to see what else -ScriptCraft's Drone can do, read on... - ### Constructing a Drone Object Drones can be created in any of the following ways... From a2b0cda3993daf4f022893a05d6d460605f05dfa Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Wed, 28 May 2014 22:40:38 +0100 Subject: [PATCH 185/456] fixes issue #139 --- src/main/js/plugins/drone/drone.js | 110 +++++++++++++++-------------- 1 file changed, 58 insertions(+), 52 deletions(-) diff --git a/src/main/js/plugins/drone/drone.js b/src/main/js/plugins/drone/drone.js index 2568d61..d3b22f7 100644 --- a/src/main/js/plugins/drone/drone.js +++ b/src/main/js/plugins/drone/drone.js @@ -654,6 +654,7 @@ var putBlock = function( x, y, z, blockId, metadata, world ) { var block = world.getBlockAt( x, y, z ); if ( block.typeId != blockId || block.data != metadata ) { block.setTypeIdAndData( blockId, metadata, false ); + block.data = metadata; } }; @@ -1580,12 +1581,18 @@ var _cylinder1 = function( block,radius,height,exactParams ) { }; var _paste = function( name, immediate ) { - - if ( !immediate ) { - getQueue(this).push(function(){ _paste(name, true);}); +/* if ( !immediate ) { + var clone = Drone.clone(this); + getQueue(this).push(this.paste.bind(clone, name, true) ); return; } +*/ + var ccContent = Drone.clipBoard[name]; + if (ccContent == undefined){ + console.warn('Nothing called ' + name + ' in clipboard!'); + return; + } var srcBlocks = ccContent.blocks; var srcDir = ccContent.dir; // direction player was facing when copied. var dirOffset = (4 + (this.dir - srcDir ) ) %4; @@ -1597,9 +1604,8 @@ var _paste = function( name, immediate ) var d = srcBlocks[ww][hh].length; _traverse[that.dir].depth(that,d,function( dd ) { var b = srcBlocks[ww][hh][dd]; - var bm = that._getBlockIdAndMeta(b ); - var cb = bm[0]; - var md = bm[1]; + var cb = b.type + var md = b.data; // // need to adjust blocks which face a direction // @@ -1607,59 +1613,59 @@ var _paste = function( name, immediate ) // // doors // - case 64: // wood - case 71: // iron - // top half of door doesn't need to change - if ( md < 8 ) { - md = (md + dirOffset ) % 4; - } - break; - // + case 64: // wood + case 71: // iron + // top half of door doesn't need to change + if ( md < 8 ) { + md = (md + dirOffset ) % 4; + } + break; + // // stairs // - case 53: // oak - case 67: // cobblestone - case 108: // red brick - case 109: // stone brick - case 114: // nether brick - case 128: // sandstone - case 134: // spruce - case 135: // birch - case 136: // junglewood - var dir = md & 0x3; - var a = Drone.PLAYER_STAIRS_FACING; - var len = a.length; - for ( var c=0;c < len;c++ ) { - if ( a[c] == dir ) { - break; + case 53: // oak + case 67: // cobblestone + case 108: // red brick + case 109: // stone brick + case 114: // nether brick + case 128: // sandstone + case 134: // spruce + case 135: // birch + case 136: // junglewood + var dir = md & 0x3; + var a = Drone.PLAYER_STAIRS_FACING; + var len = a.length; + for ( var c=0;c < len;c++ ) { + if ( a[c] == dir ) { + break; + } } - } - c = (c + dirOffset ) %4; - var newDir = a[c]; - md = (md >>2<<2 ) + newDir; - break; + c = (c + dirOffset ) %4; + var newDir = a[c]; + md = (md >>2<<2 ) + newDir; + break; // // signs , ladders etc // - case 23: // dispenser - case 54: // chest - case 61: // furnace - case 62: // burning furnace - case 65: // ladder - case 68: // wall sign - var a = Drone.PLAYER_SIGN_FACING; - var len = a.length; - for ( var c=0;c < len;c++ ) { - if ( a[c] == md ) { - break; + case 23: // dispenser + case 54: // chest + case 61: // furnace + case 62: // burning furnace + case 65: // ladder + case 68: // wall sign + var a = Drone.PLAYER_SIGN_FACING; + var len = a.length; + for ( var c=0;c < len;c++ ) { + if ( a[c] == md ) { + break; + } } + c = (c + dirOffset ) %4; + var newDir = a[c]; + md = newDir; + break; } - c = (c + dirOffset ) %4; - var newDir = a[c]; - md = newDir; - break; - } - putBlock(that.x,that.y,that.z,cb,md,that.world ); + putBlock(that.x,that.y,that.z,cb,md,that.world ); } ); } ); } ); @@ -1809,7 +1815,7 @@ var _copy = function( name, w, h, d ) { ccContent[ww].push([] ); _traverse[that.dir].depth(that,d,function( dd ) { var b = that.world.getBlockAt(that.x,that.y,that.z ); - ccContent[ww][hh][dd] = b; + ccContent[ww][hh][dd] = {type:b.getTypeId(), data:b.data}; } ); } ); } ); From 20519d88db14006090c6a58848a7be7785821e24 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Thu, 29 May 2014 22:10:16 +0100 Subject: [PATCH 186/456] fix tab completion for /jsp command --- docs/release-notes.md | 6 ++++++ src/main/js/lib/tabcomplete-jsp.js | 6 +----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 378b472..e456288 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,3 +1,9 @@ +# 2014 05 29 +Fix tab completion for /jsp command so that it conforms with tab completion norms in minecraft. +/jsp ice completes to /jsp icecream +Hitting TAB again has no effect. Player must type space then hit TAB to get list of flavors. +This is consistent with how MC treats other commands for tab completion. + # 2014 05 19 Improved Tab Completion to work with Java Enums too. diff --git a/src/main/js/lib/tabcomplete-jsp.js b/src/main/js/lib/tabcomplete-jsp.js index 3ed61cb..d3cd1f1 100644 --- a/src/main/js/lib/tabcomplete-jsp.js +++ b/src/main/js/lib/tabcomplete-jsp.js @@ -13,11 +13,7 @@ var __onTabCompleteJSP = function( result, cmdSender, pluginCmd, cmdAlias, cmdAr if ( cmd ) { opts = cmd.options; len = opts.length; - if ( cmdArgs.length == 1 ) { - for ( i = 0; i < len; i++ ) { - result.add( opts[i] ); - } - } else { + if ( cmdArgs.length > 1 ) { // partial e.g. /jsp chat_color dar for ( i = 0; i < len; i++ ) { if ( opts[i].indexOf( cmdArgs[1] ) == 0 ) { From 553bec363bf3ac6ffb26fdf72c0efb2f182b0019 Mon Sep 17 00:00:00 2001 From: TennysonHolloway Date: Thu, 5 Jun 2014 15:55:19 -0700 Subject: [PATCH 187/456] Fix Nashorn error: has no such function getHandlerList. --- src/main/js/lib/events.js | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/main/js/lib/events.js b/src/main/js/lib/events.js index 4278269..3ab3f7c 100644 --- a/src/main/js/lib/events.js +++ b/src/main/js/lib/events.js @@ -108,21 +108,20 @@ exports.on = function( } else { priority = bkEventPriority[priority.toUpperCase()]; } - if ( typeof eventType == 'string' ) { - /* - Nashorn doesn't support bracket notation for accessing packages. - E.g. java.net will work but java['net'] won't. - - https://bugs.openjdk.java.net/browse/JDK-8031715 - */ - if ( typeof Java != 'undefined' ) { - // nashorn environment - eventType = Java.type( bkEventPackage + eventType ); - } else { + if ( typeof Java != 'undefined' ) { + //Nashorn doesn't like when getHandlerList is in a superclass of your event + //so to avoid this problem, call getHandlerList using java.lang.reflect + //methods + handlerList = java.lang.Class.forName(bkEventPackage + '' + eventType) + .getMethod("getHandlerList").invoke(null); + //rhino environment doesn't have this issue. + } else if ( typeof eventType == 'string' ) { eventType = eval( bkEventPackage + eventType ); - } + handlerList = eventType.getHandlerList( ); + } else { + handlerList = eventType.getHandlerList( ); } - handlerList = eventType.getHandlerList( ); + var result = { }; eventExecutor = new bkEventExecutor( { From ede823d62cf7475697171249fb479a9d0ba737b5 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Fri, 6 Jun 2014 18:17:30 +0100 Subject: [PATCH 188/456] fixed bug in persistence module --- docs/release-notes.md | 3 +++ nbproject/ide-targets.xml | 14 -------------- src/main/js/lib/persistence.js | 2 +- 3 files changed, 4 insertions(+), 15 deletions(-) delete mode 100644 nbproject/ide-targets.xml diff --git a/docs/release-notes.md b/docs/release-notes.md index e456288..0b550f0 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,3 +1,6 @@ +# 2014 05 31 +Fix bug in persistence module. Private load function wasn't returning result of scload. + # 2014 05 29 Fix tab completion for /jsp command so that it conforms with tab completion norms in minecraft. /jsp ice completes to /jsp icecream diff --git a/nbproject/ide-targets.xml b/nbproject/ide-targets.xml deleted file mode 100644 index efb4aa5..0000000 --- a/nbproject/ide-targets.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - Starting Bukkit with ScriptCraft - - - - - - - diff --git a/src/main/js/lib/persistence.js b/src/main/js/lib/persistence.js index 1b82231..fae3526 100644 --- a/src/main/js/lib/persistence.js +++ b/src/main/js/lib/persistence.js @@ -4,7 +4,7 @@ var _dataDir = null, module.exports = function( rootDir, $ ) { var _load = function( name ) { - $.scload( _dataDir.canonicalPath + '/' + name + '-store.json' ); + return $.scload( _dataDir.canonicalPath + '/' + name + '-store.json' ); }; var _save = function( name, data ) { $.scsave( data, _dataDir.canonicalPath + '/' + name + '-store.json' ); From 5754816017548fb45253f96acf1bb73998e6a745 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Fri, 6 Jun 2014 19:40:47 +0100 Subject: [PATCH 189/456] Fix BlockBreakEvent error for Nashorn and pre-Nashorn both both cases (eventType = 'block.BlockBreakEvent' and eventType = org.bukkit.event.block.BlockBreakEvent) --- src/main/js/lib/events.js | 45 +++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/src/main/js/lib/events.js b/src/main/js/lib/events.js index 3ab3f7c..c7e5d8e 100644 --- a/src/main/js/lib/events.js +++ b/src/main/js/lib/events.js @@ -92,6 +92,36 @@ var bkEventPriority = org.bukkit.event.EventPriority, bkRegisteredListener = org.bukkit.plugin.RegisteredListener, bkEventPackage = 'org.bukkit.event.'; +var nashorn = (typeof Java != 'undefined'); + +function getHandlerListForEventType( eventType ){ + var result = null; + var clazz = null; + if (!(typeof eventType == 'string')){ + // it's a fully qualified event class + if (nashorn) { + + //Nashorn doesn't like when getHandlerList is in a superclass of your event + //so to avoid this problem, call getHandlerList using java.lang.reflect + //methods + clazz = eventType['class']; + result = clazz.getMethod("getHandlerList").invoke(null); + + } else { + result = eventType.getHandlerList(); + } + } else { + // it's an event class name partial + if (nashorn) { + clazz = java.lang.Class.forName(bkEventPackage + '' + eventType); + result = clazz.getMethod("getHandlerList").invoke(null); + } else { + var eventType2 = eval( bkEventPackage + eventType ); + result = eventType2.getHandlerList(); + } + } + return result; +} exports.on = function( /* String or java Class */ eventType, @@ -108,20 +138,7 @@ exports.on = function( } else { priority = bkEventPriority[priority.toUpperCase()]; } - if ( typeof Java != 'undefined' ) { - //Nashorn doesn't like when getHandlerList is in a superclass of your event - //so to avoid this problem, call getHandlerList using java.lang.reflect - //methods - handlerList = java.lang.Class.forName(bkEventPackage + '' + eventType) - .getMethod("getHandlerList").invoke(null); - //rhino environment doesn't have this issue. - } else if ( typeof eventType == 'string' ) { - eventType = eval( bkEventPackage + eventType ); - handlerList = eventType.getHandlerList( ); - } else { - handlerList = eventType.getHandlerList( ); - } - + handlerList = getHandlerListForEventType (eventType); var result = { }; eventExecutor = new bkEventExecutor( { From c01ce603c56adabe8307ece7aaf7ee1e8e299a3b Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 7 Jun 2014 21:50:23 +0100 Subject: [PATCH 190/456] Fix a doozy of a bug in #nashorn - engine.eval('(' + jsonContainingArray + ')' ) does not return same result as JSON.parse( jsonContainingArray ) --- src/main/js/lib/persistence.js | 10 +++++--- src/main/js/lib/scriptcraft.js | 45 +++++++++++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/src/main/js/lib/persistence.js b/src/main/js/lib/persistence.js index fae3526..36eabb4 100644 --- a/src/main/js/lib/persistence.js +++ b/src/main/js/lib/persistence.js @@ -4,10 +4,12 @@ var _dataDir = null, module.exports = function( rootDir, $ ) { var _load = function( name ) { - return $.scload( _dataDir.canonicalPath + '/' + name + '-store.json' ); + var result = $.scloadJSON( _dataDir.canonicalPath + '/' + name + '-store.json' ); + return result; }; - var _save = function( name, data ) { - $.scsave( data, _dataDir.canonicalPath + '/' + name + '-store.json' ); + + var _save = function( name, objToSave ) { + $.scsave( objToSave, _dataDir.canonicalPath + '/' + name + '-store.json' ); }; _dataDir = new java.io.File( rootDir, 'data' ); @@ -23,7 +25,7 @@ module.exports = function( rootDir, $ ) { } if ( !write ) { dataFromFile = _load( name ); - if ( dataFromFile ) { + if ( typeof dataFromFile != 'undefined') { for ( i in dataFromFile ) { data[i] = dataFromFile[i]; } diff --git a/src/main/js/lib/scriptcraft.js b/src/main/js/lib/scriptcraft.js index 7d68738..68cdc8c 100644 --- a/src/main/js/lib/scriptcraft.js +++ b/src/main/js/lib/scriptcraft.js @@ -443,14 +443,15 @@ function __onEnable ( __engine, __plugin, __script ) /* Save a javascript object to a file (saves using JSON notation) */ - var _save = function( object, filename ) { + var _save = function( objToSave, filename ) { var objectToStr = null, f, out; try { - objectToStr = JSON.stringify( object, null, 2 ); + objectToStr = JSON.stringify( objToSave, null, 2 ); + } catch( e ) { - print( 'ERROR: ' + e.getMessage() + ' while saving ' + filename ); + console.error( 'ERROR: ' + e.getMessage() + ' while saving ' + filename ); return; } f = (filename instanceof File) ? filename : new File(filename); @@ -466,6 +467,43 @@ function __onEnable ( __engine, __plugin, __script ) return __engine.eval( str ); }; } + + var _loadJSON = function ( filename ){ + var result = null, + file = filename, + r, + reader, + br, + contents; + + if ( !( filename instanceof File ) ) { + file = new File(filename); + } + var canonizedFilename = _canonize( file ); + + if ( file.exists() ) { + reader = new FileReader( file ); + br = new BufferedReader( reader ); + contents = ''; + try { + while ( (r = br.readLine()) !== null ) { + contents += r + '\n'; + } + result = JSON.parse(contents); + } catch ( e ) { + logger.severe( 'Error evaluating ' + canonizedFilename + ', ' + e ); + } + finally { + try { + reader.close(); + } catch ( re ) { + // fail silently on reader close error + } + } + } + return result; + + }; /* Load the contents of the file and evaluate as javascript */ @@ -570,6 +608,7 @@ function __onEnable ( __engine, __plugin, __script ) global.alert = _echo; global.scload = _load; global.scsave = _save; + global.scloadJSON = _loadJSON; var configRequire = _load( jsPluginsRootDirName + '/lib/require.js', true ); /* From 521e35ffe84e6a441243563cbe7c85d6a460ba33 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 14 Jun 2014 15:36:56 +0100 Subject: [PATCH 191/456] fixes issue #140 --- src/generateEventsHelper.js | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/generateEventsHelper.js b/src/generateEventsHelper.js index 370a8df..e08d9c5 100644 --- a/src/generateEventsHelper.js +++ b/src/generateEventsHelper.js @@ -4,6 +4,7 @@ var File = java.io.File, out = java.lang.System.out, err = java.lang.System.err, Modifier = java.lang.reflect.Modifier, + clz, ZipInputStream = java.util.zip.ZipInputStream, zis = new ZipInputStream(new FileInputStream('./target/minecraft/craftbukkit.jar')), entry = null; @@ -18,19 +19,17 @@ var content = [ '', '### Usage', '', - ' events.blockBreak(function(evt){ ', - ' evt.player.sendMessage("You broke a block!"); ', + ' events.blockBreak( function( event ) { ', + ' event.player.sendMessage(\'You broke a block!\'); ', ' });', '', '... which is just a shorter and less error-prone way of writing ...', '', - ' events.on("block.BlockBreakEvent",function(evt){ ', - ' evt.player.sendMessage("You broke a block!");', + ' events.on(\'block.BlockBreakEvent\',function( event ) { ', + ' event.player.sendMessage(\'You broke a block!\');', ' });', '', - 'The crucial difference is that the events module now has functions for each ', - 'of the built-in events. The functions are accessible via tab-completion so will help ', - 'beginning programmers to explore the events at the server console window.', + 'The crucial difference is that the events module now has functions for each of the built-in events. The functions are accessible via TAB-completion so will help beginning programmers to explore the events at the server console window.', '', '***/' ]; @@ -41,14 +40,18 @@ while ( ( entry = zis.nextEntry) != null) { var name = '' + entry.name; if (name.match(/org\/bukkit\/event\/.+Event\.class$/)){ name = name.replace(/\//g,'.').replace('.class',''); - var clz = java.lang.Class.forName(name); + try { + clz = java.lang.Class.forName(name); + }catch ( e) { + clz = engine.eval(name); + } var isAbstract = Modifier.isAbstract(clz.getModifiers()); if ( isAbstract ) { continue; } var parts = name.split('.'); var shortName = name.replace('org.bukkit.event.',''); - var fname = parts.reverse().shift().replace(/^(.)/,function(a){ return a.toLowerCase()}).replace(/Event$/,''); + var fname = parts.reverse().shift().replace(/^(.)/,function(a){ return a.toLowerCase();}).replace(/Event$/,''); var comment = [ '/*********************', From 965ba76a628822f6ee5b9b7b650f5acb2488ab66 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 14 Jun 2014 15:37:34 +0100 Subject: [PATCH 192/456] Fix problem with change to bukkit xml doc change --- build/bukkit-to-url.xsl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/bukkit-to-url.xsl b/build/bukkit-to-url.xsl index bb48b3c..9235de5 100644 --- a/build/bukkit-to-url.xsl +++ b/build/bukkit-to-url.xsl @@ -4,10 +4,10 @@ - http://dl.bukkit.org + - \ No newline at end of file + From 4d97452b158478b3751c9db844e1d43d9909f4b0 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 14 Jun 2014 15:38:01 +0100 Subject: [PATCH 193/456] Added new bukkit.playerNames() function. --- src/main/js/lib/bukkit.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/js/lib/bukkit.js b/src/main/js/lib/bukkit.js index bbbf532..42c407d 100644 --- a/src/main/js/lib/bukkit.js +++ b/src/main/js/lib/bukkit.js @@ -12,6 +12,13 @@ var bukkit = { } return result; }, + playerNames: function(){ + var result = []; + for (var i = 0; i < server.onlinePlayers.length; i++){ + result.push(''+ server.onlinePlayers[i].name); + } + return result; + }, worlds: function(){ var result = []; var lWorlds = server.worlds; From 918ef23773b310dca3b3d21969a63760ad72887b Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 14 Jun 2014 15:38:41 +0100 Subject: [PATCH 194/456] Support named function in lieu of command name as first argument --- src/main/js/lib/command.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/js/lib/command.js b/src/main/js/lib/command.js index 6f2ae12..2ba6a4d 100644 --- a/src/main/js/lib/command.js +++ b/src/main/js/lib/command.js @@ -42,6 +42,14 @@ var executeCmd = function( args, player ) { define a new JSP command. */ var defineCmd = function( name, func, options, intercepts ) { + + if ( typeof name == 'function'){ + intercepts = options; + options = func; + func = name; + name = func.name; + } + if ( typeof options == 'undefined' ) { options = []; } From 4df746a2d89525f49ffdda27fd93c52c8371ac22 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 14 Jun 2014 15:39:17 +0100 Subject: [PATCH 195/456] line-wrap markdown comments --- src/main/js/lib/scriptcraft.js | 94 ++++++++++------------------------ 1 file changed, 28 insertions(+), 66 deletions(-) diff --git a/src/main/js/lib/scriptcraft.js b/src/main/js/lib/scriptcraft.js index 68cdc8c..9b14295 100644 --- a/src/main/js/lib/scriptcraft.js +++ b/src/main/js/lib/scriptcraft.js @@ -123,7 +123,7 @@ As of December 24 2013, the `scriptcraft/plugins` directory has the following su * 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. + * alias - The alias plugin/module - for creating custom aliases for commonly-used commands. * home - The home module - for setting homes and visiting other homes. ## Global variables @@ -137,10 +137,7 @@ The ScriptCraft JavaPlugin object. 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... +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) @@ -169,9 +166,7 @@ ScripCraft provides some global functions which can be used by all plugins/modul ### 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). +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). #### Example @@ -183,19 +178,13 @@ e.g. `alert('Hello World')`. #### Notes -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. +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) ### 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. +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. #### Parameters @@ -303,48 +292,35 @@ ScriptCraft Plugin][anatomy]. ### command() function -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. +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. #### Parameters - * 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... + * commandFunction: The named javascript function which will be invoked when the command is invoked by a player. The name of the function will be used as the command name so name this function accordingly. 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]). + * 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]). - * 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. + * 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. #### Example -See chat/colors.js or alias/alias.js or homes/homes.js for examples of -how to use the `command()` function. + // javascript code + function boo( params, sender) { + sender.sendMessage( params[0] ); + } + command( boo ); + + # in-game execution + /jsp boo Hi! + > Hi! + +See chat/colors.js or alias/alias.js or homes/homes.js for more examples of how to use the `command()` function. ### setTimeout() function -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. +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. If Node.js supports setTimeout() then it's probably good for ScriptCraft to support it too. @@ -368,16 +344,9 @@ 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. +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. +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 @@ -387,9 +356,7 @@ 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 ... +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. @@ -402,14 +369,9 @@ See [issue #69][issue69] for more information. ### 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). +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). -This function provides a way for ScriptCraft modules to do any -required cleanup/housekeeping just prior to the ScriptCraft Plugin -unloading. +This function provides a way for ScriptCraft modules to do any required cleanup/housekeeping just prior to the ScriptCraft Plugin unloading. ***/ From c9257b3038263e114b939c72c9fc6815a0af9d68 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 14 Jun 2014 15:39:47 +0100 Subject: [PATCH 196/456] Support callback function for objects. --- src/main/js/lib/tabcomplete-jsp.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/js/lib/tabcomplete-jsp.js b/src/main/js/lib/tabcomplete-jsp.js index d3cd1f1..c73918b 100644 --- a/src/main/js/lib/tabcomplete-jsp.js +++ b/src/main/js/lib/tabcomplete-jsp.js @@ -11,7 +11,11 @@ var __onTabCompleteJSP = function( result, cmdSender, pluginCmd, cmdAlias, cmdAr i; cmd = _commands[cmdInput]; if ( cmd ) { - opts = cmd.options; + if (typeof cmd.options === 'function'){ + opts = cmd.options(); + } else { + opts = cmd.options; + } len = opts.length; if ( cmdArgs.length > 1 ) { // partial e.g. /jsp chat_color dar From bbcdb48bef7cf363b4c37858124645d698baa37c Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 14 Jun 2014 15:40:19 +0100 Subject: [PATCH 197/456] line-wrap markdown comments --- src/main/js/plugins/drone/drone.js | 180 +++++++---------------------- 1 file changed, 41 insertions(+), 139 deletions(-) diff --git a/src/main/js/plugins/drone/drone.js b/src/main/js/plugins/drone/drone.js index d3b22f7..c41cc38 100644 --- a/src/main/js/plugins/drone/drone.js +++ b/src/main/js/plugins/drone/drone.js @@ -13,8 +13,7 @@ The Drone is a convenience class for building. It can be used for... 1. Building 2. Copying and Pasting -It uses a fluent interface which means all of the Drone's methods return `this` and can -be chained together like so... +It uses a fluent interface which means all of the Drone's methods return `this` and can be chained together like so... var theDrone = new Drone(); theDrone.up().left().box(blocks.oak).down().fwd(3).cylinder0(blocks.lava,8); @@ -27,11 +26,7 @@ Drones can be created in any of the following ways... var d = box( blocks.oak ) - ... creates a 1x1x1 wooden block at the cross-hairs or player's location and returns a Drone - object. This might look odd (if you're familiar with Java's Object-dot-method syntax) but all - of the Drone class's methods are also global functions that return new Drone objects. - This is short-hand for creating drones and is useful for playing around with Drones at the in-game - command prompt. It's shorter than typing ... + ... creates a 1x1x1 wooden block at the cross-hairs or player's location and returns a Drone object. This might look odd (if you're familiar with Java's Object-dot-method syntax) but all of the Drone class's methods are also global functions that return new Drone objects. This is short-hand for creating drones and is useful for playing around with Drones at the in-game command prompt. It's shorter than typing ... var d = new Drone().box( blocks.oak ) @@ -54,12 +49,7 @@ Drones can be created in any of the following ways... d = new Drone() - ...will create a new Drone. If the cross-hairs are pointing at a - block at the time then, that block's location becomes the drone's - starting point. If the cross-hairs are _not_ pointing at a block, - then the drone's starting location will be 2 blocks directly in - front of the player. TIP: Building always happens right and front - of the drone's position... + ...will create a new Drone. If the cross-hairs are pointing at a block at the time then, that block's location becomes the drone's starting point. If the cross-hairs are _not_ pointing at a block, then the drone's starting location will be 2 blocks directly in front of the player. TIP: Building always happens right and front of the drone's position... Plan View: @@ -68,12 +58,7 @@ Drones can be created in any of the following ways... | D----> - For convenience you can use a _corner stone_ to begin building. - The corner stone should be located just above ground level. If - the cross-hair is point at or into ground level when you create a - new Drone(), then building begins at that point. You can get - around this by pointing at a 'corner stone' just above ground - level or alternatively use the following statement... + For convenience you can use a _corner stone_ to begin building. The corner stone should be located just above ground level. If the cross-hair is point at or into ground level when you create a new Drone(), then building begins at that point. You can get around this by pointing at a 'corner stone' just above ground level or alternatively use the following statement... d = new Drone().up(); @@ -85,26 +70,15 @@ Drones can be created in any of the following ways... d = new Drone(x,y,z,direction,world); - This will create a new Drone at the location you specified using - x, y, z In minecraft, the X axis runs west to east and the Z axis runs - north to south. The direction parameter says what direction you want - the drone to face: 0 = east, 1 = south, 2 = west, 3 = north. If the - direction parameter is omitted, the player's direction is used - instead. - - Both the `direction` and `world` parameters are optional. + This will create a new Drone at the location you specified using x, y, z In minecraft, the X axis runs west to east and the Z axis runs north to south. The direction parameter says what direction you want the drone to face: 0 = east, 1 = south, 2 = west, 3 = north. If the direction parameter is omitted, the player's direction is used instead. Both the `direction` and `world` parameters are optional. 4. Create a new Drone based on a Bukkit Location object... d = new Drone(location); - This is useful when you want to create a drone at a given - `org.bukkit.Location` . The `Location` class is used throughout - the bukkit API. For example, if you want to create a drone when a - block is broken at the block's location you would do so like - this... + This is useful when you want to create a drone at a given `org.bukkit.Location` . The `Location` class is used throughout the bukkit API. For example, if you want to create a drone when a block is broken at the block's location you would do so like this... - events.blockBreak( function( event) { + events.blockBreak( function( event ) { var location = event.block.location; var drone = new Drone(location); // do more stuff with the drone here... @@ -126,13 +100,10 @@ the box() method is a convenience method for building things. (For the more perf #### parameters - * b - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. - Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` + * b - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` * w (optional - default 1) - the width of the structure * h (optional - default 1) - the height of the structure - * d (optional - default 1) - the depth of the structure - NB this is - not how deep underground the structure lies - this is how far - away (depth of field) from the drone the structure will extend. + * d (optional - default 1) - the depth of the structure - NB this is not how deep underground the structure lies - this is how far away (depth of field) from the drone the structure will extend. #### Example @@ -153,8 +124,7 @@ Another convenience method - this one creates 4 walls with no floor or ceiling. #### Parameters - * block - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. - Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` + * block - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` * width (optional - default 1) - the width of the structure * height (optional - default 1) - the height of the structure * length (optional - default 1) - the length of the structure - how far @@ -170,8 +140,7 @@ To create a stone building with the insided hollowed out 7 wide by 3 tall by 6 l ### Drone.boxa() method -Construct a cuboid using an array of blocks. As the drone moves first along the width axis, -then the height (y axis) then the length, each block is picked from the array and placed. +Construct a cuboid using an array of blocks. As the drone moves first along the width axis, then the height (y axis) then the length, each block is picked from the array and placed. #### Parameters @@ -193,8 +162,7 @@ Construct a rainbow-colored road 100 blocks long... ### Drone Movement -Drones can move freely in minecraft's 3-D world. You control the -Drone's movement using any of the following methods.. +Drones can move freely in minecraft's 3-D world. You control the Drone's movement using any of the following methods.. * up() * down() @@ -204,16 +172,9 @@ Drone's movement using any of the following methods.. * back() * turn() -... Each of these methods takes a single optional parameter -`numBlocks` - the number of blocks to move in the given direction. If -no parameter is given, the default is 1. +... Each of these methods takes a single optional parameter `numBlocks` - the number of blocks to move in the given direction. If no parameter is given, the default is 1. -to change direction use the `turn()` method which also takes a single -optional parameter (numTurns) - the number of 90 degree turns to make. -Turns are always clock-wise. If the drone is facing north, then -drone.turn() will make the turn face east. If the drone is facing east -then drone.turn(2) will make the drone turn twice so that it is facing -west. +To change direction use the `turn()` method which also takes a single optional parameter (numTurns) - the number of 90 degree turns to make. Turns are always clock-wise. If the drone is facing north, then drone.turn() will make the turn face east. If the drone is facing east then drone.turn(2) will make the drone turn twice so that it is facing west. ### Drone Positional Info @@ -221,20 +182,14 @@ west. ### Drone Markers -Markers are useful when your Drone has to do a lot of work. You can -set a check-point and return to the check-point using the move() -method. If your drone is about to undertake a lot of work - -e.g. building a road, skyscraper or forest you should set a -check-point before doing so if you want your drone to return to its -current location. +Markers are useful when your Drone has to do a lot of work. You can set a check-point and return to the check-point using the move() method. If your drone is about to undertake a lot of work - e.g. building a road, skyscraper or forest you should set a check-point before doing so if you want your drone to return to its current location. A 'start' checkpoint is automatically created when the Drone is first created. Markers are created and returned to using the followng two methods... * chkpt - Saves the drone's current location so it can be returned to later. - * move - moves the drone to a saved location. Alternatively you can provide an - org.bukkit.Location object or x,y,z and direction parameters. + * move - moves the drone to a saved location. Alternatively you can provide an org.bukkit.Location object or x,y,z and direction parameters. #### Parameters @@ -260,8 +215,7 @@ Creates a prism. This is useful for roofs on houses. #### Parameters - * block - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. - Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` + * block - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` * width - the width of the prism * length - the length of the prism (will be 2 time its height) @@ -281,8 +235,7 @@ A convenience method for building cylinders. Building begins radius blocks to th #### Parameters - * block - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. - Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` + * block - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` * radius * height @@ -308,8 +261,7 @@ To create a hollow cylinder of Iron 7 blocks in radius and 1 block high... ### Drone.arc() method -The arc() method can be used to create 1 or more 90 degree arcs in the horizontal or vertical planes. -This method is called by cylinder() and cylinder0() and the sphere() and sphere0() methods. +The arc() method can be used to create 1 or more 90 degree arcs in the horizontal or vertical planes. This method is called by cylinder() and cylinder0() and the sphere() and sphere0() methods. #### Parameters @@ -319,20 +271,10 @@ arc() takes a single parameter - an object with the following named properties.. * blockType - The type of block to use - this is the block Id only (no meta). See [Data Values][dv]. * meta - The metadata value. See [Data Values][dv]. * orientation (default: 'horizontal' ) - the orientation of the arc - can be 'vertical' or 'horizontal'. - * stack (default: 1 ) - the height or length of the arc (depending on - the orientation - if orientation is horizontal then this parameter - refers to the height, if vertical then it refers to the length ). - * strokeWidth (default: 1 ) - the width of the stroke (how many - blocks) - if drawing nested arcs it's usually a good idea to set - strokeWidth to at least 2 so that there are no gaps between each - arc. The arc method uses a [bresenham algorithm][bres] to plot - points along the circumference. + * stack (default: 1 ) - the height or length of the arc (depending on the orientation - if orientation is horizontal then this parameter refers to the height, if vertical then it refers to the length ). + * strokeWidth (default: 1 ) - the width of the stroke (how many blocks) - if drawing nested arcs it's usually a good idea to set strokeWidth to at least 2 so that there are no gaps between each arc. The arc method uses a [bresenham algorithm][bres] to plot points along the circumference. * fill - If true (or present) then the arc will be filled in. - * quadrants (default: - `{topleft:true,topright:true,bottomleft:true,bottomright:true}` - An - object with 4 properties indicating which of the 4 quadrants of a - circle to draw. If the quadrants property is absent then all 4 - quadrants are drawn. + * quadrants (default: `{topleft:true,topright:true,bottomleft:true,bottomright:true}` - An object with 4 properties indicating which of the 4 quadrants of a circle to draw. If the quadrants property is absent then all 4 quadrants are drawn. #### Examples @@ -435,16 +377,11 @@ To create 4 trees in a row, point the cross-hairs at the ground then type `/js ` up( ).oak( ).right(8 ).spruce( ).right(8 ).birch( ).right(8 ).jungle( ); -Trees won't always generate unless the conditions are right. You -should use the tree methods when the drone is directly above the -ground. Trees will usually grow if the drone's current location is -occupied by Air and is directly above an area of grass (That is why -the `up( )` method is called first). +Trees won't always generate unless the conditions are right. You should use the tree methods when the drone is directly above the ground. Trees will usually grow if the drone's current location is occupied by Air and is directly above an area of grass (That is why the `up( )` method is called first). ![tree example](img/treeex1.png) -None of the tree methods require parameters. Tree methods will only be successful -if the tree is placed on grass in a setting where trees can grow. +None of the tree methods require parameters. Tree methods will only be successful if the tree is placed on grass in a setting where trees can grow. ### Drone.garden() method @@ -465,8 +402,7 @@ To create a garden 10 blocks wide by 5 blocks long... ### Drone.rand() method -rand takes either an array (if each blockid has the same chance of occurring) -or an object where each property is a blockid and the value is it's weight (an integer) +rand takes either an array (if each blockid has the same chance of occurring) or an object where each property is a blockid and the value is it's weight (an integer) #### Example @@ -491,8 +427,7 @@ A drone can be used to copy and paste areas of the game world. ### Drone.copy() method -Copies an area so it can be pasted elsewhere. The name can be used for -pasting the copied area elsewhere... +Copies an area so it can be pasted elsewhere. The name can be used for pasting the copied area elsewhere... #### Parameters @@ -511,9 +446,7 @@ Pastes a copied area to the current location. #### Example -To copy a 10x5x10 area (using the drone's coordinates as the starting -point) into memory. the copied area can be referenced using the name -'somethingCool'. The drone moves 12 blocks right then pastes the copy. +To copy a 10x5x10 area (using the drone's coordinates as the starting point) into memory. the copied area can be referenced using the name 'somethingCool'. The drone moves 12 blocks right then pastes the copy. drone.copy('somethingCool',10,5,10 ) .right(12 ) @@ -521,8 +454,7 @@ point) into memory. the copied area can be referenced using the name ### Chaining -All of the Drone methods return a Drone object, which means methods -can be 'chained' together so instead of writing this... +All of the Drone methods return a Drone object, which means methods can be 'chained' together so instead of writing this... drone = new Drone(); drone.fwd(3); @@ -536,17 +468,11 @@ can be 'chained' together so instead of writing this... var drone = new Drone().fwd(3).left(2).box(2).up().box(2).down(); -... since each Drone method is also a global function that constructs -a drone if none is supplied, you can shorten even further to just... +... since each Drone method is also a global function that constructs a drone if none is supplied, you can shorten even further to just... fwd(3).left(2).box(2).up().box(2).down() -The Drone object uses a [Fluent Interface][fl] to make ScriptCraft -scripts more concise and easier to write and read. Minecraft's -in-game command prompt is limited to about 80 characters so chaining -drone commands together means more can be done before hitting the -command prompt limit. For complex building you should save your -commands in a new script file and load it using /js load() +The Drone object uses a [Fluent Interface][fl] to make ScriptCraft scripts more concise and easier to write and read. Minecraft's in-game command prompt is limited to about 80 characters so chaining drone commands together means more can be done before hitting the command prompt limit. For complex building you should save your commands in a new script file and load it using /js load() [fl]: http://en.wikipedia.org/wiki/Fluent_interface @@ -559,8 +485,7 @@ commands in a new script file and load it using /js load() ### Extending Drone -The Drone object can be easily extended - new buidling recipes/blueprints can be added and can -become part of a Drone's chain using the *static* method `Drone.extend`. +The Drone object can be easily extended - new buidling recipes/blueprints can be added and can become part of a Drone's chain using the *static* method `Drone.extend`. ### Drone.extend() static method @@ -573,7 +498,6 @@ Use this method to add new methods (which also become chainable global functions Alternatively if you provide just a function as a parameter, then the function name will be used as the new method name. For example the following two approaches are both valid. - #### Example 1 Using name and function as parameters // submitted by [edonaldson][edonaldson] @@ -587,13 +511,14 @@ Alternatively if you provide just a function as a parameter, then the function n #### Example 2 Using just a named function as a parameter - Drone.extend(function pyramid( block,height) { + function pyramid( block,height) { this.chkpt('pyramid'); for ( var i = height; i > 0; i -= 2) { this.box(block, i, 1, i).up().right().fwd(); } return this.move('pyramid'); - }); + } + Drone.extend( pyramid ); Once the method is defined (it can be defined in a new pyramid.js file) it can be used like so... @@ -619,9 +544,7 @@ An array which can be used when constructing stairs facing in the Drone's direct #### Drone.PLAYER_SIGN_FACING -An array which can be used when placing signs so they face in a given direction. -This is used internally by the Drone.sign() method. It should also be used for placing -any of the following blocks... +An array which can be used when placing signs so they face in a given direction. This is used internally by the Drone.sign() method. It should also be used for placing any of the following blocks... * chest * ladder @@ -830,46 +753,27 @@ Say you want to do the same thing over and over. You have a couple of options... d = new Drone(); for ( var i =0;i < 4; i++) { d.cottage().right(8); } -While this will fit on the in-game prompt, it's awkward. You need to -declare a new Drone object first, then write a for loop to create the -4 cottages. It's also error prone, even the `for` loop is too much -syntax for what should really be simple. +While this will fit on the in-game prompt, it's awkward. You need to declare a new Drone object first, then write a for loop to create the 4 cottages. It's also error prone, even the `for` loop is too much syntax for what should really be simple. * You can use a while loop... d = new Drone(); var i=4; while (i--) { d.cottage().right(8); } -... which is slightly shorter but still too much syntax. Each of the -above statements is fine for creating a 1-dimensional array of -structures. But what if you want to create a 2-dimensional or -3-dimensional array of structures? Enter the `times()` method. +... which is slightly shorter but still too much syntax. Each of the above statements is fine for creating a 1-dimensional array of structures. But what if you want to create a 2-dimensional or 3-dimensional array of structures? Enter the `times()` method. -The `times()` method lets you repeat commands in a chain any number of -times. So to create 4 cottages in a row you would use the following -statement... +The `times()` method lets you repeat commands in a chain any number of times. So to create 4 cottages in a row you would use the following statement... cottage().right(8).times(4); -...which will build a cottage, then move right 8 blocks, then do it -again 4 times over so that at the end you will have 4 cottages in a -row. What's more the `times()` method can be called more than once in -a chain. So if you wanted to create a *grid* of 20 houses ( 4 x 5 ), -you would do so using the following statement... +...which will build a cottage, then move right 8 blocks, then do it again 4 times over so that at the end you will have 4 cottages in a row. What's more the `times()` method can be called more than once in a chain. So if you wanted to create a *grid* of 20 houses ( 4 x 5 ), you would do so using the following statement... cottage().right(8).times(4).fwd(8).left(32).times(5); ... breaking it down... - 1. The first 3 calls in the chain ( `cottage()`, `right(8)`, - `times(4)` ) build a single row of 4 cottages. + 1. The first 3 calls in the chain ( `cottage()`, `right(8)`, `times(4)` ) build a single row of 4 cottages. - 2. The last 3 calls in the chain ( `fwd(8)`, `left(32)`, `times(5)` ) - move the drone forward 8 then left 32 blocks (4 x 8) to return to - the original x coordinate, then everything in the chain is - repeated again 5 times so that in the end, we have a grid of 20 - cottages, 4 x 5. Normally this would require a nested loop but - the `times()` method does away with the need for loops when - repeating builds. + 2. The last 3 calls in the chain ( `fwd(8)`, `left(32)`, `times(5)` ) move the drone forward 8 then left 32 blocks (4 x 8) to return to the original x coordinate, then everything in the chain is repeated again 5 times so that in the end, we have a grid of 20 cottages, 4 x 5. Normally this would require a nested loop but the `times()` method does away with the need for loops when repeating builds. Another example: This statement creates a row of trees 2 by 3 ... @@ -1914,5 +1818,3 @@ Drone.extend('cylinder', _cylinder1 ); // wph 20130130 - make this a method - extensions can use it. // Drone.prototype._getBlockIdAndMeta = _getBlockIdAndMeta; - - From e0acaed700b1e0a1cd7bc036f076bf2ec3b609e5 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 14 Jun 2014 15:45:44 +0100 Subject: [PATCH 198/456] add doc comment for command tab completion using a function --- docs/API-Reference.md | 284 +++++++++++------------------------------- docs/release-notes.md | 5 +- 2 files changed, 78 insertions(+), 211 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 3bd5148..18e0e2e 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -434,7 +434,7 @@ As of December 24 2013, the `scriptcraft/plugins` directory has the following su * 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. + * alias - The alias plugin/module - for creating custom aliases for commonly-used commands. * home - The home module - for setting homes and visiting other homes. ## Global variables @@ -448,10 +448,7 @@ The ScriptCraft JavaPlugin object. 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... +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) @@ -480,9 +477,7 @@ ScripCraft provides some global functions which can be used by all plugins/modul ### 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). +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). #### Example @@ -494,19 +489,13 @@ e.g. `alert('Hello World')`. #### Notes -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. +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) ### 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. +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. #### Parameters @@ -614,48 +603,35 @@ ScriptCraft Plugin][anatomy]. ### command() function -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. +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. #### Parameters - * 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... + * commandFunction: The named javascript function which will be invoked when the command is invoked by a player. The name of the function will be used as the command name so name this function accordingly. 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]). + * 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]). - * 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. + * 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. #### Example -See chat/colors.js or alias/alias.js or homes/homes.js for examples of -how to use the `command()` function. + // javascript code + function boo( params, sender) { + sender.sendMessage( params[0] ); + } + command( boo ); + + # in-game execution + /jsp boo Hi! + > Hi! + +See chat/colors.js or alias/alias.js or homes/homes.js for more examples of how to use the `command()` function. ### setTimeout() function -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. +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. If Node.js supports setTimeout() then it's probably good for ScriptCraft to support it too. @@ -679,16 +655,9 @@ 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. +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. +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 @@ -698,9 +667,7 @@ 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 ... +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. @@ -713,14 +680,9 @@ See [issue #69][issue69] for more information. ### 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). +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). -This function provides a way for ScriptCraft modules to do any -required cleanup/housekeeping just prior to the ScriptCraft Plugin -unloading. +This function provides a way for ScriptCraft modules to do any required cleanup/housekeeping just prior to the ScriptCraft Plugin unloading. ## require - Node.js-style module loading in ScriptCraft @@ -938,19 +900,17 @@ to choose from any of the approx. 160 different event types to listen to. ### Usage - events.blockBreak(function(evt){ - evt.player.sendMessage("You broke a block!"); + events.blockBreak( function( event ) { + event.player.sendMessage('You broke a block!'); }); ... which is just a shorter and less error-prone way of writing ... - events.on("block.BlockBreakEvent",function(evt){ - evt.player.sendMessage("You broke a block!"); + events.on('block.BlockBreakEvent',function( event ) { + event.player.sendMessage('You broke a block!'); }); -The crucial difference is that the events module now has functions for each -of the built-in events. The functions are accessible via tab-completion so will help -beginning programmers to explore the events at the server console window. +The crucial difference is that the events module now has functions for each of the built-in events. The functions are accessible via TAB-completion so will help beginning programmers to explore the events at the server console window. ### events.worldUnload() @@ -2921,8 +2881,7 @@ The Drone is a convenience class for building. It can be used for... 1. Building 2. Copying and Pasting -It uses a fluent interface which means all of the Drone's methods return `this` and can -be chained together like so... +It uses a fluent interface which means all of the Drone's methods return `this` and can be chained together like so... var theDrone = new Drone(); theDrone.up().left().box(blocks.oak).down().fwd(3).cylinder0(blocks.lava,8); @@ -2935,11 +2894,7 @@ Drones can be created in any of the following ways... var d = box( blocks.oak ) - ... creates a 1x1x1 wooden block at the cross-hairs or player's location and returns a Drone - object. This might look odd (if you're familiar with Java's Object-dot-method syntax) but all - of the Drone class's methods are also global functions that return new Drone objects. - This is short-hand for creating drones and is useful for playing around with Drones at the in-game - command prompt. It's shorter than typing ... + ... creates a 1x1x1 wooden block at the cross-hairs or player's location and returns a Drone object. This might look odd (if you're familiar with Java's Object-dot-method syntax) but all of the Drone class's methods are also global functions that return new Drone objects. This is short-hand for creating drones and is useful for playing around with Drones at the in-game command prompt. It's shorter than typing ... var d = new Drone().box( blocks.oak ) @@ -2962,12 +2917,7 @@ Drones can be created in any of the following ways... d = new Drone() - ...will create a new Drone. If the cross-hairs are pointing at a - block at the time then, that block's location becomes the drone's - starting point. If the cross-hairs are _not_ pointing at a block, - then the drone's starting location will be 2 blocks directly in - front of the player. TIP: Building always happens right and front - of the drone's position... + ...will create a new Drone. If the cross-hairs are pointing at a block at the time then, that block's location becomes the drone's starting point. If the cross-hairs are _not_ pointing at a block, then the drone's starting location will be 2 blocks directly in front of the player. TIP: Building always happens right and front of the drone's position... Plan View: @@ -2976,12 +2926,7 @@ Drones can be created in any of the following ways... | D----> - For convenience you can use a _corner stone_ to begin building. - The corner stone should be located just above ground level. If - the cross-hair is point at or into ground level when you create a - new Drone(), then building begins at that point. You can get - around this by pointing at a 'corner stone' just above ground - level or alternatively use the following statement... + For convenience you can use a _corner stone_ to begin building. The corner stone should be located just above ground level. If the cross-hair is point at or into ground level when you create a new Drone(), then building begins at that point. You can get around this by pointing at a 'corner stone' just above ground level or alternatively use the following statement... d = new Drone().up(); @@ -2993,26 +2938,15 @@ Drones can be created in any of the following ways... d = new Drone(x,y,z,direction,world); - This will create a new Drone at the location you specified using - x, y, z In minecraft, the X axis runs west to east and the Z axis runs - north to south. The direction parameter says what direction you want - the drone to face: 0 = east, 1 = south, 2 = west, 3 = north. If the - direction parameter is omitted, the player's direction is used - instead. - - Both the `direction` and `world` parameters are optional. + This will create a new Drone at the location you specified using x, y, z In minecraft, the X axis runs west to east and the Z axis runs north to south. The direction parameter says what direction you want the drone to face: 0 = east, 1 = south, 2 = west, 3 = north. If the direction parameter is omitted, the player's direction is used instead. Both the `direction` and `world` parameters are optional. 4. Create a new Drone based on a Bukkit Location object... d = new Drone(location); - This is useful when you want to create a drone at a given - `org.bukkit.Location` . The `Location` class is used throughout - the bukkit API. For example, if you want to create a drone when a - block is broken at the block's location you would do so like - this... + This is useful when you want to create a drone at a given `org.bukkit.Location` . The `Location` class is used throughout the bukkit API. For example, if you want to create a drone when a block is broken at the block's location you would do so like this... - events.blockBreak( function( event) { + events.blockBreak( function( event ) { var location = event.block.location; var drone = new Drone(location); // do more stuff with the drone here... @@ -3034,13 +2968,10 @@ the box() method is a convenience method for building things. (For the more perf #### parameters - * b - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. - Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` + * b - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` * w (optional - default 1) - the width of the structure * h (optional - default 1) - the height of the structure - * d (optional - default 1) - the depth of the structure - NB this is - not how deep underground the structure lies - this is how far - away (depth of field) from the drone the structure will extend. + * d (optional - default 1) - the depth of the structure - NB this is not how deep underground the structure lies - this is how far away (depth of field) from the drone the structure will extend. #### Example @@ -3061,8 +2992,7 @@ Another convenience method - this one creates 4 walls with no floor or ceiling. #### Parameters - * block - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. - Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` + * block - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` * width (optional - default 1) - the width of the structure * height (optional - default 1) - the height of the structure * length (optional - default 1) - the length of the structure - how far @@ -3078,8 +3008,7 @@ To create a stone building with the insided hollowed out 7 wide by 3 tall by 6 l ### Drone.boxa() method -Construct a cuboid using an array of blocks. As the drone moves first along the width axis, -then the height (y axis) then the length, each block is picked from the array and placed. +Construct a cuboid using an array of blocks. As the drone moves first along the width axis, then the height (y axis) then the length, each block is picked from the array and placed. #### Parameters @@ -3101,8 +3030,7 @@ Construct a rainbow-colored road 100 blocks long... ### Drone Movement -Drones can move freely in minecraft's 3-D world. You control the -Drone's movement using any of the following methods.. +Drones can move freely in minecraft's 3-D world. You control the Drone's movement using any of the following methods.. * up() * down() @@ -3112,16 +3040,9 @@ Drone's movement using any of the following methods.. * back() * turn() -... Each of these methods takes a single optional parameter -`numBlocks` - the number of blocks to move in the given direction. If -no parameter is given, the default is 1. +... Each of these methods takes a single optional parameter `numBlocks` - the number of blocks to move in the given direction. If no parameter is given, the default is 1. -to change direction use the `turn()` method which also takes a single -optional parameter (numTurns) - the number of 90 degree turns to make. -Turns are always clock-wise. If the drone is facing north, then -drone.turn() will make the turn face east. If the drone is facing east -then drone.turn(2) will make the drone turn twice so that it is facing -west. +To change direction use the `turn()` method which also takes a single optional parameter (numTurns) - the number of 90 degree turns to make. Turns are always clock-wise. If the drone is facing north, then drone.turn() will make the turn face east. If the drone is facing east then drone.turn(2) will make the drone turn twice so that it is facing west. ### Drone Positional Info @@ -3129,20 +3050,14 @@ west. ### Drone Markers -Markers are useful when your Drone has to do a lot of work. You can -set a check-point and return to the check-point using the move() -method. If your drone is about to undertake a lot of work - -e.g. building a road, skyscraper or forest you should set a -check-point before doing so if you want your drone to return to its -current location. +Markers are useful when your Drone has to do a lot of work. You can set a check-point and return to the check-point using the move() method. If your drone is about to undertake a lot of work - e.g. building a road, skyscraper or forest you should set a check-point before doing so if you want your drone to return to its current location. A 'start' checkpoint is automatically created when the Drone is first created. Markers are created and returned to using the followng two methods... * chkpt - Saves the drone's current location so it can be returned to later. - * move - moves the drone to a saved location. Alternatively you can provide an - org.bukkit.Location object or x,y,z and direction parameters. + * move - moves the drone to a saved location. Alternatively you can provide an org.bukkit.Location object or x,y,z and direction parameters. #### Parameters @@ -3168,8 +3083,7 @@ Creates a prism. This is useful for roofs on houses. #### Parameters - * block - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. - Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` + * block - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` * width - the width of the prism * length - the length of the prism (will be 2 time its height) @@ -3189,8 +3103,7 @@ A convenience method for building cylinders. Building begins radius blocks to th #### Parameters - * block - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. - Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` + * block - the block id - e.g. 6 for an oak sapling or '6:2' for a birch sapling. Alternatively you can use any one of the `blocks` values e.g. `blocks.sapling.birch` * radius * height @@ -3216,8 +3129,7 @@ To create a hollow cylinder of Iron 7 blocks in radius and 1 block high... ### Drone.arc() method -The arc() method can be used to create 1 or more 90 degree arcs in the horizontal or vertical planes. -This method is called by cylinder() and cylinder0() and the sphere() and sphere0() methods. +The arc() method can be used to create 1 or more 90 degree arcs in the horizontal or vertical planes. This method is called by cylinder() and cylinder0() and the sphere() and sphere0() methods. #### Parameters @@ -3227,20 +3139,10 @@ arc() takes a single parameter - an object with the following named properties.. * blockType - The type of block to use - this is the block Id only (no meta). See [Data Values][dv]. * meta - The metadata value. See [Data Values][dv]. * orientation (default: 'horizontal' ) - the orientation of the arc - can be 'vertical' or 'horizontal'. - * stack (default: 1 ) - the height or length of the arc (depending on - the orientation - if orientation is horizontal then this parameter - refers to the height, if vertical then it refers to the length ). - * strokeWidth (default: 1 ) - the width of the stroke (how many - blocks) - if drawing nested arcs it's usually a good idea to set - strokeWidth to at least 2 so that there are no gaps between each - arc. The arc method uses a [bresenham algorithm][bres] to plot - points along the circumference. + * stack (default: 1 ) - the height or length of the arc (depending on the orientation - if orientation is horizontal then this parameter refers to the height, if vertical then it refers to the length ). + * strokeWidth (default: 1 ) - the width of the stroke (how many blocks) - if drawing nested arcs it's usually a good idea to set strokeWidth to at least 2 so that there are no gaps between each arc. The arc method uses a [bresenham algorithm][bres] to plot points along the circumference. * fill - If true (or present) then the arc will be filled in. - * quadrants (default: - `{topleft:true,topright:true,bottomleft:true,bottomright:true}` - An - object with 4 properties indicating which of the 4 quadrants of a - circle to draw. If the quadrants property is absent then all 4 - quadrants are drawn. + * quadrants (default: `{topleft:true,topright:true,bottomleft:true,bottomright:true}` - An object with 4 properties indicating which of the 4 quadrants of a circle to draw. If the quadrants property is absent then all 4 quadrants are drawn. #### Examples @@ -3343,16 +3245,11 @@ To create 4 trees in a row, point the cross-hairs at the ground then type `/js ` up( ).oak( ).right(8 ).spruce( ).right(8 ).birch( ).right(8 ).jungle( ); -Trees won't always generate unless the conditions are right. You -should use the tree methods when the drone is directly above the -ground. Trees will usually grow if the drone's current location is -occupied by Air and is directly above an area of grass (That is why -the `up( )` method is called first). +Trees won't always generate unless the conditions are right. You should use the tree methods when the drone is directly above the ground. Trees will usually grow if the drone's current location is occupied by Air and is directly above an area of grass (That is why the `up( )` method is called first). ![tree example](img/treeex1.png) -None of the tree methods require parameters. Tree methods will only be successful -if the tree is placed on grass in a setting where trees can grow. +None of the tree methods require parameters. Tree methods will only be successful if the tree is placed on grass in a setting where trees can grow. ### Drone.garden() method @@ -3373,8 +3270,7 @@ To create a garden 10 blocks wide by 5 blocks long... ### Drone.rand() method -rand takes either an array (if each blockid has the same chance of occurring) -or an object where each property is a blockid and the value is it's weight (an integer) +rand takes either an array (if each blockid has the same chance of occurring) or an object where each property is a blockid and the value is it's weight (an integer) #### Example @@ -3399,8 +3295,7 @@ A drone can be used to copy and paste areas of the game world. ### Drone.copy() method -Copies an area so it can be pasted elsewhere. The name can be used for -pasting the copied area elsewhere... +Copies an area so it can be pasted elsewhere. The name can be used for pasting the copied area elsewhere... #### Parameters @@ -3419,9 +3314,7 @@ Pastes a copied area to the current location. #### Example -To copy a 10x5x10 area (using the drone's coordinates as the starting -point) into memory. the copied area can be referenced using the name -'somethingCool'. The drone moves 12 blocks right then pastes the copy. +To copy a 10x5x10 area (using the drone's coordinates as the starting point) into memory. the copied area can be referenced using the name 'somethingCool'. The drone moves 12 blocks right then pastes the copy. drone.copy('somethingCool',10,5,10 ) .right(12 ) @@ -3429,8 +3322,7 @@ point) into memory. the copied area can be referenced using the name ### Chaining -All of the Drone methods return a Drone object, which means methods -can be 'chained' together so instead of writing this... +All of the Drone methods return a Drone object, which means methods can be 'chained' together so instead of writing this... drone = new Drone(); drone.fwd(3); @@ -3444,17 +3336,11 @@ can be 'chained' together so instead of writing this... var drone = new Drone().fwd(3).left(2).box(2).up().box(2).down(); -... since each Drone method is also a global function that constructs -a drone if none is supplied, you can shorten even further to just... +... since each Drone method is also a global function that constructs a drone if none is supplied, you can shorten even further to just... fwd(3).left(2).box(2).up().box(2).down() -The Drone object uses a [Fluent Interface][fl] to make ScriptCraft -scripts more concise and easier to write and read. Minecraft's -in-game command prompt is limited to about 80 characters so chaining -drone commands together means more can be done before hitting the -command prompt limit. For complex building you should save your -commands in a new script file and load it using /js load() +The Drone object uses a [Fluent Interface][fl] to make ScriptCraft scripts more concise and easier to write and read. Minecraft's in-game command prompt is limited to about 80 characters so chaining drone commands together means more can be done before hitting the command prompt limit. For complex building you should save your commands in a new script file and load it using /js load() [fl]: http://en.wikipedia.org/wiki/Fluent_interface @@ -3467,8 +3353,7 @@ commands in a new script file and load it using /js load() ### Extending Drone -The Drone object can be easily extended - new buidling recipes/blueprints can be added and can -become part of a Drone's chain using the *static* method `Drone.extend`. +The Drone object can be easily extended - new buidling recipes/blueprints can be added and can become part of a Drone's chain using the *static* method `Drone.extend`. ### Drone.extend() static method @@ -3481,7 +3366,6 @@ Use this method to add new methods (which also become chainable global functions Alternatively if you provide just a function as a parameter, then the function name will be used as the new method name. For example the following two approaches are both valid. - #### Example 1 Using name and function as parameters // submitted by [edonaldson][edonaldson] @@ -3495,13 +3379,14 @@ Alternatively if you provide just a function as a parameter, then the function n #### Example 2 Using just a named function as a parameter - Drone.extend(function pyramid( block,height) { + function pyramid( block,height) { this.chkpt('pyramid'); for ( var i = height; i > 0; i -= 2) { this.box(block, i, 1, i).up().right().fwd(); } return this.move('pyramid'); - }); + } + Drone.extend( pyramid ); Once the method is defined (it can be defined in a new pyramid.js file) it can be used like so... @@ -3527,9 +3412,7 @@ An array which can be used when constructing stairs facing in the Drone's direct #### Drone.PLAYER_SIGN_FACING -An array which can be used when placing signs so they face in a given direction. -This is used internally by the Drone.sign() method. It should also be used for placing -any of the following blocks... +An array which can be used when placing signs so they face in a given direction. This is used internally by the Drone.sign() method. It should also be used for placing any of the following blocks... * chest * ladder @@ -3562,46 +3445,27 @@ Say you want to do the same thing over and over. You have a couple of options... d = new Drone(); for ( var i =0;i < 4; i++) { d.cottage().right(8); } -While this will fit on the in-game prompt, it's awkward. You need to -declare a new Drone object first, then write a for loop to create the -4 cottages. It's also error prone, even the `for` loop is too much -syntax for what should really be simple. +While this will fit on the in-game prompt, it's awkward. You need to declare a new Drone object first, then write a for loop to create the 4 cottages. It's also error prone, even the `for` loop is too much syntax for what should really be simple. * You can use a while loop... d = new Drone(); var i=4; while (i--) { d.cottage().right(8); } -... which is slightly shorter but still too much syntax. Each of the -above statements is fine for creating a 1-dimensional array of -structures. But what if you want to create a 2-dimensional or -3-dimensional array of structures? Enter the `times()` method. +... which is slightly shorter but still too much syntax. Each of the above statements is fine for creating a 1-dimensional array of structures. But what if you want to create a 2-dimensional or 3-dimensional array of structures? Enter the `times()` method. -The `times()` method lets you repeat commands in a chain any number of -times. So to create 4 cottages in a row you would use the following -statement... +The `times()` method lets you repeat commands in a chain any number of times. So to create 4 cottages in a row you would use the following statement... cottage().right(8).times(4); -...which will build a cottage, then move right 8 blocks, then do it -again 4 times over so that at the end you will have 4 cottages in a -row. What's more the `times()` method can be called more than once in -a chain. So if you wanted to create a *grid* of 20 houses ( 4 x 5 ), -you would do so using the following statement... +...which will build a cottage, then move right 8 blocks, then do it again 4 times over so that at the end you will have 4 cottages in a row. What's more the `times()` method can be called more than once in a chain. So if you wanted to create a *grid* of 20 houses ( 4 x 5 ), you would do so using the following statement... cottage().right(8).times(4).fwd(8).left(32).times(5); ... breaking it down... - 1. The first 3 calls in the chain ( `cottage()`, `right(8)`, - `times(4)` ) build a single row of 4 cottages. + 1. The first 3 calls in the chain ( `cottage()`, `right(8)`, `times(4)` ) build a single row of 4 cottages. - 2. The last 3 calls in the chain ( `fwd(8)`, `left(32)`, `times(5)` ) - move the drone forward 8 then left 32 blocks (4 x 8) to return to - the original x coordinate, then everything in the chain is - repeated again 5 times so that in the end, we have a grid of 20 - cottages, 4 x 5. Normally this would require a nested loop but - the `times()` method does away with the need for loops when - repeating builds. + 2. The last 3 calls in the chain ( `fwd(8)`, `left(32)`, `times(5)` ) move the drone forward 8 then left 32 blocks (4 x 8) to return to the original x coordinate, then everything in the chain is repeated again 5 times so that in the end, we have a grid of 20 cottages, 4 x 5. Normally this would require a nested loop but the `times()` method does away with the need for loops when repeating builds. Another example: This statement creates a row of trees 2 by 3 ... diff --git a/docs/release-notes.md b/docs/release-notes.md index 0b550f0..b52021a 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,3 +1,7 @@ +# 2014 06 14 +Fix issue #140 - fails to build for JRE7 +Changed command() documentation to conform with new way of using (passing a named function) + # 2014 05 31 Fix bug in persistence module. Private load function wasn't returning result of scload. @@ -18,7 +22,6 @@ Turn off modality for conversations which are started via the 'input' module. # 2014 05 10 - Further simplification of events handling. The events.on() function can still be used but additional functions are now provided for each type of event. For example, to register a custom player-join event handler... From 8c3c7d67fec307fddcb9f86b62800d825f4c819e Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 14 Jun 2014 15:46:07 +0100 Subject: [PATCH 199/456] tab-completion using a callback --- src/main/js/lib/scriptcraft.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/js/lib/scriptcraft.js b/src/main/js/lib/scriptcraft.js index 9b14295..7369e6c 100644 --- a/src/main/js/lib/scriptcraft.js +++ b/src/main/js/lib/scriptcraft.js @@ -301,7 +301,7 @@ The `command()` function is used to expose javascript functions for use by non-o * 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]). - * 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. + * options (Array|Function - 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. If a function is supplied instead of an array then the function will be invoked at TAB-completion time and should return an array of strings. * intercepts (boolean - optional) : Indicates whether this command can intercept Tab-Completion of the `/jsp ` command - advanced usage - see alias/alias.js for example. #### Example @@ -316,6 +316,17 @@ The `command()` function is used to expose javascript functions for use by non-o /jsp boo Hi! > Hi! +To use a callback for options (TAB-Completion) ... + + + function boo( params, sender ) { + var receiver = server.getPlayer( params[0] ); + if ( receiver ){ + receiver.sendMessage( sender.name + ' says boo!'); + } + } + command( boo, bukkit.playerNames ); + See chat/colors.js or alias/alias.js or homes/homes.js for more examples of how to use the `command()` function. ### setTimeout() function From bded5e8bd5110673671f09cb4cd1c3f9a93d6913 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 14 Jun 2014 15:47:13 +0100 Subject: [PATCH 200/456] docs for tab-completion of jsp command using function --- docs/API-Reference.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 18e0e2e..5908b46 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -612,7 +612,7 @@ The `command()` function is used to expose javascript functions for use by non-o * 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]). - * 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. + * options (Array|Function - 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. If a function is supplied instead of an array then the function will be invoked at TAB-completion time and should return an array of strings. * intercepts (boolean - optional) : Indicates whether this command can intercept Tab-Completion of the `/jsp ` command - advanced usage - see alias/alias.js for example. #### Example @@ -627,6 +627,17 @@ The `command()` function is used to expose javascript functions for use by non-o /jsp boo Hi! > Hi! +To use a callback for options (TAB-Completion) ... + + + function boo( params, sender ) { + var receiver = server.getPlayer( params[0] ); + if ( receiver ){ + receiver.sendMessage( sender.name + ' says boo!'); + } + } + command( boo, bukkit.playerNames ); + See chat/colors.js or alias/alias.js or homes/homes.js for more examples of how to use the `command()` function. ### setTimeout() function From b480922b15876f44e2d2373ea93e3c21d628a6eb Mon Sep 17 00:00:00 2001 From: Tiago Freitas Date: Sat, 28 Jun 2014 13:32:55 +0100 Subject: [PATCH 201/456] - Classroom file watcher was not working as expected because lastModifiedTime of a directory is not updated when a file is changed inside it. - Added functions watchDir/unwatchDir which is responsible for watching all files and subdirectories changes - Callback is called once for each detected change - Changed classroom to check for the last "refresh" made to avoid multiple refreshes without changes - Changed refresh time to 3s because it is much more comfortable - I don't think this would be an issue because checking for lastModifiedTime should be very fast, perhaps even 3s is too much time - Tested in Windows --- src/main/js/modules/utils/utils.js | 135 ++++++++++++++++++++- src/main/js/plugins/classroom/classroom.js | 20 +-- 2 files changed, 145 insertions(+), 10 deletions(-) diff --git a/src/main/js/modules/utils/utils.js b/src/main/js/modules/utils/utils.js index a47f387..4308032 100644 --- a/src/main/js/modules/utils/utils.js +++ b/src/main/js/modules/utils/utils.js @@ -517,15 +517,67 @@ utils.watchFile( 'test.txt', function( file ) { ``` ***/ var filesWatched = {}; +var dirsWatched = {}; + exports.watchFile = function( file, callback ) { if ( typeof file == 'string' ) { file = new File(file); } + //console.log("Watching file " + file); filesWatched[file.canonicalPath] = { callback: callback, lastModified: file.lastModified() }; }; + +/************************************************************************ +### utils.watchDir() function + +Watches for changes to the given directory and calls the function provided +when the directory changes. It works by calling watchFile/watchDir for each +file/subdirectory. + +#### Parameters + + * Dir - the file to watch (can be a file or directory) + * Callback - The callback to invoke when the directory has changed. + The callback takes the changed file as a parameter. + For each change inside the directory the callback will also + be called. + +#### Example + +```javascript +var utils = require('utils'); +utils.watchDir( 'players/_ial', function( dir ) { + console.log( dir + ' has changed'); +}); +``` +***/ + +exports.watchDir = function( dir, callback ) { + if ( typeof dir == 'string' ) { + dir = new File(dir); + } + //console.log("Watching dir " + dir); + dirsWatched[dir.canonicalPath] = { + callback: callback, + lastModified: dir.lastModified() + }; + + var files = dir.listFiles(),file; + if ( !files ) { + return; + } + for ( var i = 0; i < files.length; i++ ) { + file = files[i]; + if (file.isDirectory( )) { + exports.watchDir(file,callback); + }else{ + exports.watchFile(file,callback); + } + } +}; /************************************************************************ ### utils.unwatchFile() function @@ -542,21 +594,98 @@ exports.unwatchFile = function( file, callback ) { if ( typeof file == 'string' ) { file = new File(file); } + //console.log("Unwatching file " + file); delete filesWatched[file.canonicalPath]; }; -function fileWatcher() { +/************************************************************************ +### utils.unwatchDir() function + +Removes a directory from the watch list and all files inside the directory +are also "unwatched" + +#### Example +```javascript +var utils = require('utils'); +utils.unwatchDir ('players/_ial'); +``` +Would cause also +```javascript +utils.unwatchFile (file); +``` +for each file inside directory (and unwatchDir for each directory inside it) + +***/ +exports.unwatchDir = function( dir, callback ) { + if ( typeof dir == 'string' ) { + dir = new File(dir); + } + //console.log("Unwatching dir " + dir); + delete dirsWatched[dir.canonicalPath]; + + var files = dir.listFiles(),file; + if ( !files ) { + return; + } + for ( var i = 0; i < files.length; i++ ) { + file = files[i]; + if (file.isDirectory( )) { + exports.unwatchDir(file,callback); + }else{ + exports.unwatchFile(file,callback); + } + } +}; + +function fileWatcher(calledCallbacks) { for (var file in filesWatched) { var fileObject = new File(file); var lm = fileObject.lastModified(); if ( lm != filesWatched[file].lastModified ) { + //console.log("Change found in " + file); filesWatched[file].lastModified = lm; filesWatched[file].callback(fileObject); + if (!fileObject.exists()) { + //console.log("File " + file + " was removed."); + exports.unwatchFile(file,filesWatched[file].callback); + } } } - setTimeout( fileWatcher, 5000 ); }; -setTimeout( fileWatcher, 5000 ); + + +//monitors directories for time change +//when a change is detected watchFiles are invoked for each of the files in directory +//and callback is called +function dirWatcher(calledCallbacks) { + for (var dir in dirsWatched) { + var dirObject = new File(dir); + var lm = dirObject.lastModified(); + var dw = dirsWatched[dir]; + if ( lm != dirsWatched[dir].lastModified ) { + //console.log("Change found in " + dir); + dirsWatched[dir].lastModified = lm; + dirsWatched[dir].callback(dirObject); + + exports.unwatchDir(dir, dw.callback); + //causes all files to be rewatched + if (dirObject.exists()) { + exports.watchDir(dir, dw.callback); + } else { + //console.log("Directory " + dir + " was removed."); + } + } + } +}; + +//guarantees that a callback is only called once for each change +function monitorDirAndFiles() { + fileWatcher (); + dirWatcher (); + setTimeout( monitorDirAndFiles, 3000 ); +}; + +setTimeout( monitorDirAndFiles, 3000 ); /************************************************************************** ### utils.array() function diff --git a/src/main/js/plugins/classroom/classroom.js b/src/main/js/plugins/classroom/classroom.js index fa4edfc..d0ebf2a 100644 --- a/src/main/js/plugins/classroom/classroom.js +++ b/src/main/js/plugins/classroom/classroom.js @@ -2,8 +2,8 @@ var utils = require('utils'), autoload = require('plugin').autoload, logger = __plugin.logger, foreach = utils.foreach, - watchFile = utils.watchFile, - unwatchFile = utils.unwatchFile, + watchDir = utils.watchDir, + unwatchDir = utils.unwatchDir, playersDir = __dirname + '/../../players/', serverAddress = utils.serverAddress(); @@ -104,9 +104,9 @@ function revokeScripting ( player ) { var playerName = '' + player.name; playerName = playerName.replace(/[^a-zA-Z0-9_\-]/g,''); var playerDir = new File( playersDir + playerName ); - unwatchFile( playerDir ); + unwatchDir( playerDir ); } - +exports.classroomAutoloadTime = 0; function grantScripting( player ) { console.log('Enabling scripting for player ' + player.name); var playerName = '' + player.name; @@ -117,9 +117,15 @@ function grantScripting( player ) { var playerContext = {}; autoload( playerContext, playerDir, logger, { cache: false }); global[playerName] = playerContext; - - watchFile( playerDir, function( changedDir ){ - autoload(playerContext, playerDir, logger, { cache: false }); + watchDir( playerDir, function( changedDir ){ + var currentTime = new java.util.Date().getTime(); + //this check is here because this callback might get called multiple times for the watch interval + //one call for the file change and another for directory change + //(this happens only in Linux because in Windows the folder lastModifiedTime is not changed) + if(currentTime-exports.classroomAutoloadTime>1000) { + autoload(playerContext, playerDir, logger, { cache: false }); + } + exports.classroomAutoloadTime = currentTime; }); /* From 5a900a16c830766537f35d6fbffd03210d5dfddc Mon Sep 17 00:00:00 2001 From: Tiago Freitas Date: Sat, 28 Jun 2014 14:02:14 +0100 Subject: [PATCH 202/456] last load time must be for each player --- src/main/js/plugins/classroom/classroom.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/js/plugins/classroom/classroom.js b/src/main/js/plugins/classroom/classroom.js index d0ebf2a..dd22cc8 100644 --- a/src/main/js/plugins/classroom/classroom.js +++ b/src/main/js/plugins/classroom/classroom.js @@ -106,7 +106,7 @@ function revokeScripting ( player ) { var playerDir = new File( playersDir + playerName ); unwatchDir( playerDir ); } -exports.classroomAutoloadTime = 0; +exports.classroomAutoloadTime = {}; function grantScripting( player ) { console.log('Enabling scripting for player ' + player.name); var playerName = '' + player.name; @@ -122,10 +122,10 @@ function grantScripting( player ) { //this check is here because this callback might get called multiple times for the watch interval //one call for the file change and another for directory change //(this happens only in Linux because in Windows the folder lastModifiedTime is not changed) - if(currentTime-exports.classroomAutoloadTime>1000) { + if(currentTime-exports.classroomAutoloadTime[playerName]>1000) { autoload(playerContext, playerDir, logger, { cache: false }); } - exports.classroomAutoloadTime = currentTime; + exports.classroomAutoloadTime[playerName] = currentTime; }); /* From cc9d57d4a49c3bb38955279c8e651d896abf72a8 Mon Sep 17 00:00:00 2001 From: Daniel Huhn Date: Sun, 29 Jun 2014 16:09:22 +0200 Subject: [PATCH 203/456] url is now propely set in the http.request module when using post --- src/main/js/modules/http/request.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/js/modules/http/request.js b/src/main/js/modules/http/request.js index 713d69b..59de291 100644 --- a/src/main/js/modules/http/request.js +++ b/src/main/js/modules/http/request.js @@ -70,6 +70,7 @@ exports.request = function( request, callback ) { url = request; requestMethod = 'GET'; }else{ + url = request.url; paramsAsString = paramsToString( request.params ); if ( request.method ) { requestMethod = request.method; From 0f467e616b8a59cf7b6f3cfad9dbc59c82108ca8 Mon Sep 17 00:00:00 2001 From: Daniel Huhn Date: Sun, 29 Jun 2014 16:22:37 +0200 Subject: [PATCH 204/456] replaced tabs with whitespaces --- src/main/js/modules/http/request.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/js/modules/http/request.js b/src/main/js/modules/http/request.js index 59de291..2ff7918 100644 --- a/src/main/js/modules/http/request.js +++ b/src/main/js/modules/http/request.js @@ -38,11 +38,11 @@ The following example illustrates how to use http.request to make a request to a var http = require('./http/request'); http.request( - { + { url: 'http://pixenate.com/pixenate/pxn8.pl', method: 'POST', params: {script: '[]'} - }, + }, function( responseCode, responseBody ) { var jsObj = eval('(' + responseBody + ')'); }); @@ -63,7 +63,7 @@ exports.request = function( request, callback ) { } return result; }; - + server.scheduler.runTaskAsynchronously( __plugin, function() { var url, paramsAsString, conn, requestMethod; if (typeof request === 'string'){ @@ -73,13 +73,13 @@ exports.request = function( request, callback ) { url = request.url; paramsAsString = paramsToString( request.params ); if ( request.method ) { - requestMethod = request.method; + requestMethod = request.method; } else { - requestMethod = 'GET'; + requestMethod = 'GET'; } if ( requestMethod == 'GET' && request.params ) { - // append each parameter to the URL - url = request.url + '?' + paramsAsString; + // append each parameter to the URL + url = request.url + '?' + paramsAsString; } } conn = new java.net.URL( url ).openConnection(); @@ -90,7 +90,7 @@ exports.request = function( request, callback ) { if ( conn.requestMethod == 'POST' ) { conn.doInput = true; // put each parameter in the outputstream - conn.setRequestProperty('Content-Type', 'application/x-www-form-urlencoded'); + conn.setRequestProperty('Content-Type', 'application/x-www-form-urlencoded'); conn.setRequestProperty('charset', 'utf-8'); conn.setRequestProperty('Content-Length', '' + paramsAsString.length); conn.useCaches =false ; From de122d8377b0418ca5bb68ecb62a49ad478f314e Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Sun, 13 Jul 2014 13:09:41 +0200 Subject: [PATCH 205/456] Typo/Bug fix in Anatomy doc sample code The sample code refers to `e.message` when `evt.message` was intended. This causes a run-time exception because `e` is undefined. --- docs/Anatomy-of-a-Plugin.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Anatomy-of-a-Plugin.md b/docs/Anatomy-of-a-Plugin.md index 281bf62..647a42b 100644 --- a/docs/Anatomy-of-a-Plugin.md +++ b/docs/Anatomy-of-a-Plugin.md @@ -50,7 +50,7 @@ chosen color... var player = evt.player; var playerChatColor = _store.players[ player.name ]; if ( playerChatColor ) { - evt.message = '§' + colorCodes[ playerChatColor ] + e.message; + evt.message = '§' + colorCodes[ playerChatColor ] + evt.message; } }); @@ -116,7 +116,7 @@ code is just a couple of lines of code but is a fully working plugin... var player = evt.player; var playerChatColor = _store.players[player.name]; if ( playerChatColor ) { - evt.message = '§' + colorCodes[playerChatColor] + e.message; + evt.message = '§' + colorCodes[playerChatColor] + evt.message; } }); command( 'chat_color', function( params, sender ) { From 519f20491c8724c806cadd367e6f047681d22e86 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 23 Aug 2014 13:40:50 +0100 Subject: [PATCH 206/456] update based on changes to watchDir() --- docs/API-Reference.md | 44 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 5908b46..0ac1e74 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -232,7 +232,9 @@ Walter Higgins * [utils.find() function](#utilsfind-function) * [utils.serverAddress() function](#utilsserveraddress-function) * [utils.watchFile() function](#utilswatchfile-function) + * [utils.watchDir() function](#utilswatchdir-function) * [utils.unwatchFile() function](#utilsunwatchfile-function) + * [utils.unwatchDir() function](#utilsunwatchdir-function) * [utils.array() function](#utilsarray-function) * [Drone Plugin](#drone-plugin) * [Constructing a Drone Object](#constructing-a-drone-object) @@ -2326,11 +2328,11 @@ The following example illustrates how to use http.request to make a request to a var http = require('./http/request'); http.request( - { + { url: 'http://pixenate.com/pixenate/pxn8.pl', method: 'POST', params: {script: '[]'} - }, + }, function( responseCode, responseBody ) { var jsObj = eval('(' + responseBody + ')'); }); @@ -2865,6 +2867,28 @@ utils.watchFile( 'test.txt', function( file ) { console.log( file + ' has changed'); }); ``` +### utils.watchDir() function + +Watches for changes to the given directory and calls the function provided +when the directory changes. It works by calling watchFile/watchDir for each +file/subdirectory. + +#### Parameters + + * Dir - the file to watch (can be a file or directory) + * Callback - The callback to invoke when the directory has changed. + The callback takes the changed file as a parameter. + For each change inside the directory the callback will also + be called. + +#### Example + +```javascript +var utils = require('utils'); +utils.watchDir( 'players/_ial', function( dir ) { + console.log( dir + ' has changed'); +}); +``` ### utils.unwatchFile() function Removes a file from the watch list. @@ -2875,6 +2899,22 @@ var utils = require('utils'); utils.unwatchFile( 'test.txt'); ``` +### utils.unwatchDir() function + +Removes a directory from the watch list and all files inside the directory +are also "unwatched" + +#### Example +```javascript +var utils = require('utils'); +utils.unwatchDir ('players/_ial'); +``` +Would cause also +```javascript +utils.unwatchFile (file); +``` +for each file inside directory (and unwatchDir for each directory inside it) + ### utils.array() function Converts Java collection objects to type Javascript array so they can avail of From 099991637ed6e68756d6a90f9b267a8a7fae1a04 Mon Sep 17 00:00:00 2001 From: Walter Higgins Date: Sat, 23 Aug 2014 14:15:32 +0100 Subject: [PATCH 207/456] Delete skyscraper-example.js --- .../drone/contrib/skyscraper-example.js | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 src/main/js/plugins/drone/contrib/skyscraper-example.js diff --git a/src/main/js/plugins/drone/contrib/skyscraper-example.js b/src/main/js/plugins/drone/contrib/skyscraper-example.js deleted file mode 100644 index 465aad5..0000000 --- a/src/main/js/plugins/drone/contrib/skyscraper-example.js +++ /dev/null @@ -1,18 +0,0 @@ -var Drone = require('../drone').Drone; -var blocks = require('blocks'); - -Drone.extend('skyscraper', function( floors ) { - var i = 0; - if ( typeof floors == 'undefined' ) { - floors = 10; - } - this.chkpt('skyscraper'); - for ( i = 0; i < floors; i++ ) { - this // w h d - .box( blocks.iron, 20, 1, 20) // iron floor - .up() // w h d - .box0(blocks.glass_pane, 20, 3, 20) // glass walls - .up(3); - } - return this.move('skyscraper'); -}); From e4abd679d2e607ad70a8a0df0a45384af25f6053 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 23 Aug 2014 14:19:04 +0100 Subject: [PATCH 208/456] rboxcall not used anywhere (ever?) --- src/main/js/plugins/drone/contrib/rboxcall.js | 34 ------------------- 1 file changed, 34 deletions(-) delete mode 100644 src/main/js/plugins/drone/contrib/rboxcall.js diff --git a/src/main/js/plugins/drone/contrib/rboxcall.js b/src/main/js/plugins/drone/contrib/rboxcall.js deleted file mode 100644 index 222f101..0000000 --- a/src/main/js/plugins/drone/contrib/rboxcall.js +++ /dev/null @@ -1,34 +0,0 @@ -var Drone = require('../drone').Drone; - -/** -* Iterates over each cube in a cubic region. For each cube has a chance to callback your -* function and provide a new drone to it. -* -* Parameters: -* callback - any function that accepts a drone as its first argument -* probability - chance to invoke your callback on each iteration -* width - width of the region -* height - (Optional) height of the region, defaults to width -* depth - (Optional) depth of the cube, defaults to width -*/ - -Drone.extend("rboxcall", function( callback, probability, width, height, depth ) { - this.chkpt('rboxcall-start'); - - for(var i = 0; i < width; ++i) { - this.move('rboxcall-start').right(i); - for(var j = 0; j < depth; ++j) { - this.move('rboxcall-start').right(i).fwd(j); - for(var k = 0; k < height; ++k) { - if(Math.random()*100 < probability) { - callback.call(null, new Drone(this.x, this.y, this.z)); - } - this.up(); - } - } - } - - this.move('rboxcall-start'); - - return this; -}); From 12ff59f2e540c1d7f59f28f91479e856603b0007 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 23 Aug 2014 16:43:45 +0100 Subject: [PATCH 209/456] Removing streamer because it's not used anywhere. --- src/main/js/plugins/drone/contrib/streamer.js | 31 ------------------- 1 file changed, 31 deletions(-) delete mode 100644 src/main/js/plugins/drone/contrib/streamer.js diff --git a/src/main/js/plugins/drone/contrib/streamer.js b/src/main/js/plugins/drone/contrib/streamer.js deleted file mode 100644 index 28c8d75..0000000 --- a/src/main/js/plugins/drone/contrib/streamer.js +++ /dev/null @@ -1,31 +0,0 @@ -var Drone = require('../drone').Drone; -/** -* Creates a stream of blocks in a given direction until it hits something other than air -* -* Parameters: -* block - blockId -* dir - "up", "down", "left", "right", "fwd", "back -* maxIterations - (Optional) maximum number of cubes to generate, defaults to 1000 -*/ -Drone.extend('streamer', function(block, dir, maxIterations) { - if (typeof maxIterations == 'undefined') - maxIterations = 1000; - - var usage = "Usage: streamer({block-type}, {direction: 'up', 'down', 'fwd', 'back', 'left', 'right'}, {maximum-iterations: default 1000})\nE.g.\n" + - "streamer(5, 'up', 200)"; - if (typeof dir == 'undefined'){ - throw new Error(usage); - } - if (typeof block == 'undefined') { - throw new Error(usage); - } - for ( var i = 0; i < maxIterations || 1000; ++i ) { - this.box(block); - this[dir].call(this); - var block = this.world.getBlockAt(this.x, this.y, this.z); - if ( block.typeId != 0 && block.data != 0) { - break; - } - } - return this; -}); From e874ba3ff6d4296f009af7002087b81bccb25afa Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 23 Aug 2014 16:44:36 +0100 Subject: [PATCH 210/456] Fixed a bug in boxa() drone method which caused chessboard() to fail (since move to async) --- src/main/js/plugins/drone/drone.js | 144 +++++++++++++++-------------- 1 file changed, 76 insertions(+), 68 deletions(-) diff --git a/src/main/js/plugins/drone/drone.js b/src/main/js/plugins/drone/drone.js index c41cc38..fdf1df7 100644 --- a/src/main/js/plugins/drone/drone.js +++ b/src/main/js/plugins/drone/drone.js @@ -689,9 +689,13 @@ Drone.processQueue = function(){ process = queues[i].shift(); if (process){ try { - process(); + process(); } catch( e ) { - console.log('Drone build error: %s', e); + console.log('Drone build error: %s', e); + if (process.name){ + console.log('while processing function ' + process.name); + } + } } } @@ -715,6 +719,9 @@ addUnloadHandler( function() { Drone.extend = function( name, func ) { if (arguments.length == 1){ func = name; + if ( !func.name ){ + throw 'A Drone extension function must have a name!'; + } name = func.name; } Drone.prototype[ '_' + name ] = func; @@ -808,12 +815,10 @@ Drone.prototype.times = function( numTimes, commands ) { }; Drone.prototype._checkpoints = {}; - -Drone.extend( 'chkpt', function( name ) { +Drone.extend(function chkpt( name ) { this._checkpoints[ name ] = { x:this.x, y:this.y, z:this.z, dir:this.dir }; -} ); - -Drone.extend( 'move', function( ) { +}); +Drone.extend(function move( ) { if ( arguments[0] instanceof bkLocation ) { this.x = arguments[0].x; this.y = arguments[0].y; @@ -841,9 +846,9 @@ Drone.extend( 'move', function( ) { this.x = arguments[0]; } } -} ); +}); -Drone.extend( 'turn', function ( n ) { +Drone.extend( function turn( n ) { if ( typeof n == 'undefined' ) { n = 1; } @@ -851,42 +856,42 @@ Drone.extend( 'turn', function ( n ) { this.dir %=4; } ); -Drone.extend( 'right', function( n ) { +Drone.extend( function right( n ) { if ( typeof n == 'undefined' ) { n = 1; } _movements[ this.dir ].right( this, n ); }); -Drone.extend( 'left', function( n ) { +Drone.extend( function left( n ) { if ( typeof n == 'undefined') { n = 1; } _movements[ this.dir ].left( this, n ); }); -Drone.extend( 'fwd', function( n ) { +Drone.extend( function fwd( n ) { if ( typeof n == 'undefined' ) { n = 1; } _movements[ this.dir ].fwd( this, n ); }); -Drone.extend( 'back', function( n ) { +Drone.extend( function back( n ) { if ( typeof n == 'undefined' ) { n = 1; } _movements[ this.dir ].back( this, n ); }); -Drone.extend( 'up', function( n ) { +Drone.extend( function up( n ) { if ( typeof n == 'undefined' ) { n = 1; } this.y+= n; }); -Drone.extend( 'down', function( n ) { +Drone.extend( function down( n ) { if ( typeof n == 'undefined' ) { n = 1; } @@ -901,7 +906,7 @@ Drone.prototype.getLocation = function( ) { // // building // -Drone.extend( 'sign', function( message, block ) { +Drone.extend( function sign( message, block ) { if ( message.constructor != Array ) { message = [message]; } @@ -955,16 +960,20 @@ function getAllQueues() { return result; } Drone.prototype.cuboida = function(/* Array */ blocks, w, h, d, overwrite, immediate ) { - - var len = blocks.length, + // + // wph 20140823 make a copy because don't want to modify array in background + // + var blocksForBuild = blocks.slice(); + var len = blocksForBuild.length, i = 0; if ( !immediate ) { for ( ; i < len; i++ ) { - blocks[i] = this._getBlockIdAndMeta( blocks[ i ] ); + blocksForBuild[i] = this._getBlockIdAndMeta( blocksForBuild[ i ] ); } var clone = Drone.clone(this); - getQueue(this).push(this.cuboida.bind(clone, blocks, w, h, d, overwrite, true) ); + var impl = this.cuboida.bind(clone, blocksForBuild, w, h, d, overwrite, true); + getQueue(this).push( function cuboida(){ impl(); }); return this; } if ( typeof overwrite == 'undefined' ) { @@ -982,14 +991,14 @@ Drone.prototype.cuboida = function(/* Array */ blocks, w, h, d, overwrite, immed var that = this; var dir = this.dir; var bi = 0; - _traverse[dir].depth( that, d, function( ) { - _traverseHeight( that, h, function( ) { - _traverse[dir].width( that, w, function( ) { + _traverse[dir].depth( that, d, function traverseDepthCallback( ) { + traverseHeight( that, h, function traverseHeightCallback( ) { + _traverse[dir].width( that, w, function traverseWidthCallback( ) { var block = that.world.getBlockAt( that.x, that.y, that.z ); - var properBlock = blocks[ bi % len ]; - if (overwrite || block.type.equals(bkMaterial.AIR) ) { + var properBlock = blocksForBuild[ bi % len ]; + if (overwrite || block.type.equals(bkMaterial.AIR) ) { block.setTypeIdAndData( properBlock[0], properBlock[1], false ); - } + } bi++; }); }); @@ -1036,7 +1045,8 @@ Drone.prototype.cuboidX = function( blockType, meta, w, h, d, immediate ) { if ( !immediate ) { var clone = Drone.clone(this); - getQueue(this).push(this.cuboidX.bind(clone, blockType, meta, w, h, d, true)); + var impl = this.cuboidX.bind(clone, blockType, meta, w, h, d, true); + getQueue(this).push(function cuboidX(){ impl(); }); return this; } var depthFunc = function( ) { @@ -1052,7 +1062,7 @@ Drone.prototype.cuboidX = function( blockType, meta, w, h, d, immediate ) { _traverse[dir].depth( that, d, depthFunc ); }; var widthFunc = function( ) { - _traverseHeight( that, h, heightFunc ); + traverseHeight( that, h, heightFunc ); }; _traverse[dir].width( that, w, widthFunc ); return this; @@ -1080,7 +1090,7 @@ Drone.prototype.cuboid0 = function( block, w, h, d ) { }; -Drone.extend( 'door', function( doorMaterial ) { +Drone.extend( function door( doorMaterial ) { if ( typeof doorMaterial == 'undefined' ) { doorMaterial = 64; // wood } else { @@ -1092,14 +1102,14 @@ Drone.extend( 'door', function( doorMaterial ) { .down( ); } ); -Drone.extend( 'door_iron', function( ) { +Drone.extend( function door_iron( ) { this.cuboidX( 71, this.dir ) .up( ) .cuboidX( 71, 8 ) .down( ); } ); -Drone.extend( 'door2' , function( doorMaterial ) { +Drone.extend( function door2( doorMaterial ) { if ( typeof doorMaterial == 'undefined' ) { doorMaterial = 64; } else { @@ -1111,7 +1121,7 @@ Drone.extend( 'door2' , function( doorMaterial ) { .cuboidX( doorMaterial, 9 ).down( ) .cuboidX( doorMaterial, this.dir ).left( ); } ); -Drone.extend( 'door2_iron' , function( ) { +Drone.extend( function door2_iron( ) { this .cuboidX( 71, this.dir ).up( ) .cuboidX( 71, 8 ).right( ) @@ -1141,7 +1151,7 @@ var _STAIRBLOCKS = { // // prism private implementation // -var _prism = function( block, w, d ) { +function prism( block, w, d ) { var stairEquiv = _STAIRBLOCKS[block]; if ( stairEquiv ) { this.fwd( ).prism(stairEquiv,w,d-2 ).back( ); @@ -1196,7 +1206,8 @@ var _prism = function( block, w, d ) { // // prism0 private implementation // -var _prism0 = function( block,w,d ) { +; +Drone.extend( function prism0( block,w,d ) { this.prism(block,w,d ) .fwd( ).right( ) .prism(0,w-2,d-2 ) @@ -1207,10 +1218,9 @@ var _prism0 = function( block,w,d ) { var f = Math.floor(d/2 ); this.fwd(f ).up(f ).cuboid(se,w ).down(f ).back(f ); } -}; -Drone.extend('prism0',_prism0 ); -Drone.extend('prism',_prism ); -Drone.extend('box',Drone.prototype.cuboid ); +} ); +Drone.extend(prism); +Drone.extend('box', Drone.prototype.cuboid ); Drone.extend('box0',Drone.prototype.cuboid0 ); Drone.extend('boxa',Drone.prototype.cuboida ); // @@ -1341,7 +1351,7 @@ var _arc2 = function( params ) { } }else{ if ( strokeWidth == 1 ) { - gotoxy(x,y ) + gotoxy(x,y ) .cuboidX( params.blockType, params.meta, 1, // width stack, // height @@ -1485,12 +1495,6 @@ var _cylinder1 = function( block,radius,height,exactParams ) { }; var _paste = function( name, immediate ) { -/* if ( !immediate ) { - var clone = Drone.clone(this); - getQueue(this).push(this.paste.bind(clone, name, true) ); - return; - } -*/ var ccContent = Drone.clipBoard[name]; if (ccContent == undefined){ @@ -1504,7 +1508,7 @@ var _paste = function( name, immediate ) _traverse[this.dir].width(that,srcBlocks.length,function( ww ) { var h = srcBlocks[ww].length; - _traverseHeight(that,h,function( hh ) { + traverseHeight(that,h,function( hh ) { var d = srcBlocks[ww][hh].length; _traverse[that.dir].depth(that,d,function( dd ) { var b = srcBlocks[ww][hh][dd]; @@ -1524,7 +1528,7 @@ var _paste = function( name, immediate ) md = (md + dirOffset ) % 4; } break; - // + // // stairs // case 53: // oak @@ -1541,7 +1545,7 @@ var _paste = function( name, immediate ) var len = a.length; for ( var c=0;c < len;c++ ) { if ( a[c] == dir ) { - break; + break; } } c = (c + dirOffset ) %4; @@ -1561,7 +1565,7 @@ var _paste = function( name, immediate ) var len = a.length; for ( var c=0;c < len;c++ ) { if ( a[c] == md ) { - break; + break; } } c = (c + dirOffset ) %4; @@ -1654,42 +1658,46 @@ _movements[3].fwd = _movements[0].left; _movements[3].back = _movements[0].right; var _traverse = [{},{},{},{}]; // east -_traverse[0].width = function( that,n,callback ) { +function walkWidthEast( that,n,callback ) { var s = that.z, e = s + n; for ( ; that.z < e; that.z++ ) { callback(that.z-s ); } that.z = s; -}; -_traverse[0].depth = function( that,n,callback ) { +} +function walkDepthEast( that,n,callback ) { var s = that.x, e = s+n; for ( ;that.x < e;that.x++ ) { callback(that.x-s ); } that.x = s; -}; -// south -_traverse[1].width = function( that,n,callback ) { +} +function walkWidthSouth( that,n,callback ) { var s = that.x, e = s-n; for ( ;that.x > e;that.x-- ) { callback(s-that.x ); } that.x = s; -}; -_traverse[1].depth = _traverse[0].width; -// west -_traverse[2].width = function( that,n,callback ) { +} +function walkWidthWest( that,n,callback ) { var s = that.z, e = s-n; for ( ;that.z > e;that.z-- ) { callback(s-that.z ); } that.z = s; -}; -_traverse[2].depth = _traverse[1].width; +} +_traverse[0].width = walkWidthEast; +_traverse[0].depth = walkDepthEast; +// south +_traverse[1].width = walkWidthSouth; +_traverse[1].depth = walkWidthEast; +// west +_traverse[2].width = walkWidthWest; +_traverse[2].depth = walkWidthSouth; // north -_traverse[3].width = _traverse[0].depth; -_traverse[3].depth = _traverse[2].width; -var _traverseHeight = function( that,n,callback ) { +_traverse[3].width = walkDepthEast; +_traverse[3].depth = walkWidthWest; +function traverseHeight( that,n,callback ) { var s = that.y, e = s + n; for ( ; that.y < e; that.y++ ) { callback(that.y-s ); @@ -1699,7 +1707,7 @@ var _traverseHeight = function( that,n,callback ) { // // standard fisher-yates shuffle algorithm // -var _fisherYates = function( myArray ) { +function fisherYates( myArray ) { var i = myArray.length; if ( i == 0 ) return false; while ( --i ) { @@ -1709,13 +1717,13 @@ var _fisherYates = function( myArray ) { myArray[i] = tempj; myArray[j] = tempi; } -}; +} var _copy = function( name, w, h, d ) { var that = this; var ccContent = []; _traverse[this.dir].width(that,w,function( ww ) { ccContent.push([] ); - _traverseHeight(that,h,function( hh ) { + traverseHeight(that,h,function( hh ) { ccContent[ww].push([] ); _traverse[that.dir].depth(that,d,function( dd ) { var b = that.world.getBlockAt(that.x,that.y,that.z ); @@ -1768,7 +1776,7 @@ var _rand = function( blockDistribution ) { // make array bigger so that it's more random blockDistribution = blockDistribution.concat(blockDistribution ); } - _fisherYates(blockDistribution ); + fisherYates(blockDistribution ); return blockDistribution; }; From cdccd1fe2e95ce0aa821d5c1f3a1429aec5f2f46 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 23 Aug 2014 16:45:16 +0100 Subject: [PATCH 211/456] using new Drone.extend() style invocation. added chessboard floor. --- src/main/js/plugins/drone/contrib/fort.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/js/plugins/drone/contrib/fort.js b/src/main/js/plugins/drone/contrib/fort.js index 42a0f2c..5e5782a 100644 --- a/src/main/js/plugins/drone/contrib/fort.js +++ b/src/main/js/plugins/drone/contrib/fort.js @@ -1,9 +1,9 @@ var Drone = require('../drone').Drone; - +var blocks = require('blocks'); // // constructs a medieval fort // -Drone.extend('fort', function( side, height ) { +function fort( side, height ) { var brick = 98, turret, i, @@ -31,6 +31,7 @@ Drone.extend('fort', function( side, height ) { // build walls. // this.chkpt('fort') + .down().chessboard(blocks.wool.black, blocks.wool.white, side).up() .box0(brick,side,height-1,side) .up(height-1); // @@ -87,5 +88,6 @@ Drone.extend('fort', function( side, height ) { .fwd(1) // move inside fort .box(ladder, 1,height-1,1) .move('fort'); -}); +} +Drone.extend(fort); From b74b4c4f28652bc4bf637eec566f83a9a95ded2c Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 23 Aug 2014 16:45:43 +0100 Subject: [PATCH 212/456] New drone.extend() style invocation. --- src/main/js/plugins/drone/contrib/cottage.js | 11 ++++---- .../js/plugins/drone/contrib/dancefloor.js | 5 ++-- .../js/plugins/drone/contrib/hangtorch.js | 5 ++-- src/main/js/plugins/drone/contrib/mazegen.js | 10 +++---- src/main/js/plugins/drone/contrib/rainbow.js | 5 ++-- .../js/plugins/drone/contrib/spiral_stairs.js | 27 ++++++++++--------- src/main/js/plugins/drone/contrib/temple.js | 5 ++-- 7 files changed, 37 insertions(+), 31 deletions(-) diff --git a/src/main/js/plugins/drone/contrib/cottage.js b/src/main/js/plugins/drone/contrib/cottage.js index b9c2272..15f62d1 100644 --- a/src/main/js/plugins/drone/contrib/cottage.js +++ b/src/main/js/plugins/drone/contrib/cottage.js @@ -10,8 +10,7 @@ var Drone = require('../drone').Drone; // // /js drone.cottage(); // - -Drone.extend('cottage',function ( ) { +function cottage( ) { this.chkpt('cottage') .box0(48,7,2,6) // 4 walls .right(3) @@ -28,12 +27,12 @@ Drone.extend('cottage',function ( ) { .right(4) .sign(['Home','Sweet','Home'],68) .move('cottage'); -}); +} // // a more complex script that builds an tree-lined avenue with // cottages on both sides. // -Drone.extend('cottage_road', function( numberCottages ) { +function cottage_road( numberCottages ) { if (typeof numberCottages == 'undefined'){ numberCottages = 6; } @@ -90,5 +89,7 @@ Drone.extend('cottage_road', function( numberCottages ) { } // return drone to where it was at start of function return this.move('cottage_road'); -}); +} +Drone.extend(cottage_road); +Drone.extend(cottage); diff --git a/src/main/js/plugins/drone/contrib/dancefloor.js b/src/main/js/plugins/drone/contrib/dancefloor.js index a1b17d6..67333e0 100644 --- a/src/main/js/plugins/drone/contrib/dancefloor.js +++ b/src/main/js/plugins/drone/contrib/dancefloor.js @@ -6,7 +6,7 @@ var Drone = require('../drone').Drone; // // See it in action here => http://www.youtube.com/watch?v=UEooBt6NTFo // -Drone.extend('dancefloor',function(width,length) +function dancefloor(width,length) { if (typeof width == "undefined") width = 5; @@ -35,4 +35,5 @@ Drone.extend('dancefloor',function(width,length) var everySecond = 20; task = server.scheduler.runTaskTimer(__plugin,strobe,now,everySecond); return this; -}); +} +Drone.extend(dancefloor); diff --git a/src/main/js/plugins/drone/contrib/hangtorch.js b/src/main/js/plugins/drone/contrib/hangtorch.js index aa15c57..f0265ae 100644 --- a/src/main/js/plugins/drone/contrib/hangtorch.js +++ b/src/main/js/plugins/drone/contrib/hangtorch.js @@ -10,7 +10,7 @@ function canHang( material ) { return false; } } -Drone.extend('hangtorch', function () { +function hangtorch() { var torch = '50:' + Drone.PLAYER_TORCH_FACING[this.dir]; var moves = 0; var block = this.world.getBlockAt(this.x, this.y, this.z); @@ -28,4 +28,5 @@ Drone.extend('hangtorch', function () { } this.box(torch) .fwd(moves); -}); +} +Drone.extend(hangtorch); diff --git a/src/main/js/plugins/drone/contrib/mazegen.js b/src/main/js/plugins/drone/contrib/mazegen.js index b02c22c..17e51cb 100644 --- a/src/main/js/plugins/drone/contrib/mazegen.js +++ b/src/main/js/plugins/drone/contrib/mazegen.js @@ -96,12 +96,12 @@ function maze_draw(maze_string, d) { } } } - -// User-facing code starts here -// Example: Try /js amazing(5,7) -Drone.extend('amazing', function(size_x, size_y) { +function maze(size_x, size_y) { m = maze_make(size_x, size_y); if (m.x > 0 && m.y > 0) { maze_draw(maze_display(m), this); } -}); +} +// User-facing code starts here +// Example: Try /js amazing(5,7) +Drone.extend(maze); diff --git a/src/main/js/plugins/drone/contrib/rainbow.js b/src/main/js/plugins/drone/contrib/rainbow.js index 2575a62..a16f179 100644 --- a/src/main/js/plugins/drone/contrib/rainbow.js +++ b/src/main/js/plugins/drone/contrib/rainbow.js @@ -18,7 +18,7 @@ Creates a Rainbow. ![rainbow example](img/rainbowex1.png) ***/ -Drone.extend('rainbow', function(radius){ +function rainbow( radius ) { var i, colors, bm; @@ -44,4 +44,5 @@ Drone.extend('rainbow', function(radius){ orientation: 'vertical'}).right().up(); } return this.move('rainbow'); -}); +} +Drone.extend(rainbow); diff --git a/src/main/js/plugins/drone/contrib/spiral_stairs.js b/src/main/js/plugins/drone/contrib/spiral_stairs.js index ad51633..43e4f9d 100644 --- a/src/main/js/plugins/drone/contrib/spiral_stairs.js +++ b/src/main/js/plugins/drone/contrib/spiral_stairs.js @@ -30,17 +30,18 @@ To construct a spiral staircase 5 floors high made of oak... spiral_stairs('oak', 5); ***/ -Drone.extend("spiral_stairs",function(stairBlock, flights){ - this.chkpt('spiral_stairs'); +function spiral_stairs(stairBlock, flights){ + this.chkpt('spiral_stairs'); - for (var i = 0; i < flights; i++){ - this - .box(blocks.stairs[stairBlock] + ':' + Drone.PLAYER_STAIRS_FACING[this.dir]) - .up().fwd() - .box(blocks.stairs[stairBlock] + ':' + Drone.PLAYER_STAIRS_FACING[this.dir]) - .up().fwd() - .box(blocks.slab[stairBlock]) - .turn().fwd(); - } - return this.move('spiral_stairs'); -}); + for (var i = 0; i < flights; i++){ + this + .box(blocks.stairs[stairBlock] + ':' + Drone.PLAYER_STAIRS_FACING[this.dir]) + .up().fwd() + .box(blocks.stairs[stairBlock] + ':' + Drone.PLAYER_STAIRS_FACING[this.dir]) + .up().fwd() + .box(blocks.slab[stairBlock]) + .turn().fwd(); + } + return this.move('spiral_stairs'); +} +Drone.extend(spiral_stairs); diff --git a/src/main/js/plugins/drone/contrib/temple.js b/src/main/js/plugins/drone/contrib/temple.js index 8545e60..0597e0a 100644 --- a/src/main/js/plugins/drone/contrib/temple.js +++ b/src/main/js/plugins/drone/contrib/temple.js @@ -2,7 +2,7 @@ var Drone = require('../drone').Drone; // // constructs a mayan temple // -Drone.extend('temple', function(side) { +function temple( side ) { if ( !side ) { side = 20; } @@ -27,4 +27,5 @@ Drone.extend('temple', function(side) { } this.move('temple'); -}); +} +Drone.extend(temple); From df0491ff1416049525f6b4acf88d06821fec55ca Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 23 Aug 2014 16:46:02 +0100 Subject: [PATCH 213/456] Improved - added doorways between forts and rampart. --- src/main/js/plugins/drone/contrib/castle.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/js/plugins/drone/contrib/castle.js b/src/main/js/plugins/drone/contrib/castle.js index c8ac9fc..d7d7d50 100644 --- a/src/main/js/plugins/drone/contrib/castle.js +++ b/src/main/js/plugins/drone/contrib/castle.js @@ -1,9 +1,8 @@ var Drone = require('../drone').Drone; - // // a castle is just a big wide fort with 4 taller forts at each corner // -Drone.extend('castle', function( side, height ) { +function castle( side, height ) { // // use sensible default parameter values // if no parameters are supplied @@ -42,8 +41,17 @@ Drone.extend('castle', function( side, height ) { for ( var corner = 0; corner < 4; corner++ ) { // construct a 'tower' fort this.fort(towerSide,towerHeight); + this.chkpt('tower-' + corner); + // create 2 doorways from main castle rampart into each tower + this.fwd(towerSide-1).right(towerSide-3).up(towerHeight-5); + this.box(blocks.air, 1,2,1); + this.back(2).right(2); + this.box(blocks.air, 1,2,1); + + this.move('tower-' + corner); // move forward the length of the castle then turn right this.fwd(side+towerSide-1).turn(); } return this.move('castle'); -}); +} +Drone.extend(castle); From e7916f8afeac56708068e77a430c4873a718fb69 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 23 Aug 2014 17:36:19 +0100 Subject: [PATCH 214/456] Rainbows are made from stained glass now. --- src/main/js/modules/blocks.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/main/js/modules/blocks.js b/src/main/js/modules/blocks.js index 5b96cf3..cb8ddad 100644 --- a/src/main/js/modules/blocks.js +++ b/src/main/js/modules/blocks.js @@ -133,7 +133,9 @@ var blocks = { cake: 92, redstone_repeater: 93, redeston_repeater_active: 94, - chest_locked: 95, + stained_glass: { + white: 95 // all other colors added below + }, trapdoor: 96, monster_egg: 97, brick: { @@ -254,7 +256,7 @@ var colors = { red: ':14', black: ':15' }; -var colorized_blocks = ['wool', 'stained_clay', 'carpet']; +var colorized_blocks = ['wool', 'stained_clay', 'carpet', 'stained_glass']; for (var i = 0, len = colorized_blocks.length; i < len; i++) { var block = colorized_blocks[i], @@ -272,12 +274,12 @@ for (var i = 0, len = colorized_blocks.length; i < len; i++) { clay blocks. */ blocks.rainbow = [ - blocks.wool.red, - blocks.wool.orange, - blocks.wool.yellow, - blocks.wool.lime, - blocks.wool.lightblue, - blocks.wool.blue, - blocks.wool.purple]; + blocks.stained_glass.red, + blocks.stained_glass.orange, + blocks.stained_glass.yellow, + blocks.stained_glass.lime, + blocks.stained_glass.lightblue, + blocks.stained_glass.blue, + blocks.stained_glass.purple]; module.exports = blocks; From 13525c3d10ca715c4066c76c1076b66414f6c28a Mon Sep 17 00:00:00 2001 From: Walter Higgins Date: Sat, 23 Aug 2014 17:45:17 +0100 Subject: [PATCH 215/456] Delete color.js Removing chat/color.js module because it will be used as an exercise. --- src/main/js/plugins/chat/color.js | 102 ------------------------------ 1 file changed, 102 deletions(-) delete mode 100644 src/main/js/plugins/chat/color.js diff --git a/src/main/js/plugins/chat/color.js b/src/main/js/plugins/chat/color.js deleted file mode 100644 index 482caca..0000000 --- a/src/main/js/plugins/chat/color.js +++ /dev/null @@ -1,102 +0,0 @@ -/************************************************************************* -## chat Plugin - -This plugin lets players choose a text color to use when chatting. Players can list colors by typing... - - /jsp list_colors - -... and can set the color to use when chatting by typing... - - /jsp chat_color {color} - -... where {color} is one of the following colors... - - * black - * blue - * darkgreen - * darkaqua - * darkred - * purple - * gold - * gray - * darkgray - * indigo - * brightgreen - * aqua - * red - * pink - * yellow - * white - -This plugin's source code is useful to study because it is short and demonstrates use of the `plugin()`, and `command()` functions, persistence and event handling. - -***/ -var _store = { players: { } }, - colorCodes = {}, - i, - colors = [ - 'black', - 'blue', - 'darkgreen', - 'darkaqua', - 'darkred', - 'purple', - 'gold', - 'gray', - 'darkgray', - 'indigo', - 'brightgreen', - 'aqua', - 'red', - 'pink', - 'yellow', - 'white' - ], - foreach = require('utils').foreach; - -/* - declare a new javascript plugin for changing chat text color -*/ -exports.chat = plugin( 'chat', { - /* - set the color of text for a given player - */ - setColor: function( player, color ) { - _store.players[ player.name ] = color; - }, - - store: _store - -},true); - -foreach( colors, function ( color, i ) { - colorCodes[color] = i.toString( 16 ); -} ); - -events.asyncPlayerChat( function( event ) { - var player = event.player; - var playerChatColor = _store.players[ player.name ]; - if ( playerChatColor ) { - event.message = '§' + colorCodes[ playerChatColor ] + event.message; - } -}); - -var listColors = function( params, sender ) { - var colorNamesInColor = []; - foreach (colors, function( color ) { - colorNamesInColor.push( '§' + colorCodes[color] + color ); - } ); - sender.sendMessage( 'valid chat colors are ' + colorNamesInColor.join( ', ') ); -}; - -command( 'list_colors', listColors ); -command( 'chat_color', function( params, sender ) { - var color = params[0]; - if ( colorCodes[color] ) { - chat.setColor( sender, color ); - } else { - sender.sendMessage( color + ' is not a valid color' ); - listColors(); - } -}, colors ); - From c535f05fbadb76a2b78312c21c50f0fc3448233a Mon Sep 17 00:00:00 2001 From: Walter Higgins Date: Sat, 23 Aug 2014 17:46:33 +0100 Subject: [PATCH 216/456] Delete SnowballFight.js Removing SnowballFight.js because it will be used as an exercise. An improved version will be posted in a separate repository coming soon. --- .../js/plugins/minigames/SnowballFight.js | 210 ------------------ 1 file changed, 210 deletions(-) delete mode 100644 src/main/js/plugins/minigames/SnowballFight.js diff --git a/src/main/js/plugins/minigames/SnowballFight.js b/src/main/js/plugins/minigames/SnowballFight.js deleted file mode 100644 index 8d1dfbd..0000000 --- a/src/main/js/plugins/minigames/SnowballFight.js +++ /dev/null @@ -1,210 +0,0 @@ -/************************************************************************* -## SnowballFight mini-game - -### Description - -This is a rough and ready prototype of a simple multi-player -shoot-em-up. To start a game with all players playing against one another... - - /js new Game_SnowballFight(60).start(); - -... this obviously works best if all of the players are in close -proximity within the same game world. Alternatively you can have team -matches... - - - /js var redTeam = ['','',...etc] - /js var blueTeam = ['',',...etc] - /js var greenTeam = ['',',...etc] - /js new Game_SnowballFight(60, {red: redTeam,blue: blueTeam,green: greenTeam}).start(); - -Or you can just have specific players play against each other... - - /js new Game_SnowballFight(60, ['player1','player2','player3']).start(); - -(where 'player1' etc are the names of actual players) - -You specify the teams in the game as an object where each property's -name is a team name and each property's value is the list of players -on that team. You specify the duration of the game (in seconds) You -kick off the game with the start() method. I need to work on a -better in-game mechanism for players to choose teams and start the -game but this will do for now. - -When the game starts, each player is put in survival mode and given -snowballs. The aim of the game is to hit players on opposing teams. If -you hit a player on your own team, you lose a point. - -At the end of the game the scores for each team are broadcast and each -player returns to their previous mode of play (creative or -survival). Create a small arena with a couple of small buildings for -cover to make the game more fun. - -***/ - -var bkGameMode = org.bukkit.GameMode, - bkItemStack = org.bukkit.inventory.ItemStack, - bkMaterial = org.bukkit.Material, - bkSnowball = org.bukkit.entity.Snowball; - -var _startGame = function( gameState ) { - var i, - teamName, - team, - player; - - // don't let game start if already in progress (wait for game to finish) - if ( gameState.inProgress ) { - return; - } - gameState.inProgress = true; - // reset timer - gameState.duration = gameState.originalDuration; - // put all players in survival mode and give them each 200 snowballs - // 64 snowballs for every 30 seconds should be more than enough - for ( i = 10; i < gameState.duration; i += 10 ) { - gameState.ammo.push( gameState.ammo[ 0 ] ); - } - - for ( teamName in gameState.teams ) { - gameState.teamScores[teamName] = 0; - team = gameState.teams[ teamName ]; - for ( i = 0; i < team.length; i++ ) { - player = server.getPlayer( team[i] ); - gameState.savedModes[ player.name ] = player.gameMode; - player.gameMode = bkGameMode.SURVIVAL; - player.inventory.addItem( gameState.ammo ); - } - } -}; -/* - end the game - */ -var _endGame = function( gameState ) { - var scores = [], - leaderBoard = [], - tn, - i, - teamName, - team, - player, - handlerList; - - leaderBoard = []; - for ( tn in gameState.teamScores){ - leaderBoard.push([tn,gameState.teamScores[tn]]); - } - leaderBoard.sort(function(a,b){ return b[1] - a[1];}); - - for ( i = 0; i < leaderBoard.length; i++ ) { - scores.push( 'Team ' + leaderBoard[i][0] + ' scored ' + leaderBoard[i][1] ); - } - - for ( teamName in gameState.teams ) { - team = gameState.teams[teamName]; - for ( i = 0; i < team.length; i++ ) { - // restore player's previous game mode and take back snowballs - player = server.getPlayer( team[i] ); - player.gameMode = gameState.savedModes[ player.name ]; - player.inventory.removeItem( gameState.ammo ); - player.sendMessage( 'GAME OVER.' ); - player.sendMessage( scores ); - } - } - gameState.listener.unregister(); - gameState.inProgress = false; -}; -/* - get the team the player belongs to - */ -var _getTeam = function( player, pteams ) { - var teamName, - team, - i; - for ( teamName in pteams ) { - team = pteams[ teamName ]; - for ( i = 0; i < team.length; i++ ) { - if ( team[i] == player.name ) { - return teamName; - } - } - } - return null; -}; -/* - construct a new game - */ -var createGame = function( duration, teams ) { - var players, - i, - _snowBalls = new bkItemStack( bkMaterial.SNOW_BALL, 64 ); - - var _gameState = { - teams: teams, - duration: duration, - originalDuration: duration, - inProgress: false, - teamScores: {}, - listener: null, - savedModes: {}, - ammo: [ _snowBalls ] - }; - if ( typeof duration == 'undefined' ) { - duration = 60; - } - if ( typeof teams == 'undefined' ) { - /* - wph 20130511 use all players - */ - teams = []; - players = server.onlinePlayers; - for ( i = 0; i < players.length; i++ ) { - teams.push( players[i].name ); - } - } - // - // allow for teams param to be either {red:['player1','player2'],blue:['player3']} or - // ['player1','player2','player3'] if all players are against each other (no teams) - // - if ( teams instanceof Array ) { - _gameState.teams = {}; - for ( i = 0;i < teams.length; i++ ) { - _gameState.teams[ teams[i] ] = [ teams[i] ]; - } - } - /* - this function is called every time a player is damaged by another entity/player - */ - var _onSnowballHit = function( event ) { - var snowball = event.damager; - if ( !snowball || !( snowball instanceof bkSnowball ) ) { - return; - } - var throwersTeam = _getTeam( snowball.shooter, _gameState.teams ); - var damageeTeam = _getTeam( event.entity, _gameState.teams); - if ( !throwersTeam || !damageeTeam ) { - return; // thrower/damagee wasn't in game - } - if ( throwersTeam != damageeTeam ) { - _gameState.teamScores[ throwersTeam ]++; - } else { - _gameState.teamScores[ throwersTeam ]--; - } - }; - - return { - start: function( ) { - _startGame( _gameState ); - _gameState.listener = events.entityDamageByEntity( _onSnowballHit ); - new java.lang.Thread( function( ) { - while ( _gameState.duration-- ) { - java.lang.Thread.sleep( 1000 ); // sleep 1,000 millisecs (1 second) - } - _endGame(_gameState); - } ).start( ); - } - }; -}; -exports.Game_SnowballFight = createGame; - - From 9caf5b4bd6ec072489888df17ea8408acbc4a986 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 23 Aug 2014 17:51:52 +0100 Subject: [PATCH 217/456] Removed chat plugin and SnowballFight mini-game - will be present in another repository soon to be made public. --- docs/API-Reference.md | 75 ------------------------------------------- 1 file changed, 75 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 0ac1e74..8773ee9 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -292,7 +292,6 @@ Walter Higgins * [Usage](#usage-10) * [alias Plugin](#alias-plugin) * [Examples](#examples-2) - * [chat Plugin](#chat-plugin) * [Classroom Plugin](#classroom-plugin) * [classroom.allowScripting() function](#classroomallowscripting-function) * [Commando Plugin](#commando-plugin) @@ -307,8 +306,6 @@ Walter Higgins * [NumberGuess mini-game:](#numberguess-mini-game) * [Description](#description-1) * [Example](#example-1) - * [SnowballFight mini-game](#snowballfight-mini-game) - * [Description](#description-2) * [Cow Clicker Mini-Game](#cow-clicker-mini-game) * [How to Play](#how-to-play) * [Rules](#rules) @@ -4027,37 +4024,6 @@ Aliases can be used at the in-game prompt by players or in the server console. Aliases will not be able to avail of command autocompletion (pressing the TAB key will have no effect). -## chat Plugin - -This plugin lets players choose a text color to use when chatting. Players can list colors by typing... - - /jsp list_colors - -... and can set the color to use when chatting by typing... - - /jsp chat_color {color} - -... where {color} is one of the following colors... - - * black - * blue - * darkgreen - * darkaqua - * darkred - * purple - * gold - * gray - * darkgray - * indigo - * brightgreen - * aqua - * red - * pink - * yellow - * white - -This plugin's source code is useful to study because it is short and demonstrates use of the `plugin()`, and `command()` functions, persistence and event handling. - ## Classroom Plugin The `classroom` object contains a couple of utility functions for use @@ -4289,47 +4255,6 @@ code is to demonstrate use of Bukkit's Conversation API. Once the game begins, guess a number by typing the `/` character followed by a number between 1 and 10. -## SnowballFight mini-game - -### Description - -This is a rough and ready prototype of a simple multi-player -shoot-em-up. To start a game with all players playing against one another... - - /js new Game_SnowballFight(60).start(); - -... this obviously works best if all of the players are in close -proximity within the same game world. Alternatively you can have team -matches... - - - /js var redTeam = ['','',...etc] - /js var blueTeam = ['',',...etc] - /js var greenTeam = ['',',...etc] - /js new Game_SnowballFight(60, {red: redTeam,blue: blueTeam,green: greenTeam}).start(); - -Or you can just have specific players play against each other... - - /js new Game_SnowballFight(60, ['player1','player2','player3']).start(); - -(where 'player1' etc are the names of actual players) - -You specify the teams in the game as an object where each property's -name is a team name and each property's value is the list of players -on that team. You specify the duration of the game (in seconds) You -kick off the game with the start() method. I need to work on a -better in-game mechanism for players to choose teams and start the -game but this will do for now. - -When the game starts, each player is put in survival mode and given -snowballs. The aim of the game is to hit players on opposing teams. If -you hit a player on your own team, you lose a point. - -At the end of the game the scores for each team are broadcast and each -player returns to their previous mode of play (creative or -survival). Create a small arena with a couple of small buildings for -cover to make the game more fun. - ## Cow Clicker Mini-Game ### How to Play From 4e6e50c1509fd0b0dfa97efa8f829cbb46c746b6 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sat, 23 Aug 2014 18:13:55 +0100 Subject: [PATCH 218/456] release notes update --- docs/release-notes.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index b52021a..0553b05 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,3 +1,9 @@ +# 2014 08 23 +Chessboard was broken, is now fixed. +Rainbows are now made from stained glass. Full range of stained_glass colors is available in blocks variable. +SnowballFight mini-game has been removed. An improved version will be available soon online. +chat plugin has been removed - will be available in another github repo soon. + # 2014 06 14 Fix issue #140 - fails to build for JRE7 Changed command() documentation to conform with new way of using (passing a named function) From 88b8e299928708a13d02cb7f92368e58c71b8c7c Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Sun, 24 Aug 2014 10:20:02 +0100 Subject: [PATCH 219/456] Include links to bukkit documentation for event helper functions. --- docs/API-Reference.md | 314 ++++++++++++++++++------------------ src/generateEventsHelper.js | 3 +- 2 files changed, 159 insertions(+), 158 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 8773ee9..04f02b6 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -926,7 +926,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the world.WorldUnloadEvent event is fired + * callback - A function which is called whenever the [world.WorldUnloadEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/world/WorldUnloadEvent.html) is fired * priority - optional - see events.on() for more information. @@ -934,7 +934,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the world.WorldLoadEvent event is fired + * callback - A function which is called whenever the [world.WorldLoadEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/world/WorldLoadEvent.html) is fired * priority - optional - see events.on() for more information. @@ -942,7 +942,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the world.ChunkLoadEvent event is fired + * callback - A function which is called whenever the [world.ChunkLoadEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/world/ChunkLoadEvent.html) is fired * priority - optional - see events.on() for more information. @@ -950,7 +950,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the world.ChunkPopulateEvent event is fired + * callback - A function which is called whenever the [world.ChunkPopulateEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/world/ChunkPopulateEvent.html) is fired * priority - optional - see events.on() for more information. @@ -958,7 +958,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the world.PortalCreateEvent event is fired + * callback - A function which is called whenever the [world.PortalCreateEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/world/PortalCreateEvent.html) is fired * priority - optional - see events.on() for more information. @@ -966,7 +966,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the world.SpawnChangeEvent event is fired + * callback - A function which is called whenever the [world.SpawnChangeEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/world/SpawnChangeEvent.html) is fired * priority - optional - see events.on() for more information. @@ -974,7 +974,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the world.ChunkUnloadEvent event is fired + * callback - A function which is called whenever the [world.ChunkUnloadEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/world/ChunkUnloadEvent.html) is fired * priority - optional - see events.on() for more information. @@ -982,7 +982,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the world.WorldInitEvent event is fired + * callback - A function which is called whenever the [world.WorldInitEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/world/WorldInitEvent.html) is fired * priority - optional - see events.on() for more information. @@ -990,7 +990,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.HorseJumpEvent event is fired + * callback - A function which is called whenever the [entity.HorseJumpEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/HorseJumpEvent.html) is fired * priority - optional - see events.on() for more information. @@ -998,7 +998,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.EntityCombustEvent event is fired + * callback - A function which is called whenever the [entity.EntityCombustEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/EntityCombustEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1006,7 +1006,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.EntityRegainHealthEvent event is fired + * callback - A function which is called whenever the [entity.EntityRegainHealthEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/EntityRegainHealthEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1014,7 +1014,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.EntityCombustByBlockEvent event is fired + * callback - A function which is called whenever the [entity.EntityCombustByBlockEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/EntityCombustByBlockEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1022,7 +1022,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.EntityCombustByEntityEvent event is fired + * callback - A function which is called whenever the [entity.EntityCombustByEntityEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/EntityCombustByEntityEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1030,7 +1030,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.PlayerLeashEntityEvent event is fired + * callback - A function which is called whenever the [entity.PlayerLeashEntityEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/PlayerLeashEntityEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1038,7 +1038,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.PigZapEvent event is fired + * callback - A function which is called whenever the [entity.PigZapEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/PigZapEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1046,7 +1046,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.ItemDespawnEvent event is fired + * callback - A function which is called whenever the [entity.ItemDespawnEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/ItemDespawnEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1054,7 +1054,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.EntityTargetEvent event is fired + * callback - A function which is called whenever the [entity.EntityTargetEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/EntityTargetEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1062,7 +1062,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.SlimeSplitEvent event is fired + * callback - A function which is called whenever the [entity.SlimeSplitEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/SlimeSplitEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1070,7 +1070,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.EntityChangeBlockEvent event is fired + * callback - A function which is called whenever the [entity.EntityChangeBlockEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/EntityChangeBlockEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1078,7 +1078,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.EntityPortalEnterEvent event is fired + * callback - A function which is called whenever the [entity.EntityPortalEnterEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/EntityPortalEnterEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1086,7 +1086,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.CreeperPowerEvent event is fired + * callback - A function which is called whenever the [entity.CreeperPowerEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/CreeperPowerEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1094,7 +1094,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.EntityDeathEvent event is fired + * callback - A function which is called whenever the [entity.EntityDeathEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/EntityDeathEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1102,7 +1102,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.ProjectileHitEvent event is fired + * callback - A function which is called whenever the [entity.ProjectileHitEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/ProjectileHitEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1110,7 +1110,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.EntityTameEvent event is fired + * callback - A function which is called whenever the [entity.EntityTameEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/EntityTameEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1118,7 +1118,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.PotionSplashEvent event is fired + * callback - A function which is called whenever the [entity.PotionSplashEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/PotionSplashEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1126,7 +1126,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.ExpBottleEvent event is fired + * callback - A function which is called whenever the [entity.ExpBottleEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/ExpBottleEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1134,7 +1134,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.EntityExplodeEvent event is fired + * callback - A function which is called whenever the [entity.EntityExplodeEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/EntityExplodeEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1142,7 +1142,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.CreatureSpawnEvent event is fired + * callback - A function which is called whenever the [entity.CreatureSpawnEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/CreatureSpawnEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1150,7 +1150,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.FoodLevelChangeEvent event is fired + * callback - A function which is called whenever the [entity.FoodLevelChangeEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/FoodLevelChangeEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1158,7 +1158,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.EntityInteractEvent event is fired + * callback - A function which is called whenever the [entity.EntityInteractEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/EntityInteractEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1166,7 +1166,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.EntityBreakDoorEvent event is fired + * callback - A function which is called whenever the [entity.EntityBreakDoorEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/EntityBreakDoorEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1174,7 +1174,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.EntityCreatePortalEvent event is fired + * callback - A function which is called whenever the [entity.EntityCreatePortalEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/EntityCreatePortalEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1182,7 +1182,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.SheepRegrowWoolEvent event is fired + * callback - A function which is called whenever the [entity.SheepRegrowWoolEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/SheepRegrowWoolEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1190,7 +1190,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.ExplosionPrimeEvent event is fired + * callback - A function which is called whenever the [entity.ExplosionPrimeEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/ExplosionPrimeEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1198,7 +1198,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.EntityUnleashEvent event is fired + * callback - A function which is called whenever the [entity.EntityUnleashEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/EntityUnleashEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1206,7 +1206,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.EntityShootBowEvent event is fired + * callback - A function which is called whenever the [entity.EntityShootBowEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/EntityShootBowEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1214,7 +1214,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.ProjectileLaunchEvent event is fired + * callback - A function which is called whenever the [entity.ProjectileLaunchEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/ProjectileLaunchEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1222,7 +1222,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.ItemSpawnEvent event is fired + * callback - A function which is called whenever the [entity.ItemSpawnEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/ItemSpawnEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1230,7 +1230,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.SheepDyeWoolEvent event is fired + * callback - A function which is called whenever the [entity.SheepDyeWoolEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/SheepDyeWoolEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1238,7 +1238,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.EntityTeleportEvent event is fired + * callback - A function which is called whenever the [entity.EntityTeleportEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/EntityTeleportEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1246,7 +1246,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the block.BlockFadeEvent event is fired + * callback - A function which is called whenever the [block.BlockFadeEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/block/BlockFadeEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1254,7 +1254,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the block.BlockDamageEvent event is fired + * callback - A function which is called whenever the [block.BlockDamageEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/block/BlockDamageEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1262,7 +1262,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the block.BlockPistonExtendEvent event is fired + * callback - A function which is called whenever the [block.BlockPistonExtendEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/block/BlockPistonExtendEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1270,7 +1270,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the block.BlockExpEvent event is fired + * callback - A function which is called whenever the [block.BlockExpEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/block/BlockExpEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1278,7 +1278,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the block.BlockGrowEvent event is fired + * callback - A function which is called whenever the [block.BlockGrowEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/block/BlockGrowEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1286,7 +1286,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the block.BlockPistonRetractEvent event is fired + * callback - A function which is called whenever the [block.BlockPistonRetractEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/block/BlockPistonRetractEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1294,7 +1294,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the block.BlockDispenseEvent event is fired + * callback - A function which is called whenever the [block.BlockDispenseEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/block/BlockDispenseEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1302,7 +1302,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the block.BlockBreakEvent event is fired + * callback - A function which is called whenever the [block.BlockBreakEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/block/BlockBreakEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1310,7 +1310,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the painting.PaintingPlaceEvent event is fired + * callback - A function which is called whenever the [painting.PaintingPlaceEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/painting/PaintingPlaceEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1318,7 +1318,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the weather.LightningStrikeEvent event is fired + * callback - A function which is called whenever the [weather.LightningStrikeEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/weather/LightningStrikeEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1326,7 +1326,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the vehicle.VehicleEnterEvent event is fired + * callback - A function which is called whenever the [vehicle.VehicleEnterEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/vehicle/VehicleEnterEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1334,7 +1334,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the vehicle.VehicleMoveEvent event is fired + * callback - A function which is called whenever the [vehicle.VehicleMoveEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/vehicle/VehicleMoveEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1342,7 +1342,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the vehicle.VehicleCreateEvent event is fired + * callback - A function which is called whenever the [vehicle.VehicleCreateEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/vehicle/VehicleCreateEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1350,7 +1350,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.AsyncPlayerPreLoginEvent event is fired + * callback - A function which is called whenever the [player.AsyncPlayerPreLoginEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/AsyncPlayerPreLoginEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1358,7 +1358,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerUnleashEntityEvent event is fired + * callback - A function which is called whenever the [player.PlayerUnleashEntityEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerUnleashEntityEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1366,7 +1366,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerPreLoginEvent event is fired + * callback - A function which is called whenever the [player.PlayerPreLoginEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerPreLoginEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1374,7 +1374,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the inventory.InventoryPickupItemEvent event is fired + * callback - A function which is called whenever the [inventory.InventoryPickupItemEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/inventory/InventoryPickupItemEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1382,7 +1382,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the inventory.InventoryMoveItemEvent event is fired + * callback - A function which is called whenever the [inventory.InventoryMoveItemEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/inventory/InventoryMoveItemEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1390,7 +1390,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the inventory.FurnaceBurnEvent event is fired + * callback - A function which is called whenever the [inventory.FurnaceBurnEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/inventory/FurnaceBurnEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1398,7 +1398,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the inventory.InventoryEvent event is fired + * callback - A function which is called whenever the [inventory.InventoryEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/inventory/InventoryEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1406,7 +1406,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the inventory.BrewEvent event is fired + * callback - A function which is called whenever the [inventory.BrewEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/inventory/BrewEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1414,7 +1414,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the inventory.FurnaceExtractEvent event is fired + * callback - A function which is called whenever the [inventory.FurnaceExtractEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/inventory/FurnaceExtractEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1422,7 +1422,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the inventory.FurnaceSmeltEvent event is fired + * callback - A function which is called whenever the [inventory.FurnaceSmeltEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/inventory/FurnaceSmeltEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1430,7 +1430,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the inventory.InventoryCloseEvent event is fired + * callback - A function which is called whenever the [inventory.InventoryCloseEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/inventory/InventoryCloseEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1438,7 +1438,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the inventory.InventoryDragEvent event is fired + * callback - A function which is called whenever the [inventory.InventoryDragEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/inventory/InventoryDragEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1446,7 +1446,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the inventory.InventoryClickEvent event is fired + * callback - A function which is called whenever the [inventory.InventoryClickEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/inventory/InventoryClickEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1454,7 +1454,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the inventory.InventoryCreativeEvent event is fired + * callback - A function which is called whenever the [inventory.InventoryCreativeEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/inventory/InventoryCreativeEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1462,7 +1462,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the hanging.HangingPlaceEvent event is fired + * callback - A function which is called whenever the [hanging.HangingPlaceEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/hanging/HangingPlaceEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1470,7 +1470,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the hanging.HangingBreakEvent event is fired + * callback - A function which is called whenever the [hanging.HangingBreakEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/hanging/HangingBreakEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1478,7 +1478,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the world.WorldSaveEvent event is fired + * callback - A function which is called whenever the [world.WorldSaveEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/world/WorldSaveEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1486,7 +1486,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the world.StructureGrowEvent event is fired + * callback - A function which is called whenever the [world.StructureGrowEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/world/StructureGrowEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1494,7 +1494,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.EntityDamageEvent event is fired + * callback - A function which is called whenever the [entity.EntityDamageEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/EntityDamageEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1502,7 +1502,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.EntityTargetLivingEntityEvent event is fired + * callback - A function which is called whenever the [entity.EntityTargetLivingEntityEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/EntityTargetLivingEntityEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1510,7 +1510,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.PlayerDeathEvent event is fired + * callback - A function which is called whenever the [entity.PlayerDeathEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/PlayerDeathEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1518,7 +1518,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.EntityDamageByBlockEvent event is fired + * callback - A function which is called whenever the [entity.EntityDamageByBlockEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/EntityDamageByBlockEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1526,7 +1526,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.EntityDamageByEntityEvent event is fired + * callback - A function which is called whenever the [entity.EntityDamageByEntityEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/EntityDamageByEntityEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1534,7 +1534,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.EntityPortalEvent event is fired + * callback - A function which is called whenever the [entity.EntityPortalEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/EntityPortalEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1542,7 +1542,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the entity.EntityPortalExitEvent event is fired + * callback - A function which is called whenever the [entity.EntityPortalExitEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/entity/EntityPortalExitEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1550,7 +1550,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the block.SignChangeEvent event is fired + * callback - A function which is called whenever the [block.SignChangeEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/block/SignChangeEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1558,7 +1558,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the block.LeavesDecayEvent event is fired + * callback - A function which is called whenever the [block.LeavesDecayEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/block/LeavesDecayEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1566,7 +1566,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the block.BlockRedstoneEvent event is fired + * callback - A function which is called whenever the [block.BlockRedstoneEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/block/BlockRedstoneEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1574,7 +1574,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the block.BlockCanBuildEvent event is fired + * callback - A function which is called whenever the [block.BlockCanBuildEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/block/BlockCanBuildEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1582,7 +1582,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the block.BlockBurnEvent event is fired + * callback - A function which is called whenever the [block.BlockBurnEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/block/BlockBurnEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1590,7 +1590,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the block.BlockPhysicsEvent event is fired + * callback - A function which is called whenever the [block.BlockPhysicsEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/block/BlockPhysicsEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1598,7 +1598,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the block.BlockIgniteEvent event is fired + * callback - A function which is called whenever the [block.BlockIgniteEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/block/BlockIgniteEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1606,7 +1606,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the block.NotePlayEvent event is fired + * callback - A function which is called whenever the [block.NotePlayEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/block/NotePlayEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1614,7 +1614,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the block.BlockPlaceEvent event is fired + * callback - A function which is called whenever the [block.BlockPlaceEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/block/BlockPlaceEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1622,7 +1622,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the block.BlockFromToEvent event is fired + * callback - A function which is called whenever the [block.BlockFromToEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/block/BlockFromToEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1630,7 +1630,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the block.BlockFormEvent event is fired + * callback - A function which is called whenever the [block.BlockFormEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/block/BlockFormEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1638,7 +1638,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the block.BlockSpreadEvent event is fired + * callback - A function which is called whenever the [block.BlockSpreadEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/block/BlockSpreadEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1646,7 +1646,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the enchantment.EnchantItemEvent event is fired + * callback - A function which is called whenever the [enchantment.EnchantItemEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/enchantment/EnchantItemEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1654,7 +1654,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the enchantment.PrepareItemEnchantEvent event is fired + * callback - A function which is called whenever the [enchantment.PrepareItemEnchantEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/enchantment/PrepareItemEnchantEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1662,7 +1662,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the painting.PaintingBreakEvent event is fired + * callback - A function which is called whenever the [painting.PaintingBreakEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/painting/PaintingBreakEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1670,7 +1670,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the painting.PaintingBreakByEntityEvent event is fired + * callback - A function which is called whenever the [painting.PaintingBreakByEntityEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/painting/PaintingBreakByEntityEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1678,7 +1678,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the weather.WeatherChangeEvent event is fired + * callback - A function which is called whenever the [weather.WeatherChangeEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/weather/WeatherChangeEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1686,7 +1686,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the weather.ThunderChangeEvent event is fired + * callback - A function which is called whenever the [weather.ThunderChangeEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/weather/ThunderChangeEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1694,7 +1694,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the vehicle.VehicleEntityCollisionEvent event is fired + * callback - A function which is called whenever the [vehicle.VehicleEntityCollisionEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/vehicle/VehicleEntityCollisionEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1702,7 +1702,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the vehicle.VehicleBlockCollisionEvent event is fired + * callback - A function which is called whenever the [vehicle.VehicleBlockCollisionEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/vehicle/VehicleBlockCollisionEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1710,7 +1710,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the vehicle.VehicleExitEvent event is fired + * callback - A function which is called whenever the [vehicle.VehicleExitEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/vehicle/VehicleExitEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1718,7 +1718,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the vehicle.VehicleUpdateEvent event is fired + * callback - A function which is called whenever the [vehicle.VehicleUpdateEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/vehicle/VehicleUpdateEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1726,7 +1726,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the vehicle.VehicleDamageEvent event is fired + * callback - A function which is called whenever the [vehicle.VehicleDamageEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/vehicle/VehicleDamageEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1734,7 +1734,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the vehicle.VehicleDestroyEvent event is fired + * callback - A function which is called whenever the [vehicle.VehicleDestroyEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/vehicle/VehicleDestroyEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1742,7 +1742,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerExpChangeEvent event is fired + * callback - A function which is called whenever the [player.PlayerExpChangeEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerExpChangeEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1750,7 +1750,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerRespawnEvent event is fired + * callback - A function which is called whenever the [player.PlayerRespawnEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerRespawnEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1758,7 +1758,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerCommandPreprocessEvent event is fired + * callback - A function which is called whenever the [player.PlayerCommandPreprocessEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerCommandPreprocessEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1766,7 +1766,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerPickupItemEvent event is fired + * callback - A function which is called whenever the [player.PlayerPickupItemEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerPickupItemEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1774,7 +1774,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerInventoryEvent event is fired + * callback - A function which is called whenever the [player.PlayerInventoryEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerInventoryEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1782,7 +1782,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerFishEvent event is fired + * callback - A function which is called whenever the [player.PlayerFishEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerFishEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1790,7 +1790,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerBedEnterEvent event is fired + * callback - A function which is called whenever the [player.PlayerBedEnterEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerBedEnterEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1798,7 +1798,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerLoginEvent event is fired + * callback - A function which is called whenever the [player.PlayerLoginEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerLoginEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1806,7 +1806,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerDropItemEvent event is fired + * callback - A function which is called whenever the [player.PlayerDropItemEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerDropItemEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1814,7 +1814,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerLevelChangeEvent event is fired + * callback - A function which is called whenever the [player.PlayerLevelChangeEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerLevelChangeEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1822,7 +1822,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerVelocityEvent event is fired + * callback - A function which is called whenever the [player.PlayerVelocityEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerVelocityEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1830,7 +1830,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerInteractEvent event is fired + * callback - A function which is called whenever the [player.PlayerInteractEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerInteractEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1838,7 +1838,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerQuitEvent event is fired + * callback - A function which is called whenever the [player.PlayerQuitEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerQuitEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1846,7 +1846,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerChatTabCompleteEvent event is fired + * callback - A function which is called whenever the [player.PlayerChatTabCompleteEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerChatTabCompleteEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1854,7 +1854,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerEggThrowEvent event is fired + * callback - A function which is called whenever the [player.PlayerEggThrowEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerEggThrowEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1862,7 +1862,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerChatEvent event is fired + * callback - A function which is called whenever the [player.PlayerChatEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerChatEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1870,7 +1870,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerAchievementAwardedEvent event is fired + * callback - A function which is called whenever the [player.PlayerAchievementAwardedEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerAchievementAwardedEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1878,7 +1878,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerBedLeaveEvent event is fired + * callback - A function which is called whenever the [player.PlayerBedLeaveEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerBedLeaveEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1886,7 +1886,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerStatisticIncrementEvent event is fired + * callback - A function which is called whenever the [player.PlayerStatisticIncrementEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerStatisticIncrementEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1894,7 +1894,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerToggleSprintEvent event is fired + * callback - A function which is called whenever the [player.PlayerToggleSprintEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerToggleSprintEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1902,7 +1902,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerInteractEntityEvent event is fired + * callback - A function which is called whenever the [player.PlayerInteractEntityEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerInteractEntityEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1910,7 +1910,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerEditBookEvent event is fired + * callback - A function which is called whenever the [player.PlayerEditBookEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerEditBookEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1918,7 +1918,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerKickEvent event is fired + * callback - A function which is called whenever the [player.PlayerKickEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerKickEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1926,7 +1926,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerItemHeldEvent event is fired + * callback - A function which is called whenever the [player.PlayerItemHeldEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerItemHeldEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1934,7 +1934,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerItemConsumeEvent event is fired + * callback - A function which is called whenever the [player.PlayerItemConsumeEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerItemConsumeEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1942,7 +1942,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerGameModeChangeEvent event is fired + * callback - A function which is called whenever the [player.PlayerGameModeChangeEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerGameModeChangeEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1950,7 +1950,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerItemBreakEvent event is fired + * callback - A function which is called whenever the [player.PlayerItemBreakEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerItemBreakEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1958,7 +1958,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerToggleFlightEvent event is fired + * callback - A function which is called whenever the [player.PlayerToggleFlightEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerToggleFlightEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1966,7 +1966,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerAnimationEvent event is fired + * callback - A function which is called whenever the [player.PlayerAnimationEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerAnimationEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1974,7 +1974,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.AsyncPlayerChatEvent event is fired + * callback - A function which is called whenever the [player.AsyncPlayerChatEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/AsyncPlayerChatEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1982,7 +1982,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerRegisterChannelEvent event is fired + * callback - A function which is called whenever the [player.PlayerRegisterChannelEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerRegisterChannelEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1990,7 +1990,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerMoveEvent event is fired + * callback - A function which is called whenever the [player.PlayerMoveEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerMoveEvent.html) is fired * priority - optional - see events.on() for more information. @@ -1998,7 +1998,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerTeleportEvent event is fired + * callback - A function which is called whenever the [player.PlayerTeleportEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerTeleportEvent.html) is fired * priority - optional - see events.on() for more information. @@ -2006,7 +2006,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerBucketFillEvent event is fired + * callback - A function which is called whenever the [player.PlayerBucketFillEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerBucketFillEvent.html) is fired * priority - optional - see events.on() for more information. @@ -2014,7 +2014,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerJoinEvent event is fired + * callback - A function which is called whenever the [player.PlayerJoinEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerJoinEvent.html) is fired * priority - optional - see events.on() for more information. @@ -2022,7 +2022,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerShearEntityEvent event is fired + * callback - A function which is called whenever the [player.PlayerShearEntityEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerShearEntityEvent.html) is fired * priority - optional - see events.on() for more information. @@ -2030,7 +2030,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerToggleSneakEvent event is fired + * callback - A function which is called whenever the [player.PlayerToggleSneakEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerToggleSneakEvent.html) is fired * priority - optional - see events.on() for more information. @@ -2038,7 +2038,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerChangedWorldEvent event is fired + * callback - A function which is called whenever the [player.PlayerChangedWorldEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerChangedWorldEvent.html) is fired * priority - optional - see events.on() for more information. @@ -2046,7 +2046,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the server.ServerCommandEvent event is fired + * callback - A function which is called whenever the [server.ServerCommandEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/server/ServerCommandEvent.html) is fired * priority - optional - see events.on() for more information. @@ -2054,7 +2054,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the server.RemoteServerCommandEvent event is fired + * callback - A function which is called whenever the [server.RemoteServerCommandEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/server/RemoteServerCommandEvent.html) is fired * priority - optional - see events.on() for more information. @@ -2062,7 +2062,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the server.MapInitializeEvent event is fired + * callback - A function which is called whenever the [server.MapInitializeEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/server/MapInitializeEvent.html) is fired * priority - optional - see events.on() for more information. @@ -2070,7 +2070,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the server.ServiceRegisterEvent event is fired + * callback - A function which is called whenever the [server.ServiceRegisterEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/server/ServiceRegisterEvent.html) is fired * priority - optional - see events.on() for more information. @@ -2078,7 +2078,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the server.ServerListPingEvent event is fired + * callback - A function which is called whenever the [server.ServerListPingEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/server/ServerListPingEvent.html) is fired * priority - optional - see events.on() for more information. @@ -2086,7 +2086,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the server.ServiceUnregisterEvent event is fired + * callback - A function which is called whenever the [server.ServiceUnregisterEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/server/ServiceUnregisterEvent.html) is fired * priority - optional - see events.on() for more information. @@ -2094,7 +2094,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the inventory.PrepareItemCraftEvent event is fired + * callback - A function which is called whenever the [inventory.PrepareItemCraftEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/inventory/PrepareItemCraftEvent.html) is fired * priority - optional - see events.on() for more information. @@ -2102,7 +2102,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the inventory.InventoryOpenEvent event is fired + * callback - A function which is called whenever the [inventory.InventoryOpenEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/inventory/InventoryOpenEvent.html) is fired * priority - optional - see events.on() for more information. @@ -2110,7 +2110,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the inventory.CraftItemEvent event is fired + * callback - A function which is called whenever the [inventory.CraftItemEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/inventory/CraftItemEvent.html) is fired * priority - optional - see events.on() for more information. @@ -2118,7 +2118,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the hanging.HangingBreakByEntityEvent event is fired + * callback - A function which is called whenever the [hanging.HangingBreakByEntityEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/hanging/HangingBreakByEntityEvent.html) is fired * priority - optional - see events.on() for more information. @@ -2126,7 +2126,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the block.BlockMultiPlaceEvent event is fired + * callback - A function which is called whenever the [block.BlockMultiPlaceEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/block/BlockMultiPlaceEvent.html) is fired * priority - optional - see events.on() for more information. @@ -2134,7 +2134,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the block.EntityBlockFormEvent event is fired + * callback - A function which is called whenever the [block.EntityBlockFormEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/block/EntityBlockFormEvent.html) is fired * priority - optional - see events.on() for more information. @@ -2142,7 +2142,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerBucketEmptyEvent event is fired + * callback - A function which is called whenever the [player.PlayerBucketEmptyEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerBucketEmptyEvent.html) is fired * priority - optional - see events.on() for more information. @@ -2150,7 +2150,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerPortalEvent event is fired + * callback - A function which is called whenever the [player.PlayerPortalEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerPortalEvent.html) is fired * priority - optional - see events.on() for more information. @@ -2158,7 +2158,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the player.PlayerUnregisterChannelEvent event is fired + * callback - A function which is called whenever the [player.PlayerUnregisterChannelEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerUnregisterChannelEvent.html) is fired * priority - optional - see events.on() for more information. @@ -2166,7 +2166,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the server.PluginDisableEvent event is fired + * callback - A function which is called whenever the [server.PluginDisableEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/server/PluginDisableEvent.html) is fired * priority - optional - see events.on() for more information. @@ -2174,7 +2174,7 @@ The crucial difference is that the events module now has functions for each of t #### Parameters - * callback - A function which is called whenever the server.PluginEnableEvent event is fired + * callback - A function which is called whenever the [server.PluginEnableEvent event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/server/PluginEnableEvent.html) is fired * priority - optional - see events.on() for more information. diff --git a/src/generateEventsHelper.js b/src/generateEventsHelper.js index e08d9c5..b27792c 100644 --- a/src/generateEventsHelper.js +++ b/src/generateEventsHelper.js @@ -59,11 +59,12 @@ while ( ( entry = zis.nextEntry) != null) { '', '#### Parameters ', '', - ' * callback - A function which is called whenever the ' + shortName + ' event is fired', + ' * callback - A function which is called whenever the ['+ shortName + ' event](http://jd.bukkit.org/rb/apidocs/org/bukkit/event/' + shortName.replace('.','/') + '.html) is fired', '', ' * priority - optional - see events.on() for more information.', '', '***/' +//http://jd.bukkit.org/rb/apidocs/org/bukkit/event/player/PlayerJoinEvent.html ]; for (var i = 0; i < comment.length; i++){ out.println(comment[i]); From 3b06ec5e08ab183d251245b3156a46332ccfd0a4 Mon Sep 17 00:00:00 2001 From: Pieter van Ginkel Date: Sun, 7 Sep 2014 09:12:58 +0200 Subject: [PATCH 220/456] Fixed class loader of the script engine. The script engine didn't have the plugin class loader. Instead, it had the thread context class loader. The problem with this is that this prevents scripts from instantiating classes from other plugins to e.g. implement undo when WorldEdit is available. The thread context class loader is now set to the plugin class loader while instantiating the script engine which solves this issue. --- .../scriptcraft/ScriptCraftPlugin.java | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java b/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java index 7a0681c..e000849 100644 --- a/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java +++ b/src/main/java/net/walterhiggins/scriptcraft/ScriptCraftPlugin.java @@ -1,13 +1,16 @@ package net.walterhiggins.scriptcraft; -import java.io.InputStreamReader; -import javax.script.*; -import java.util.List; -import java.util.ArrayList; - -import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.command.*; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; import org.bukkit.event.Listener; +import org.bukkit.plugin.java.JavaPlugin; + +import javax.script.Invocable; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; public class ScriptCraftPlugin extends JavaPlugin implements Listener { @@ -19,12 +22,15 @@ public class ScriptCraftPlugin extends JavaPlugin implements Listener @Override public void onEnable() { + Thread currentThread = Thread.currentThread(); + ClassLoader previousClassLoader = currentThread.getContextClassLoader(); + currentThread.setContextClassLoader(getClassLoader()); try{ ScriptEngineManager factory = new ScriptEngineManager(); this.engine = factory.getEngineByName("JavaScript"); if (this.engine == null){ this.getLogger().severe(NO_JAVASCRIPT_MESSAGE); - } else { + } else { Invocable inv = (Invocable)this.engine; this.engine.eval(new InputStreamReader(this.getResource("boot.js"))); inv.invokeFunction("__scboot", this, engine); @@ -32,6 +38,8 @@ public class ScriptCraftPlugin extends JavaPlugin implements Listener }catch(Exception e){ e.printStackTrace(); this.getLogger().severe(e.getMessage()); + }finally{ + currentThread.setContextClassLoader(previousClassLoader); } } public List onTabComplete(CommandSender sender, Command cmd, @@ -62,7 +70,7 @@ public class ScriptCraftPlugin extends JavaPlugin implements Listener this.getLogger().severe(NO_JAVASCRIPT_MESSAGE); return false; } - try { + try { jsResult = ((Invocable)this.engine).invokeFunction("__onCommand", sender, cmd, label, args); }catch (Exception se){ this.getLogger().severe(se.toString()); From 127697f774d212a60b8ccec55d43254953b7f778 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Fri, 12 Sep 2014 19:00:09 +0100 Subject: [PATCH 221/456] added documentation for the bukkit object. --- docs/API-Reference.md | 64 ++++++++++++++++++- src/main/js/lib/bukkit.js | 61 ++++++++++++++++++ src/main/js/modules/items.js | 7 +- src/main/js/modules/sounds.js | 9 ++- .../examples/example-7-hello-events.js | 2 +- src/main/js/plugins/minigames/cow-clicker.js | 5 +- 6 files changed, 133 insertions(+), 15 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 04f02b6..7700986 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -43,6 +43,7 @@ Walter Higgins * [module name resolution](#module-name-resolution) * [events Module](#events-module) * [events.on() static method](#eventson-static-method) + * [bukkit](#bukkit) * [console global variable](#console-global-variable) * [Example](#example) * [Using string substitutions](#using-string-substitutions) @@ -867,6 +868,65 @@ events.on( org.bukkit.event.block.BlockBreakEvent, function( evt ) { [buk2]: http://wiki.bukkit.org/Event_API_Reference [buk]: http://jd.bukkit.org/dev/apidocs/index.html?org/bukkit/event/Event.html + +### bukkit + +The bukkit global variable provides short names for commonly used Bukkit +Java classes and Enums. It also provides some helper functions for getting +players, player names and worlds. + +#### bukkit.stat and bukkit.stats + +This is a short name for the [org.bukkit.Statistic](http://jd.bukkit.org/rb/apidocs/org/bukkit/Statistic.html) Enum. + +##### Usage + + var jumpStat = bukkit.stat.JUMP; // var jumpStat = org.bukkit.Statistic.JUMP + +#### bukkit.material + +This is a short name for the [org.bukkit.Material](http://jd.bukkit.org/rb/apidocs/org/bukkit/Material.html) Enum. + +##### Usage + + var apple = bukkit.material.APPLE; + +#### bukkit.art + +This is a short name for the [org.bukkit.Art](http://jd.bukkit.org/rb/apidocs/org/bukkit/Art.html) Enum. + +##### Usage + + var sunsetArt = bukkit.art.SUNSET; + +#### bukkit.mode + +This is a short name for the [org.bukkit.GameMode](http://jd.bukkit.org/rb/apidocs/org/bukkit/GameMode.html) Enum. + +##### Usage + + var creative = bukkit.mode.CREATIVE; + +#### bukkit.sound + +This is a short name for the [org.bukkit.Sound](http://jd.bukkit.org/rb/apidocs/org/bukkit/Sound.html) Enum. + +##### Usage + + var oink = bukkit.sound.PIG_IDLE; + +#### bukkit.players() function + +This function returns a javascript array of all online players on the server. + +#### bukkit.playerNames() function + +This function returns a javascript array of player names (as javascript strings) + +#### bukkit.worlds() function + +This function returns a javascript array of all worlds on the server. + ## console global variable ScriptCraft provides a `console` global variable with the followng methods... @@ -2491,8 +2551,8 @@ a simpler way to play sounds. All of the org.bukkit.Sound Enum values are attach ### Usage: var sounds = require('sounds'); - sounds.play( org.bukkit.Sound.VILLAGER_NO , self, 1, 0); // plays VILLAGER_NO sound at full volume and medium pitch - sounds.play( org.bukkit.Sound.VILLAGER_NO , self ); // same as previous statement + sounds.play( bukkit.sound.VILLAGER_NO , self, 1, 0); // plays VILLAGER_NO sound at full volume and medium pitch + sounds.play( bukkit.sound.VILLAGER_NO , self ); // same as previous statement The play() function takes either a Location object or any object which has a location. The volume parameter is in the range 0 to 1 and the pitch parameter is in the range 0 to 4. diff --git a/src/main/js/lib/bukkit.js b/src/main/js/lib/bukkit.js index 42c407d..5928af3 100644 --- a/src/main/js/lib/bukkit.js +++ b/src/main/js/lib/bukkit.js @@ -1,3 +1,64 @@ +/************************************************************************ + +### bukkit + +The bukkit global variable provides short names for commonly used Bukkit +Java classes and Enums. It also provides some helper functions for getting +players, player names and worlds. + +#### bukkit.stat and bukkit.stats + +This is a short name for the [org.bukkit.Statistic](http://jd.bukkit.org/rb/apidocs/org/bukkit/Statistic.html) Enum. + +##### Usage + + var jumpStat = bukkit.stat.JUMP; // var jumpStat = org.bukkit.Statistic.JUMP + +#### bukkit.material + +This is a short name for the [org.bukkit.Material](http://jd.bukkit.org/rb/apidocs/org/bukkit/Material.html) Enum. + +##### Usage + + var apple = bukkit.material.APPLE; + +#### bukkit.art + +This is a short name for the [org.bukkit.Art](http://jd.bukkit.org/rb/apidocs/org/bukkit/Art.html) Enum. + +##### Usage + + var sunsetArt = bukkit.art.SUNSET; + +#### bukkit.mode + +This is a short name for the [org.bukkit.GameMode](http://jd.bukkit.org/rb/apidocs/org/bukkit/GameMode.html) Enum. + +##### Usage + + var creative = bukkit.mode.CREATIVE; + +#### bukkit.sound + +This is a short name for the [org.bukkit.Sound](http://jd.bukkit.org/rb/apidocs/org/bukkit/Sound.html) Enum. + +##### Usage + + var oink = bukkit.sound.PIG_IDLE; + +#### bukkit.players() function + +This function returns a javascript array of all online players on the server. + +#### bukkit.playerNames() function + +This function returns a javascript array of player names (as javascript strings) + +#### bukkit.worlds() function + +This function returns a javascript array of all worlds on the server. + +***/ var bukkit = { stat: org.bukkit.Statistic, stats: org.bukkit.Statistic, diff --git a/src/main/js/modules/items.js b/src/main/js/modules/items.js index a3b60cd..4b87d60 100644 --- a/src/main/js/modules/items.js +++ b/src/main/js/modules/items.js @@ -1,13 +1,12 @@ -var bkItemStack = org.bukkit.inventory.ItemStack, - bkMaterial = org.bukkit.Material; +var bkItemStack = org.bukkit.inventory.ItemStack; var items = function( material, amount ) { material = material.toUpperCase(); - return new bkItemStack(bkMaterial[material],amount); + return new bkItemStack(bukkit.material[material],amount); }; module.exports = items; -var materials = bkMaterial.values(); +var materials = bukkit.material.values(); for (var i = 0;i < materials.length; i++ ){ var name = (''+materials[i].name()).toLowerCase(); diff --git a/src/main/js/modules/sounds.js b/src/main/js/modules/sounds.js index 84114fb..e580092 100644 --- a/src/main/js/modules/sounds.js +++ b/src/main/js/modules/sounds.js @@ -1,8 +1,7 @@ -var bkSound = org.bukkit.Sound, - bkLocation = org.bukkit.Location, +var bkLocation = org.bukkit.Location, i = 0, foreach = require('utils').foreach, - allSounds = bkSound.values(), + allSounds = bukkit.sound.values(), len = allSounds.length, sound, soundName; @@ -47,8 +46,8 @@ a simpler way to play sounds. All of the org.bukkit.Sound Enum values are attach ### Usage: var sounds = require('sounds'); - sounds.play( org.bukkit.Sound.VILLAGER_NO , self, 1, 0); // plays VILLAGER_NO sound at full volume and medium pitch - sounds.play( org.bukkit.Sound.VILLAGER_NO , self ); // same as previous statement + sounds.play( bukkit.sound.VILLAGER_NO , self, 1, 0); // plays VILLAGER_NO sound at full volume and medium pitch + sounds.play( bukkit.sound.VILLAGER_NO , self ); // same as previous statement The play() function takes either a Location object or any object which has a location. The volume parameter is in the range 0 to 1 and the pitch parameter is in the range 0 to 4. diff --git a/src/main/js/plugins/examples/example-7-hello-events.js b/src/main/js/plugins/examples/example-7-hello-events.js index 58b62b5..6c32f8f 100644 --- a/src/main/js/plugins/examples/example-7-hello-events.js +++ b/src/main/js/plugins/examples/example-7-hello-events.js @@ -93,7 +93,7 @@ Update: Since version 2.0.8 the above code can be replaced by the more succinct: }); ***/ -events.on( 'player.PlayerJoinEvent', function( event ) { +events.playerJoin( function( event ) { if ( event.player.op ) { event.player.sendMessage( 'Welcome to ' + __plugin ); } diff --git a/src/main/js/plugins/minigames/cow-clicker.js b/src/main/js/plugins/minigames/cow-clicker.js index 6ed4250..cde048b 100644 --- a/src/main/js/plugins/minigames/cow-clicker.js +++ b/src/main/js/plugins/minigames/cow-clicker.js @@ -44,7 +44,6 @@ your own mini-game... var store = {}, bkBukkit = org.bukkit.Bukkit, bkCow = org.bukkit.entity.Cow, - bkSound = org.bukkit.Sound, bkOfflinePlayer = org.bukkit.OfflinePlayer, scoreboardConfig = { cowclicker: { @@ -71,9 +70,9 @@ var _onPlayerInteract = function( event ) { scoreboard.update( 'cowclicker', player, store[ player.name ].score ); bkBukkit.dispatchCommand( player, 'me clicked a cow!' ); - sound( bkSound.CLICK, 1, 1 ); + sound( bukkit.sound.CLICK, 1, 1 ); setTimeout( function( ) { - sound( bkSound.COW_HURT, 10, 0.85 ) ; + sound( bukkit.sound.COW_HURT, 10, 0.85 ) ; }, 200 ); } }; From f2e54d2168e30bd2f07d74f7420627c19f5fb9cf Mon Sep 17 00:00:00 2001 From: Walter Higgins Date: Wed, 17 Sep 2014 14:23:38 +0100 Subject: [PATCH 222/456] Update Anatomy-of-a-Plugin.md --- docs/Anatomy-of-a-Plugin.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Anatomy-of-a-Plugin.md b/docs/Anatomy-of-a-Plugin.md index 647a42b..5ee8ba4 100644 --- a/docs/Anatomy-of-a-Plugin.md +++ b/docs/Anatomy-of-a-Plugin.md @@ -46,7 +46,7 @@ chosen color... var colorCodes = {}; for (var i =0;i < colors.length;i++) colorCodes[colors[i]] = i.toString(16); - events.on( 'player.AsyncPlayerChatEvent', function( evt ) { + events.asyncPlayerChat( function( evt ) { var player = evt.player; var playerChatColor = _store.players[ player.name ]; if ( playerChatColor ) { @@ -112,7 +112,7 @@ code is just a couple of lines of code but is a fully working plugin... colorCodes[ colors[i] ] = i.toString(16); } - events.on( 'player.AsyncPlayerChatEvent', function( evt ) { + events.asyncPlayerChat( function( evt ) { var player = evt.player; var playerChatColor = _store.players[player.name]; if ( playerChatColor ) { From a15721322fed4e9998f9cccca78ed07eb105338f Mon Sep 17 00:00:00 2001 From: Walter Higgins Date: Sun, 21 Sep 2014 10:48:25 +0100 Subject: [PATCH 223/456] Update YoungPersonsGuideToProgrammingMinecraft.md --- docs/YoungPersonsGuideToProgrammingMinecraft.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/YoungPersonsGuideToProgrammingMinecraft.md b/docs/YoungPersonsGuideToProgrammingMinecraft.md index 443d214..a107155 100644 --- a/docs/YoungPersonsGuideToProgrammingMinecraft.md +++ b/docs/YoungPersonsGuideToProgrammingMinecraft.md @@ -171,9 +171,9 @@ called `location`. We can use that name like this... Blackrock Castle -...You might be wondering where the `''` (called double-quotes) went. +...You might be wondering where the enclosing `'` single-quotes went. When telling the computer to store some text, you have to put `'` -(that's the double-quote character - press Shift+2) at the start and end +(that's the single-quote character) at the start and end of the text. The computer doesn't store these quote characters, only the text between them. The computer will store the variables while the Minecraft Server is running. Repeat the last command you entered by From d744ceeb7bf07e2cbc1291727df6ba58d85cdf65 Mon Sep 17 00:00:00 2001 From: Walter Higgins Date: Sun, 21 Sep 2014 10:51:13 +0100 Subject: [PATCH 224/456] Update ypgpm.md --- src/docs/templates/ypgpm.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/docs/templates/ypgpm.md b/src/docs/templates/ypgpm.md index 82f3433..5902577 100644 --- a/src/docs/templates/ypgpm.md +++ b/src/docs/templates/ypgpm.md @@ -135,9 +135,9 @@ called `location`. We can use that name like this... Blackrock Castle -...You might be wondering where the `''` (called double-quotes) went. +...You might be wondering why there's no enclosing `'` single quotes. When telling the computer to store some text, you have to put `'` -(that's the double-quote character - press Shift+2) at the start and end +(that's the single-quote character) at the start and end of the text. The computer doesn't store these quote characters, only the text between them. The computer will store the variables while the Minecraft Server is running. Repeat the last command you entered by From cc4f98474d38e644fac95bd1a459109b0dc3c2f1 Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Thu, 25 Sep 2014 20:36:39 +0100 Subject: [PATCH 225/456] moving bukkit.js from lib to modules (it shouldn't be part of core) --- src/main/js/lib/events.js | 70 ++++++++------------------ src/main/js/lib/legacy-check.js | 27 ++++++++++ src/main/js/lib/scriptcraft.js | 26 +--------- src/main/js/{lib => modules}/bukkit.js | 0 4 files changed, 49 insertions(+), 74 deletions(-) create mode 100644 src/main/js/lib/legacy-check.js rename src/main/js/{lib => modules}/bukkit.js (100%) diff --git a/src/main/js/lib/events.js b/src/main/js/lib/events.js index c7e5d8e..c0c1660 100644 --- a/src/main/js/lib/events.js +++ b/src/main/js/lib/events.js @@ -13,16 +13,8 @@ This method is used to register event listeners. #### Parameters - * eventName - A string or java class. If a string is supplied it must - be part of the Bukkit event class name. See [Bukkit API][buk] for - details of the many bukkit event types. When a string is supplied - there is no need to provide the full class name - you should omit - the 'org.bukkit.event' prefix. e.g. if the string - "block.BlockBreakEvent" is supplied then it's converted to the - org.bukkit.event.block.BlockBreakEvent class . - - If a java class is provided (say in the case where you've defined - your own custom event) then provide the full class name (without + * eventName - A java class. See [Bukkit API][buk] for + details of the many bukkit event types. Provide the full class name (without enclosing quotes). * callback - A function which will be called whenever the event @@ -43,7 +35,7 @@ An object which can be used to unregister the listener. The following code will print a message on screen every time a block is broken in the game ```javascript -events.on( 'block.BlockBreakEvent', function( evt ) { +events.on( Packages.org.bukkit.event.block.BlockBreakEvent, function( evt ) { evt.player.sendMessage( evt.player.name + ' broke a block!'); } ); ``` @@ -51,7 +43,7 @@ events.on( 'block.BlockBreakEvent', function( evt ) { To handle an event only once and unregister from further events... ```javascript -events.on( 'block.BlockBreakEvent', function( evt ) { +events.on( Packages.org.bukkit.event.block.BlockBreakEvent, function( evt ) { evt.player.sendMessage( evt.player.name + ' broke a block!'); this.unregister(); } ); @@ -65,19 +57,10 @@ object which is returned by the `events.on()` function. To unregister a listener *outside* of the listener function... ```javascript -var myBlockBreakListener = events.on( 'block.BlockBreakEvent', function( evt ) { ... } ); +var myBlockBreakListener = events.on( Packages.org.bukkit.event.block.BlockBreakEvent, function( evt ) { ... } ); ... myBlockBreakListener.unregister(); ``` - -To listen for events using a full class name as the `eventName` parameter... - -```javascript -events.on( org.bukkit.event.block.BlockBreakEvent, function( evt ) { - evt.player.sendMessage( evt.player.name + ' broke a block!'); -} ); -``` - [buk2]: http://wiki.bukkit.org/Event_API_Reference [buk]: http://jd.bukkit.org/dev/apidocs/index.html?org/bukkit/event/Event.html @@ -97,46 +80,35 @@ var nashorn = (typeof Java != 'undefined'); function getHandlerListForEventType( eventType ){ var result = null; var clazz = null; - if (!(typeof eventType == 'string')){ - // it's a fully qualified event class - if (nashorn) { - - //Nashorn doesn't like when getHandlerList is in a superclass of your event - //so to avoid this problem, call getHandlerList using java.lang.reflect - //methods - clazz = eventType['class']; - result = clazz.getMethod("getHandlerList").invoke(null); - - } else { - result = eventType.getHandlerList(); - } + if (nashorn) { + + //Nashorn doesn't like when getHandlerList is in a superclass of your event + //so to avoid this problem, call getHandlerList using java.lang.reflect + //methods + clazz = eventType['class']; + result = clazz.getMethod("getHandlerList").invoke(null); + } else { - // it's an event class name partial - if (nashorn) { - clazz = java.lang.Class.forName(bkEventPackage + '' + eventType); - result = clazz.getMethod("getHandlerList").invoke(null); - } else { - var eventType2 = eval( bkEventPackage + eventType ); - result = eventType2.getHandlerList(); - } + result = eventType.getHandlerList(); } + return result; } exports.on = function( - /* String or java Class */ + /* Java Class */ eventType, /* function( registeredListener, event) */ handler, /* (optional) String (HIGH, HIGHEST, LOW, LOWEST, NORMAL, MONITOR), */ priority ) { var handlerList, - listener = {}, + regd, eventExecutor; if ( typeof priority == 'undefined' ) { priority = bkEventPriority.HIGHEST; } else { - priority = bkEventPriority[priority.toUpperCase()]; + priority = bkEventPriority[priority.toUpperCase().trim()]; } handlerList = getHandlerListForEventType (eventType); @@ -154,10 +126,10 @@ exports.on = function( The workaround is to make the ScriptCraftPlugin java class a Listener. Should only unregister() registered plugins in ScriptCraft js code. */ - listener.reg = new bkRegisteredListener( __plugin, eventExecutor, priority, __plugin, true ); - handlerList.register( listener.reg ); + regd = new bkRegisteredListener( __plugin, eventExecutor, priority, __plugin, true ); + handlerList.register( regd ); result.unregister = function(){ - handlerList.unregister( listener.reg ); + handlerList.unregister( regd ); }; return result; }; diff --git a/src/main/js/lib/legacy-check.js b/src/main/js/lib/legacy-check.js new file mode 100644 index 0000000..9db7578 --- /dev/null +++ b/src/main/js/lib/legacy-check.js @@ -0,0 +1,27 @@ + + /* + wph 20140102 - warn if legacy 'craftbukkit/js-plugins' or 'craftbukkit/scriptcraft' directories are present + */ +module.exports = function( jsPluginsRootDir ) { + 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() ) { + + legacyExists = true; + + console.warn('Legacy ScriptCraft directory %s was found. This directory is no longer used.', + legacyDirs[i].canonicalPath); + console.warn('Please put plugins in the plugins/scriptcraft/plugins directory'); + } + } + if ( legacyExists ) { + console.info( 'Please note that the working directory for %s is %s', + __plugin, jsPluginsRootDir.canonicalPath ); + } +}; diff --git a/src/main/js/lib/scriptcraft.js b/src/main/js/lib/scriptcraft.js index 7369e6c..5e5c781 100644 --- a/src/main/js/lib/scriptcraft.js +++ b/src/main/js/lib/scriptcraft.js @@ -714,30 +714,6 @@ function __onEnable ( __engine, __plugin, __script ) }; 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() ) { - - legacyExists = true; - - console.warn('Legacy ScriptCraft directory %s was found. This directory is no longer used.',legacyDirs[i].canonicalPath); - console.warn('Please put plugins in the plugins/scriptcraft/plugins directory'); - } - } - if ( legacyExists ) { - console.info( 'Please note that the working directory for %s is %s', - __plugin, jsPluginsRootDir.canonicalPath ); - } - })(); + require('legacy-check')(jsPluginsRootDir); } diff --git a/src/main/js/lib/bukkit.js b/src/main/js/modules/bukkit.js similarity index 100% rename from src/main/js/lib/bukkit.js rename to src/main/js/modules/bukkit.js From 19162c368801ceae1a483908cd4dadc9dc4c3bfb Mon Sep 17 00:00:00 2001 From: walterhiggins Date: Mon, 29 Sep 2014 23:42:41 +0100 Subject: [PATCH 226/456] First phase of transition from Bukkit to Canary. Some of the plugins are not yet supported. If you're feeling brave you can build from source using ant. --- build.xml | 68 ++- ...YoungPersonsGuideToProgrammingMinecraft.md | 2 +- lib/canary.jar | Bin 0 -> 23065389 bytes src/generateEventsHelper.js | 46 ++- .../canarymod/ScriptCraftPlugin.java | 143 +++++++ src/main/js/lib/console.js | 57 +-- src/main/js/lib/events-bukkit.js | 63 +++ src/main/js/lib/events-canary.js | 45 ++ src/main/js/lib/events.js | 87 +--- src/main/js/lib/js-patch.js | 47 ++- src/main/js/lib/legacy-check.js | 26 +- src/main/js/lib/plugin.js | 12 +- src/main/js/lib/require.js | 19 +- src/main/js/lib/scriptcraft.js | 391 ++++++++++-------- src/main/js/lib/tabcomplete-jsp.js | 2 +- src/main/js/lib/tabcomplete.js | 29 +- src/main/js/lib/task-bukkit.js | 22 + src/main/js/lib/task-canary.js | 33 ++ src/main/js/modules/bukkit.js | 94 ----- .../{fireworks => bukkit}/fireworks.js | 43 +- src/main/js/modules/bukkit/input.js | 28 ++ src/main/js/modules/bukkit/items.js | 28 ++ src/main/js/modules/bukkit/sounds.js | 62 +++ src/main/js/modules/canary/fireworks.js | 32 ++ src/main/js/modules/canary/input.js | 26 ++ src/main/js/modules/canary/items.js | 45 ++ src/main/js/modules/canary/sounds.js | 65 +++ src/main/js/modules/fireworks.js | 42 ++ src/main/js/modules/fireworks/package.json | 4 - src/main/js/modules/input.js | 38 +- src/main/js/modules/items.js | 30 +- src/main/js/modules/signs/menu.js | 4 + src/main/js/modules/signs/signs.js | 2 +- src/main/js/modules/sounds.js | 68 +-- src/main/js/modules/utils/string-exts.js | 2 +- src/main/js/modules/utils/utils.js | 127 +++++- src/main/js/plugins/alias/alias.js | 34 +- src/main/js/plugins/arrows.js | 5 +- src/main/js/plugins/classroom/classroom.js | 33 +- src/main/js/plugins/commando/commando-test.js | 6 +- src/main/js/plugins/commando/commando.js | 8 +- .../js/plugins/drone/contrib/dancefloor.js | 9 +- src/main/js/plugins/drone/drone.js | 101 +++-- .../examples/example-1-hello-module.js | 4 +- .../examples/example-2-hello-command.js | 4 +- .../examples/example-3-hello-ops-only.js | 10 +- .../examples/example-4-hello-parameters.js | 4 +- .../examples/example-6-hello-player.js | 4 +- .../examples/example-7-hello-events.js | 13 +- src/main/js/plugins/homes/homes.js | 38 +- src/main/js/plugins/minigames/cow-clicker.js | 10 +- src/main/js/plugins/signs/examples.js | 56 +-- src/main/js/plugins/spawn.js | 29 +- src/main/resources/Canary.inf | 4 + src/main/resources/boot.js | 40 +- 55 files changed, 1436 insertions(+), 808 deletions(-) create mode 100644 lib/canary.jar create mode 100644 src/main/java/canary/org/scriptcraftjs/canarymod/ScriptCraftPlugin.java create mode 100644 src/main/js/lib/events-bukkit.js create mode 100644 src/main/js/lib/events-canary.js create mode 100644 src/main/js/lib/task-bukkit.js create mode 100644 src/main/js/lib/task-canary.js delete mode 100644 src/main/js/modules/bukkit.js rename src/main/js/modules/{fireworks => bukkit}/fireworks.js (58%) create mode 100644 src/main/js/modules/bukkit/input.js create mode 100644 src/main/js/modules/bukkit/items.js create mode 100644 src/main/js/modules/bukkit/sounds.js create mode 100644 src/main/js/modules/canary/fireworks.js create mode 100644 src/main/js/modules/canary/input.js create mode 100644 src/main/js/modules/canary/items.js create mode 100644 src/main/js/modules/canary/sounds.js create mode 100644 src/main/js/modules/fireworks.js delete mode 100644 src/main/js/modules/fireworks/package.json create mode 100644 src/main/resources/Canary.inf diff --git a/build.xml b/build.xml index 6b4e694..ad9acd5 100644 --- a/build.xml +++ b/build.xml @@ -1,7 +1,7 @@ Builds the scriptcraft.jar file - a plugin for bukkit - + @@ -24,46 +24,15 @@ - - - - - Retrieving CraftBukkit artifact info - - - - - - Retrieving CraftBukkit jar - - - Creating default ops.txt for your user - - - - - - - Starting Bukkit with ScriptCraft - - - + + classpath="lib/canary.jar" /> @@ -87,14 +56,33 @@ - + - + + + + + + + + + + + + + + + + + + + + @@ -148,7 +136,7 @@ Walter Higgins - + @@ -185,7 +173,7 @@ Walter Higgins - + @@ -206,7 +194,7 @@ Walter Higgins - + diff --git a/docs/YoungPersonsGuideToProgrammingMinecraft.md b/docs/YoungPersonsGuideToProgrammingMinecraft.md index a107155..cb1138e 100644 --- a/docs/YoungPersonsGuideToProgrammingMinecraft.md +++ b/docs/YoungPersonsGuideToProgrammingMinecraft.md @@ -171,7 +171,7 @@ called `location`. We can use that name like this... Blackrock Castle -...You might be wondering where the enclosing `'` single-quotes went. +...You might be wondering why there's no enclosing `'` single quotes. When telling the computer to store some text, you have to put `'` (that's the single-quote character) at the start and end of the text. The computer doesn't store these quote characters, only the diff --git a/lib/canary.jar b/lib/canary.jar new file mode 100644 index 0000000000000000000000000000000000000000..d11673ee10a27e32fa1fd8ecace6d002d3ed163b GIT binary patch literal 23065389 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

@~HG+jZ0|nNlKDTErmuB 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

T-962OMe44YMv81ve~GBp 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�-=5a@KALTvX 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

x0#@{E#AyCPd2s+onkacNz1B~EU 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�noU}z^fd9<2 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

hJ_PY@H>^ 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

e!_UX6nQ>(@!<=i4&&W>O@(N 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⪙c4QN?j`n*lR*ZHu<(n{MY4TN+(H=8Tq$3v7-_Reoi9Ka#?$O`zH_4Mc{j( z&_HWYT-P(5H$-(hv)BUs*v=UEm_4#X#Y!jheO7oR$561Dqqw399MScHL~ZH%{F_6p z%Jp5OYh$-a?!!+r{>f9G%XZ`|7^Noo(IYeWl2k-n76xZ4Zo&PtRXXRrocc@+vJDx$ z&=R1QO}y+ZS-J(N;~eoZNv6g<2X9|!sqTJwEg?jkvAxn=R0742wj*o6QRMBL7%qxTu=&3`1xT01#4tVAE52aMdepok_!wa$WoDb5cTsN|FofUv!x9o)&Fb5>fP5 zVifdKWAtvJ`0!2cYv4HHJ!RnW09Z+3e2&6ewLee$6^w=RXsEsoaCvro?+Fg5e5d*n z-#E_8$o$N~F+~0KS*=?%P`+)J9T!Ttq*cf{zS%L|Sz-7~6y5U-!<*KAQ3m?0BIE~eWhgDEQKO$E zW_oO_O^pWZpYXJM*KA=^Kn;1TQQg&~pF4nc=#xFc)lb*XdKpx>0nIpIx=|rc9Q|H% z=s7v@*-25z_%YK4YqYatVC!0HdQ3g}Hmr7b2=tI2cnE@P6PRW;@G<9X>8xKCc4iW} zW{^bF)5%m>+&Xhe`B7ED zV7h3hVbSHi*$Y}*g0-PL7%)xp4oTh}V)6I+!=dEN#&Cx|v|&NRN_Oy;4?(iP5p%90 z(KFe@9>}mLatQbt%cmTRK`qQ9vnWMZRu1{iud_Dh@F4r~u`x$DJqPZzD5P{#Q}b5u zQmaLYz&WZo^&z@Zhm2OHz&Bv?@iW+S$gW z?H7aLp8RO0S}K1!Hc4Zz$c9hn3G#5$8hS}@ob@-!KYqbUZf_<3NvD;31~bO$4M~li zMiGx{>9tF*w}oK{dkeFYaVBs2n|$p_HE#shzdlopMMzUYgkgqBRiVhJN2Q$E!bhUh zCJBkDeNa+^O9p+Xyv(f=>{raXtQjZ{cMiULiDWokpdY)-#3G*6acarmbBg)LCX9}f zgvPg=e1jncya$g%#AT)j(%&=9Sbl7s4v=Az50BR9FG;$i?q~b;Il_~!N+YQ}az$>K z<}|guA;s|@=u-)YI0ixy5mNpyJS$JJ5fw_xos?lyFm}v0PzC*5vviC*!(FEN+yM|ymEhbM$_VTXU z1b)kX`M#+?rTcST#ht|JoGgtU?Q?~bDQu3<JB3-Tjkd&H{>V_ zAOgZ>9>m)-yqZ*2z5g1c{mKL8;-<08J-0+*o>kR$G_>r|80I;>rp*+LpfJ7KvURQ5 z4SNc?WZj2t{K}|DPS9x=Vj0Cbiy&QaUt;rZXr zN92rMEY1I)jMqGkD^)a2^nW|ZTFv#yCB(QWtvZiCG?i)?Y-k`L@Djn&L<({mM^?#m z81d59G`@3_excQX!U8^1(TX{@PczF8DtF$tv+ddKqsg z@66|=IplK#pP!`SwFw(`UV@`AnT=Qs>&;Im5QeSagwBXk<;@AaIhDEmb?uLdH!%uF zvUENXsAFGA1+Y!j#(hv&G22&$M)xU>rHt{|VETMJ6kXwo7<@fEDZ!J=Bk<*A z35$g}jBAs=ZBlu1&p5fw6qxJ1ZG|6s%`pnu)$@MK8f8R?hOG72xZTK$PRNR?CPedz z%8i5+R!Xo$Ef&Ka9Nb!Fw~>%o-ND7-be*)w|BxovoDgJFaS`tJ^&xxe4+MG}4j>(D zxPuG;)1RM#lMySuc+Uyp2~Kju43r&NzOjlB?Z?+E+T*NPl`^n`tIUD(*w?NIhWoO+ zh$`t3^%%`+{Cq;jh8zR{!=|ZqIY{jjJcPYNzMztSN2_SudO54r!kTe@685dq8&eZt zRO{`!_NpO#jf2n3-N_t0C9i1h1NH9X(0NFU*(HM+EIZ?OIB{aOZ^&z`nOS_SzgszXBMU^g>6&%5*zIwpq6IFdC(F~ z34&;d3s7)04`LMc`P{gTVkNCPK}3Syx3Qy|#3o+_2QfRMJ9c(}k@bIKl7b_V;Zi8S zBu?d@lTbzI+Q}4>*P3~{ouL!Ajn9_|;*91e89o+Z7Fq-_@8a86J80;veyym}wI|jP zgWd|~xVb|jz^gc~-gcSPM+i>(Ci6v`KC_H%J0D(Byumxe7Ud62KBkEwu~)U>_rRSn z;@%lu5Q4Qdp{~4x>v%Sucv2sZv+4$a1A{5pcO;E!dqPk7xW^x%tPUh&e+2g%=~Al= zz2Zoqzcq#AGqmbh-;|#8+?=JLZi@8C{L&WzhWFkaG+vqg`m-p-F1KM{Vf1>a?N&&X zmsyHxP(iv|8ta0~E8UNEE`bzowU|xPo|2z-lU1rfw@*LasH`? z&fFtQp?5A|JkjUr18~G=Pp%}rHI6bb=g)4>^c~~f>=jag{})BzbdoB{&}@FXXkaw4 z2X&`-7!;qZbz{uBgL&~&V*|P~IXX6dXAk;cY$aopj%c1=!)A%n$!FnOf@P#}#`E6R znMQ$#lp%7-Wt~Jwjn5&m zrfP$tu!NZR`u62=HR49rg>2?p2PACfBWxcIbiO{kDRQA5EcS@6(7??l7*QhwDrq}h zoJ%%V-IqS{*;LQ+BkyL(`AmKfN`bY&EDSAy(aNM|Ia@;@cgn8m2m_mS=+=qcTVlSP z#BA)MWdAIf!G(UI3#1Yl0h=-7^@hi~^(}|$-#YI#t9UU|{2gB(0UE3iE8)UEDLAnD zkuosSp&4^IMy$#_7#%VHUv+egBW4&Wt7f8DR=L$YB3`hLaxML{e!1=SZfmVGZZhek zTN!fWP}1@?qc5rX8(gUhp)bbE4>W-01J16MDe&OhtiLqYrDS=8xv?*fH=x<>E2jUD zkG16EImY0}ZB%OWL9I>2N$?bwx*q{|%naj8LnJt=I%DkDBjm{RyuLxr4zdYI4l5Dn z>y!EZVuPTYcA?HOfqcl@j7pl!B`AJUcU;}eWLcey_~tweBo)mrGKCfz4AB8xmSnVr zo2tqwIeUhMRhqYpZP}k2KG;~$7<=)!yW>!5<;6NmgD42&_b8%YT7%9A1^W;oA8NzW zxu{kjL{#wuQ-`fz8kJ1Vvhy)ly&_xb)>Kf%%Dao23ij)Vi1HdQIPrB=f4p!jRt8BK zlvr+*#jkFj7fv(R9ku?A;lV@1ye%;`nw3ALn$cQnVOk-{-OaGD)CBs*jhg?aod;+7 zGs}~$#wH6o5?4Bi(`is>lV0+}sPk9#2^^;>FKm9WDk9O~-NujD5>&tEkqeUEz900M zM^APReP#*_S8y6LjBi2^lh4hhBh>Q8KW_ICF!O|#ZtNV3Cf^jT*Ba07+a;RuMd`*D z)K|}ke!sq7n37YCs-je>EhwkUouJ{4K za{2%_IpRPgGbzQgS+P#KsyjB;FmwwpU3{Cli`2YXPk}A+vavGNWi|N=wu?2)1BtQ) z+s^uBSq|5VD2?CsH$^cPiMKyLAB69xAW5pNf8fZn2L z_xZthXgco36pJp&=9i;jsm3IcU8g`Mexg?xwv&6Jxf_nxP%EU= zW)Z79m^1o*=vR{LzrFi?q4r;{Q!1&zx0Yop7Jiy_rY}t|v=@fPofNHpU(!DqCs~LS zj+!Q$s=OM zoU^q8;F6*_2#aIZfl(TS2fymW{vNC_R#Yf|{Q(TkP=Jw^xqG+EZ&Ch2&N9I@3@fKi-q~E25MC3Dzr=sFJ+YcWKC zzmGl;e)gvz2ru;H=`T^gvAz`i8UDn)pUfg)m6|nh4}|SY;?3UfybaFokeRjd;pjKk z>qHTVZ?o@=s*FLMEV(g)qpjN!&AB=Mi0=O%tfrUY;KVIR5Rk`jIp}|_n!^6C>K9|{ z|6ev2WdF@N#%^vd)+X+5=C1$0mi%ty{eNSmzF+NsT2jQ=)m-@hdcyz7ky5==TvkFC zaHMmq$r}b06?P7k*#aG<`2rJ$ke5OhGGe~JsY_M9?k>BE{UrIqqO>_APZAT#ewVCz zb9WuMhB}MbUEj_3neRIL`F3=c(E##IDNh`dL#35Nu`u73D=hpm#DLR*F_DS(n~!u` z-3jTtBU@JS%ZsJ9W@ zOs$WH5$E#BRQ+U4{N%sS`Ybxjhm~f}!G~OM`oeS5-N^i{an=<2tWKj8ERW^o6*^GopEWNeK?6X zT@R@+N=C&A+?Y#(Z$Lec_L!^bxy`g{8)06J6H$JW8YOO~{b2dQS+h{MT)C4yvEW(! z>Gd$s4iRH(wef9oTiU1FUP5dP#w!ZzgF-HLM{RL5cr zQJ3r#rjfN9?v%8Uael?%C*68EhLV}&NNiz+F!HpQ#2en4cflia$907L$O(#yKrV#J z9d8_6vH~kckK&$OIgjQXUdg1o66qHDbqXyzOXCTE-jjeC9O|eEjX?n;eTpG=#xZ3b z69?=l%C(Ybw?;=39-)~K4MjotIcCm;v)iC86dSYuZu4-i)yEImQ|8Cl-cGnenVIT| zKU|zRWOrzd&VVLB^T#7YpdO2IqL52aT@?F8x)s_ckjx^DN11tX+@6i0BZE2=GS9tE zWAT`pDW)46sets9lp+^ad9n8&88BpjNB7{jzYO^I9{#WQ?|+#A|2LaN-q`6s z_eq(Cy(*qMy1xUemQ;J$k6#-oRsd8NK$CETH}j58l$mo^Ryi&wa}>H zz9ygPR@5A~|02#`xG!{qm#!S-+LEDHUR0|XtAD_FgBHF2>KQyA&i8ptFU^0s;kj&OzI zcGQO}xYAf;tokDpOyENDG~YT^pvK5-W`{?n1uX9=-QEihY!GN7N}TG_Ub@s~*QLQ| zQRcHZgGqfM%g58wXxyE?xgJX5I8F;QQ)+*KBc6S%=eosKdZWGDXRH%`;Z>i;;L~hI zpH1O)WuzO|k0umy zE=KhpDH$;OKN$PQ;LO@>+fF)8-q^M~PRF)wzp-s~Y;|ng9d&Hm9oz2cjc@ijyUs<` z{c2aOA8Y-cPpvt}9M70@@Xk8O(3C31b4@$h5EjQKS4B6N+P-MGu+_C~?Ev-i?MxTZ zA?&(rU;^N8T&8GnJ%>lJih50F>$#ppqh(vSwZt|%1LuUUF8Pmm7PSoOZBI(z%*x5N zRO=6&2p*fT^I8Cx)>K`QH9)}69+2aY`M$qJ)?Tr6$hJRzD<>-NEwq^*c9ENXBwK*O z4^mS%ORO(1r8DFPD~l6ZZRaxsRP3>```7Hzb{WdMq>Y@87|G;yPs*LepB~GektwNCy4Y0qpBNK!%lQyx zVf5S_fPGd+EAd{Dphx0AEDY)4wkQ}x94AFiv#_C1dubg|LgBg1(W0(QWqST_rO7K_ zHlew+zk1O!3C!1Mdu2mGS` z$}D)#k~V0o57~?BVFz$|SHU%9l+<8YSD$evrVW{-_SZX@L45|JXZrZP=CXReCHhGp zsv?7((TY9xmRIAQauqb6b7p_c_0B+yhypC6?4q3#oxEx!f`*~%#t4Sg_Kjs@0&p=KP*&-%6t4JcP_l#oc9v$`(keb;|AGVi4yo6FUkO(BN~ri; zC~nz6P`vgbWm%Q1Jntu{RE4jcWhh4nUI94-V;x*a$t(kMF5--Hr8oS_fombZY?)I# zzh3pPN7pv5oM`41O$H2ej=r;UE$4CA-CYWJL`>hqq(@ad?~a zKdbf1Mt0_=CjW1>SJP8LlS1dqPB#f<3cyBb_#O4>jfQ4QygST5A1(@4IRjl^=W`xR z87og&Zd3r0S%)6)&Qp(2tyXJ)>F_uC7`)uPj3x=yq~M>pSaW|~_r4s>p8DM2{SN(u zWod2%=mt}mmRMr5T@mrb=Az5$xG@0u$}x}KH~sg5agJVc0GQEX0HDXwG&9V~f#Xy2 zvO;K$DShQ~_a$7|n54c7dnuYJMh#;{i^3^L>jnqMSwsO{Cinj9z_`PMPDh7(z z@FijWZoR&>n@{!Vs2G>y(T;G77ZAPQ+=Vqf*XWJ@S6wC;Vfg|v5f{k=^lNtA&GQT%vGR0KiLzk}>O7=~BlDt; za*3DbUDIF;XXR&zW0fXnKvqcN2y+&X&0Wn^6!)V>pI5t()y7I?g!(@F*HD)GGxpV( zKIav~0Bp#~GDVD#3XR&RI%K{bq5R%Q#A(@4;TD`jT3}eq+cC_Xeb+I#ccOw)Q>{^# zevIJovZk_blVUFcyfLR~elXpfZfpaAz1uPL3)GBl7o8XJ$~&_$HqqV9sKV7k9!V(( z@ZwFWM=D`$AdlQXaYKoy;xcM-k}(&b$SS5UAB#~9OG8slSw@jXnOV9a;9{HJXva_w9EX@S47BS)drD`+WYteh-?X!LD-j7*W{2V6Jo~Pja;VH1W7i_;; zi&sH6sD6`gMe!s&!Y<;>LTrr4gQyoZD1%}JE#d=&$W5@^d~roZA*>)2#Z3j#TpIhI zOt1&p6bc&p0UhW~NMnU>66J|dQ#hQpmbmegsDB|KdE^+;w08lejY#93r3(q7^R&D| z#naTNd)dZ%sN%ALJERPQqqw^0{_hX}r#{(ZK5H1Zmn zW!KhuNv+oTrP3|8WPFW^tWy6}_)l$}rK!r4GSbQ@!-`Zfno7p8WVFdhi_-a=yo)8( z=EH0bf#ezv%+ho3?bqHrjNxr3`ZOu$pHJ{2Zy6isy_p0)Zese75)dEZ34RVvhBr8_ zV{b(N!X@D1U#qOYe)-Z5{ZF_=;6MAJRZN}SOr4x9P5w88{~vVHq^_g!c{}nhABdBN zph+rRRhE|$3V~lMCZrn80T-ZNg|5AZI7)&eXXe17bn>R}-So)dFJy40jUla9*~ECE z?R#eY5!HFliW5p+r1#@uoXh-S<roz;KbFk96NWup4=R)kD3{c~=}B2qz^ZCAHm@mVzRzEsl&Mv_PSoG#e-r z$U!&4MPdkH1Qe%SJs_sv52{l_9#1C5jZbuJ^6-|UZ$QmSy$1g(!(2twRi-z)5Lj)# z$ZYqB!mcGRXTd58pkNniFoU*Pn=6I;PDR_FV-&Pzbwg=hqG~Iy(Ighzfoni190?xg zD$)|i1R~2m!m-*li-Zqq>y?0OrLoJNFZm@33&@oE=B?l0a&v)deIA`-e(ZdHC`oQ< zIo~`6&}lJOtDbnqZmDsgt1a~iflei(Lm|2=dgj*X%Vkn|ZrrP+;DC5#0=|&Y!3LZH$?2q;Oasutne_sCz)VQt)3_~&cqh`l`C0WTOt)6i-0`2;z zW30L$u1G?Ns$e0rQnsn?H6swa4Sm2dX=mhZhp{nQ+C#4w&m(cCCqub34_XP<9@f;J z&I5WGrHJ4BL%3*GN6J2H0V4J)ubQn_m)y9vuFPb!7H{ta+>SaF*rF;a$eD z$g}S#RQGB5AI+sC83K0)vBaV|Idh+d)VGckg-py)ZfPB7ms8TG`Q_HEftk*VjKToq zld6Xo7)rVRC5sWn8!Z$wjI%_HyTJe<=AvcHcPoEtlTiVcx@;H5TlmY<~+ir>EN#1*6u6e&a!0JkVCVRqOQTL{B$>>SH<&*l9b3# zc{$+uEFmgcv>T4>gtE5Ub4K1WU7@dp3nBe8fltd$+@{dv0PoU<_>b5*2))Hk@r`f5 zTcei3iqIwE>U-qr>Cg2cgay=MYIpLR5qyAg+N)PJf$>i zLFgm#dvZyftK}qOz7YfWZGMZ$q(L4@yw-V`1buZnzW3K30Z_28uw(@M(1l9NZ~9n; zf=Pm_N;t0;u&7{25kGWxg{Nn&yxi1JabBSLqV%59`aeMb)#9R&_LgscUh)wABSppf zpOq4+{|6ST$qUJY3n5nxg(Qmw1jT70a)wq?kuj5y?DX^w=v@7=3+<`H!9(jh?1ctV z@12tipNnV|S5_)-3FI1lF~dH>KEy@|hvhghY288kuJh2OW$n<_MP+i}!xBe?u~Q&B zLHQs|Z8dw;BS#kCfOVn2p!t9w+x}L$Gla90rb3{)Icbba_>Rcjrg??bJC?WxXVl0k z)nW_iYm8arV*GxciF{ZGK;>`g4qEKQyM&7P_Kw<^I_A8#)Z z8G@(`pf2+ZVy~T+l-439P+dv!{ESko&*~RmA^@}6xBDab9qa*~)z)0v~W8{O^oB5ki2-;2*xfevax?$oJH42d^ zMcLB-yAVhl*zQ3W*m5I}ef4+Pn^qJ;C*CNZK69j;>CZ7+Vn<{;OdAtn=;$k9;)@Y_ z8diT@+L}>PngC4UL`UC%QXY&pStCp1T;ngeg2KmvDoKpsWQ7N^-Byv7oAn{haqq@yV=^4xOnp|ot)Qn|vaZBUshC(UD<#gTG>Ny)%ZO$zq& zYKan&Qn_)LsrUSW@CiFDt&)y_rrED zn;1jQBP}->2d+xtq!@EM)6~PIU=bS}5GUZHB}&?Na1k(uXjNw7-qWZ@$yE$!VWz{!qIJ^ijN3_Q!uJ8@zv!hYnmtK#{#fXU4qaGrS?jX3 z7U@}&BQl9&r~w){t9OXynLW!$BK!c{&ujw;Gj&KW>>Z){knO3e#wpae%z-8=^7O_7 z(ZJF4!W#Qn?ZFq^!)wVwV*t@MZ1FeS$;iO;s1@{V2idi`PcvR+2NyCSmhCupd_n1c zDo~@{!AFW-Ka)6{E>w5kAtH|fJ3JS|KrFjgoY-uQ_EcWz^fgx^Bf7=t!N~q#s_JUd z#IhlyzrPq?N_0Ax=Tnnv8TK6r?vP9*$G30?4ho~O#_x;j)UP@xx7oKm`O>2~$*xb7 zwp`*lXeG+|l|xOIr=1>N#ddmu3!h#vI%Vn;T6-Nkyle)V>_Dss_7(`kh+@0_D*Iu8= z(Ah@MS%tA%9O=z~>~0*9Wb8J;O^FcY=lo237A1@$Q5@RnNiynk-b^8%=pB>q1|_@@ z{~}iY-5}4`>FpOOX(AF_lBtyMi7kbV^C%0%iV6(GMO2u1r`f%bUL~_~?*=GZ1BX%{q>aSY@|3ffkH6LY zW52nNh_8XKrYfDXpk?njz-QJ0s{hD)xFc^w@EubIw=)HA+*Le|P{)piOW-L*1Anj- zj}Q#+CGx}$GrHp|#9uz{d>XBxJEkC=3Xp+}ze-9HK1fn!&?&kXC^|y%C5cRqz3?UP zaqi{g=h!RDd(fVtzyADHn_u-lQ?!0no;9F_G#y|z9mbpdxTCA*8D7LyU9mYiXb`)4 zQDF@zL8>q+y{CAH&UQW)MaA*M;poGk`K#nd&}6g&K#^rc5tU5jJAtZQ{P) z7yB7GZSa@`I}Ryb;yiww9n^giU2-nSREw>^AI}^v7$?^TERo|e()txg4gDdj1u|*b zTV!?M>6sgqbidY=9-XDkR+s%!s&y^dmW`E7>d&TUU5{{@#afo@g2wgwrz~39k(TS| z+SWG0!kg}w>ZqP0y#=v>vxM$MWk&XEXl+$Hi-+Q!_2BdO;)4>*hqDY9lVfN(7fx!@ zdM{Z2%J|FDvVoDGH8KX_pEADaf0pt8JJJ8kaN~b+zm%P`i;DV@Qm{raX|nRdp@MLLd|eSaHdyH&=+}Lt6Qd?0y1NrN#EQ0+jm^!AUga3o zsY~#iU=#u9wr8o&yzek?dAawAMCU=YO%J2FmtFVY zf7pUVz7R-4Get}%8Nsu#n3y`4jEp*{hdLi{#?ff$!EBS}EZnSoK`0u(;o>dcJNZI; z6W#Id^P@ir!64o})rFT1X*%K#%axZ%kU&2^oAJb>9-R@g4v}&EFxAyZ>`-H@9?Sr+ zZ4GLx>LTf{bXwpLJ7cfEsdie}b>ixZxf;1{EA!RBf6a-#Is*m7a1f9 zsko6F$8Z_(91LWa*I^*(#3n|>9pdxg@f(_6ygA1OEtw#>O0WlCNwjq5QEQ_BlE0c) zHK=nQ4WJo4R#E$Z_cwmM|BJPEMee_cdrGE0HYN)wDZHuw&7P6=DAJE;D!>>oE78BS z4?YeZ#dcv)kN3WDqq@FQNP_8=cddc#e&z`13OXtK)~jMoor>SYDnWq@VE=j&p3G@y zs_JrCLe-VL^Qbb36KBqT-cA%XZn%&;#;s0E%B&H?5*g?+m$86+%n0thSZ6FoiJk2v zRKG7rAh0llW2NVq)0wY+IE!_fJj161r9yOS|4sU}*M-zbBFlqY?4)hR-?unUXXU2q z!a`Gzd~SHsXzj!(!txj%qm#?6s=MW=>or@!B8M19`}V-e(W&0*CIGkXDvDZWItlc4 zGRuk}sa!oJr&+EEs$1O~UA*OTOg;?n1R7UaL-N^rVVDgTQ+hB9U%LN_v7nCOaT{`) zV{0jqtlIiqh&qWn%}tSxKX&Nixezx}n066uQZ*`d^QM%_fTWlbu;apjR!{Ie?K(VwfaN^N2+Iq>My#|Ku`8Q5<*6<}Vzzp(6v{6&7wwrM zdhQ1PYxh7cn+u;L!tPuxfH#y6c+wb9`CHs0b+4JzeW1tnb*QOn&So5}JK0E7WS+P7 z*^=0d;=vr)N{+j?av4j;zmMsmOvkZic!lOvR3tVOM z?gz((U1Ei@+r`ug_UMT>cO380`TecZI*`A9Gsf=gcRz_(Y*1&1H{O+GEMnALJMP1) zzg$d6+wUBY-0Cv5Nus7VuuCw2j=D(ZnU(;%>xx-1&Pz zlCL~Wh3pc8MareyG=BOmz9g5Mtd)|0HHFiIk$$B${6&jqfw_>?;?3r07MWN_quRF( zOTC(0f{t4GCn5LbQv>D`gURvV^;JsU7%`U$ z0DJyYF{7&cLG^iYt5}8e=4`A$f2e25%F^D7$f%ULba{3Xc_YJ?@ zThAZdL;0LeDH9{Dt zT;F#|Y<7pO!xh#*BM}cjEFaEZ85_)MuOGPFmWa7ZxACv zany7<-m2Fppt4vnf*^E{TyskZ=aCTN39UP6Y^f9Wkfh%y8kurqRS8%QF_uof=t+E# z2R(~oW0T{w_JhN^2MSEM5kS|dPZ9SrLqC%a3_XetACgAQSez78BTAhczk8W6PDUB# z7tNSBG#0s#ixBlfdIHC91O;{B8ZymG%sLI4d%f*tdieI{vnz`s4V4(%;3`<58)Uip z7><60nINz$W59m@>GuXBC~NcUnGp&ksq4pP>k!2yRLJwOYLdTuTr#>K}oRJ z0_pk(baAdRsmiC+v8@3(*RObB1j9{__2OcjyKT-rF?E3`DUGc=i{B9K?Ug(*Lau4A zA>_-hn|F)6HwD(O8PaT7%W`}BuZ$LLt9X>=9nj9U@==H$@$`7`pw>RZ!}N%uj`W+4 ztc8e=j2#H?0eAL=&}0|4G6(E{rdDov>X}|{Fg`{q?xwr8bX|_ozqSJ=0#J^Tx`bpD z&L&If&irBKtck9X^obUX=mniW6n0e@a!jumHia@{6)L+NR{?&xE@D&U*;5phh{LvJ0pmx`(90Hny>Q9H!w&_ zGTC+2Mcs<|RUvX;8ozYfx&U0nsW4rm`9@q>MK&UooEU|V3bD7B$E60CV_78)g&`LP zuTMt=qMYsoszO~A&Bq7LNUnqUt8>Xo8y@?6IkWL}P7S*pTLxJFYIadR!tn}3+9 zrr$Mt;(jaPKyn>l6f4dxII_&)k{U9P%Bykjbb$AZIR({VT%f}mjA7YJnxC+C(vNBd zvz`B4d@-A)MP3>mTPTtZ)oz~1`!?r(u(_q(=37!rd{O(@J{R+AOy+9t%;(!ZH#;Ti zMi<@VyG_cgeTZ~1a{35e-Tcz5S38yl*1-wBQuj#B2f4FL!aWGx`-#HoO>6D~HQy)a z)fa5q(GP=>hAoz9#_R!cG&r@|>Z>!C_Yd z5yW?xg^1Vs>iqn&qqDmMoU`93{0$izL6#|NXtY`>R-s90 zE;3H14IZRb^CsU{M~E5@Lh$=EMfVPpg`CJr;D z6fgb-=1U*I1=ohm#sN+5BbL|BbCb%>`yJ6uD#9r464UFyf=?yp^%MP*Fu?m%6a4>` z1bqJxd@(y0OBYWeDODq9YiIKR^Xv27$@Aa5f%2&IS0Q9y_6VGqLaFuC-vb-ru^uQ& z5CFKZLMTBJ`BcrT-13HIr27)O{QV-xyq``8Nt%3KioDpoF87tEUbXrhZx8Q(;S2%u z1Qz|0xHQpLNy|nP1Hlne;IMtVE2#;-g+5w<0Z$Vd0cE9r%(2a=+1Lg;;nvYf2F9@X zNCq{|2I;hqL@ksZ*O7&Vl)q|?a-7;0344u)RyV=%^ovvEBr1o8LC-OCL6Hu8HFrtY zYCGBCf+w$)X$zOItyg>4(15@LP#1!94vfg?H@s@C?5E-ZtncXEardLhW2TkCcaGJB zss~+>oo_Rmt*9-1!c_F|deoZ7f~$cMZfGjO_x{hsCQUULupfQ=F104?q~w@)nUl@r z5#atQ=>CNelI=#KtDpBjRt=8w=f5+JLmO1ECI4<}=voCvPBTS5QKAkWQ2tcV<%mD{ zgZ8*yKgY*PG!rGU|C?phIc@ytO)whDBL$^UC@05|7rhd}8PZ4m+&=5+4(wm80(GbS zobTs3U;X1b)BUIC{BMnj#)-4aGRB9V)P1gtoUP&_f@O;OSW^(U8(=ayUG{$Jq@A89+nxjU?g>t43xxt4X8Ci17xUY zQ%7Z};T}O`F>HEad=VD%$oPRe1O=?W#1!5q#4Ok-r%Cv{WE=|6T+w6KRat&I3jkPG z${!6vS^dtx5|OQ^pdG=c$5-H;u7O_|M|Tgt=)-RF6db4}+-BJLX@p)u-uQMfX!{v% zhe2&BYIZRuIIBrn z@i^7|ezQq;O(o&X`- zeGh|NI?fW9vp*?X32lI!S<%_QSfoM=>8a#aJM~&;lk0&&1WR?XzXLe5#Gp2nN8lq` zpL`uFbMuuXFyN|H$8cj<2hqozi<-uor+8B;`f5T7Z?^C)@={#kA;R@ze4GIvsg`El z*`tSD!qf|Oe`sOPBL_RVVcjSsl$l?IB&go-o4%o{pta8yD?Ve~usb4s&-_E$k7#2) z`AxhNro#eA<(B>@(i`LQEtDBq$R5vQc%a>5S`>OHje&B=adaT=x+_A?H_`B0tNG@#{d3g52jQ&Aex%?rx~R<_BePAq0CxeyWT!ND>I4qXG?(x3W+e-jkMXSR%|c(QrBRG zu#5R}`JMyDGt|M@+7Ql*jalJmDH&yBtn2}eHU28V*1Fq@1Zll~dO)W{7&+yxTIvmX z`$Fd(>NqFuzFNxKCKW%MI}`N_=UrDw^qY7%Jyq`y%%b!BeVWh$uYgiuj*?zFI+4JI z$Pf0iT&a{1d|$7`)0C+-VANhRgSiE#ox8TkgAWhiij5;;_t#Y_{xH1m%bUsOAHtDt zM8=zWe?s(as*aEXFyDVO*C{(CB>gN{y2a#6l}vDYkQzPq4C81BHl&X;v<$ha9dyCJ{z|k@V{5cgoNgI%*3VfpH5> znhK>@an)T0)%@gAj2cwlAKwL4ye-N1#H@17$57CTa3k3R$E(C`F`kS4R zs-jv6g+)Z{0i8xUh?BcaGfcNi@P!qB- zB>p}(h8yc@A;^~iixL8*{U?~3>yfO$_m6DmQoJ9;RzXtCe)ybI_n-9pvmXzkZ@Dcz zGg1N%)aPI?cp11?&w9`WM+8_SB03m*&Qw8zrD*PK)9 zI59}(h0J&X)$dLp*up!9XPV5Rz;wD{MJDDqtcXABGaj5ZRT8%^4h&3S8CUQ{*NfjeeN z4uzWrg0iVn7||vN+#V5Ur2j59e`)ZVhw#%) z5p8Jk*prUsHy|y9j;(O+YJ^`C(pTC@p=_05trPEbny_nZw7irLwe_l%AvBKZnqR7V zr_P5JCDath7tBT->+p(~M%slIN43sQwNPR$O4i$s*?v0depXMZf36{YS`Znl*eob)}kW7L;l+UUkFWy^ZvIDRhn~xIfVDNFxqBGA7BBRj%k7Tz8&({KfXs z4SZkMSqqia5_I-;XK>GSvyO_NBk;5E1jM0jE2y^biB8T(Fh5cp$W*x5VkxKpbkMa9 zW7x&AMfXqSD99Q-SbQYg^qm`}LdegqoBtseUm43W_2%sN^Gu;}i9NZ-w|?Z+^UV2R zF%ZOM1VYqqv*{m3roMY}$l)HcY|IXmN0R5f$e@M;S|9oOA>x>j3ftbXUrxn2-+ zIXglrQ~_nu@7J6^HrdRZrb!9$a?MYJnyITNK?Jz|Q6-hn7nz;F2n2i%th)Rg+i5)cqnqP=$h#$uGJJ*IFO&SC zSi|>2uGI>*7?vcd>C-SnHLy|EEzHC)&cz)q^4`jJa0$4mJuY6ZM(2^kB1<+r# z>b%nNzv1`*NAI5IX&|A3y*z5TVxNBV$u-~f-0QCGfHM5{zB5d4d~b|qL!=>L%1J5G z7e}t_w;tFSe>eI7hQs1#l7RNpkKil+#E_!^;Q%brW=L4@_J*k(K(SlP18I{k=_+yO zjhis#C=PT3#!Fi{?Be5qiMd@wMAWzE#~Xk&{vbtA>Qgo>;o|`ZOC$#>BO>Y_Hob2| z$VEOhAE;6|^-%v_>bgI;H17w&Ox<5<0&3*t*&B8SMOWoK?lpk2h;oPF`F+65O*u7| zRChHi_oBGFiiDypH8)d!IVBWj{{rINN-_Y8o!l-(i`3-4;fzn*n^%d?4Lx1$ZtZtx zeo%j4en_w-**2@V*+qpK8j8=g1-`I*=*&6()o4ucF!%qpHg?Y5j>en5oD!QJ@n$xZ z(&>FXdB3U!P#NK{)ev)bsZBqrcVhF1y`?{-T~zcUH+o>RfSH_;G%W_U$puj%7u`}g z{H1H%Vw~ch5gSA{apUT4FS+aYe*$T&?$WcDZXw@CMyqwl@ zqs?ShVUFzdm5!4LZ%D9kb|i4c!E^P|iYP|f!Q`zaxDu9}sVfLOy599@4s{#BwQ6l0!=>-l6=!zGK#-n>l!t)Y1hJiem- zi>C7eJ`=!3q#Z#I3b%fxC;Ja@o55!GM(wLXe-u=+VQK*8IY%9a0mn6}{?ZK~ALr+u z*${9}@{VExO??O;8MfN!9{v-a8?L}SH8u(=!$HbK&x}R1*N@9G1p|^XS1DX{GCMi#v%~xl*E`Sac8+(NdBJ8r?hW5x7V^c{*ctsYNy9o^- zGH!{@4G*%%1<|9ys*|V4 zQ{u>p)lX5Zq&A?b9<(&@%(pI?HfO%N4rl;n_q!(2n0`&p~ z!4rTD{8)Bn?g!X2cDQ)qu1pBIr0X0Io+jnB=*^XlMvf0VEyG2a&aMMT+jV$PjDZ9e z-#*?;VB{rF>7Y%lC!@||ke<1)u&yw$Zdi73esl;}B9U!5qp}p#TIM2ZPh8QLh&~dh za1LT+Z;zpNCc92+I2g+!EMh-fE< zkwWWB66P;za39`frIB0Z=5(-QiRQ+Y(S(YDw(Vg7lLJ1s+E-h+;LNZ4gP%G|5$r(8 za|ms#TyXnO!*ux`iN`GrkE|bmX7X@V{1JGxa~UiFcHj=*`k>yyAy(3Fy0mNwQxEKh zFs9NUkwV9t&evX3DoqkxEfJSr=&*CXAozt$=F70d+}_%-_v7}`vKa!p`8W&-R2p(F zZF_`l21uMRzv>SsBA%;H5xR>g#j-u*C0<@0)W98RFiQ;c%Qxk;wW{S%?&{f9+fnYC zsHdYZ>x-L$Ia82#SxxXj8-Q3U*gH6ZjfwY^ehqeN;tOrm4yx0Ay6aKyzbmA?G1$2g z8>OLeXAi8_YwEtJN`tIS%G3fnavnWtMb^^a+G$LoX{ikwaS(GW<;V)6SU6f)05`&T zL*F1fOnKGUEDT<#xkBlN$0Iq9M`&mY<##o8jtYbavI(M)pmA^EJfvbo=^vFJUU7?+b~7qoTku4W2Kr zKhk!G2II%K#v6HV52E(psin6#GiC)`BV#-hJzlESSna0kfN4^%KQU=|Ik+CR!1P#< z8pu`iqgPwbn2GIBSi1bk|6edDFL;El`jc=(M*AliME(DRLH|v0Nq)-BE$#lj2eMid z_R}g1_qCXSLLXi-uIh4V;SHYr3tUt1ln}FY02VHgCUh=*0jT7X)KGeK? ziB=KQN8i>UOxkrahSKO5s6ozKu0eN5{!t03OSY{IwhTL33#oh6tG;fi`g1ug!4mej zQjgqFr!Ly=mPb8+1Kus_hYsYcdXFu*9o97um^;kzU*Z5B$TbFKH<;h!0~>$4hQ~X- z=N*YIAAwYa*SkWzuH!y_vyUe@{xe73&i6!3cu^7j0Dt;_OysBaAr^axsd(20*TsPzSCqDAnb({?` zaBL>lcEfuZ^aup+<-*bSm4+&WD-h%|<$6IbH(FS;@#`6B8rfQt>#w5P#ssg40B+`* z>K_iD=ZeAUB4ag}%!W^CObox#9M)QvM>R6SF0wlh8sNc)WyB#hNgS-{v+Y(E5WszI zsKB+1k=Q$vK001rSF|>6Y^oRTEl;L_Kr{>z?hTNw+R05z$NYxoko3b5e29({uIXzC zrqEYd)h1%$1!7Ua9;7TXlagTT4=FgDsJ(oL`3}X*he!zvDUt^5;=vk4hnC=7l;_PV zCO3hX(+uOClDW&bInVFPBxVtjWzILV_=;J%w*H z+xxZ&+u0vzX>`?PF+vHcmoO;#nQrnU;w)Yqebniq{|MxCat&}{s9|>sq&Mf$E}&XP z)5lg@B1zCzge+-dM6uJz&MfRGT9^6i$y9Zzs<%_DQQ6UyMY;^E8J`_G<=TPhe>NY^ zw83NpO1@97HhA*{XKE znR`l>NeRX^AJ$$4T5DJLVfWm7lrh`XGAkfWNdN%c0al292&(y_a>nFcEY$Kw>Jv1Ur!yF`dNP1KzgPv}aM-?!E z;X(eY;Xz9N#5jL=%P6MotNtTU-$3f$n`ESR#{eV)_&;RZcwk#_f9Ud3lrif1RL4r^ zlKM*#z!`>p#v+pGU%rAEaT)SW#|H22Y0#KeT7Aa~J4&QWg~q5daY}Fn39N|cb=Sia z3&8o4YeQKG7MOBzYs5hdk`anv7sD&Swo=+v zLfGNLdUX{728LK*$~PnZZ;5fU55rC`lc(UXd#AhXABvT~ef3l_swhZe@xMP0vshH&i~ zvLQ6PAX*ZJkt)+7h;DsNlXi zYad~<0N=XY9T`q#>|90)1rPpZIp>$H8h%?-b2bgO7f$|_XGhGhY=~#De@^!jcaFrmK2nOu?Anl67=K#{kxKYBZW9 zi(CXv`r>nEj~`>>R@5C&6t+3v4yxa`(4edyquyPf{J;}EN%&_1KMC+5TV45nci1;3 zm@q}DAKiI>pZL&+ohjCUgUa+@*_!d`s5Q~E4C(#|;-=0-f}1mS!`E!jFr@X;7s6lq za{rL$K0knt@bc!XDABx0;V+@Mw=Gly$;{DSl8qxqDC3=v7(f zIk}nDgxEwi^L#GX3W0+@q6y@%JWnXdEfr2n6Mld!_&yjT8&|>vvaJqb8IUYSocTWD4{kd=P*|V$?$wn%k*K0YS>0MoOX;CDmyKza@3V?e9tUp;9|ck z*Ybl2>E4s@Hj4MSC9S%h`hqp92)#D@s|VK&S(TMiZDK($H|X+}j8bbjqL?xd(WqqB zK}~p-$rq<^h*Wrjc$tCU$ZH{Vc}8+VjLx8k7D#F9i>U;(v#RK^0ur(*d4P3n0fE}u z+DrO}ox`FH(hcuFTVR^Y1$a{9qus_1n0`K{ka4tm9;fY|xz^Lk6 z*w5@ujWcjuLB6z-j zGX8FA!e%p;c^+cA?z*rTs#NAP=X14<7o+ph7t%ZGX>iE6I`6qfW6&av)93`wSPuhF z&*O#{85L^mNJlC9l`hAeAZvsQ3)d%DZfkp4Y$ubk>8`$E=ld28YKf<`vW%x6Xr~Jr zfs3hZ=(zp~Lp=DoiEiHZ_%|HV75#gL)!8QA_coZ8yKt2oRgD~L=vc)xtHX32ha$v` zdbw*<$ee5#&o9L~4_z03Qh56T=i5Ey*wL;N_4DbI=5?XdCKow_UVNGNtvT$i0dFtR zHdUF(!=6RlM43-lsIL$l+obE0L`g_{K;9Uc8(M~bjboD--I~yCxa7E~CzarCVuxzl zVOJ|CJ5~1IQUp<%i^}U2@_n}40^fgW`O9anrg116>BILMSKYapwMD#WIui`&Qs<%5 zy~r$h@QKYa<=p!eTI?u_7r1L}fv0%J|D!Ja?5P$y){BR%B_T? zr0}UsXl?P~t~e%}Imm!hF!^m++@IdRU|+p>!?;mFwqJ~l=WQ5Dsn}J-w+luWGZ{tg zfA*Q`l-^@ER(_7)XiX9)fwQGg68w=r+m*VqR1@8Re-n6^DFVvZtAC@-qjGvtjg{hh zii6L8OP>ouO+z6clzkvEHXttne%T3PdnS2^lkm9lOiIleUdhWbWDyuFxkG>+X^<^G zN@HMTL13&u=$=Tqk92bapEQtNo;JnB-Pav3*($1~tS{PQ{ukMT61PGo3d{uSZ-6e8 z&C>Xl$wA!uE8Bk9gB59HznvZ@M3|1AY&h1ImevaHh}$_^ZU-z?f7Zai$ zo!0zCph?bqSpMSy_(5-(hDD*Mb)NeTz#32i{J=lC#jR{!HBMICC`wlAeMY0`e?q^( znLauOelW|Atwb6PAjAMD4`ACPNF?1eoBH+%-P6qPvvNN} z&L7K6t=4j7Kh`l`433_oy_^>F;rwuW7|vY@IS#nBUhv{2Ol8QmOLbpk$Q4Ot?T4M% z2%I3kfw1zh-=OUx6w!Urob4Yp@`4D$kxvYgm&?RsyW>#_<=nc=lez4QHU!^F$mAs} zV@}j;8d_%&Ip{#??p?5&CSCL^WTk75Y^kq4BU`m1EY{f-RbhoSMTHDD^*kn} zhffFo|Fw!l#cmjy1MuYw0p33~Vn@#$Uaa$=pvd@Ac-X3eb5%UH4ee>uM(ELvqVDVqHLV@sN-tcC7DGvd}u zm>^QF3kNmYH;`hRG?o~WtD&J{#RMsWz`Ov7Vs>kUj!Hq@=m<9h+jJY$~JpvX;+L`iufSft~G!+*Z2@e&XSk|X1B3tOu zhQ^#F)GG^Tro@yP$b~*p%y6i*mw4ajF7$>%d3)c-xV9xf3;(h9u3mygXz8yb*elHC z$Jj-hCWf9R-D`=SCEtsKo}^NjHlil9p{6{nhNyIb$_|80rom+_HpKZ%%k-srIe}1c z>e#!oVHQOXa(A1u<;e{7(p7E|4FkBTnsieofzD;yy3*B!3!V$3Pf2Y*0^#*`N$d3) zeY%QGsbc#=(rg?eVt|P%y7Uap1H}wwDCvlfID@Gwqh`{!j^T7=m&tcd7?X#KUOWD> z(#Ii4Ei<_pFV=Sj)cy}dn8i#l+EyLO$iHLtYfHDGL>jSynJVklc22!dz;^g4G!B)z zpulYObz#UB<(aF?WM?&AfwlO5i=yZSdA(i;-a z4gx!W1=Bnf&+`{VAS@X%8c)S2i5*~a@n7a-7*%HZJFnS4!Y^6M#aC<>c8#R@gySFa zc9qoRgJ*I;;fmDOzp&lWB4cG^)Lp`!jJB`9Q05>V{h>`w^b^XZp?b~8Eh>$$g z=2XRQ$_?%`AS%eK+1IWsbfR#76rEQc##fWHXR>&rv3eu`p7`RsNUsFCk1T*cf1xxK zG(p1e9>F+}ig<%DXrS`W5eiy@%#FA@KJe~zINY_ge|1?#!QDU-y&$6bpb7(NVFCqy zAxE4ecPoz8LcDNvY#HsmvQ-p}P$ z@o(*ClhO(CQ6Lu(!lBX}%T*`d_1#+w(6Y43P^x^|zTM~NF?N%pqneomx!2g6{T)A5U_;(aw2d-9`r=yO8Xnexv%I&bQs@HL%0)99&!MS$Tdd- zUp7XKKIz1Uk#ihxd~8}Jf>}_NAg0PYMG^(z+8i_+g$>OLzZa!ENafro|6c%aK#;$O zoa~UL)O3oAoheUp$*FRhUX*672h5sIg)=CaNsVSvYBmLPD41)=c@&>az0Rl9DHN1Z zXA3Yeds;}rB8NQHAy0G3)8!e4EJxTP(~yf@tVvc-N0o-GaPa)yqO;5jh4K6B$xAQ2iA|pED_F)QeV`^C-uqnv>Z? zL9-!O8gdmSR~zyy7uzD&V1P>xgPkt9R+4O%B%9^AxO2&M6r4xF`PAzL6kMnmJ+nzc zcgTwza=k-taLJ8wlS5u?$V+esmY2HZW%6o zYMy688I5(8<5Q1M1B0ZXx~m%+>nii+Q%E&e;BzKEjrcU-(~Qqbd{*JJ8lSW9S%Xgr zK5OwAhfgs+;~Df1g1oZ1wk~hnS+z~$>S}B1t163&se_65Ou}a}K2z{H37@I>Ov7h7 zJ~Qx{iO(#2X5&+d&m4T_;xiAQlku64&nftnF^HMEw0u>$+1Kpy`pSmo*aOB<6fCEpo`MDn zR!~56RCSSszXqnqUnGE}>Y^7`_|HOm_+^U7D$`rD3I znfBy6mRB{GSK^=;3<%MvesOc&_=e>x8dla<&MmLO1|l;Hc}8$C)U>2Hjib{C-XQgN7Xfz z!61B2b9u$GY2_;vP*jo)c~GH)b<#4kdNN&l)tM{H>kyOpOq1Ys<@GgrGZrtcs%Rda zh08^6P-&ZS0?UzBOUj#;m;+}>FDhq@hen90s2nm_g=QMj7OR#cmuDXbpOdSW)K=70 zO>0=?M@m#t8}D)2nCuwPL|-wvx=t;s+F=w7r(gtwFeFmblE$hP4UIUOenGTTg?ZyC zoUN)Lq}YHK73IxUH4TlpK!Vw&Q_JyIRfOPdUeegG+T`T;0KS;(&sO+0R;`*j+n<=} zPn_aUl=%}2h}JZwN*Yt;2nzBkIDvwZ$QU&6!B&iPH&#_ulr%Oh?@V=Cpyp~>(a==e zT-#8eN2`w#h?0iw<~H=G)yc@;4UNM{?5JLcqRx)$x>C#EHYBR>pWz;bYpi|nbDA4# z>q#E)+L06x_O&>Uewn4ErN?1uS{RWk}sP` zvxLmfNLkCWX>iAN+>Ub1=}K@cpfx;UL1+OIa(|MU){4go^acwq6U*huI4Jz;@}^k{ zC0HE(Y`<*ozL_CkR9KeKsxI-18e*F25hf;B;-K|zmZb*c({Z0S;gEEg(0$zrB(9K1 zxn0y-QCGgEs?n-GQ@yB}b(`g8v46?;sCD-^A^7y*Dr%BU0f>CjSWz{)gsAPVR9Q_b ztFxHs+=}@2xV@K#?4EYV>BVz#WAyKlYO*6Rzq!1o!piHcON3}=$d`K90WF}89iWhK znK`DA@6#m|CGt^OBw&Qv{ID3rw@u}e@4 z#viAjP6Oz&qe)#tQHsmxorb$A!! zuBxuaJ<~#XYHiJu=6c*)iW};h6|h*dTAjaOF%~cw@;}20vCDKI)zsQmRJFXRN_89M z@0M~GV{Yf&rmN=G&OgkGhg&hJC$uXN@%@NN=duh=cz0WC;{t7*WYk}?)-V=3-OldPhujxt8X%nTEWT!NSnS z(ZsV>)S>Q%YTj{dr*8Bx6t>PPKO6>qyXlC*qW|A;DKUBDEDF|8uof-f;NH5{(3N6T zUX03~UqkI^N8?_`D^91~HKZUZ9lxZ!S#7WN$Z^;rs-~(Lx2Tzo4J)b|n`?>5L^IhU zQB9o+BCxJ$QO`H6)C{YWQ2KU2MdA^JHlcQTRlPX@nVCImM}rlWwafjC-+e!kHi{E1 z??6^a97FpDqMK@$udG8-&qR`!*UclDGNY3`?YsuMra~xNCAy*W!VatS6NeT~L_@%m zq72#C(0qy+k!@98xpZYyvmsx_hB)A9Rn6rSlvg7?7$MH#SK9U!>*;n|=zGdIJ`E>c3n@g^_xs(0i z=57vd9!^0F`x^Nu*eT^^Kk(jeo=o6Uv4MPDxZHdwALf>C@DXl490T?%`whF`a}0UV z%?`;o4f&Rv51}S`1T?if{|90Ekem1C0}S~Ow|rZ^LwLSx$bZtydpH_Ci9yQo98NhAC z(3p=A%@_vkPilLJg2U7{j0$>D!B-}!aSo*X2>HasgZ8w!wH3<@`7w?16F2*r{p9A6 z)GUIU{g*)fPQf1pPf!8pfP%0a&L%rvEd@9Gj*oKlTpWdn7BPnW)Ga^bR~YhhxBNnW zX~?hK@@u}`%?HsOWm4T7Vvw(RAq9x=AS!k9?S}jYDI~uoOus{O8|PYnkLDk~^V`wH z4K`tmSJu{55+-3viXx(DO}pg}@<$@aPn7zZ0XrS(EPrwHpZFDS`77V$W=CkeM@{A|*f4SvR zdCaW=qJ^W}OH+f$=tP!inhS$c)A`55mIx(<7aAJF9F5atO~PzV(5p1$b8bze#xa`i z<{rgVnn20099Od&m&Oln_Lt&3z60ls%5(X46u3QAfT`&t;utE#4Elm~={q1Fh$86|R$g(cEbbN8fy-={;%eh%u9Jss_|nQoCr z^cfhsMR`+G?W!t&liHdt3E83I5FpnD4ID3t{|MK77DJR$&E(7B54^L!W)Pp zWc-qrpcNuhO-$_r6mG^*`pP<8EERI#FK%9mFSs;^qv z+*n>WsA+ZWavEAM>M$C``*^~jGGG*|Ynzv(T3+i!wMF{zRy7fEs$pme1i?d8yWTCr zh>|HtKU~MVNZhMnJ5@;}dhaBf;sWnO%k-Gmn@~WU)A{XGC5fc<6)18SuUxE1t2ZIb zHZ5V`>4f|sv&hZUR3BQhp`{SCKDbMo(E1V*cTE$r|YEtOtpxOpsz#yAS% zm5|i>xp^WnKpO5+Je!uQgMwdZ08zv>34|T5t)$BRX`qjydN#A6i8}c^Wez}`r!_Py z70)rdq1jr*)AXosH4JFE9>g38i@r-FF=7FVd`vX2h~+JtR@6VC!W@T0!TPf!_YFdENbpW zcvLdAsvL{7Y%L2jrdF-0s+*2Pc568_eP7YgbW_r!n%jj+rE&rkD-`ouZYPP^*{wx0 z(}wV8D~?0noKwCkfc3C4s`nihOH(=OY9w9<(KO~6+F-XfL>o%%jx(OF4Wra>;-o^F zV$`BiwS3|SR9zxES_wG6+DKY91q2V(j-RzrBsL$Vd3u!ejP2@RtGqH8rPvNA)nSdQ zN<$k>-JHlE`FO4`27S6Jo}heagwPn8p0S;@vi7Pay=w5e`w8f_)_qnfBqN3~mzyDO_YfLIbf9Kx8g8DUooZ;O5v9<|b~aYknJa~K+D|C=3}U%o(I(K!(Lf`!0Ow?g zwwMI7((>-L4Xaf10O+_@$WVfbv*om!&;p*WRkp(*snrdPVW?J{$&9AdiD#8pG~?3fX+Af~ffHZDixjgh{-NDr`x9Tifx=14wlGUgjY2~Dt z|6*u0Zf%MDmciiT4I%WEkI2VWhQ zk7@9rpj{a1Kt71Xp&fXgunXxSBv0!E_17a`w#(F{X!pPsJg0QQ#M6iRy?Y@EI(p!|JNaPU1~*DK4QUldI}h5N%}U1iGk`O6T#x zEZy;rNmeJnXjezl6#_(7@Kq=jvb9YUm#-ifdFY!4K>!xuj5Vo^R3LRo z9Cct}dz%{A17PY}MvrM(8e~DNCA6$ydT)?C~xZc0Q2myrBijk4^_r45nm;ybjjYrf~d=YF%$8J-epX zNtku+ghu5gK}xc8fwlki06K${dVU;t{IkB&-Nl= zE?Sj#VupYrO|3n1Wo@N-a{$?H>i->X>PD&>bBVgCres7$2sR0=1q}{WGo&NIzCh3` z)NQ)13g&cGu%N2~HTk{hc&?$Tc@9#ewz?MAd~em43yE8IH{KD(ZkA%F&ewwiuXYt4 zZxg^>vio~M3y-sRb#gu(M-S{=m!KzykQEbZ8~rMiTJ)tR>lK^PoDkx(XWC7uV=EkI zGdcRzZml$x9%%J+6`n2?(03_hj|SFInWHW0SUWN{RP zAW#U`L`a%tb{a#MH;x>BOEdJk?If#0-9bw4(G?H#y2Mo5_mz8``zxVtZmj)3Rqg&q zEpTjC6kU!WYO0hmV$!37IXz945{*Xvzqe*k(TzRR?HqM~iRNUbPjt77clctan_GGr zp{>0T7v{7~)8HQ7rNW_FRy5RCtZZx~VvR@ZRVl!hjdDCaCBQ?KV7e$mteBK8R=xjf zQ+!8uncL7%SKM$G19x>D4rOkG($8c+7|3n|fXU#ZD>|6sbaMw|Mz?iT{5|_Y#Xqv2 zRQ$6Q|6=9;%6?Pvf35nzTk#)O{HGNkvf{&5e8h_Xvf`swe2mlQfsF39sF+)^P;oTf z9a8naGyA8zGAafuHmulT;rF%m?zCc;6^B`H-BuiK#SvB?yGOGm|nta^D?{$Q*BAy)ZNK1`Jl=Ofg6K0iUl zBY6SF7pR#Vh2^91Eswsv#8~t01$M6jb0U2#tuoK%V=#1^75G?_op=l{v>J~E4n!;6 zo74*ILA$j}<(>x`oUb7CGHY27C{=_gW5;|PFE(MZG6+|-_ImdLQlJ%tcMr%idB#H^ z5x6(96>*GEe{G<*g5kw%hd0LA3NCL_aTH6AZZ<9AY;}aj#7l6YSmq)ce|~_vI(?sTS%wh(u9Dj4`ce9NHEVQ-Q-Z z8xIGr%5>s`j9?CZ6gl*<00@2#m3%s%Ve#dCVBl;5{4xOhcWvDU?lerex3<99lu_ax zKn$2h{I?gG5}&=0NhO;rS|O`F&+E_2ZqLghNR*dLh3Q*wh73%^Y*P%EuonhxLvBr{ zvg*wdR_uek0pX1$>6_zV@C`R1!|#Ei$ke-GE@cg~vf@?Nxb)4DR>laGF_`M)TUjGj zRtjYmSXrZ0mJ3rSVyd037vQV`JPk4M3?#s_kPLr^esBOrz(Hi==ipR$9vS`xWa?Lt zkzYlIeGP7b*Wqq>1NOq3upi#S7XQG~x8YrQ2R_2^8+Z?Xh7T0^hvP&fKog&dvm%Jt zHh28OXK_%I^9!GiDUR@~%z75HJ7NyQ>LbvL$3(@%=qR?o^3NU2M_>dl+B+a-Ok*pI z^`@z~u$>ViRD}=00snz0_()-sYBsWQh_-Qvws2r{ht=xjRm8!G@j`D#D-?ONT49{m zqqRaY%~yE+NSbT+NC(Y$MuM@qhInH9<{AtqY_9NVltuX!9r6<#R#}A)CeCk1w-0h7 zz^6#A&ya^dN0h%niSs4qe~pv$jY5Act_?)TMt>oniyIG9sd<d?yODF}rbmW4y^eIMpj!Vcu@FNEh6U>xO_BmDCgyH{oc6nOOq4#T+5^>hzy*}O zA8K&7TM|?2gQbbG4VL+!E@nBt*lp@D#|I763qDakSb^*P%o-()mQ`4CC=Qw^Ssc2? z%GGhOYFQkdh3T~_?L*<`gLCFvQ_uooH0E=6!z#=<_h!}Ix)w;L(Vt7CIhXQpg85dp z8T1+zw_+QN-Vf)M(aw>+AI>k^0~hRr3*GzRA|I@O63&~J_#S^_^KcmDz@@GJC(-@yxiz)<)TM!_K{hQlxgj=*d<2GzI#8d0@f%>=At8f;-Y z+|CTRi#gyy76wl+H$2NC-~fw;7g-#<$r9i_=7CRH5`4pw;YXGVzq5Xfu{0LOGFT$Z z#5XS+#K<-9$U!s zSrH$@#_^e~gfC!IcsVGjnc;X^RF<^@Ve4A{~V z?L8sU(!e!{Gbhthzi?JOq<%pN4N6$O1#AIqCHA%|qy}Y#+(WJl2CxQsW^JfF1a;Qa zEC3JUT+t3&p!U!#kLH8xN71&L;7v4qaKk7^qT^v`VKk|zc*ao)6>QsPE%J6nRBiX{ zTnK0DAd#IPuwNF~v?;Ln%L049EU@;=aLD1OqgiV}BF^C%yc`^gatQY?$T0X~gI7c< z1vbX7AVhm13Z$!LqY=Qahqqu3lw_RbxD61k4wl|O#(XJnk-&~ja~}S0BePn6zrm4ZAL+SRzZSCD@Y`ae6V{| z5+?3!fq0ziyIRn+dJ67tu_hruIX=0CjgEtR3Q{s6u5ZDOo@P){BM$zC*4Xtpmuenc zU~q;f1x0rhMys#C5gZYdcSA31bu)xvRt>&6Fs_)480pOdy>R1=?;%qNcb_Q*I4qxF}UopwXaqAG=E-y2*rpeg}Mm;8*kz;r|g09G{8F zn)c2W>)cO#pCa=6#P>-|c^DpGXd+@NzR$z(AcrTFPGn^H;Grq&`>@@|K2T)Iicmba z7aZFl+LMrlgK}a25mO>2)4bW45uSty+BnU5>m%~}#`o>eOW(j=GJLRi^7_91UNRy& z^b(&D&`U>1slkwFV66q3g7u!lIKo;c3RsLM(W7<5*%!9;W_BRHPYZi0K6x7ur%#C4 zcil~F4|Ti^!ptV@wv2-Gtb$aeWq(h+YPfldC$T{D^DWt3Mq-i=+H5*|;j|N|fn*Xht!Kmxgp?W#$xqC*9upID<4k13t<2p}NVHnDdCLD20X*rR z^kts@p8m@!GBTDSOKZz2kmq}O(kFXTr+E6C^iRyN$Zl!Jmaux5gw_9rc=UE)1&ga& zQSmm%>VMzw4Z-}aXi^}!N4B9+ychPj*M8JX_S&63cx*pBUPk5xiCq%9Byvd{lQ7-~ zPmIdg4^Ng^>i0Mjv-{wwQJE+ipWem>V#8-zSYLd5mb!Qf{%(uQtoW=J*ntfW9Do}r z!WJ#CWj`DwXz5SFbBP&=nfu^*AH47+9317{4=<{c#EgCLk`G>f5?&mYorpcWf`d;) zedSe>xg90AHwbGos%HA&HLu4>7MLfD>@QC^SzieT*<1+@vcP=s`fjKV)uER<{6Fo; zWO|2!XU>pH@3N-TVS-2cdLnBI$VoEJ6J1k4##yl^wx*yz!g!;FRiSubXL!l%V`?_g zu*%Q!2HRZuI>8+=5(5c9DQ zK0)G9gHQLvXJv`reen4cSoI4<{RCR8aqy-2zYo6l!8a|C;YBO=+xV<)kZYQGXIK^_ zso+r#)VFs7=_==L2R98ox*5x9xeWFy(=N*!^IgpMF+X%M27e20fX&AoA38p9d=4L) zilV58_fb(?k81n|RHQe8mu-S^>|$8RE`u6&Ijmt@U<11Xu4Grjb?hp*iCqo1vTM=a z+Y0xw>tHXt9v)#gz|*V+4zg|V61x#zXE(!JYzMrSY%jZ!wX!?dK6WqWKg9O4f3Zi|x9lA|ERM zgYoBlg#3w*)SSFPbMsMJBpe5qmrLzEa&GNtM~=R zCHz9;YQEmMgKseI;~R~~_$K3Zeu?opzts4hZ#ItcEe_7Fa-{OB9TWI9jz#=hM-AWV zsOQ%?Zsa#OZsjeGJNQkGXZQ}st9+;9Lw<+j6MnDbbH2;*CExA%iQnls#P4oX*>v^Z3Kg)A*y#rTj7HCjPkdO8%5{8-Ioj zG4=`!VlFm}SE8wv!skJTGE)19o6u0JLZd4~T*$WZYAj6^8`*Wd22=gTC@Ub=od)bm=t6uf#fl<{S}4zl&nU=|wPb(ojO48ENG09lS3Ayc_ObB(LnG+vKT zvW)xK5Z-_(ukje`ue=)B#_KGCufV(<;|?~;?9K5L8qsI+M({cgK@@M|&5-T*68iI% zd=+FtxwC+;R*uyg=P-Vja;z?Q=JGX&pA(v#8GJ3K!eEWFA3qyY;c%T(;%fX3BH<>- zQGPC_qTyD@AAB9AV&N^v0e&8);^7^fhx0L&0Pj1V;1^&j5k7VJlv$d@P>JIg;@Fb; zY-6%AP*ZrD{xQD@^ZM}p`iIKg?~A;VYf=(9B+aBG@=U%-KkavM8(+^iK!)xXTlhx4 z2{N5ga4El-s79u&-&n?!Wh`ULGL|u=jAb&6c`nR63=5CJN^l!|q7VXQfcgVC2H6nR zF%$pLh6X5Ne?b@%Bf_ONDoWSKgv(x0`XO-s1qoU;CEPx#pCfIH`MW}Dqq#~o>rXK(~7hHm+p_j zsgQEK;ix|YS3bWKx40s96i&vX%M5<`pD>vpf~v4E_c6E>`gBJ}g1|6iGXjlr9f4c1 zamq1Bg5Jk#3BuoZiNUuFI0EBYy9HiMw`HdX>3d`-2Yx#{t%#T3_l>9(|$4K~H zPV_aq*_4+e&Ol0Q2LUA*-lanU5T#t z4Ah8z-VLKig(Zf?{6fl;JJB8UD=AOmiQ#6I!0JAzZ-d{ILgglfYP}Opubb98FuZ)d zf#Er*(!h538$94wY0N`B{sKhu7f}?v1iAcW7{gzIY5Y|##Mck?^hMsq6#) zGCh^Z7q6IZXBY#IE1@l68r-LZwuVdKW+ke1<(Df0$1w!#P`+dJ2XF+OjGkkV31RI8 zil52VyyXy_a0oQ>e{~jojm424Vo&5NT$CvH!hc6;CIiSb=xAmS{Jsm)Ff>aN(bWEf zfThn8?3_h5UyA z-{m6Pce%)-7NtyTQOcwirIeE%;m+gN^6O0zjdQ?{z$k;?kcz{c&ReX(ooq~ECs_TbAP4?h2INDuyW zezVo`N(;ZiDRj`Z7cLJ&3nrbE`^$ZB)Qbk?<+~w1gKYUomAg5k1}&(|g9&^Hp&o{C zq(MCY3wO(7FaT+lhcc|)aX#1{*I?T@JXr0s<|(uUo)S$rm-vq0aj_uTCA5&^O0_-y zbTUDGlCNOhHaIcpWa8L9xG-sBPfo~!P6mV%3=swcg&T53I24I!dw>Jmk7{6?J=Ag5 zL5_VgQ3TQ4WzSsTy^M81lPBUrxb*2{ey7!Vo^^vH%JgrLpZdT6oVIqHm8V{K5a&dQ z6iESBN1|*Dqs)VOg&|rck?O9PAHPeiDWrJ z@dzK2fhP1Grg?o#Z($46$Qg|#W*ZyTQ-?q-GnP&mK>h&DJpnCy<`~eGH^AIZkvtnG zim@phshTU6IhQWJ?k1?TDu*w1S5yAdk+M^clAy7SHnyS!xw#_Ey?8SbJV8@#y_FW0 z)QGTBwI~ni$Oz#EC3RjA5h2SXPDa%S16- zA;zoWS#&XjRWHd)S}$;a$Mjm*dI_4Em0gr*jm^ii(h3da|esEX#wmiZFZf` zZ?*1D4F>wRVL)967>A4$zPX~*?|YM(#5Qd#Lg`h-wnF9CrYc7nbcR=zy$gl}=GV8e z$XFItJrb2GdB(;BNA*Z&W`c9G?XtZ$V1at6doc^b#B7KYbI_KYi&o@3$QSdWP@Dqu zL>Zhe7Q%9Is(t!B4?K#b4mcem6iMmqvB4HP4g5BKI}SMvD%D=Vk%$Ww!6oh}v(&bW zwkj&rwC(DU4+U!5H59>9KpcZ*;8d6M?hvNdK^JF>*q@4eX?$1k77}(}5ZxDt*h1(U ztY5}Vq+2`~C)pl079B_TU!ze}>|wD!78fjesslhK1x)2`YbyI;JyOih88BdJTH4aZ zBV__|MEuqpp)NtTv0n7<#wDn`N6PdBSwB(^NRUe>q+u`-F&G=m5|+j?56)hqT7m%? zy+`WgVb?a@v-P3-TN2K^zQVZl;28Ja@ zYvNe)vI1%CKI1pncw~jWERy4OY}DsI#R~!Ii&zH^aUMj83m`>Y2t&n1IOq*9Q*5&J z!=b3{6QfETw%cM##LOP-Y-_L%n4;L2LlnvlVq;UeF#rwinT#oZDDHIimu}Mr>4oBQ z(8T60uKucvstVRU7H{QX7hzbo!RyU9wbi=&m4zwu_LoP)q!C260-u?0>I54TM zgN2z#*|QRi>dggCk28+-MPU#@3^Xfd%wnufdHt@x(ax(@5CV>5eBeRY2@W3{(9UF| z$b=MEf=gV5!sBY_E3UyU;93|Vw!$cJ9n$#*m?m1_6mb*E`|VICcEUO07FaKCwHXZu zWecG)x1tvK~%(NdMiWK*dir9Gg{d|b{P z)m&LZ_+hfR=E{*0PXIfTDFSGzmE2TebyXJOF=>5$?Z#xbZWskbWTa-~#KzA<2W!0$r7R4T|rXb8? zc#LHpyU=KpPcn)`j z7XoHI(1tT--G#E>+`;3Kl=GB`Ohu!DFvney-_IYgaHH=iNR9=3<6v%^uLKA=W_Q5& zgM5z?a%ub_OUNxl5|A#m9Y=A%dk0*P@f3`&$Tn9V4maDpfr(?3nI7C!GYW8kaV!%h zSFJ~dNBh%wpMr97%MKbB$4!%%?Z>T0SZ}n!% z@h6d{nX4l_a6~XHxKw-&*NQLTCh@f`DrUk|#S;$b4HsH7 zkPfS@8OTKq%bWoumQB*_=A0=}m&uqx|=?&jn5KYr0Z0 zGW@#5uB-|=np8|9%Q^cBI?LrP~W2-Ch9c z)&h|98;c>nMqs(Yo93^$Y5o@CE*))^do2iyv4o&UqBcFK1smLpiM#-f*IWaCpxOK< zYDb45TpS7TV?^30NAgzgQ{6~T+Ng+)fc)u*k%E{!1@HBAJS=t|d5aF>X~61t=#o@9s> zB2DV}lmpObKbupwkIkjonSygMuLZ)e>dBbQ-wkQga$?#1Huz|IZY(>6{PWUFK3#RW zA(ve7532jesceAlc&~>foVs+#k^^C?%zz~_6PjceoF~0-naqakWDeXSbKzb&2=>W5 z_`4hoZ_1(YksJn}%MtLM%!i|LB-3O83zwsrN1n)17P2&1#B$^~HbRbPqvZrP zL6-RCEDMKN{unX=M-AJf-135r!1YdUdCAfsRk`I!_n+uz8%!y-QOpts@-Q6jS8VVt zscNgX_(de5KqY>8j`$@?_p!2R#1JYqo#q`C=`tituJEx1ByV%2O6>CHYAQhvrl`d*Ouk#gBY zN|vbHMZ}JVk5#rn2JxbqO!n?)Rb^DD z&t{dGZ?*f_(%s;qjD2hweJP1tU56~cyai%|p;;_jZ?SAc*DQM*+hw!t&5(gCI|W?w zB#4t!p^uyn8FB^;mNTJ1&VosDHnQ?uSRm)Y>2f|Sm#09ZTnHQFBDg`G3irs<;1PK` zJTK3Hw`4hdC>O)mvI2gS^dmD>EKF9jUa|&RcL|#)m$7-Wj+M)Lwn?sFm&-HRHL{W2 zCfBlEvYG9cE7|>W6>F1cvB%{ac2J&e`@~$Vg+I=pASVpFfTKY@^&!6COGH(db{@+#a>uSSl#7P2utL|$(j6@4IFX;L~w<1Cqu zw2tQ50e(<glEvPR(I&QW2vq74dmBSI^Vg-y!I$hWLU#MAJ`m zyde%ANkd#!mViTC-SrS}#39~y`f2q?D zU&g%S4)GN=#8>=7B$e@1YltN{L{beL-t6QsA3H0Uo$d-y9~?Fw4*nW{U2%rPuT{}+ zGuvl_x>A9oP^hgb%T5;i*xGa-J3AQm-vZR-z+)Rf>?jMi|0qYOUf#5NxfRDwEyrZ< zW#_cO2`J^xJphCEvvp__A)HQ#w*L$(GYQs&v zF63#E4E)|Wyu-IYJWL%GCHdj8JVc1r-v)7FQk3IXSeGQ$YAZIzY&MTFV%ddDCniZN zE0$ffv?NIjuAojpiqh0kmh!Y)e)#mwwr{bRjzQ=}eNsbS_IQJCxU%(VV90ydGi3J* z1I96koOSXkWcQ~bNj`&W&9g8@{vAr?0az#xqI&Z@oGo8~bFt1v@)cWFC&6ON$&v+g zEhozuD6*U^r7+xD4hx~b<)vH#@#=h^KGnLwKH1(7Ks7grBS1$NYw4_>6#sXv$;ijL z#8{o}k2eBrc5crxw{7-x{?A|-!H2l=?Ji)LrU>K7_Ei)O@uDL;^72kmU%Q5>uU&Vu zl;A7UKDI%rpo}!;DL!}|ty8wqY&X`7W|4Co^RQr(nNw&+GgSfRv5ls$Ge$LzV;9$8 z{oTjDGE2xaYL`^lC7S};Y%<$yvPtf z#`G#Wm2pr!Ijg;7N>*UW6opMjjek^G)OWidVl7Hcknf>feIH`vztHsgHzdmsp`ZK@ zq|1*WM}7>$dW7=Ivi~K9mpqqe@9-{dWG~k^s`$JCl@Hj zbV}={-xydH454=b1XqC9!teVU4o)ReZ$myu9>ri|_Ca+bkM`C`ugSqhW@LTWF-4uf zn~7puA%@o|2ba$Qa}B{96GoUn8*RO(*ya`(n609|``MN<+%~Rwl3nRFqkZfuAG`WV zwq+Wc-7YlhuA$F{vJqOg7Xh@>x)`ExXnjD_`hrVKg$S)5#B2SbSQ`LSw1F^N%Y-s5 z3(B=zsL%$%nOYuf)`q|h+ECc84TD>?6YQO}0t)#e{=O0yx7p(2wg8_NZsdnyiV<-H zvJL)$>kyD1roVj_7;p-EQP63iHY(&P>_Gmn?yjO}V?+9IGHgbD5HzKpC_DRRi12?J zzgrz$O;cV=`fzB5@>)*u`oFQJyZQ6Bf!mvr)yA%EWn1ajH|DjIRw+zcF|zS^h|wlO zvQ`2EwaIXTRtiPhRG6quw@H_b>W{hj20~xkyH*mw?qMKe{(@W~{#3sDO#AC#n`qH2 zCukGUW`!i0m;bw4qRk8GzWRoL*p2&2E9=62<=@ws(lSWV%C=RC3#?OYV+f2;bRJHgb zb_9Ae^YThgNDT*SiF~hTFJ+*mq}->>*7|Gc3}Y6 zcpGedhdXIJwuq@afggka?woliaD=~yvB@pUQ0OC4u(R{8qMvhOH|&O9b{@ZQos}DO zUryTyPVHg{*Dis6+GQ|Y+ZdJzBgI z;|R-uG<nrYfZ~F~vcH=g6zM^turJZMeo*8WwoDp0QdvRt6RXpd-%+Ea;`@C&aOv3Ow=AEfC{zc|532yF9J}kH}Amk7^sx3(Ks&T;VB@rdY@+rYo2?OzjP}N_&&7)81z5wRhNM+I#FO?S0mw{hRI3K4iPJkJ-K2Cv30wIeSF=f<2{~ zN1ZP~94up}D5($)>sg`t2qy-%vw`YEnpi+foPUL>ICvF)<@EE1@$e6=iQLSwyER!2*_uf)h9$zYT-@=&oMOQ!^|^RMqhLYgIAzXCS@Uk zbsZjs6s(<)5SwrWMS(jU{{#AYLcg9l#uA5Nq$l2@E?frvLYMXf%9$Tg5C17ZFoxNJ z@qXKLy2etNvdMEg6|D>-mfbwn`kACyw!;TYv+;>#J2CV@LmRuL-9w}fPqkmMtN)_x z{~e|0p#Tx&zn`JOK=X2Y`-xh(d7}0$|E^p0@n}e$dkFvDYB|LkK$tgsSLm%#=K)BC z*>W)~z?#6V(Wz9eAG*IysOupy`muXAw(bh43V&;lrAr@-9?^~ex0h^| zdt3D8dhZj!{NJJ&l?tlk&q5R2&ej}QSvX^m5w~9f7+ErQs|6W z$ClTg6e=Gm_ZKfi22o(z``3a&58b;um;U zEbNKDeyU%g%uv{7GyA6v;5hy!$@YU~pay?wEGC2VdJBX>Ew^Z9Sbf*T?KWA9n4eWY zdR-&k0fKJ%f&mEC7WgT!|H_kCbT$mx3n6Cp!JtwAEA>p&AXETtb6XOg5-(XrcNL>cFDYf z`JIkKDsQ^He*{Xa=Hi5Z74Qwtj?Ww?(gb zD&N12%6SPS_N;jHxx>(ue{0f=bLlZz&(ld%pFNIAE+rx(K)|jv1Z+A1rzY-+l#3gb zch;3|tD5*0nwAqwr_eOYhr_x0>uTu9R9*gZ{e>n?m1o7f#T%jCsbTjrajoCJP21{y zto{eU!f3-%;QDpU&foTZub34yC;FL7yNR>aMgu?-qc7Uj5%;nk)3fPt(4_y41}hNW z&B33{-JH#3e7G@Yd|iuveNSrke_fk=eV*`pra|v9!{{NH+?kuU9qkR4^oE=V;XrD9 z%Gy!P!)mT?ojULb6Vb60-82=|*+if}L#XY+BYC2boX;Yb_zesD)ab~b3+jG;3IX-j zc@5pGljBa>eD-_%TGlH9R$2D(K{QFA8q%uoJ%RNGW4{H*z$w>T&^t@)w$}G5XK#lQ zAz&XtG5Q0-UgKAxpC{(8Let$SsPCGTKH5tG4pNIwwyiej@bI1n29jkt8j3=Z!AjUL z^w7G(&Omf}BRWk{`T-tU-{Wnf1*}NmV#l?kSae7Ae~uUo@&m_tIrfLR)Kt4axv0aY zdB-S^d6|M_B0l1DGJdQyzn?AcYg&DB@f#$h0`brJOZG^js7LktMv((qg|_$wjFV#^ zYh1SmQtFVqNcQM$2s;`U0j2oNwuABl2di^NMtcm)gQdC4>D&lb4{mn)Yd}v_X--{V z{_DD&3YC|a3b2*mH%>)p>{9X(!r3Wv%TR;=XanNrnm-3}U!Ml$rvvK1`n1dCZ#!L`u}IKd!Lj86pNyst5|&*1m9KLbj#R zT$1TjJouIOvR(V^l@S3B@bgjLK))Kxjtpjb(*#PBu#7;Xr$hV#cp}o<&c-01ZCyy_ z7~K`!;wpY4qJOeAj5)p_pzCE8g8p%LnBtLFYD~jJ4`y(nQxW#)xNoj|AK(9t#S-=3 zksOl-0`e~S->_Kp|2q~-)ydG<+SKX)sl&wsEd*FzIo-0~3!W7g#s^0S*8p=J0teSs z6e9*+WoxPi{)L5a=J3OfTU=Opwpk>$osVK+rw5MpDM-;?@owqX=)U+YX<7B|UNuwy zI`!G*gw2vQ+|`@qe%W%q(Ynde|NLz41?q!xR1A!FsE;v)f%-|jcGqXa;$q6QX-