// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/> // 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 <http://www.gnu.org/licenses/>. #include "mission_compiler.h" #include "step.h" #include "nel/misc/i18n.h" using namespace std; using namespace NLMISC; using namespace NLLIGO; // hack to get access to string manager item enumeration to string without including // almost all of the Ryzom server side project namespace STRING_MANAGER { NL_BEGIN_STRING_CONVERSION_TABLE (TParamType) NL_STRING_CONVERSION_TABLE_ENTRY( item ) NL_STRING_CONVERSION_TABLE_ENTRY( place ) NL_STRING_CONVERSION_TABLE_ENTRY( creature ) NL_STRING_CONVERSION_TABLE_ENTRY( skill ) NL_STRING_CONVERSION_TABLE_ENTRY( role ) NL_STRING_CONVERSION_TABLE_ENTRY( ecosystem ) NL_STRING_CONVERSION_TABLE_ENTRY( race ) NL_STRING_CONVERSION_TABLE_ENTRY( sbrick ) NL_STRING_CONVERSION_TABLE_ENTRY( faction ) NL_STRING_CONVERSION_TABLE_ENTRY( guild ) NL_STRING_CONVERSION_TABLE_ENTRY( player ) NL_STRING_CONVERSION_TABLE_ENTRY( bot ) { "int", integer}, // NL_STRING_CONVERSION_TABLE_ENTRY( integer ) NL_STRING_CONVERSION_TABLE_ENTRY( time ) NL_STRING_CONVERSION_TABLE_ENTRY( money ) NL_STRING_CONVERSION_TABLE_ENTRY( compass ) NL_STRING_CONVERSION_TABLE_ENTRY( string_id ) NL_STRING_CONVERSION_TABLE_ENTRY( dyn_string_id ) NL_STRING_CONVERSION_TABLE_ENTRY( self ) NL_STRING_CONVERSION_TABLE_ENTRY( creature_model ) NL_STRING_CONVERSION_TABLE_ENTRY( entity ) NL_STRING_CONVERSION_TABLE_ENTRY( body_part ) NL_STRING_CONVERSION_TABLE_ENTRY( score ) NL_STRING_CONVERSION_TABLE_ENTRY( sphrase ) NL_STRING_CONVERSION_TABLE_ENTRY( characteristic ) NL_STRING_CONVERSION_TABLE_ENTRY( damage_type ) NL_STRING_CONVERSION_TABLE_ENTRY( bot_name) NL_STRING_CONVERSION_TABLE_ENTRY( power_type ) NL_STRING_CONVERSION_TABLE_ENTRY( literal ) NL_END_STRING_CONVERSION_TABLE(TParamType, ParamTypeConversion, NB_PARAM_TYPES) //----------------------------------------------- // stringToParamType //----------------------------------------------- TParamType stringToParamType( const std::string & str ) { return ParamTypeConversion.fromString( str ); } //----------------------------------------------- // stringToParamType //----------------------------------------------- const std::string & paramTypeToString( TParamType type ) { return ParamTypeConversion.toString( type ); } } // utility to 'tabulate' the lines in a string void tabulateLine(std::string &text, uint nbTabs) { if (text.empty()) return; string::size_type pos = 0; string tabs; for (uint i=0; i<nbTabs; ++i) tabs += "\t"; // add a tab at start text = tabs + text; // add a tab at each new line while ((pos = text.find('\n', pos)) != string::npos) { if (pos < text.size()-1 && text[pos+1] == '\r') { // add after the '\r' char ++pos; } if (pos < text.size()-1) text = text.substr(0, pos+1) + tabs + text.substr(pos+1); } } class GenderExtractor { public: GenderExtractor(const std::string & literal, const std::string& identifier, unsigned int level=0); std::string operator()(unsigned int i) const; unsigned int size() const; ~GenderExtractor(); private: bool extractMarkup(const std::string& literal, const std::string & markup, std::string &before, std::string &inside, std::string & after); bool parseMarkup(const std::string& literal, const std::string & markup, std::string& newPhrase, bool include = false ); std::string getPhrase(unsigned int i) const; std::string getExtension(unsigned int i) const; std::string getCondition(unsigned int i) const; std::string getEntity(unsigned int i) const; std::string getIdentifier(unsigned int i) const; private: bool _Entity; std::string _EntityName; std::string _Text; std::string _Identifier; GenderExtractor* _Female; GenderExtractor* _Male; }; GenderExtractor::~GenderExtractor() { delete _Female; delete _Male; } std::string GenderExtractor::getIdentifier(unsigned int i) const { return _Identifier + getExtension(i); } std::string GenderExtractor::operator()(unsigned int i) const { std::string ret("\t"); std::string condition = getCondition(i); ret += condition.empty() ? "":std::string("( ") + condition + " )" + NL + "\t\t"; ret += getIdentifier(i) + "\t[" + getPhrase(i) + "]" + NL; return ret; } GenderExtractor::GenderExtractor(const std::string & literal, const std::string& identifier, unsigned int level) { static const char * es[] ={"e", "e1", "e2", "e3"}; static char * fs[] ={"f", "f1", "f2", "f3"}; static char * hs[] ={"h", "h1", "h2", "h3"}; const char * e = es[level]; const char * f = fs[level]; const char * h = hs[level]; _Identifier = toLower(identifier); std::string newPhrase; std::string before; std::string after; std::string femaleText; std::string maleText; _Entity = extractMarkup(literal, e, before, _EntityName, after); if (_EntityName.size() > 2) { if (_EntityName[0] == '$' && _EntityName[_EntityName.size() - 1] == '$') { _EntityName = _EntityName.substr(1, _EntityName.size() - 2); } } std::string newLiteral = before + after; bool isFemale = parseMarkup(newLiteral,f,newPhrase, true); if ( isFemale) { parseMarkup(newPhrase,h,newPhrase, false); femaleText = newPhrase; } bool isMale = parseMarkup(newLiteral, h, newPhrase, true); if (isMale) { parseMarkup(newPhrase, f, newPhrase, false); maleText = newPhrase; } if (isMale != isFemale) { std::string goodMarkup = isMale ? std::string("") +"<" + h + "></" + h + ">" : std::string("")+"<"+f+"></"+f+">"; std::string badMarkup = isFemale ? std::string("") +"<" + h + "></" + h + ">" : std::string("")+"<"+f+"></"+f+">"; std::string exceptionText = std::string("La phrase ") + identifier + " qui contient une balise " + goodMarkup + " n�cessite aussi les balises " + badMarkup + " m�me vide."; throw EParseException(0, exceptionText.c_str()); } if (!isMale && !isFemale) { _Text = literal; _Female = 0; _Male = 0; } else { if (!_Entity) { _EntityName = "self"; } _Female = new GenderExtractor(femaleText, identifier, level+1); _Male = new GenderExtractor(maleText, identifier, level+1); } } bool GenderExtractor::extractMarkup(const std::string& literal, const std::string & markup, std::string &before, std::string &inside, std::string & after) { std::string::size_type posBegin; std::string::size_type posEnd; std::string::size_type posInside; std::string beginMarkup = std::string("<") + markup + std::string(">"); std::string endMarkup = std::string("</") + markup + std::string(">"); posBegin = literal.find(beginMarkup); if ( posBegin != std::string::npos ) { posEnd = literal.find(endMarkup, posBegin + beginMarkup.size()); if (posEnd != std::string::npos) { before = literal.substr(0, posBegin); posInside = posBegin + beginMarkup.size(); inside = literal.substr(posInside, posEnd - posInside); after = literal.substr(posEnd+endMarkup.size()); return true; } } after = literal; return false; } bool GenderExtractor::parseMarkup(const std::string& literal, const std::string & markup, std::string& newPhrase, bool include ) { bool markupExist; bool changed = false; std::string oldPhrase = literal; newPhrase = ""; do { std::string before; std::string inside; std::string after; markupExist = extractMarkup(oldPhrase, markup, before, inside, after); newPhrase += before; if (include){ newPhrase += inside; } oldPhrase = after; if (markupExist){ changed = true; } } while(markupExist); newPhrase += oldPhrase; return changed; } std::string GenderExtractor::getPhrase(unsigned int i) const { if ( i%2 == 0) { return _Male ? _Male->getPhrase(i/2) : _Text; } if ( i%2 == 1) { nlassert(_Female); return _Female->getPhrase(i/2);} nlassert(0); return ""; } std::string GenderExtractor::getExtension(unsigned int i) const { if ( i%2 == 0) { return _Male ? std::string("_m") + _Male->getExtension(i/2) : ""; } if ( i%2 == 1) { nlassert(_Female); return std::string("_f") + _Female->getExtension(i/2);} nlassert(0); return ""; } std::string GenderExtractor::getCondition(unsigned int i) const { //if ( i%2 == 0) { return _Male ? std::string("\t(") + _Male->getExtension(i/2) : "\t"; } //if ( i%2 == 1) { nlassert(_Female); return std::string("_f") + _Female->getExtension(i/2);} if ( i%2 == 0) { if (_Male) { std::string next = _Male->getCondition(i/2); std::string current = _EntityName + ".gender = male"; return next.size() ? current + " & " + next : current; } else { return ""; } } if ( i%2 == 1) { std::string next = _Female->getCondition(i/2); std::string current = _EntityName + ".gender = female"; return next.size() ? current + " & " + next : current; } nlassert(0); return ""; } unsigned int GenderExtractor::size() const { return _Male ? _Male->size() + _Female->size(): 1; } string CPhrase::genPhrase() { string ret; if (!_PhraseLiterals.empty()) { for (uint p=0; p<_PhraseLiterals.size(); ++p) { string identifier = _PhraseId; if (_NumEntry != 0) identifier += toString("_%u", p+1); GenderExtractor gender(_PhraseLiterals[p], identifier, 0); ret += identifier + " ("; // generate default param list if (_DefaultParams.size() > p) { for (uint i=0; i<_DefaultParams[p].size(); ++i) { ret += STRING_MANAGER::paramTypeToString(_DefaultParams[p][i].ParamType) + " "+_DefaultParams[p][i].ParamName; if (i != _DefaultParams[p].size()-1 || !_AdditionalParams.empty()) ret += ", "; } } // generate additional param list for (uint i=0; i<_AdditionalParams.size(); ++i) { ret += STRING_MANAGER::paramTypeToString(_AdditionalParams[i].ParamType) + " "+_AdditionalParams[i].ParamName; if (i != _AdditionalParams.size()-1) ret += ", "; } ret += ")" + NL; ret += "{" + NL; for (unsigned int i = 0; i < gender.size(); ++i) { ret += gender(i); } ret += "}" + NL + NL; } } nlinfo("genphrase: %s", ret.c_str()); return ret; } bool CMissionCompiler::generateDotScript(NLLIGO::IPrimitive *missionPrim, std::string &dotScript, std::string &log) { //assume that the mission is compiled in the last compiled mission slot try { if (compileMission(missionPrim, string())) { dotScript = _CompiledMission.back()->generateDotScript(); return true; } else { return false; } } catch(const EParseException & e) { log = e.Why; return false; } } /* bool CMissionCompiler::parseGlobalMissionData(IPrimitive *mission, CMissionData &md) { // Mission name string *s; if (!mission->getPropertyByName("name", s) || s->empty()) throw EParseException(mission, "missing mission name !"); md.setMissionName(*s); // giver primitive file if (!mission->getPropertyByName("giver_primitive", s) || s->empty()) throw EParseException(mission, "missing giver primitive !"); md.setGiverPrimitive(*s); // giver name if (!mission->getPropertyByName("giver_primitive", s) || s->empty()) throw EParseException(mission, "missing giver primitive !"); md.setGiverName(*s); // If the mission is under a npc_bot node, then the giver is directly taken // from the npc name if (mission->getParent()) { if (mission->getParent()->getPropertyByName("class", s) && *s == "npc_bot") { if (mission->getParent()->getPropertyByName("name", s)) md.setGiverName(*s); } } // TODO : read all other params... return true; } */ void CMissionData::initHeaderPhrase(IPrimitive *prim) { CPhrase::TPredefParams params; params.resize(1); params[0].push_back(CPhrase::TParamInfo("giver", STRING_MANAGER::bot)); _MissionTitle.initPhrase(*this, prim, _MissionTitleRaw, 0, params); _MissionDescription.initPhrase(*this, prim, _MissionDescriptionRaw, 0, params); _MissionAutoMenu.initPhrase(*this, prim, _MissionAutoMenuRaw); } bool CMissionCompiler::compileMission(NLLIGO::IPrimitive *rootPrim, const std::string &primFileName) { TPrimitiveClassPredicate pred("mission_tree"); if (!pred(rootPrim)) return false; IPrimitive *mission = rootPrim; CMissionData *pmd = new CMissionData; CMissionData &md = *pmd; // Read the mission name string missionName = md.getProperty(mission, "name", false, false); if( missionName.find(' ') != string::npos) { throw EParseException(mission, toString("Mission name '%s' must not contains space", missionName.c_str()).c_str()); } md.setMissionName(missionName); // Create a temporary primitive node to create default variable { // giver default var IPrimitive *temp = new CPrimNode(); temp->addPropertyByName("class", new CPropertyString("var_npc")); temp->addPropertyByName("name", new CPropertyString("giver = giver")); temp->addPropertyByName("npc_name", new CPropertyString("giver")); temp->addPropertyByName("var_name", new CPropertyString("giver")); IVar *var = IVar::createVar(md, temp); md.addVariable(NULL, var); delete temp; } { // player default var IPrimitive *temp = new CPrimNode(); temp->addPropertyByName("class", new CPropertyString("var_npc")); temp->addPropertyByName("name", new CPropertyString("player = player")); temp->addPropertyByName("npc_name", new CPropertyString("player")); temp->addPropertyByName("var_name", new CPropertyString("player")); IVar *var = IVar::createVar(md, temp); md.addVariable(NULL, var); delete temp; } { // guild_name default var IPrimitive *temp = new CPrimNode(); temp->addPropertyByName("class", new CPropertyString("var_text")); temp->addPropertyByName("name", new CPropertyString("guild_name = guild_name")); temp->addPropertyByName("npc_name", new CPropertyString("guild_name")); temp->addPropertyByName("var_name", new CPropertyString("guild_name")); IVar *var = IVar::createVar(md, temp); md.addVariable(NULL, var); delete temp; } // first, start by reading mission variables IPrimitive *variables = getPrimitiveChild(mission, TPrimitiveClassPredicate("variables")); if (!variables) { nlwarning("Can't find variables !"); return false; } parseVariables(md, variables); // read global mission data md.parseMissionHeader(rootPrim); // now, we can init the mission header phrase (they need variable knwoled) md.initHeaderPhrase(rootPrim); IPrimitive *preReq = getPrimitiveChild(mission, TPrimitiveClassPredicate("pre_requisite")); if (!preReq) { nlwarning("Can't find pre requisite !"); return false; } parsePreRequisite(md, preReq); /* IPrimitive *steps = getPrimitiveChild(mission, TPrimitivePropertyPredicate("step_tag", "true")); if (!steps) { nlwarning("Can't find steps !"); return false; } */ parseSteps(md, mission); // Store the compiled mission _CompiledMission.push_back(pmd); string script = md.generateMissionScript(primFileName); nlinfo("The script :"); nlinfo("%s", script.c_str()); string phrases = md.generatePhraseFile(); nlinfo("The phrase file is :"); { vector<string> lines; explode(phrases, string("\n"), lines, false); for (uint i=0; i<lines.size(); ++i) { if(lines[i][0] == '\r') lines[i] = lines[i].substr(1); nlinfo("%s", lines[i].c_str()); } } string dot = md.generateDotScript(); nlinfo("The dot script is :"); { vector<string> lines; explode(dot, string("\n"), lines, false); for (uint i=0; i<lines.size(); ++i) { if(lines[i][0] == '\r') lines[i] = lines[i].substr(1); nlinfo("%s", lines[i].c_str()); } } return true; } bool CMissionCompiler::compileMissions(IPrimitive *rootPrim, const std::string &primFileName) { bool ret = true; // 1st, build a set of mission_scrip nodes NLLIGO::TPrimitiveSet missionTrees; CPrimitiveSet<TPrimitiveClassPredicate> scriptsSet; scriptsSet.buildSet(rootPrim, TPrimitiveClassPredicate("mission_tree"), missionTrees); nlinfo("Found %u mission tree in the primitive file", missionTrees.size()); for (uint i=0; i<missionTrees.size(); ++i) { // try // { compileMission(missionTrees[i], primFileName); // } // catch (EParseException e) // { // nlwarning("Error while parsing a mission: '%s'", e.Why.c_str()); // ret = false; // } } return ret; } bool CMissionCompiler::installCompiledMission(NLLIGO::CLigoConfig &ligoConfig, const std::string &primFileName) { // generate the mission script into the npcs... { map<string, TLoadedPrimitive > loadedPrimitives; // store the previous alias value map<string, uint32> missionAlias; // First loop to remove any mission that belong to the compiled primitive file for (uint i=0; i<_CompiledMission.size(); ++i) { CMissionData &mission = *(_CompiledMission[i]); // first, look for the primitive file to load string fileName = mission.getGiverPrimitive(); if (fileName.empty()) { // use mission primitive instead fileName = primFileName; } if (loadedPrimitives.find(toLower(fileName)) == loadedPrimitives.end()) { string fullFileName = CPath::lookup(fileName, false); if (fullFileName.empty()) { throw EParseException(NULL, toString("Can't find primitive file '%s' in path", fileName.c_str()).c_str()); } // we need to load this primitive file. CPrimitives *primDoc = new CPrimitives; CPrimitiveContext::instance().CurrentPrimitive = primDoc; if (loadXmlPrimitiveFile(*primDoc, fullFileName, ligoConfig)) { // the primitive file is loaded correctly loadedPrimitives.insert(make_pair(toLower(fileName), TLoadedPrimitive(primDoc, fullFileName))); CPrimitiveContext::instance().CurrentPrimitive = NULL; } else { CPrimitiveContext::instance().CurrentPrimitive = NULL; throw EParseException(NULL, toString("Can't read primitive file '%s'", fullFileName.c_str()).c_str()); } } TLoadedPrimitive &loadedPrim = loadedPrimitives[toLower(fileName)]; CPrimitives *primDoc = loadedPrim.PrimDoc; TPrimitiveSet scripts; CPrimitiveSet<TPrimitiveClassPredicate> filter; filter.buildSet(primDoc->RootNode, TPrimitiveClassPredicate("mission"), scripts); // for each script, check if it was generated, and if so, check the name // of the source primitive file. for (uint i=0; i<scripts.size(); ++i) { vector<string> *script; if (scripts[i]->getPropertyByName("script", script) && !script->empty()) { string missionName; scripts[i]->getPropertyByName("name", missionName); // Format should be : #compiled from <source_primitive_name> if (script->front().find("generated from") != string::npos) { // we have a compiled mission if (script->front().find(CFile::getFilename(primFileName)) != string::npos) { // ok, this mission is compiled from the same primitive // store it's alias TPrimitiveClassPredicate pred("alias"); IPrimitive *p = getPrimitiveChild(scripts[i], pred); if (p) { CPrimAlias *pa = dynamic_cast<CPrimAlias*>(p); if (pa) { uint32 alias = pa->getAlias(); missionAlias.insert(make_pair(missionName, alias)); } } else { nlwarning("Can't find alias prim in primitive '%s'", buildPrimPath(scripts[i]).c_str()); } // and remove it scripts[i]->getParent()->removeChild(scripts[i]); } } } } } // second loop to assign compiled mission to giver npc for (uint i=0; i<_CompiledMission.size(); ++i) { CMissionData &mission = *(_CompiledMission[i]); string fileName = mission.getGiverPrimitive(); if (fileName.empty()) { // no giver primitive file specified in the mission, use the mission primitive instead fileName = primFileName; } TLoadedPrimitive &loadedPrim = loadedPrimitives[toLower(fileName)]; CPrimitives *primDoc = loadedPrim.PrimDoc; CPrimitiveContext::instance().CurrentPrimitive = primDoc; TPrimitiveSet bots; CPrimitiveSet<TPrimitiveClassAndNamePredicate> filter; filter.buildSet(primDoc->RootNode, TPrimitiveClassAndNamePredicate("npc_bot", mission.getGiverName()), bots); if (bots.empty()) { string err = toString("Can't find bot '%s' in primitive '%s' !", mission.getGiverName().c_str(), fileName.c_str()); throw EParseException(NULL, err.c_str()); } else if (bots.size() > 1) { string err = toString("Found more than one bot named '%s' in primitive '%s' !", mission.getGiverName().c_str(), fileName.c_str()); throw EParseException(NULL, err.c_str()); } // ok, all is good, we can add the mission node to the giver IPrimitive *giver = bots.front(); // create a new node for the mission IPrimitive *script = new CPrimNode; // set the class script->addPropertyByName("class", new CPropertyString("mission")); // set the name script->addPropertyByName("name", new CPropertyString(mission.getMissionName())); // string alias(toString("%u", makeHash32(mission.getMissionName()))); // script->addPropertyByName("alias", new CPropertyString(mission.getAlias())); string scriptLines = mission.generateMissionScript(primFileName); vector<string> lines; explode(scriptLines, NL, lines, false); script->addPropertyByName("script", new CPropertyStringArray(lines)); // insert the script into the giver giver->insertChild(script); // add the alias { CPrimAlias *pa = new CPrimAlias; pa->addPropertyByName("class", new CPropertyString ("alias")); pa->addPropertyByName("name", new CPropertyString ("alias")); if (missionAlias.find(mission.getMissionName()) != missionAlias.end()) { // restore the previous alias primDoc->forceAlias(pa, missionAlias.find(mission.getMissionName())->second); } // insert in first place script->insertChild(pa, 0); } CPrimitiveContext::instance().CurrentPrimitive = NULL; } // Save the modified primitive files while (!loadedPrimitives.empty()) { TLoadedPrimitive &loadedPrim = loadedPrimitives.begin()->second; if (!saveXmlPrimitiveFile(*(loadedPrim.PrimDoc), loadedPrim.FullFileName)) return false; _FilesToPublish.push_back(loadedPrim.FullFileName); // Free the memory delete loadedPrim.PrimDoc; loadedPrimitives.erase(loadedPrimitives.begin()); } } // generate the phrase file (if any) { string phraseFileName = CFile::getFilenameWithoutExtension(primFileName) + "_wk.txt"; CSString content; for (uint i=0; i<_CompiledMission.size(); ++i) { content += _CompiledMission[i]->generatePhraseFile(); } // transform NL (\n\r) into single \n content = content.replace(NL.c_str(), "\n"); ucstring ucs; ucs.fromUtf8(content); CI18N::writeTextFile(phraseFileName, ucs, true); _FilesToPublish.push_back(phraseFileName); } return true; } bool CMissionCompiler::publishFiles(const std::string &serverPathPrim, const std::string &serverPathText, const std::string &localPathText) { for (uint i=0 ; i<_FilesToPublish.size() ; i++) { string dst, src = _FilesToPublish[i]; uint n = src.find("primitives"); if (n == string::npos) { // text files : copy it and check include in phrase_rites_wk.txt // server string textFile = CPath::standardizePath(serverPathText) + "phrase_rites_wk.txt"; includeText(textFile, string("#include \"") + src + string("\"\n")); dst = CPath::standardizePath(serverPathText) + src; NLMISC::CFile::copyFile(dst.c_str(), src.c_str()); // local textFile = CPath::standardizePath(localPathText) + "phrase_rites_wk.txt"; includeText(textFile, string("#include \"") + src + string("\"\n")); dst = CPath::standardizePath(localPathText) + src; NLMISC::CFile::copyFile(dst.c_str(), src.c_str()); } else { // primitive file : copy to server dst = CPath::standardizePath(serverPathPrim) + string(src, n, src.size()); NLMISC::CFile::copyFile(dst.c_str(), src.c_str()); } } return true; } bool CMissionCompiler::includeText(const std::string filename, const std::string text) { FILE *f = fopen(filename.c_str(), "r+"); if (f == NULL) return false; bool isIn = false; char buffer[1024]; // Check for UTF8 format fread(buffer, 1, 3, f); if (buffer[0] != -17 || buffer[1] != -69 || buffer[2] != -65) fseek(f, 0, SEEK_SET); // Compare each line while(fgets(buffer, 1024, f)) { if (!strcmp(text.c_str(), buffer)) { isIn = true; break; } } if (!isIn) fputs(text.c_str(), f); fclose(f); return true; } bool CMissionCompiler::parsePreRequisite(CMissionData &md, IPrimitive *preReq) { md.parsePrerequisites(preReq); return true; } bool CMissionCompiler::parseOneStep(CMissionData &md, IPrimitive *stepToParse, IStep *parent, bool bEndOfBranch) { IStep *step = IStep::createStep(md, stepToParse); if (step != NULL) { if (!step->isAJump() && !step->getStepName().empty()) { if (md.getStepByName(step->getStepName()) != NULL) { string err = toString("Step '%s' already defined !", step->getStepName().c_str()); throw EParseException(step->getPrimitive(), err.c_str()); } if (step->getStepName().find(' ') != string::npos) { throw EParseException(step->getPrimitive(), toString("Step name '%s' must not contains space", step->getStepName().c_str()).c_str()); } md.addStepName(step->getStepName(), step); } TPrimitiveSet subBranchs = step->getSubBranchs(); // Add the step (if no parent add to the mission data) if (parent == NULL) { if (!md.addStep(step)) { throw EParseException(stepToParse, "Error parsing mission step"); } } else { parent->addSubStep(step); } CStepIf *pSI = dynamic_cast<CStepIf *>(step); // If this is a IF step : parse with 'step' as a parent IStep *pParentStep = NULL; if ((dynamic_cast<CStepIf*>(step) != NULL) || (dynamic_cast<CStepPlayerReconnect*>(step) != NULL)) pParentStep = step; if (!subBranchs.empty()) { // need to parse subbranch before continuing for (uint i=0; i<subBranchs.size(); ++i) { if (!parseOneStep(md, subBranchs[i], pParentStep, i==(subBranchs.size()-1))) return false; } } // if this is the last step, flag it as such step->EndOfBranch = bEndOfBranch; } return true; } bool CMissionCompiler::parseSteps(CMissionData &md, IPrimitive *steps, IStep *parent) { TPrimitiveSet childs; filterPrimitiveChilds(steps, TPrimitivePropertyPredicate("step_tag", "true"), childs); if (childs.empty()) { CPrimNode node; node.addPropertyByName("class", new CPropertyString("end")); node.addPropertyByName("name", new CPropertyString("")); IStep *step = IStep::createStep(md, &node); delete step; // md.addStep(step); } if (!childs.empty()) { for (uint i=0; i<childs.size(); ++i) { IPrimitive *child = childs[i]; parseOneStep(md, childs[i], NULL, i == (childs.size()-1)); } } return true; } string CMissionCompiler::getProp(IPrimitive *prim, const string &propName) { string s; bool ret = prim->getPropertyByName(propName.c_str(), s); if (!ret) throw EParseException(prim, toString("Property %s does't exist", propName.c_str()).c_str()); return s; } string CMissionCompiler::getClass(IPrimitive *prim) { string className; bool ret = prim->getPropertyByName("class", className); nlassert(ret); return className; } bool CMissionCompiler::parseVariables(CMissionData &md, IPrimitive *variables) { for (uint i=0; i<variables->getNumChildren(); ++i) { IPrimitive *child; if (variables->getChild(child, i)) { IVar *var = IVar::createVar(md, child); if (var) { nldebug("Adding variable '%s' as type %u", var->getVarName().c_str(), var->getVarType()); md.addVariable(child, var); } } } return true; } template <class VectorType> bool strtokquote(const string &src, VectorType &tokens) { enum TMode { read_blank, read_token, read_quoted }; string temp; TMode mode = read_blank; for (uint i=0; i<src.size(); ++i) { switch (mode) { case read_blank: if (src[i] != ' ' && src[i] != '\t' && src[i] != '\n' && src[i] != '\r') { // end of blank ! if (src[i] == '\"') { // begin of a quoted string temp = "\""; mode = read_quoted; } else { // begin of a token temp.clear(); temp += src[i]; mode = read_token; } } break; case read_token: if (src[i] == ' ' || src[i] == '\t' || src[i] == '\n' || src[i] == '\r' || src[i] == '\"') { // end of token tokens.push_back(temp); temp.clear(); --i; mode = read_blank; } else { temp += src[i]; } break; case read_quoted: if (src[i] == '\\') { // special treatment for escape command if (i < src.size()-1) { temp += src[i]; temp += src[i+1]; // skip escaped char i++; } else { nlwarning("Error parsing escape char in quoted string"); return false; } } else if (src[i] != '\"') { // just add this char temp += src[i]; } else { // end of quoted string temp += src[i]; tokens.push_back(temp); temp.clear(); mode = read_blank; } break; } } if (!temp.empty()) { if (mode == read_quoted) { nlwarning("Missing closing quote at end of string while reading text in '%s'", src.c_str()); return false; } tokens.push_back(temp); } return true; } template <class VectorType> bool strtokquote(const vector<string> &src, VectorType &tokens) { for (uint i=0; i<src.size(); ++i) { if (!strtokquote(src[i], tokens)) return false; } return true; } struct TFindParamPred : std::unary_function<CPhrase::TParamInfo, bool> { string Name; TFindParamPred(const std::string &name) : Name (name) {} bool operator() (const CPhrase::TParamInfo ¶mInfo) const { return paramInfo.ParamName == Name; } }; bool CPhrase::isEmpty() { return _PhraseId.empty(); } bool CPhrase::asAdditionnalParams() { return !_AdditionalParams.empty(); } void CPhrase::initPhrase (CMissionData &md, IPrimitive *prim, const vector<string> &texts, uint32 numEntry, const TPredefParams &predefParams ) { // nlassert(numEntry == predefParams.size()); // store the predefined/default parameters _DefaultParams = predefParams; // store the number of entry to generate (for literal with variant) _NumEntry = numEntry; numEntry = max(uint32(1), numEntry); _PhraseLiterals.clear(); // _PhraseLiterals.resize(numEntry); // first, concatenate the text vector string text; for (uint i=0; i<texts.size(); ++i) { text = text + texts[i]; if (i != texts.size() -1) text += "\n"; } nldebug("phrase text: %s", text.c_str()); CVectorSString tokens; if (!strtokquote(text, tokens)) throw EParseException(prim, toString("failed to tokenize the string '%s'", text.c_str()).c_str()); if (tokens.empty()) // nothing to parse return; // storage for additional parameters vector<string> params; retry: // ok, the string is parsed, now we can analyze it // look at the first letter of the first token to determine the type of data we have if (tokens[0][0] == '\"') { // we have a literal, so we must found numEntry literal, then a suffix tag for the phrase name if (tokens.size() != numEntry +1) throw EParseException(prim, toString("bad number of tokens in phrase : need %u (%u entries + 1 suffix), found %u\n(in : '%s')", numEntry+1, numEntry, tokens.size(), text.c_str() ).c_str()); _PhraseLiterals.resize(numEntry); for (uint i=0; i<numEntry; ++i) { CSString text = tokens[i]; // remove quotation marks text = text.leftCrop(1); text = text.rightCrop(1); // store the literal phrase value _PhraseLiterals[i] = text; // escape any ']' in the string _PhraseLiterals[i] = CSString(_PhraseLiterals[i]).replace("]", "\\]"); // now, we can analyse the string content, looking for parameters replacement while (text.contains('$')) { // 'advance' to replacement point text = text.splitFrom('$'); if (!text.empty()) { if (text[0] != '$') { if (!text.contains('$')) throw EParseException(prim, "missing parameter closing tag '$'"); string::size_type paramStart = _PhraseLiterals[i].size() - text.size(); // ok, we found a parameter CSString p = text.splitTo('$', true); // remove any subpart access p = p.splitTo('.'); if (i >= predefParams.size() || find_if(predefParams[i].begin(), predefParams[i].end(), TFindParamPred(static_cast<string&>(p))) == predefParams[i].end()) { // this param is not in the predefined params list, add it to the optional params params.push_back(p); } // remove any compiler param from the phrase literal if (p.find("@") != string::npos) { string::size_type pos = _PhraseLiterals[i].find(p, paramStart); if (pos != string::npos) { string::size_type pos2 = _PhraseLiterals[i].find("@", pos); if (pos2 != string::npos) { while (pos2 < _PhraseLiterals[i].size() && _PhraseLiterals[i][pos2] != '.' && _PhraseLiterals[i][pos2] != '$') { _PhraseLiterals[i].erase(pos2, 1); } } } } } else { // this is an escaped $, skip it text.leftCrop(1); } } } } // last, read the suffix _Suffixe = tokens.back(); // generate identifier _PhraseId = toUpper(md.getMissionName()+"_"+_Suffixe); set<string> ps; // select only unique params ps.insert(params.begin(), params.end()); vector<string> temp(ps.begin(), ps.end()); params.swap(temp); } else if (tokens[0][0] == '$') { // we have a variable substitution. Retrieve the var and recall init // do the var replacement CVectorSString tokens2; tokens[0] = md.replaceVar(prim, tokens[0]); if (!strtokquote(tokens[0], tokens2)) throw EParseException(prim, toString("failed to tokenize the string ('%s')", tokens[0].c_str()).c_str()); tokens2.insert(tokens2.end(), tokens.begin()+1, tokens.end()); tokens.swap(tokens2); // and retry the decoding goto retry; } else { // this should be a simple identifier, followed by any number of additional parameters // do the var replacement // tokens = md.replaceVar(prim, tokens); // untagVar(tokens[0]); // ok, now extract the phrase label and the additional parameters _PhraseId = tokens[0]; for (uint i=1; i<tokens.size(); ++i) { untagVar(tokens[i]); if (predefParams.empty() || find_if(predefParams[0].begin(), predefParams[0].end(), TFindParamPred(static_cast<string&>(tokens[i]))) == predefParams[0].end()) { // this param is not in the predefined params list, add it to the optional params params.push_back(tokens[i]); } } } // now, build the parameter list vector<string>::iterator first(params.begin()), last(params.end()); for (; first != last; ++first) { string name, param; vector<string> parts; NLMISC::explode(*first, string("@"), parts, false); if (parts.size() > 0) name = parts[0]; if (parts.size() > 1) param = parts[1]; const string &varName = name; if (varName != "self") { IVar *var = md.getVariable(varName); if (var == NULL) { string err = toString("Can't find variable '%s' referenced from a phrase", name.c_str()); throw EParseException(prim, err.c_str()); } TParamInfo pi; pi.ParamName = name; pi.CompilerParam = param; pi.ParamType = var->getStringManagerType(); _AdditionalParams.push_back(pi); } } } std::string CPhrase::genScript(CMissionData &md) { std::string ret; ret = _PhraseId; for (uint i=0; i<_AdditionalParams.size(); ++i) { IVar *var = md.getVariable(_AdditionalParams[i].ParamName); if (var == NULL) { string err = toString("Can't find variable named '%s' to generate phrase param", _AdditionalParams[i].ParamName.c_str()); throw EParseException(NULL, err.c_str()); } ret += "; " + var->evalVar(_AdditionalParams[i].CompilerParam); } return ret; } CMissionData::CMissionData() { // init all datas _MonoInstance = false; _MissionAuto = false; _RunOnce = false; _Replayable = false; _Solo = false; _Guild = false; _NotInJournal = false; _AutoRemoveFromJournal = false; _PlayerReplayTimer = 0; _GlobalReplayTimer = 0; _NotProposed = false; _NonAbandonnable = false; _NeedValidation = false; _FailIfInventoryIsFull = false; } CMissionData::~CMissionData() { while (!_Variables.empty()) { delete _Variables.begin()->second; _Variables.erase(_Variables.begin()); } while (!_Steps.empty()) { delete _Steps.back(); _Steps.pop_back(); } } void CMissionData::setMissionName(const string &missionName) { _MissionName = missionName; } const string &CMissionData::getMissionName() { return _MissionName;} bool CMissionData::addVariable(NLLIGO::IPrimitive *prim, IVar *var) { if (_Variables.find(var->getVarName()) != _Variables.end()) throw EParseException(prim, toString("Variable '%s' already defined !", var->getVarName().c_str()).c_str()); _Variables.insert(make_pair(var->getVarName(), var)); _VariablesOrder.push_back(var); return true; } IVar *CMissionData::getVariable(const string &varName) { map<string, IVar*>::iterator it(_Variables.find(varName)); if (it != _Variables.end()) return it->second; return NULL; } IStep *CMissionData::getNextStep(IStep *current) { for (uint i=0; i<_Steps.size(); ++i) { if (_Steps[i] == current && i < _Steps.size()-1) return _Steps[i+1]; } return NULL; } IStep *CMissionData::getStepByName(const std::string &stepName) { if (_StepsByNames.find(stepName) != _StepsByNames.end()) { return _StepsByNames[stepName]; } return NULL; } bool CMissionData::addStep(IStep *step) { _Steps.push_back(step); return true; } string CMissionData::genPreRequisites() { string ret; if (!_ReqSkills.empty()) { ret += "req_skill : "; for (uint i=0; i<_ReqSkills.size(); ++i) { ret += _ReqSkills[i].Skill+" "+_ReqSkills[i].MinLevel+" "+_ReqSkills[i].MaxLevel; if (i < _ReqSkills.size()-1) ret +="; "; else ret += NL; } } if (!_ReqMissionDone.empty()) { for (uint i=0; i<_ReqMissionDone.size(); ++i) { ret += "req_mission : "+ _ReqMissionDone[i]+NL; } } if (!_ReqMissionNotDone.empty()) { for (uint i=0; i<_ReqMissionNotDone.size(); ++i) { ret += "req_mission_neg : "+_ReqMissionNotDone[i]+NL; } } if (!_ReqMissionRunning.empty()) { for (uint i=0; i<_ReqMissionRunning.size(); ++i) { ret += "req_mission_running : "+_ReqMissionRunning[i]+NL; } } if (!_ReqMissionNotRunning.empty()) { for (uint i=0; i<_ReqMissionNotRunning.size(); ++i) { ret += "req_mission_running_neg : "+_ReqMissionNotRunning[i]+NL; } } if (!_ReqWearItem.empty()) { ret += "req_wear : "; for (uint i=0; i<_ReqWearItem.size(); ++i) { ret += _ReqWearItem[i]; if(i < _ReqWearItem.size()-1) ret +="; "; ret += NL; } } if (!_ReqOwnItem.empty()) { ret += "req_item : "; for (uint i=0; i<_ReqOwnItem.size(); ++i) { ret += _ReqOwnItem[i]; if(i < _ReqOwnItem.size()-1) ret +="; "; ret += NL; } } if (!_ReqTitle.empty()) { ret += "req_title : "+_ReqTitle+NL; } if (!_ReqFames.empty()) { for (uint i=0; i<_ReqFames.size(); ++i) { ret += "req_fame : "+_ReqFames[i].Faction+" "+_ReqFames[i].Fame; ret += NL; } } if(_ReqGuild) { ret += "req_guild"+NL; } if (!_ReqGrade.empty()) { ret += "req_grade : "+_ReqGrade+NL; } if (!_ReqTeamSize.empty()) { ret += "req_team_size : "+_ReqTeamSize+NL; } if (!_ReqBrick.empty()) { ret += "req_brick : "; for (uint i=0; i<_ReqBrick.size(); ++i) { ret += _ReqBrick[i]; if(i < _ReqBrick.size()-1) ret +="; "; ret += NL; } } if (!_ReqSeason.empty()) { ret += "req_season : "+_ReqSeason+NL; } if (!_ReqEncyclo.empty()) { ret += "req_encyclo_thema : " + _ReqEncyclo + NL; } if (!_ReqEncycloNeg.empty()) { ret += "req_encyclo_thema_neg : " + _ReqEncycloNeg + NL; } if (!_ReqEventFaction.empty()) { ret += "req_event_faction : " + _ReqEventFaction + NL; } return ret; } string CMissionData::generateMissionScript(const std::string &primFileName) { _JumpPoints.clear(); // first, gather jump point list for (uint i=0; i<_Steps.size(); ++i) { set<TJumpInfo> temp; _Steps[i]->fillStepJump(*this, temp); // remove any jump to the next step (normal flow) if (i < _Steps.size()-1) { set<TJumpInfo>::iterator first(temp.begin()), last(temp.end()); for (; first != last; ) { const TJumpInfo &ji = *first; if (ji.StepName == _Steps[i+1]->getStepName() && ji.Discardable) { temp.erase(first); first = temp.begin(); } else ++first; } } _JumpPoints.insert(temp.begin(), temp.end()); } // generate the script string script; // generate mission header script += "# script generated from '"+CFile::getFilename(primFileName)+"'"+NL+NL; script += "#mission tags and pre-requisites"+NL; if (_MonoInstance) script += "mono"+NL; if (_RunOnce) script += "once"+NL; if (_Replayable) script += "replayable"+NL; if (_Solo) script += "solo"+NL; if (_Guild) script += "guild"+NL; if (_NotInJournal) script += "no_list"+NL; if (_AutoRemoveFromJournal) script += "auto_remove"+NL; if (!_MissionCategory.empty()) script += "mission_category : "+_MissionCategory+NL; if (_PlayerReplayTimer != 0) script += "player_replay_timer : "+toString("%u", _PlayerReplayTimer)+NL; if (_GlobalReplayTimer != 0) script += "global_replay_timer : "+toString("%u", _GlobalReplayTimer)+NL; if (_NotProposed) script += "not_proposed"+NL; if (_MissionAuto) script += string("auto : ")+_MissionAutoMenu.genScript(*this)+NL; if (_NonAbandonnable) script += "non_abandonnable"+NL; if (!_MissionIcon.empty()) script += "mission_icon : "+_MissionIcon+NL; if (_NeedValidation) script += "need_validation"+NL; if (_FailIfInventoryIsFull) script += "fail_if_inventory_is_full"+NL; if (!_ParentMissions.empty()) { set<string>::iterator first(_ParentMissions.begin()), last(_ParentMissions.end()); for (; first != last; ++first) { script += "parent : "+ *first+NL; } } script += NL+"#Variables declaration"+NL; // declare all the variables { std::vector<IVar*>::iterator first(_VariablesOrder.begin()), last(_VariablesOrder.end()); for (; first != last; ++first) { script += (*first)->genDecl(*this); } } script += NL+"#pre-requisites"+NL; script += genPreRequisites(); script += NL+"#script"+NL; // generate mission title and desc script += "mission_title : "+_MissionTitle.genScript(*this)+NL; script += "mission_desc : "+_MissionDescription.genScript(*this)+NL; // generate steps scripts for (uint i=0; i<_Steps.size(); ++i) { script += "# "+_Steps[i]->getStepName()+NL; if (_JumpPoints.find(_Steps[i]->getStepName()) != _JumpPoints.end() && !_Steps[i]->isAJump()) { // insert a jump point script += "jump_point : " + _Steps[i]->getStepName() + NL; } script += _Steps[i]->genCode(*this); //if (_Steps[i]->EndOfBranch && !_Steps[i]->isAJump()) // script += "end"+NL; } return script; } string CMissionData::generatePhraseFile() { string ret; // generate header phrase ret = _MissionTitle.genPhrase(); ret += _MissionDescription.genPhrase(); ret += _MissionAutoMenu.genPhrase(); // generate var phrase for (uint i=0; i<_VariablesOrder.size(); ++i) { ret += _VariablesOrder[i]->genPhrase(); } // generate step phrase for (uint i=0; i<_Steps.size(); ++i) { ret += _Steps[i]->genPhrase(); } return ret; } string CMissionData::generateDotScript() { string ret = "digraph " + _MissionName + NL; ret += "{" + NL; // set default shape to 'record' ret += "node [shape=record]"+NL; ret += "\t__start__ [shape=\"point\", peripheries=2, label=\"\"]"+NL; // 1st pass, generate node for each step for (uint i=0; i<_Steps.size(); ++i) { if (!_Steps[i]->isEnd() && !_Steps[i]->isAJump()) { ret += "\t"+_Steps[i]->getStepName(); ret += " [URL=\""+buildPrimPath(_Steps[i]->getPrimitive())+"\"]"+NL; } } ret += "\t__end__ [shape=\"point\"]"+NL; // activate red color for shapes that are created after this points ret += "node [color=red]"+NL; // 2nd pass, generate link between steps for (uint i=0; i<_Steps.size(); ++i) { if (_Steps[i]->isAJump()) continue; if (i == 0) { ret += "\t__start__ -> " + _Steps[i]->getStepName() + NL; } set<TJumpInfo> jumps; _Steps[i]->fillStepJump(*this, jumps); // there is a link there while (!jumps.empty()) { const TJumpInfo &ji = *(jumps.begin()); if (_StepsByNames.find(ji.StepName) != _StepsByNames.end() && _StepsByNames[ji.StepName]->isAJump()) { // this step is a jump, skip to link to the jump destination IStep *jumpStep = _StepsByNames[ji.StepName]; set<TJumpInfo> jumpJump; jumpStep->fillStepJump(*this, jumpJump); if (jumpJump.size() != 1) { string str = toString("Step jump contains %u jumps destination instead of 1", jumpJump.size()); throw EParseException(jumpStep->getPrimitive(), str.c_str()); } ret += "\t"+_Steps[i]->getStepName() + " -> " + jumpJump.begin()->StepName+" [label=\""+ji.JumpName+"\"]" + NL; } else { ret += "\t"+_Steps[i]->getStepName() + " -> " + ji.StepName+" [label=\""+jumps.begin()->JumpName+"\"]" + NL; } jumps.erase(jumps.begin()); } } ret += "}" + NL; return ret; } void CMissionData::parseMissionHeader(NLLIGO::IPrimitive *prim) { // _MissionName = getProperty(prim, "name", false, false); // if( _MissionName.find(' ') != string::npos) // { // throw EParseException(prim, toString("Mission name '%s' must not contains space", _MissionName.c_str()).c_str()); // } _GiverPrimitive = getProperty(prim,"giver_primitive", true, false); _MissionGiver = getProperty(prim, "mission_giver", true, false); // _Alias = getProperty(prim, "alias", false, false); // If the mission is under a npc_bot node, then the giver is directly taken // from the npc name if (prim->getParent()) { if (getProperty(prim->getParent(), "class", false, false) == "npc_bot") { _MissionGiver = getProperty(prim->getParent(), "name", false, false); } } vector<string> vs; _MissionTitleRaw = getPropertyArray(prim, "mission_title", false, false); // _MissionTitle.init(*this, prim, vs); _MissionDescriptionRaw = getPropertyArray(prim, "mission_description", false, false); // _MissionDescription.init(*this, prim, vs); _MonoInstance = strlwr(getProperty(prim, "mono_instance", true, false)) == "true"; _RunOnce = strlwr(getProperty(prim, "run_only_once", true, false)) == "true"; _Replayable = strlwr(getProperty(prim, "replayable", true, false)) == "true"; _NeedValidation = strlwr(getProperty(prim, "need_validation", true, false)) == "true"; _MissionAutoMenuRaw = getPropertyArray(prim, "phrase_auto_menu", false, false); // audience setting string s = getProperty(prim, "audience", false, false); if (s == "solo") _Solo = true; else if (s == "guild") _Guild = true; _NotInJournal = strlwr(getProperty(prim, "not_in_journal", false, false)) == "true"; _AutoRemoveFromJournal = strlwr(getProperty(prim, "auto_remove_from_journal", false, false)) == "true"; _MissionCategory = getProperty(prim, "mission_category", false, false); _PlayerReplayTimer = atoi(getProperty(prim, "player_replay_timer", true, false).c_str()); _GlobalReplayTimer = atoi(getProperty(prim, "global_replay_timer", true, false).c_str()); _NotProposed = strlwr(getProperty(prim, "not_proposed", false, false)) == "true"; _MissionAuto = strlwr(getProperty(prim, "automatic", false, false)) == "true"; _NonAbandonnable = strlwr(getProperty(prim, "non_abandonnable", false, false)) == "true"; _FailIfInventoryIsFull = strlwr(getProperty(prim, "fail_if_inventory_is_full", false, false)) == "true"; _MissionIcon = getProperty(prim, "mission_icon", false, false); if (_MissionAuto) { if (_MissionAutoMenuRaw.empty()) { string error = toString("Mission is flagged automatic, but no phrase_auto_menu defined !"); throw EParseException(prim, error.c_str()); } } vs = getPropertyArray(prim, "parent_missions", true, false); _ParentMissions.insert(vs.begin(), vs.end()); } void CMissionData::parsePrerequisites(NLLIGO::IPrimitive *prim) { // skills vector<string> vs; vs = getPropertyArray(prim, "require_skill/min_level/max_level", true, false); for (uint i=0; i<vs.size(); ++i) { if (!vs[i].empty()) { vector<string> parts; strtokquote(vs[i], parts); if (parts.size() != 3) { throw EParseException(prim, toString("Invalide argument count in line %u of require_skill array. Need 3, found %u", i, parts.size()).c_str()); } TReqSkill rs; rs.Skill = parts[0]; rs.MinLevel = parts[1]; rs.MaxLevel = parts[2]; _ReqSkills.push_back(rs); } } // Mission done vs = getPropertyArray(prim, "require_mission_done", true, false); for (uint i=0; i<vs.size(); ++i) { if (!vs[i].empty()) { vector<string> parts; strtokquote(vs[i], parts); if (parts.size() != 1) { throw EParseException(prim, toString("Invalide argument count in line %u of require_mission_done array. Need 1, found %u", i, parts.size()).c_str()); } _ReqMissionDone.push_back(parts[0]); } } // Mission not done vs = getPropertyArray(prim, "require_mission_not_done", true, false); for (uint i=0; i<vs.size(); ++i) { if (!vs[i].empty()) { vector<string> parts; strtokquote(vs[i], parts); if (parts.size() != 1) { throw EParseException(prim, toString("Invalide argument count in line %u of require_mission_not_done array. Need 1, found %u", i, parts.size()).c_str()); } _ReqMissionNotDone.push_back(parts[0]); } } // Mission running vs = getPropertyArray(prim, "require_mission_running", true, false); for (uint i=0; i<vs.size(); ++i) { if (!vs[i].empty()) { vector<string> parts; strtokquote(vs[i], parts); if (parts.size() != 1) { throw EParseException(prim, toString("Invalide argument count in line %u of require_mission_running array. Need 1, found %u", i, parts.size()).c_str()); } _ReqMissionRunning.push_back(parts[0]); } } // Mission not running vs = getPropertyArray(prim, "require_mission_not_running", true, false); for (uint i=0; i<vs.size(); ++i) { if (!vs[i].empty()) { vector<string> parts; strtokquote(vs[i], parts); if (parts.size() != 1) { throw EParseException(prim, toString("Invalide argument count in line %u of require_mission_not_running array. Need 1, found %u", i, parts.size()).c_str()); } _ReqMissionNotRunning.push_back(parts[0]); } } // wearing item vs = getPropertyArray(prim, "require_wearing_item", true, false); for (uint i=0; i<vs.size(); ++i) { if (!vs[i].empty()) { vector<string> parts; strtokquote(vs[i], parts); if (parts.size() != 1) { throw EParseException(prim, toString("Invalide argument count in line %u of require_wearing_item array. Need 1, found %u", i, parts.size()).c_str()); } _ReqWearItem.push_back(parts[0]); } } // own item vs = getPropertyArray(prim, "require_own_item", true, false); for (uint i=0; i<vs.size(); ++i) { if (!vs[i].empty()) { vector<string> parts; strtokquote(vs[i], parts); if (parts.size() != 1) { throw EParseException(prim, toString("Invalide argument count in line %u of require_own_item array. Need 1, found %u", i, parts.size()).c_str()); } _ReqOwnItem.push_back(parts[0]); } } // title _ReqTitle = getProperty(prim, "require_title", true, false); // fame vs = getPropertyArray(prim, "require_faction/fame", true, false); for (uint i=0; i<vs.size(); ++i) { if (!vs[i].empty()) { vector<string> parts; strtokquote(vs[i], parts); if (parts.size() != 2) { throw EParseException(prim, toString("Invalide argument count in line %u of require_faction/fame array. Need 2, found %u", i, parts.size()).c_str()); } TReqFame rf; rf.Faction = parts[0]; rf.Fame = parts[1]; _ReqFames.push_back(rf); } } // guild if (getProperty(prim, "require_guild_membership", true, false) == "true") _ReqGuild = true; else _ReqGuild = false; // grade _ReqGrade = getProperty(prim, "require_guild_grade", true, false); // team size _ReqTeamSize = getProperty(prim, "require_team_size", true, false); // brick vs = getPropertyArray(prim, "require_brick_knowledge", true, false); for (uint i=0; i<vs.size(); ++i) { if (!vs[i].empty()) { vector<string> parts; strtokquote(vs[i], parts); if (parts.size() != 1) { throw EParseException(prim, toString("Invalide argument count in line %u of require_brick_knowledge array. Need 1, found %u", i, parts.size()).c_str()); } _ReqBrick.push_back(parts[0]); } } // season _ReqSeason = getProperty(prim, "require_season", true, false); // encyclopedia _ReqEncyclo = getProperty(prim, "require_encyclo_thema", true, false); _ReqEncycloNeg = getProperty(prim, "require_encyclo_thema_neg", true, false); if ((!_ReqEncyclo.empty() && !_ReqEncycloNeg.empty()) || (!_ReqEncycloNeg.empty() && !_ReqEncyclo.empty())) { string err = toString("You can't mix positive and negative encyclopedy requirement"); throw EParseException(prim, err.c_str()); } // event faction _ReqEventFaction = getProperty(prim, "require_event_faction", true, false); } std::string CMissionData::replaceVar(NLLIGO::IPrimitive *prim, const std::string &str) { string::size_type pos = 0; string::size_type pos2 = 0; string ret; while (pos < str.size()) { if (str[pos] != '$') { ret += str[pos++]; } else if (pos+1 < str.size() && str[pos+1] == '$') { // check that this $ is not escaped ret += '$'; pos+=2; } else { // ok, this is not an escaped $ CSString varName; // skip the initial '$' pos++; // while (str[pos] != ' ' && str[pos] != '\t' && str[pos] != '\n' && str[pos] != '\r') while (pos < str.size() && str[pos] != '$') varName += str[pos++]; if (pos >= str.size()) { string err = toString("Error while parsing variable in '%s', missing closing '$'", str.c_str()); throw EParseException (NULL, err.c_str()); } // skip the final '$' pos++; // split the var name and subpart vector<string> varParts; explode(string(varName), string("@"), varParts, true); if (varParts.empty() || varParts.size() > 2) { throw EParseException(prim, toString("Error parsing varName '%s' in string '%s'", varName.c_str(), str.c_str()).c_str()); } if (_Variables.find(varParts.front()) == _Variables.end()) { string err = toString("Unknown variable '%s' in string '%s'", varParts.front().c_str(), str.c_str()); throw EParseException (prim, err.c_str()); } IVar *var = _Variables[varParts[0]]; if (varParts.size() == 1) ret += var->evalVar(""); else ret += var->evalVar(varParts[1]); } } return ret; } std::vector<std::string> CMissionData::replaceVar(NLLIGO::IPrimitive *prim, const std::vector<std::string> &strs) { vector<string> ret; for (uint i=0; i<strs.size(); ++i) { ret.push_back(replaceVar(prim, strs[i])); } return ret; } std::string CMissionData::getProperty(NLLIGO::IPrimitive *prim, const std::string &propertyName, bool replaceVar, bool canFail) { string ret; string *s; if (!prim->getPropertyByName(propertyName.c_str(), s)) { if (!canFail) { string err = toString("Can't find property '%s'", propertyName.c_str()); throw EParseException (prim, err.c_str()); } } else { ret = *s; } if (replaceVar) { ret = this->replaceVar(prim, ret); } return ret; } std::vector<std::string> CMissionData::getPropertyArray(NLLIGO::IPrimitive *prim, const std::string &propertyName, bool replaceVar, bool canFail) { vector<string> ret; vector<string> *vs; if (!prim->getPropertyByName(propertyName.c_str(), vs)) { if (!canFail) { string err = toString("Can't find property '%s'", propertyName.c_str()); throw EParseException (prim, err.c_str()); } } else { ret = *vs; } if (replaceVar) { ret = this->replaceVar(prim, ret); } return ret; } bool CMissionData::isThereAJumpTo(const std::string &stepName) { if (_JumpPoints.find(stepName) != _JumpPoints.end()) return true; else return false; } void TCompilerVarName::init(const std::string &defaultName, STRING_MANAGER::TParamType type, CMissionData &md, NLLIGO::IPrimitive *prim, const std::string propName) { _DefaultName = defaultName; _ParamType = type; _VarName = md.getProperty(prim, propName, false, false); // remove the variable tag if any untagVar(_VarName); _VarValue = md.getProperty(prim, propName, true, false); } void TCompilerVarName::initWithText(const std::string &defaultName, STRING_MANAGER::TParamType type, CMissionData &md, NLLIGO::IPrimitive *prim, const std::string &text) { _DefaultName = defaultName; _ParamType = type; _VarName = text; // remove the variable tag if any untagVar(_VarName); _VarValue = md.replaceVar(prim, text); } CPhrase::TParamInfo TCompilerVarName::getParamInfo() const { if (_VarName.empty()) return CPhrase::TParamInfo(_DefaultName, _ParamType); else return CPhrase::TParamInfo(_VarName, _ParamType); } bool TCompilerVarName::empty() const { return _VarValue.empty(); } TCompilerVarName::operator const std::string () const { return _VarValue; } TCompilerVarName::operator CPhrase::TParamInfo() const { return getParamInfo(); } std::string operator+(const TCompilerVarName& left, const std::string & right) { return left._VarValue + right;} std::string operator+(const std::string & left, const TCompilerVarName& right) { return left + right._VarValue;} std::vector<TCompilerVarName> TCompilerVarName::getPropertyArrayWithText(const std::string &defaultName, STRING_MANAGER::TParamType type, CMissionData &md, NLLIGO::IPrimitive *prim, const std::string & arrayProperyName) { std::vector<TCompilerVarName> compilerParams; std::vector<std::string> values = md.getPropertyArray(prim, arrayProperyName,false, false); uint first = 0; uint last = values.size(); compilerParams.resize(last); for ( ; first != last; ++first) { compilerParams[first].initWithText( toString("%s%d", defaultName.c_str(), first+1) , type, md, prim, values[first]); } return compilerParams; } std::vector<TCompilerVarName> TCompilerVarName::getPropertyArrayWithTextStaticDefaultName(const std::string &defaultName, STRING_MANAGER::TParamType type, CMissionData &md, NLLIGO::IPrimitive *prim, const std::string & arrayProperyName) { std::vector<TCompilerVarName> compilerParams; std::vector<std::string> values = md.getPropertyArray(prim, arrayProperyName,false, false); uint first = 0; uint last = values.size(); compilerParams.resize(last); for ( ; first != last; ++first) { compilerParams[first].initWithText( defaultName, type, md, prim, values[first]); } return compilerParams; }