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 0000000..43e2fe9 Binary files /dev/null and b/docs/img/cowclicker.png differ 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: