// Ryzom - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program 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 Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #include "command_event_manager.h" #include "nel/misc/time_nl.h" #include "nel/misc/file.h" #include "nel/misc/path.h" #include "nel/misc/command.h" #include "nel/misc/variable.h" #include "game_share/tick_event_handler.h" using namespace std; using namespace NLMISC; using namespace NLNET; //using CCommandEventManager::CScript; //using CCommandEventManager::CScriptNode; //using CCommandEventManager::CEnvironment; #define EXEC_CHILD_AND_EXIT(child) if (child) return executeChild(child, env) #define EXEC_CHILD_AND_EXIT_ON_HALT_ERROR(child) if (child) { EState state = executeChild(child, env); if (state==Halt || state==Error) return state; } CCommandEventManager::TScriptMap CCommandEventManager::_RunningScripts; string CCommandEventManager::_Empty(""); /* * Constructor */ CCommandEventManager::CCommandEventManager() { } // // Env // string CCommandEventManager::CEnvironment::replace(const string &text) { uint start = 0, stop; string replaced = text; do { start = replaced.find_first_of('$', start); if (start == string::npos || start == replaced.size()-1) break; if (replaced[start+1] == '$') { replaced.erase(start, 1); ++start; continue; } else if (replaced[start+1] == '(') { stop = replaced.find_first_of(')', start+1); if (stop == string::npos) break; string val = get(replaced.substr(start+2, stop-start-2)); replaced.erase(start, stop-start+1); replaced.insert(start, val); start += val.size(); } else if (replaced[start+1] == '[') { stop = replaced.find_first_of(']', start+1); if (stop == string::npos) break; string var = replaced.substr(start+2, stop-start-2); string val = ""; ICommand::TCommand::iterator it = ICommand::Commands->find(var); if (it != ICommand::Commands->end()) { IVariable *variable = dynamic_cast((*it).second); if (variable != NULL) { val = variable->toString(); } } replaced.erase(start, stop-start+1); replaced.insert(start, val); start += val.size(); } else { ++start; } } while (start < replaced.size()); return replaced; } // // Script nodes // // ----------------------------------------------------------- // list node class CInstListNode : public CCommandEventManager::CScriptNode { public: vector Nodes; uint NextToRun; CInstListNode() : NextToRun(0) {} ~CInstListNode() { uint i; for (i=0; iVerbose) nldebug("[%s:%d:DEBUG] if %s", Root->Name.c_str(), Line, Condition.c_str()); double res; CEvalNumExpr::TReturnState ev = env.evalExpression(Condition.c_str(), res, NULL); if (ev == CEvalNumExpr::NoError) { if (res != 0.0) { // exec true statement EXEC_CHILD_AND_EXIT(TrueStatement); } else { // exec false statement EXEC_CHILD_AND_EXIT(FalseStatement); } } else { return Error; } return Ok; } }; // ----------------------------------------------------------- // while node class CWhileNode : public CCommandEventManager::CScriptNode { public: string Condition; CCommandEventManager::CScriptNode *Statement; CWhileNode() : Statement(NULL) {} ~CWhileNode() { if (Statement) delete Statement; } EState execute(CCommandEventManager::CEnvironment &env) { if (CurrentRun) { EState state = goOnCurrentRun(env); if (state == Error || state == Halt) return state; } do { if (Root->Verbose) nldebug("[%s:%d:DEBUG] while %s", Root->Name.c_str(), Line, Condition.c_str()); double res; CEvalNumExpr::TReturnState ev = env.evalExpression(Condition.c_str(), res, NULL); if (ev == CEvalNumExpr::NoError) { if (res != 0.0) { // exec true statement if (Statement) { EXEC_CHILD_AND_EXIT_ON_HALT_ERROR(Statement); } } else { break; } } else { return Error; } } while (true); return Ok; } }; // ----------------------------------------------------------- // cmd node class CCmdNode : public CCommandEventManager::CScriptNode { public: string Command; EState execute(CCommandEventManager::CEnvironment &env) { if (Root->Verbose) nldebug("[%s:%d:DEBUG] cmd %s", Root->Name.c_str(), Line, Command.c_str()); ICommand::execute(env.replace(Command), *InfoLog); return Ok; } }; // ----------------------------------------------------------- // set text node class CSetTextNode : public CCommandEventManager::CScriptNode { public: string Variable; string Text; EState execute(CCommandEventManager::CEnvironment &env) { if (Root->Verbose) nldebug("[%s:%d:DEBUG] set$ %s %s", Root->Name.c_str(), Line, Variable.c_str(), Text.c_str()); env.set(Variable, env.replace(Text)); return Ok; } }; // ----------------------------------------------------------- // set text node class CSetNumericNode : public CCommandEventManager::CScriptNode { public: string Variable; string Text; EState execute(CCommandEventManager::CEnvironment &env) { if (Root->Verbose) nldebug("[%s:%d:DEBUG] set# %s %s", Root->Name.c_str(), Line, Variable.c_str(), Text.c_str()); double res; CEvalNumExpr::TReturnState ev = env.evalExpression(Text.c_str(), res, NULL); if (ev == CEvalNumExpr::NoError) { env.set(Variable, toString(res)); return Ok; } else { return Error; } } }; // ----------------------------------------------------------- // erase node class CEraseNode : public CCommandEventManager::CScriptNode { public: string Variable; EState execute(CCommandEventManager::CEnvironment &env) { if (Root->Verbose) nldebug("[%s:%d:DEBUG] erase %s", Root->Name.c_str(), Line, Variable.c_str()); env.erase(Variable); return Ok; } }; // ----------------------------------------------------------- // wait node class CWaitNode : public CCommandEventManager::CScriptNode { public: uint Wait; TGameCycle StartCycle; CWaitNode() : Wait(0), StartCycle(0) {} EState execute(CCommandEventManager::CEnvironment &env) { if (!StartCycle) { if (Root->Verbose) nldebug("[%s:%d:DEBUG] wait %d", Root->Name.c_str(), Line, Wait); StartCycle = CTickEventHandler::getGameCycle(); return Halt; } else if (CTickEventHandler::getGameCycle() >= StartCycle+Wait) { StartCycle = 0; return Ok; } else { return Halt; } } }; // ----------------------------------------------------------- // display node class CDisplayNode : public CCommandEventManager::CScriptNode { public: string Text; EState execute(CCommandEventManager::CEnvironment &env) { if (Root->Verbose) nldebug("[%s:%d:DEBUG] ? %s", Root->Name.c_str(), Line, Text.c_str()); nlinfo("[%s] %s", Root->Name.c_str(), env.replace(Text).c_str()); return Ok; } }; // ----------------------------------------------------------- // wait node class CReceiveNode : public CCommandEventManager::CScriptNode { public: bool Waiting; string Expression; CReceiveNode() : Waiting(false) {} EState execute(CCommandEventManager::CEnvironment &env) { if (!Waiting) { if (Root->Verbose) nldebug("[%s:%d:DEBUG] receive %s", Root->Name.c_str(), Line, Expression.c_str()); Waiting = true; } double res; CEvalNumExpr::TReturnState ev = env.evalExpression(Expression.c_str(), res, NULL); if (ev != CEvalNumExpr::NoError) return Error; else { if (res != 0.0) { Waiting = false; return Ok; } else { return Halt; } } return (ev != CEvalNumExpr::NoError) ? Error : ((res != 0.0) ? Ok : Halt); } }; // ----------------------------------------------------------- // wait node class CSendNode : public CCommandEventManager::CScriptNode { public: string Variable; string Text; EState execute(CCommandEventManager::CEnvironment &env) { if (Root->Verbose) nldebug("[%s:%d:DEBUG] send %s %s", Root->Name.c_str(), Line, Variable.c_str(), Text.c_str()); CCommandEventManager::sendEvent(Variable, env.replace(Text)); return Ok; } }; // ----------------------------------------------------------- // Parse script CCommandEventManager::CScript *parsedScript = NULL; enum { TOKEN_UNKNOWN = 0, TOKEN_END, TOKEN_COMMENT, TOKEN_IF, TOKEN_ELSE, TOKEN_ENDIF, TOKEN_WHILE, TOKEN_ENDLOOP, TOKEN_CMD, // execute a command (while command is evaluated using $(...) syntax) TOKEN_SETTEXT, // set a variable to a text value (evaluated using $(...) syntax) TOKEN_SETNUMERIC, // set a variable to a numeric value (evaluated as a normal numeric expression, variables don't need any $(...) form) TOKEN_ERASE, // erase a variable from env TOKEN_WAIT, // wait for n game cycles TOKEN_RECEIVE, // wait for expression to be evaluated to true TOKEN_SEND, TOKEN_DISPLAY, }; class CAssociatedToken { public: CAssociatedToken(uint token, string chars) : Token(token), Chars(chars) {} uint Token; string Chars; }; CAssociatedToken CTable[] = { CAssociatedToken(TOKEN_IF, "if"), CAssociatedToken(TOKEN_ELSE, "else"), CAssociatedToken(TOKEN_ENDIF, "endif"), CAssociatedToken(TOKEN_WHILE, "while"), CAssociatedToken(TOKEN_ENDLOOP, "endloop"), CAssociatedToken(TOKEN_CMD, "cmd"), CAssociatedToken(TOKEN_SETTEXT, "setText"), CAssociatedToken(TOKEN_SETTEXT, "set$"), CAssociatedToken(TOKEN_SETNUMERIC, "setNumeric"), CAssociatedToken(TOKEN_SETNUMERIC, "set#"), CAssociatedToken(TOKEN_ERASE, "erase"), CAssociatedToken(TOKEN_WAIT, "wait"), CAssociatedToken(TOKEN_RECEIVE, "receive"), CAssociatedToken(TOKEN_SEND, "send"), CAssociatedToken(TOKEN_DISPLAY, "display"), CAssociatedToken(TOKEN_DISPLAY, "?"), CAssociatedToken(TOKEN_COMMENT, "!"), }; class CParser { public: const char *Text; const char *TokenStart; uint Line; CParser() : Text(NULL), Line(0) {} void skipSpace() { while (*Text != '\0' && (*Text == ' ' || *Text == '\t')) ++Text; } void skipAllSpace() { while (*Text != '\0' && (*Text == ' ' || *Text == '\t' || *Text == '\r' || *Text == '\n')) { if (*Text == '\n') ++Line; ++Text; } } void skipToSpace() { while (*Text != '\0' && (*Text != ' ' && *Text != '\t' && *Text != '\r' && *Text != '\n')) ++Text; } void skipLine() { while (*Text != '\0' && (*Text != '\n' && *Text != '\r')) ++Text; } string getLine() { const char *first = Text; const char *last = Text; while (*Text != '\0' && (*Text != '\n' && *Text != '\r')) { if (*Text != ' ' && *Text != '\t') last = Text; ++Text; } return string(first, last-first+1); } bool compare(const char *a, const char *t) { while (*t>32 && *t==*a) ++t, ++a; return ((*a=='\0' || *a==' ' || *a=='\n' || *a=='\r' || *a=='\t') && (*t=='\0' || *t==' ' || *t=='\n' || *t=='\r' || *t=='\t')); } string getWord() { skipSpace(); const char *start = Text; skipToSpace(); return string(start, Text-start); } uint getToken() { skipAllSpace(); if (*Text == '\0') return TOKEN_END; TokenStart = Text; skipToSpace(); skipSpace(); uint i; for (i=0; iRoot = parsedScript; node->Line = Parser.Line; node->Condition = Parser.getLine(); Parser.skipLine(); node->TrueStatement = parseInstructionList(); uint token = Parser.getToken(); if (token == TOKEN_ELSE) { node->FalseStatement = parseInstructionList(); token = Parser.getToken(); } if (token != TOKEN_ENDIF) { nlwarning("[%s] Unknown token found in structure if, expected endif at line %d. Skip line", parsedScript->Name.c_str(), Parser.Line); Parser.skipLine(); } return (CCommandEventManager::CScriptNode*)node; } // CCommandEventManager::CScriptNode *parseWhile() { Parser.skipSpace(); CWhileNode *node = new CWhileNode(); node->Root = parsedScript; node->Line = Parser.Line; node->Condition = Parser.getLine(); Parser.skipLine(); node->Statement = parseInstructionList(); if (Parser.getToken() != TOKEN_ENDLOOP) { nlwarning("[%s] Unknown token found in structure while, expected endloop at line %d. Skip line", parsedScript->Name.c_str(), Parser.Line); Parser.skipLine(); } return (CCommandEventManager::CScriptNode*)node; } // CCommandEventManager::CScriptNode *parseCmd() { Parser.skipSpace(); CCmdNode *node = new CCmdNode(); node->Root = parsedScript; node->Line = Parser.Line; node->Command = Parser.getLine(); Parser.skipLine(); return (CCommandEventManager::CScriptNode*)node; } // CCommandEventManager::CScriptNode *parseSetText() { Parser.skipSpace(); CSetTextNode *node = new CSetTextNode(); node->Root = parsedScript; node->Line = Parser.Line; node->Variable = Parser.getWord(); Parser.skipSpace(); node->Text = Parser.getLine(); Parser.skipLine(); return (CCommandEventManager::CScriptNode*)node; } // CCommandEventManager::CScriptNode *parseSetNumeric() { Parser.skipSpace(); CSetNumericNode *node = new CSetNumericNode(); node->Root = parsedScript; node->Line = Parser.Line; node->Variable = Parser.getWord(); Parser.skipSpace(); node->Text = Parser.getLine(); Parser.skipLine(); return (CCommandEventManager::CScriptNode*)node; } // CCommandEventManager::CScriptNode *parseErase() { Parser.skipSpace(); CEraseNode *node = new CEraseNode(); node->Root = parsedScript; node->Line = Parser.Line; node->Variable = Parser.getWord(); Parser.skipLine(); return (CCommandEventManager::CScriptNode*)node; } // CCommandEventManager::CScriptNode *parseWait() { Parser.skipSpace(); CWaitNode *node = new CWaitNode(); node->Root = parsedScript; node->Line = Parser.Line; node->Wait = atoi(Parser.getWord().c_str()); Parser.skipLine(); return (CCommandEventManager::CScriptNode*)node; } // CCommandEventManager::CScriptNode *parseReceive() { Parser.skipSpace(); CReceiveNode *node = new CReceiveNode(); node->Root = parsedScript; node->Line = Parser.Line; node->Expression = Parser.getLine(); Parser.skipLine(); return (CCommandEventManager::CScriptNode*)node; } // CCommandEventManager::CScriptNode *parseSend() { Parser.skipSpace(); CSendNode *node = new CSendNode(); node->Root = parsedScript; node->Line = Parser.Line; node->Variable = Parser.getWord(); Parser.skipSpace(); node->Text = Parser.getLine(); Parser.skipLine(); return (CCommandEventManager::CScriptNode*)node; } // CCommandEventManager::CScriptNode *parseDisplay() { Parser.skipSpace(); CDisplayNode *node = new CDisplayNode(); node->Root = parsedScript; node->Line = Parser.Line; node->Text = Parser.getLine(); Parser.skipLine(); return (CCommandEventManager::CScriptNode*)node; } // CCommandEventManager::CScriptNode *parseInstructionList() { CInstListNode *list = new CInstListNode(); list->Root = parsedScript; list->Line = Parser.Line; do { uint token = Parser.getToken(); switch (token) { case TOKEN_END: return list; break; case TOKEN_IF: list->Nodes.push_back(parseIf()); break; case TOKEN_ELSE: Parser.rewindToken(); return list; break; case TOKEN_ENDIF: Parser.rewindToken(); return list; break; case TOKEN_WHILE: list->Nodes.push_back(parseWhile()); break; case TOKEN_ENDLOOP: Parser.rewindToken(); return list; break; case TOKEN_CMD: list->Nodes.push_back(parseCmd()); break; case TOKEN_SETTEXT: list->Nodes.push_back(parseSetText()); break; case TOKEN_SETNUMERIC: list->Nodes.push_back(parseSetNumeric()); break; case TOKEN_ERASE: list->Nodes.push_back(parseErase()); break; case TOKEN_WAIT: list->Nodes.push_back(parseWait()); break; case TOKEN_RECEIVE: list->Nodes.push_back(parseReceive()); break; case TOKEN_SEND: list->Nodes.push_back(parseSend()); break; case TOKEN_DISPLAY: list->Nodes.push_back(parseDisplay()); break; case TOKEN_COMMENT: Parser.skipLine(); break; case TOKEN_UNKNOWN: nlwarning("[%s] Unknow token at line %d: '%20s...', line ignored", parsedScript->Name.c_str(), Parser.Line, Parser.TokenStart); Parser.skipLine(); break; } } while (true); return (CCommandEventManager::CScriptNode*)list; } bool CCommandEventManager::CScript::parse(const char *text) { parsedScript = this; Parser.Text = text; Parser.Line = 1; Root = parseInstructionList(); return true; } // ----------------------------------------------------------- // Runs script till it is finished or it waits for an event bool CCommandEventManager::CScript::run() { CScriptNode::EState state = Root->execute(Environment); if (state == CScriptNode::Error) { nlwarning("[%s] An error occured in script, script ended", Name.c_str()); return false; } else if (state == CScriptNode::Ok) { nlinfo("[%s] Script succesfully terminated, script ended", Name.c_str()); return false; } else { return true; } } // void cbProcessScriptEvent( CMessage& msgin, const string &serviceName, uint16 serviceId ) { string var; string value; msgin.serial(var, value); CCommandEventManager::processEvent(var, value); } TUnifiedCallbackItem CbScriptArray[] = { { "S_EVENT", cbProcessScriptEvent, } }; // void CCommandEventManager::init() { CUnifiedNetwork::getInstance()->addCallbackArray( CbScriptArray, sizeof(CbScriptArray)/sizeof(CbScriptArray[0]) ); } // void CCommandEventManager::release() { TScriptMap::iterator it; for (it = _RunningScripts.begin(); it != _RunningScripts.end(); ++it) delete (*it).second; _RunningScripts.clear(); } // bool CCommandEventManager::update() { TScriptMap::iterator it; for (it = _RunningScripts.begin(); it != _RunningScripts.end(); ) if (!((*it).second->run())) { TScriptMap::iterator itn = it++; delete (*itn).second; _RunningScripts.erase(itn); } else { ++it; } return true; } // bool CCommandEventManager::runScript(const std::string &scriptName, const std::string &scriptFile, bool verbosity) { if (_RunningScripts.find(scriptName) != _RunningScripts.end()) { nlwarning("Script '%s' already running, stop script or wait for its end before restarting it", scriptName.c_str()); return false; } string fname = CPath::lookup(scriptFile, false); if (fname == "") { nlwarning("Can't find file '%s' to launch script '%s'", scriptFile.c_str(), scriptName.c_str()); return false; } CIFile file(fname); uint sz = file.getFileSize(); char *buffer = new char[sz+1]; try { file.serialBuffer((uint8*)buffer, sz); buffer[sz] = '\0'; } catch (EReadError&) { nlwarning("Unable to read script file '%s' for script '%s'", scriptFile.c_str(), scriptName.c_str()); delete buffer; return false; } CScript *script = new CScript(scriptName); script->Verbose = verbosity; if (!script->parse(buffer)) { nlwarning("Unable to parse script file '%s' for script '%s'", scriptFile.c_str(), scriptName.c_str()); delete buffer; delete script; return false; } script->init(); nlinfo("Loaded script file '%s' for script '%s' and initialized, ready to run (verbosity %s)", scriptFile.c_str(), scriptName.c_str(), verbosity ? "on":"off"); _RunningScripts.insert(make_pair(scriptName, script)); delete buffer; return true; } bool CCommandEventManager::stopScript(const std::string &scriptName) { TScriptMap::iterator it = _RunningScripts.find(scriptName); if (it == _RunningScripts.end()) { nlwarning("Can't stop script '%s', script not found (already stopped?)", scriptName.c_str()); return false; } // delete script delete (*it).second; _RunningScripts.erase(it); return true; } const std::string &CCommandEventManager::getVarInScript(const std::string &scriptName, const std::string &varName) { TScriptMap::iterator it = _RunningScripts.find(scriptName); if (it == _RunningScripts.end()) { nlwarning("Can't find script '%s' to get variable '%s'", scriptName.c_str(), varName.c_str()); return _Empty; } return (*it).second->Environment.get(varName); } void CCommandEventManager::setVarInScript(const std::string &scriptName, const std::string &varName, const std::string &value) { TScriptMap::iterator it = _RunningScripts.find(scriptName); if (it == _RunningScripts.end()) { nlwarning("Can't find script '%s' to set variable '%s' to '%s'", scriptName.c_str(), varName.c_str(), value.c_str()); return; } (*it).second->Environment.set(varName, value); } void CCommandEventManager::eraseVarInScript(const std::string &scriptName, const std::string &varName) { TScriptMap::iterator it = _RunningScripts.find(scriptName); if (it == _RunningScripts.end()) { nlwarning("Can't find script '%s' to erase variable '%s'", scriptName.c_str(), varName.c_str()); return; } (*it).second->Environment.erase(varName); } void CCommandEventManager::sendEvent(const std::string &var, const std::string &value) { CMessage msg("S_EVNT"); msg.serial(const_cast(var)); msg.serial(const_cast(value)); //CUnifiedNetwork::getInstance()->sendAll(msg); processEvent(var, value); } void CCommandEventManager::processEvent(const std::string &var, const std::string &value) { TScriptMap::iterator it; for (it = _RunningScripts.begin(); it != _RunningScripts.end(); ++it) (*it).second->Environment.set(var, value); } // void CCommandEventManager::setScriptVerbose(const std::string &scriptName, bool verbosity) { TScriptMap::iterator it = _RunningScripts.find(scriptName); if (it == _RunningScripts.end()) { nlwarning("Can't find script '%s' to set verbosity", scriptName.c_str()); return; } (*it).second->Verbose = verbosity; } // commands NLMISC_COMMAND(runScript, "Run a script"," [verbosity]") { if (args.size() < 2 || args.size() > 3) return false; bool verb = false; if (args.size() == 3) verb = !(args[2] == "0"); CCommandEventManager::runScript(args[0], args[1], verb); return true; } NLMISC_COMMAND(stopScript, "Stop a running script","") { if (args.size() != 1) return false; CCommandEventManager::stopScript(args[0]); return true; } NLMISC_COMMAND(getVarInScript, "Get a variable value in a script"," ") { if (args.size() != 2) return false; CCommandEventManager::getVarInScript(args[0], args[1]); return true; } NLMISC_COMMAND(setVarInScript, "Set a variable value in a script"," ") { if (args.size() != 3) return false; CCommandEventManager::setVarInScript(args[0], args[1], args[2]); return true; } NLMISC_COMMAND(eraseVarInScript, "Erase a variable value in a script"," ") { if (args.size() != 2) return false; CCommandEventManager::eraseVarInScript(args[0], args[1]); return true; } NLMISC_COMMAND(sendEvent, "Send a script event to all scripts (on all services of the shard)"," ") { if (args.size() != 2) return false; CCommandEventManager::sendEvent(args[0], args[1]); return true; }