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