Linux multi-monitor fullscreen support
[ryzomcore.git] / ryzom / tools / leveldesign / mission_compiler_lib / mission_compiler.cpp
blobd58ecbb43bf9c99a54de5a4704ee1bde26aa87e4
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2011 Matt RAYKOWSKI (sfb) <matt.raykowski@gmail.com>
6 // Copyright (C) 2013-2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
7 //
8 // This program is free software: you can redistribute it and/or modify
9 // it under the terms of the GNU Affero General Public License as
10 // published by the Free Software Foundation, either version 3 of the
11 // License, or (at your option) any later version.
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 // GNU Affero General Public License for more details.
18 // You should have received a copy of the GNU Affero General Public License
19 // along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "mission_compiler.h"
22 #include "step.h"
23 #include "nel/misc/i18n.h"
24 #include "nel/misc/common.h"
25 #include "nel/ligo/primitive_utils.h"
27 using namespace std;
28 using namespace NLMISC;
29 using namespace NLLIGO;
32 // hack to get access to string manager item enumeration to string without including
33 // almost all of the Ryzom server side project
34 namespace STRING_MANAGER
36 NL_BEGIN_STRING_CONVERSION_TABLE (TParamType)
37 NL_STRING_CONVERSION_TABLE_ENTRY( item )
38 NL_STRING_CONVERSION_TABLE_ENTRY( place )
39 NL_STRING_CONVERSION_TABLE_ENTRY( creature )
40 NL_STRING_CONVERSION_TABLE_ENTRY( skill )
41 NL_STRING_CONVERSION_TABLE_ENTRY( role )
42 NL_STRING_CONVERSION_TABLE_ENTRY( ecosystem )
43 NL_STRING_CONVERSION_TABLE_ENTRY( race )
44 NL_STRING_CONVERSION_TABLE_ENTRY( sbrick )
45 NL_STRING_CONVERSION_TABLE_ENTRY( faction )
46 NL_STRING_CONVERSION_TABLE_ENTRY( guild )
47 NL_STRING_CONVERSION_TABLE_ENTRY( player )
48 NL_STRING_CONVERSION_TABLE_ENTRY( bot )
49 { "int", integer},
50 // NL_STRING_CONVERSION_TABLE_ENTRY( integer )
51 NL_STRING_CONVERSION_TABLE_ENTRY( time )
52 NL_STRING_CONVERSION_TABLE_ENTRY( money )
53 NL_STRING_CONVERSION_TABLE_ENTRY( compass )
54 NL_STRING_CONVERSION_TABLE_ENTRY( string_id )
55 NL_STRING_CONVERSION_TABLE_ENTRY( dyn_string_id )
56 NL_STRING_CONVERSION_TABLE_ENTRY( self )
57 NL_STRING_CONVERSION_TABLE_ENTRY( creature_model )
58 NL_STRING_CONVERSION_TABLE_ENTRY( entity )
59 NL_STRING_CONVERSION_TABLE_ENTRY( body_part )
60 NL_STRING_CONVERSION_TABLE_ENTRY( score )
61 NL_STRING_CONVERSION_TABLE_ENTRY( sphrase )
62 NL_STRING_CONVERSION_TABLE_ENTRY( characteristic )
63 NL_STRING_CONVERSION_TABLE_ENTRY( damage_type )
64 NL_STRING_CONVERSION_TABLE_ENTRY( bot_name)
65 NL_STRING_CONVERSION_TABLE_ENTRY( power_type )
66 NL_STRING_CONVERSION_TABLE_ENTRY( literal )
67 NL_END_STRING_CONVERSION_TABLE(TParamType, ParamTypeConversion, NB_PARAM_TYPES)
69 //-----------------------------------------------
70 // stringToParamType
71 //-----------------------------------------------
72 TParamType stringToParamType( const std::string & str )
74 return ParamTypeConversion.fromString( str );
77 //-----------------------------------------------
78 // stringToParamType
79 //-----------------------------------------------
80 const std::string & paramTypeToString( TParamType type )
82 return ParamTypeConversion.toString( type );
86 // utility to 'tabulate' the lines in a string
87 void tabulateLine(std::string &text, uint nbTabs)
89 if (text.empty())
90 return;
91 string::size_type pos = 0;
92 string tabs;
94 for (uint i=0; i<nbTabs; ++i)
95 tabs += "\t";
97 // add a tab at start
98 text = tabs + text;
100 // add a tab at each new line
101 while ((pos = text.find('\n', pos)) != string::npos)
103 if (pos < text.size()-1 && text[pos+1] == '\r')
105 // add after the '\r' char
106 ++pos;
108 if (pos < text.size()-1)
109 text = text.substr(0, pos+1) + tabs + text.substr(pos+1);
115 class GenderExtractor
117 public:
120 GenderExtractor(const std::string & literal, const std::string& identifier, unsigned int level=0);
122 std::string operator()(unsigned int i) const;
124 unsigned int size() const;
127 ~GenderExtractor();
128 private:
129 bool extractMarkup(const std::string& literal, const std::string & markup, std::string &before, std::string &inside, std::string & after);
131 bool parseMarkup(const std::string& literal, const std::string & markup, std::string& newPhrase, bool include = false );
133 std::string getPhrase(unsigned int i) const;
135 std::string getExtension(unsigned int i) const;
137 std::string getCondition(unsigned int i) const;
139 std::string getEntity(unsigned int i) const;
141 std::string getIdentifier(unsigned int i) const;
144 private:
147 bool _Entity;
148 std::string _EntityName;
150 std::string _Text;
151 std::string _Identifier;
153 GenderExtractor* _Female;
154 GenderExtractor* _Male;
159 GenderExtractor::~GenderExtractor()
161 delete _Female;
162 delete _Male;
164 std::string GenderExtractor::getIdentifier(unsigned int i) const
166 return _Identifier + getExtension(i);
169 std::string GenderExtractor::operator()(unsigned int i) const
171 std::string ret("\t");
172 std::string condition = getCondition(i);
174 ret += condition.empty() ? "":std::string("( ") + condition + " )" + NL + "\t\t";
175 ret += getIdentifier(i) + "\t[" + getPhrase(i) + "]" + NL;
176 return ret;
180 GenderExtractor::GenderExtractor(const std::string & literal, const std::string& identifier, unsigned int level)
184 static const char * es[] ={"e", "e1", "e2", "e3"};
185 static const char * fs[] ={"f", "f1", "f2", "f3"};
186 static const char * hs[] ={"h", "h1", "h2", "h3"};
188 const char * e = es[level];
189 const char * f = fs[level];
190 const char * h = hs[level];
192 _Identifier = toLowerAscii(identifier);
194 std::string newPhrase;
196 std::string before;
197 std::string after;
198 std::string femaleText;
199 std::string maleText;
201 _Entity = extractMarkup(literal, e, before, _EntityName, after);
202 if (_EntityName.size() > 2)
204 if (_EntityName[0] == '$' && _EntityName[_EntityName.size() - 1] == '$')
206 _EntityName = _EntityName.substr(1, _EntityName.size() - 2);
210 std::string newLiteral = before + after;
212 bool isFemale = parseMarkup(newLiteral,f,newPhrase, true);
213 if ( isFemale)
215 parseMarkup(newPhrase,h,newPhrase, false);
216 femaleText = newPhrase;
219 bool isMale = parseMarkup(newLiteral, h, newPhrase, true);
220 if (isMale)
223 parseMarkup(newPhrase, f, newPhrase, false);
224 maleText = newPhrase;
227 if (isMale != isFemale)
229 std::string goodMarkup = isMale ? std::string("") +"<" + h + "></" + h + ">" : std::string("")+"<"+f+"></"+f+">";
230 std::string badMarkup = isFemale ? std::string("") +"<" + h + "></" + h + ">" : std::string("")+"<"+f+"></"+f+">";
231 std::string exceptionText = std::string("Expression ") + identifier + " that contains a tag " + goodMarkup + " needs also tags " + badMarkup + " even empty.";
232 throw EParseException(0, exceptionText.c_str());
235 if (!isMale && !isFemale)
237 _Text = literal;
238 _Female = 0;
239 _Male = 0;
242 else
244 if (!_Entity) { _EntityName = "self"; }
245 _Female = new GenderExtractor(femaleText, identifier, level+1);
246 _Male = new GenderExtractor(maleText, identifier, level+1);
253 bool GenderExtractor::extractMarkup(const std::string& literal, const std::string & markup, std::string &before, std::string &inside, std::string & after)
255 std::string::size_type posBegin;
256 std::string::size_type posEnd;
257 std::string::size_type posInside;
259 std::string beginMarkup = std::string("<") + markup + std::string(">");
260 std::string endMarkup = std::string("</") + markup + std::string(">");
261 posBegin = literal.find(beginMarkup);
262 if ( posBegin != std::string::npos )
264 posEnd = literal.find(endMarkup, posBegin + beginMarkup.size());
265 if (posEnd != std::string::npos)
267 before = literal.substr(0, posBegin);
268 posInside = posBegin + beginMarkup.size();
269 inside = literal.substr(posInside, posEnd - posInside);
270 after = literal.substr(posEnd+endMarkup.size());
271 return true;
274 after = literal;
275 return false;
278 bool GenderExtractor::parseMarkup(const std::string& literal, const std::string & markup, std::string& newPhrase, bool include )
281 bool markupExist;
282 bool changed = false;
283 std::string oldPhrase = literal;
285 newPhrase.clear();
288 std::string before;
289 std::string inside;
290 std::string after;
291 markupExist = extractMarkup(oldPhrase, markup, before, inside, after);
292 newPhrase += before;
293 if (include){ newPhrase += inside; }
294 oldPhrase = after;
295 if (markupExist){ changed = true; }
297 } while(markupExist);
299 newPhrase += oldPhrase;
300 return changed;
303 std::string GenderExtractor::getPhrase(unsigned int i) const
305 if ( i%2 == 0) { return _Male ? _Male->getPhrase(i/2) : _Text; }
306 if ( i%2 == 1) { nlassert(_Female); return _Female->getPhrase(i/2);}
307 nlassert(0);
308 return "";
311 std::string GenderExtractor::getExtension(unsigned int i) const
313 if ( i%2 == 0) { return _Male ? std::string("_m") + _Male->getExtension(i/2) : ""; }
314 if ( i%2 == 1) { nlassert(_Female); return std::string("_f") + _Female->getExtension(i/2);}
315 nlassert(0);
316 return "";
319 std::string GenderExtractor::getCondition(unsigned int i) const
322 //if ( i%2 == 0) { return _Male ? std::string("\t(") + _Male->getExtension(i/2) : "\t"; }
323 //if ( i%2 == 1) { nlassert(_Female); return std::string("_f") + _Female->getExtension(i/2);}
325 if ( i%2 == 0)
327 if (_Male)
329 std::string next = _Male->getCondition(i/2);
330 std::string current = _EntityName + ".gender = male";
331 return next.size() ? current + " & " + next : current;
334 else
336 return "";
340 if ( i%2 == 1)
342 std::string next = _Female->getCondition(i/2);
343 std::string current = _EntityName + ".gender = female";
344 return next.size() ? current + " & " + next : current;
347 nlassert(0);
349 return "";
352 unsigned int GenderExtractor::size() const
354 return _Male ? _Male->size() + _Female->size(): 1;
358 string CPhrase::genPhrase()
360 string ret;
361 if (!_PhraseLiterals.empty())
363 for (uint p=0; p<_PhraseLiterals.size(); ++p)
365 string identifier = _PhraseId;
366 if (_NumEntry != 0)
367 identifier += toString("_%u", p+1);
369 GenderExtractor gender(_PhraseLiterals[p], identifier, 0);
371 ret += identifier + " (";
372 // generate default param list
373 if (_DefaultParams.size() > p)
375 for (uint i=0; i<_DefaultParams[p].size(); ++i)
377 ret += STRING_MANAGER::paramTypeToString(_DefaultParams[p][i].ParamType) + " "+_DefaultParams[p][i].ParamName;
378 if (i != _DefaultParams[p].size()-1 || !_AdditionalParams.empty())
379 ret += ", ";
382 // generate additional param list
383 for (uint i=0; i<_AdditionalParams.size(); ++i)
385 ret += STRING_MANAGER::paramTypeToString(_AdditionalParams[i].ParamType) + " "+_AdditionalParams[i].ParamName;
386 if (i != _AdditionalParams.size()-1)
387 ret += ", ";
389 ret += ")" + NL;
390 ret += "{" + NL;
392 for (unsigned int i = 0; i < gender.size(); ++i)
394 ret += gender(i);
397 ret += "}" + NL + NL;
400 nlinfo("genphrase: %s", ret.c_str());
401 return ret;
407 bool CMissionCompiler::generateDotScript(NLLIGO::IPrimitive *missionPrim, std::string &dotScript, std::string &log)
409 //assume that the mission is compiled in the last compiled mission slot
412 if (compileMission(missionPrim, string()))
414 dotScript = _CompiledMission.back()->generateDotScript();
415 return true;
417 else
419 return false;
422 catch(const EParseException & e)
424 log = e.Why;
425 return false;
430 bool CMissionCompiler::parseGlobalMissionData(IPrimitive *mission, CMissionData &md)
432 // Mission name
433 string *s;
434 if (!mission->getPropertyByName("name", s) || s->empty())
435 throw EParseException(mission, "missing mission name !");
436 md.setMissionName(*s);
438 // giver primitive file
439 if (!mission->getPropertyByName("giver_primitive", s) || s->empty())
440 throw EParseException(mission, "missing giver primitive !");
441 md.setGiverPrimitive(*s);
443 // giver name
444 if (!mission->getPropertyByName("giver_primitive", s) || s->empty())
445 throw EParseException(mission, "missing giver primitive !");
446 md.setGiverName(*s);
447 // If the mission is under a npc_bot node, then the giver is directly taken
448 // from the npc name
449 if (mission->getParent())
451 if (mission->getParent()->getPropertyByName("class", s) && *s == "npc_bot")
453 if (mission->getParent()->getPropertyByName("name", s))
454 md.setGiverName(*s);
458 // TODO : read all other params...
460 return true;
464 void CMissionData::initHeaderPhrase(IPrimitive *prim)
466 CPhrase::TPredefParams params;
467 params.resize(1);
468 params[0].push_back(CPhrase::TParamInfo("giver", STRING_MANAGER::bot));
469 _MissionTitle.initPhrase(*this, prim, _MissionTitleRaw, 0, params);
470 _MissionDescription.initPhrase(*this, prim, _MissionDescriptionRaw, 0, params);
471 _MissionAutoMenu.initPhrase(*this, prim, _MissionAutoMenuRaw);
474 bool CMissionCompiler::compileMission(NLLIGO::IPrimitive *rootPrim, const std::string &primFileName)
476 TPrimitiveClassPredicate pred("mission_tree");
477 if (!pred(rootPrim))
478 return false;
480 IPrimitive *mission = rootPrim;
481 CMissionData *pmd = new CMissionData;
482 CMissionData &md = *pmd;
484 // Read the mission name
485 string missionName = md.getProperty(mission, "name", false, false);
486 if( missionName.find(' ') != string::npos)
488 throw EParseException(mission, toString("Mission name '%s' must not contains space", missionName.c_str()).c_str());
490 md.setMissionName(missionName);
491 // Create a temporary primitive node to create default variable
493 // giver default var
494 IPrimitive *temp = new CPrimNode();
495 temp->addPropertyByName("class", new CPropertyString("var_npc"));
496 temp->addPropertyByName("name", new CPropertyString("giver = giver"));
497 temp->addPropertyByName("npc_name", new CPropertyString("giver"));
498 temp->addPropertyByName("var_name", new CPropertyString("giver"));
500 IVar *var = IVar::createVar(md, temp);
501 md.addVariable(NULL, var);
503 delete temp;
507 // player default var
508 IPrimitive *temp = new CPrimNode();
509 temp->addPropertyByName("class", new CPropertyString("var_npc"));
510 temp->addPropertyByName("name", new CPropertyString("player = player"));
511 temp->addPropertyByName("npc_name", new CPropertyString("player"));
512 temp->addPropertyByName("var_name", new CPropertyString("player"));
514 IVar *var = IVar::createVar(md, temp);
515 md.addVariable(NULL, var);
517 delete temp;
521 // guild_name default var
522 IPrimitive *temp = new CPrimNode();
523 temp->addPropertyByName("class", new CPropertyString("var_text"));
524 temp->addPropertyByName("name", new CPropertyString("guild_name = guild_name"));
525 temp->addPropertyByName("npc_name", new CPropertyString("guild_name"));
526 temp->addPropertyByName("var_name", new CPropertyString("guild_name"));
528 IVar *var = IVar::createVar(md, temp);
529 md.addVariable(NULL, var);
531 delete temp;
534 // first, start by reading mission variables
535 IPrimitive *variables;
537 TPrimitiveClassPredicate predTmp("variables");
538 variables= NLLIGO::getPrimitiveChild(mission, predTmp);
541 if (!variables)
543 nlwarning("Can't find variables !");
544 return false;
546 parseVariables(md, variables);
548 // read global mission data
549 md.parseMissionHeader(rootPrim);
551 // now, we can init the mission header phrase (they need variable knwoled)
552 md.initHeaderPhrase(rootPrim);
554 IPrimitive *preReq;
556 TPrimitiveClassPredicate predTmp("pre_requisite");
557 preReq = getPrimitiveChild(mission, predTmp);
560 if (!preReq)
562 nlwarning("Can't find pre requisite !");
563 return false;
565 parsePreRequisite(md, preReq);
567 /* IPrimitive *steps = getPrimitiveChild(mission, TPrimitivePropertyPredicate("step_tag", "true"));
568 if (!steps)
570 nlwarning("Can't find steps !");
571 return false;
573 */ parseSteps(md, mission);
575 // Store the compiled mission
576 _CompiledMission.push_back(pmd);
578 string script = md.generateMissionScript(primFileName);
580 nlinfo("The script :");
581 nlinfo("%s", script.c_str());
583 string phrases = md.generatePhraseFile();
584 nlinfo("The phrase file is :");
586 vector<string> lines;
587 explode(phrases, string("\n"), lines, false);
588 for (uint i=0; i<lines.size(); ++i)
590 if(lines[i][0] == '\r') lines[i] = lines[i].substr(1);
591 nlinfo("%s", lines[i].c_str());
595 string dot = md.generateDotScript();
596 nlinfo("The dot script is :");
598 vector<string> lines;
599 explode(dot, string("\n"), lines, false);
600 for (uint i=0; i<lines.size(); ++i)
602 if(lines[i][0] == '\r') lines[i] = lines[i].substr(1);
603 nlinfo("%s", lines[i].c_str());
606 return true;
609 bool CMissionCompiler::compileMissions(IPrimitive *rootPrim, const std::string &primFileName)
611 bool ret = true;
612 // 1st, build a set of mission_scrip nodes
613 NLLIGO::TPrimitiveSet missionTrees;
615 CPrimitiveSet<TPrimitiveClassPredicate> scriptsSet;
617 TPrimitiveClassPredicate pred("mission_tree");
618 scriptsSet.buildSet(rootPrim, pred, missionTrees);
620 nlinfo("Found %u mission tree in the primitive file", missionTrees.size());
622 for (uint i=0; i<missionTrees.size(); ++i)
624 // try
625 // {
626 compileMission(missionTrees[i], primFileName);
627 // }
628 // catch (const EParseException &e)
629 // {
630 // nlwarning("Error while parsing a mission: '%s'", e.Why.c_str());
631 // ret = false;
632 // }
636 return ret;
640 bool CMissionCompiler::installCompiledMission(NLLIGO::CLigoConfig &ligoConfig, const std::string &primFileName)
642 // generate the mission script into the npcs...
644 map<string, TLoadedPrimitive > loadedPrimitives;
646 // store the previous alias value
647 map<string, uint32> missionAlias;
649 // First loop to remove any mission that belong to the compiled primitive file
650 for (uint i=0; i<_CompiledMission.size(); ++i)
652 CMissionData &mission = *(_CompiledMission[i]);
653 // first, look for the primitive file to load
654 string fileName = mission.getGiverPrimitive();
655 if (fileName.empty())
657 // use mission primitive instead
658 fileName = primFileName;
660 if (loadedPrimitives.find(toLowerAscii(fileName)) == loadedPrimitives.end())
662 string fullFileName = CPath::lookup(fileName, false);
663 if (fullFileName.empty())
665 throw EParseException(NULL, toString("Can't find primitive file '%s' in path", fileName.c_str()).c_str());
667 // we need to load this primitive file.
668 CPrimitives *primDoc = new CPrimitives;
669 CPrimitiveContext::instance().CurrentPrimitive = primDoc;
670 if (loadXmlPrimitiveFile(*primDoc, fullFileName, ligoConfig))
672 // the primitive file is loaded correctly
673 loadedPrimitives.insert(make_pair(toLowerAscii(fileName), TLoadedPrimitive(primDoc, fullFileName)));
674 CPrimitiveContext::instance().CurrentPrimitive = NULL;
676 else
678 CPrimitiveContext::instance().CurrentPrimitive = NULL;
679 throw EParseException(NULL, toString("Can't read primitive file '%s'", fullFileName.c_str()).c_str());
682 TLoadedPrimitive &loadedPrim = loadedPrimitives[toLowerAscii(fileName)];
683 CPrimitives *primDoc = loadedPrim.PrimDoc;
685 TPrimitiveSet scripts;
686 CPrimitiveSet<TPrimitiveClassPredicate> filter;
687 TPrimitiveClassPredicate pred("mission");
688 filter.buildSet(primDoc->RootNode, pred, scripts);
690 // for each script, check if it was generated, and if so, check the name
691 // of the source primitive file.
692 for (uint i=0; i<scripts.size(); ++i)
694 vector<string> *script;
695 if (scripts[i]->getPropertyByName("script", script) && !script->empty())
697 string missionName;
699 scripts[i]->getPropertyByName("name", missionName);
701 // Format should be : #compiled from <source_primitive_name>
702 if (script->front().find("generated from") != string::npos)
704 // we have a compiled mission
705 if (script->front().find(CFile::getFilename(primFileName)) != string::npos)
707 // ok, this mission is compiled from the same primitive
709 // store it's alias
710 TPrimitiveClassPredicate pred("alias");
712 IPrimitive *p = getPrimitiveChild(scripts[i], pred);
714 if (p)
716 CPrimAlias *pa = dynamic_cast<CPrimAlias*>(p);
717 if (pa)
719 uint32 alias = pa->getAlias();
720 missionAlias.insert(make_pair(missionName, alias));
723 else
725 nlwarning("Can't find alias prim in primitive '%s'", buildPrimPath(scripts[i]).c_str());
728 // and remove it
729 scripts[i]->getParent()->removeChild(scripts[i]);
736 // second loop to assign compiled mission to giver npc
737 for (uint i=0; i<_CompiledMission.size(); ++i)
739 CMissionData &mission = *(_CompiledMission[i]);
740 string fileName = mission.getGiverPrimitive();
741 if (fileName.empty())
743 // no giver primitive file specified in the mission, use the mission primitive instead
744 fileName = primFileName;
747 TLoadedPrimitive &loadedPrim = loadedPrimitives[toLowerAscii(fileName)];
748 CPrimitives *primDoc = loadedPrim.PrimDoc;
749 CPrimitiveContext::instance().CurrentPrimitive = primDoc;
751 TPrimitiveSet bots;
752 CPrimitiveSet<TPrimitiveClassAndNamePredicate> filter;
753 TPrimitiveClassAndNamePredicate pred("npc_bot", mission.getGiverName());
754 filter.buildSet(primDoc->RootNode, pred, bots);
756 if (bots.empty())
758 string err = toString("Can't find bot '%s' in primitive '%s' !",
759 mission.getGiverName().c_str(),
760 fileName.c_str());
761 throw EParseException(NULL, err.c_str());
763 else if (bots.size() > 1)
765 string err = toString("Found more than one bot named '%s' in primitive '%s' !",
766 mission.getGiverName().c_str(),
767 fileName.c_str());
768 throw EParseException(NULL, err.c_str());
771 // ok, all is good, we can add the mission node to the giver
772 IPrimitive *giver = bots.front();
773 // create a new node for the mission
774 IPrimitive *script = new CPrimNode;
775 // set the class
776 script->addPropertyByName("class", new CPropertyString("mission"));
777 // set the name
778 script->addPropertyByName("name", new CPropertyString(mission.getMissionName()));
779 // string alias(toString("%u", makeHash32(mission.getMissionName())));
780 // script->addPropertyByName("alias", new CPropertyString(mission.getAlias()));
781 string scriptLines = mission.generateMissionScript(primFileName);
782 vector<string> lines;
783 explode(scriptLines, NL, lines, false);
785 script->addPropertyByName("script", new CPropertyStringArray(lines));
787 // insert the script into the giver
788 giver->insertChild(script);
790 // add the alias
792 CPrimAlias *pa = new CPrimAlias;
793 pa->addPropertyByName("class", new CPropertyString ("alias"));
794 pa->addPropertyByName("name", new CPropertyString ("alias"));
796 if (missionAlias.find(mission.getMissionName()) != missionAlias.end())
798 // restore the previous alias
799 primDoc->forceAlias(pa, missionAlias.find(mission.getMissionName())->second);
802 // insert in first place
803 script->insertChild(pa, 0);
806 CPrimitiveContext::instance().CurrentPrimitive = NULL;
809 // Save the modified primitive files
810 while (!loadedPrimitives.empty())
812 TLoadedPrimitive &loadedPrim = loadedPrimitives.begin()->second;
813 if (!saveXmlPrimitiveFile(*(loadedPrim.PrimDoc), loadedPrim.FullFileName))
814 return false;
816 _FilesToPublish.push_back(loadedPrim.FullFileName);
818 // Free the memory
819 delete loadedPrim.PrimDoc;
821 loadedPrimitives.erase(loadedPrimitives.begin());
825 // generate the phrase file (if any)
827 string phraseFileName = CFile::getFilenameWithoutExtension(primFileName) + "_wk.txt";
829 CSString content;
831 for (uint i=0; i<_CompiledMission.size(); ++i)
833 content += _CompiledMission[i]->generatePhraseFile();
835 // transform NL (\n\r) into single \n
836 content = content.replace(NL.c_str(), "\n");
837 ucstring ucs;
838 ucs.fromUtf8(content);
840 CI18N::writeTextFile(phraseFileName, ucs, true);
842 _FilesToPublish.push_back(phraseFileName);
845 return true;
849 bool CMissionCompiler::publishFiles(const std::string &serverPathPrim, const std::string &serverPathText, const std::string &localPathText)
851 for (uint i=0 ; i<_FilesToPublish.size() ; i++)
853 string dst, src = _FilesToPublish[i];
855 string::size_type n = src.find("primitives");
856 if (n == string::npos)
858 // text files : copy it and check include in phrase_rites_wk.txt
860 // server
861 string textFile = CPath::standardizePath(serverPathText) + "phrase_rites_wk.txt";
862 includeText(textFile, string("#include \"") + src + string("\"\n"));
863 dst = CPath::standardizePath(serverPathText) + src;
864 NLMISC::CFile::copyFile(dst, src);
866 // local
867 textFile = CPath::standardizePath(localPathText) + "phrase_rites_wk.txt";
868 includeText(textFile, string("#include \"") + src + string("\"\n"));
869 dst = CPath::standardizePath(localPathText) + src;
870 NLMISC::CFile::copyFile(dst, src);
872 else
874 // primitive file : copy to server
875 dst = CPath::standardizePath(serverPathPrim) + string(src, n, src.size());
876 NLMISC::CFile::copyFile(dst, src);
879 return true;
882 bool CMissionCompiler::includeText(const std::string &filename, const std::string &text)
884 FILE *f = nlfopen(filename, "r+");
885 if (f == NULL)
887 nlwarning("Unable to open %s", filename.c_str());
888 return false;
891 bool isIn = false;
892 char buffer[1024];
894 // Check for UTF8 format
895 if (fread(buffer, 1, 3, f) != 3)
897 fclose(f);
898 nlwarning("Unable to read 3 bytes from %s", filename.c_str());
899 return false;
902 if (buffer[0] != -17 || buffer[1] != -69 || buffer[2] != -65)
903 fseek(f, 0, SEEK_SET);
905 // Compare each line
906 while(fgets(buffer, 1024, f))
908 if (!strcmp(text.c_str(), buffer))
910 isIn = true;
911 break;
915 if (!isIn)
916 fputs(text.c_str(), f);
918 fclose(f);
919 return true;
922 bool CMissionCompiler::parsePreRequisite(CMissionData &md, IPrimitive *preReq)
924 md.parsePrerequisites(preReq);
925 return true;
928 bool CMissionCompiler::parseOneStep(CMissionData &md, IPrimitive *stepToParse, IStep *parent, bool bEndOfBranch)
930 IStep *step = IStep::createStep(md, stepToParse);
931 if (step != NULL)
934 if (!step->isAJump() && !step->getStepName().empty())
936 if (md.getStepByName(step->getStepName()) != NULL)
938 string err = toString("Step '%s' already defined !", step->getStepName().c_str());
939 throw EParseException(step->getPrimitive(), err.c_str());
942 if (step->getStepName().find(' ') != string::npos)
944 throw EParseException(step->getPrimitive(), toString("Step name '%s' must not contains space", step->getStepName().c_str()).c_str());
946 md.addStepName(step->getStepName(), step);
949 TPrimitiveSet subBranchs = step->getSubBranchs();
951 // Add the step (if no parent add to the mission data)
952 if (parent == NULL)
954 if (!md.addStep(step))
956 throw EParseException(stepToParse, "Error parsing mission step");
959 else
961 parent->addSubStep(step);
964 CStepIf *pSI = dynamic_cast<CStepIf *>(step);
965 // If this is a IF step : parse with 'step' as a parent
967 IStep *pParentStep = NULL;
969 if ((dynamic_cast<CStepIf*>(step) != NULL) ||
970 (dynamic_cast<CStepPlayerReconnect*>(step) != NULL))
971 pParentStep = step;
973 if (!subBranchs.empty())
975 // need to parse subbranch before continuing
976 for (uint i=0; i<subBranchs.size(); ++i)
978 if (!parseOneStep(md, subBranchs[i], pParentStep, i==(subBranchs.size()-1)))
979 return false;
983 // if this is the last step, flag it as such
984 step->EndOfBranch = bEndOfBranch;
986 return true;
989 bool CMissionCompiler::parseSteps(CMissionData &md, IPrimitive *steps, IStep *parent)
991 TPrimitiveSet childs;
992 TPrimitivePropertyPredicate pred("step_tag", "true");
993 filterPrimitiveChilds(steps, pred, childs);
995 if (childs.empty())
997 CPrimNode node;
998 node.addPropertyByName("class", new CPropertyString("end"));
999 node.addPropertyByName("name", new CPropertyString(""));
1000 IStep *step = IStep::createStep(md, &node);
1001 delete step;
1002 // md.addStep(step);
1004 if (!childs.empty())
1006 for (uint i=0; i<childs.size(); ++i)
1008 IPrimitive *child = childs[i];
1010 parseOneStep(md, childs[i], NULL, i == (childs.size()-1));
1014 return true;
1017 string CMissionCompiler::getProp(IPrimitive *prim, const string &propName)
1019 string s;
1020 bool ret = prim->getPropertyByName(propName.c_str(), s);
1021 if (!ret)
1022 throw EParseException(prim, toString("Property %s does't exist", propName.c_str()).c_str());
1024 return s;
1027 string CMissionCompiler::getClass(IPrimitive *prim)
1029 string className;
1030 bool ret = prim->getPropertyByName("class", className);
1031 nlassert(ret);
1032 return className;
1035 bool CMissionCompiler::parseVariables(CMissionData &md, IPrimitive *variables)
1037 for (uint i=0; i<variables->getNumChildren(); ++i)
1039 IPrimitive *child;
1040 if (variables->getChild(child, i))
1042 IVar *var = IVar::createVar(md, child);
1043 if (var)
1045 nldebug("Adding variable '%s' as type %u", var->getVarName().c_str(), var->getVarType());
1046 md.addVariable(child, var);
1050 return true;
1053 template <class VectorType>
1054 bool strtokquote(const string &src, VectorType &tokens)
1056 enum TMode
1058 read_blank,
1059 read_token,
1060 read_quoted
1063 string temp;
1064 TMode mode = read_blank;
1066 for (uint i=0; i<src.size(); ++i)
1068 switch (mode)
1070 case read_blank:
1071 if (src[i] != ' ' && src[i] != '\t' && src[i] != '\n' && src[i] != '\r')
1073 // end of blank !
1074 if (src[i] == '\"')
1076 // begin of a quoted string
1077 temp = "\"";
1078 mode = read_quoted;
1080 else
1082 // begin of a token
1083 temp.clear();
1084 temp += src[i];
1085 mode = read_token;
1088 break;
1089 case read_token:
1090 if (src[i] == ' ' || src[i] == '\t' || src[i] == '\n' || src[i] == '\r' || src[i] == '\"')
1092 // end of token
1093 tokens.push_back(temp);
1094 temp.clear();
1095 --i;
1096 mode = read_blank;
1098 else
1100 temp += src[i];
1102 break;
1103 case read_quoted:
1104 if (src[i] == '\\')
1106 // special treatment for escape command
1107 if (i < src.size()-1)
1109 temp += src[i];
1110 temp += src[i+1];
1111 // skip escaped char
1112 i++;
1114 else
1116 nlwarning("Error parsing escape char in quoted string");
1117 return false;
1120 else if (src[i] != '\"')
1122 // just add this char
1123 temp += src[i];
1125 else
1127 // end of quoted string
1128 temp += src[i];
1129 tokens.push_back(temp);
1130 temp.clear();
1131 mode = read_blank;
1133 break;
1136 if (!temp.empty())
1138 if (mode == read_quoted)
1140 nlwarning("Missing closing quote at end of string while reading text in '%s'", src.c_str());
1141 return false;
1143 tokens.push_back(temp);
1145 return true;
1148 template <class VectorType>
1149 bool strtokquote(const vector<string> &src, VectorType &tokens)
1151 for (uint i=0; i<src.size(); ++i)
1153 if (!strtokquote(src[i], tokens))
1154 return false;
1156 return true;
1159 struct TFindParamPred : std::unary_function<CPhrase::TParamInfo, bool>
1161 string Name;
1162 TFindParamPred(const std::string &name)
1163 : Name (name)
1166 bool operator() (const CPhrase::TParamInfo &paramInfo) const
1168 return paramInfo.ParamName == Name;
1173 bool CPhrase::isEmpty()
1175 return _PhraseId.empty();
1178 bool CPhrase::asAdditionnalParams()
1180 return !_AdditionalParams.empty();
1184 void CPhrase::initPhrase (CMissionData &md,
1185 IPrimitive *prim,
1186 const vector<string> &texts,
1187 uint32 numEntry,
1188 const TPredefParams &predefParams )
1190 // nlassert(numEntry == predefParams.size());
1192 // store the predefined/default parameters
1193 _DefaultParams = predefParams;
1194 // store the number of entry to generate (for literal with variant)
1195 _NumEntry = numEntry;
1197 numEntry = max(uint32(1), numEntry);
1199 _PhraseLiterals.clear();
1200 // _PhraseLiterals.resize(numEntry);
1202 // first, concatenate the text vector
1203 string text;
1204 for (uint i=0; i<texts.size(); ++i)
1206 text = text + texts[i];
1207 if (i != texts.size() -1)
1208 text += "\n";
1211 nldebug("phrase text: %s", text.c_str());
1213 CVectorSString tokens;
1215 if (!strtokquote(text, tokens))
1216 throw EParseException(prim, toString("failed to tokenize the string '%s'", text.c_str()).c_str());
1218 if (tokens.empty())
1219 // nothing to parse
1220 return;
1222 // storage for additional parameters
1223 vector<string> params;
1225 retry:
1226 // ok, the string is parsed, now we can analyze it
1227 // look at the first letter of the first token to determine the type of data we have
1228 if (tokens[0][0] == '\"')
1230 // we have a literal, so we must found numEntry literal, then a suffix tag for the phrase name
1231 if (tokens.size() != numEntry +1)
1232 throw EParseException(prim, toString("bad number of tokens in phrase : need %u (%u entries + 1 suffix), found %u\n(in : '%s')",
1233 numEntry+1,
1234 numEntry,
1235 tokens.size(),
1236 text.c_str()
1237 ).c_str());
1239 _PhraseLiterals.resize(numEntry);
1240 for (uint i=0; i<numEntry; ++i)
1242 CSString text = tokens[i];
1243 // remove quotation marks
1244 text = text.leftCrop(1);
1245 text = text.rightCrop(1);
1247 // store the literal phrase value
1248 _PhraseLiterals[i] = text;
1249 // escape any ']' in the string
1250 _PhraseLiterals[i] = CSString(_PhraseLiterals[i]).replace("]", "\\]");
1252 // now, we can analyse the string content, looking for parameters replacement
1253 while (text.contains('$'))
1255 // 'advance' to replacement point
1256 text = text.splitFrom('$');
1257 if (!text.empty())
1259 if (text[0] != '$')
1261 if (!text.contains('$'))
1262 throw EParseException(prim, "missing parameter closing tag '$'");
1264 string::size_type paramStart = _PhraseLiterals[i].size() - text.size();
1265 // ok, we found a parameter
1266 CSString p = text.splitTo('$', true);
1267 // remove any subpart access
1268 p = p.splitTo('.');
1269 if (i >= predefParams.size() || find_if(predefParams[i].begin(), predefParams[i].end(), TFindParamPred(static_cast<string&>(p))) == predefParams[i].end())
1271 // this param is not in the predefined params list, add it to the optional params
1272 params.push_back(p);
1275 // remove any compiler param from the phrase literal
1276 if (p.find("@") != string::npos)
1278 string::size_type pos = _PhraseLiterals[i].find(p, paramStart);
1279 if (pos != string::npos)
1281 string::size_type pos2 = _PhraseLiterals[i].find("@", pos);
1282 if (pos2 != string::npos)
1284 while (pos2 < _PhraseLiterals[i].size()
1285 && _PhraseLiterals[i][pos2] != '.'
1286 && _PhraseLiterals[i][pos2] != '$')
1288 _PhraseLiterals[i].erase(pos2, 1);
1295 else
1297 // this is an escaped $, skip it
1298 text.leftCrop(1);
1303 // last, read the suffix
1304 _Suffixe = tokens.back();
1306 // generate identifier
1307 _PhraseId = toUpperAscii(md.getMissionName()+"_"+_Suffixe);
1309 set<string> ps;
1310 // select only unique params
1311 ps.insert(params.begin(), params.end());
1313 vector<string> temp(ps.begin(), ps.end());
1314 params.swap(temp);
1317 else if (tokens[0][0] == '$')
1319 // we have a variable substitution. Retrieve the var and recall init
1321 // do the var replacement
1322 CVectorSString tokens2;
1324 tokens[0] = md.replaceVar(prim, tokens[0]);
1326 if (!strtokquote(tokens[0], tokens2))
1327 throw EParseException(prim, toString("failed to tokenize the string ('%s')", tokens[0].c_str()).c_str());
1329 tokens2.insert(tokens2.end(), tokens.begin()+1, tokens.end());
1330 tokens.swap(tokens2);
1332 // and retry the decoding
1333 goto retry;
1335 else
1337 // this should be a simple identifier, followed by any number of additional parameters
1339 // do the var replacement
1340 // tokens = md.replaceVar(prim, tokens);
1341 // untagVar(tokens[0]);
1343 // ok, now extract the phrase label and the additional parameters
1344 _PhraseId = tokens[0];
1345 for (uint i=1; i<tokens.size(); ++i)
1347 untagVar(tokens[i]);
1348 if (predefParams.empty() || find_if(predefParams[0].begin(), predefParams[0].end(), TFindParamPred(static_cast<string&>(tokens[i]))) == predefParams[0].end())
1350 // this param is not in the predefined params list, add it to the optional params
1351 params.push_back(tokens[i]);
1356 // now, build the parameter list
1358 vector<string>::iterator first(params.begin()), last(params.end());
1359 for (; first != last; ++first)
1361 string name, param;
1362 vector<string> parts;
1363 NLMISC::explode(*first, string("@"), parts, false);
1365 if (!parts.empty())
1366 name = parts[0];
1367 if (parts.size() > 1)
1368 param = parts[1];
1370 const string &varName = name;
1372 if (varName != "self")
1374 IVar *var = md.getVariable(varName);
1375 if (var == NULL)
1377 string err = toString("Can't find variable '%s' referenced from a phrase",
1378 name.c_str());
1379 throw EParseException(prim, err.c_str());
1382 TParamInfo pi;
1383 pi.ParamName = name;
1384 pi.CompilerParam = param;
1385 pi.ParamType = var->getStringManagerType();
1386 _AdditionalParams.push_back(pi);
1391 std::string CPhrase::genScript(CMissionData &md)
1393 std::string ret;
1395 ret = _PhraseId;
1396 for (uint i=0; i<_AdditionalParams.size(); ++i)
1398 IVar *var = md.getVariable(_AdditionalParams[i].ParamName);
1399 if (var == NULL)
1401 string err = toString("Can't find variable named '%s' to generate phrase param", _AdditionalParams[i].ParamName.c_str());
1402 throw EParseException(NULL, err.c_str());
1404 ret += "; " + var->evalVar(_AdditionalParams[i].CompilerParam);
1407 return ret;
1410 CMissionData::CMissionData()
1412 // init all datas
1413 _MonoInstance = false;
1414 _MissionAuto = false;
1415 _RunOnce = false;
1416 _Replayable = false;
1417 _Solo = false;
1418 _Guild = false;
1419 _NotInJournal = false;
1420 _AutoRemoveFromJournal = false;
1421 _PlayerReplayTimer = 0;
1422 _GlobalReplayTimer = 0;
1423 _NotProposed = false;
1424 _NonAbandonnable = false;
1425 _NeedValidation = false;
1426 _FailIfInventoryIsFull = false;
1429 CMissionData::~CMissionData()
1431 while (!_Variables.empty())
1433 delete _Variables.begin()->second;
1434 _Variables.erase(_Variables.begin());
1437 while (!_Steps.empty())
1439 delete _Steps.back();
1440 _Steps.pop_back();
1444 void CMissionData::setMissionName(const string &missionName)
1446 _MissionName = missionName;
1449 const string &CMissionData::getMissionName() { return _MissionName;}
1451 bool CMissionData::addVariable(NLLIGO::IPrimitive *prim, IVar *var)
1453 if (_Variables.find(var->getVarName()) != _Variables.end())
1454 throw EParseException(prim, toString("Variable '%s' already defined !", var->getVarName().c_str()).c_str());
1456 _Variables.insert(make_pair(var->getVarName(), var));
1457 _VariablesOrder.push_back(var);
1458 return true;
1461 IVar *CMissionData::getVariable(const string &varName)
1463 map<string, IVar*>::iterator it(_Variables.find(varName));
1464 if (it != _Variables.end())
1465 return it->second;
1466 return NULL;
1469 IStep *CMissionData::getNextStep(IStep *current)
1471 for (uint i=0; i<_Steps.size(); ++i)
1473 if (_Steps[i] == current && i < _Steps.size()-1)
1474 return _Steps[i+1];
1476 return NULL;
1479 IStep *CMissionData::getStepByName(const std::string &stepName)
1481 if (_StepsByNames.find(stepName) != _StepsByNames.end())
1483 return _StepsByNames[stepName];
1486 return NULL;
1490 bool CMissionData::addStep(IStep *step)
1492 _Steps.push_back(step);
1493 return true;
1496 string CMissionData::genPreRequisites()
1498 string ret;
1499 if (!_ReqSkills.empty())
1501 ret += "req_skill : ";
1502 for (uint i=0; i<_ReqSkills.size(); ++i)
1504 ret += _ReqSkills[i].Skill+" "+_ReqSkills[i].MinLevel+" "+_ReqSkills[i].MaxLevel;
1505 if (i < _ReqSkills.size()-1)
1506 ret +="; ";
1507 else
1508 ret += NL;
1511 if (!_ReqMissionDone.empty())
1513 for (uint i=0; i<_ReqMissionDone.size(); ++i)
1515 ret += "req_mission : "+ _ReqMissionDone[i]+NL;
1518 if (!_ReqMissionNotDone.empty())
1520 for (uint i=0; i<_ReqMissionNotDone.size(); ++i)
1522 ret += "req_mission_neg : "+_ReqMissionNotDone[i]+NL;
1525 if (!_ReqMissionRunning.empty())
1527 for (uint i=0; i<_ReqMissionRunning.size(); ++i)
1529 ret += "req_mission_running : "+_ReqMissionRunning[i]+NL;
1532 if (!_ReqMissionNotRunning.empty())
1535 for (uint i=0; i<_ReqMissionNotRunning.size(); ++i)
1537 ret += "req_mission_running_neg : "+_ReqMissionNotRunning[i]+NL;
1540 if (!_ReqWearItem.empty())
1542 ret += "req_wear : ";
1543 for (uint i=0; i<_ReqWearItem.size(); ++i)
1545 ret += _ReqWearItem[i];
1546 if(i < _ReqWearItem.size()-1)
1547 ret +="; ";
1548 ret += NL;
1551 if (!_ReqOwnItem.empty())
1553 ret += "req_item : ";
1554 for (uint i=0; i<_ReqOwnItem.size(); ++i)
1556 ret += _ReqOwnItem[i];
1557 if(i < _ReqOwnItem.size()-1)
1558 ret +="; ";
1559 ret += NL;
1562 if (!_ReqTitle.empty())
1564 ret += "req_title : "+_ReqTitle+NL;
1566 if (!_ReqFames.empty())
1568 for (uint i=0; i<_ReqFames.size(); ++i)
1570 ret += "req_fame : "+_ReqFames[i].Faction+" "+_ReqFames[i].Fame;
1571 ret += NL;
1574 if(_ReqGuild)
1576 ret += "req_guild"+NL;
1578 if (!_ReqGrade.empty())
1580 ret += "req_grade : "+_ReqGrade+NL;
1582 if (!_ReqTeamSize.empty())
1584 ret += "req_team_size : "+_ReqTeamSize+NL;
1586 if (!_ReqBrick.empty())
1588 ret += "req_brick : ";
1589 for (uint i=0; i<_ReqBrick.size(); ++i)
1591 ret += _ReqBrick[i];
1592 if(i < _ReqBrick.size()-1)
1593 ret +="; ";
1594 ret += NL;
1597 if (!_ReqCharacterAge.empty())
1599 ret += "req_character_age : "+_ReqCharacterAge+NL;
1601 if (!_ReqMaxPlayerID.empty())
1603 ret += "req_max_player_id : "+_ReqMaxPlayerID+NL;
1605 if (!_ReqSeason.empty())
1607 ret += "req_season : "+_ReqSeason+NL;
1609 if (!_ReqEncyclo.empty())
1611 ret += "req_encyclo_thema : " + _ReqEncyclo + NL;
1613 if (!_ReqEncycloNeg.empty())
1615 ret += "req_encyclo_thema_neg : " + _ReqEncycloNeg + NL;
1617 if (!_ReqEventFaction.empty())
1619 ret += "req_event_faction : " + _ReqEventFaction + NL;
1622 return ret;
1626 string CMissionData::generateMissionScript(const std::string &primFileName)
1628 _JumpPoints.clear();
1629 // first, gather jump point list
1630 for (uint i=0; i<_Steps.size(); ++i)
1632 set<TJumpInfo> temp;
1633 _Steps[i]->fillStepJump(*this, temp);
1635 // remove any jump to the next step (normal flow)
1636 if (i < _Steps.size()-1)
1638 set<TJumpInfo>::iterator first(temp.begin()), last(temp.end());
1639 for (; first != last; )
1641 const TJumpInfo &ji = *first;
1643 if (ji.StepName == _Steps[i+1]->getStepName() && ji.Discardable)
1645 temp.erase(first);
1646 first = temp.begin();
1648 else
1649 ++first;
1653 _JumpPoints.insert(temp.begin(), temp.end());
1655 // generate the script
1656 string script;
1657 // generate mission header
1658 script += "# script generated from '"+CFile::getFilename(primFileName)+"'"+NL+NL;
1659 script += "#mission tags and pre-requisites"+NL;
1660 if (_MonoInstance)
1661 script += "mono"+NL;
1662 if (_RunOnce)
1663 script += "once"+NL;
1664 if (_Replayable)
1665 script += "replayable"+NL;
1666 if (_Solo)
1667 script += "solo"+NL;
1668 if (_Guild)
1669 script += "guild"+NL;
1670 if (_NotInJournal)
1671 script += "no_list"+NL;
1672 if (_AutoRemoveFromJournal)
1673 script += "auto_remove"+NL;
1674 if (!_MissionCategory.empty())
1675 script += "mission_category : "+_MissionCategory+NL;
1676 if (_PlayerReplayTimer != 0)
1677 script += "player_replay_timer : "+toString("%u", _PlayerReplayTimer)+NL;
1678 if (_GlobalReplayTimer != 0)
1679 script += "global_replay_timer : "+toString("%u", _GlobalReplayTimer)+NL;
1680 if (_NotProposed)
1681 script += "not_proposed"+NL;
1682 if (_MissionAuto)
1683 script += string("auto : ")+_MissionAutoMenu.genScript(*this)+NL;
1684 if (_NonAbandonnable)
1685 script += "non_abandonnable"+NL;
1686 if (!_MissionIcon.empty())
1687 script += "mission_icon : "+_MissionIcon+NL;
1688 if (_NeedValidation)
1689 script += "need_validation"+NL;
1690 if (_FailIfInventoryIsFull)
1691 script += "fail_if_inventory_is_full"+NL;
1693 if (!_ParentMissions.empty())
1695 set<string>::iterator first(_ParentMissions.begin()), last(_ParentMissions.end());
1696 for (; first != last; ++first)
1698 script += "parent : "+ *first+NL;
1702 script += NL+"#Variables declaration"+NL;
1704 // declare all the variables
1706 std::vector<IVar*>::iterator first(_VariablesOrder.begin()), last(_VariablesOrder.end());
1707 for (; first != last; ++first)
1709 script += (*first)->genDecl(*this);
1713 script += NL+"#pre-requisites"+NL;
1714 script += genPreRequisites();
1716 script += NL+"#script"+NL;
1717 // generate mission title and desc
1718 script += "mission_title : "+_MissionTitle.genScript(*this)+NL;
1719 script += "mission_desc : "+_MissionDescription.genScript(*this)+NL;
1721 // generate steps scripts
1722 for (uint i=0; i<_Steps.size(); ++i)
1724 script += "# "+_Steps[i]->getStepName()+NL;
1725 if (_JumpPoints.find(_Steps[i]->getStepName()) != _JumpPoints.end()
1726 && !_Steps[i]->isAJump())
1728 // insert a jump point
1729 script += "jump_point : " + _Steps[i]->getStepName() + NL;
1732 script += _Steps[i]->genCode(*this);
1733 //if (_Steps[i]->EndOfBranch && !_Steps[i]->isAJump())
1734 // script += "end"+NL;
1737 return script;
1740 string CMissionData::generatePhraseFile()
1742 string ret;
1743 // generate header phrase
1744 ret = _MissionTitle.genPhrase();
1745 ret += _MissionDescription.genPhrase();
1746 ret += _MissionAutoMenu.genPhrase();
1748 // generate var phrase
1749 for (uint i=0; i<_VariablesOrder.size(); ++i)
1751 ret += _VariablesOrder[i]->genPhrase();
1754 // generate step phrase
1755 for (uint i=0; i<_Steps.size(); ++i)
1757 ret += _Steps[i]->genPhrase();
1759 return ret;
1762 string CMissionData::generateDotScript()
1764 string ret = "digraph " + _MissionName + NL;
1765 ret += "{" + NL;
1767 // set default shape to 'record'
1768 ret += "node [shape=record]"+NL;
1770 ret += "\t__start__ [shape=\"point\", peripheries=2, label=\"\"]"+NL;
1772 // 1st pass, generate node for each step
1773 for (uint i=0; i<_Steps.size(); ++i)
1775 if (!_Steps[i]->isEnd() && !_Steps[i]->isAJump())
1777 ret += "\t"+_Steps[i]->getStepName();
1778 ret += " [URL=\""+buildPrimPath(_Steps[i]->getPrimitive())+"\"]"+NL;
1782 ret += "\t__end__ [shape=\"point\"]"+NL;
1784 // activate red color for shapes that are created after this points
1785 ret += "node [color=red]"+NL;
1787 // 2nd pass, generate link between steps
1788 for (uint i=0; i<_Steps.size(); ++i)
1790 if (_Steps[i]->isAJump())
1791 continue;
1793 if (i == 0)
1795 ret += "\t__start__ -> " + _Steps[i]->getStepName() + NL;
1797 set<TJumpInfo> jumps;
1798 _Steps[i]->fillStepJump(*this, jumps);
1799 // there is a link there
1800 while (!jumps.empty())
1802 const TJumpInfo &ji = *(jumps.begin());
1803 if (_StepsByNames.find(ji.StepName) != _StepsByNames.end()
1804 && _StepsByNames[ji.StepName]->isAJump())
1806 // this step is a jump, skip to link to the jump destination
1807 IStep *jumpStep = _StepsByNames[ji.StepName];
1808 set<TJumpInfo> jumpJump;
1809 jumpStep->fillStepJump(*this, jumpJump);
1810 if (jumpJump.size() != 1)
1812 string str = toString("Step jump contains %u jumps destination instead of 1", jumpJump.size());
1813 throw EParseException(jumpStep->getPrimitive(), str.c_str());
1816 ret += "\t"+_Steps[i]->getStepName() + " -> " + jumpJump.begin()->StepName+" [label=\""+ji.JumpName+"\"]" + NL;
1818 else
1820 ret += "\t"+_Steps[i]->getStepName() + " -> " + ji.StepName+" [label=\""+jumps.begin()->JumpName+"\"]" + NL;
1822 jumps.erase(jumps.begin());
1827 ret += "}" + NL;
1829 return ret;
1833 void CMissionData::parseMissionHeader(NLLIGO::IPrimitive *prim)
1835 // _MissionName = getProperty(prim, "name", false, false);
1836 // if( _MissionName.find(' ') != string::npos)
1837 // {
1838 // throw EParseException(prim, toString("Mission name '%s' must not contains space", _MissionName.c_str()).c_str());
1839 // }
1840 _GiverPrimitive = getProperty(prim,"giver_primitive", true, false);
1841 _MissionGiver = getProperty(prim, "mission_giver", true, false);
1843 // _Alias = getProperty(prim, "alias", false, false);
1845 // If the mission is under a npc_bot node, then the giver is directly taken
1846 // from the npc name
1847 if (prim->getParent())
1849 if (getProperty(prim->getParent(), "class", false, false) == "npc_bot")
1851 _MissionGiver = getProperty(prim->getParent(), "name", false, false);
1855 vector<string> vs;
1856 _MissionTitleRaw = getPropertyArray(prim, "mission_title", false, false);
1857 // _MissionTitle.init(*this, prim, vs);
1858 _MissionDescriptionRaw = getPropertyArray(prim, "mission_description", false, false);
1859 // _MissionDescription.init(*this, prim, vs);
1860 _MonoInstance = toBool(getProperty(prim, "mono_instance", true, false));
1861 _RunOnce = toBool(getProperty(prim, "run_only_once", true, false));
1862 _Replayable = toBool(getProperty(prim, "replayable", true, false));
1864 _NeedValidation = toBool(getProperty(prim, "need_validation", true, false));
1866 _MissionAutoMenuRaw = getPropertyArray(prim, "phrase_auto_menu", false, false);
1868 // audience setting
1869 string s = getProperty(prim, "audience", false, false);
1870 if (s == "solo")
1871 _Solo = true;
1872 else if (s == "guild")
1873 _Guild = true;
1875 _NotInJournal = NLMISC::toBool(getProperty(prim, "not_in_journal", false, false));
1876 _AutoRemoveFromJournal = NLMISC::toBool(getProperty(prim, "auto_remove_from_journal", false, false));
1877 _MissionCategory = getProperty(prim, "mission_category", false, false);
1878 NLMISC::fromString(getProperty(prim, "player_replay_timer", true, false), _PlayerReplayTimer);
1879 NLMISC::fromString(getProperty(prim, "global_replay_timer", true, false), _GlobalReplayTimer);
1880 _NotProposed = NLMISC::toBool(getProperty(prim, "not_proposed", false, false));
1881 _MissionAuto = NLMISC::toBool(getProperty(prim, "automatic", false, false));
1882 _NonAbandonnable = NLMISC::toBool(getProperty(prim, "non_abandonnable", false, false));
1883 _FailIfInventoryIsFull = NLMISC::toBool(getProperty(prim, "fail_if_inventory_is_full", false, false));
1884 _MissionIcon = getProperty(prim, "mission_icon", false, false);
1886 if (_MissionAuto)
1888 if (_MissionAutoMenuRaw.empty())
1890 string error = toString("Mission is flagged automatic, but no phrase_auto_menu defined !");
1891 throw EParseException(prim, error.c_str());
1895 vs = getPropertyArray(prim, "parent_missions", true, false);
1896 _ParentMissions.insert(vs.begin(), vs.end());
1900 void CMissionData::parsePrerequisites(NLLIGO::IPrimitive *prim)
1902 // skills
1903 vector<string> vs;
1904 vs = getPropertyArray(prim, "require_skill/min_level/max_level", true, false);
1905 for (uint i=0; i<vs.size(); ++i)
1907 if (!vs[i].empty())
1909 vector<string> parts;
1910 strtokquote(vs[i], parts);
1911 if (parts.size() != 3)
1913 throw EParseException(prim, toString("Invalide argument count in line %u of require_skill array. Need 3, found %u", i, parts.size()).c_str());
1915 TReqSkill rs;
1916 rs.Skill = parts[0];
1917 rs.MinLevel = parts[1];
1918 rs.MaxLevel = parts[2];
1920 _ReqSkills.push_back(rs);
1924 // Mission done
1925 vs = getPropertyArray(prim, "require_mission_done", true, false);
1926 for (uint i=0; i<vs.size(); ++i)
1928 if (!vs[i].empty())
1930 vector<string> parts;
1931 strtokquote(vs[i], parts);
1932 if (parts.size() != 1)
1934 throw EParseException(prim, toString("Invalide argument count in line %u of require_mission_done array. Need 1, found %u", i, parts.size()).c_str());
1936 _ReqMissionDone.push_back(parts[0]);
1939 // Mission not done
1940 vs = getPropertyArray(prim, "require_mission_not_done", true, false);
1941 for (uint i=0; i<vs.size(); ++i)
1943 if (!vs[i].empty())
1945 vector<string> parts;
1946 strtokquote(vs[i], parts);
1947 if (parts.size() != 1)
1949 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());
1951 _ReqMissionNotDone.push_back(parts[0]);
1954 // Mission running
1955 vs = getPropertyArray(prim, "require_mission_running", true, false);
1956 for (uint i=0; i<vs.size(); ++i)
1958 if (!vs[i].empty())
1960 vector<string> parts;
1961 strtokquote(vs[i], parts);
1962 if (parts.size() != 1)
1964 throw EParseException(prim, toString("Invalide argument count in line %u of require_mission_running array. Need 1, found %u", i, parts.size()).c_str());
1966 _ReqMissionRunning.push_back(parts[0]);
1969 // Mission not running
1970 vs = getPropertyArray(prim, "require_mission_not_running", true, false);
1971 for (uint i=0; i<vs.size(); ++i)
1973 if (!vs[i].empty())
1975 vector<string> parts;
1976 strtokquote(vs[i], parts);
1977 if (parts.size() != 1)
1979 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());
1981 _ReqMissionNotRunning.push_back(parts[0]);
1984 // wearing item
1985 vs = getPropertyArray(prim, "require_wearing_item", true, false);
1986 for (uint i=0; i<vs.size(); ++i)
1988 if (!vs[i].empty())
1990 vector<string> parts;
1991 strtokquote(vs[i], parts);
1992 if (parts.size() != 1)
1994 throw EParseException(prim, toString("Invalide argument count in line %u of require_wearing_item array. Need 1, found %u", i, parts.size()).c_str());
1996 _ReqWearItem.push_back(parts[0]);
1999 // own item
2000 vs = getPropertyArray(prim, "require_own_item", true, false);
2001 for (uint i=0; i<vs.size(); ++i)
2003 if (!vs[i].empty())
2005 vector<string> parts;
2006 strtokquote(vs[i], parts);
2007 if (parts.size() != 1)
2009 throw EParseException(prim, toString("Invalide argument count in line %u of require_own_item array. Need 1, found %u", i, parts.size()).c_str());
2011 _ReqOwnItem.push_back(parts[0]);
2014 // title
2015 _ReqTitle = getProperty(prim, "require_title", true, false);
2016 // fame
2017 vs = getPropertyArray(prim, "require_faction/fame", true, false);
2018 for (uint i=0; i<vs.size(); ++i)
2020 if (!vs[i].empty())
2022 vector<string> parts;
2023 strtokquote(vs[i], parts);
2024 if (parts.size() != 2)
2026 throw EParseException(prim, toString("Invalide argument count in line %u of require_faction/fame array. Need 2, found %u", i, parts.size()).c_str());
2028 TReqFame rf;
2029 rf.Faction = parts[0];
2030 rf.Fame = parts[1];
2032 _ReqFames.push_back(rf);
2035 // guild
2036 if (getProperty(prim, "require_guild_membership", true, false) == "true")
2037 _ReqGuild = true;
2038 else
2039 _ReqGuild = false;
2040 // grade
2041 _ReqGrade = getProperty(prim, "require_guild_grade", true, false);
2042 // team size
2043 _ReqTeamSize = getProperty(prim, "require_team_size", true, false);
2044 // character minimum age
2045 _ReqCharacterAge = getProperty(prim, "require_character_age", true, false);
2046 // maximum player ID
2047 _ReqMaxPlayerID = getProperty(prim, "require_max_player_id", true, false);
2048 // brick
2049 vs = getPropertyArray(prim, "require_brick_knowledge", true, false);
2050 for (uint i=0; i<vs.size(); ++i)
2052 if (!vs[i].empty())
2054 vector<string> parts;
2055 strtokquote(vs[i], parts);
2056 if (parts.size() != 1)
2058 throw EParseException(prim, toString("Invalide argument count in line %u of require_brick_knowledge array. Need 1, found %u", i, parts.size()).c_str());
2060 _ReqBrick.push_back(parts[0]);
2063 // season
2064 _ReqSeason = getProperty(prim, "require_season", true, false);
2065 // encyclopedia
2066 _ReqEncyclo = getProperty(prim, "require_encyclo_thema", true, false);
2067 _ReqEncycloNeg = getProperty(prim, "require_encyclo_thema_neg", true, false);
2069 if ((!_ReqEncyclo.empty() && !_ReqEncycloNeg.empty())
2070 || (!_ReqEncycloNeg.empty() && !_ReqEncyclo.empty()))
2072 string err = toString("You can't mix positive and negative encyclopedy requirement");
2073 throw EParseException(prim, err.c_str());
2075 // event faction
2076 _ReqEventFaction = getProperty(prim, "require_event_faction", true, false);
2079 std::string CMissionData::replaceVar(NLLIGO::IPrimitive *prim, const std::string &str)
2081 string::size_type pos = 0;
2082 string::size_type pos2 = 0;
2083 string ret;
2085 while (pos < str.size())
2087 if (str[pos] != '$')
2089 ret += str[pos++];
2091 else if (pos+1 < str.size() && str[pos+1] == '$')
2093 // check that this $ is not escaped
2094 ret += '$';
2095 pos+=2;
2097 else
2099 // ok, this is not an escaped $
2100 CSString varName;
2101 // skip the initial '$'
2102 pos++;
2103 // while (str[pos] != ' ' && str[pos] != '\t' && str[pos] != '\n' && str[pos] != '\r')
2104 while (pos < str.size() && str[pos] != '$')
2105 varName += str[pos++];
2107 if (pos >= str.size())
2109 string err = toString("Error while parsing variable in '%s', missing closing '$'", str.c_str());
2110 throw EParseException (NULL, err.c_str());
2113 // skip the final '$'
2114 pos++;
2116 // split the var name and subpart
2117 vector<string> varParts;
2118 explode(string(varName), string("@"), varParts, true);
2120 if (varParts.empty() || varParts.size() > 2)
2122 throw EParseException(prim, toString("Error parsing varName '%s' in string '%s'", varName.c_str(), str.c_str()).c_str());
2125 if (_Variables.find(varParts.front()) == _Variables.end())
2127 string err = toString("Unknown variable '%s' in string '%s'", varParts.front().c_str(), str.c_str());
2128 throw EParseException (prim, err.c_str());
2131 IVar *var = _Variables[varParts[0]];
2133 if (varParts.size() == 1)
2134 ret += var->evalVar("");
2135 else
2136 ret += var->evalVar(varParts[1]);
2141 return ret;
2144 std::vector<std::string> CMissionData::replaceVar(NLLIGO::IPrimitive *prim, const std::vector<std::string> &strs)
2146 vector<string> ret;
2148 for (uint i=0; i<strs.size(); ++i)
2150 ret.push_back(replaceVar(prim, strs[i]));
2153 return ret;
2156 std::string CMissionData::getProperty(NLLIGO::IPrimitive *prim, const std::string &propertyName, bool replaceVar, bool canFail)
2158 string ret;
2159 string *s;
2160 if (!prim->getPropertyByName(propertyName.c_str(), s))
2162 if (!canFail)
2164 string err = toString("Can't find property '%s'", propertyName.c_str());
2165 throw EParseException (prim, err.c_str());
2168 else
2170 ret = *s;
2173 if (replaceVar)
2175 ret = this->replaceVar(prim, ret);
2178 return ret;
2181 std::vector<std::string> CMissionData::getPropertyArray(NLLIGO::IPrimitive *prim, const std::string &propertyName, bool replaceVar, bool canFail)
2183 vector<string> ret;
2184 vector<string> *vs;
2185 if (!prim->getPropertyByName(propertyName.c_str(), vs))
2187 if (!canFail)
2189 string err = toString("Can't find property '%s'", propertyName.c_str());
2190 throw EParseException (prim, err.c_str());
2193 else
2195 ret = *vs;
2198 if (replaceVar)
2200 ret = this->replaceVar(prim, ret);
2202 return ret;
2205 bool CMissionData::isThereAJumpTo(const std::string &stepName)
2207 if (_JumpPoints.find(stepName) != _JumpPoints.end())
2208 return true;
2209 else
2210 return false;
2213 void TCompilerVarName::init(const std::string &defaultName, STRING_MANAGER::TParamType type, CMissionData &md, NLLIGO::IPrimitive *prim, const std::string propName)
2215 _DefaultName = defaultName;
2216 _ParamType = type;
2218 _VarName = md.getProperty(prim, propName, false, false);
2219 // remove the variable tag if any
2220 untagVar(_VarName);
2222 _VarValue = md.getProperty(prim, propName, true, false);
2225 void TCompilerVarName::initWithText(const std::string &defaultName, STRING_MANAGER::TParamType type, CMissionData &md, NLLIGO::IPrimitive *prim, const std::string &text)
2227 _DefaultName = defaultName;
2228 _ParamType = type;
2230 _VarName = text;
2231 // remove the variable tag if any
2232 untagVar(_VarName);
2234 _VarValue = md.replaceVar(prim, text);
2238 CPhrase::TParamInfo TCompilerVarName::getParamInfo() const
2240 if (_VarName.empty())
2241 return CPhrase::TParamInfo(_DefaultName, _ParamType);
2242 else
2243 return CPhrase::TParamInfo(_VarName, _ParamType);
2247 bool TCompilerVarName::empty() const
2249 return _VarValue.empty();
2252 TCompilerVarName::operator const std::string () const
2254 return _VarValue;
2257 TCompilerVarName::operator CPhrase::TParamInfo() const
2259 return getParamInfo();
2263 std::string operator+(const TCompilerVarName& left, const std::string & right) { return left._VarValue + right;}
2265 std::string operator+(const std::string & left, const TCompilerVarName& right) { return left + right._VarValue;}
2267 std::vector<TCompilerVarName> TCompilerVarName::getPropertyArrayWithText(const std::string &defaultName, STRING_MANAGER::TParamType type, CMissionData &md, NLLIGO::IPrimitive *prim, const std::string & arrayProperyName)
2269 std::vector<TCompilerVarName> compilerParams;
2271 std::vector<std::string> values = md.getPropertyArray(prim, arrayProperyName,false, false);
2272 uint first = 0;
2273 uint last = (uint)values.size();
2274 compilerParams.resize(last);
2275 for ( ; first != last; ++first)
2277 compilerParams[first].initWithText( toString("%s%d", defaultName.c_str(), first+1) , type, md, prim, values[first]);
2280 return compilerParams;
2283 std::vector<TCompilerVarName> TCompilerVarName::getPropertyArrayWithTextStaticDefaultName(const std::string &defaultName, STRING_MANAGER::TParamType type, CMissionData &md, NLLIGO::IPrimitive *prim, const std::string & arrayProperyName)
2285 std::vector<TCompilerVarName> compilerParams;
2286 std::vector<std::string> values = md.getPropertyArray(prim, arrayProperyName,false, false);
2287 uint first = 0;
2288 uint last = (uint)values.size();
2289 compilerParams.resize(last);
2290 for ( ; first != last; ++first)
2292 compilerParams[first].initWithText( defaultName, type, md, prim, values[first]);
2294 return compilerParams;