From 0b65df4588595c396acdaf53bbc73f4d77e5809b Mon Sep 17 00:00:00 2001 From: Eric Driggers Date: Wed, 27 Feb 2013 17:51:41 -0800 Subject: [PATCH] first major work for SCE done. A couple TODO's remain, but I have to do my homework sometime... I feel sure that I am missing piles of hidden errors... --- .../examples/events/playerFirstJoin.sce | 1 + .../examples/events/playerJoin.sce | 1 + .../examples/events/playerQuit.sce | 1 + .../examples/events/playerTeleportWorld.sce | 1 + .../examples/events/serverEmpty.sce | 1 + .../examples/events/serverNotEmpty.sce | 1 + .../examples/events/worldEmpty.sce | 1 + .../examples/events/worldNotEmpty.sce | 1 + SimpleCronClone/examples/tab.sce | 20 ++ .../simplecronclone/CronEngine.java | 3 - .../simplecronclone/EventEngine.java | 207 ++++++++++++++++++ .../simplecronclone/EventListener.java | 57 +++++ .../bukkitplugins/simplecronclone/Plugin.java | 13 ++ .../simplecronclone/ScriptParser.java | 53 ++++- 14 files changed, 357 insertions(+), 4 deletions(-) create mode 100644 SimpleCronClone/examples/events/playerFirstJoin.sce create mode 100644 SimpleCronClone/examples/events/playerJoin.sce create mode 100644 SimpleCronClone/examples/events/playerQuit.sce create mode 100644 SimpleCronClone/examples/events/playerTeleportWorld.sce create mode 100644 SimpleCronClone/examples/events/serverEmpty.sce create mode 100644 SimpleCronClone/examples/events/serverNotEmpty.sce create mode 100644 SimpleCronClone/examples/events/worldEmpty.sce create mode 100644 SimpleCronClone/examples/events/worldNotEmpty.sce create mode 100644 SimpleCronClone/examples/tab.sce create mode 100644 SimpleCronClone/src/org/bonsaimind/bukkitplugins/simplecronclone/EventEngine.java create mode 100644 SimpleCronClone/src/org/bonsaimind/bukkitplugins/simplecronclone/EventListener.java diff --git a/SimpleCronClone/examples/events/playerFirstJoin.sce b/SimpleCronClone/examples/events/playerFirstJoin.sce new file mode 100644 index 0000000..f905451 --- /dev/null +++ b/SimpleCronClone/examples/events/playerFirstJoin.sce @@ -0,0 +1 @@ +do say $0:::$1 \ No newline at end of file diff --git a/SimpleCronClone/examples/events/playerJoin.sce b/SimpleCronClone/examples/events/playerJoin.sce new file mode 100644 index 0000000..f905451 --- /dev/null +++ b/SimpleCronClone/examples/events/playerJoin.sce @@ -0,0 +1 @@ +do say $0:::$1 \ No newline at end of file diff --git a/SimpleCronClone/examples/events/playerQuit.sce b/SimpleCronClone/examples/events/playerQuit.sce new file mode 100644 index 0000000..f905451 --- /dev/null +++ b/SimpleCronClone/examples/events/playerQuit.sce @@ -0,0 +1 @@ +do say $0:::$1 \ No newline at end of file diff --git a/SimpleCronClone/examples/events/playerTeleportWorld.sce b/SimpleCronClone/examples/events/playerTeleportWorld.sce new file mode 100644 index 0000000..90091e3 --- /dev/null +++ b/SimpleCronClone/examples/events/playerTeleportWorld.sce @@ -0,0 +1 @@ +do say $0:::$1::$2::$3 \ No newline at end of file diff --git a/SimpleCronClone/examples/events/serverEmpty.sce b/SimpleCronClone/examples/events/serverEmpty.sce new file mode 100644 index 0000000..f905451 --- /dev/null +++ b/SimpleCronClone/examples/events/serverEmpty.sce @@ -0,0 +1 @@ +do say $0:::$1 \ No newline at end of file diff --git a/SimpleCronClone/examples/events/serverNotEmpty.sce b/SimpleCronClone/examples/events/serverNotEmpty.sce new file mode 100644 index 0000000..f905451 --- /dev/null +++ b/SimpleCronClone/examples/events/serverNotEmpty.sce @@ -0,0 +1 @@ +do say $0:::$1 \ No newline at end of file diff --git a/SimpleCronClone/examples/events/worldEmpty.sce b/SimpleCronClone/examples/events/worldEmpty.sce new file mode 100644 index 0000000..201da71 --- /dev/null +++ b/SimpleCronClone/examples/events/worldEmpty.sce @@ -0,0 +1 @@ +do say $0:::$1::$2 \ No newline at end of file diff --git a/SimpleCronClone/examples/events/worldNotEmpty.sce b/SimpleCronClone/examples/events/worldNotEmpty.sce new file mode 100644 index 0000000..201da71 --- /dev/null +++ b/SimpleCronClone/examples/events/worldNotEmpty.sce @@ -0,0 +1 @@ +do say $0:::$1::$2 \ No newline at end of file diff --git a/SimpleCronClone/examples/tab.sce b/SimpleCronClone/examples/tab.sce new file mode 100644 index 0000000..4b6d226 --- /dev/null +++ b/SimpleCronClone/examples/tab.sce @@ -0,0 +1,20 @@ +#list of event then file to run when that event occurs +#PS: spaces in the file name are a bad idea mmkay? + +playerJoin events/playerJoin.sce + +playerFirstJoin events/playerFirstJoin.sce + +playerQuit events/playerQuit.sce + + +serverEmpty events/serverEmpty.sce + +serverNotEmpty events/serverNotEmpty.sce + + +playerTeleportWorld events/playerTeleportWorld.sce + +worldEmpty events/worldEmpty.sce + +worldNotEmpty events/worldNotEmpty.sce \ No newline at end of file diff --git a/SimpleCronClone/src/org/bonsaimind/bukkitplugins/simplecronclone/CronEngine.java b/SimpleCronClone/src/org/bonsaimind/bukkitplugins/simplecronclone/CronEngine.java index c7ee953..2730a05 100644 --- a/SimpleCronClone/src/org/bonsaimind/bukkitplugins/simplecronclone/CronEngine.java +++ b/SimpleCronClone/src/org/bonsaimind/bukkitplugins/simplecronclone/CronEngine.java @@ -17,12 +17,9 @@ package org.bonsaimind.bukkitplugins.simplecronclone; import it.sauronsoftware.cron4j.Scheduler; -import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; -import java.io.FileReader; import java.io.IOException; -import java.io.Reader; import java.util.logging.Level; import java.util.logging.Logger; diff --git a/SimpleCronClone/src/org/bonsaimind/bukkitplugins/simplecronclone/EventEngine.java b/SimpleCronClone/src/org/bonsaimind/bukkitplugins/simplecronclone/EventEngine.java new file mode 100644 index 0000000..e049064 --- /dev/null +++ b/SimpleCronClone/src/org/bonsaimind/bukkitplugins/simplecronclone/EventEngine.java @@ -0,0 +1,207 @@ +/* + * This file is part of SimpleCronClone. + * + * SimpleCronClone is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * SimpleCronClone is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SimpleCronClone. If not, see . + */ +package org.bonsaimind.bukkitplugins.simplecronclone; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.bukkit.Server; + +/** + * This is the engine which does the heavy lifting and interfacing + */ +public final class EventEngine { + + private static final String COMMENT_START = "#"; + + private File workingDir; + private Server server; + private Plugin plugin; + private Logger logger; + + + //strings of the filePaths to the .sce files + private ArrayList eventJoin; + private ArrayList eventFirstJoin; + private ArrayList eventQuit; + + private ArrayList eventServerEmpty; + private ArrayList eventServerNotEmpty; + + private ArrayList eventPlayerWorldMove; + + private ArrayList eventWorldEmpty; + private ArrayList eventWorldNotEmpty; + + + private static final String EVENT_JOIN = "playerJoin"; + private static final String EVENT_QUIT = "playerQuit"; + private static final String EVENT_FIRST_JOIN = "playerFirstJoin"; + private static final String EVENT_SERVER_EMPTY = "serverEmpty"; + private static final String EVENT_SERVER_NOT_EMPTY = "serverNotEmpty"; + private static final String EVENT_PLAYER_WORLD_MOVE = "playerTeleportWorld"; + private static final String EVENT_WORLD_EMPTY = "worldEmpty"; + private static final String EVENT_WORLD_NOT_EMPTY = "worldNotEmpty"; + + public EventEngine (Plugin _plugin, Server server, File workingDir) { + this.server = server; + this.workingDir = workingDir; + this.plugin = _plugin; + this.logger = plugin.getLogger(); + } + + public void start() { + // clear all the old stuff away + stop(); + readTab();//TODO: when does this fail? what do we do if it does? + + + } + + public void stop() { + eventJoin = new ArrayList(); + + eventFirstJoin = new ArrayList(); + eventQuit = new ArrayList(); + + eventServerEmpty = new ArrayList(); + eventServerNotEmpty = new ArrayList(); + + eventPlayerWorldMove = new ArrayList(); + + eventWorldEmpty = new ArrayList(); + eventWorldNotEmpty = new ArrayList(); + } + + + /** + * Reads the tab.sce (from the default location) and parses it. + * @return Returns true if reading and parsing was without incident. + */ + protected boolean readTab() { + File tab = new File(workingDir, "tab.sce"); + + if (!tab.exists() || !tab.canRead()) { + logger.log(Level.WARNING, "{0} does not exist or is not accessible.", tab.getPath()); + return false; + } + + try { + for (String line : ScriptParser.getLines(tab)) { + if (!line.isEmpty() && !line.trim().startsWith(COMMENT_START) && line.trim().endsWith(".sce")) { + parseTabLine(line); + } + } + + return true; + } catch (FileNotFoundException ex) { + logger.log(Level.WARNING, "tab.sce does not exists!"); + } catch (IOException ex) { + logger.log(Level.WARNING, "Failed to read tab.sce:\n{0}", ex.getMessage()); + } + + return false; + } + + /** + * Parse the given line and add it to the event runner. + * @param line The line form the tab.sce. + */ + protected void parseTabLine(String line) { + line = line.trim(); + + String eventPart = line.substring(0, line.lastIndexOf(" ")).trim(); + final String commandPart = line.substring(line.lastIndexOf(" ") + 1).trim(); + + if (eventPart.equalsIgnoreCase(EVENT_JOIN)){ + eventJoin.add(commandPart); + }else if(eventPart.equalsIgnoreCase(EVENT_FIRST_JOIN)){ + eventFirstJoin.add(commandPart); + }else if(eventPart.equalsIgnoreCase(EVENT_QUIT)){ + eventQuit.add(commandPart); + }else if(eventPart.equalsIgnoreCase(EVENT_SERVER_EMPTY)){ + eventServerEmpty.add(commandPart); + }else if(eventPart.equalsIgnoreCase(EVENT_SERVER_NOT_EMPTY)){ + eventServerNotEmpty.add(commandPart); + }else if(eventPart.equalsIgnoreCase(EVENT_PLAYER_WORLD_MOVE)){ + eventPlayerWorldMove.add(commandPart); + }else if(eventPart.equalsIgnoreCase(EVENT_WORLD_EMPTY)){ + eventWorldEmpty.add(commandPart); + }else if(eventPart.equalsIgnoreCase(EVENT_WORLD_NOT_EMPTY)){ + eventWorldNotEmpty.add(commandPart); + }else{ + //TODO: do we want to raise an exception if the line fails parsing? + logger.warning(String.format("line failed parsing:'%s':eventPart:'%s':commandPart:'%s'",line,eventPart,commandPart)); + return; //bypasses the next logging. Already logged that we failed this line. + } + //TODO: better name this logging output? + logger.info(String.format("SCE waiting: %s:::%s", eventPart,commandPart)); + } + private void runEvents(ArrayList filesToCall,final String[] args){ + for (final String filePath : filesToCall){ + Thread t = new Thread(new Runnable() { + @Override + public void run(){ + ScriptParser.executeScript(server,logger,new File(workingDir,filePath),args); + } + }); + t.start(); + } + } + public void eventPlayerJoin(final String player){ + final String[] args = {EVENT_JOIN,player}; + runEvents(eventJoin, args); + } + public void eventFirstJoin(String player) { + final String[] args = {EVENT_FIRST_JOIN,player}; + runEvents(eventFirstJoin, args); + } + + public void eventPlayerQuit(String player) { + final String[] args = {EVENT_QUIT,player}; + runEvents(eventQuit, args); + } + + public void eventPlayerWorldMove(String player, String from, String to) { + final String[] args = {EVENT_PLAYER_WORLD_MOVE,player,from,to}; + runEvents(eventPlayerWorldMove, args); + } + + public void eventWorldEmpty(String player, String world) { + final String[] args = {EVENT_WORLD_EMPTY,player,world}; + runEvents(eventWorldEmpty, args); + } + + public void eventWorldNotEmpty(String player, String world) { + final String[] args = {EVENT_WORLD_NOT_EMPTY,player,world}; + runEvents(eventWorldNotEmpty, args); + } + + public void eventServerNotEmpty(String player) { + final String[] args = {EVENT_SERVER_NOT_EMPTY,player}; + runEvents(eventServerNotEmpty, args); + } + + public void eventServerEmpty(String player) { + final String[] args = {EVENT_SERVER_EMPTY,player}; + runEvents(eventServerEmpty, args); + } +} diff --git a/SimpleCronClone/src/org/bonsaimind/bukkitplugins/simplecronclone/EventListener.java b/SimpleCronClone/src/org/bonsaimind/bukkitplugins/simplecronclone/EventListener.java new file mode 100644 index 0000000..0632035 --- /dev/null +++ b/SimpleCronClone/src/org/bonsaimind/bukkitplugins/simplecronclone/EventListener.java @@ -0,0 +1,57 @@ +package org.bonsaimind.bukkitplugins.simplecronclone; + + +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerChangedWorldEvent; + +public class EventListener implements Listener { + private Plugin sccMain; + + public EventListener(Plugin plugin) { + sccMain = plugin; + } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event){ + //eventJoin + sccMain.eventEngine.eventPlayerJoin(event.getPlayer().getName()); + if (!event.getPlayer().hasPlayedBefore()){ + //eventFirstJoin + //TODO: this seems not to be reliable? + sccMain.eventEngine.eventFirstJoin(event.getPlayer().getName()); + } + if (sccMain.getServer().getOnlinePlayers().length == 1){ + //only user logged in means that we were just empty. + sccMain.eventEngine.eventServerNotEmpty(event.getPlayer().getName()); + } + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + //eventQuit + sccMain.eventEngine.eventPlayerQuit(event.getPlayer().getName()); + if (sccMain.getServer().getOnlinePlayers().length == 0){ + //no users logged in, means last player just quit. + sccMain.eventEngine.eventServerEmpty(event.getPlayer().getName()); + } + } + + @EventHandler + public void onPlayerChangedWorld(PlayerChangedWorldEvent event){ + //eventPlayerWorldMove + sccMain.eventEngine.eventPlayerWorldMove(event.getPlayer().getName(),event.getFrom().getName(),event.getPlayer().getWorld().getName()); + + if(event.getFrom().getPlayers().size()==0){ + //eventWorldEmpty + sccMain.eventEngine.eventWorldEmpty(event.getPlayer().getName(),event.getFrom().getName()); + } + if(event.getPlayer().getWorld().getPlayers().size() == 1){ + //eventWorldNotEmpty (if one player, that must means its ours that just moved) + sccMain.eventEngine.eventWorldNotEmpty(event.getPlayer().getName(),event.getPlayer().getWorld().getName()); + } + + } +} diff --git a/SimpleCronClone/src/org/bonsaimind/bukkitplugins/simplecronclone/Plugin.java b/SimpleCronClone/src/org/bonsaimind/bukkitplugins/simplecronclone/Plugin.java index 00044cd..a5ed708 100644 --- a/SimpleCronClone/src/org/bonsaimind/bukkitplugins/simplecronclone/Plugin.java +++ b/SimpleCronClone/src/org/bonsaimind/bukkitplugins/simplecronclone/Plugin.java @@ -35,11 +35,18 @@ public class Plugin extends JavaPlugin { private Server server; private CronEngine engine; + private EventListener eventListener; + + //public so that eventListener calls into it via this.plugin.scriptEngine.$EVENT_NAME() + public EventEngine eventEngine; @Override public void onDisable() { engine.stop(); engine = null; + eventListener = null; + eventEngine.stop(); + eventEngine = null; } @Override @@ -52,6 +59,12 @@ public void onEnable() { engine = new CronEngine(server, getLogger(), new File("plugins/SimpleCronClone/")); engine.start(); + eventEngine = new EventEngine(this, server, new File("plugins/SimpleCronClone")); + eventEngine.start(); + + eventListener = new EventListener(this); + server.getPluginManager().registerEvents(eventListener, this); + setCommands(); try { diff --git a/SimpleCronClone/src/org/bonsaimind/bukkitplugins/simplecronclone/ScriptParser.java b/SimpleCronClone/src/org/bonsaimind/bukkitplugins/simplecronclone/ScriptParser.java index d32b11b..d95af75 100644 --- a/SimpleCronClone/src/org/bonsaimind/bukkitplugins/simplecronclone/ScriptParser.java +++ b/SimpleCronClone/src/org/bonsaimind/bukkitplugins/simplecronclone/ScriptParser.java @@ -45,7 +45,8 @@ public final class ScriptParser { private static final String COMMAND_EXEC = "exec"; private static final String COMMAND_EXECWAIT = "execWait"; private static final String COMMENT_START = "#"; - private static final String OUTPUT_TOKEN = "$?"; + private static final String VARIABLE_START_TOKEN = "$"; + private static final String OUTPUT_TOKEN = "?"; /** * If you wonder what this is, no problem. I'll tell you. * This is some awesome RegEx written by Tim Pietzcker @@ -100,6 +101,56 @@ public static boolean executeScript(final Server server, final Logger logger, Fi return true; } + /** + * Parses and executes the given script. + * @param server + * @param logger + * @param script The file which represents the script. + * @param args array of arguments to replace within the script (eg replace "$1" with args[1]), arg[0] is event name + * @return Returns true of the execution was without incident. + */ + public static boolean executeScript(final Server server, final Logger logger, File script, String[] args){ + logger.log(Level.INFO, "Executing: {0}", script.getPath()); + + String lastOutput = ""; + + try { + for (String line : getLines(script)) { + if (!line.isEmpty() && !line.trim().startsWith(COMMENT_START)) { + // Remove inlined comments + if (line.contains(COMMENT_START)) { + line = line.substring(0, line.indexOf(COMMENT_START)); + } + + line = line.trim(); + + if (!line.isEmpty() && line.indexOf(' ') > 0) { + line = line.replace(VARIABLE_START_TOKEN+OUTPUT_TOKEN, lastOutput); + // replace $0,$1 ect... with what it actually is + //TODO: make it so that we ignore the VARIABLE_START_TOKEN if it is preceded with `\` + //or just do whatever string-escaping is needed, there should be a built-in for it right? + for (int i=0; i