/*
* Copyright (c) 2010 SimpleServer authors (see CONTRIBUTORS)
*
* 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.
*/
package simpleserver.events;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import simpleserver.Coordinate;
import simpleserver.Player;
import simpleserver.Server;
import simpleserver.bot.BotController.ConnectException;
import simpleserver.bot.NpcBot;
import simpleserver.command.ExternalCommand;
import simpleserver.command.PlayerCommand;
import simpleserver.command.ServerCommand;
import simpleserver.config.xml.CommandConfig;
import simpleserver.config.xml.CommandConfig.Forwarding;
import simpleserver.config.xml.Event;
import simpleserver.log.EventsLog;
class RunningEvent extends Thread implements Runnable {
/**
*
*/
public boolean stopping = false; // indicates that the thread should stop
protected EventHost eventHost;
protected Server server;
protected Event event; // currently run event
protected Player player; // referred player context
// (default -> triggering player)
private String threadname = null; // null = just subroutine
protected static final int MAXDEPTH = 5;
protected static final char LOCALSCOPE = '#';
protected static final char PLAYERSCOPE = '@';
protected static final char GLOBALSCOPE = '$';
protected static final char REFERENCEOP = '\'';
private HashMap<String, String> vars; // local vars
private ArrayList<String> threadstack; // thread-shared data stack
// for control structures
private int stackdepth = 0; // to limit subroutines
private String lastline; // line being executed
private int currline = 0; // execution line pointer
private ArrayList<Integer> ifstack;
private ArrayList<Integer> whilestack;
private boolean running = false;
public RunningEvent(EventHost h, String n, Event e, Player p) {
eventHost = h;
server = h.server;
event = e;
player = p;
threadname = n;
// Top level event without initialized stack -> initialize empty data stack
threadstack = new ArrayList<String>();
vars = new HashMap<String, String>();
ifstack = new ArrayList<Integer>();
whilestack = new ArrayList<Integer>();
}
/* init as subroutine */
public RunningEvent(EventHost h, String n, Event e, Player p, int depth, ArrayList<String> stack) {
this(h, n, e, p);
stackdepth = depth;
if (stack != null) {
threadstack = stack;
}
}
@Override
public void run() {
if (running) { // prevent running the same object twice
return;
}
running = true;
String script = event.script;
if (script == null)
{
return; // no script data present
}
// Split actions by semicola and newlines
ArrayList<String> actions =
new ArrayList<String>(java.util.Arrays.asList(script.split("[\n;]")));
currline = 0;
while (currline < actions.size()) {
if (stopping) {
break;
}
lastline = actions.get(currline);
ArrayList<String> tokens = parseLine(lastline);
evaluateVariables(tokens);
if (tokens.size() == 0) {
currline++;
continue;
}
// run action
String cmd = tokens.remove(0);
if (cmd.equals("return")) {
cmdreturn(tokens);
return;
} else if (cmd.equals("rem") || cmd.equals("endif")) {
currline++;
continue;
} else if (cmd.equals("print") && tokens.size() > 0) {
System.out.println("L" + String.valueOf(currline) + "@" + event.name + " msg: " + tokens.get(0));
} else if (cmd.equals("log") && tokens.size() > 0){
server.eventsLog(event.name + " @L" + String.valueOf(currline),tokens.get(0));
} else if (cmd.equals("logprint") && tokens.size() > 0){
server.eventsLog(event.name + " @L" + String.valueOf(currline),tokens.get(0));
System.out.println("L" + String.valueOf(currline) + "@" + event.name + " msg: " + tokens.get(0));
} else if (cmd.equals("sleep") && tokens.size() > 0) {
try {
Thread.sleep(Integer.valueOf(tokens.get(0)));
} catch (InterruptedException e) {
if (stopping) {
break;
}
}
} else if (cmd.equals("teleport")) {
teleport(tokens);
} else if (cmd.equals("npc")) {
npcSpawn(tokens);
} else if (cmd.equals("npckill")) {
npcKill(tokens);
} else if (cmd.equals("say")) {
say(tokens);
} else if (cmd.equals("broadcast")) {
broadcast(tokens);
} else if (cmd.equals("execsvrcmd")) {
execsvrcmd(tokens);
} else if (cmd.equals("execcmd")) {
execcmd(tokens);
} else if (cmd.equals("set")) {
set(tokens);
} else if (cmd.equals("inc")) {
increment(tokens);
} else if (cmd.equals("dec")) {
decrement(tokens);
} else if (cmd.equals("push")) {
push(tokens);
} else if (cmd.equals("if")) {
condition(tokens, actions);
} else if (cmd.equals("else")) {
currline = ifstack.remove(0);
} else if (cmd.equals("while")) {
loop(tokens, actions);
} else if (cmd.equals("break")) {
breakloop(tokens, actions);
} else if (cmd.equals("endwhile") || cmd.equals("continue")) {
currline = whilestack.remove(0);
} else if (cmd.equals("run")) {
cmdrun(tokens, false);
} else if (cmd.equals("launch")) {
cmdrun(tokens, true);
} else {
notifyError("Command not found!");
}
currline++;
}
// implicit return value -> empty array
threadstack.add(0, PostfixEvaluator.fromArray(new ArrayList<String>()));
// finished -> remove itself from the running thread list
if (threadname != null) {
eventHost.running.remove(threadname);
}
}
private ArrayList<String> parseLine(String line) {
ArrayList<String> tokens = new ArrayList<String>();
String currtok = null;
boolean inString = false;
for (int i = 0; i < line.length(); i++) {
if (!inString && String.valueOf(line.charAt(i)).matches("\\s")) {
if (currtok != null) {
tokens.add(currtok);
currtok = null;
}
continue;
}
if (currtok == null) {
currtok = "";
}
if (inString && line.charAt(i) == '\\') { // Escape character
i++;
currtok += line.charAt(i);
continue;
}
if (line.charAt(i) == '"') {
inString = !inString;
continue;
}
currtok += line.charAt(i);
}
if (currtok != null) {
tokens.add(currtok);
}
return tokens;
}
private void evaluateVariables(ArrayList<String> tokens) {
for (int i = 0; i < tokens.size(); i++) {
String var = evaluateVar(tokens.get(i));
if (var != null) {
tokens.set(i, var);
}
}
}
protected String evaluateVar(String varname) {
if (varname == null || varname.equals("")) {
return "";
}
if (varname.charAt(0) == REFERENCEOP) { // escape variable
char c = varname.charAt(1);
if (c == LOCALSCOPE || c == PLAYERSCOPE || c == GLOBALSCOPE) {
return varname.substring(1);
} else {
return null; // not a variable
}
} else if (varname.charAt(0) == LOCALSCOPE) { // local var
String loc = varname.substring(1);
// check special vars
if (loc.equals("PLAYER")) {
return player != null ? player.getName() : "null";
} else if (loc.equals("EVENT")) {
return event.name;
} else if (loc.equals("THIS")) {
return "$" + event.name;
} else if (loc.equals("VALUE")) {
return eventHost.globals.get(event.name);
} else if (loc.equals("COORD")) {
return String.valueOf(event.coordinate);
} else if (loc.equals("POP")) {
return threadstack.size() == 0 ? "null" : threadstack.remove(0);
} else if (loc.equals("TOP")) {
return threadstack.size() == 0 ? "null" : threadstack.get(0);
} else if (eventHost.colors.containsKey(loc)) {
return "\u00a7" + eventHost.colors.get(loc);
} else {
String v = vars.get(loc);
if (v == null) {
vars.put(loc, "null");
v = "null";
}
return String.valueOf(v);
}
} else if (varname.charAt(0) == PLAYERSCOPE) { // player var
return String.valueOf(player.vars.get(varname.substring(1)));
} else if (varname.charAt(0) == GLOBALSCOPE) { // global perm var (event
// value)
String name = varname.substring(1);
if (eventHost.globals.containsKey(name)) {
String ret = eventHost.globals.get(name);
return ret;
} else {
notifyError("Event " + name + " not found!");
return "null";
}
}
return null; // not a variable
}
/*---- Interaction ----*/
private void say(ArrayList<String> tokens) {
if (tokens.size() < 2) {
notifyError("Wrong number of arguments!");
return;
}
Player p = server.findPlayer(tokens.remove(0));
if (p == null) {
notifyError("Player not found!");
return;
}
String message = new PostfixEvaluator(this).evaluateSingle(tokens);
p.addTMessage(message);
}
private void broadcast(ArrayList<String> tokens) {
String message = new PostfixEvaluator(this).evaluateSingle(tokens);
server.runCommand("say", message);
}
private void teleport(ArrayList<String> tokens) {
if (tokens.size() < 2) {
notifyError("Wrong number of arguments!");
return;
}
Player p = server.findPlayer(tokens.get(0));
if (p == null) {
notifyError("Source player not online!");
return;
}
Coordinate c = null;
String dest = tokens.get(1);
// Try to find such a player
Player q = server.findPlayer(dest);
if (q != null) {
c = q.position.coordinate();
}
// Try to find such an event
if (c == null && dest.length() > 0 && dest.charAt(0) == GLOBALSCOPE) {
Event evdest = eventHost.findEvent(dest.substring(1));
if (evdest != null) {
c = evdest.coordinate;
}
}
if (c == null) { // try to find a warp with that name
String w = server.data.warp.getName(dest);
if (w != null) {
p.teleportSelf(server.data.warp.get(w)); // port to warp, ignore rest
return;
}
}
// try to parse as coordinate
if (c == null) {
c = Coordinate.fromString(dest);
}
if (c != null) {
p.teleportSelf(c);
} else {
notifyError("Destination not found!");
}
}
private void execcmd(ArrayList<String> tokens) {
if (tokens.size() == 0) {
notifyError("Wrong number of arguments!");
return; // no command to execute
}
Player p = server.findPlayer(tokens.remove(0));
if (p == null) {
notifyError("Player not found!");
return;
}
String message = server.options.getBoolean("useSlashes") ? "/" : "!";
for (String token : tokens) {
message += token + " ";
}
message = message.substring(0, message.length() - 1);
// execute the server command, overriding the player permissions
p.parseCommand(message, true);
// handle forwarding
String cmd = tokens.get(0);
PlayerCommand command = server.resolvePlayerCommand(cmd, p.getGroup());
// forwarding if necessary
CommandConfig config = server.config.commands.getTopConfig(cmd);
if ((command instanceof ExternalCommand)
|| (config != null && config.forwarding != Forwarding.NONE)
|| server.config.properties.getBoolean("forwardAllCommands")) {
p.forwardMessage(message);
}
}
private void execsvrcmd(ArrayList<String> tokens) {
if (tokens.size() == 0) {
notifyError("Wrong number of arguments!");
return; // no command to execute
}
String cmd = tokens.get(0);
String args = "";
for (String t : tokens) {
args += t + " ";
}
args = args.substring(0, args.length() - 1);
ServerCommand command = server.getCommandParser().getServerCommand(cmd);
if (command != null) {
server.runCommand(cmd, args);
}
}
private void npcSpawn(ArrayList<String> tokens) {
if (tokens.size() < 5) {
notifyError("Wrong number of arguments!");
return;
}
String name = tokens.remove(0);
if (eventHost.npcs.get(name) != null) {
notifyError("An NPC with this name still exists!");
return;
}
Event event = eventHost.findEvent(tokens.remove(0));
if (event == null) {
notifyError("Event associated with NPC not found!");
return;
}
int x = 0;
int y = 0;
int z = 0;
try {
x = Integer.valueOf(tokens.remove(0));
y = Integer.valueOf(tokens.remove(0));
z = Integer.valueOf(tokens.remove(0));
} catch (Exception e) {
notifyError("Invalid NPC spawn coordinate!");
return;
}
Coordinate c = new Coordinate(x, y, z);
try {
NpcBot s = new NpcBot(name, event.name, server, c);
server.bots.connect(s);
eventHost.npcs.put(name, s);
} catch (IOException ex) {
notifyError("Could not spawn NPC!");
ex.printStackTrace();
} catch (ConnectException e) {
notifyError("Could not spawn NPC!");
e.printStackTrace();
}
}
private void npcKill(ArrayList<String> tokens) {
if (tokens.size() < 1) {
notifyError("Wrong number of arguments!");
return;
}
String name = new PostfixEvaluator(this).evaluateSingle(tokens);
NpcBot b = eventHost.npcs.remove(name);
if (b == null) {
notifyError("An NPC with this name is not logged on!");
return;
}
try {
b.logout();
} catch (IOException e) {
notifyError("Error while logging out bot!");
e.printStackTrace();
}
}
/*---- variable & runtime ----*/
private void set(ArrayList<String> tokens) {
if (tokens.size() < 2) {
notifyError("Wrong number of arguments!");
return;
}
ArrayList<String> vals = new PostfixEvaluator(this).evaluate(tokens);
if (vals == null || vals.size() < 2) {
return;
}
/* only 2 elements left - normal behaviour like before */
if (vals.size() == 2) {
String s = vals.remove(0);
String v = vals.remove(0);
assignVariable(s, v);
} else if (vals.size() > 2) { /* more elements = multiple assignment */
/* separating symbols and values (looking for "to" keyword */
ArrayList<String> vars = new ArrayList<String>();
while (vals.size() > 0 && !vals.get(0).equals("to")) {
vars.add(vals.remove(0));
}
if (vals.size() == 0) {
notifyError("Invalid multiple assignment / invalid expression! ('to' keyword missing?)");
return;
}
vals.remove(0); // remove 'to'
/* execute multiple assignment */
while (vars.size() != 0) {
String s = vars.remove(0);
String v = "null";
if (vals.size() != 0) {
if (vars.size() != 0 || vals.size() == 1) {
v = vals.remove(0);
} else {
v = PostfixEvaluator.fromArray(vals);
}
}
assignVariable(s, v);
}
}
}
private void increment(ArrayList<String> tokens) {
if (tokens.size() != 1) {
notifyError("Wrong number of arguments!");
return;
}
String v = tokens.get(0);
assignVariable(v, String.valueOf(PostfixEvaluator.toNum(evaluateVar(v)) + 1));
}
private void decrement(ArrayList<String> tokens) {
if (tokens.size() != 1) {
notifyError("Wrong number of arguments!");
return;
}
String v = tokens.get(0);
assignVariable(v, String.valueOf(PostfixEvaluator.toNum(evaluateVar(v)) - 1));
}
private void assignVariable(String symbol, String value) {
if (symbol == null || symbol.length() < 2) {
return;
}
if (value == null) {
value = "null";
}
char scope = symbol.charAt(0);
String var = symbol.substring(1);
if (scope == LOCALSCOPE) {
vars.put(var, value);
} else if (scope == PLAYERSCOPE) {
player.vars.put(var, value);
} else if (scope == GLOBALSCOPE) {
if (eventHost.globals.containsKey(var)) {
eventHost.globals.put(var, value);
} else {
notifyError("Assignment failed: event " + var + " not found!");
}
} else {
notifyError("Assignment failed: Invalid variable reference!");
}
}
private void push(ArrayList<String> tokens) {
String exp = new PostfixEvaluator(this).evaluateSingle(tokens);
if (exp == null) {
threadstack.add(0, "null");
return;
}
threadstack.add(0, exp);
}
private void cmdreturn(ArrayList<String> tokens) {
threadstack.add(0, PostfixEvaluator.fromArray(new PostfixEvaluator(this).evaluate(tokens)));
}
private void cmdrun(ArrayList<String> tokens, boolean newThread) {
if (tokens.size() < 1) {
notifyError("Wrong number of arguments!");
return;
}
Event e = eventHost.findEvent(tokens.remove(0));
if (e == null) {
notifyError("Event to run not found!");
return;
}
String args = PostfixEvaluator.fromArray(new PostfixEvaluator(this).evaluate(tokens));
if (!newThread) {
if (stackdepth < MAXDEPTH) {
threadstack.add(0, args);
(new RunningEvent(eventHost, null, e, player, stackdepth + 1, threadstack)).run();
} else {
notifyError("Can not run event - stack level too deep!");
}
} else { // run in a new thread (passing threadstack copy!)
@SuppressWarnings("unchecked")
ArrayList<String> clone = (ArrayList<String>) threadstack.clone();
clone.add(args);
eventHost.executeEvent(e, player, clone);
}
}
private void condition(ArrayList<String> tokens, ArrayList<String> actions) {
boolean result = new PostfixEvaluator(this).evaluateCondition(tokens);
int ifcounter = 0;
boolean hasElse = false;
if (result) {
for (int i = currline + 1; i < actions.size(); i++) {
ArrayList<String> line = parseLine(actions.get(i));
if (line.size() == 0) {
continue;
}
String cmd = line.get(0);
if (cmd.equals("if")) {
ifcounter++;
}
if (cmd.equals("else") && ifcounter == 0) {
hasElse = true;
}
if (cmd.equals("endif")) {
if (ifcounter == 0) {
if (hasElse) {
ifstack.add(0, i);
}
break;
} else {
ifcounter--;
}
}
}
} else {
for (int i = currline + 1; i < actions.size(); i++) {
ArrayList<String> line = parseLine(actions.get(i));
if (line.size() == 0) {
continue;
}
String cmd = line.get(0);
if (cmd.equals("if")) {
ifcounter++;
}
if (cmd.equals("endif")) {
if (ifcounter == 0) { // no else found -> jump here
currline = i;
break;
}
ifcounter--;
}
if (cmd.equals("else") && ifcounter == 0) { // jump to else part
currline = i;
break;
}
}
}
}
private void loop(ArrayList<String> tokens, ArrayList<String> actions) {
boolean result = new PostfixEvaluator(this).evaluateCondition(tokens);
if (!result) { // jump over while loop -> leave
breakloop(tokens, actions);
} else { // save current line for jump back -> enter
whilestack.add(0, currline - 1);
}
}
private void breakloop(ArrayList<String> tokens, ArrayList<String> actions) {
int whilecounter = 0;
for (int i = currline + 1; i < actions.size(); i++) {
ArrayList<String> line = parseLine(actions.get(i));
if (line.size() == 0) {
continue;
}
String cmd = line.get(0);
if (cmd.equals("while")) {
whilecounter++;
}
if (cmd.equals("endwhile")) {
if (whilecounter == 0) {
currline = i;
break;
} else {
whilecounter--;
}
}
}
}
/*--------*/
protected void notifyError(String err) {
System.out.println("Error at L" + String.valueOf(currline) + "@" + event.name + ": " + lastline);
System.out.println(err + "\n");
}
}