1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "ultima/ultima4/game/script.h"
24 #include "ultima/ultima4/game/armor.h"
25 #include "ultima/ultima4/game/context.h"
26 #include "ultima/ultima4/controllers/inn_controller.h"
27 #include "ultima/ultima4/conversation/conversation.h"
28 #include "ultima/ultima4/core/settings.h"
29 #include "ultima/ultima4/core/utils.h"
30 #include "ultima/ultima4/events/event_handler.h"
31 #include "ultima/ultima4/filesys/savegame.h"
32 #include "ultima/ultima4/game/game.h"
33 #include "ultima/ultima4/game/player.h"
34 #include "ultima/ultima4/game/weapon.h"
35 #include "ultima/ultima4/game/spell.h"
36 #include "ultima/ultima4/views/stats.h"
37 #include "ultima/ultima4/gfx/screen.h"
38 #include "ultima/ultima4/map/tileset.h"
39 #include "ultima/ultima4/sound/music.h"
40 #include "ultima/shared/conf/xml_tree.h"
41
42 namespace Ultima {
43 namespace Ultima4 {
44
45 /*
46 * Script::Variable class
47 */
Variable()48 Script::Variable::Variable() : _iVal(0), _sVal(""), _set(false) {}
Variable(const Common::String & v)49 Script::Variable::Variable(const Common::String &v) : _set(true) {
50 _iVal = static_cast<int>(strtol(v.c_str(), nullptr, 10));
51 _sVal = v;
52 }
53
Variable(const int & v)54 Script::Variable::Variable(const int &v) : _set(true) {
55 _iVal = v;
56 _sVal = xu4_to_string(v);
57 }
58
getInt()59 int &Script::Variable::getInt() {
60 return _iVal;
61 }
62
getString()63 Common::String &Script::Variable::getString() {
64 return _sVal;
65 }
66
setValue(const int & v)67 void Script::Variable::setValue(const int &v) {
68 _iVal = v;
69 }
70
setValue(const Common::String & v)71 void Script::Variable::setValue(const Common::String &v) {
72 _sVal = v;
73 }
74
unset()75 void Script::Variable::unset() {
76 _set = false;
77 _iVal = 0;
78 _sVal = "";
79 }
80
isInt() const81 bool Script::Variable::isInt() const {
82 return _iVal > 0;
83 }
84
isString() const85 bool Script::Variable::isString() const {
86 return _iVal == 0;
87 }
88
isSet() const89 bool Script::Variable::isSet() const {
90 return _set;
91 }
92
Script()93 Script::Script() : _vendorScriptDoc(nullptr), _scriptNode(nullptr),
94 _debug(false), _state(STATE_UNLOADED), _currentScript(nullptr),
95 _currentItem(nullptr), _inputType(INPUT_CHOICE), _inputMaxLen(0),
96 _nounName("item"), _idPropName("id"), _iterator(0) {
97 _actionMap["context"] = ACTION_SET_CONTEXT;
98 _actionMap["unset_context"] = ACTION_UNSET_CONTEXT;
99 _actionMap["end"] = ACTION_END;
100 _actionMap["redirect"] = ACTION_REDIRECT;
101 _actionMap["wait_for_keypress"] = ACTION_WAIT_FOR_KEY;
102 _actionMap["wait"] = ACTION_WAIT;
103 _actionMap["stop"] = ACTION_STOP;
104 _actionMap["include"] = ACTION_INCLUDE;
105 _actionMap["for"] = ACTION_FOR_LOOP;
106 _actionMap["random"] = ACTION_RANDOM;
107 _actionMap["move"] = ACTION_MOVE;
108 _actionMap["sleep"] = ACTION_SLEEP;
109 _actionMap["cursor"] = ACTION_CURSOR;
110 _actionMap["pay"] = ACTION_PAY;
111 _actionMap["if"] = ACTION_IF;
112 _actionMap["input"] = ACTION_INPUT;
113 _actionMap["add"] = ACTION_ADD;
114 _actionMap["lose"] = ACTION_LOSE;
115 _actionMap["heal"] = ACTION_HEAL;
116 _actionMap["cast_spell"] = ACTION_CAST_SPELL;
117 _actionMap["damage"] = ACTION_DAMAGE;
118 _actionMap["karma"] = ACTION_KARMA;
119 _actionMap["music"] = ACTION_MUSIC;
120 _actionMap["var"] = ACTION_SET_VARIABLE;
121 _actionMap["ztats"] = ACTION_ZTATS;
122 }
123
~Script()124 Script::~Script() {
125 unload();
126
127 // We have many Variables that are allocated but need to have delete called on them.
128 // We do not need to clear the containers (that will happen automatically), but we do need to delete
129 // these things. Do NOT clean up the providers though, it seems the providers map doesn't own its pointers.
130 // Smart pointers anyone?
131
132 // Clean variables
133 Std::map<Common::String, Script::Variable *>::iterator variableItem = _variables.begin();
134 Std::map<Common::String, Script::Variable *>::iterator variablesEnd = _variables.end();
135 while (variableItem != variablesEnd) {
136 delete variableItem->_value;
137 ++variableItem;
138 }
139 }
140
removeCurrentVariable(const Common::String & name)141 void Script::removeCurrentVariable(const Common::String &name) {
142 Std::map<Common::String, Script::Variable *>::iterator dup = _variables.find(name);
143 if (dup != _variables.end()) {
144 delete dup->_value;
145 _variables.erase(dup); // not strictly necessary, but correct.
146 }
147 }
148
addProvider(const Common::String & name,Provider * p)149 void Script::addProvider(const Common::String &name, Provider *p) {
150 _providers[name] = p;
151 }
152
load(const Common::String & filename,const Common::String & baseId,const Common::String & subNodeName,const Common::String & subNodeId)153 bool Script::load(const Common::String &filename, const Common::String &baseId, const Common::String &subNodeName, const Common::String &subNodeId) {
154 Shared::XMLNode *root, *node, *child;
155 _state = STATE_NORMAL;
156
157 /* unload previous script */
158 unload();
159
160 /**
161 * Open and parse the .xml file
162 */
163 Shared::XMLTree *doc = new Shared::XMLTree(
164 Common::String::format("data/conf/%s", filename.c_str()));
165 _vendorScriptDoc = root = doc->getTree();
166
167 if (!root->id().equalsIgnoreCase("scripts"))
168 error("malformed %s", filename.c_str());
169
170 // Check whether script is set to debug
171 _debug = root->getPropertyBool("debug");
172
173 /**
174 * Get a new global item name or id name
175 */
176 if (root->hasProperty("noun"))
177 _nounName = root->getProperty("noun");
178 if (root->hasProperty("id_prop"))
179 _idPropName = root->getProperty("id_prop");
180
181 _currentScript = nullptr;
182 _currentItem = nullptr;
183
184 for (node = root->firstChild(); node; node = node->getNext()) {
185 if (node->nodeIsText() || !node->id().equalsIgnoreCase("script"))
186 continue;
187
188 if (baseId == node->getProperty("id")) {
189 /**
190 * We use the base node as our main script node
191 */
192 if (subNodeName.empty()) {
193 _scriptNode = node;
194 _translationContext.push_back(node);
195
196 break;
197 }
198
199 for (child = node->firstChild(); child; child = child->getNext()) {
200 if (child->nodeIsText() || !child->id().equalsIgnoreCase(subNodeName))
201 continue;
202
203 Common::String id = child->getProperty("id");
204
205 if (id == subNodeId) {
206 _scriptNode = child;
207 _translationContext.push_back(child);
208
209 /**
210 * Get a new local item name or id name
211 */
212 if (node->hasProperty("noun"))
213 _nounName = node->getProperty("noun");
214 if (node->hasProperty("id_prop"))
215 _idPropName = node->getProperty("id_prop");
216
217 break;
218 }
219 }
220
221 if (_scriptNode)
222 break;
223 }
224 }
225
226 if (_scriptNode) {
227 /**
228 * Get a new local item name or id name
229 */
230 if (_scriptNode->hasProperty("noun"))
231 _nounName = _scriptNode->getProperty("noun");
232 if (_scriptNode->hasProperty("id_prop"))
233 _idPropName = _scriptNode->getProperty("id_prop");
234
235 if (_debug)
236 debug("\n<Loaded subscript '%s' where id='%s' for script '%s'>\n", subNodeName.c_str(), subNodeId.c_str(), baseId.c_str());
237 } else {
238 if (subNodeName.empty())
239 error("Couldn't find script '%s' in %s", baseId.c_str(), filename.c_str());
240 else
241 error("Couldn't find subscript '%s' where id='%s' in script '%s' in %s", subNodeName.c_str(), subNodeId.c_str(), baseId.c_str(), filename.c_str());
242 }
243
244 _state = STATE_UNLOADED;
245
246 return false;
247 }
248
unload()249 void Script::unload() {
250 if (_vendorScriptDoc) {
251 _vendorScriptDoc->freeDoc();
252 _vendorScriptDoc = nullptr;
253 }
254 }
255
run(const Common::String & script)256 void Script::run(const Common::String &script) {
257 Shared::XMLNode *scriptNode;
258 Common::String search_id;
259
260 if (_variables.find(_idPropName) != _variables.end()) {
261 if (_variables[_idPropName]->isSet())
262 search_id = _variables[_idPropName]->getString();
263 else
264 search_id = "null";
265 }
266
267 scriptNode = find(_scriptNode, script, search_id);
268
269 if (!scriptNode)
270 error("Script '%s' not found in vendorScript.xml", script.c_str());
271
272 execute(scriptNode);
273 }
274
execute(Shared::XMLNode * script,Shared::XMLNode * currentItem,Common::String * output)275 Script::ReturnCode Script::execute(Shared::XMLNode *script, Shared::XMLNode *currentItem, Common::String *output) {
276 Shared::XMLNode *current;
277 Script::ReturnCode retval = RET_OK;
278
279 if (!script->hasChildren()) {
280 /* redirect the script to another node */
281 if (script->hasProperty("redirect"))
282 retval = redirect(nullptr, script);
283 /* end the conversation */
284 else {
285 if (_debug)
286 debug("A script with no children found (nowhere to go). Ending script...");
287 g_screen->screenMessage("\n");
288 _state = STATE_DONE;
289 }
290 }
291
292 /* do we start where we left off, or start from the beginning? */
293 if (currentItem) {
294 current = currentItem->getNext();
295 if (_debug)
296 debug("Returning to execution from end of '%s' script", currentItem->id().c_str());
297 } else {
298 current = script->firstChild();
299 }
300
301 for (; current; current = current->getNext()) {
302 Common::String name = current->id();
303 retval = RET_OK;
304 ActionMap::iterator action;
305
306 /* nothing left to do */
307 if (_state == STATE_DONE)
308 break;
309
310 /* begin execution of script */
311
312 /**
313 * Handle Text
314 */
315 if (current->nodeIsText()) {
316 Common::String content = getContent(current);
317 if (output)
318 *output += content;
319 else
320 g_screen->screenMessage("%s", content.c_str());
321
322 if (_debug && content.size())
323 debug("Output: \n====================\n%s\n====================", content.c_str());
324 } else {
325 /**
326 * Search for the corresponding action and execute it!
327 */
328 action = _actionMap.find(name);
329 if (action != _actionMap.end()) {
330 /**
331 * Found it!
332 */
333 switch (action->_value) {
334 case ACTION_SET_CONTEXT:
335 retval = pushContext(script, current);
336 break;
337 case ACTION_UNSET_CONTEXT:
338 retval = popContext(script, current);
339 break;
340 case ACTION_END:
341 retval = end(script, current);
342 break;
343 case ACTION_REDIRECT:
344 retval = redirect(script, current);
345 break;
346 case ACTION_WAIT_FOR_KEY:
347 retval = waitForKeypress(script, current);
348 break;
349 case ACTION_WAIT:
350 retval = wait(script, current);
351 break;
352 case ACTION_STOP:
353 retval = RET_STOP;
354 break;
355 case ACTION_INCLUDE:
356 retval = include(script, current);
357 break;
358 case ACTION_FOR_LOOP:
359 retval = forLoop(script, current);
360 break;
361 case ACTION_RANDOM:
362 retval = randomScript(script, current);
363 break;
364 case ACTION_MOVE:
365 retval = move(script, current);
366 break;
367 case ACTION_SLEEP:
368 retval = sleep(script, current);
369 break;
370 case ACTION_CURSOR:
371 retval = cursor(script, current);
372 break;
373 case ACTION_PAY:
374 retval = pay(script, current);
375 break;
376 case ACTION_IF:
377 retval = _if(script, current);
378 break;
379 case ACTION_INPUT:
380 retval = input(script, current);
381 break;
382 case ACTION_ADD:
383 retval = add(script, current);
384 break;
385 case ACTION_LOSE:
386 retval = lose(script, current);
387 break;
388 case ACTION_HEAL:
389 retval = heal(script, current);
390 break;
391 case ACTION_CAST_SPELL:
392 retval = castSpell(script, current);
393 break;
394 case ACTION_DAMAGE:
395 retval = damage(script, current);
396 break;
397 case ACTION_KARMA:
398 retval = karma(script, current);
399 break;
400 case ACTION_MUSIC:
401 retval = music(script, current);
402 break;
403 case ACTION_SET_VARIABLE:
404 retval = setVar(script, current);
405 break;
406 case ACTION_ZTATS:
407 retval = ztats(script, current);
408 break;
409 default:
410
411 break;
412 }
413 }
414 /**
415 * Didn't find the corresponding action...
416 */
417 else if (_debug)
418 debug("ERROR: '%s' method not found", name.c_str());
419
420 /* The script was redirected or stopped, stop now! */
421 if ((retval == RET_REDIRECTED) || (retval == RET_STOP))
422 break;
423 }
424
425 if (_debug)
426 debug("\n");
427 }
428
429 return retval;
430 }
431
_continue()432 void Script::_continue() {
433 /* reset our script state to normal */
434 resetState();
435
436 /* there's no target indicated, just start where we left off! */
437 if (_target.empty())
438 execute(_currentScript, _currentItem);
439 else
440 run(_target);
441 }
442
resetState()443 void Script::resetState() {
444 _state = STATE_NORMAL;
445 }
446
setState(Script::State s)447 void Script::setState(Script::State s) {
448 _state = s;
449 }
450
setTarget(const Common::String & val)451 void Script::setTarget(const Common::String &val) {
452 _target = val;
453 }
454
setChoices(const Common::String & val)455 void Script::setChoices(const Common::String &val) {
456 _choices = val;
457 }
458
setVar(const Common::String & name,const Common::String & val)459 void Script::setVar(const Common::String &name, const Common::String &val) {
460 removeCurrentVariable(name);
461 _variables[name] = new Variable(val);
462 }
463
setVar(const Common::String & name,int val)464 void Script::setVar(const Common::String &name, int val) {
465 removeCurrentVariable(name);
466 _variables[name] = new Variable(val);
467 }
468
unsetVar(const Common::String & name)469 void Script::unsetVar(const Common::String &name) {
470 // Ensure that the variable at least exists, but has no value
471 if (_variables.find(name) != _variables.end())
472 _variables[name]->unset();
473 else
474 _variables[name] = new Variable();
475 }
476
getState()477 Script::State Script::getState() {
478 return _state;
479 }
480
getTarget()481 Common::String Script::getTarget() {
482 return _target;
483 }
484
getInputType()485 Script::InputType Script::getInputType() {
486 return _inputType;
487 }
488
getChoices()489 Common::String Script::getChoices() {
490 return _choices;
491 }
492
getInputName()493 Common::String Script::getInputName() {
494 return _inputName;
495 }
496
getInputMaxLen()497 int Script::getInputMaxLen() {
498 return _inputMaxLen;
499 }
500
translate(Common::String * text)501 void Script::translate(Common::String *text) {
502 uint pos;
503 bool nochars = true;
504 Shared::XMLNode *node = _translationContext.back();
505
506 /* determine if the script is completely whitespace */
507 for (Common::String::iterator current = text->begin(); current != text->end(); current++) {
508 if (Common::isAlnum(*current)) {
509 nochars = false;
510 break;
511 }
512 }
513
514 /* erase scripts that are composed entirely of whitespace */
515 if (nochars)
516 text->clear();
517
518 while ((pos = text->findFirstOf("{")) < text->size()) {
519 Common::String pre = text->substr(0, pos);
520 Common::String post;
521 Common::String item = text->substr(pos + 1);
522
523 /**
524 * Handle embedded items
525 */
526 int num_embedded = 0;
527 int total_pos = 0;
528 Common::String current = item;
529 while (true) {
530 uint open = current.findFirstOf("{"),
531 close = current.findFirstOf("}");
532
533 if (close == current.size())
534 error("Error: no closing } found in script.");
535
536 if (open < close) {
537 num_embedded++;
538 total_pos += open + 1;
539 current = current.substr(open + 1);
540 }
541 if (close < open) {
542 total_pos += close;
543 if (num_embedded == 0) {
544 pos = total_pos;
545 break;
546 }
547 num_embedded--;
548 total_pos += 1;
549 current = current.substr(close + 1);
550 }
551 }
552
553 /**
554 * Separate the item itself from the pre- and post-data
555 */
556 post = item.substr(pos + 1);
557 item = item.substr(0, pos);
558
559 if (_debug)
560 debugN("{%s} == ", item.c_str());
561
562 /* translate any stuff contained in the item */
563 translate(&item);
564
565 Common::String prop;
566
567 // Get defined variables
568 if (item[0] == '$') {
569 Common::String varName = item.substr(1);
570 if (_variables.find(varName) != _variables.end())
571 prop = _variables[varName]->getString();
572 }
573 // Get the current iterator for our loop
574 else if (item == "iterator")
575 prop = xu4_to_string(_iterator);
576 else if ((pos = item.find("show_inventory:")) < item.size()) {
577 pos = item.find(":");
578 Common::String itemScript = item.substr(pos + 1);
579
580 Shared::XMLNode *itemShowScript = find(node, itemScript);
581
582 Shared::XMLNode *nodePtr;
583 prop.clear();
584
585 /**
586 * Save iterator
587 */
588 int oldIterator = _iterator;
589
590 /* start iterator at 0 */
591 _iterator = 0;
592
593 for (nodePtr = node->firstChild(); nodePtr; nodePtr = nodePtr->getNext()) {
594 if (nodePtr->id().equalsIgnoreCase(_nounName)) {
595 bool hidden = nodePtr->getPropertyBool("hidden");
596
597 if (!hidden) {
598 /* make sure the nodePtr's requisites are met */
599 if (!nodePtr->hasProperty("req") || compare(nodePtr->getProperty("req"))) {
600 /* put a newline after each */
601 if (_iterator > 0)
602 prop += "\n";
603
604 /* set translation context to nodePtr */
605 _translationContext.push_back(nodePtr);
606 execute(itemShowScript, nullptr, &prop);
607 _translationContext.pop_back();
608
609 _iterator++;
610 }
611 }
612 }
613 }
614
615 /**
616 * Restore iterator to previous value
617 */
618 _iterator = oldIterator;
619 }
620
621 /**
622 * Make a Common::String containing the available ids using the
623 * vendor's inventory (i.e. "bcde")
624 */
625 else if (item == "inventory_choices") {
626 Shared::XMLNode *nodePtr;
627 Common::String ids;
628
629 for (nodePtr = node->firstChild(); nodePtr; nodePtr = nodePtr->getNext()) {
630 if (nodePtr->id().equalsIgnoreCase(_nounName)) {
631 Common::String id = getPropAsStr(nodePtr, _idPropName.c_str());
632 /* make sure the nodePtr's requisites are met */
633 if (!nodePtr->hasProperty("req") || (compare(getPropAsStr(nodePtr, "req"))))
634 ids += id[0];
635 }
636 }
637
638 prop = ids;
639 }
640
641 /**
642 * Ask our providers if they have a valid translation for us
643 */
644 else if (item.findFirstOf(":") != Common::String::npos) {
645 int index = item.findFirstOf(":");
646 Common::String provider = item;
647 Common::String to_find;
648
649 provider = item.substr(0, index);
650 to_find = item.substr(index + 1);
651 if (_providers.find(provider) != _providers.end()) {
652 Std::vector<Common::String> parts = split(to_find, ":");
653 Provider *p = _providers[provider];
654 prop = p->translate(parts);
655 }
656 }
657
658 /**
659 * Resolve as a property name or a function
660 */
661 else {
662 Common::String funcName, content;
663
664 funcParse(item, &funcName, &content);
665
666 /*
667 * Check to see if it's a property name
668 */
669 if (funcName.empty()) {
670 /* we have the property name, now go get the property value! */
671 prop = getPropAsStr(_translationContext, item, true);
672 }
673
674 /**
675 * We have a function, make it work!
676 */
677 else {
678 /* perform the <math> function on the content */
679 if (funcName == "math") {
680 if (content.empty())
681 warning("Error: empty math() function");
682
683 prop = xu4_to_string(mathValue(content));
684 }
685
686 /**
687 * Does a true/false comparison on the content.
688 * Replaced with "true" if evaluates to true, or "false" if otherwise
689 */
690 else if (funcName == "compare") {
691 if (compare(content))
692 prop = "true";
693 else
694 prop = "false";
695 }
696
697 /* make the Common::String upper case */
698 else if (funcName == "toupper") {
699 Common::String::iterator it;
700 for (it = content.begin(); it != content.end(); it++)
701 *it = toupper(*it);
702
703 prop = content;
704 }
705 /* make the Common::String lower case */
706 else if (funcName == "tolower") {
707 Common::String::iterator it;
708 for (it = content.begin(); it != content.end(); it++)
709 *it = tolower(*it);
710
711 prop = content;
712 }
713
714 /* generate a random number */
715 else if (funcName == "random")
716 prop = xu4_to_string(xu4_random((int)strtol(content.c_str(), nullptr, 10)));
717
718 /* replaced with "true" if content is empty, or "false" if not */
719 else if (funcName == "isempty") {
720 if (content.empty())
721 prop = "true";
722 else
723 prop = "false";
724 }
725 }
726 }
727
728 if (prop.empty() && _debug)
729 debug("Warning: dynamic property '{%s}' not found in vendor script (was this intentional?)", item.c_str());
730
731 if (_debug)
732 debug("\"%s\"", prop.c_str());
733
734 /* put the script back together */
735 *text = pre + prop + post;
736 }
737
738 /* remove all unnecessary spaces from xml */
739 while ((pos = text->find("\t")) < text->size())
740 text->replace(pos, 1, "");
741 while ((pos = text->find(" ")) < text->size())
742 text->replace(pos, 2, "");
743 while ((pos = text->find("\n ")) < text->size())
744 text->replace(pos, 2, "\n");
745 }
746
find(Shared::XMLNode * node,const Common::String & script_to_find,const Common::String & id,bool _default)747 Shared::XMLNode *Script::find(Shared::XMLNode *node, const Common::String &script_to_find, const Common::String &id, bool _default) {
748 Shared::XMLNode *current;
749 if (node) {
750 for (current = node->firstChild(); current; current = current->getNext()) {
751 if (!current->nodeIsText() && (script_to_find == current->id().c_str())) {
752 if (id.empty() && !current->hasProperty(_idPropName.c_str()) && !_default)
753 return current;
754 else if (current->hasProperty(_idPropName.c_str()) && (id == current->getProperty(_idPropName)))
755 return current;
756 else if (_default && current->hasProperty("default") && current->getPropertyBool("default"))
757 return current;
758 }
759 }
760
761 /* only search the parent nodes if we haven't hit the base <script> node */
762 if (!node->id().equalsIgnoreCase("script"))
763 current = find(node->getParent(), script_to_find, id);
764
765 /* find the default script instead */
766 if (!current && !id.empty() && !_default)
767 current = find(node, script_to_find, "", true);
768 return current;
769 }
770 return nullptr;
771 }
772
getPropAsStr(Std::list<Shared::XMLNode * > & nodes,const Common::String & prop,bool recursive)773 Common::String Script::getPropAsStr(Std::list<Shared::XMLNode *> &nodes, const Common::String &prop, bool recursive) {
774 Common::String propvalue;
775 Std::list<Shared::XMLNode *>::reverse_iterator i;
776
777 for (i = nodes.rbegin(); i != nodes.rend(); ++i) {
778 Shared::XMLNode *node = *i;
779 if (node->hasProperty(prop)) {
780 propvalue = node->getProperty(prop);
781 break;
782 }
783 }
784
785 if (propvalue.empty() && recursive) {
786 for (i = nodes.rbegin(); i != nodes.rend(); ++i) {
787 Shared::XMLNode *node = *i;
788 if (node->getParent()) {
789 propvalue = getPropAsStr(node->getParent(), prop, recursive);
790 break;
791 }
792 }
793 }
794
795 translate(&propvalue);
796 return propvalue;
797 }
798
getPropAsStr(Shared::XMLNode * node,const Common::String & prop,bool recursive)799 Common::String Script::getPropAsStr(Shared::XMLNode *node, const Common::String &prop, bool recursive) {
800 Std::list<Shared::XMLNode *> list;
801 list.push_back(node);
802 return getPropAsStr(list, prop, recursive);
803 }
804
getPropAsInt(Std::list<Shared::XMLNode * > & nodes,const Common::String & prop,bool recursive)805 int Script::getPropAsInt(Std::list<Shared::XMLNode *> &nodes, const Common::String &prop, bool recursive) {
806 Common::String propvalue = getPropAsStr(nodes, prop, recursive);
807 return mathValue(propvalue);
808 }
809
getPropAsInt(Shared::XMLNode * node,const Common::String & prop,bool recursive)810 int Script::getPropAsInt(Shared::XMLNode *node, const Common::String &prop, bool recursive) {
811 Common::String propvalue = getPropAsStr(node, prop, recursive);
812 return mathValue(propvalue);
813 }
814
getContent(Shared::XMLNode * node)815 Common::String Script::getContent(Shared::XMLNode *node) {
816 Common::String content = node->text();
817 translate(&content);
818 return content;
819 }
820
pushContext(Shared::XMLNode * script,Shared::XMLNode * current)821 Script::ReturnCode Script::pushContext(Shared::XMLNode *script, Shared::XMLNode *current) {
822 Common::String nodeName = getPropAsStr(current, "name");
823 Common::String search_id;
824
825 if (current->hasProperty(_idPropName.c_str()))
826 search_id = getPropAsStr(current, _idPropName);
827 else if (_variables.find(_idPropName) != _variables.end()) {
828 if (_variables[_idPropName]->isSet())
829 search_id = _variables[_idPropName]->getString();
830 else
831 search_id = "null";
832 }
833
834 // When looking for a new context, start from within our old one
835 _translationContext.push_back(find(_translationContext.back(), nodeName, search_id));
836 if (_debug) {
837 if (!_translationContext.back())
838 debug("Warning!!! Invalid translation context <%s %s=\"%s\" ...>", nodeName.c_str(), _idPropName.c_str(), search_id.c_str());
839 else
840 debug("Changing translation context to <%s %s=\"%s\" ...>", nodeName.c_str(), _idPropName.c_str(), search_id.c_str());
841 }
842
843 return RET_OK;
844 }
845
popContext(Shared::XMLNode * script,Shared::XMLNode * current)846 Script::ReturnCode Script::popContext(Shared::XMLNode *script, Shared::XMLNode *current) {
847 if (_translationContext.size() > 1) {
848 _translationContext.pop_back();
849 if (_debug)
850 debug("Reverted translation context to <%s ...>", _translationContext.back()->id().c_str());
851 }
852 return RET_OK;
853 }
854
end(Shared::XMLNode * script,Shared::XMLNode * current)855 Script::ReturnCode Script::end(Shared::XMLNode *script, Shared::XMLNode *current) {
856 /**
857 * See if there's a global 'end' node declared for cleanup
858 */
859 Shared::XMLNode *endScript = find(_scriptNode, "end");
860 if (endScript)
861 execute(endScript);
862
863 if (_debug)
864 debug("<End script>");
865
866 _state = STATE_DONE;
867
868 return RET_STOP;
869 }
870
waitForKeypress(Shared::XMLNode * script,Shared::XMLNode * current)871 Script::ReturnCode Script::waitForKeypress(Shared::XMLNode *script, Shared::XMLNode *current) {
872 _currentScript = script;
873 _currentItem = current;
874 _choices = "abcdefghijklmnopqrstuvwxyz01234567890\015 \033";
875 _target.clear();
876 _state = STATE_INPUT;
877 _inputType = INPUT_KEYPRESS;
878
879 if (_debug)
880 debug("<Wait>");
881
882 return RET_STOP;
883 }
884
redirect(Shared::XMLNode * script,Shared::XMLNode * current)885 Script::ReturnCode Script::redirect(Shared::XMLNode *script, Shared::XMLNode *current) {
886 Common::String target;
887
888 if (current->hasProperty("redirect"))
889 target = getPropAsStr(current, "redirect");
890 else
891 target = getPropAsStr(current, "target");
892
893 /* set a new search id */
894 Common::String search_id = getPropAsStr(current, _idPropName);
895
896 Shared::XMLNode *newScript = find(_scriptNode, target, search_id);
897 if (!newScript)
898 error("Error: redirect failed -- could not find target script '%s' with %s=\"%s\"", target.c_str(), _idPropName.c_str(), search_id.c_str());
899
900 if (_debug) {
901 debugN("Redirected to <%s", target.c_str());
902 if (search_id.size())
903 debugN(" %s=\"%s\"", _idPropName.c_str(), search_id.c_str());
904 debug(" .../>");
905 }
906
907 execute(newScript);
908 return RET_REDIRECTED;
909 }
910
include(Shared::XMLNode * script,Shared::XMLNode * current)911 Script::ReturnCode Script::include(Shared::XMLNode *script, Shared::XMLNode *current) {
912 Common::String scriptName = getPropAsStr(current, "script");
913 Common::String id = getPropAsStr(current, _idPropName);
914
915 Shared::XMLNode *newScript = find(_scriptNode, scriptName, id);
916 if (!newScript)
917 error("Error: include failed -- could not find target script '%s' with %s=\"%s\"", scriptName.c_str(), _idPropName.c_str(), id.c_str());
918
919 if (_debug) {
920 debugN("Included script <%s", scriptName.c_str());
921 if (!id.empty())
922 debugN(" %s=\"%s\"", _idPropName.c_str(), id.c_str());
923 debug(" .../>");
924 }
925
926 execute(newScript);
927 return RET_OK;
928 }
929
wait(Shared::XMLNode * script,Shared::XMLNode * current)930 Script::ReturnCode Script::wait(Shared::XMLNode *script, Shared::XMLNode *current) {
931 int msecs = getPropAsInt(current, "msecs");
932 EventHandler::wait_msecs(msecs);
933 return RET_OK;
934 }
935
forLoop(Shared::XMLNode * script,Shared::XMLNode * current)936 Script::ReturnCode Script::forLoop(Shared::XMLNode *script, Shared::XMLNode *current) {
937 Script::ReturnCode retval = RET_OK;
938 int start = getPropAsInt(current, "start"),
939 end = getPropAsInt(current, "end"),
940 /* save the iterator in case this loop is nested */
941 oldIterator = _iterator,
942 i;
943
944 if (_debug)
945 debug("\n<For Start=%d End=%d>", start, end);
946
947 for (i = start, _iterator = start;
948 i <= end;
949 i++, _iterator++) {
950
951 if (_debug)
952 debug("%d: ", i);
953
954 retval = execute(current);
955 if ((retval == RET_REDIRECTED) || (retval == RET_STOP))
956 break;
957 }
958
959 /* restore the previous iterator */
960 _iterator = oldIterator;
961
962 return retval;
963 }
964
randomScript(Shared::XMLNode * script,Shared::XMLNode * current)965 Script::ReturnCode Script::randomScript(Shared::XMLNode *script, Shared::XMLNode *current) {
966 int perc = getPropAsInt(current, "chance");
967 int num = xu4_random(100);
968 Script::ReturnCode retval = RET_OK;
969
970 if (num < perc)
971 retval = execute(current);
972
973 if (_debug)
974 debug("Random (%d%%): rolled %d (%s)", perc, num, (num < perc) ? "Succeeded" : "Failed");
975
976 return retval;
977 }
978
move(Shared::XMLNode * script,Shared::XMLNode * current)979 Script::ReturnCode Script::move(Shared::XMLNode *script, Shared::XMLNode *current) {
980 if (current->hasProperty("x"))
981 g_context->_location->_coords.x = getPropAsInt(current, "x");
982 if (current->hasProperty("y"))
983 g_context->_location->_coords.y = getPropAsInt(current, "y");
984 if (current->hasProperty("z"))
985 g_context->_location->_coords.z = getPropAsInt(current, "z");
986
987 if (_debug)
988 debug("Move: x-%d y-%d z-%d", g_context->_location->_coords.x, g_context->_location->_coords.y, g_context->_location->_coords.z);
989
990 gameUpdateScreen();
991 return RET_OK;
992 }
993
sleep(Shared::XMLNode * script,Shared::XMLNode * current)994 Script::ReturnCode Script::sleep(Shared::XMLNode *script, Shared::XMLNode *current) {
995 if (_debug)
996 debug("Sleep!");
997
998 CombatController *cc = new InnController();
999 cc->begin();
1000
1001 return RET_OK;
1002 }
1003
cursor(Shared::XMLNode * script,Shared::XMLNode * current)1004 Script::ReturnCode Script::cursor(Shared::XMLNode *script, Shared::XMLNode *current) {
1005 bool enable = current->getPropertyBool("enable");
1006 if (enable)
1007 g_screen->screenEnableCursor();
1008 else
1009 g_screen->screenDisableCursor();
1010
1011 return RET_OK;
1012 }
1013
pay(Shared::XMLNode * script,Shared::XMLNode * current)1014 Script::ReturnCode Script::pay(Shared::XMLNode *script, Shared::XMLNode *current) {
1015 int price = getPropAsInt(current, "price");
1016 int quant = getPropAsInt(current, "quantity");
1017
1018 Common::String cantpay = getPropAsStr(current, "cantpay");
1019
1020 if (price < 0)
1021 error("Error: could not find price for item");
1022
1023 if (_debug) {
1024 debug("Pay: price(%d) quantity(%d)", price, quant);
1025 debug("\tParty gold: %d -", g_ultima->_saveGame->_gold);
1026 debug("\tTotal price: %d", price * quant);
1027 }
1028
1029 price *= quant;
1030 if (price > g_ultima->_saveGame->_gold) {
1031 if (_debug)
1032 debug("\t=== Can't pay! ===");
1033 run(cantpay);
1034 return RET_STOP;
1035 } else {
1036 g_context->_party->adjustGold(-price);
1037 }
1038
1039 if (_debug)
1040 debug("\tBalance: %d\n", g_ultima->_saveGame->_gold);
1041
1042 return RET_OK;
1043 }
1044
_if(Shared::XMLNode * script,Shared::XMLNode * current)1045 Script::ReturnCode Script::_if(Shared::XMLNode *script, Shared::XMLNode *current) {
1046 Common::String test = getPropAsStr(current, "test");
1047 Script::ReturnCode retval = RET_OK;
1048
1049 if (_debug)
1050 debugN("If(%s) - ", test.c_str());
1051
1052 if (compare(test)) {
1053 if (_debug)
1054 debug("True - Executing '%s'", current->id().c_str());
1055
1056 retval = execute(current);
1057 } else if (_debug)
1058 debug("False");
1059
1060 return retval;
1061 }
1062
input(Shared::XMLNode * script,Shared::XMLNode * current)1063 Script::ReturnCode Script::input(Shared::XMLNode *script, Shared::XMLNode *current) {
1064 Common::String type = getPropAsStr(current, "type");
1065
1066 _currentScript = script;
1067 _currentItem = current;
1068
1069 if (current->hasProperty("target"))
1070 _target = getPropAsStr(current, "target");
1071 else
1072 _target.clear();
1073
1074 _state = STATE_INPUT;
1075 _inputName = "input";
1076
1077 // Does the variable have a maximum length?
1078 if (current->hasProperty("maxlen"))
1079 _inputMaxLen = getPropAsInt(current, "maxlen");
1080 else
1081 _inputMaxLen = Conversation::BUFFERLEN;
1082
1083 // Should we name the variable something other than "input"
1084 if (current->hasProperty("name"))
1085 _inputName = getPropAsStr(current, "name");
1086 else {
1087 if (type == "choice")
1088 _inputName = _idPropName;
1089 }
1090
1091 if (type == "number")
1092 _inputType = INPUT_NUMBER;
1093 else if (type == "keypress")
1094 _inputType = INPUT_KEYPRESS;
1095 else if (type == "choice") {
1096 _inputType = INPUT_CHOICE;
1097 _choices = getPropAsStr(current, "options");
1098 _choices += " \015\033";
1099 } else if (type == "text")
1100 _inputType = INPUT_STRING;
1101 else if (type == "direction")
1102 _inputType = INPUT_DIRECTION;
1103 else if (type == "player")
1104 _inputType = INPUT_PLAYER;
1105
1106 if (_debug)
1107 debug("Input: %s", type.c_str());
1108
1109 /* the script stops here, at least for now */
1110 return RET_STOP;
1111 }
1112
add(Shared::XMLNode * script,Shared::XMLNode * current)1113 Script::ReturnCode Script::add(Shared::XMLNode *script, Shared::XMLNode *current) {
1114 Common::String type = getPropAsStr(current, "type");
1115 Common::String subtype = getPropAsStr(current, "subtype");
1116 int quant = getPropAsInt(_translationContext.back(), "quantity");
1117 if (quant == 0)
1118 quant = getPropAsInt(current, "quantity");
1119 else
1120 quant *= getPropAsInt(current, "quantity");
1121
1122 if (_debug) {
1123 debugN("Add: %s ", type.c_str());
1124 if (!subtype.empty())
1125 debug("- %s ", subtype.c_str());
1126 }
1127
1128 if (type == "gold")
1129 g_context->_party->adjustGold(quant);
1130 else if (type == "food") {
1131 quant *= 100;
1132 g_context->_party->adjustFood(quant);
1133 } else if (type == "horse")
1134 g_context->_party->setTransport(g_tileSets->findTileByName("horse")->getId());
1135 else if (type == "torch") {
1136 AdjustValueMax(g_ultima->_saveGame->_torches, quant, 99);
1137 g_context->_party->notifyOfChange(0, PartyEvent::INVENTORY_ADDED);
1138 } else if (type == "gem") {
1139 AdjustValueMax(g_ultima->_saveGame->_gems, quant, 99);
1140 g_context->_party->notifyOfChange(0, PartyEvent::INVENTORY_ADDED);
1141 } else if (type == "key") {
1142 AdjustValueMax(g_ultima->_saveGame->_keys, quant, 99);
1143 g_context->_party->notifyOfChange(0, PartyEvent::INVENTORY_ADDED);
1144 } else if (type == "sextant") {
1145 AdjustValueMax(g_ultima->_saveGame->_sextants, quant, 99);
1146 g_context->_party->notifyOfChange(0, PartyEvent::INVENTORY_ADDED);
1147 } else if (type == "weapon") {
1148 AdjustValueMax(g_ultima->_saveGame->_weapons[subtype[0] - 'a'], quant, 99);
1149 g_context->_party->notifyOfChange(0, PartyEvent::INVENTORY_ADDED);
1150 } else if (type == "armor") {
1151 AdjustValueMax(g_ultima->_saveGame->_armor[subtype[0] - 'a'], quant, 99);
1152 g_context->_party->notifyOfChange(0, PartyEvent::INVENTORY_ADDED);
1153 } else if (type == "reagent") {
1154 int reagent;
1155 static const Common::String reagents[] = {
1156 "ash", "ginseng", "garlic", "silk", "moss", "pearl", "mandrake", "nightshade", ""
1157 };
1158
1159 for (reagent = 0; reagents[reagent].size(); reagent++) {
1160 if (reagents[reagent] == subtype)
1161 break;
1162 }
1163
1164 if (reagents[reagent].size()) {
1165 AdjustValueMax(g_ultima->_saveGame->_reagents[reagent], quant, 99);
1166 g_context->_party->notifyOfChange(0, PartyEvent::INVENTORY_ADDED);
1167 g_context->_stats->resetReagentsMenu();
1168 } else {
1169 warning("Error: reagent '%s' not found", subtype.c_str());
1170 }
1171 }
1172
1173 if (_debug)
1174 debug("(x%d)", quant);
1175
1176 return RET_OK;
1177 }
1178
lose(Shared::XMLNode * script,Shared::XMLNode * current)1179 Script::ReturnCode Script::lose(Shared::XMLNode *script, Shared::XMLNode *current) {
1180 Common::String type = getPropAsStr(current, "type");
1181 Common::String subtype = getPropAsStr(current, "subtype");
1182 int quant = getPropAsInt(current, "quantity");
1183
1184 if (type == "weapon")
1185 AdjustValueMin(g_ultima->_saveGame->_weapons[subtype[0] - 'a'], -quant, 0);
1186 else if (type == "armor")
1187 AdjustValueMin(g_ultima->_saveGame->_armor[subtype[0] - 'a'], -quant, 0);
1188
1189 if (_debug) {
1190 debugN("Lose: %s ", type.c_str());
1191 if (subtype.size())
1192 debug("- %s ", subtype.c_str());
1193 debug("(x%d)", quant);
1194 }
1195
1196 return RET_OK;
1197 }
1198
heal(Shared::XMLNode * script,Shared::XMLNode * current)1199 Script::ReturnCode Script::heal(Shared::XMLNode *script, Shared::XMLNode *current) {
1200 Common::String type = getPropAsStr(current, "type");
1201 PartyMember *p = g_context->_party->member(getPropAsInt(current, "player") - 1);
1202
1203 if (type == "cure")
1204 p->heal(HT_CURE);
1205 else if (type == "heal")
1206 p->heal(HT_HEAL);
1207 else if (type == "fullheal")
1208 p->heal(HT_FULLHEAL);
1209 else if (type == "resurrect")
1210 p->heal(HT_RESURRECT);
1211
1212 return RET_OK;
1213 }
1214
castSpell(Shared::XMLNode * script,Shared::XMLNode * current)1215 Script::ReturnCode Script::castSpell(Shared::XMLNode *script, Shared::XMLNode *current) {
1216 g_spells->spellEffect('r', -1, SOUND_MAGIC);
1217 if (_debug)
1218 debug("<Spell effect>");
1219
1220 return RET_OK;
1221 }
1222
damage(Shared::XMLNode * script,Shared::XMLNode * current)1223 Script::ReturnCode Script::damage(Shared::XMLNode *script, Shared::XMLNode *current) {
1224 int player = getPropAsInt(current, "player") - 1;
1225 int pts = getPropAsInt(current, "pts");
1226 PartyMember *p;
1227
1228 p = g_context->_party->member(player);
1229 p->applyDamage(pts);
1230
1231 if (_debug)
1232 debug("Damage: %d damage to player %d", pts, player + 1);
1233
1234 return RET_OK;
1235 }
1236
karma(Shared::XMLNode * script,Shared::XMLNode * current)1237 Script::ReturnCode Script::karma(Shared::XMLNode *script, Shared::XMLNode *current) {
1238 Common::String action = getPropAsStr(current, "action");
1239
1240 if (_debug)
1241 debugN("Karma: adjusting - '%s'", action.c_str());
1242
1243 typedef Std::map<Common::String, KarmaAction /*, Std::less<Common::String> */> KarmaActionMap;
1244 static KarmaActionMap action_map;
1245
1246 if (action_map.size() == 0) {
1247 action_map["found_item"] = KA_FOUND_ITEM;
1248 action_map["stole_chest"] = KA_STOLE_CHEST;
1249 action_map["gave_to_beggar"] = KA_GAVE_TO_BEGGAR;
1250 action_map["bragged"] = KA_BRAGGED;
1251 action_map["humble"] = KA_HUMBLE;
1252 action_map["hawkwind"] = KA_HAWKWIND;
1253 action_map["meditation"] = KA_MEDITATION;
1254 action_map["bad_mantra"] = KA_BAD_MANTRA;
1255 action_map["attacked_good"] = KA_ATTACKED_GOOD;
1256 action_map["fled_evil"] = KA_FLED_EVIL;
1257 action_map["fled_good"] = KA_FLED_GOOD;
1258 action_map["healthy_fled_evil"] = KA_HEALTHY_FLED_EVIL;
1259 action_map["killed_evil"] = KA_KILLED_EVIL;
1260 action_map["spared_good"] = KA_SPARED_GOOD;
1261 action_map["gave_blood"] = KA_DONATED_BLOOD;
1262 action_map["didnt_give_blood"] = KA_DIDNT_DONATE_BLOOD;
1263 action_map["cheated_merchant"] = KA_CHEAT_REAGENTS;
1264 action_map["honest_to_merchant"] = KA_DIDNT_CHEAT_REAGENTS;
1265 action_map["used_skull"] = KA_USED_SKULL;
1266 action_map["destroyed_skull"] = KA_DESTROYED_SKULL;
1267 }
1268
1269 KarmaActionMap::iterator ka = action_map.find(action);
1270 if (ka != action_map.end())
1271 g_context->_party->adjustKarma(ka->_value);
1272 else if (_debug)
1273 debug(" <FAILED - action '%s' not found>", action.c_str());
1274
1275 return RET_OK;
1276 }
1277
music(Shared::XMLNode * script,Shared::XMLNode * current)1278 Script::ReturnCode Script::music(Shared::XMLNode *script, Shared::XMLNode *current) {
1279 if (current->getPropertyBool("reset"))
1280 g_music->playMapMusic();
1281 else {
1282 Common::String type = getPropAsStr(current, "type");
1283
1284 if (current->getPropertyBool("play"))
1285 g_music->playMapMusic();
1286 if (current->getPropertyBool("stop"))
1287 g_music->stop();
1288 else if (type == "shopping")
1289 g_music->shopping();
1290 else if (type == "camp")
1291 g_music->camp();
1292 }
1293
1294 return RET_OK;
1295 }
1296
setVar(Shared::XMLNode * script,Shared::XMLNode * current)1297 Script::ReturnCode Script::setVar(Shared::XMLNode *script, Shared::XMLNode *current) {
1298 Common::String name = getPropAsStr(current, "name");
1299 Common::String value = getPropAsStr(current, "value");
1300
1301 if (name.empty()) {
1302 if (_debug)
1303 debug("Variable name empty!");
1304 return RET_STOP;
1305 }
1306
1307 removeCurrentVariable(name);
1308 _variables[name] = new Variable(value);
1309
1310 if (_debug)
1311 debug("Set Variable: %s=%s", name.c_str(), _variables[name]->getString().c_str());
1312
1313 return RET_OK;
1314 }
1315
ztats(Shared::XMLNode * script,Shared::XMLNode * current)1316 Script::ReturnCode Script::ztats(Shared::XMLNode *script, Shared::XMLNode *current) {
1317 typedef Std::map<Common::String, StatsView/*, Std::less<Common::String>*/ > StatsViewMap;
1318 static StatsViewMap view_map;
1319
1320 if (view_map.size() == 0) {
1321 view_map["party"] = STATS_PARTY_OVERVIEW;
1322 view_map["party1"] = STATS_CHAR1;
1323 view_map["party2"] = STATS_CHAR2;
1324 view_map["party3"] = STATS_CHAR3;
1325 view_map["party4"] = STATS_CHAR4;
1326 view_map["party5"] = STATS_CHAR5;
1327 view_map["party6"] = STATS_CHAR6;
1328 view_map["party7"] = STATS_CHAR7;
1329 view_map["party8"] = STATS_CHAR8;
1330 view_map["weapons"] = STATS_WEAPONS;
1331 view_map["armor"] = STATS_ARMOR;
1332 view_map["equipment"] = STATS_EQUIPMENT;
1333 view_map["item"] = STATS_ITEMS;
1334 view_map["reagents"] = STATS_REAGENTS;
1335 view_map["mixtures"] = STATS_MIXTURES;
1336 }
1337
1338 if (current->hasProperty("screen")) {
1339 Common::String screen = getPropAsStr(current, "screen");
1340 StatsViewMap::iterator view;
1341
1342 if (_debug)
1343 debug("Ztats: %s", screen.c_str());
1344
1345 /**
1346 * Find the correct stats view
1347 */
1348 view = view_map.find(screen);
1349 if (view != view_map.end())
1350 g_context->_stats->setView(view->_value); /* change it! */
1351 else if (_debug)
1352 debug(" <FAILED - view could not be found>");
1353 } else {
1354 g_context->_stats->setView(STATS_PARTY_OVERVIEW);
1355 }
1356
1357 return RET_OK;
1358 }
1359
mathParseChildren(Shared::XMLNode * math,Common::String * result)1360 void Script::mathParseChildren(Shared::XMLNode *math, Common::String *result) {
1361 Shared::XMLNode *current;
1362 result->clear();
1363
1364 for (current = math->firstChild(); current; current = current->getNext()) {
1365 if (current->nodeIsText()) {
1366 *result = getContent(current);
1367 } else if (current->id().equalsIgnoreCase("math")) {
1368 Common::String children_results;
1369
1370 mathParseChildren(current, &children_results);
1371 *result = xu4_to_string(mathValue(children_results));
1372 }
1373 }
1374 }
1375
mathParse(const Common::String & str,int * lval,int * rval,Common::String * op)1376 bool Script::mathParse(const Common::String &str, int *lval, int *rval, Common::String *op) {
1377 Common::String left, right;
1378 parseOperation(str, &left, &right, op);
1379
1380 if (op->empty())
1381 return false;
1382
1383 if (left.size() == 0 || right.size() == 0)
1384 return false;
1385
1386 /* make sure that we're dealing with numbers */
1387 if (!Common::isDigit(left[0]) || !Common::isDigit(right[0]))
1388 return false;
1389
1390 *lval = (int)strtol(left.c_str(), nullptr, 10);
1391 *rval = (int)strtol(right.c_str(), nullptr, 10);
1392 return true;
1393 }
1394
parseOperation(const Common::String & str,Common::String * left,Common::String * right,Common::String * op)1395 void Script::parseOperation(const Common::String &str, Common::String *left, Common::String *right, Common::String *op) {
1396 /* list the longest operators first, so they're detected correctly */
1397 static const Common::String ops[] = {"==", ">=", "<=", "+", "-", "*", "/", "%", "=", ">", "<", ""};
1398 int pos = 0,
1399 i = 0;
1400
1401 pos = str.find(ops[i]);
1402 while ((pos <= 0) && !ops[i].empty()) {
1403 i++;
1404 pos = str.find(ops[i]);
1405 }
1406
1407 if (ops[i].empty()) {
1408 op->clear();
1409 return;
1410 } else {
1411 *op = ops[i];
1412 }
1413
1414 *left = str.substr(0, pos);
1415 *right = str.substr(pos + ops[i].size());
1416 }
1417
mathValue(const Common::String & str)1418 int Script::mathValue(const Common::String &str) {
1419 int lval, rval;
1420 Common::String op;
1421
1422 /* something was invalid, just return the integer value */
1423 if (!mathParse(str, &lval, &rval, &op))
1424 return (int)strtol(str.c_str(), nullptr, 10);
1425 else
1426 return math(lval, rval, op);
1427 }
1428
math(int lval,int rval,Common::String & op)1429 int Script::math(int lval, int rval, Common::String &op) {
1430 if (op == "+")
1431 return lval + rval;
1432 else if (op == "-")
1433 return lval - rval;
1434 else if (op == "*")
1435 return lval * rval;
1436 else if (op == "/")
1437 return lval / rval;
1438 else if (op == "%")
1439 return lval % rval;
1440 else if ((op == "=") || (op == "=="))
1441 return lval == rval;
1442 else if (op == ">")
1443 return lval > rval;
1444 else if (op == "<")
1445 return lval < rval;
1446 else if (op == ">=")
1447 return lval >= rval;
1448 else if (op == "<=")
1449 return lval <= rval;
1450 else
1451 error("Error: invalid 'math' operation attempted in vendorScript.xml");
1452
1453 return 0;
1454 }
1455
compare(const Common::String & statement)1456 bool Script::compare(const Common::String &statement) {
1457 Common::String str = statement;
1458 int lval, rval;
1459 Common::String left, right, op;
1460 int and_pos, or_pos;
1461 bool invert = false,
1462 _and = false;
1463
1464 /**
1465 * Handle parsing of complex comparisons
1466 * For example:
1467 *
1468 * true&&true&&true||false
1469 *
1470 * Since this resolves right-to-left, this would evaluate
1471 * similarly to (true && (true && (true || false))), returning
1472 * true.
1473 */
1474 and_pos = str.findFirstOf("&&");
1475 or_pos = str.findFirstOf("||");
1476
1477 if ((and_pos > 0) || (or_pos > 0)) {
1478 bool retfirst, retsecond;
1479 int pos;
1480
1481 if ((or_pos < 0) || ((and_pos > 0) && (and_pos < or_pos)))
1482 _and = true;
1483
1484 if (_and)
1485 pos = and_pos;
1486 else
1487 pos = or_pos;
1488
1489 retsecond = compare(str.substr(pos + 2));
1490 str = str.substr(0, pos);
1491 retfirst = compare(str);
1492
1493 if (_and)
1494 return (retfirst && retsecond);
1495 else
1496 return (retfirst || retsecond);
1497 }
1498
1499 if (str[0] == '!') {
1500 str = str.substr(1);
1501 invert = true;
1502 }
1503
1504 if (str == "true")
1505 return !invert;
1506 else if (str == "false")
1507 return invert;
1508 else if (mathParse(str, &lval, &rval, &op))
1509 return (bool)math(lval, rval, op) ? !invert : invert;
1510 else {
1511 parseOperation(str, &left, &right, &op);
1512 /* can only really do equality comparison */
1513 if ((op[0] == '=') && (left == right))
1514 return !invert;
1515 }
1516 return invert;
1517 }
1518
funcParse(const Common::String & str,Common::String * funcName,Common::String * contents)1519 void Script::funcParse(const Common::String &str, Common::String *funcName, Common::String *contents) {
1520 uint pos;
1521 *funcName = str;
1522
1523 pos = funcName->findFirstOf("(");
1524 if (pos < funcName->size()) {
1525 *funcName = funcName->substr(0, pos);
1526
1527 *contents = str.substr(pos + 1);
1528 pos = contents->findFirstOf(")");
1529 if (pos >= contents->size())
1530 warning("Error: No closing ) in function %s()", funcName->c_str());
1531 else
1532 *contents = contents->substr(0, pos);
1533 } else {
1534 funcName->clear();
1535 }
1536 }
1537
1538 } // End of namespace Ultima4
1539 } // End of namespace Ultima
1540