1 /*
2  * Copyright 2011-2013 Arx Libertatis Team (see the AUTHORS file)
3  *
4  * This file is part of Arx Libertatis.
5  *
6  * Arx Libertatis is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Arx Libertatis is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Arx Libertatis.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "script/ScriptUtils.h"
21 
22 #include <set>
23 
24 #include "game/Entity.h"
25 #include "graphics/data/Mesh.h"
26 
27 using std::string;
28 
29 namespace script {
30 
isWhitespace(char c)31 static inline bool isWhitespace(char c) {
32 	return (((unsigned char)c) <= 32 || c == '(' || c == ')');
33 }
34 
loadUnlocalized(const std::string & str)35 string loadUnlocalized(const std::string & str) {
36 
37 	// if the section name has the qualifying brackets "[]", cut them off
38 	if(!str.empty() && str[0] == '[' && str[str.length() - 1] == ']') {
39 		return str.substr(1, str.length() - 2);
40 	}
41 
42 	return str;
43 }
44 
Context(EERIE_SCRIPT * script,size_t pos,Entity * entity,ScriptMessage msg)45 Context::Context(EERIE_SCRIPT * script, size_t pos, Entity * entity, ScriptMessage msg)
46 	: script(script), pos(pos), entity(entity), message(msg) { }
47 
getStringVar(const string & var) const48 string Context::getStringVar(const string & var) const {
49 	return GetVarValueInterpretedAsText(var, getMaster(), entity);
50 }
51 
52 #define ScriptParserWarning Logger(__FILE__,__LINE__, isSuppressed(*this, "?") ? Logger::Debug : Logger::Warning) << ScriptContextPrefix(*this) << ": "
53 
getCommand(bool skipNewlines)54 std::string Context::getCommand(bool skipNewlines) {
55 
56 	const char * esdat = script->data;
57 
58 	skipWhitespace(skipNewlines);
59 
60 	string word;
61 
62 	// now take chars until it finds a space or unused char
63 	for(; pos != script->size && !isWhitespace(esdat[pos]); pos++) {
64 
65 		char c = esdat[pos];
66 		if(c == '"') {
67 			ScriptParserWarning << "unexpected '\"' in command name";
68 		} else if(c == '~') {
69 			ScriptParserWarning << "unexpected '~' in command name";
70 		} else if(c == '\n') {
71 			break;
72 		} else if(c == '/' && pos + 1 != script->size && esdat[pos + 1] == '/') {
73 			pos = std::find(esdat + pos + 2, esdat + script->size, '\n') - esdat;
74 			if(!word.empty()) {
75 				break;
76 			}
77 			skipWhitespace(skipNewlines), pos--;
78 		} else {
79 			word.push_back(c);
80 		}
81 	}
82 
83 	return word;
84 }
85 
getWord()86 string Context::getWord() {
87 
88 	skipWhitespace();
89 
90 	if(pos >= script->size) {
91 		return string();
92 	}
93 
94 	const char * esdat = script->data;
95 
96 	bool tilde = false; // number of tildes
97 
98 	string word;
99 	string var;
100 
101 	if(pos != script->size && esdat[pos] == '"') {
102 
103 		for(pos++; pos != script->size && esdat[pos] != '"'; pos++) {
104 			if(esdat[pos] == '\n') {
105 				if(tilde) {
106 					ScriptParserWarning << "unmatched '\"' before end of line";
107 				}
108 				return word;
109 			} else if(esdat[pos] == '~') {
110 				if(tilde) {
111 					word += GetVarValueInterpretedAsText(var, getMaster(), getEntity());
112 					var.clear();
113 				}
114 				tilde = !tilde;
115 			} else if(tilde) {
116 				var.push_back(esdat[pos]);
117 			} else {
118 				word.push_back(esdat[pos]);
119 			}
120 		}
121 
122 		if(pos != script->size) {
123 			pos++;
124 		} else {
125 			ScriptParserWarning << "unmatched '\"'";
126 		}
127 
128 	} else {
129 
130 		// now take chars until it finds a space or unused char
131 		for(; pos != script->size && !isWhitespace(esdat[pos]); pos++) {
132 
133 			if(esdat[pos] == '"') {
134 				ScriptParserWarning << "unexpected '\"' inside token";
135 			} else if(esdat[pos] == '~') {
136 				if(tilde) {
137 					word += GetVarValueInterpretedAsText(var, getMaster(), getEntity());
138 					var.clear();
139 				}
140 				tilde = !tilde;
141 			} else if(tilde) {
142 				var.push_back(esdat[pos]);
143 			} else if(esdat[pos] == '/' && pos + 1 != script->size && esdat[pos + 1] == '/') {
144 				pos = std::find(esdat + pos + 2, esdat + script->size, '\n') - esdat;
145 				break;
146 			} else {
147 				word.push_back(esdat[pos]);
148 			}
149 
150 		}
151 
152 	}
153 
154 	if(tilde) {
155 		ScriptParserWarning << "unmatched '~'";
156 	}
157 
158 	return word;
159 }
160 
skipWord()161 void Context::skipWord() {
162 
163 	skipWhitespace();
164 
165 	const char * esdat = script->data;
166 
167 	if(pos != script->size && esdat[pos] == '"') {
168 
169 		for(pos++; pos != script->size && esdat[pos] != '"'; pos++) {
170 			if(esdat[pos] == '\n') {
171 				ScriptParserWarning << "missing '\"' before end of line";
172 				return;
173 			}
174 		}
175 
176 		if(pos != script->size) {
177 			pos++;
178 		} else {
179 			ScriptParserWarning << "unmatched '\"'";
180 		}
181 
182 	} else {
183 
184 		// now take chars until it finds a space or unused char
185 		for(; pos != script->size && !isWhitespace(esdat[pos]); pos++) {
186 			if(esdat[pos] == '"') {
187 				ScriptParserWarning << "unexpected '\"' inside token";
188 			} else if(esdat[pos] == '/' && pos + 1 != script->size && esdat[pos + 1] == '/') {
189 				pos = std::find(esdat + pos + 2, esdat + script->size, '\n') - esdat;
190 				break;
191 			}
192 		}
193 
194 	}
195 }
196 
skipWhitespace(bool skipNewlines)197 void Context::skipWhitespace(bool skipNewlines) {
198 
199 	const char * esdat = script->data;
200 
201 	// First ignores spaces & unused chars
202 	for(; pos != script->size && isWhitespace(esdat[pos]); pos++) {
203 		if(!skipNewlines && esdat[pos] == '\n') {
204 			return;
205 		}
206 	}
207 }
208 
getFlags()209 string Context::getFlags() {
210 
211 	skipWhitespace();
212 
213 	if(pos < script->size && script->data[pos] == '-') {
214 		return getWord();
215 	}
216 
217 	return string();
218 }
219 
getFloat()220 float Context::getFloat() {
221 	return getFloatVar(getWord());
222 }
223 
getBool()224 bool Context::getBool() {
225 
226 	string word = getWord();
227 
228 	return (word == "on" || word == "yes");
229 }
230 
getFloatVar(const std::string & name) const231 float Context::getFloatVar(const std::string & name) const {
232 	return GetVarValueInterpretedAsFloat(name, getMaster(), entity);
233 }
234 
skipCommand()235 size_t Context::skipCommand() {
236 
237 	skipWhitespace();
238 
239 	const char * esdat = script->data;
240 
241 	if(pos == script->size || esdat[pos] == '\n') {
242 		return (size_t)-1;
243 	}
244 
245 	size_t oldpos = pos;
246 
247 	if(esdat[pos] == '/' && pos + 1 != script->size && esdat[pos + 1] == '/') {
248 		oldpos = (size_t)-1;
249 		pos += 2;
250 	}
251 
252 	pos = std::find(esdat + pos, esdat + script->size, '\n') - esdat;
253 
254 	return oldpos;
255 }
256 
jumpToLabel(const string & target,bool substack)257 bool Context::jumpToLabel(const string & target, bool substack) {
258 
259 	if(substack) {
260 		stack.push_back(pos);
261 	}
262 
263 	long targetpos = FindScriptPos(script, ">>" + target);
264 	if(targetpos == -1) {
265 		return false;
266 	}
267 
268 	pos = targetpos + target.length() + 2;
269 	return true;
270 }
271 
returnToCaller()272 bool Context::returnToCaller() {
273 
274 	if(stack.empty()) {
275 		return false;
276 	}
277 
278 	pos = stack.back();
279 	stack.pop_back();
280 	return true;
281 }
282 
skipStatement()283 void Context::skipStatement() {
284 
285 	string word = getCommand();
286 	if(pos == script->size) {
287 		ScriptParserWarning << "missing statement before end of script";
288 		return;
289 	}
290 
291 	if(word == "{") {
292 		long brackets = 1;
293 		while(brackets > 0) {
294 
295 			if(script->data[pos] == '\n') {
296 				pos++;
297 			}
298 			word = getWord(); // TODO should not evaluate ~var~
299 			if(pos == script->size) {
300 				ScriptParserWarning << "missing '}' before end of script";
301 				return;
302 			}
303 
304 			if(word == "{") {
305 				brackets++;
306 			} else if(word == "}") {
307 				brackets--;
308 			}
309 		}
310 	} else {
311 		skipCommand();
312 	}
313 
314 	skipWhitespace(true);
315 	size_t oldpos = pos;
316 	word = getCommand();
317 	if(word != "else") {
318 		pos = oldpos;
319 	}
320 }
321 
322 namespace {
323 
324 typedef std::set<string> SuppressedCommands;
325 typedef std::map<string, SuppressedCommands> SuppressionsForFile;
326 typedef std::map<size_t, SuppressionsForFile> SuppressionsForPos;
327 
328 size_t suppressionCount = 0;
329 SuppressionsForPos suppressions;
330 SuppressionsForPos blockSuppressions;
331 
suppress(const string & script,size_t pos,const string & command)332 void suppress(const string & script, size_t pos, const string & command) {
333 	suppressionCount++;
334 	suppressions[pos][script].insert(command);
335 }
336 
suppressBlockEnd(const string & script,size_t pos,const string & command)337 void suppressBlockEnd(const string & script, size_t pos, const string & command) {
338 	suppressionCount++;
339 	blockSuppressions[pos][script].insert(command);
340 }
341 
contains(const SuppressionsForPos & list,const Context & context,const string & command)342 bool contains(const SuppressionsForPos & list, const Context & context, const string & command) {
343 
344 	SuppressionsForPos::const_iterator i0 = list.find(context.getPosition());
345 	if(i0 == list.end()) {
346 		return false;
347 	}
348 
349 	SuppressionsForFile::const_iterator i1 = i0->second.find(context.getEntity() ? ((context.getScript() == &context.getEntity()->script) ? context.getEntity()->short_name() : context.getEntity()->long_name()) : "unknown");
350 	if(i1 == i0->second.end()) {
351 		return false;
352 	}
353 
354 	return (i1->second.find(command) != i1->second.end());
355 }
356 
357 }
358 
initSuppressions()359 size_t initSuppressions() {
360 
361 	suppressBlockEnd("camera_0027", 1140, "}"); // '}' should be commented out!
362 
363 	suppressBlockEnd("black_thing_0002", 1075, "on"); // missing '}' and accept/refuse
364 
365 	suppressBlockEnd("chest_metal_0103", 626, "on"); // missing '}' and accept/refuse
366 
367 	suppressBlockEnd("chest_metal_0104", 667, "on"); // missing '}' and accept/refuse
368 
369 	suppressBlockEnd("goblin_base_0021", 617, "on"); // missing '}'
370 
371 	suppressBlockEnd("goblin_base_0031", 974, "on"); // missing '}'
372 
373 	suppressBlockEnd("lever_0028", 402, "}"); // extranous '}'
374 
375 	// TODO(broken-scripts)
376 	// TODO move to external file
377 
378 	suppress("akbaa_phase2", 13884, "play"); // missing sound files 'akbaa_strike1' to 'akbaa_strike3', should be 'akbaa_strike'
379 	suppress("akbaa_phase2", 19998, "play"); // sound number is sonetimes too high; missing 'akbaa_hail1', should be 'akbaa_hail'
380 	suppress("akbaa_phase2", 18549, "playanim"); // animation 'grunt' not loaded
381 
382 	suppress("akbaa_tentacle", 2432, "on"); // unknown command 'on' (bad newline!)
383 
384 	suppress("axe_2handed", 26, "settwohanded"); // obsolete command
385 
386 	suppress("black_thing", 3703, "play"); // variable is never set
387 
388 	suppress("camera_0072", 269, "goto"); // missing label 'dialogue7_2'
389 
390 	suppress("camera_0076", 2139, ""); // missing accept/refuse/return/goto/gosub before end of script
391 
392 	suppress("black_thing_0003", 4360, "setevent"); // unsupported event: "eat"
393 	suppress("black_thing_0003", 4388, "setevent"); // unsupported event: "no_more_eat"
394 	suppress("black_thing_0003", 4411, "setevent"); // unsupported event: "no_eat"
395 	suppress("black_thing_0003", 4709, "behvaior"); // unknown command 'behvaior', should be 'behavior'
396 
397 	suppress("chest_metal_0011", 78, "inventory add"); // missing object: "graph/obj3d/interactive/items/magic/dragon_bone_powder/dragon_bone_powder.teo" (should be 'powder_dragon_bone/dragon_bone_powder'?)
398 
399 	suppress("chest_metal_0012", 389, "inventory add"); // missing object: "graph/obj3d/interactive/items/magic/dragon_bone_powder/dragon_bone_powder.teo" (should be 'powder_dragon_bone/dragon_bone_powder'?)
400 
401 	suppress("chest_metal_0020", 54, "inventory add"); // missing object: "graph/obj3d/interactive/items/provisions/candle/candle.teo" (should be 'candle/candel'?)
402 	suppress("chest_metal_0020", 99, "inventory add"); // missing object: "graph/obj3d/interactive/items/provisions/candle/candle.teo" (should be 'candle/candel'?)
403 	suppress("chest_metal_0020", 149, "inventory add"); // missing object: "graph/obj3d/interactive/items/magic/ring_darkaa/ring_darkaa.teo" (should be 'ring_daarka/ring_daarka'?)
404 
405 	suppress("chest_metal_0029", 224, "inventory add"); // missing object: "graph/obj3d/interactive/items/provisions/candle/candle.teo" (should be 'candle/candel'?)
406 	suppress("chest_metal_0029", 317, "inventory add"); // missing object: "graph/obj3d/interactive/items/provisions/candle/candle.teo" (should be 'candle/candel'?)
407 	suppress("chest_metal_0029", 461, "inventory add"); // missing object: "graph/obj3d/interactive/items/provisions/candle/candle.teo" (should be 'candle/candel'?)
408 	suppress("chest_metal_0029", 557, "inventory add"); // missing object: "graph/obj3d/interactive/items/provisions/candle/candle.teo" (should be 'candle/candel'?)
409 	suppress("chest_metal_0029", 650, "inventory add"); // missing object: "graph/obj3d/interactive/items/provisions/candle/candle.teo" (should be 'candle/candel'?)
410 
411 	suppress("chest_metal_0045", 242, "inventory addfromscene"); // bad target ident: "magic\\potion_life\\potion_life"
412 
413 	suppress("chest_metal_0095", 143, "inventory add"); // missing object: "graph/obj3d/interactive/items/armor/legging_leatherac/legging_leatherac.teo" (should be 'legging_leather_ac'?)
414 
415 	suppress("chest_metal_0100", 629, "inventory add"); // missing object: "graph/obj3d/interactive/items/magic/dragon_bone_powder/dragon_bone_powder.teo" (should be 'powder_dragon_bone/dragon_bone_powder'?)
416 	suppress("chest_metal_0100", 693, "inventory add"); // missing object: "graph/obj3d/interactive/items/magic/dragon_bone_powder/dragon_bone_powder.teo" (should be 'powder_dragon_bone/dragon_bone_powder'?)
417 
418 	suppress("chicken_base", 2037, "gosub"); // missing label 'save_behavior'
419 	suppress("chicken_base", 2410, "}"); // missing accept/refuse before end of event block
420 
421 	suppress("corpse_0003", 399, "inventory addfromscene"); // bad target ident: "magic\\potion_life\\potion_life" (should be 'inventory add'?)
422 
423 	suppress("corpse_0006", 172, "inventory add"); // missing object: "graph/obj3d/interactive/items/armor/helmet_leather/helmet_leather.teo"
424 
425 	suppress("corpse_0084", 274, "inventory add"); // missing object: "graph/obj3d/interactive/items/weapons/chest_leather_ac/chest_leather_ac.teo"
426 
427 	suppress("demon", 3571, "loadanim"); // missing animation 'demon_fight_left_start'
428 	suppress("demon", 3634, "loadanim"); // missing animation 'demon_fight_left_cycle'
429 	suppress("demon", 3698, "loadanim"); // missing animation 'demon_fight_left_strike'
430 	suppress("demon", 3762, "loadanim"); // missing animation 'demon_fight_right_start'
431 	suppress("demon", 3826, "loadanim"); // missing animation 'demon_fight_right_cycle'
432 	suppress("demon", 3891, "loadanim"); // missing animation 'demon_fight_right_strike'
433 	suppress("demon", 18479, "play"); // sound number is sometimes too high
434 
435 	suppress("diamond", 139, "play"); // unknown flag -e (ignored) note: fix_inter/diamond_inwall/diamond.asl!
436 
437 	suppress("dog", 19669, "play"); // sound number is sometimes too high
438 
439 	suppress("dog_0011", 31, "playanim"); // animation 'action2' not loaded
440 
441 	suppress("door_orbiplanax_chest", 371, "if"); // unknown operator '==1' (should be '== 1'), interpreted as constant true
442 
443 	suppress("dragon_ice", 9029, "setevent"); // unsupported event 'agression', should be 'aggression'
444 
445 	suppress("dragon_ice_0001", 93, "loadanim"); // missing animation: "dragon_talk_head"
446 	suppress("dragon_ice_0001", 3687, "playanim"); // animation 'action9' not loaded
447 
448 	suppress("dragon's_lair_ice_wall", 41, "satangular"); // unknown command 'satangular', should be setangular
449 
450 	suppress("dwarf_little_crusher_0001", 204, "?"); // 'playanim' only takes one parameter
451 	suppress("dwarf_little_crusher_0001", 228, "?"); // 'playanim' only takes one parameter
452 
453 	suppress("dwarf_little_crusher_0002", 201, "?"); // 'playanim' only takes one parameter
454 	suppress("dwarf_little_crusher_0002", 225, "?"); // 'playanim' only takes one parameter
455 
456 	suppress("dwarf_little_crusher_0003", 113, "?"); // 'playanim' only takes one parameter
457 	suppress("dwarf_little_crusher_0003", 137, "?"); // 'playanim' only takes one parameter
458 
459 	suppress("emerald_inwall", 136, "play"); // unknown flag -e (ignored)
460 
461 	suppress("fake_golden_snake", 185, "setinternalname"); // obsolete command (ignored)
462 
463 	suppress("flour_bag", 41, "collison"); // unknown command 'collison', should be collision
464 
465 	suppress("gem_inwall", 114, "play"); // unknown flag -e (ignored)
466 
467 	suppress("goblin_base", 30010, "goto"); // missing label "main_alert"
468 
469 	suppress("goblin_base_0009", 1455, "setevent"); // unsupported event: combine
470 
471 	suppress("goblin_base_0034", 771, "detach"); // object mug_full_0003 already destroyed
472 
473 	suppress("goblin_base_0041", 3063, "if"); // unknown operator '==1' (should be '== 1'), interpreted as constant true
474 
475 	suppress("goblin_base_0048", 632, "setevent"); // unsupported event: combine
476 
477 	suppress("goblin_base_0046", 2924, "if"); // unknown operator '=>' (should be '>='?), interpreted as constant true
478 
479 	suppress("gold_chunk_inwall", 144, "play"); // unknown flag -e (ignored)
480 
481 	suppress("golden_snake", 156, "setinternalname"); // obsolete command
482 
483 	suppress("hammer_club", 66, "settwohanded"); // obsolete command
484 
485 	suppress("human_base", 5872, "loadanim"); // bad animation id: 'bae_ready', should be 'bare_ready'
486 	suppress("human_base", 13711, "loadanim"); // missing animation "child_get_hit", should be "child_hit"?
487 	suppress("human_base", 13751, "loadanim"); // missing animation "child_get_hit", should be "child_hit"?
488 	suppress("human_base", 39089, "teleport"); // variable dying_marker not set
489 	suppress("human_base", 45586, "goto"); // missing label "main_alert"
490 
491 	suppress("human_base_0006", 83, "playanim"); // animation 'wait' not loaded yet
492 
493 	suppress("human_base_0012", 1519, "goto"); // missing label 'stop'
494 
495 	suppress("human_base_0016", 7142, "setcontrolledzone"); // unknown zone 'calpale_beer', should be 'calpal_beer'?
496 	suppress("human_base_0016", 1270, "inventory addfromscene"); // unknown target 'key_calpale_0003' already taken by player?
497 
498 	suppress("human_base_0022", 10108, "behaviormoveto"); // unknown command 'behaviormoveto', should be 'behavior move_to'
499 
500 	suppress("human_base_0025", 732, "detach"); // object mug_full_0002 already destroyed
501 
502 	suppress("human_base_0041", 4279, "if"); // missing space between oprateor '==' and argument
503 
504 	suppress("human_base_0051", 5396, "/"); // unknown command, should be '//' instead of '/ /'
505 
506 	suppress("human_base_0051", 6083, "set"); // bad variable name: "waiting"
507 
508 	suppress("human_base_0046", 679, "goto"); // missing label 'next_step02', should be 'next_step01'?
509 
510 	suppress("human_base_0079", 239, "inventory add"); // missing object: "graph/obj3d/interactive/items/armor/chest_leatherac/chest_leatherac.teo" (should be 'chest_leather_ac'?)
511 	suppress("human_base_0079", 303, "inventory add"); // missing object: "graph/obj3d/interactive/items/armor/leggings_leatherac/leggings_leatherac.teo" (should be 'legging_leather_ac'?)
512 
513 	suppress("human_base_0082", 24114, "on"); // unknown command 'on' (bad linebreak!)
514 	suppress("human_base_0082", 24141, "hide"); // unknown command 'hide' (bad linebreak!)
515 
516 	suppress("human_base_0085", 426, "loadanim"); // missing animation 'human_noraml_sit_out', should be 'human_normal_sit_out'?
517 
518 	suppress("human_base_0086", 189, "if"); // unknown operator '==1' (should be '== 1')
519 	suppress("human_base_0086", 787, "loadanim"); // missing animation 'human_noraml_sit_out', should be 'human_normal_sit_out'?
520 
521 	suppress("human_base_0095", 722, "setcontrolledzone"); // unknown zone 'maria_shop'
522 
523 	suppress("human_base_0099", 997, "errata"); // unknown command 'errata', should be 'goto errata'
524 
525 	suppress("human_base_0114", 6541, "teleport"); // unknown target 'marker_0327'
526 
527 	suppress("human_base_0118", 101, "collisions"); // unknown command 'collisions', should be 'collision'
528 
529 	suppress("human_base_0119", 179, "collisions"); // unknown command 'collisions', should be 'collision'
530 
531 	suppress("human_base_0120", 101, "collisions"); // unknown command 'collisions', should be 'collision'
532 
533 	suppress("human_base_0121", 135, "collisions"); // unknown command 'collisions', should be 'collision'
534 
535 	suppress("human_base_0122", 350, "collisions"); // unknown command 'collisions', should be 'collision'
536 
537 	suppress("human_base_0135", 939, "detroy"); // unknown command 'detroy', should be 'destroy'
538 
539 	suppress("human_base_0136", 995, "detroy"); // unknown command 'detroy', should be 'destroy'
540 
541 	suppress("human_base_0137", 992, "detroy"); // unknown command 'detroy', should be 'destroy'
542 
543 	suppress("human_base_0138", 2439, "setcontrolledzone"); // unknown zone 'shani_flee'
544 
545 	suppress("human_base_0174", 136, "play"); // missing sound file 'loop_crypt1l', should be 'ambiance/loop_crypt1l'
546 
547 	suppress("jail_wood_grid", 152, "set"); // bad variable name: "material"
548 
549 	suppress("lamp_goblin2_0003", 737, "no"); // unknown command 'no' should be 'nop'?
550 
551 	suppress("lava_event01_0004", 277, "action1"); // unknown command 'action1' (missing space in '200 playanim')
552 
553 	suppress("light_door", 422, "set"); // bad variable name: "durability"
554 
555 	suppress("light_door_0019", 105, "setspeakpitch"); // setspeakpitch only applies to NPCs
556 
557 	suppress("light_door_0020", 230, "setspeakpitch"); // setspeakpitch only applies to NPCs
558 
559 	suppress("light_door_0021", 234, "setspeakpitch"); // setspeakpitch only applies to NPCs
560 
561 	suppress("light_door_0029", 88, "setspeakpitch"); // setspeakpitch only applies to NPCs
562 
563 	suppress("light_door_0030", 162, "setevent"); // unsupported event: "npc_open"
564 	suppress("light_door_0030", 488, "setevent"); // unsupported event: "npc_open"
565 	suppress("light_door_0030", 717, "setevent"); // unsupported event: "npc_open"
566 
567 	suppress("light_door_0100", 69, "setspeakpitch"); // setspeakpitch only applies to NPCs
568 
569 	suppress("light_door_0102", 88, "setspeakpitch"); // setspeakpitch only applies to NPCs
570 
571 	suppress("light_door_0106", 110, "setcontrolledzone"); // unknown zone 'city_entrance'
572 
573 	suppress("light_door_0121", 88, "setspeakpitch"); // setspeakpitch only applies to NPCs
574 
575 	suppress("lockpicks", 462, "play"); // missing sound file 'brokenweapon.wav', should be 'broken_weapon'
576 
577 	suppress("long_sword_recovery", 591, "setequip"); // unknown flag '-s' (ignored)
578 
579 	suppress("marker_0025", 288, "sendevent"); // unknown zone 'cooking' (should be 'cook_gary'?)
580 
581 	suppress("marker_0247", 44, "setcontrolledzone"); // unknown zone 'level3_zone4'
582 
583 	suppress("marker_0811", 536, "worldface"); // unknown command 'worldface', should be 'worldfade'
584 
585 	suppress("metal_chunk_inwall", 143, "play"); // unknown flag -e (ignored)
586 
587 	suppress("metal_grid_0008", 338, "}"); // missing accept/refuse before end of event block
588 
589 	suppress("mithril_chunk_inwall", 144, "play"); // unknown flag -e (ignored)
590 
591 	suppress("morning_glory", 971, "playanim"); // animation 'action1' not loaded
592 
593 	suppress("orb_crypt", 76, "setsteal"); // setsteal only applies to items
594 
595 	suppress("pig", 2409, "}"); // missing accept/refuse before end of event block
596 
597 	suppress("player", 7725, "loadanim"); // bad animation id: "cast_hold"
598 	suppress("player", 8463, "loadanim"); // bad animation id: "lean_left_cycle"
599 	suppress("player", 8531, "loadanim"); // bad animation id: "lean_left_out"
600 	suppress("player", 8666, "loadanim"); // bad animation id: "lean_right_cycle"
601 	suppress("player", 8733, "loadanim"); // bad animation id: "lean_right_out"
602 	suppress("player", 9284, "loadanim"); // missing animation "human_death_cool"
603 	suppress("player", 9558, "loadanim"); // missing animation "human_talk_happyneutral_headonly"
604 	suppress("player", 18044, "play"); // missing sound file 'bell', should be 'arx_bell'
605 
606 	suppress("porticullis_0039", 806, "setevent"); // unsupported event: "custom"
607 
608 	suppress("porticullis_0049", 231, "?"); // missing '}' before end of script
609 	suppress("porticullis_0049", 231, ""); // missing accept / refuse / return before script end
610 
611 	suppress("pressurepad_gob_0029", 74, "goto"); // missing label 'stress'
612 
613 	suppress("public_notice_0011", 965, "magicoff"); // unknown command 'magicoff', should be 'magic off'
614 
615 	suppress("rat_base", 17145, "play"); // sound number is sometimes too high
616 
617 	suppress("rat_base_0059", 62, "behavior"); // unknown behavior 'firendly', should be 'friendly'
618 	suppress("rat_base_0059", 160, "behavior"); // unknown behavior 'firendly', should be 'friendly'
619 
620 	suppress("ratman_base", 22834, "goto"); // missing label "main_alert"
621 
622 	suppress("ratman_base_0024", 608, "goto"); // missing label 'test'
623 
624 	suppress("ratman_base_0026", 712, "setevent"); // unsupported event: "detect_player"
625 
626 	suppress("rock_akbaa", 135, "setinternalname"); // obsolete command 'setinternalname'
627 
628 	suppress("ruby_inwall", 135, "play"); // unknown flag -e (ignored)
629 
630 	suppress("sausagev", 12376, "inventory playeraddfromscene"); // unknown target 'note_0015'
631 
632 	suppress("secret_door_council_2b", 609, "}"); // extraneous '}'
633 
634 	suppress("shiny_orb", 103, "setinternalname"); // obsolete command
635 
636 	suppress("snake_woman_base", 26358, "goto"); // missing label 'main_alert'
637 
638 	suppress("snake_woman_base_0004", 1660, "goto"); // unreachable code after goto
639 
640 	suppress("snake_woman_base_0007", 1138, "goto"); // missing label 'short'
641 
642 	suppress("snake_woman_base_0008", 16149, "goto"); // missing label 'dialogue5_2'
643 
644 	suppress("snake_woman_base_0010", 122, "collions"); // unknown command 'collions', should be 'collision'
645 
646 	suppress("snake_woman_base_0015", 113, "setevent"); // unsupported event: "misc_reflection"
647 
648 	suppress("snake_woman_base_0016", 138, "setevent"); // unsupported event: "misc_reflection"
649 
650 	suppress("spider_base_0024", 660, "play"); // missing sound file 'spider_stress'
651 	suppress("spider_base_0024", 858, "play"); // missing sound file 'spider_stress'
652 
653 	suppress("sword_2handed_meteor_enchant_0001", 48, "}"); // missing accept/refuse before end of event block
654 
655 	suppress("sword_mx", 458, "halo"); // unknown flag -a (ignored)
656 
657 	suppress("sylib", 832, "timer"); // unknown flag -t (ignored)
658 
659 	suppress("timed_lever_0033", 1027, "-smf"); // command wrongly interpreted as event (script parser limitation)
660 
661 	suppress("timed_lever_0052", 648, "-smf"); // command wrongly interpreted as event (script parser limitation)
662 
663 	suppress("torch_rotating_0004", 68, "?"); // 'playanim' only takes one parameter
664 	suppress("torch_rotating_0004", 88, "?"); // 'playanim' only takes one parameter
665 	suppress("torch_rotating_0004", 89, "rotatingtorchdown"); // 'playanim' only takes one parameter
666 
667 	suppress("torch_rotating_0005", 68, "?"); // 'playanim' only takes one parameter
668 	suppress("torch_rotating_0005", 88, "?"); // 'playanim' only takes one parameter
669 	suppress("torch_rotating_0005", 89, "rotatingtorchdown"); // 'playanim' only takes one parameter
670 
671 	suppress("training_dummy", 174, "play"); // missing sound file "wooddoorhit", closest match is "door_wood_hit"
672 
673 	suppress("troll_base", 5107, "loadanim"); // missing animation: "troll_fight_ready_toponly"
674 	suppress("troll_base", 5175, "loadanim"); // missing animation: "troll_fight_unready_toponly"
675 	suppress("troll_base", 19054, "goto"); // missing label "main_alert"
676 
677 	suppress("undead_base_0039", 102, "}"); // missing accept/refuse before end of event block
678 
679 	suppress("undead_base_0046", 110, "playanim"); // animation 'wait' not loaded yet
680 
681 	suppress("wall_breakable", 523, "}"); // missing accept/refuse before end of event block
682 
683 	suppress("wrat_base", 17152, "play"); // sound number is sometimes too high
684 
685 	suppress("y_mx", 3106, "loadanim"); // bad animation id: 'bae_ready', should be 'bare_ready'
686 
687 	class FakeCommand : public Command {
688 
689 	public:
690 
691 		explicit FakeCommand(const string & name) : Command(name) { }
692 
693 		Result execute(Context & context) {
694 			ARX_UNUSED(context);
695 			return Success;
696 		}
697 
698 	};
699 
700 	/* 'playanim' only takes one parameter
701 	 * dwarf_little_crusher_0001:229
702 	 * dwarf_little_crusher_0002:226
703 	 * dwarf_little_crusher_0003:138
704 	 * need to use fake command so other commands on same line get executed!
705 	*/
706 	ScriptEvent::registerCommand(new FakeCommand("dwarflittlecrusherup"));
707 
708 	return suppressionCount;
709 }
710 
isSuppressed(const Context & context,const string & command)711 bool isSuppressed(const Context & context, const string & command) {
712 	return contains(suppressions, context, command);
713 }
714 
isBlockEndSuprressed(const Context & context,const string & command)715 bool isBlockEndSuprressed(const Context & context, const string & command) {
716 	return contains(blockSuppressions, context, command);
717 }
718 
719 }
720