1 /**
2  * @file
3  * @brief UFO scripts used in client and server
4  * @note interpreters for: object, inventory, equipment, name and team, damage
5  */
6 
7 /*
8 Copyright (C) 2002-2013 UFO: Alien Invasion.
9 
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23 */
24 
25 
26 #include "scripts.h"
27 #include "../shared/parse.h"
28 #include "../game/inventory.h"
29 #include "../client/cl_screen.h"
30 
31 #define CONSTNAMEINT_HASH_SIZE	32
32 
33 #define	MAX_CONSTNAMEINT_NAME	32
34 
35 /**
36  * @brief Structure to map (script) strings and integer (enum) values
37  */
38 typedef struct com_constNameInt_s {
39 	char name[MAX_CONSTNAMEINT_NAME];	/**< script id */
40 	char *fullname;			/**< only set in case there was a namespace given */
41 	int value;				/**< integer value */
42 	struct com_constNameInt_s *hash_next;	/**< hash next pointer */
43 	struct com_constNameInt_s *next;	/**< linked list next pointer */
44 } com_constNameInt_t;
45 
46 /** @brief Linked list of all the registeres mappings */
47 static com_constNameInt_t *com_constNameInt;
48 /** @brief Hash of all the registeres mappings */
49 static com_constNameInt_t *com_constNameInt_hash[CONSTNAMEINT_HASH_SIZE];
50 
51 /**
52  * @brief Will extract the variable from a string<=>int mapping string which contain a namespace
53  * @param name The name of the script entry to map to an integer
54  * @return The namespace in case one was found, @c nullptr otherwise
55  */
Com_ConstIntGetVariable(const char * name)56 static const char *Com_ConstIntGetVariable (const char *name)
57 {
58 	const char *space = strstr(name, "::");
59 	if (space)
60 		return space + 2;
61 	return name;
62 }
63 
64 /**
65  * @brief Searches whether a given value was registered as a string to int mapping
66  * @param[in] name The name of the string mapping (maybe including a namespace)
67  * @param[out] value The mapped integer if found, not touched if the given string
68  * was found in the registered values.
69  * @return True if the value is found.
70  * @sa Com_RegisterConstInt
71  * @sa Com_ParseValue
72  */
Com_GetConstInt(const char * name,int * value)73 bool Com_GetConstInt (const char *name, int *value)
74 {
75 	com_constNameInt_t *a;
76 	unsigned int hash;
77 	const char *variable;
78 
79 	variable = Com_ConstIntGetVariable(name);
80 
81 	/* if the alias already exists */
82 	hash = Com_HashKey(variable, CONSTNAMEINT_HASH_SIZE);
83 	for (a = com_constNameInt_hash[hash]; a; a = a->hash_next) {
84 		if (Q_streq(variable, a->name)) {
85 			if (!a->fullname || variable == name || Q_streq(a->fullname, name)) {
86 				*value = a->value;
87 				return true;
88 			}
89 		}
90 	}
91 
92 	return false;
93 }
94 
95 /**
96  * @brief Searches whether a given value was registered as a string to int mapping
97  * @param[in] space The namespace of the mapping variable
98  * @param[in] variable The name of the string mapping
99  * @param[out] value The mapped integer if found, not touched if the given string
100  * was found in the registered values.
101  * @return True if the value is found.
102  * @sa Com_RegisterConstInt
103  * @sa Com_ParseValue
104  * @sa Com_GetConstInt
105  */
Com_GetConstIntFromNamespace(const char * space,const char * variable,int * value)106 bool Com_GetConstIntFromNamespace (const char *space, const char *variable, int *value)
107 {
108 	if (Q_strnull(variable))
109 		return false;
110 
111 	if (Q_strnull(space))
112 		return Com_GetConstInt(variable, value);
113 
114 	return Com_GetConstInt(va("%s::%s", space, variable), value);
115 }
116 
117 /**
118  * @brief Searches the mapping variable for a given integer value and a namespace
119  * @param[in] space The namespace to search in - might not be @c nullptr or empty.
120  * @param[in] value The mapped integer
121  * @note only variables with a namespace given are found here
122  * @sa Com_RegisterConstInt
123  * @sa Com_ParseValue
124  */
Com_GetConstVariable(const char * space,int value)125 const char *Com_GetConstVariable (const char *space, int value)
126 {
127 	com_constNameInt_t *a;
128 	const size_t namespaceLength = strlen(space);
129 
130 	a = com_constNameInt;
131 	while (a) {
132 		if (a->value == value && a->fullname) {
133 			if (!strncmp(a->fullname, space, namespaceLength))
134 				return a->name;
135 		}
136 		a = a->next;
137 	}
138 
139 	return nullptr;
140 }
141 
142 /**
143  * @brief Removes a registered constant from the script mapping hash table.
144  * @param name The name of the script entry to remove out of the const int hash. In case this string
145  * is equipped with a namespace, the string is in the form "namespace::variable". If you try to
146  * unregister a variable that was registered with a namespace, this namespace must be included in the
147  * given name, too.
148  * @sa Com_RegisterConstInt
149  * @sa Com_GetConstVariable
150  */
Com_UnregisterConstVariable(const char * name)151 bool Com_UnregisterConstVariable (const char *name)
152 {
153 	com_constNameInt_t *prev = nullptr;
154 
155 	com_constNameInt_t *a = com_constNameInt;
156 	while (a) {
157 		if (!a->fullname || !Q_streq(a->fullname, name)) {
158 			prev = a;
159 			a = a->next;
160 			continue;
161 		}
162 
163 		if (prev)
164 			prev->next = a->next;
165 		else
166 			com_constNameInt = a->next;
167 
168 		prev = nullptr;
169 
170 		const char *variable = Com_ConstIntGetVariable(name);
171 		const unsigned int hash = Com_HashKey(variable, CONSTNAMEINT_HASH_SIZE);
172 		for (com_constNameInt_t *b = com_constNameInt_hash[hash]; b; prev = b, b = b->hash_next) {
173 			if (!b->fullname)
174 				continue;
175 			if (!Q_streq(name, b->fullname))
176 				continue;
177 
178 			if (prev)
179 				prev->hash_next = b->hash_next;
180 			else
181 				com_constNameInt_hash[hash] = com_constNameInt_hash[hash]->hash_next;
182 			break;
183 		}
184 		Mem_Free(a->fullname);
185 		Mem_Free(a);
186 		return true;
187 	}
188 	return false;
189 }
190 
191 /**
192  * @brief Register mappings between script strings and enum values for values of the type @c V_INT
193  * @param name The name of the script entry to map to an integer. This can also include a namespace prefix
194  * for the case we want to map back an integer to a string from a specific namespace. In case this string
195  * is equipped with a namespace, the string is in the form "namespace::variable"
196  * @param value The value to map the given name to
197  * @note You still can't register the same name twice even if you put it into different namespaces (yet). The
198  * namespaces are only for converting an integer back into a string.
199  * @sa Com_GetConstInt
200  * @sa Com_UnregisterConstVariable
201  */
Com_RegisterConstInt(const char * name,int value)202 void Com_RegisterConstInt (const char *name, int value)
203 {
204 	com_constNameInt_t *a;
205 	unsigned int hash;
206 	const char *variable;
207 
208 	variable = Com_ConstIntGetVariable(name);
209 
210 	/* if the alias already exists, reuse it */
211 	hash = Com_HashKey(variable, CONSTNAMEINT_HASH_SIZE);
212 	for (a = com_constNameInt_hash[hash]; a; a = a->hash_next) {
213 		if (a->fullname) {
214 			if (Q_streq(a->fullname, name))
215 				break;
216 		} else if (!strncmp(variable, a->name, sizeof(a->name))) {
217 			break;
218 		}
219 	}
220 
221 	if (a) {
222 		Com_Printf("Com_RegisterConstInt: Const string already defined. '%s = %d' is not set.\n", name, value);
223 		return;
224 	}
225 
226 	a = Mem_PoolAllocType(com_constNameInt_t, com_aliasSysPool);
227 	Q_strncpyz(a->name, variable, sizeof(a->name));
228 	if (!Q_streq(variable, name))
229 		a->fullname = Mem_StrDup(name);
230 	a->next = com_constNameInt;
231 	/* com_constNameInt_hash should be null on the first run */
232 	a->hash_next = com_constNameInt_hash[hash];
233 	com_constNameInt_hash[hash] = a;
234 	com_constNameInt = a;
235 	a->value = value;
236 }
237 
238 /**
239  * @brief Unregisters a list of string aliases
240  * @param[in] constList Array of string => int mappings. Must be terminated
241  * with a nullptr string ({nullptr, -1}) line
242  * @sa constListEntry_t
243  */
Com_UnregisterConstList(const constListEntry_t constList[])244 bool Com_UnregisterConstList (const constListEntry_t constList[])
245 {
246 	int i;
247 	bool state = true;
248 
249 	for (i = 0; constList[i].name != nullptr; i++)
250 		state &= Com_UnregisterConstVariable(constList[i].name);
251 
252 	return state;
253 }
254 
255 /**
256  * @brief Registers a list of string aliases
257  * @param[in] constList Array of string => int mappings. Must be terminated
258  * with a nullptr string ({nullptr, -1}) line
259  * @sa constListEntry_t
260  */
Com_RegisterConstList(const constListEntry_t constList[])261 void Com_RegisterConstList (const constListEntry_t constList[])
262 {
263 	int i;
264 
265 	for (i = 0; constList[i].name != nullptr; i++)
266 		Com_RegisterConstInt(constList[i].name, constList[i].value);
267 }
268 
269 /**
270  * Find name type id by is name
271  * @return id of the name type, else -1 if not found
272  */
Com_FindNameType(const char * nameType)273 static int Com_FindNameType (const char *nameType)
274 {
275 	int i;
276 	for (i = 0; i < NAME_NUM_TYPES; i++) {
277 		if (Q_streq(nameType, name_strings[i])) {
278 			return i;
279 		}
280 	}
281 	return -1;
282 }
283 
284 /**
285  * @brief Parsing function that prints an error message when there is no text in the buffer
286  * @sa Com_Parse
287  */
Com_EParse(const char ** text,const char * errhead,const char * errinfo,char * target,size_t size)288 const char *Com_EParse (const char** text, const char *errhead, const char *errinfo, char *target, size_t size)
289 {
290 	const char *token;
291 
292 	token = Com_Parse(text, target, size);
293 	if (!*text) {
294 		if (errinfo)
295 			Com_Printf("%s \"%s\")\n", errhead, errinfo);
296 		else
297 			Com_Printf("%s\n", errhead);
298 
299 		return nullptr;
300 	}
301 
302 	return token;
303 }
304 
305 static bool versionParsed;
306 
Com_ParseVersion(const char * version)307 static void Com_ParseVersion (const char *version)
308 {
309 	if (!versionParsed) {
310 		if (!Q_streq(version, UFO_VERSION))
311 			Sys_Error("You are mixing versions of the binary (" UFO_VERSION ") and the script (%s) files.", version);
312 	} else {
313 		Sys_Error("More than one version string found in the script files.");
314 	}
315 
316 	versionParsed = true;
317 }
318 
319 /**
320  * @brief possible values for parsing functions
321  * @sa valueTypes_t
322  */
323 const char *const vt_names[] = {
324 	"",
325 	"bool",
326 	"char",
327 	"int",
328 	"int2",
329 	"float",
330 	"pos",
331 	"vector",
332 	"color",
333 	"string",
334 	"translation_string",
335 	"longstring",
336 	"align",
337 	"blend",
338 	"style",
339 	"fade",
340 	"shapes",
341 	"shapeb",
342 	"damage",
343 	"date",
344 	"relabs",
345 	"hunk_string",
346 	"team",
347 	"ufo",
348 	"ufocrashed",
349 	"aircrafttype",
350 	"list"
351 };
352 CASSERT(lengthof(vt_names) == V_NUM_TYPES);
353 
354 const char *const align_names[] = {
355 	"ul", "uc", "ur", "cl", "cc", "cr", "ll", "lc", "lr", "ul_rsl", "uc_rsl", "ur_rsl", "cl_rsl", "cc_rsl", "cr_rsl", "ll_rsl", "lc_rsl", "lr_rsl"
356 };
357 CASSERT(lengthof(align_names) == ALIGN_LAST);
358 
359 const char *const blend_names[] = {
360 	"replace", "one", "blend", "add", "filter", "invfilter"
361 };
362 CASSERT(lengthof(blend_names) == BLEND_LAST);
363 
364 const char *const style_names[] = {
365 	"facing", "rotated", "beam", "line", "axis", "circle"
366 };
367 CASSERT(lengthof(style_names) == STYLE_LAST);
368 
369 const char *const fade_names[] = {
370 	"none", "in", "out", "sin", "saw"
371 };
372 CASSERT(lengthof(fade_names) == FADE_LAST);
373 
374 /** @brief target sizes for buffer */
375 static const size_t vt_sizes[] = {
376 	0,	/* V_NULL */
377 	sizeof(bool),	/* V_BOOL */
378 	sizeof(char),	/* V_CHAR */
379 	sizeof(int),	/* V_INT */
380 	2 * sizeof(int),	/* V_INT2 */
381 	sizeof(float),	/* V_FLOAT */
382 	sizeof(vec2_t),	/* V_POS */
383 	sizeof(vec3_t),	/* V_VECTOR */
384 	sizeof(vec4_t),	/* V_COLOR */
385 	0,	/* V_STRING */
386 	0,	/* V_TRANSLATION_STRING */
387 	0,	/* V_LONGSTRING */
388 	sizeof(align_t),	/* V_ALIGN */
389 	sizeof(blend_t),	/* V_BLEND */
390 	sizeof(style_t),	/* V_STYLE */
391 	sizeof(fade_t),	/* V_FADE */
392 	sizeof(int),	/* V_SHAPE_SMALL */
393 	0,	/* V_SHAPE_BIG */
394 	sizeof(byte),	/* V_DAMAGE */
395 	0,	/* V_DATE */
396 	sizeof(float),	/* V_RELABS */
397 	0,	/* V_HUNK_STRING */
398 	sizeof(int),		/* V_TEAM */
399 	sizeof(ufoType_t),	/* V_UFO */
400 	sizeof(ufoType_t),	/* V_UFOCRASHED */
401 	sizeof(humanAircraftType_t),		/* V_AIRCRAFTTYPE */
402 	0					/* V_LIST */
403 };
404 CASSERT(lengthof(vt_sizes) == V_NUM_TYPES);
405 
406 /** @brief natural align for each targets */
407 static const size_t vt_aligns[] = {
408 	0,	/* V_NULL */
409 	sizeof(bool),	/* V_BOOL */
410 	sizeof(char),	/* V_CHAR */
411 	sizeof(int),	/* V_INT */
412 	sizeof(int),	/* V_INT2 */
413 	sizeof(float),	/* V_FLOAT */
414 	sizeof(vec_t),	/* V_POS */
415 	sizeof(vec_t),	/* V_VECTOR */
416 	sizeof(vec_t),	/* V_COLOR */
417 	sizeof(char),	/* V_STRING */
418 	sizeof(char),	/* V_TRANSLATION_STRING */
419 	sizeof(char),	/* V_LONGSTRING */
420 	sizeof(align_t),/* V_ALIGN */
421 	sizeof(blend_t),/* V_BLEND */
422 	sizeof(style_t),/* V_STYLE */
423 	sizeof(fade_t),	/* V_FADE */
424 	sizeof(int),	/* V_SHAPE_SMALL */
425 	sizeof(uint32_t),	/* V_SHAPE_BIG */
426 	sizeof(byte),	/* V_DAMAGE */
427 	sizeof(date_t),	/* V_DATE */
428 	sizeof(float),	/* V_RELABS */
429 	sizeof(char),	/* V_HUNK_STRING */
430 	sizeof(int),		/* V_TEAM */
431 	sizeof(ufoType_t),	/* V_UFO */
432 	sizeof(ufoType_t),	/* V_UFOCRASHED */
433 	sizeof(humanAircraftType_t),		/* V_AIRCRAFTTYPE */
434 	sizeof(void*)
435 };
436 CASSERT(lengthof(vt_aligns) == V_NUM_TYPES);
437 
438 static char parseErrorMessage[256];
439 
440 /**
441  * Returns the last error message
442  * @return string that contains the last error message
443  */
Com_GetLastParseError(void)444 const char *Com_GetLastParseError (void)
445 {
446 	return parseErrorMessage;
447 }
448 
449 /**
450  * @brief Align a memory to use a natural address for the data type we will write
451  * @note it speed up data read, and fix crash on PPC processors
452  */
Com_AlignPtr(const void * memory,valueTypes_t type)453 void *Com_AlignPtr (const void *memory, valueTypes_t type)
454 {
455 	const size_t align = vt_aligns[type];
456 	assert(memory != nullptr);
457 	if (align == V_NULL)
458 		Sys_Error("Com_AlignPtr: can't align V_NULL");
459 	if (type >= V_NUM_TYPES)
460 		Sys_Error("Com_AlignPtr: type hit V_NUM_TYPES");
461 	return ALIGN_PTR(memory, align);
462 }
463 
464 /**
465  * @brief Parse a value from a string
466  * @param[in] base The start pointer to a given data type (typedef, struct) where the parsed data is stored
467  * @param[in] token The data which should be parsed
468  * @param[in] type The data type that should be parsed
469  * @param[in] ofs The offset for the value
470  * @param[in] size The expected size of the data type. If 0, no checks are done
471  * @param[out] writtenBytes
472  * @return A resultStatus_t value
473  * @note instead of , this function separate error message and write byte result
474  * @todo This function has much in common with Com_SetValue. Refactor them !
475  */
Com_ParseValue(void * base,const char * token,valueTypes_t type,int ofs,size_t size,size_t * writtenBytes)476 resultStatus_t Com_ParseValue (void *base, const char *token, valueTypes_t type, int ofs, size_t size, size_t *writtenBytes)
477 {
478 	byte *b;
479 	int x, y, w, h;
480 	byte num;
481 	resultStatus_t status = RESULT_OK;
482 	b = (byte *) base + ofs;
483 	*writtenBytes = 0;
484 
485 #ifdef DEBUG
486 	if (b != Com_AlignPtr(b, type))
487 		Com_Printf("Wrong alignment: %p %p type:%d size:" UFO_SIZE_T "\n", b, Com_AlignPtr(b, type), type, vt_aligns[type]);
488 #endif
489 
490 	if (size) {
491 #ifdef DEBUG
492 		if (size > vt_sizes[type]) {
493 			snprintf(parseErrorMessage, sizeof(parseErrorMessage), "Size mismatch: given size: " UFO_SIZE_T ", should be: " UFO_SIZE_T " (type: %i). ", size, vt_sizes[type], type);
494 			status = RESULT_WARNING;
495 		}
496 #endif
497 		if (size < vt_sizes[type]) {
498 			snprintf(parseErrorMessage, sizeof(parseErrorMessage), "Size mismatch: given size: " UFO_SIZE_T ", should be: " UFO_SIZE_T ". (type: %i)", size, vt_sizes[type], type);
499 			return RESULT_ERROR;
500 		}
501 	}
502 
503 	switch (type) {
504 	case V_HUNK_STRING:
505 		snprintf(parseErrorMessage, sizeof(parseErrorMessage), "V_HUNK_STRING is not parsed here");
506 		return RESULT_ERROR;
507 
508 	case V_NULL:
509 		*writtenBytes = 0;
510 		break;
511 
512 	case V_BOOL:
513 		if (Q_streq(token, "true") || *token == '1')
514 			*(bool *)b = true;
515 		else if (Q_streq(token, "false") || *token == '0')
516 			*(bool *)b = false;
517 		else {
518 			snprintf(parseErrorMessage, sizeof(parseErrorMessage), "Illegal bool statement '%s'", token);
519 			return RESULT_ERROR;
520 		}
521 		*writtenBytes = sizeof(bool);
522 		break;
523 
524 	case V_CHAR:
525 		if (token[0] == '\0') {
526 			snprintf(parseErrorMessage, sizeof(parseErrorMessage), "Char expected, but end of string found");
527 			return RESULT_ERROR;
528 		}
529 		if (token[1] != '\0') {
530 			snprintf(parseErrorMessage, sizeof(parseErrorMessage), "Illegal end of string. '\\0' explected but 0x%x found", token[1]);
531 			return RESULT_ERROR;
532 		}
533 		*(char *) b = token[0];
534 		*writtenBytes = sizeof(char);
535 		break;
536 
537 	case V_TEAM:
538 		if (Q_streq(token, "civilian"))
539 			*(int *) b = TEAM_CIVILIAN;
540 		else if (Q_streq(token, "phalanx"))
541 			*(int *) b = TEAM_PHALANX;
542 		else if (Q_streq(token, "alien"))
543 			*(int *) b = TEAM_ALIEN;
544 		else
545 			Sys_Error("Unknown team string: '%s' found in script files", token);
546 		*writtenBytes = sizeof(int);
547 		break;
548 
549 	case V_AIRCRAFTTYPE:
550 		if (Q_streq(token, "craft_drop_firebird"))
551 			*(humanAircraftType_t *) b = DROPSHIP_FIREBIRD;
552 		else if (Q_streq(token, "craft_drop_herakles"))
553 			*(humanAircraftType_t *) b = DROPSHIP_HERAKLES;
554 		else if (Q_streq(token, "craft_drop_raptor"))
555 			*(humanAircraftType_t *) b = DROPSHIP_RAPTOR;
556 		else if (Q_streq(token, "craft_inter_stiletto"))
557 			*(humanAircraftType_t *) b = INTERCEPTOR_STILETTO;
558 		else if (Q_streq(token, "craft_inter_saracen"))
559 			*(humanAircraftType_t *) b = INTERCEPTOR_SARACEN;
560 		else if (Q_streq(token, "craft_inter_dragon"))
561 			*(humanAircraftType_t *) b = INTERCEPTOR_DRAGON;
562 		else if (Q_streq(token, "craft_inter_starchaser"))
563 			*(humanAircraftType_t *) b = INTERCEPTOR_STARCHASER;
564 		else if (Q_streq(token, "craft_inter_stingray"))
565 			*(humanAircraftType_t *) b = INTERCEPTOR_STINGRAY;
566 		else
567 			Sys_Error("Unknown aircrafttype type: '%s'", token);
568 		*writtenBytes = sizeof(humanAircraftType_t);
569 		break;
570 
571 	case V_UFO:
572 		if (Q_streq(token, "craft_ufo_bomber"))
573 			*(ufoType_t *) b = UFO_BOMBER;
574 		else if (Q_streq(token, "craft_ufo_carrier"))
575 			*(ufoType_t *) b = UFO_CARRIER;
576 		else if (Q_streq(token, "craft_ufo_corrupter"))
577 			*(ufoType_t *) b = UFO_CORRUPTER;
578 		else if (Q_streq(token, "craft_ufo_fighter"))
579 			*(ufoType_t *) b = UFO_FIGHTER;
580 		else if (Q_streq(token, "craft_ufo_harvester"))
581 			*(ufoType_t *) b = UFO_HARVESTER;
582 		else if (Q_streq(token, "craft_ufo_scout"))
583 			*(ufoType_t *) b = UFO_SCOUT;
584 		else if (Q_streq(token, "craft_ufo_supply"))
585 			*(ufoType_t *) b = UFO_SUPPLY;
586 		else if (Q_streq(token, "craft_ufo_gunboat"))
587 			*(ufoType_t *) b = UFO_GUNBOAT;
588 		else if (Q_streq(token, "craft_ufo_ripper"))
589 			*(ufoType_t *) b = UFO_RIPPER;
590 		else if (Q_streq(token, "craft_ufo_mothership"))
591 			*(ufoType_t *) b = UFO_MOTHERSHIP;
592 		else
593 			Sys_Error("Unknown ufo type: '%s'", token);
594 		*writtenBytes = sizeof(ufoType_t);
595 		break;
596 
597 	case V_UFOCRASHED:
598 		if (Q_streq(token, "craft_crash_bomber"))
599 			*(ufoType_t *) b = UFO_BOMBER;
600 		else if (Q_streq(token, "craft_crash_carrier"))
601 			*(ufoType_t *) b = UFO_CARRIER;
602 		else if (Q_streq(token, "craft_crash_corrupter"))
603 			*(ufoType_t *) b = UFO_CORRUPTER;
604 		else if (Q_streq(token, "craft_crash_fighter"))
605 			*(ufoType_t *) b = UFO_FIGHTER;
606 		else if (Q_streq(token, "craft_crash_harvester"))
607 			*(ufoType_t *) b = UFO_HARVESTER;
608 		else if (Q_streq(token, "craft_crash_scout"))
609 			*(ufoType_t *) b = UFO_SCOUT;
610 		else if (Q_streq(token, "craft_crash_supply"))
611 			*(ufoType_t *) b = UFO_SUPPLY;
612 		else if (Q_streq(token, "craft_crash_gunboat"))
613 			*(ufoType_t *) b = UFO_GUNBOAT;
614 		else if (Q_streq(token, "craft_crash_ripper"))
615 			*(ufoType_t *) b = UFO_RIPPER;
616 		else if (Q_streq(token, "craft_crash_mothership"))
617 			*(ufoType_t *) b = UFO_MOTHERSHIP;
618 		else
619 			Sys_Error("Unknown ufo type: '%s'", token);
620 		*writtenBytes = sizeof(ufoType_t);
621 		break;
622 
623 	case V_INT:
624 		if (sscanf(token, "%i", &((int *) b)[0]) != 1) {
625 			if (!Com_GetConstInt(token, &((int *) b)[0])) {
626 				snprintf(parseErrorMessage, sizeof(parseErrorMessage), "Illegal int statement '%s'", token);
627 				return RESULT_ERROR;
628 			}
629 		}
630 		*writtenBytes = sizeof(int);
631 		break;
632 
633 	case V_INT2:
634 		if (sscanf(token, "%i %i", &((int *) b)[0], &((int *) b)[1]) != 2) {
635 			snprintf(parseErrorMessage, sizeof(parseErrorMessage), "Illegal int2 statement '%s'", token);
636 			return RESULT_ERROR;
637 		}
638 		*writtenBytes = 2 * sizeof(int);
639 		break;
640 
641 	case V_FLOAT:
642 		if (sscanf(token, "%f", &((float *) b)[0]) != 1) {
643 			snprintf(parseErrorMessage, sizeof(parseErrorMessage), "Illegal float statement '%s'", token);
644 			return RESULT_ERROR;
645 		}
646 		*writtenBytes = sizeof(float);
647 		break;
648 
649 	case V_POS:
650 		if (sscanf(token, "%f %f", &((float *) b)[0], &((float *) b)[1]) != 2) {
651 			snprintf(parseErrorMessage, sizeof(parseErrorMessage), "Illegal pos statement '%s'", token);
652 			return RESULT_ERROR;
653 		}
654 		*writtenBytes = 2 * sizeof(float);
655 		break;
656 
657 	case V_VECTOR:
658 		if (sscanf(token, "%f %f %f", &((float *) b)[0], &((float *) b)[1], &((float *) b)[2]) != 3) {
659 			snprintf(parseErrorMessage, sizeof(parseErrorMessage), "Illegal vector statement '%s'", token);
660 			return RESULT_ERROR;
661 		}
662 		*writtenBytes = 3 * sizeof(float);
663 		break;
664 
665 	case V_COLOR:
666 		{
667 			float* f = (float *) b;
668 			if (sscanf(token, "%f %f %f %f", &f[0], &f[1], &f[2], &f[3]) != 4) {
669 				snprintf(parseErrorMessage, sizeof(parseErrorMessage), "Illegal color statement '%s'", token);
670 				return RESULT_ERROR;
671 			}
672 			*writtenBytes = 4 * sizeof(float);
673 		}
674 		break;
675 
676 	case V_STRING:
677 		Q_strncpyz((char *) b, token, MAX_VAR);
678 		w = (int)strlen(token) + 1;
679 		*writtenBytes = w;
680 		break;
681 
682 	/* just remove the _ but don't translate */
683 	case V_TRANSLATION_STRING:
684 		if (*token == '_')
685 			token++;
686 
687 		Q_strncpyz((char *) b, token, MAX_VAR);
688 		w = (int)strlen((char *) b) + 1;
689 		*writtenBytes = w;
690 		break;
691 
692 	case V_LONGSTRING:
693 		strcpy((char *) b, token);
694 		w = (int)strlen(token) + 1;
695 		*writtenBytes = w;
696 		break;
697 
698 	case V_ALIGN:
699 		for (num = 0; num < ALIGN_LAST; num++)
700 			if (Q_streq(token, align_names[num]))
701 				break;
702 		if (num == ALIGN_LAST) {
703 			snprintf(parseErrorMessage, sizeof(parseErrorMessage), "Illegal align token '%s'", token);
704 			return RESULT_ERROR;
705 		}
706 		*(align_t *)b = (align_t)num;
707 		*writtenBytes = sizeof(align_t);
708 		break;
709 
710 	case V_BLEND:
711 		for (num = 0; num < BLEND_LAST; num++)
712 			if (Q_streq(token, blend_names[num]))
713 				break;
714 		if (num == BLEND_LAST) {
715 			snprintf(parseErrorMessage, sizeof(parseErrorMessage), "Illegal blend token '%s'", token);
716 			return RESULT_ERROR;
717 		}
718 		*(blend_t *)b = (blend_t)num;
719 		*writtenBytes = sizeof(blend_t);
720 		break;
721 
722 	case V_STYLE:
723 		for (num = 0; num < STYLE_LAST; num++)
724 			if (Q_streq(token, style_names[num]))
725 				break;
726 		if (num == STYLE_LAST) {
727 			snprintf(parseErrorMessage, sizeof(parseErrorMessage), "Illegal style token '%s'", token);
728 			return RESULT_ERROR;
729 		}
730 		*(style_t *)b = (style_t)num;
731 		*writtenBytes = sizeof(style_t);
732 		break;
733 
734 	case V_FADE:
735 		for (num = 0; num < FADE_LAST; num++)
736 			if (Q_streq(token, fade_names[num]))
737 				break;
738 		if (num == FADE_LAST) {
739 			snprintf(parseErrorMessage, sizeof(parseErrorMessage), "Illegal fade token '%s'", token);
740 			return RESULT_ERROR;
741 		}
742 		*(fade_t *)b = (fade_t)num;
743 		*writtenBytes = sizeof(fade_t);
744 		break;
745 
746 	case V_SHAPE_SMALL:
747 		if (sscanf(token, "%i %i %i %i", &x, &y, &w, &h) != 4) {
748 			snprintf(parseErrorMessage, sizeof(parseErrorMessage), "Illegal shape small statement '%s'", token);
749 			return RESULT_ERROR;
750 		}
751 
752 		if (y + h > SHAPE_SMALL_MAX_HEIGHT || y >= SHAPE_SMALL_MAX_HEIGHT || h > SHAPE_SMALL_MAX_HEIGHT) {
753 			snprintf(parseErrorMessage, sizeof(parseErrorMessage), "illegal shape small statement - max h value is %i (y: %i, h: %i)", SHAPE_SMALL_MAX_HEIGHT, y, h);
754 			return RESULT_ERROR;
755 		}
756 		if (x + w > SHAPE_SMALL_MAX_WIDTH || x >= SHAPE_SMALL_MAX_WIDTH || w > SHAPE_SMALL_MAX_WIDTH) {
757 			snprintf(parseErrorMessage, sizeof(parseErrorMessage), "illegal shape small statement - max x and w values are %i", SHAPE_SMALL_MAX_WIDTH);
758 			return RESULT_ERROR;
759 		}
760 		for (h += y; y < h; y++)
761 			*(uint32_t *) b |= ((1 << w) - 1) << x << (y * SHAPE_SMALL_MAX_WIDTH);
762 		*writtenBytes = SHAPE_SMALL_MAX_HEIGHT;
763 		break;
764 
765 	case V_SHAPE_BIG:
766 		if (sscanf(token, "%i %i %i %i", &x, &y, &w, &h) != 4) {
767 			snprintf(parseErrorMessage, sizeof(parseErrorMessage), "Illegal shape big statement '%s'", token);
768 			return RESULT_ERROR;
769 		}
770 		if (y + h > SHAPE_BIG_MAX_HEIGHT || y >= SHAPE_BIG_MAX_HEIGHT || h > SHAPE_BIG_MAX_HEIGHT) {
771 			snprintf(parseErrorMessage, sizeof(parseErrorMessage), "Illegal shape big statement, max height is %i", SHAPE_BIG_MAX_HEIGHT);
772 			return RESULT_ERROR;
773 		}
774 		if (x + w > SHAPE_BIG_MAX_WIDTH || x >= SHAPE_BIG_MAX_WIDTH || w > SHAPE_BIG_MAX_WIDTH) {
775 			snprintf(parseErrorMessage, sizeof(parseErrorMessage), "illegal shape big statement - max x and w values are %i ('%s')", SHAPE_BIG_MAX_WIDTH, token);
776 			return RESULT_ERROR;
777 		}
778 		w = ((1 << w) - 1) << x;
779 		for (h += y; y < h; y++)
780 			((uint32_t *) b)[y] |= w;
781 		*writtenBytes = SHAPE_BIG_MAX_HEIGHT * SHAPE_SMALL_MAX_HEIGHT;
782 		break;
783 
784 	case V_DAMAGE:
785 		for (num = 0; num < csi.numDTs; num++)
786 			if (Q_streq(token, csi.dts[num].id))
787 				break;
788 		if (num == csi.numDTs)
789 			*b = 0;
790 		else
791 			*b = num;
792 		*writtenBytes = sizeof(byte);
793 		break;
794 
795 	case V_DATE:
796 		if (sscanf(token, "%i %i %i", &x, &y, &w) != 3) {
797 			snprintf(parseErrorMessage, sizeof(parseErrorMessage), "Illegal if statement '%s'", token);
798 			return RESULT_ERROR;
799 		}
800 
801 		((date_t *) b)->day = DAYS_PER_YEAR * x + y;
802 		((date_t *) b)->sec = SECONDS_PER_HOUR * w;
803 		*writtenBytes = sizeof(date_t);
804 		break;
805 
806 	case V_RELABS:
807 		if (token[0] == '-' || token[0] == '+') {
808 			if (fabs(atof(token + 1)) <= 2.0f) {
809 				snprintf(parseErrorMessage, sizeof(parseErrorMessage), "a V_RELABS (absolute) value should always be bigger than +/-2.0");
810 				status = RESULT_WARNING;
811 			}
812 			if (token[0] == '-')
813 				*(float *) b = atof(token + 1) * (-1);
814 			else
815 				*(float *) b = atof(token + 1);
816 		} else {
817 			if (fabs(atof(token)) > 2.0f) {
818 				snprintf(parseErrorMessage, sizeof(parseErrorMessage), "a V_RELABS (relative) value should only be between 0.00..1 and 2.0");
819 				status = RESULT_WARNING;
820 			}
821 			*(float *) b = atof(token);
822 		}
823 		*writtenBytes = sizeof(float);
824 		break;
825 
826 	default:
827 		snprintf(parseErrorMessage, sizeof(parseErrorMessage), "unknown value type '%s'", token);
828 		return RESULT_ERROR;
829 	}
830 	return status;
831 }
832 
833 /**
834  * @note translatable string are marked with _ at the beginning
835  * @code menu exampleName
836  * {
837  *   string "_this is translatable"
838  * }
839  * @endcode
840  */
Com_EParseValue(void * base,const char * token,valueTypes_t type,int ofs,size_t size)841 int Com_EParseValue (void *base, const char *token, valueTypes_t type, int ofs, size_t size)
842 {
843 	size_t writtenBytes;
844 	const resultStatus_t result = Com_ParseValue(base, token, type, ofs, size, &writtenBytes);
845 	switch (result) {
846 	case RESULT_ERROR:
847 		Sys_Error("Com_EParseValue: %s\n", parseErrorMessage);
848 		break;
849 	case RESULT_WARNING:
850 		Com_Printf("Com_EParseValue: %s\n", parseErrorMessage);
851 		break;
852 	case RESULT_OK:
853 		break;
854 	}
855 	return writtenBytes;
856 }
857 
858 /**
859  * @brief Parses a boolean from a string
860  * @param token The token to convert into a boolean
861  * @return @c false if the string could not get parsed
862  */
Com_ParseBoolean(const char * token)863 bool Com_ParseBoolean (const char *token)
864 {
865 	bool b;
866 	size_t writtenBytes;
867 	if (Com_ParseValue(&b, token, V_BOOL, 0, sizeof(b), &writtenBytes) != RESULT_ERROR) {
868 		assert(writtenBytes == sizeof(b));
869 		return b;
870 	}
871 	return false;
872 }
873 
874 /**
875  * @param[in] base The start pointer to a given data type (typedef, struct)
876  * @param[in] set The data which should be parsed
877  * @param[in] type The data type that should be parsed
878  * @param[in] ofs The offset for the value
879  * @param[in] size The expected size of the data type. If 0, no checks are done
880  * @sa Com_ValueToStr
881  * @note The offset is most likely given by the offsetof macro
882  */
883 #ifdef DEBUG
Com_SetValueDebug(void * base,const void * set,valueTypes_t type,int ofs,size_t size,const char * file,int line)884 int Com_SetValueDebug (void *base, const void *set, valueTypes_t type, int ofs, size_t size, const char *file, int line)
885 #else
886 int Com_SetValue (void *base, const void *set, valueTypes_t type, int ofs, size_t size)
887 #endif
888 {
889 	byte *b;
890 	int len;
891 
892 	b = (byte *) base + ofs;
893 
894 	if (size) {
895 #ifdef DEBUG
896 		if (size > vt_sizes[type])
897 			Com_Printf("Warning: Size mismatch: given size: " UFO_SIZE_T ", should be: " UFO_SIZE_T ". File: '%s', line: %i (type: %i)\n", size, vt_sizes[type], file, line, type);
898 
899 		if (size < vt_sizes[type])
900 			Sys_Error("Size mismatch: given size: " UFO_SIZE_T ", should be: " UFO_SIZE_T ". File: '%s', line: %i (type: %i)", size, vt_sizes[type], file, line, type);
901 #else
902 		if (size < vt_sizes[type])
903 			Sys_Error("Size mismatch: given size: " UFO_SIZE_T ", should be: " UFO_SIZE_T ". (type: %i)", size, vt_sizes[type], type);
904 #endif
905 	}
906 
907 #ifdef DEBUG
908 	if (b != Com_AlignPtr(b, type)) {
909 		Com_Printf("Wrong alignment: %p %p type:%d size:" UFO_SIZE_T " - this code will CRASH on ARM CPU\n", b, Com_AlignPtr(b, type), type, vt_aligns[type]);
910 		Sys_Backtrace();
911 	}
912 #endif
913 
914 	switch (type) {
915 	case V_NULL:
916 		return 0;
917 
918 	case V_BOOL:
919 		if (*(const bool *) set)
920 			*(bool *)b = true;
921 		else
922 			*(bool *)b = false;
923 		return sizeof(bool);
924 
925 	case V_CHAR:
926 		*(char *) b = *(const char *) set;
927 		return sizeof(char);
928 
929 	case V_TEAM:
930 		if (Q_streq((const char *)set, "civilian"))
931 			*(int *) b = TEAM_CIVILIAN;
932 		else if (Q_streq((const char *)set, "phalanx"))
933 			*(int *) b = TEAM_PHALANX;
934 		else if (Q_streq((const char *)set, "alien"))
935 			*(int *) b = TEAM_ALIEN;
936 		else
937 			Sys_Error("Unknown team given: '%s'", (const char *)set);
938 		return sizeof(int);
939 
940 	case V_AIRCRAFTTYPE:
941 		if (Q_streq((const char *)set, "craft_drop_firebird"))
942 			*(humanAircraftType_t *) b = DROPSHIP_FIREBIRD;
943 		else if (Q_streq((const char *)set, "craft_drop_herakles"))
944 			*(humanAircraftType_t *) b = DROPSHIP_HERAKLES;
945 		else if (Q_streq((const char *)set, "craft_drop_raptor"))
946 			*(humanAircraftType_t *) b = DROPSHIP_RAPTOR;
947 		else if (Q_streq((const char *)set, "craft_inter_stiletto"))
948 			*(humanAircraftType_t *) b = INTERCEPTOR_STILETTO;
949 		else if (Q_streq((const char *)set, "craft_inter_saracen"))
950 			*(humanAircraftType_t *) b = INTERCEPTOR_SARACEN;
951 		else if (Q_streq((const char *)set, "craft_inter_dragon"))
952 			*(humanAircraftType_t *) b = INTERCEPTOR_DRAGON;
953 		else if (Q_streq((const char *)set, "craft_inter_starchaser"))
954 			*(humanAircraftType_t *) b = INTERCEPTOR_STARCHASER;
955 		else if (Q_streq((const char *)set, "craft_inter_stingray"))
956 			*(humanAircraftType_t *) b = INTERCEPTOR_STINGRAY;
957 		else
958 			Sys_Error("Unknown aircrafttype type: '%s'", (const char *)set);
959 		return sizeof(humanAircraftType_t);
960 
961 	case V_UFO:
962 		if (Q_streq((const char *)set, "craft_ufo_bomber"))
963 			*(ufoType_t *) b = UFO_BOMBER;
964 		else if (Q_streq((const char *)set, "craft_ufo_carrier"))
965 			*(ufoType_t *) b = UFO_CARRIER;
966 		else if (Q_streq((const char *)set, "craft_ufo_corrupter"))
967 			*(ufoType_t *) b = UFO_CORRUPTER;
968 		else if (Q_streq((const char *)set, "craft_ufo_fighter"))
969 			*(ufoType_t *) b = UFO_FIGHTER;
970 		else if (Q_streq((const char *)set, "craft_ufo_harvester"))
971 			*(ufoType_t *) b = UFO_HARVESTER;
972 		else if (Q_streq((const char *)set, "craft_ufo_scout"))
973 			*(ufoType_t *) b = UFO_SCOUT;
974 		else if (Q_streq((const char *)set, "craft_ufo_supply"))
975 			*(ufoType_t *) b = UFO_SUPPLY;
976 		else if (Q_streq((const char *)set, "craft_ufo_gunboat"))
977 			*(ufoType_t *) b = UFO_GUNBOAT;
978 		else if (Q_streq((const char *)set, "craft_ufo_ripper"))
979 			*(ufoType_t *) b = UFO_RIPPER;
980 		else if (Q_streq((const char *)set, "craft_ufo_mothership"))
981 			*(ufoType_t *) b = UFO_MOTHERSHIP;
982 		else
983 			Sys_Error("Unknown ufo type: '%s'", (const char *)set);
984 		return sizeof(ufoType_t);
985 
986 	case V_UFOCRASHED:
987 		if (Q_streq((const char *)set, "craft_crash_bomber"))
988 			*(ufoType_t *) b = UFO_BOMBER;
989 		else if (Q_streq((const char *)set, "craft_crash_carrier"))
990 			*(ufoType_t *) b = UFO_CARRIER;
991 		else if (Q_streq((const char *)set, "craft_crash_corrupter"))
992 			*(ufoType_t *) b = UFO_CORRUPTER;
993 		else if (Q_streq((const char *)set, "craft_crash_fighter"))
994 			*(ufoType_t *) b = UFO_FIGHTER;
995 		else if (Q_streq((const char *)set, "craft_crash_harvester"))
996 			*(ufoType_t *) b = UFO_HARVESTER;
997 		else if (Q_streq((const char *)set, "craft_crash_scout"))
998 			*(ufoType_t *) b = UFO_SCOUT;
999 		else if (Q_streq((const char *)set, "craft_crash_supply"))
1000 			*(ufoType_t *) b = UFO_SUPPLY;
1001 		else if (Q_streq((const char *)set, "craft_crash_gunboat"))
1002 			*(ufoType_t *) b = UFO_GUNBOAT;
1003 		else if (Q_streq((const char *)set, "craft_crash_ripper"))
1004 			*(ufoType_t *) b = UFO_RIPPER;
1005 		else if (Q_streq((const char *)set, "craft_crash_mothership"))
1006 			*(ufoType_t *) b = UFO_MOTHERSHIP;
1007 		else
1008 			Sys_Error("Unknown ufo type: '%s'", (const char *)set);
1009 		return sizeof(ufoType_t);
1010 
1011 	case V_INT:
1012 		*(int *) b = *(const int *) set;
1013 		return sizeof(int);
1014 
1015 	case V_INT2:
1016 		((int *) b)[0] = ((const int *) set)[0];
1017 		((int *) b)[1] = ((const int *) set)[1];
1018 		return 2 * sizeof(int);
1019 
1020 	case V_FLOAT:
1021 		*(float *) b = *(const float *) set;
1022 		return sizeof(float);
1023 
1024 	case V_POS:
1025 		((float *) b)[0] = ((const float *) set)[0];
1026 		((float *) b)[1] = ((const float *) set)[1];
1027 		return 2 * sizeof(float);
1028 
1029 	case V_VECTOR:
1030 		((float *) b)[0] = ((const float *) set)[0];
1031 		((float *) b)[1] = ((const float *) set)[1];
1032 		((float *) b)[2] = ((const float *) set)[2];
1033 		return 3 * sizeof(float);
1034 
1035 	case V_COLOR:
1036 		((float *) b)[0] = ((const float *) set)[0];
1037 		((float *) b)[1] = ((const float *) set)[1];
1038 		((float *) b)[2] = ((const float *) set)[2];
1039 		((float *) b)[3] = ((const float *) set)[3];
1040 		return 4 * sizeof(float);
1041 
1042 	case V_STRING:
1043 		Q_strncpyz((char *) b, (const char *) set, MAX_VAR);
1044 		len = (int)strlen((const char *) set) + 1;
1045 		if (len > MAX_VAR)
1046 			len = MAX_VAR;
1047 		return len;
1048 
1049 	case V_LONGSTRING:
1050 		strcpy((char *) b, (const char *) set);
1051 		len = (int)strlen((const char *) set) + 1;
1052 		return len;
1053 
1054 	case V_ALIGN:
1055 		*(align_t *)b = *(const align_t *) set;
1056 		return sizeof(align_t);
1057 
1058 	case V_BLEND:
1059 		*(blend_t *)b = *(const blend_t *) set;
1060 		return sizeof(blend_t);
1061 
1062 	case V_STYLE:
1063 		*(style_t *)b = *(const style_t *) set;
1064 		return sizeof(style_t);
1065 
1066 	case V_FADE:
1067 		*(fade_t *)b = *(const fade_t *) set;
1068 		return sizeof(fade_t);
1069 
1070 	case V_SHAPE_SMALL:
1071 		*(int *) b = *(const int *) set;
1072 		return SHAPE_SMALL_MAX_HEIGHT;
1073 
1074 	case V_SHAPE_BIG:
1075 		memcpy(b, set, 64);
1076 		return SHAPE_BIG_MAX_HEIGHT * 4;
1077 
1078 	case V_DAMAGE:
1079 		*b = *(const byte *) set;
1080 		return 1;
1081 
1082 	case V_DATE:
1083 		memcpy(b, set, sizeof(date_t));
1084 		return sizeof(date_t);
1085 
1086 	default:
1087 		Sys_Error("Com_SetValue: unknown value type\n");
1088 	}
1089 }
1090 
1091 /**
1092  * @param[in] base The start pointer to a given data type (typedef, struct)
1093  * @param[in] type The data type that should be parsed
1094  * @param[in] ofs The offset for the value
1095  * @sa Com_SetValue
1096  * @return char pointer with translated data type value
1097  */
Com_ValueToStr(const void * base,const valueTypes_t type,const int ofs)1098 const char *Com_ValueToStr (const void *base, const valueTypes_t type, const int ofs)
1099 {
1100 	static char valuestr[MAX_VAR];
1101 	const byte *b;
1102 
1103 	b = (const byte *) base + ofs;
1104 
1105 #ifdef DEBUG
1106 	if (b != Com_AlignPtr(b, type)) {
1107 		Com_Printf("Wrong alignment: %p %p type:%d size:" UFO_SIZE_T " - this code will CRASH on ARM CPU\n", b, Com_AlignPtr(b, type), type, vt_aligns[type]);
1108 		Sys_Backtrace();
1109 	}
1110 #endif
1111 
1112 	switch (type) {
1113 	case V_NULL:
1114 		return 0;
1115 
1116 	case V_HUNK_STRING:
1117 		if (b == nullptr)
1118 			return "(null)";
1119 		else
1120 			return (const char*)b;
1121 
1122 	case V_BOOL:
1123 		if (*(const bool *)b)
1124 			return "true";
1125 		else
1126 			return "false";
1127 
1128 	case V_CHAR:
1129 		return (const char *) b;
1130 		break;
1131 
1132 	case V_TEAM:
1133 		switch (*(const int *) b) {
1134 		case TEAM_CIVILIAN:
1135 			return "civilian";
1136 		case TEAM_PHALANX:
1137 			return "phalanx";
1138 		case TEAM_ALIEN:
1139 			return "alien";
1140 		default:
1141 			Sys_Error("Unknown team id '%i'", *(const int *) b);
1142 		}
1143 
1144 	case V_AIRCRAFTTYPE:
1145 		switch (*(const humanAircraftType_t *) b) {
1146 		case DROPSHIP_FIREBIRD:
1147 			return "craft_drop_firebird";
1148 		case DROPSHIP_HERAKLES:
1149 			return "craft_drop_herakles";
1150 		case DROPSHIP_RAPTOR:
1151 			return "craft_drop_raptor";
1152 		case INTERCEPTOR_STILETTO:
1153 			return "craft_inter_stiletto";
1154 		case INTERCEPTOR_SARACEN:
1155 			return "craft_inter_saracen";
1156 		case INTERCEPTOR_DRAGON:
1157 			return "craft_inter_dragon";
1158 		case INTERCEPTOR_STARCHASER:
1159 			return "craft_inter_starchaser";
1160 		case INTERCEPTOR_STINGRAY:
1161 			return "craft_inter_stingray";
1162 		default:
1163 			Sys_Error("Unknown aircrafttype type: '%i'", *(const humanAircraftType_t *) b);
1164 		}
1165 
1166 	case V_UFO:
1167 		switch (*(const ufoType_t *) b) {
1168 		case UFO_BOMBER:
1169 			return "craft_ufo_bomber";
1170 		case UFO_CARRIER:
1171 			return "craft_ufo_carrier";
1172 		case UFO_CORRUPTER:
1173 			return "craft_ufo_corrupter";
1174 		case UFO_FIGHTER:
1175 			return "craft_ufo_fighter";
1176 		case UFO_HARVESTER:
1177 			return "craft_ufo_harvester";
1178 		case UFO_SCOUT:
1179 			return "craft_ufo_scout";
1180 		case UFO_SUPPLY:
1181 			return "craft_ufo_supply";
1182 		case UFO_GUNBOAT:
1183 			return "craft_ufo_gunboat";
1184 		case UFO_RIPPER:
1185 			return "craft_ufo_ripper";
1186 		case UFO_MOTHERSHIP:
1187 			return "craft_ufo_mothership";
1188 		default:
1189 			Sys_Error("Unknown ufo type: '%i'", *(const ufoType_t *) b);
1190 		}
1191 
1192 	case V_UFOCRASHED:
1193 		switch (*(const ufoType_t *) b) {
1194 		case UFO_BOMBER:
1195 			return "craft_crash_bomber";
1196 		case UFO_CARRIER:
1197 			return "craft_crash_carrier";
1198 		case UFO_CORRUPTER:
1199 			return "craft_crash_corrupter";
1200 		case UFO_FIGHTER:
1201 			return "craft_crash_fighter";
1202 		case UFO_HARVESTER:
1203 			return "craft_crash_harvester";
1204 		case UFO_SCOUT:
1205 			return "craft_crash_scout";
1206 		case UFO_SUPPLY:
1207 			return "craft_crash_supply";
1208 		case UFO_GUNBOAT:
1209 			return "craft_crash_gunboat";
1210 		case UFO_RIPPER:
1211 			return "craft_crash_ripper";
1212 		case UFO_MOTHERSHIP:
1213 			return "craft_crash_mothership";
1214 		default:
1215 			Sys_Error("Unknown crashed ufo type: '%i'", *(const ufoType_t *) b);
1216 		}
1217 
1218 	case V_INT:
1219 		Com_sprintf(valuestr, sizeof(valuestr), "%i", *(const int *) b);
1220 		return valuestr;
1221 
1222 	case V_INT2:
1223 		Com_sprintf(valuestr, sizeof(valuestr), "%i %i", ((const int *) b)[0], ((const int *) b)[1]);
1224 		return valuestr;
1225 
1226 	case V_FLOAT:
1227 		Com_sprintf(valuestr, sizeof(valuestr), "%.2f", *(const float *) b);
1228 		return valuestr;
1229 
1230 	case V_POS:
1231 		Com_sprintf(valuestr, sizeof(valuestr), "%.2f %.2f", ((const float *) b)[0], ((const float *) b)[1]);
1232 		return valuestr;
1233 
1234 	case V_VECTOR:
1235 		Com_sprintf(valuestr, sizeof(valuestr), "%.2f %.2f %.2f", ((const float *) b)[0], ((const float *) b)[1], ((const float *) b)[2]);
1236 		return valuestr;
1237 
1238 	case V_COLOR:
1239 		Com_sprintf(valuestr, sizeof(valuestr), "%.2f %.2f %.2f %.2f", ((const float *) b)[0], ((const float *) b)[1], ((const float *) b)[2], ((const float *) b)[3]);
1240 		return valuestr;
1241 
1242 	case V_TRANSLATION_STRING:
1243 	case V_STRING:
1244 	case V_LONGSTRING:
1245 		if (b == nullptr)
1246 			return "(null)";
1247 		else
1248 			return (const char *) b;
1249 
1250 	case V_ALIGN:
1251 		assert(*(const align_t *)b < ALIGN_LAST);
1252 		Q_strncpyz(valuestr, align_names[*(const align_t *)b], sizeof(valuestr));
1253 		return valuestr;
1254 
1255 	case V_BLEND:
1256 		assert(*(const blend_t *)b < BLEND_LAST);
1257 		Q_strncpyz(valuestr, blend_names[*(const blend_t *)b], sizeof(valuestr));
1258 		return valuestr;
1259 
1260 	case V_STYLE:
1261 		assert(*(const style_t *)b < STYLE_LAST);
1262 		Q_strncpyz(valuestr, style_names[*(const style_t *)b], sizeof(valuestr));
1263 		return valuestr;
1264 
1265 	case V_FADE:
1266 		assert(*(const fade_t *)b < FADE_LAST);
1267 		Q_strncpyz(valuestr, fade_names[*(const fade_t *)b], sizeof(valuestr));
1268 		return valuestr;
1269 
1270 	case V_SHAPE_SMALL:
1271 	case V_SHAPE_BIG:
1272 		return "";
1273 
1274 	case V_DAMAGE:
1275 		assert(*(const byte *)b < MAX_DAMAGETYPES);
1276 		return csi.dts[*(const byte *)b].id;
1277 
1278 	case V_DATE:
1279 		Com_sprintf(valuestr, sizeof(valuestr), "%i %i %i", ((const date_t *) b)->day / DAYS_PER_YEAR, ((const date_t *) b)->day % DAYS_PER_YEAR, ((const date_t *) b)->sec);
1280 		return valuestr;
1281 
1282 	case V_RELABS:
1283 		/* absolute value */
1284 		if (*(const float *) b > 2.0)
1285 			Com_sprintf(valuestr, sizeof(valuestr), "+%.2f", *(const float *) b);
1286 		/* absolute value */
1287 		else if (*(const float *) b < 2.0)
1288 			Com_sprintf(valuestr, sizeof(valuestr), "-%.2f", *(const float *) b);
1289 		/* relative value */
1290 		else
1291 			Com_sprintf(valuestr, sizeof(valuestr), "%.2f", *(const float *) b);
1292 		return valuestr;
1293 
1294 	default:
1295 		Sys_Error("Com_ValueToStr: unknown value type %i\n", type);
1296 	}
1297 }
1298 
Com_ParseBlockToken(const char * name,const char ** text,void * base,const value_t * values,memPool_t * mempool,const char * token)1299 bool Com_ParseBlockToken (const char *name, const char** text, void *base, const value_t *values, memPool_t *mempool, const char *token)
1300 {
1301 	const value_t *v;
1302 	const char *errhead = "Com_ParseBlockToken: unexpected end of file (";
1303 
1304 	for (v = values; v->string; v++)
1305 		if (Q_streq(token, v->string)) {
1306 			/* found a definition */
1307 			token = Com_EParse(text, errhead, name);
1308 			if (!*text)
1309 				return false;
1310 
1311 			switch (v->type) {
1312 			case V_TRANSLATION_STRING:
1313 				if (mempool == nullptr) {
1314 					if (Com_EParseValue(base, token, v->type, v->ofs, v->size) == -1)
1315 						Com_Printf("Com_ParseBlockToken: Wrong size for value %s\n", v->string);
1316 					break;
1317 				} else {
1318 					if (*token == '_')
1319 						token++;
1320 					/* fall through */
1321 				}
1322 			case V_HUNK_STRING:
1323 				Mem_PoolStrDupTo(token, &Com_GetValue<char*>(base, v), mempool, 0);
1324 				break;
1325 			case V_LIST: {
1326 				linkedList_t*& list = Com_GetValue<linkedList_t*>(base, v);
1327 				assert(!list);
1328 				Com_UnParseLastToken();
1329 				if (!Com_ParseList(text, &list)) {
1330 					return false;
1331 				}
1332 				break;
1333 			}
1334 			default:
1335 				if (Com_EParseValue(base, token, v->type, v->ofs, v->size) == -1)
1336 					Com_Printf("Com_ParseBlockToken: Wrong size for value %s\n", v->string);
1337 				break;
1338 			}
1339 			break;
1340 		}
1341 
1342 	return v->string != nullptr;
1343 }
1344 
1345 /**
1346  * Parse tokens between '(' and ')' and return them into a linked list.
1347  * It the list is not well formed, the returned list is null.
1348  * @param[in] text Pointer to a token stream
1349  * @param[out] list list to return
1350  * @return True if the list is well formed, else false.
1351  */
Com_ParseList(const char ** text,linkedList_t ** list)1352 bool Com_ParseList (const char** text, linkedList_t** list)
1353 {
1354 	*list = nullptr;
1355 
1356 	if (Com_NextToken(text) != TT_BEGIN_LIST) {
1357 		Com_Printf("Com_ParseList: expected '(' but \"%s\" found\n", Com_GetToken(text));
1358 		return false;
1359 	}
1360 
1361 	while (true) {
1362 		Com_TokenType_t type = Com_NextToken(text);
1363 		if (type == TT_END_LIST)
1364 			break;
1365 		if (type == TT_EOF) {
1366 			Com_Printf("Com_ParseList: expected list content but end of file found\n");
1367 			LIST_Delete(list);
1368 			return false;
1369 		}
1370 		if (type < TT_CONTENT) {
1371 			Com_Printf("Com_ParseList: expected list content but \"%s\" found\n", Com_GetToken(text));
1372 			LIST_Delete(list);
1373 			return false;
1374 		}
1375 		// read content
1376 		LIST_AddString(list, Com_GetToken(text));
1377 	}
1378 
1379 	return true;
1380 }
1381 
Com_ParseBlock(const char * name,const char ** text,void * base,const value_t * values,memPool_t * mempool)1382 bool Com_ParseBlock (const char *name, const char** text, void *base, const value_t *values, memPool_t *mempool)
1383 {
1384 	const char *errhead = "Com_ParseBlock: unexpected end of file (";
1385 	const char *token;
1386 
1387 	/* get name/id */
1388 	token = Com_Parse(text);
1389 
1390 	if (!*text || *token != '{') {
1391 		Com_Printf("Com_ParseBlock: block \"%s\" without body ignored\n", name);
1392 		return false;
1393 	}
1394 
1395 	do {
1396 		/* get the name type */
1397 		token = Com_EParse(text, errhead, name);
1398 		if (!*text)
1399 			break;
1400 		if (*token == '}')
1401 			break;
1402 		if (!Com_ParseBlockToken(name, text, base, values, mempool, token))
1403 			Com_Printf("Com_ParseBlock: unknown token '%s' ignored (%s)\n", token, name);
1404 	} while (*text);
1405 
1406 	return true;
1407 }
1408 
1409 /*
1410 ==============================================================================
1411 OBJECT DEFINITION INTERPRETER
1412 ==============================================================================
1413 */
1414 
1415 static const char *const skillNames[SKILL_NUM_TYPES + 1] = {
1416 	"strength",
1417 	"speed",
1418 	"accuracy",
1419 	"mind",
1420 	"close",
1421 	"heavy",
1422 	"assault",
1423 	"sniper",
1424 	"explosive",
1425 	"piloting",
1426 	"targeting",
1427 	"evading",
1428 	"health"
1429 };
1430 
1431 /** @brief The order here must be the same as in od_vals */
1432 enum {
1433 	OD_WEAPON,			/**< parse a weapon */
1434 	OD_PROTECTION,		/**< parse armour protection values */
1435 	OD_RATINGS			/**< parse rating values for displaying in the menus */
1436 };
1437 
1438 static const value_t od_vals[] = {
1439 	{"name", V_TRANSLATION_STRING, offsetof(objDef_t, name), 0},
1440 	{"armourpath", V_HUNK_STRING, offsetof(objDef_t, armourPath), 0},
1441 	{"model", V_HUNK_STRING, offsetof(objDef_t, model), 0},
1442 	{"image", V_HUNK_STRING, offsetof(objDef_t, image), 0},
1443 	{"type", V_HUNK_STRING, offsetof(objDef_t, type), 0},
1444 	{"reloadsound", V_HUNK_STRING, offsetof(objDef_t, reloadSound), 0},
1445 	{"animationindex", V_CHAR, offsetof(objDef_t, animationIndex), MEMBER_SIZEOF(objDef_t, animationIndex)},
1446 	{"shape", V_SHAPE_SMALL, offsetof(objDef_t, shape), MEMBER_SIZEOF(objDef_t, shape)},
1447 	{"scale", V_FLOAT, offsetof(objDef_t, scale), MEMBER_SIZEOF(objDef_t, scale)},
1448 	{"center", V_VECTOR, offsetof(objDef_t, center), MEMBER_SIZEOF(objDef_t, center)},
1449 	{"weapon", V_BOOL, offsetof(objDef_t, weapon), MEMBER_SIZEOF(objDef_t, weapon)},
1450 	{"holdtwohanded", V_BOOL, offsetof(objDef_t, holdTwoHanded), MEMBER_SIZEOF(objDef_t, holdTwoHanded)},
1451 	{"firetwohanded", V_BOOL, offsetof(objDef_t, fireTwoHanded), MEMBER_SIZEOF(objDef_t, fireTwoHanded)},
1452 	{"implant", V_BOOL, offsetof(objDef_t, implant), MEMBER_SIZEOF(objDef_t, implant)},
1453 	{"headgear", V_BOOL, offsetof(objDef_t, headgear), MEMBER_SIZEOF(objDef_t, headgear)},
1454 	{"thrown", V_BOOL, offsetof(objDef_t, thrown), MEMBER_SIZEOF(objDef_t, thrown)},
1455 	{"ammo", V_INT, offsetof(objDef_t, ammo), MEMBER_SIZEOF(objDef_t, ammo)},
1456 	{"oneshot", V_BOOL, offsetof(objDef_t, oneshot), MEMBER_SIZEOF(objDef_t, oneshot)},
1457 	{"deplete", V_BOOL, offsetof(objDef_t, deplete), MEMBER_SIZEOF(objDef_t, deplete)},
1458 	{"reload", V_INT, offsetof(objDef_t, _reload), MEMBER_SIZEOF(objDef_t, _reload)},
1459 	{"reloadattenuation", V_FLOAT, offsetof(objDef_t, reloadAttenuation), MEMBER_SIZEOF(objDef_t, reloadAttenuation)},
1460 	{"size", V_INT, offsetof(objDef_t, size), MEMBER_SIZEOF(objDef_t, size)},
1461 	{"weight", V_FLOAT, offsetof(objDef_t, weight), MEMBER_SIZEOF(objDef_t, weight)},
1462 	{"price", V_INT, offsetof(objDef_t, price), MEMBER_SIZEOF(objDef_t, price)},
1463 	{"productioncost", V_INT, offsetof(objDef_t, productionCost), MEMBER_SIZEOF(objDef_t, productionCost)},
1464 	{"useable", V_TEAM, offsetof(objDef_t, useable), MEMBER_SIZEOF(objDef_t, useable)},
1465 	{"notonmarket", V_BOOL, offsetof(objDef_t, notOnMarket), MEMBER_SIZEOF(objDef_t, notOnMarket)},
1466 
1467 	{"installationTime", V_INT, offsetof(objDef_t, craftitem.installationTime), MEMBER_SIZEOF(objDef_t, craftitem.installationTime)},
1468 	{"bullets", V_BOOL, offsetof(objDef_t, craftitem.bullets), MEMBER_SIZEOF(objDef_t, craftitem.bullets)},
1469 	{"beam", V_BOOL, offsetof(objDef_t, craftitem.beam), MEMBER_SIZEOF(objDef_t, craftitem.beam)},
1470 	{"beamcolor", V_COLOR, offsetof(objDef_t, craftitem.beamColor), MEMBER_SIZEOF(objDef_t, craftitem.beamColor)},
1471 	{"wdamage", V_FLOAT, offsetof(objDef_t, craftitem.weaponDamage), MEMBER_SIZEOF(objDef_t, craftitem.weaponDamage)},
1472 	{"wspeed", V_FLOAT, offsetof(objDef_t, craftitem.weaponSpeed), MEMBER_SIZEOF(objDef_t, craftitem.weaponSpeed)},
1473 	{"delay", V_FLOAT, offsetof(objDef_t, craftitem.weaponDelay), MEMBER_SIZEOF(objDef_t, craftitem.weaponDelay)},
1474 	{"shield", V_FLOAT, offsetof(objDef_t, craftitem.stats[AIR_STATS_SHIELD]), MEMBER_SIZEOF(objDef_t, craftitem.stats[AIR_STATS_SHIELD])},
1475 	{"wrange", V_FLOAT, offsetof(objDef_t, craftitem.stats[AIR_STATS_WRANGE]), MEMBER_SIZEOF(objDef_t, craftitem.stats[AIR_STATS_WRANGE])},
1476 	{"damage", V_RELABS, offsetof(objDef_t, craftitem.stats[AIR_STATS_DAMAGE]), MEMBER_SIZEOF(objDef_t, craftitem.stats[AIR_STATS_DAMAGE])},
1477 	{"accuracy", V_RELABS, offsetof(objDef_t, craftitem.stats[AIR_STATS_ACCURACY]), MEMBER_SIZEOF(objDef_t, craftitem.stats[AIR_STATS_ACCURACY])},
1478 	{"ecm", V_RELABS, offsetof(objDef_t, craftitem.stats[AIR_STATS_ECM]), MEMBER_SIZEOF(objDef_t, craftitem.stats[AIR_STATS_ECM])},
1479 	{"speed", V_RELABS, offsetof(objDef_t, craftitem.stats[AIR_STATS_SPEED]), MEMBER_SIZEOF(objDef_t, craftitem.stats[AIR_STATS_SPEED])},
1480 	{"maxspeed", V_RELABS, offsetof(objDef_t, craftitem.stats[AIR_STATS_MAXSPEED]), MEMBER_SIZEOF(objDef_t, craftitem.stats[AIR_STATS_SPEED])},
1481 	{"fuelsize", V_RELABS, offsetof(objDef_t, craftitem.stats[AIR_STATS_FUELSIZE]), MEMBER_SIZEOF(objDef_t, craftitem.stats[AIR_STATS_FUELSIZE])},
1482 	{"dmgtype", V_DAMAGE, offsetof(objDef_t, dmgtype), MEMBER_SIZEOF(objDef_t, dmgtype)},
1483 
1484 	{"is_primary", V_BOOL, offsetof(objDef_t, isPrimary), MEMBER_SIZEOF(objDef_t, isPrimary)},
1485 	{"is_secondary", V_BOOL, offsetof(objDef_t, isSecondary), MEMBER_SIZEOF(objDef_t, isSecondary)},
1486 	{"is_heavy", V_BOOL, offsetof(objDef_t, isHeavy), MEMBER_SIZEOF(objDef_t, isHeavy)},
1487 	{"is_misc", V_BOOL, offsetof(objDef_t, isMisc), MEMBER_SIZEOF(objDef_t, isMisc)},
1488 	{"is_ugvitem", V_BOOL, offsetof(objDef_t, isUGVitem), MEMBER_SIZEOF(objDef_t, isUGVitem)},
1489 	{"is_dummy", V_BOOL, offsetof(objDef_t, isDummy), MEMBER_SIZEOF(objDef_t, isDummy)},
1490 	{"virtual", V_BOOL, offsetof(objDef_t, isVirtual), MEMBER_SIZEOF(objDef_t, isVirtual)},
1491 
1492 	{nullptr, V_NULL, 0, 0}
1493 };
1494 
1495 static const value_t effect_vals[] = {
1496 	{"period", V_INT, offsetof(itemEffect_t, period), MEMBER_SIZEOF(itemEffect_t, period)},
1497 	{"duration", V_INT, offsetof(itemEffect_t, duration), MEMBER_SIZEOF(itemEffect_t, duration)},
1498 	{"permanent", V_BOOL, offsetof(itemEffect_t, isPermanent), MEMBER_SIZEOF(itemEffect_t, isPermanent)},
1499 
1500 	{"accuracy", V_FLOAT, offsetof(itemEffect_t, accuracy), MEMBER_SIZEOF(itemEffect_t, accuracy)},
1501 	{"tu", V_FLOAT, offsetof(itemEffect_t, TUs), MEMBER_SIZEOF(itemEffect_t, TUs)},
1502 	{"power", V_FLOAT, offsetof(itemEffect_t, power), MEMBER_SIZEOF(itemEffect_t, power)},
1503 	{"mind", V_FLOAT, offsetof(itemEffect_t, mind), MEMBER_SIZEOF(itemEffect_t, mind)},
1504 	{"morale", V_FLOAT, offsetof(itemEffect_t, morale), MEMBER_SIZEOF(itemEffect_t, morale)},
1505 
1506 	{nullptr, V_NULL, 0, 0}
1507 };
1508 
1509 /* =========================================================== */
1510 
1511 static const value_t fdps[] = {
1512 	{"name", V_TRANSLATION_STRING, offsetof(fireDef_t, name), 0},
1513 	{"shotorg", V_POS, offsetof(fireDef_t, shotOrg), MEMBER_SIZEOF(fireDef_t, shotOrg)},
1514 	{"projtl", V_HUNK_STRING, offsetof(fireDef_t, projectile), 0},
1515 	{"impact", V_HUNK_STRING, offsetof(fireDef_t, impact), 0},
1516 	{"hitbody", V_HUNK_STRING, offsetof(fireDef_t, hitBody), 0},
1517 	{"firesnd", V_HUNK_STRING, offsetof(fireDef_t, fireSound), 0},
1518 	{"impsnd", V_HUNK_STRING, offsetof(fireDef_t, impactSound), 0},
1519 	{"bodysnd", V_HUNK_STRING, offsetof(fireDef_t, hitBodySound), 0},
1520 	{"bncsnd", V_HUNK_STRING, offsetof(fireDef_t, bounceSound), 0},
1521 	{"fireattenuation", V_FLOAT, offsetof(fireDef_t, fireAttenuation), MEMBER_SIZEOF(fireDef_t, fireAttenuation)},
1522 	{"impactattenuation", V_FLOAT, offsetof(fireDef_t, impactAttenuation), MEMBER_SIZEOF(fireDef_t, impactAttenuation)},
1523 	{"throughwall", V_INT, offsetof(fireDef_t, throughWall), MEMBER_SIZEOF(fireDef_t, throughWall)},
1524 	{"sndonce", V_BOOL, offsetof(fireDef_t, soundOnce), MEMBER_SIZEOF(fireDef_t, soundOnce)},
1525 	{"gravity", V_BOOL, offsetof(fireDef_t, gravity), MEMBER_SIZEOF(fireDef_t, gravity)},
1526 	{"launched", V_BOOL, offsetof(fireDef_t, launched), MEMBER_SIZEOF(fireDef_t, launched)},
1527 	{"rolled", V_BOOL, offsetof(fireDef_t, rolled), MEMBER_SIZEOF(fireDef_t, rolled)},
1528 	{"reaction", V_BOOL, offsetof(fireDef_t, reaction), MEMBER_SIZEOF(fireDef_t, reaction)},
1529 	{"delay", V_INT, offsetof(fireDef_t, delay), MEMBER_SIZEOF(fireDef_t, delay)},
1530 	{"bounce", V_INT, offsetof(fireDef_t, bounce), MEMBER_SIZEOF(fireDef_t, bounce)},
1531 	{"bncfac", V_FLOAT, offsetof(fireDef_t, bounceFac), MEMBER_SIZEOF(fireDef_t, bounceFac)},
1532 	{"speed", V_FLOAT, offsetof(fireDef_t, speed), MEMBER_SIZEOF(fireDef_t, speed)},
1533 	{"spread", V_POS, offsetof(fireDef_t, spread), MEMBER_SIZEOF(fireDef_t, spread)},
1534 	{"crouch", V_FLOAT, offsetof(fireDef_t, crouch), MEMBER_SIZEOF(fireDef_t, crouch)},
1535 	{"shots", V_INT, offsetof(fireDef_t, shots), MEMBER_SIZEOF(fireDef_t, shots)},
1536 	{"ammo", V_INT, offsetof(fireDef_t, ammo), MEMBER_SIZEOF(fireDef_t, ammo)},
1537 	{"delaybetweenshots", V_FLOAT, offsetof(fireDef_t, delayBetweenShots), MEMBER_SIZEOF(fireDef_t, delayBetweenShots)},
1538 	{"time", V_INT, offsetof(fireDef_t, time), MEMBER_SIZEOF(fireDef_t, time)},
1539 	{"damage", V_POS, offsetof(fireDef_t, damage), MEMBER_SIZEOF(fireDef_t, damage)},
1540 	{"spldmg", V_POS, offsetof(fireDef_t, spldmg), MEMBER_SIZEOF(fireDef_t, spldmg)},
1541 	{"dmgweight", V_DAMAGE, offsetof(fireDef_t, dmgweight), MEMBER_SIZEOF(fireDef_t, dmgweight)},
1542 	{"irgoggles", V_BOOL, offsetof(fireDef_t, irgoggles), MEMBER_SIZEOF(fireDef_t, irgoggles)},
1543 	{"rounds", V_INT, offsetof(fireDef_t, rounds), MEMBER_SIZEOF(fireDef_t, rounds)},
1544 	{nullptr, V_NULL, 0, 0}
1545 };
1546 
1547 /**
1548  * @brief Parses the item effect.
1549  * @param[in,out] e The item effect that is filled in here.
1550  */
Com_ParseItemEffect(itemEffect_t * e,const char * name,const char ** text)1551 static effectStages_t Com_ParseItemEffect (itemEffect_t *e, const char *name, const char **text)
1552 {
1553 	effectStages_t stage = EFFECT_MAX;
1554 
1555 	const char *token = Com_Parse(text);
1556 	if (!*text) {
1557 		Com_Printf("Com_ParseItemEffect: syntax error for item '%s'\n", name);
1558 		return stage;
1559 	}
1560 
1561 	if (Q_streq(token, "active")) {
1562 		stage = EFFECT_ACTIVE;
1563 	} else if (Q_streq(token, "inactive")) {
1564 		stage = EFFECT_INACTIVE;
1565 	} else if (Q_streq(token, "overdose")) {
1566 		stage = EFFECT_OVERDOSE;
1567 	} else if (Q_streq(token, "strengthen")) {
1568 		stage = EFFECT_STRENGTHEN;
1569 	} else {
1570 		token = Com_Parse(text);
1571 		if (!*text || *token != '{') {
1572 			Com_Printf("Com_ParseItemEffect: syntax error for item '%s'\n", name);
1573 			return stage;
1574 		}
1575 		Com_SkipBlock(text);
1576 		Com_Printf("Com_ParseItemEffect: item effect of \"%s\" has invalid effect stage: '%s'\n", name, token);
1577 		return stage;
1578 	}
1579 
1580 	token = Com_Parse(text);
1581 	if (token[0] != '{') {
1582 		Com_Printf("Com_ParseItemEffect: syntax error for item '%s'\n", name);
1583 		return stage;
1584 	}
1585 
1586 	do {
1587 		token = Com_Parse(text);
1588 		if (!*text)
1589 			break;
1590 		if (*token == '}')
1591 			break;
1592 
1593 		if (!Com_ParseBlockToken(name, text, e, effect_vals, com_genericPool, token)) {
1594 			Com_Printf("Com_ParseItemEffect: item effect of \"%s\" contains invalid values\n", name);
1595 			return stage;
1596 		}
1597 	} while (*text); /* dummy condition */
1598 
1599 	return stage;
1600 }
1601 
1602 /**
1603  * @brief Parses the effect that is bound to a fire definitions.
1604  * @param[in,out] fd The fire definition to add the effect to
1605  */
Com_ParseFireEffect(fireDef_t * fd,const char * name,const char ** text)1606 static void Com_ParseFireEffect (fireDef_t *fd, const char *name, const char **text)
1607 {
1608 	itemEffect_t *e = Mem_AllocType(itemEffect_t);
1609 	const effectStages_t stage = Com_ParseItemEffect(e, name, text);
1610 	if (stage == EFFECT_MAX) {
1611 		Mem_Free(e);
1612 		return;
1613 	}
1614 
1615 	itemEffect_t **stagePtr = nullptr;
1616 	switch (stage) {
1617 	case EFFECT_ACTIVE:
1618 		stagePtr = &fd->activeEffect;
1619 		break;
1620 	case EFFECT_INACTIVE:
1621 		stagePtr = &fd->deactiveEffect;
1622 		break;
1623 	case EFFECT_OVERDOSE:
1624 		stagePtr = &fd->overdoseEffect;
1625 		break;
1626 	case EFFECT_STRENGTHEN:
1627 		/* strengthen effect isn't available here */
1628 	case EFFECT_MAX:
1629 		stagePtr = nullptr;
1630 		break;
1631 	}
1632 
1633 	if (stagePtr == nullptr) {
1634 		Mem_Free(e);
1635 		Com_Printf("Com_ParseFireEffect: invalid effect stage for '%s'\n", name);
1636 		return;
1637 	}
1638 
1639 	if (*stagePtr != nullptr) {
1640 		Mem_Free(e);
1641 		Com_Printf("Com_ParseFireEffect: item effect of \"%s\" already has an effect assigned\n", name);
1642 		return;
1643 	}
1644 
1645 	*stagePtr = e;
1646 }
1647 
1648 /**
1649  * @brief Parses the firemode
1650  * @param[in,out] fd The fire definition to fill
1651  */
Com_ParseFire(const char * name,const char ** text,fireDef_t * fd)1652 static bool Com_ParseFire (const char *name, const char** text, fireDef_t *fd)
1653 {
1654 	const char *errhead = "Com_ParseFire: unexpected end of file";
1655 	const char *token;
1656 
1657 	/* get its body */
1658 	token = Com_Parse(text);
1659 
1660 	if (!*text || *token != '{') {
1661 		Com_Printf("Com_ParseFire: fire definition \"%s\" without body ignored\n", name);
1662 		return false;
1663 	}
1664 
1665 	do {
1666 		token = Com_EParse(text, errhead, name);
1667 		if (!*text)
1668 			return true;
1669 		if (*token == '}')
1670 			return true;
1671 
1672 		if (!Com_ParseBlockToken(name, text, fd, fdps, com_genericPool, token)) {
1673 			if (Q_streq(token, "skill")) {
1674 				int skill;
1675 
1676 				token = Com_EParse(text, errhead, name);
1677 				if (!*text)
1678 					return false;
1679 
1680 				for (skill = ABILITY_NUM_TYPES; skill < SKILL_NUM_TYPES; skill++)
1681 					if (Q_streq(skillNames[skill], token)) {
1682 						fd->weaponSkill = skill;
1683 						break;
1684 					}
1685 				if (skill >= SKILL_NUM_TYPES)
1686 					Com_Printf("Com_ParseFire: unknown weapon skill \"%s\" ignored (weapon %s)\n", token, name);
1687 			} else if (Q_streq(token, "effect")) {
1688 				Com_ParseFireEffect(fd, name, text);
1689 			} else if (Q_streq(token, "range")) {
1690 				token = Com_EParse(text, errhead, name);
1691 				if (!*text)
1692 					return false;
1693 				fd->range = atof(token) * UNIT_SIZE;
1694 			} else if (Q_streq(token, "splrad")) {
1695 				token = Com_EParse(text, errhead, name);
1696 				if (!*text)
1697 					return false;
1698 				fd->splrad = atof(token) * UNIT_SIZE;
1699 			} else
1700 				Com_Printf("Com_ParseFire: unknown token \"%s\" ignored (weapon %s)\n", token, name);
1701 		}
1702 	} while (*text);
1703 
1704 	if (fd->impactAttenuation < SOUND_ATTN_NONE || fd->impactAttenuation > SOUND_ATTN_MAX)
1705 		Com_Printf("Com_ParseFire: firedef for weapon \"%s\" has an invalid impact sound attenuation value set\n", name);
1706 
1707 	if (fd->fireAttenuation < SOUND_ATTN_NONE || fd->fireAttenuation > SOUND_ATTN_MAX)
1708 		Com_Printf("Com_ParseFire: firedef for weapon \"%s\" has an invalid fire sound attenuation value set\n", name);
1709 
1710 	if (fd->weaponSkill < ABILITY_NUM_TYPES)
1711 		Com_Printf("Com_ParseFire: firedef for weapon \"%s\" doesn't have a skill set\n", name);
1712 
1713 	if (fd->shots == 1 && fd->delayBetweenShots > 0.0f) {
1714 		Com_Printf("Com_ParseFire: firedef for weapon \"%s\" has delayBetweenShots set but is only a one-shot-firedef\n", name);
1715 		fd->delayBetweenShots = 0.0f;
1716 	}
1717 
1718 	if (fd->name == nullptr) {
1719 		Com_Printf("firedef without name\n");
1720 		return false;
1721 	}
1722 
1723 	return true;
1724 }
1725 
1726 /**
1727  * @brief Parses the armour definitions or the team resistance values from script files. The
1728  * protection and rating values.
1729  * @note The rating values are just for menu displaying
1730  * @sa Com_ParseItem
1731  */
Com_ParseArmourOrResistance(const char * name,const char ** text,short * ad,bool rating)1732 static void Com_ParseArmourOrResistance (const char *name, const char** text, short *ad, bool rating)
1733 {
1734 	const char *errhead = "Com_ParseArmourOrResistance: unexpected end of file";
1735 	const char *token;
1736 	int i;
1737 
1738 	/* get its body */
1739 	token = Com_Parse(text);
1740 
1741 	if (!*text || *token != '{') {
1742 		Com_Printf("Com_ParseArmourOrResistance: armour definition \"%s\" without body ignored\n", name);
1743 		return;
1744 	}
1745 
1746 	do {
1747 		token = Com_EParse(text, errhead, name);
1748 		if (!*text)
1749 			return;
1750 		if (*token == '}')
1751 			return;
1752 
1753 		for (i = 0; i < csi.numDTs; i++) {
1754 			const damageType_t &dt = csi.dts[i];
1755 			if (Q_streq(token, dt.id)) {
1756 				token = Com_EParse(text, errhead, name);
1757 				if (!*text)
1758 					return;
1759 				if (rating && !dt.showInMenu)
1760 					Sys_Error("Com_ParseArmourOrResistance: You try to set a rating value for a none menu displayed damage type '%s'",
1761 							dt.id);
1762 				/* protection or rating values */
1763 				ad[i] = atoi(token);
1764 				break;
1765 			}
1766 		}
1767 
1768 		if (i >= csi.numDTs)
1769 			Com_Printf("Com_ParseArmourOrResistance: unknown damage type \"%s\" ignored (in %s)\n", token, name);
1770 	} while (*text);
1771 }
1772 
1773 /**
1774  * @brief List of valid strings for slot types
1775  * @note slot names are the same as the item types (and must be in the same order)
1776  */
1777 const char *const air_slot_type_strings[] = {
1778 	"base_missile",
1779 	"base_laser",
1780 	"weapon",
1781 	"shield",
1782 	"electronics",
1783 	"pilot",
1784 	"ammo",
1785 	"base_ammo_missile",
1786 	"base_ammo_laser"
1787 };
1788 CASSERT(lengthof(air_slot_type_strings) == MAX_ACITEMS);
1789 
1790 /**
1791  * @brief Temporary list of weapon ids as parsed from the ufo file "weapon_mod \<id\>"
1792  * in Com_ParseItem and used in Com_AddObjectLinks.
1793  */
1794 static linkedList_t *parseItemWeapons = nullptr;
1795 
1796 struct parseItemWeapon_t {
1797 	objDef_t *od;
1798 	int numWeapons;
1799 	char *token;
1800 };
1801 
Com_ParseFireDefinition(objDef_t * od,const char * name,const char ** text)1802 static void Com_ParseFireDefinition (objDef_t *od, const char *name, const char** text)
1803 {
1804 	const char *token;
1805 	if (od->numWeapons < MAX_WEAPONS_PER_OBJDEF) {
1806 		/* get it's body */
1807 		token = Com_Parse(text);
1808 		if (!*text || *token != '{') {
1809 			Com_Printf("Com_ParseItem: weapon_mod \"%s\" without body ignored\n", name);
1810 			return;
1811 		}
1812 
1813 		/* get weapon property */
1814 		token = Com_Parse(text);
1815 		if (!*text || !Q_streq(token, "weapon")) {
1816 			Com_Printf("Com_ParseFireDefinition: weapon_mod \"%s\" weapon as first element expected.\n", name);
1817 			return;
1818 		}
1819 
1820 		/* Save the weapon id. */
1821 		token = Com_Parse(text);
1822 		/* Store the current item-pointer and the weapon id for later linking of the "weapon" pointers */
1823 		parseItemWeapon_t parse;
1824 		parse.od = od;
1825 		parse.numWeapons = od->numWeapons;
1826 		parse.token = Mem_StrDup(token);
1827 		LIST_Add(&parseItemWeapons, parse);
1828 
1829 		/* For each firedef entry for this weapon.  */
1830 		do {
1831 			const char *errhead = "Com_ParseFireDefinition: unexpected end of file (weapon_mod ";
1832 			token = Com_EParse(text, errhead, name);
1833 			if (!*text)
1834 				return;
1835 			if (*token == '}')
1836 				break;
1837 
1838 			if (Q_streq(token, "firedef")) {
1839 				const weaponFireDefIndex_t weapFdsIdx = od->numWeapons;
1840 				if (od->numFiredefs[weapFdsIdx] < MAX_FIREDEFS_PER_WEAPON) {
1841 					const fireDefIndex_t fdIdx = od->numFiredefs[weapFdsIdx];
1842 					fireDef_t *fd = &od->fd[weapFdsIdx][fdIdx];
1843 					fd->fireAttenuation = SOUND_ATTN_NORM;
1844 					fd->impactAttenuation = SOUND_ATTN_NORM;
1845 					/* Parse firemode into fd[IDXweapon][IDXfiremode] */
1846 					Com_ParseFire(name, text, fd);
1847 					/* Self-link fd */
1848 					fd->fdIdx = fdIdx;
1849 					/* Self-link weapon_mod */
1850 					fd->weapFdsIdx = weapFdsIdx;
1851 					od->numFiredefs[od->numWeapons]++;
1852 				} else {
1853 					Com_Printf("Com_ParseFireDefinition: Too many firedefs at \"%s\". Max is %i\n", name, MAX_FIREDEFS_PER_WEAPON);
1854 				}
1855 			} else {
1856 				Com_Printf("Unknown token '%s' - expected firedef\n", token);
1857 			}
1858 		} while (*text);
1859 		od->numWeapons++;
1860 	}
1861 }
1862 
Com_ParseObjDefEffect(objDef_t * od,const char * name,const char ** text)1863 static void Com_ParseObjDefEffect (objDef_t *od, const char *name, const char **text)
1864 {
1865 	itemEffect_t *e = Mem_AllocType(itemEffect_t);
1866 	const effectStages_t stage = Com_ParseItemEffect(e, name, text);
1867 	if (stage != EFFECT_STRENGTHEN) {
1868 		Com_Printf("Com_ParseObjDefEffect: ignore invalid item effect stage for item: '%s'\n", name);
1869 		Mem_Free(e);
1870 		return;
1871 	}
1872 	if (od->strengthenEffect != nullptr) {
1873 		Com_Printf("Com_ParseObjDefEffect: there is already a strengthen effect assigned to: '%s'\n", name);
1874 		Mem_Free(e);
1875 		return;
1876 	}
1877 	od->strengthenEffect = e;
1878 }
1879 
1880 /**
1881  * @brief Parses weapon, equipment, craft items and armour
1882  * @sa Com_ParseArmour
1883  */
Com_ParseItem(const char * name,const char ** text)1884 static void Com_ParseItem (const char *name, const char** text)
1885 {
1886 	const char *errhead = "Com_ParseItem: unexpected end of file (weapon ";
1887 	objDef_t *od;
1888 	const char *token;
1889 	int i;
1890 
1891 	/* search for items with same name */
1892 	if (INVSH_GetItemByIDSilent(name) != nullptr) {
1893 		Com_Printf("Com_ParseItem: weapon def \"%s\" with same name found, second ignored\n", name);
1894 		return;
1895 	}
1896 
1897 	if (csi.numODs >= MAX_OBJDEFS)
1898 		Sys_Error("Com_ParseItem: MAX_OBJDEFS exceeded\n");
1899 
1900 	Com_DPrintf(DEBUG_SHARED, "...found item: '%s' (%i)\n", name, csi.numODs);
1901 
1902 	/* initialize the object definition */
1903 	od = &csi.ods[csi.numODs++];
1904 	OBJZERO(*od);
1905 
1906 	/* default is no craftitem */
1907 	od->craftitem.type = MAX_ACITEMS;
1908 	od->reloadAttenuation = SOUND_ATTN_IDLE;
1909 	od->reloadSound = "weapons/reload-pistol";
1910 	od->armourPath = od->image = od->type = od->model = od->name = "";
1911 
1912 	od->id = Mem_StrDup(name);
1913 	if (Q_strnull(od->id))
1914 		Sys_Error("Com_ParseItem: no id given\n");
1915 
1916 	od->idx = csi.numODs - 1;
1917 
1918 	/* get it's body */
1919 	token = Com_Parse(text);
1920 
1921 	if (!*text || *token != '{') {
1922 		Com_Printf("Com_ParseItem: weapon def \"%s\" without body ignored\n", name);
1923 		csi.numODs--;
1924 		return;
1925 	}
1926 
1927 	do {
1928 		token = Com_EParse(text, errhead, name);
1929 		if (!*text)
1930 			break;
1931 		if (*token == '}')
1932 			break;
1933 
1934 		if (!Com_ParseBlockToken(name, text, od, od_vals, com_genericPool, token)) {
1935 			if (Q_streq(token, "craftweapon")) {
1936 				/* parse a value */
1937 				token = Com_EParse(text, errhead, name);
1938 				if (od->numWeapons < MAX_WEAPONS_PER_OBJDEF) {
1939 					parseItemWeapon_t parse;
1940 					parse.od = od;
1941 					parse.numWeapons = od->numWeapons;
1942 					parse.token = Mem_StrDup(token);
1943 					/* Store the current item-pointer and the weapon id for later linking of the "weapon" pointers */
1944 					LIST_Add(&parseItemWeapons, parse);
1945 					od->numWeapons++;
1946 				} else {
1947 					Com_Printf("Com_ParseItem: Too many weapon_mod definitions at \"%s\". Max is %i\n", name, MAX_WEAPONS_PER_OBJDEF);
1948 				}
1949 			} else if (Q_streq(token, "effect")) {
1950 				Com_ParseObjDefEffect(od, name, text);
1951 			} else if (Q_streq(token, "crafttype")) {
1952 				/* Craftitem type definition. */
1953 				token = Com_EParse(text, errhead, name);
1954 				if (!*text)
1955 					return;
1956 
1957 				/* Check which type it is and store the correct one.*/
1958 				for (i = 0; i < MAX_ACITEMS; i++) {
1959 					if (Q_streq(token, air_slot_type_strings[i])) {
1960 						od->craftitem.type = (aircraftItemType_t)i;
1961 						break;
1962 					}
1963 				}
1964 				if (i == MAX_ACITEMS)
1965 					Com_Printf("AII_ParseAircraftItem: \"%s\" unknown craftitem type: \"%s\" - ignored.\n", name, token);
1966 			} else if (Q_streq(token, "protection")) {
1967 				Com_ParseArmourOrResistance(name, text, od->protection, false);
1968 			} else if (Q_streq(token, "rating")) {
1969 				Com_ParseArmourOrResistance(name, text, od->ratings, true);
1970 			} else if (Q_streq(token, "weapon_mod")) {
1971 				Com_ParseFireDefinition(od, name, text);
1972 			} else {
1973 				Com_Printf("Com_ParseItem: unknown token \"%s\" ignored (weapon %s)\n", token, name);
1974 			}
1975 		}
1976 	} while (*text);
1977 	if (od->productionCost == 0)
1978 		od->productionCost = od->price;
1979 
1980 	/* get size */
1981 	for (i = SHAPE_SMALL_MAX_WIDTH - 1; i >= 0; i--)
1982 		if (od->shape & (0x01010101 << i))
1983 			break;
1984 	od->sx = i + 1;
1985 
1986 	for (i = SHAPE_SMALL_MAX_HEIGHT - 1; i >= 0; i--)
1987 		if (od->shape & (0xFF << (i * SHAPE_SMALL_MAX_WIDTH)))
1988 			break;
1989 	od->sy = i + 1;
1990 
1991 	if ((od->weapon || od->isAmmo() || od->isArmour() || od->implant) && !od->isVirtual && od->shape == 0) {
1992 		Sys_Error("Item %s has no shape\n", od->id);
1993 	}
1994 
1995 	if (od->thrown && od->deplete && od->oneshot && od->ammo) {
1996 		Sys_Error("Item %s has invalid parameters\n", od->id);
1997 	}
1998 
1999 	if (od->reloadAttenuation < SOUND_ATTN_NONE || od->reloadAttenuation > SOUND_ATTN_MAX)
2000 		Com_Printf("Com_ParseItem: weapon \"%s\" has an invalid reload sound attenuation value set\n", od->id);
2001 }
2002 
2003 /*
2004 ==============================================================================
2005 IMPLANT DEFINITION INTERPRETER
2006 ==============================================================================
2007 */
2008 
2009 static const value_t implant_vals[] = {
2010 	{"installationtime", V_INT, offsetof(implantDef_t, installationTime), 0},
2011 	{"removetime", V_INT, offsetof(implantDef_t, removeTime), 0},
2012 
2013 	{nullptr, V_NULL, 0, 0}
2014 };
2015 
Com_ParseImplant(const char * name,const char ** text)2016 static void Com_ParseImplant (const char *name, const char **text)
2017 {
2018 	/* search for implants with same name */
2019 	if (INVSH_GetItemByIDSilent(name) != nullptr) {
2020 		Com_Printf("Com_ParseImplant: implant def \"%s\" with same name found, second ignored\n", name);
2021 		return;
2022 	}
2023 
2024 	if (csi.numImplants >= MAX_IMPLANTS)
2025 		Sys_Error("Com_ParseImplant: MAX_IMPLANTS exceeded\n");
2026 
2027 	Com_DPrintf(DEBUG_SHARED, "...found implant: '%s' (%i)\n", name, csi.numImplants);
2028 
2029 	/* initialize the implant definition */
2030 	implantDef_t *implant = &csi.implants[csi.numImplants++];
2031 	OBJZERO(*implant);
2032 	implant->id = Mem_StrDup(name);
2033 	if (Q_strnull(implant->id))
2034 		Sys_Error("Com_ParseImplant: no id given\n");
2035 
2036 	implant->idx = csi.numImplants - 1;
2037 
2038 	/* get it's body */
2039 	const char *token = Com_Parse(text);
2040 
2041 	if (!*text || *token != '{') {
2042 		Com_Printf("Com_ParseImplant: implant def \"%s\" without body ignored\n", name);
2043 		csi.numImplants--;
2044 		return;
2045 	}
2046 
2047 	const char *errhead = "Com_ParseImplant: unexpected end of file (implant ";
2048 	do {
2049 		token = Com_EParse(text, errhead, name);
2050 		if (!*text)
2051 			break;
2052 		if (*token == '}')
2053 			break;
2054 
2055 		if (!Com_ParseBlockToken(name, text, implant, implant_vals, com_genericPool, token)) {
2056 			if (Q_streq(token, "item")) {
2057 				token = Com_EParse(text, errhead, name);
2058 				if (!*text) {
2059 					Com_Printf("Com_ParseImplant: syntax error (implant %s)\n", name);
2060 					break;
2061 				}
2062 				implant->item = INVSH_GetItemByID(token);
2063 			} else {
2064 				Com_Printf("Com_ParseImplant: unknown token \"%s\" ignored (implant %s)\n", token, name);
2065 			}
2066 		}
2067 	} while (*text);
2068 
2069 	if (implant->item == nullptr) {
2070 		Sys_Error("implant %s without item found", name);
2071 	}
2072 }
2073 
2074 /*
2075 ==============================================================================
2076 INVENTORY DEFINITION INTERPRETER
2077 ==============================================================================
2078 */
2079 
2080 static const value_t idps[] = {
2081 	{"shape", V_SHAPE_BIG, offsetof(invDef_t, shape), 0},
2082 	/* only a single item */
2083 	{"single", V_BOOL, offsetof(invDef_t, single), MEMBER_SIZEOF(invDef_t, single)},
2084 	/* Scrollable container */
2085 	{"scroll", V_BOOL, offsetof(invDef_t, scroll), MEMBER_SIZEOF(invDef_t, scroll)},
2086 	/* this is the implant container */
2087 	{"implant", V_BOOL, offsetof(invDef_t, implant), MEMBER_SIZEOF(invDef_t, implant)},
2088 	/* this is the armour container */
2089 	{"armour", V_BOOL, offsetof(invDef_t, armour), MEMBER_SIZEOF(invDef_t, armour)},
2090 	/* this is the headgear container */
2091 	{"headgear", V_BOOL, offsetof(invDef_t, headgear), MEMBER_SIZEOF(invDef_t, headgear)},
2092 	/* allow everything to be stored in this container (e.g armour and weapons) */
2093 	{"all", V_BOOL, offsetof(invDef_t, all), MEMBER_SIZEOF(invDef_t, all)},
2094 	/* Does not allow to put the same item more than once into the container */
2095 	{"unique", V_BOOL, offsetof(invDef_t, unique), MEMBER_SIZEOF(invDef_t, unique)},
2096 	{"temp", V_BOOL, offsetof(invDef_t, temp), MEMBER_SIZEOF(invDef_t, temp)},
2097 	/* time units for moving something in */
2098 	{"in", V_INT, offsetof(invDef_t, in), MEMBER_SIZEOF(invDef_t, in)},
2099 	/* time units for moving something out */
2100 	{"out", V_INT, offsetof(invDef_t, out), MEMBER_SIZEOF(invDef_t, out)},
2101 
2102 	{nullptr, V_NULL, 0, 0}
2103 };
2104 
Com_ParseInventory(const char * name,const char ** text)2105 static void Com_ParseInventory (const char *name, const char** text)
2106 {
2107 	containerIndex_t cid;
2108 
2109 	/* Special IDs for container. These are also used elsewhere, so be careful. */
2110 	if (Q_streq(name, "right")) {
2111 		cid = CID_RIGHT;
2112 	} else if (Q_streq(name, "left")) {
2113 		cid = CID_LEFT;
2114 	} else if (Q_streq(name, "implant")) {
2115 		cid = CID_IMPLANT;
2116 	} else if (Q_streq(name, "belt")) {
2117 		cid = CID_BELT;
2118 	} else if (Q_streq(name, "holster")) {
2119 		cid = CID_HOLSTER;
2120 	} else if (Q_streq(name, "backpack")) {
2121 		cid = CID_BACKPACK;
2122 	} else if (Q_streq(name, "armour")) {
2123 		cid = CID_ARMOUR;
2124 	} else if (Q_streq(name, "floor")) {
2125 		cid = CID_FLOOR;
2126 	} else if (Q_streq(name, "equip")) {
2127 		cid = CID_EQUIP;
2128 	} else if (Q_streq(name, "headgear")) {
2129 		cid = CID_HEADGEAR;
2130 	} else {
2131 		Sys_Error("Unknown inventory definition \"%s\". Aborting.\n", name);
2132 		return; /* never reached */
2133 	}
2134 
2135 	/* search for containers with same name */
2136 	if (!strncmp(name, csi.ids[cid].name, sizeof(csi.ids[cid].name))) {
2137 		Com_Printf("Com_ParseInventory: inventory def \"%s\" with same name found, second ignored\n", name);
2138 		return;
2139 	}
2140 
2141 	/* initialize the inventory definition */
2142 	invDef_t *id = &csi.ids[cid];
2143 	OBJZERO(*id);
2144 
2145 	if (!Com_ParseBlock(name, text, id, idps, nullptr))
2146 		return;
2147 
2148 	Q_strncpyz(id->name, name, sizeof(id->name));
2149 	id->id = cid;
2150 
2151 	csi.numIDs++;
2152 }
2153 
2154 /*
2155 ==============================================================================
2156 EQUIPMENT DEFINITION INTERPRETER
2157 ==============================================================================
2158 */
2159 
2160 const char *const name_strings[NAME_NUM_TYPES] = {
2161 	"neutral",
2162 	"female",
2163 	"male",
2164 	"lastname",
2165 	"female_lastname",
2166 	"male_lastname"
2167 };
2168 
2169 /** @brief Valid equipment definition values from script files. */
2170 static const value_t equipment_definition_vals[] = {
2171 	{"mininterest", V_INT, offsetof(equipDef_t, minInterest), MEMBER_SIZEOF(equipDef_t, minInterest)},
2172 	{"maxinterest", V_INT, offsetof(equipDef_t, maxInterest), MEMBER_SIZEOF(equipDef_t, maxInterest)},
2173 	{"name", V_TRANSLATION_STRING, offsetof(equipDef_t, name), 0},
2174 
2175 	{nullptr, V_NULL, 0, 0}
2176 };
2177 
Com_ParseEquipment(const char * name,const char ** text)2178 static void Com_ParseEquipment (const char *name, const char** text)
2179 {
2180 	const char *errhead = "Com_ParseEquipment: unexpected end of file (equipment ";
2181 	equipDef_t *ed;
2182 	const char *token;
2183 	int i, n;
2184 
2185 	/* search for equipments with same name */
2186 	for (i = 0; i < csi.numEDs; i++)
2187 		if (Q_streq(name, csi.eds[i].id))
2188 			break;
2189 
2190 	if (i < csi.numEDs) {
2191 		Com_Printf("Com_ParseEquipment: equipment def \"%s\" with same name found, second ignored\n", name);
2192 		return;
2193 	}
2194 
2195 	if (i >= MAX_EQUIPDEFS)
2196 		Sys_Error("Com_ParseEquipment: MAX_EQUIPDEFS exceeded\n");
2197 
2198 	/* initialize the equipment definition */
2199 	ed = &csi.eds[csi.numEDs++];
2200 	OBJZERO(*ed);
2201 
2202 	Q_strncpyz(ed->id, name, sizeof(ed->id));
2203 	ed->name = ed->id;
2204 
2205 	/* get it's body */
2206 	token = Com_Parse(text);
2207 
2208 	if (!*text || *token != '{') {
2209 		Com_Printf("Com_ParseEquipment: equipment def \"%s\" without body ignored\n", name);
2210 		csi.numEDs--;
2211 		return;
2212 	}
2213 
2214 	do {
2215 		token = Com_EParse(text, errhead, name);
2216 		if (!*text || *token == '}')
2217 			return;
2218 
2219 		if (!Com_ParseBlockToken(name, text, ed, equipment_definition_vals, com_genericPool, token)) {
2220 			if (Q_streq(token, "item")) {
2221 				linkedList_t *list;
2222 				if (!Com_ParseList(text, &list)) {
2223 					Com_Error(ERR_DROP, "Com_ParseEquipment: error while reading equipment item tuple");
2224 				}
2225 				if (LIST_Count(list) != 2) {
2226 					Com_Error(ERR_DROP, "Com_ParseEquipment: equipment item tuple must contains 2 elements (id amount)");
2227 				}
2228 				const char *itemToken = (char*)list->data;
2229 				const char *amountToken = (char*)list->next->data;
2230 
2231 				const objDef_t *od;
2232 				od = INVSH_GetItemByID(itemToken);
2233 				if (od) {
2234 					n = atoi(amountToken);
2235 					if (ed->numItems[od->idx])
2236 						Com_Printf("Com_ParseEquipment: item '%s' is used several times in def '%s'. Only last entry will be taken into account.\n",
2237 							od->id, name);
2238 					if (n)
2239 						ed->numItems[od->idx] = n;
2240 				} else {
2241 					Com_Printf("Com_ParseEquipment: unknown token \"%s\" ignored (equipment %s)\n", itemToken, name);
2242 				}
2243 				LIST_Delete(&list);
2244 			} else if (Q_streq(token, "aircraft")) {
2245 				linkedList_t *list;
2246 				if (!Com_ParseList(text, &list)) {
2247 					Com_Error(ERR_DROP, "Com_ParseEquipment: error while reading equipment aircraft tuple");
2248 				}
2249 				if (LIST_Count(list) != 2) {
2250 					Com_Error(ERR_DROP, "Com_ParseEquipment: equipment aircraft tuple must contains 2 elements (id amount)");
2251 				}
2252 				const char *aircraftToken = (char*)list->data;
2253 				const char *amountToken = (char*)list->next->data;
2254 
2255 				humanAircraftType_t type;
2256 				type = Com_DropShipShortNameToID(aircraftToken);
2257 				n = atoi(amountToken);
2258 				if (ed->numAircraft[type])
2259 					Com_Printf("Com_ParseEquipment: aircraft type '%i' is used several times in def '%s'. Only last entry will be taken into account.\n",
2260 						type, name);
2261 				if (n)
2262 					ed->numAircraft[type] = n;
2263 				LIST_Delete(&list);
2264 			} else {
2265 				Sys_Error("unknown token in equipment in definition %s: '%s'", ed->id, token);
2266 			}
2267 		}
2268 	} while (*text);
2269 }
2270 
2271 
2272 /*
2273 ==============================================================================
2274 NAME AND TEAM DEFINITION INTERPRETER
2275 ==============================================================================
2276 */
2277 
2278 /**
2279  * @param[in] gender 1 (female) or 2 (male)
2280  * @param[in] td The team definition to get the name from
2281  * @sa Com_GetCharacterValues
2282  */
Com_GiveName(int gender,const teamDef_t * td)2283 static const char *Com_GiveName (int gender, const teamDef_t *td)
2284 {
2285 	int j, name = 0;
2286 	linkedList_t *list;
2287 
2288 #ifdef DEBUG
2289 	for (j = 0; j < NAME_NUM_TYPES; j++)
2290 		name += td->numNames[j];
2291 	if (!name)
2292 		Sys_Error("Could not find any valid name definitions for category '%s'\n", td->id);
2293 #endif
2294 	/* found category */
2295 	if (!td->numNames[gender]) {
2296 #ifdef DEBUG
2297 		Com_DPrintf(DEBUG_ENGINE, "No valid name definitions for gender %i in category '%s'\n", gender, td->id);
2298 #endif
2299 		return nullptr;
2300 	}
2301 	name = rand() % td->numNames[gender];
2302 
2303 	/* skip names */
2304 	list = td->names[gender];
2305 	for (j = 0; j < name; j++) {
2306 		assert(list);
2307 		list = list->next;
2308 	}
2309 
2310 	/* store the name */
2311 	return (const char*)list->data;
2312 }
2313 
2314 /**
2315  * @param[in] gender 1 (female) or 2 (male)
2316  * @param[in] td The team definition
2317  * @sa Com_GetCharacterValues
2318  */
Com_GiveModel(int gender,const teamDef_t * td)2319 static teamDef_t::model_t const* Com_GiveModel (int gender, const teamDef_t *td)
2320 {
2321 	const linkedList_t *list;
2322 
2323 	/* found category */
2324 	if (!td->numModels[gender]) {
2325 		Com_Printf("Com_GiveModel: no models defined for gender %i and category '%s'\n", gender, td->id);
2326 		return nullptr;
2327 	}
2328 
2329 	/* search one of the model definitions */
2330 	size_t n = rand() % td->numModels[gender];
2331 
2332 	/* skip models and unwanted info */
2333 	list = td->models[gender];
2334 	while (n-- != 0) {
2335 		assert(list);
2336 		list = list->next;
2337 	}
2338 
2339 	/* return the value */
2340 	return static_cast<teamDef_t::model_t const*>(list->data);
2341 }
2342 
2343 /**
2344  * @brief Returns the actor sounds for a given category
2345  * @param[in] td teamDef pointer
2346  * @param[in] gender The gender of the actor
2347  * @param[in] soundType Which sound category (actorSound_t)
2348  */
Com_GetActorSound(teamDef_t * td,int gender,actorSound_t soundType)2349 const char *Com_GetActorSound (teamDef_t *td, int gender, actorSound_t soundType)
2350 {
2351 	int random, j;
2352 	linkedList_t *list;
2353 
2354 	if (!td)
2355 		return nullptr;
2356 
2357 	if (gender < 0 || gender >= NAME_LAST) {
2358 		Com_DPrintf(DEBUG_SOUND|DEBUG_CLIENT, "Com_GetActorSound: invalid gender: %i\n", gender);
2359 		return nullptr;
2360 	}
2361 	if (td->numSounds[soundType][gender] <= 0) {
2362 		Com_DPrintf(DEBUG_SOUND|DEBUG_CLIENT, "Com_GetActorSound: no sound defined for soundtype: %i, teamID: '%s', gender: %i\n", soundType, td->id, gender);
2363 		return nullptr;
2364 	}
2365 
2366 	random = rand() % td->numSounds[soundType][gender];
2367 	list = td->sounds[soundType][gender];
2368 	for (j = 0; j < random; j++) {
2369 		assert(list);
2370 		list = list->next;
2371 	}
2372 
2373 	assert(list);
2374 	assert(list->data);
2375 	return (const char*)list->data;
2376 }
2377 
2378 /**
2379  * @brief Returns the teamDef pointer for the searched team id - or nullptr if not
2380  * found in the teamDef array
2381  * @param[in] team The team id (given in ufo-script files)
2382  */
Com_GetTeamDefinitionByID(const char * team)2383 const teamDef_t *Com_GetTeamDefinitionByID (const char *team)
2384 {
2385 	int i;
2386 
2387 	/* get team definition */
2388 	for (i = 0; i < csi.numTeamDefs; i++) {
2389 		const teamDef_t *t = &csi.teamDef[i];
2390 		if (Q_streq(team, t->id))
2391 			return t;
2392 	}
2393 
2394 	Com_Printf("Com_GetTeamDefinitionByID: could not find team definition for '%s' in team definitions\n", team);
2395 	return nullptr;
2396 }
2397 
Com_GetCharacterModel(character_t * chr)2398 bool Com_GetCharacterModel (character_t *chr)
2399 {
2400 	if (!chr->teamDef)
2401 		return false;
2402 
2403 	/* get model */
2404 	teamDef_t::model_t const* const model = Com_GiveModel(chr->gender, chr->teamDef);
2405 	if (!model)
2406 		return false;
2407 
2408 	Q_strncpyz(chr->path, model->path, sizeof(chr->path));
2409 	Q_strncpyz(chr->body, model->body, sizeof(chr->body));
2410 	Q_strncpyz(chr->head, model->head, sizeof(chr->head));
2411 	chr->bodySkin = model->bodySkin;
2412 	chr->headSkin = model->headSkin;
2413 
2414 	return true;
2415 }
2416 
2417 /**
2418  * @brief Return a random (weighted by number of models) gender for this teamDef.
2419  * @param[in] teamDef pointer to the teamDef to get the gender for.
2420  * @return A valid gender for the teamDef.
2421  */
Com_GetGender(const teamDef_t * teamDef)2422 static int Com_GetGender (const teamDef_t *teamDef)
2423 {
2424 	int gender;
2425 	int numModels = 0;
2426 	for (gender = 0; gender < NAME_LAST; ++gender)
2427 		if (teamDef->numNames[gender] > 0 && teamDef->numNames[gender + NAME_LAST] > 0)
2428 			numModels += teamDef->numModels[gender];
2429 	if (numModels == 0)
2430 		Com_Error(ERR_DROP, "Could not set character values for team '%s'", teamDef->name);
2431 	int roll = rand() % numModels;
2432 	for (gender = 0; gender < NAME_LAST; ++gender)
2433 		if (teamDef->numNames[gender] > 0 && teamDef->numNames[gender + NAME_LAST] > 0) {
2434 			if (roll < teamDef->numModels[gender])
2435 				break;
2436 			roll -= teamDef->numModels[gender];
2437 		}
2438 	return gender;
2439 }
2440 
2441 /**
2442  * @brief Assign character values, 3D models and names to a character.
2443  * @param[in] teamDefition The team definition id to use to generate the character values.
2444  * @param[in,out] chr The character that should get the paths to the different models/skins.
2445  * @sa Com_GiveName
2446  * @sa Com_GiveModel
2447  */
Com_GetCharacterValues(const char * teamDefition,character_t * chr)2448 void Com_GetCharacterValues (const char *teamDefition, character_t *chr)
2449 {
2450 	int retry = 1000;
2451 
2452 	assert(chr);
2453 
2454 	chr->teamDef = Com_GetTeamDefinitionByID(teamDefition);
2455 	if (chr->teamDef == nullptr)
2456 		Com_Error(ERR_DROP, "Com_GetCharacterValues: could not find team '%s' in team definitions", teamDefition);
2457 
2458 	if (chr->teamDef->size != ACTOR_SIZE_INVALID)
2459 		chr->fieldSize = chr->teamDef->size;
2460 	else
2461 		chr->fieldSize = ACTOR_SIZE_NORMAL;
2462 
2463 	/* get the models */
2464 	while (retry--) {
2465 		const char *str;
2466 		const int gender = Com_GetGender(chr->teamDef);
2467 
2468 		chr->gender = gender;
2469 
2470 		/* get name */
2471 		str = Com_GiveName(gender, chr->teamDef);
2472 		if (!str)
2473 			continue;
2474 		Q_strncpyz(chr->name, str, sizeof(chr->name));
2475 		Q_strcat(chr->name, sizeof(chr->name), " ");
2476 		str = Com_GiveName(gender + NAME_LAST, chr->teamDef);
2477 		if (!str)
2478 			continue;
2479 		Q_strcat(chr->name, sizeof(chr->name), "%s", str);
2480 
2481 		if (!Com_GetCharacterModel(chr))
2482 			continue;
2483 		return;
2484 	}
2485 	Com_Error(ERR_DROP, "Could not set character values for team '%s'\n", teamDefition);
2486 }
2487 
2488 /**
2489  * @brief Parses "name" definition from team_* ufo script files
2490  * @sa Com_ParseActors
2491  * @sa Com_ParseScripts
2492  */
Com_ParseActorNames(const char * name,const char ** text)2493 static void Com_ParseActorNames (const char *name, const char** text)
2494 {
2495 	const char *errhead = "Com_ParseNames: unexpected end of file (names ";
2496 	const char *token;
2497 	teamNames_t nameList;
2498 
2499 	LIST_Foreach(csi.actorNames, teamNames_t, names) {
2500 		if (Q_streq(name, names->id)) {
2501 			Com_Printf("Com_ParseActorNames: Name list with same name found, second ignored '%s'\n", name);
2502 			return;
2503 		}
2504 	}
2505 
2506 	OBJZERO(nameList);
2507 	Q_strncpyz(nameList.id, name, sizeof(nameList.id));
2508 
2509 	/* get name list body */
2510 	token = Com_Parse(text);
2511 	if (!*text || *token != '{') {
2512 		Com_Printf("Com_ParseActorNames: names def \"%s\" without body ignored\n", name);
2513 		return;
2514 	}
2515 
2516 	do {
2517 		/* get the name type */
2518 		token = Com_EParse(text, errhead, name);
2519 		if (!*text)
2520 			break;
2521 		if (*token == '}')
2522 			break;
2523 
2524 		int nameType = Com_FindNameType(token);
2525 
2526 		linkedList_t *list;
2527 		if (!Com_ParseList(text, &list)) {
2528 			Com_Error(ERR_DROP, "Com_ParseActorNames: error while reading names (%s)", name);
2529 		}
2530 
2531 		for (linkedList_t *element = list; element != nullptr; element = element->next) {
2532 			/* some names can be translatable */
2533 			const char *n = (char*)element->data;
2534 			if (*n == '_')
2535 				token++;
2536 			LIST_AddString(&nameList.names[nameType], n);
2537 			nameList.numNames[nameType]++;
2538 		}
2539 		LIST_Delete(&list);
2540 
2541 		/* lastname is different */
2542 		/* fill female and male lastnames from neutral lastnames */
2543 		if (nameType == NAME_LAST) {
2544 			for (int i = NAME_NUM_TYPES - 1; i > NAME_LAST; i--) {
2545 				nameList.names[i] = nameList.names[NAME_LAST];
2546 				nameList.numNames[i] = nameList.numNames[NAME_LAST];
2547 			}
2548 		}
2549 
2550 	} while (*text);
2551 
2552 	if (nameList.numNames[NAME_FEMALE] && !nameList.numNames[NAME_FEMALE_LAST])
2553 		Sys_Error("Com_ParseNames: '%s' has no female lastname category\n", nameList.id);
2554 	if (nameList.numNames[NAME_MALE] && !nameList.numNames[NAME_MALE_LAST])
2555 		Sys_Error("Com_ParseNames: '%s' has no male lastname category\n", nameList.id);
2556 	if (nameList.numNames[NAME_NEUTRAL] && !nameList.numNames[NAME_LAST])
2557 		Sys_Error("Com_ParseNames: '%s' has no neutral lastname category\n", nameList.id);
2558 
2559 	LIST_Add(&csi.actorNames, nameList);
2560 }
2561 
2562 /**
2563  * @brief Parses "actors" definition from team_* ufo script files
2564  * @sa Com_ParseNames
2565  * @sa Com_ParseScripts
2566  */
Com_ParseActorModels(const char * name,const char ** text,teamDef_t * td)2567 static void Com_ParseActorModels (const char *name, const char** text, teamDef_t *td)
2568 {
2569 	const char *errhead = "Com_ParseActorModels: unexpected end of file (actors ";
2570 	const char *token;
2571 
2572 	/* get name list body body */
2573 	token = Com_Parse(text);
2574 
2575 	if (!*text || *token != '{') {
2576 		Com_Printf("Com_ParseActorModels: actor def \"%s\" without body ignored\n", td->id);
2577 		return;
2578 	}
2579 
2580 	do {
2581 		/* get the name type */
2582 		token = Com_EParse(text, errhead, name);
2583 		if (!*text)
2584 			break;
2585 		if (*token == '}')
2586 			break;
2587 
2588 		const int nameType = Com_FindNameType(token);
2589 		if (nameType == -1) {
2590 			Com_Error(ERR_DROP, "Com_ParseActorModels: name type \"%s\" unknown", token);
2591 		}
2592 
2593 		linkedList_t *list;
2594 		if (!Com_ParseList(text, &list)) {
2595 			Com_Error(ERR_DROP, "Com_ParseActorModels: error while reading model tuple (%s)", name);
2596 		}
2597 		if (LIST_Count(list) != 5) {
2598 			LIST_Delete(&list);
2599 			Com_Error(ERR_DROP, "Com_ParseActorModels: model tuple must contains 5 elements");
2600 		}
2601 
2602 		linkedList_t *element = list;
2603 		const char *pathToken = (const char*)element->data;
2604 		element = element->next;
2605 		const char *bodyToken = (const char*)element->data;
2606 		element = element->next;
2607 		const char *headToken = (const char*)element->data;
2608 		element = element->next;
2609 		const char *bodySkinToken = (const char*)element->data;
2610 		element = element->next;
2611 		const char *headSkinToken = (const char*)element->data;
2612 
2613 		teamDef_t::model_t model;
2614 		model.path = Mem_StrDup(pathToken);
2615 		model.body = Mem_StrDup(bodyToken);
2616 		model.head = Mem_StrDup(headToken);
2617 		model.bodySkin = atoi(bodySkinToken);
2618 		model.headSkin = atoi(headSkinToken);
2619 
2620 		LIST_Add(&td->models[nameType], model);
2621 		td->numModels[nameType]++;
2622 		LIST_Delete(&list);
2623 
2624 	} while (*text);
2625 }
2626 
2627 /**
2628  * @brief Parses "actorsounds" definition from team_* ufo script files
2629  * @sa Com_ParseNames
2630  * @sa Com_ParseScripts
2631  */
Com_ParseActorSounds(const char * name,const char ** text,teamDef_t * td)2632 static void Com_ParseActorSounds (const char *name, const char** text, teamDef_t *td)
2633 {
2634 	const char *const errhead = "Com_ParseActorSounds: unexpected end of file (actorsounds ";
2635 	const char *token;
2636 	int i;
2637 
2638 	/* get name list body body */
2639 	token = Com_Parse(text);
2640 
2641 	if (!*text || *token != '{') {
2642 		Com_Printf("Com_ParseActorSounds: actorsounds def \"%s\" without body ignored\n", name);
2643 		return;
2644 	}
2645 
2646 	do {
2647 		/* get the name type */
2648 		token = Com_EParse(text, errhead, name);
2649 		if (!*text)
2650 			break;
2651 		if (*token == '}')
2652 			break;
2653 
2654 		for (i = 0; i < NAME_LAST; i++)
2655 			if (Q_streq(token, name_strings[i])) {
2656 				token = Com_EParse(text, errhead, name);
2657 				if (!*text)
2658 					break;
2659 				if (*token != '{')
2660 					break;
2661 
2662 				do {
2663 					/* get the sounds */
2664 					token = Com_EParse(text, errhead, name);
2665 					if (!*text)
2666 						break;
2667 					if (*token == '}')
2668 						break;
2669 					if (Q_streq(token, "hurtsound")) {
2670 						token = Com_EParse(text, errhead, name);
2671 						if (!*text)
2672 							break;
2673 						LIST_AddString(&td->sounds[SND_HURT][i], token);
2674 						td->numSounds[SND_HURT][i]++;
2675 					} else if (Q_streq(token, "deathsound")) {
2676 						token = Com_EParse(text, errhead, name);
2677 						if (!*text)
2678 							break;
2679 						LIST_AddString(&td->sounds[SND_DEATH][i], token);
2680 						td->numSounds[SND_DEATH][i]++;
2681 					} else {
2682 						Com_Printf("Com_ParseActorSounds: unknown token \"%s\" ignored (actorsounds %s)\n", token, name);
2683 					}
2684 				} while (*text);
2685 				break; /* next gender sound definition */
2686 			}
2687 
2688 		if (i == NAME_NUM_TYPES)
2689 			Com_Printf("Com_ParseActorSounds: unknown token \"%s\" ignored (actorsounds %s)\n", token, name);
2690 
2691 	} while (*text);
2692 }
2693 
Com_GetBodyTemplateByID(const char * id)2694 static const BodyData* Com_GetBodyTemplateByID (const char *id)
2695 {
2696 	LIST_Foreach(csi.bodyTemplates, BodyData, bd)
2697 		if (Q_streq(id, bd->id()))
2698 			return bd;
2699 	Com_Printf("Com_GetBodyTemplateByID: could not find template: '%s'\n", id);
2700 	return nullptr;
2701 }
2702 
Com_GetNameListByID(const char * id)2703 static const teamNames_t *Com_GetNameListByID (const char *id)
2704 {
2705 	LIST_Foreach(csi.actorNames, teamNames_t, names)
2706 		if (Q_streq(id, names->id))
2707 			return names;
2708 	Com_Printf("Com_GetNameListByID: could not find name list: '%s'\n", id);
2709 	return nullptr;
2710 }
2711 
2712 /** @brief possible teamdesc values (ufo-scriptfiles) */
2713 static const value_t teamDefValues[] = {
2714 	{"tech", V_STRING, offsetof(teamDef_t, tech), 0}, /**< tech id from research.ufo */
2715 	{"footstepsound", V_STRING, offsetof(teamDef_t, footstepSound), 0}, /**< play this sound for footsteps - overrides the terrain definitions */
2716 	{"name", V_TRANSLATION_STRING, offsetof(teamDef_t, name), 0}, /**< internal team name */
2717 	{"armour", V_BOOL, offsetof(teamDef_t, armour), MEMBER_SIZEOF(teamDef_t, armour)}, /**< are these team members able to wear armour? */
2718 	{"weapons", V_BOOL, offsetof(teamDef_t, weapons), MEMBER_SIZEOF(teamDef_t, weapons)}, /**< are these team members able to use weapons? */
2719 	{"size", V_INT, offsetof(teamDef_t, size), MEMBER_SIZEOF(teamDef_t, size)}, /**< What size is this unit on the field (1=1x1 or 2=2x2)? */
2720 	{"hit_particle", V_STRING, offsetof(teamDef_t, hitParticle), 0}, /**< What particle effect should be spawned if a unit of this type is hit? */
2721 	{"death_texture", V_STRING, offsetof(teamDef_t, deathTextureName), 0},
2722 	{"team", V_TEAM, offsetof(teamDef_t, team), MEMBER_SIZEOF(teamDef_t, team)},
2723 	{"robot", V_BOOL, offsetof(teamDef_t, robot), MEMBER_SIZEOF(teamDef_t, robot)},
2724 
2725 	{nullptr, V_NULL, 0, 0}
2726 };
2727 
Com_ParseTeam(const char * name,const char ** text)2728 static void Com_ParseTeam (const char *name, const char** text)
2729 {
2730 	teamDef_t *td;
2731 	const char *errhead = "Com_ParseTeam: unexpected end of file (team ";
2732 	const char *token;
2733 	int i;
2734 
2735 	/* check for additions to existing name categories */
2736 	for (i = 0, td = csi.teamDef; i < csi.numTeamDefs; i++, td++)
2737 		if (Q_streq(td->id, name))
2738 			break;
2739 
2740 	/* reset new category */
2741 	if (i == csi.numTeamDefs) {
2742 		if (csi.numTeamDefs < MAX_TEAMDEFS) {
2743 			OBJZERO(*td);
2744 			/* index backlink */
2745 			td->idx = csi.numTeamDefs;
2746 			csi.numTeamDefs++;
2747 		} else {
2748 			Com_Printf("CL_ParseTeam: Too many team definitions, '%s' ignored.\n", name);
2749 			return;
2750 		}
2751 	} else {
2752 		Com_Printf("CL_ParseTeam: Team with same name found, second ignored '%s'\n", name);
2753 		Com_SkipBlock(text);
2754 		return;
2755 	}
2756 
2757 	Q_strncpyz(td->id, name, sizeof(td->id));
2758 	td->armour = td->weapons = true; /* default values */
2759 	td->onlyWeapon = nullptr;
2760 
2761 	/* get name list body body */
2762 	token = Com_Parse(text);
2763 
2764 	if (!*text || *token != '{') {
2765 		Com_Printf("Com_ParseTeam: team def \"%s\" without body ignored\n", name);
2766 		if (csi.numTeamDefs - 1 == td - csi.teamDef)
2767 			csi.numTeamDefs--;
2768 		return;
2769 	}
2770 
2771 	do {
2772 		/* get the name type */
2773 		token = Com_EParse(text, errhead, name);
2774 		if (!*text)
2775 			break;
2776 		if (*token == '}')
2777 			break;
2778 
2779 		if (!Com_ParseBlockToken(name, text, td, teamDefValues, nullptr, token)) {
2780 			if (Q_streq(token, "onlyWeapon")) {
2781 				const objDef_t *od;
2782 				token = Com_EParse(text, errhead, name);
2783 				if (!*text)
2784 					return;
2785 				od = INVSH_GetItemByID(token);
2786 
2787 				if (od)
2788 					td->onlyWeapon = od;
2789 				else
2790 					Sys_Error("Com_ParseTeam: Could not get item definition for '%s'", token);
2791 			} else if (Q_streq(token, "templates")) {
2792 				linkedList_t *list;
2793 				if (!Com_ParseList(text, &list)) {
2794 					Com_Error(ERR_DROP, "Com_ParseTeam: error while reading templates (team \"%s\")", name);
2795 				}
2796 
2797 				for (linkedList_t *element = list; element != nullptr; element = element->next) {
2798 					for (i = 0; i < td->numTemplates; i++) {
2799 						if (Q_streq(token, td->characterTemplates[i]->id)) {
2800 							Com_Printf("Com_ParseTeam: template %s used more than once in team def %s second ignored", (char*)element->data, name);
2801 							break;
2802 						}
2803 					}
2804 					if (i >= td->numTemplates) {
2805 						const chrTemplate_t *ct = Com_GetCharacterTemplateByID((char*)element->data);
2806 						if (ct)
2807 							td->characterTemplates[td->numTemplates++] = ct;
2808 						else
2809 							Sys_Error("Com_ParseTeam: Could not get character template for '%s' in %s", (char*)element->data, name);
2810 					} else
2811 						break;
2812 				}
2813 				LIST_Delete(&list);
2814 			} else if (Q_streq(token, "bodytype")) {
2815 				const BodyData *bd;
2816 				token = Com_EParse(text, errhead, name);
2817 				bd = Com_GetBodyTemplateByID(token);
2818 				if (bd == nullptr)
2819 					Sys_Error("Com_ParseTeam: Could not find body type %s in team def %s\n", token, name);
2820 				td->bodyTemplate = bd;
2821 			} else if (Q_streq(token, "names")) {
2822 				const teamNames_t *nameList;
2823 				token = Com_EParse(text, errhead, name);
2824 				nameList = Com_GetNameListByID(token);
2825 				if (nameList == nullptr)
2826 					Sys_Error("Com_ParseTeam: Could not find name list %s in team def %s\n", token, name);
2827 				td->names = nameList->names;
2828 				td->numNames = nameList->numNames;
2829 			} else if (Q_streq(token, "models"))
2830 				Com_ParseActorModels(name, text, td);
2831 			else if (Q_streq(token, "actorsounds"))
2832 				Com_ParseActorSounds(name, text, td);
2833 			else if (Q_streq(token, "resistance"))
2834 				Com_ParseArmourOrResistance(name, text, td->resistance, false);
2835 			else
2836 				Com_Printf("Com_ParseTeam: unknown token \"%s\" ignored (team %s)\n", token, name);
2837 		}
2838 	} while (*text);
2839 
2840 	if (td->deathTextureName[0] == '\0') {
2841 		const int i = rand() % MAX_DEATH;
2842 		Q_strncpyz(td->deathTextureName, va("pics/sfx/blood_%i", i), sizeof(td->deathTextureName));
2843 		Com_DPrintf(DEBUG_CLIENT, "Using random blood for teamdef: '%s' (%i)\n", td->id, i);
2844 	}
2845 	if (td->bodyTemplate == nullptr)
2846 		Sys_Error("Teamdef without body data: %s\n", td->id);
2847 }
2848 
2849 /**
2850  * @brief Returns the chrTemplate pointer for the given id - or nullptr if not found in the chrTemplates
2851  * array
2852  * @param[in] chrTemplate The character template id (given in ufo-script files)
2853  */
Com_GetCharacterTemplateByID(const char * chrTemplate)2854 const chrTemplate_t *Com_GetCharacterTemplateByID (const char *chrTemplate)
2855 {
2856 	int i;
2857 
2858 	if (Q_strnull(chrTemplate))
2859 		return nullptr;
2860 
2861 	/* get character template */
2862 	for (i = 0; i < csi.numChrTemplates; i++)
2863 		if (Q_streq(chrTemplate, csi.chrTemplates[i].id))
2864 			return &csi.chrTemplates[i];
2865 
2866 	Com_Printf("Com_GetCharacterTemplateByID: could not find character template: '%s'\n", chrTemplate);
2867 	return nullptr;
2868 }
2869 
2870 static const value_t ugvValues[] = {
2871 	{"tu", V_INT, offsetof(ugv_t, tu), MEMBER_SIZEOF(ugv_t, tu)},
2872 	{"weapon", V_STRING, offsetof(ugv_t, weapon), 0},
2873 	{"armour", V_STRING, offsetof(ugv_t, armour), 0},
2874 	{"actors", V_STRING, offsetof(ugv_t, actors), 0},
2875 	{"price", V_INT, offsetof(ugv_t, price), 0},
2876 
2877 	{nullptr, V_NULL, 0, 0}
2878 };
2879 
2880 /**
2881  * @brief Parse 2x2 units (e.g. UGVs)
2882  * @sa CL_ParseClientData
2883  */
Com_ParseUGVs(const char * name,const char ** text)2884 static void Com_ParseUGVs (const char *name, const char** text)
2885 {
2886 	ugv_t *ugv;
2887 	int i;
2888 
2889 	for (i = 0; i < csi.numUGV; i++) {
2890 		if (Q_streq(name, csi.ugvs[i].id)) {
2891 			Com_Printf("Com_ParseUGVs: ugv \"%s\" with same name already loaded\n", name);
2892 			return;
2893 		}
2894 	}
2895 
2896 	if (csi.numUGV >= MAX_UGV) {
2897 		Com_Printf("Com_ParseUGVs: Too many UGV descriptions, '%s' ignored.\n", name);
2898 		return;
2899 	}
2900 
2901 	/* parse ugv */
2902 	ugv = &csi.ugvs[csi.numUGV];
2903 	OBJZERO(*ugv);
2904 
2905 	if (Com_ParseBlock(name, text, ugv, ugvValues, nullptr)) {
2906 		ugv->id = Mem_PoolStrDup(name, com_genericPool, 0);
2907 		ugv->idx = csi.numUGV;
2908 		csi.numUGV++;
2909 	}
2910 }
2911 
2912 /**
2913  * @brief Parses character templates from scripts
2914  */
Com_ParseCharacterTemplate(const char * name,const char ** text)2915 static void Com_ParseCharacterTemplate (const char *name, const char** text)
2916 {
2917 	const char *errhead = "Com_ParseCharacterTemplate: unexpected end of file";
2918 	const char *token;
2919 	chrTemplate_t *ct;
2920 	int i;
2921 
2922 	for (i = 0; i < csi.numChrTemplates; i++)
2923 		if (Q_streq(name, csi.chrTemplates[i].id)) {
2924 			Com_Printf("Com_ParseCharacterTemplate: Template with same name found, second ignored '%s'\n", name);
2925 			return;
2926 		}
2927 
2928 	if (i >= MAX_CHARACTER_TEMPLATES)
2929 		Sys_Error("Com_ParseCharacterTemplate: too many character templates");
2930 
2931 	/* initialize the character template */
2932 	ct = &csi.chrTemplates[csi.numChrTemplates++];
2933 	OBJZERO(*ct);
2934 
2935 	Q_strncpyz(ct->id, name, sizeof(ct->id));
2936 
2937 	token = Com_Parse(text);
2938 
2939 	if (!*text || *token != '{') {
2940 		Com_Printf("Com_ParseCharacterTemplate: character template \"%s\" without body ignored\n", name);
2941 		csi.numChrTemplates--;
2942 		return;
2943 	}
2944 
2945 	do {
2946 		token = Com_EParse(text, errhead, name);
2947 		if (!*text || *token == '}')
2948 			return;
2949 
2950 		for (i = 0; i < SKILL_NUM_TYPES + 1; i++)
2951 			if (Q_streq(token, skillNames[i])) {
2952 				/* found a definition */
2953 				token = Com_EParse(text, errhead, name);
2954 				if (!*text)
2955 					return;
2956 
2957 				Com_EParseValue(ct->skills[i], token, V_INT2, 0, sizeof(ct->skills[i]));
2958 				break;
2959 			}
2960 		if (i >= SKILL_NUM_TYPES + 1) {
2961 			if (Q_streq(token, "rate")) {
2962 				token = Com_EParse(text, errhead, name);
2963 				if (!*text)
2964 					return;
2965 				ct->rate = atof(token);
2966 			} else
2967 				Com_Printf("Com_ParseCharacterTemplate: unknown token \"%s\" ignored (template %s)\n", token, name);
2968 		}
2969 	} while (*text);
2970 }
2971 
2972 static const value_t bodyPartValues[] = {
2973 		{"name", V_TRANSLATION_STRING, offsetof(BodyPartData, name), 0},
2974 		{"hit_area", V_COLOR, offsetof(BodyPartData, shape), MEMBER_SIZEOF(BodyPartData, shape)},
2975 		{"bleeding_rate", V_INT, offsetof(BodyPartData, bleedingFactor), MEMBER_SIZEOF(BodyPartData, bleedingFactor)},
2976 		{"wound_threshold", V_INT, offsetof(BodyPartData, woundThreshold), MEMBER_SIZEOF(BodyPartData, woundThreshold)},
2977 
2978 		{nullptr, V_NULL, 0, 0}
2979 };
2980 
2981 static const char *const penaltyNames[MODIFIER_MAX] = {
2982 		"accuracy", "shooting_tu", "movement_tu", "detection", "reaction_time", "max_tu"
2983 };
2984 
Com_ParseBodyPart(const char * name,const char ** text,BodyData * bd)2985 static void Com_ParseBodyPart (const char *name, const char** text, BodyData *bd)
2986 {
2987 	const char *errhead = "Com_ParseBodyPart: unexpected end of file";
2988 	const char *token;
2989 	BodyPartData bp;
2990 	int i;
2991 
2992 	for (i = 0; i < bd->numBodyParts(); i++) {
2993 		if (Q_streq(name, bd->id(i))) {
2994 			Com_Printf("Com_ParseBodyPart: BodyPart with same name found, second ignored '%s'\n", name);
2995 			return;
2996 		}
2997 	}
2998 
2999 	if (i > BODYPART_MAXTYPE) {
3000 		Com_Printf("Com_ParseBodyPart: too many BodyParts '%s' ignored ('%s')\n", name, bd->id());
3001 	}
3002 
3003 	OBJZERO(bp);
3004 	Q_strncpyz(bp.id, name, sizeof(bp.id));
3005 
3006 	token = Com_Parse(text);
3007 
3008 	if (!*text || *token != '{') {
3009 		Com_Printf("Com_ParseBodyPart: BodyPart '%s' without body ignored\n", name);
3010 		return;
3011 	}
3012 
3013 	do {
3014 		token = Com_EParse(text, errhead, name);
3015 		if (!*text || *token == '}')
3016 			break;
3017 
3018 		if (!Com_ParseBlockToken(name, text, &bp, bodyPartValues, nullptr, token)) {
3019 			if (Q_streq(token, "penalty")) {
3020 				linkedList_t *list;
3021 				if (!Com_ParseList(text, &list)) {
3022 					Com_Error(ERR_DROP, "Com_ParseBodyPart: error while reading penalties ('%s')", name);
3023 				}
3024 
3025 				if (LIST_Count(list) != 2) {
3026 					LIST_Delete(&list);
3027 					Com_Error(ERR_DROP, "Com_ParseBodyPart: penalty tuple must contain 2 elements ('%s')", name);
3028 				}
3029 
3030 				linkedList_t *element = list;
3031 				for (i = 0; i < MODIFIER_MAX; i++) {
3032 					if (Q_streq(static_cast<const char*>(element->data), penaltyNames[i])) {
3033 						/* Found a definition */
3034 						element = element->next;
3035 						Com_EParseValue(&bp.penalties[i], static_cast<const char*>(element->data), V_INT, 0, sizeof(bp.penalties[i]));
3036 						break;
3037 					}
3038 				}
3039 				if (i >= MODIFIER_MAX)
3040 					Com_Printf("Com_ParseBodyPart: Unknown penalty '%s' ignored ('%s')\n", static_cast<const char*>(element->data), name);
3041 
3042 				LIST_Delete(&list);
3043 			} else {
3044 				Com_Printf("Com_ParseBodyPart: Unknown token '%s' ignored ('%s')\n", token, name);
3045 			}
3046 		}
3047 	} while (*text);
3048 
3049 	bd->addBodyPart(bp);
3050 }
3051 
Com_ParseBodyTemplate(const char * name,const char ** text)3052 static void Com_ParseBodyTemplate (const char *name, const char** text)
3053 {
3054 	const char *errhead = "Com_ParseBodyTemplate: unexpected end of file";
3055 	const char *token;
3056 	BodyData bd;
3057 
3058 	LIST_Foreach(csi.bodyTemplates, BodyData, bt) {
3059 		if (Q_streq(name, bt->id())) {
3060 			Com_Printf("Com_ParseBodyTemplate: BodyTemplate with same name found, second ignored '%s'\n", name);
3061 			return;
3062 		}
3063 	}
3064 
3065 	token = Com_Parse(text);
3066 
3067 	if (!*text || *token != '{') {
3068 		Com_Printf("Com_ParseBodyTemplate: body template '%s' without body ignored\n", name);
3069 		return;
3070 	}
3071 
3072 	bd.setId(name);
3073 
3074 	do {
3075 		token = Com_EParse(text, errhead, name);
3076 		if (!*text || *token == '}')
3077 			break;
3078 
3079 		if (Q_streq(token, "bodypart")) {
3080 			token = Com_EParse(text, errhead, name);
3081 			if (!*text)
3082 				break;
3083 
3084 			Com_ParseBodyPart (token, text, &bd);
3085 		} else {
3086 			Com_Printf("Com_ParseBodyTemplate: unknown token '%s' ignored ('%s')\n", token, name);
3087 		}
3088 	} while (*text);
3089 
3090 	if (bd.numBodyParts() < 1) {
3091 		Com_Printf("Body template without bodyparts %s ignored!\n", name);
3092 		return;
3093 	}
3094 
3095 	LIST_Add(&csi.bodyTemplates, bd);
3096 }
3097 
3098 /*
3099 ==============================================================================
3100 TERRAIN PARSERS
3101 ==============================================================================
3102 */
3103 
3104 #define TERRAIN_HASH_SIZE 64
3105 static terrainType_t *terrainTypesHash[TERRAIN_HASH_SIZE];
3106 
3107 static const value_t terrainTypeValues[] = {
3108 	{"footstepsound", V_HUNK_STRING, offsetof(terrainType_t, footstepSound), 0},
3109 	{"particle", V_HUNK_STRING, offsetof(terrainType_t, particle), 0},
3110 	{"footstepvolume", V_FLOAT, offsetof(terrainType_t, footstepVolume), 0},
3111 	{"bouncefraction", V_FLOAT, offsetof(terrainType_t, bounceFraction), 0},
3112 
3113 	{nullptr, V_NULL, 0, 0}
3114 };
3115 
3116 /**
3117  * @brief Searches the terrain definition if given
3118  * @param[in] textureName The terrain definition id from script files
3119  * which is the texture name relative to base/textures
3120  */
Com_GetTerrainType(const char * textureName)3121 const terrainType_t *Com_GetTerrainType (const char *textureName)
3122 {
3123 	unsigned hash;
3124 	const terrainType_t *t;
3125 
3126 	assert(textureName);
3127 	hash = Com_HashKey(textureName, TERRAIN_HASH_SIZE);
3128 	for (t = terrainTypesHash[hash]; t; t = t->hash_next) {
3129 		if (Q_streq(textureName, t->texture))
3130 			return t;
3131 	}
3132 
3133 	return nullptr;
3134 }
3135 
3136 /**
3137  * @brief Parses "terrain" definition from script files
3138  * @note Terrain definitions are used for footstep sounds and terrain particles
3139  * @sa Com_ParseScripts
3140  */
Com_ParseTerrain(const char * name,const char ** text)3141 static void Com_ParseTerrain (const char *name, const char** text)
3142 {
3143 
3144 	/* check for additions to existing name categories */
3145 	if (Com_GetTerrainType(name) != nullptr) {
3146 		Com_Printf("Terrain definition with same name already parsed: '%s'\n", name);
3147 		return;
3148 	}
3149 
3150 	terrainType_t* const t = Mem_PoolAllocType(terrainType_t, com_genericPool);
3151 	t->footstepVolume = SND_VOLUME_FOOTSTEPS;
3152 	t->bounceFraction = 1.0f;
3153 
3154 	if (Com_ParseBlock(name, text, t, terrainTypeValues, com_genericPool)) {
3155 		const unsigned hash = Com_HashKey(name, TERRAIN_HASH_SIZE);
3156 		t->texture = Mem_PoolStrDup(name, com_genericPool, 0);
3157 		/* link in terrainTypesHash[hash] should be nullptr on the first run */
3158 		t->hash_next = terrainTypesHash[hash];
3159 		terrainTypesHash[hash] = t;
3160 	} else {
3161 		Mem_Free(t);
3162 	}
3163 }
3164 
3165 /*
3166 ==============================================================================
3167 GAMETYPE INTERPRETER
3168 ==============================================================================
3169 */
3170 
3171 /** @brief possible gametype values for the gameserver (ufo-scriptfiles) */
3172 static const value_t gameTypeValues[] = {
3173 	{"name", V_TRANSLATION_STRING, offsetof(gametype_t, name), 0}, /**< translated game-type name for menu displaying */
3174 	{nullptr, V_NULL, 0, 0}
3175 };
3176 
Com_ParseGameTypes(const char * name,const char ** text)3177 static void Com_ParseGameTypes (const char *name, const char** text)
3178 {
3179 	const char *errhead = "Com_ParseGameTypes: unexpected end of file (gametype ";
3180 	const char *token;
3181 	int i;
3182 	gametype_t *gt;
3183 	cvarlist_t *cvarlist;
3184 
3185 	/* get it's body */
3186 	token = Com_Parse(text);
3187 	if (!*text || *token != '{') {
3188 		Com_Printf("Com_ParseGameTypes: gametype \"%s\" without body ignored\n", name);
3189 		return;
3190 	}
3191 
3192 	/* search for game types with same name */
3193 	for (i = 0; i < csi.numGTs; i++)
3194 		if (!strncmp(token, csi.gts[i].id, MAX_VAR))
3195 			break;
3196 
3197 	if (i == csi.numGTs) {
3198 		if (i >= MAX_GAMETYPES)
3199 			Sys_Error("Com_ParseGameTypes: MAX_GAMETYPES exceeded");
3200 		gt = &csi.gts[csi.numGTs++];
3201 		OBJZERO(*gt);
3202 		Q_strncpyz(gt->id, name, sizeof(gt->id));
3203 		if (csi.numGTs >= MAX_GAMETYPES)
3204 			Sys_Error("Com_ParseGameTypes: Too many gametypes.");
3205 
3206 		do {
3207 			token = Com_EParse(text, errhead, name);
3208 			if (!*text)
3209 				break;
3210 			if (*token == '}')
3211 				break;
3212 
3213 			if (!Com_ParseBlockToken(name, text, gt, gameTypeValues, nullptr, token)) {
3214 				if (!Q_streq(token, "cvarlist"))
3215 					Sys_Error("Com_ParseGameTypes: gametype \"%s\" without cvarlist", name);
3216 
3217 				token = Com_EParse(text, errhead, name);
3218 				if (!*text)
3219 					break;
3220 				if (*token != '{')
3221 					Sys_Error("Com_ParseGameTypes: gametype \"%s\" without cvarlist", name);
3222 
3223 				do {
3224 					token = Com_EParse(text, errhead, name);
3225 					if (!*text || *token == '}') {
3226 						if (!gt->num_cvars)
3227 							Sys_Error("Com_ParseGameTypes: gametype \"%s\" with empty cvarlist", name);
3228 						else
3229 							break;
3230 					}
3231 					/* initial pointer */
3232 					cvarlist = &gt->cvars[gt->num_cvars++];
3233 					if (gt->num_cvars >= MAX_CVARLISTINGAMETYPE)
3234 						Sys_Error("Com_ParseGameTypes: gametype \"%s\" max cvarlist hit", name);
3235 					Q_strncpyz(cvarlist->name, token, sizeof(cvarlist->name));
3236 					token = Com_EParse(text, errhead, name);
3237 					if (!*text || *token == '}')
3238 						Sys_Error("Com_ParseGameTypes: gametype \"%s\" cvar \"%s\" with no value", name, cvarlist->name);
3239 					Q_strncpyz(cvarlist->value, token, sizeof(cvarlist->value));
3240 				} while (*text && *token != '}');
3241 			}
3242 		} while (*text);
3243 	} else {
3244 		Com_Printf("Com_ParseGameTypes: gametype \"%s\" with same already exists - ignore the second one\n", name);
3245 		Com_SkipBlock(text);
3246 	}
3247 }
3248 
3249 /*
3250 ==============================================================================
3251 DAMAGE TYPES INTERPRETER
3252 ==============================================================================
3253 */
3254 
Com_ParseDamageTypes(const char * name,const char ** text)3255 static void Com_ParseDamageTypes (const char *name, const char** text)
3256 {
3257 	const char *errhead = "Com_ParseDamageTypes: unexpected end of file (damagetype ";
3258 	const char *token;
3259 	int i;
3260 
3261 	/* get it's body */
3262 	token = Com_Parse(text);
3263 
3264 	if (!*text || *token != '{') {
3265 		Com_Printf("Com_ParseDamageTypes: damage type list \"%s\" without body ignored\n", name);
3266 		return;
3267 	}
3268 
3269 	do {
3270 		token = Com_EParse(text, errhead, name);
3271 		if (!*text)
3272 			break;
3273 		if (*token == '}')
3274 			break;
3275 
3276 		/* Gettext marker (also indicates that it is a dmgtype value - additional to being a dmgweight value) */
3277 		if (*token == '_') {
3278 			token++;
3279 			csi.dts[csi.numDTs].showInMenu = true;
3280 		}
3281 
3282 		/* search for damage types with same name */
3283 		for (i = 0; i < csi.numDTs; i++)
3284 			if (Q_streq(token, csi.dts[i].id))
3285 				break;
3286 
3287 		/* Not found in the for loop. */
3288 		if (i == csi.numDTs) {
3289 			Q_strncpyz(csi.dts[csi.numDTs].id, token, sizeof(csi.dts[csi.numDTs].id));
3290 
3291 			/* Special IDs */
3292 			if (Q_streq(token, "normal"))
3293 				csi.damNormal = csi.numDTs;
3294 			else if (Q_streq(token, "blast"))
3295 				csi.damBlast = csi.numDTs;
3296 			else if (Q_streq(token, "fire"))
3297 				csi.damFire = csi.numDTs;
3298 			else if (Q_streq(token, "shock"))
3299 				csi.damShock = csi.numDTs;
3300 			else if (Q_streq(token, "laser"))
3301 				csi.damLaser = csi.numDTs;
3302 			else if (Q_streq(token, "plasma"))
3303 				csi.damPlasma = csi.numDTs;
3304 			else if (Q_streq(token, "particlebeam"))
3305 				csi.damParticle = csi.numDTs;
3306 			else if (Q_streq(token, "stun_electro"))
3307 				csi.damStunElectro = csi.numDTs;
3308 			else if (Q_streq(token, "stun_gas"))
3309 				csi.damStunGas = csi.numDTs;
3310 			else if (Q_streq(token, "smoke"))
3311 				csi.damSmoke = csi.numDTs;
3312 			else if (Q_streq(token, "incendiary"))
3313 				csi.damIncendiary = csi.numDTs;
3314 
3315 			csi.numDTs++;
3316 			if (csi.numDTs >= MAX_DAMAGETYPES)
3317 				Sys_Error("Com_ParseDamageTypes: Too many damage types.");
3318 		} else {
3319 			Com_Printf("Com_ParseDamageTypes: damage type \"%s\" in list \"%s\" with same already exists - ignore the second one (#%i)\n", token, name, csi.numDTs);
3320 		}
3321 	} while (*text);
3322 }
3323 
3324 
3325 /*
3326 ==============================================================================
3327 MAIN SCRIPT PARSING FUNCTION
3328 ==============================================================================
3329 */
3330 
3331 /**
3332  * @brief Returns the name of an aircraft or an ufo that is used in the ump files for
3333  * the random map assembly
3334  * @see cvar rm_drop, rm_ufo, rm_crashed
3335  * @note Uses a static buffer - so after you got the name you should ensure that you
3336  * put it into a proper location. Otherwise it will get overwritten with the next call
3337  * of this function.
3338  */
Com_GetRandomMapAssemblyNameForCraft(const char * craftID)3339 const char *Com_GetRandomMapAssemblyNameForCraft (const char *craftID)
3340 {
3341 	return va("+%s", craftID);
3342 }
3343 
3344 /**
3345  * @todo implement this in a better way
3346  */
Com_GetRandomMapAssemblyNameForCrashedCraft(const char * craftID)3347 const char *Com_GetRandomMapAssemblyNameForCrashedCraft (const char *craftID)
3348 {
3349 	if (Q_streq(craftID, "craft_drop_firebird"))
3350 		return "+craft_crash_drop_firebird";
3351 	else if (Q_streq(craftID, "craft_drop_raptor"))
3352 		return "+craft_crash_drop_raptor";
3353 	else if (Q_streq(craftID, "craft_inter_dragon"))
3354 		return "+craft_crash_inter_dragon";
3355 	else if (Q_streq(craftID, "craft_inter_saracen"))
3356 		return "+craft_crash_inter_saracen";
3357 	else if (Q_streq(craftID, "craft_inter_starchaser"))
3358 		return "+craft_crash_inter_starchaser";
3359 
3360 	return "";
3361 }
3362 
3363 /**
3364  * @brief Translate DropShip type to short name.
3365  * @return Will always return a valid human aircraft type or errors out if the given token can't
3366  * be mapped to an human aircraft type
3367  * @sa Com_DropShipTypeToShortName
3368  */
Com_DropShipShortNameToID(const char * token)3369 humanAircraftType_t Com_DropShipShortNameToID (const char *token)
3370 {
3371 	humanAircraftType_t aircraftType;
3372 	size_t dummy;
3373 	Com_ParseValue(&aircraftType, token, V_AIRCRAFTTYPE, 0, sizeof(aircraftType), &dummy);
3374 	return aircraftType;
3375 }
3376 
3377 /**
3378  * @brief Translate DropShip type to short name.
3379  * @sa Com_DropShipShortNameToID
3380  */
Com_DropShipTypeToShortName(humanAircraftType_t type)3381 const char *Com_DropShipTypeToShortName (humanAircraftType_t type)
3382 {
3383 	return Com_ValueToStr(&type, V_AIRCRAFTTYPE, 0);
3384 }
3385 
3386 /**
3387  * @brief Translate UFO type to short name.
3388  * @sa UFO_TypeToName
3389  * @sa Com_UFOTypeToShortName
3390  */
Com_UFOShortNameToID(const char * token)3391 ufoType_t Com_UFOShortNameToID (const char *token)
3392 {
3393 	ufoType_t ufoType;
3394 	size_t dummy;
3395 	Com_ParseValue(&ufoType, token, V_UFO, 0, sizeof(ufoType), &dummy);
3396 	return ufoType;
3397 }
3398 
3399 /**
3400  * @brief Translate UFO type to short name.
3401  * @sa UFO_TypeToName
3402  * @sa Com_UFOShortNameToID
3403  */
Com_UFOTypeToShortName(ufoType_t type)3404 const char *Com_UFOTypeToShortName (ufoType_t type)
3405 {
3406 	return Com_ValueToStr(&type, V_UFO, 0);
3407 }
3408 
3409 /**
3410  * @brief Translate UFO type to short name when UFO is crashed.
3411  * @sa Com_UFOTypeToShortName
3412  */
Com_UFOCrashedTypeToShortName(ufoType_t type)3413 const char *Com_UFOCrashedTypeToShortName (ufoType_t type)
3414 {
3415 	return Com_ValueToStr(&type, V_UFOCRASHED, 0);
3416 }
3417 
3418 /**
3419  * @brief Searches an UGV definition by a given script id and returns the pointer to the global data
3420  * @param[in] ugvID The script id of the UGV definition you are looking for
3421  * @return ugv_t pointer or nullptr if not found.
3422  * @note This function gives no warning on null name or if no ugv found
3423  */
Com_GetUGVByIDSilent(const char * ugvID)3424 const ugv_t *Com_GetUGVByIDSilent (const char *ugvID)
3425 {
3426 	int i;
3427 
3428 	if (!ugvID)
3429 		return nullptr;
3430 	for (i = 0; i < csi.numUGV; i++) {
3431 		const ugv_t *ugv = &csi.ugvs[i];
3432 		if (Q_streq(ugv->id, ugvID)) {
3433 			return ugv;
3434 		}
3435 	}
3436 	return nullptr;
3437 }
3438 
3439 /**
3440  * @brief Searches an UGV definition by a given script id and returns the pointer to the global data
3441  * @param[in] ugvID The script id of the UGV definition you are looking for
3442  * @return ugv_t pointer or nullptr if not found.
3443  */
Com_GetUGVByID(const char * ugvID)3444 const ugv_t *Com_GetUGVByID (const char *ugvID)
3445 {
3446 	const ugv_t *ugv = Com_GetUGVByIDSilent(ugvID);
3447 
3448 	if (!ugvID)
3449 		Com_Printf("Com_GetUGVByID Called with nullptr ugvID!\n");
3450 	else if (!ugv)
3451 		Com_Printf("Com_GetUGVByID: No ugv_t entry found for id '%s' in %i entries.\n", ugvID, csi.numUGV);
3452 	return ugv;
3453 }
3454 
3455 /**
3456  * @brief Creates links to other items (i.e. ammo<->weapons)
3457  */
Com_AddObjectLinks(void)3458 static void Com_AddObjectLinks (void)
3459 {
3460 	/* Add links to weapons. */
3461 	LIST_Foreach (parseItemWeapons, parseItemWeapon_t, parse) {
3462 		const int weaponsIdx = parse->numWeapons;
3463 		const char *id = parse->token;
3464 
3465 		/* Link the weapon pointers for this item. */
3466 		parse->od->weapons[weaponsIdx] = INVSH_GetItemByID(id);
3467 		if (!parse->od->weapons[weaponsIdx]) {
3468 			Sys_Error("Com_AddObjectLinks: Could not get item '%s' for linking into item '%s'\n",
3469 				id , parse->od->id);
3470 		}
3471 
3472 		/* Back-link the obj-idx inside the fds */
3473 		for (int k = 0; k < parse->od->numFiredefs[weaponsIdx]; k++) {
3474 			parse->od->fd[weaponsIdx][k].obj = parse->od;
3475 		}
3476 
3477 		Mem_Free(parse->token);
3478 	}
3479 
3480 	/* Clear the temporary list. */
3481 	LIST_Delete(&parseItemWeapons);
3482 
3483 	/* Add links to ammos */
3484 	objDef_t *od;
3485 	int i;
3486 	for (i = 0, od = csi.ods; i < csi.numODs; i++, od++) {
3487 		od->numAmmos = 0;	/* Default value */
3488 		if (od->weapon || od->craftitem.type <= AC_ITEM_WEAPON) {
3489 			/* this is a weapon, an aircraft weapon, or a base defence system */
3490 			for (int n = 0; n < csi.numODs; n++) {
3491 				const objDef_t *weapon = INVSH_GetItemByIDX(n);
3492 				for (int m = 0; m < weapon->numWeapons; m++) {
3493 					if (weapon->weapons[m] == od) {
3494 						assert(od->numAmmos <= MAX_AMMOS_PER_OBJDEF);
3495 						od->ammos[od->numAmmos++] = weapon;
3496 						Com_DPrintf(DEBUG_SHARED, "link ammo %s to weapon: %s\n", weapon->id, od->id);
3497 					}
3498 				}
3499 			}
3500 		}
3501 	}
3502 }
3503 
3504 /** @brief valid mapdef descriptors */
3505 static const value_t mapdef_vals[] = {
3506 	{"description", V_TRANSLATION_STRING, offsetof(mapDef_t, description), 0},
3507 	{"victorycondition", V_TRANSLATION_STRING, offsetof(mapDef_t, victoryCondition), 0},
3508 	{"missionbriefing", V_TRANSLATION_STRING, offsetof(mapDef_t, missionBriefing), 0},
3509 	{"map", V_HUNK_STRING, offsetof(mapDef_t, map), 0},
3510 	{"size", V_HUNK_STRING, offsetof(mapDef_t, size), 0},
3511 	{"civilianteam", V_HUNK_STRING, offsetof(mapDef_t, civTeam), 0},
3512 
3513 	{"maxaliens", V_INT, offsetof(mapDef_t, maxAliens), MEMBER_SIZEOF(mapDef_t, maxAliens)},
3514 	{"hwclass", V_INT, offsetof(mapDef_t, hwclass), MEMBER_SIZEOF(mapDef_t, hwclass)},
3515 	{"storyrelated", V_BOOL, offsetof(mapDef_t, storyRelated), MEMBER_SIZEOF(mapDef_t, storyRelated)},
3516 	{"nocunit", V_BOOL, offsetof(mapDef_t, nocunit), MEMBER_SIZEOF(mapDef_t, nocunit)},
3517 
3518 	{"teams", V_INT, offsetof(mapDef_t, teams), MEMBER_SIZEOF(mapDef_t, teams)},
3519 	{"multiplayer", V_BOOL, offsetof(mapDef_t, multiplayer), MEMBER_SIZEOF(mapDef_t, multiplayer)},
3520 	{"singleplayer", V_BOOL, offsetof(mapDef_t, singleplayer), MEMBER_SIZEOF(mapDef_t, singleplayer)},
3521 	{"campaign", V_BOOL, offsetof(mapDef_t, campaign), MEMBER_SIZEOF(mapDef_t, campaign)},
3522 
3523 	{"onwin", V_HUNK_STRING, offsetof(mapDef_t, onwin), 0},
3524 	{"onlose", V_HUNK_STRING, offsetof(mapDef_t, onlose), 0},
3525 
3526 	{"ufos", V_LIST, offsetof(mapDef_t, ufos), 0},
3527 	{"aircraft", V_LIST, offsetof(mapDef_t, aircraft), 0},
3528 	{"terrains", V_LIST, offsetof(mapDef_t, terrains), 0},
3529 	{"populations", V_LIST, offsetof(mapDef_t, populations), 0},
3530 	{"cultures", V_LIST, offsetof(mapDef_t, cultures), 0},
3531 	{"gametypes", V_LIST, offsetof(mapDef_t, gameTypes), 0},
3532 
3533 	{nullptr, V_NULL, 0, 0}
3534 };
3535 
Com_ParseMapDefinition(const char * name,const char ** text)3536 static void Com_ParseMapDefinition (const char *name, const char** text)
3537 {
3538 	const char *errhead = "Com_ParseMapDefinition: unexpected end of file (mapdef ";
3539 	mapDef_t *md;
3540 	const char *token;
3541 
3542 	/* get it's body */
3543 	token = Com_Parse(text);
3544 
3545 	if (!*text || *token != '{') {
3546 		Com_Printf("Com_ParseMapDefinition: mapdef \"%s\" without body ignored\n", name);
3547 		return;
3548 	}
3549 
3550 	md = Com_GetMapDefByIDX(csi.numMDs);
3551 	csi.numMDs++;
3552 	if (csi.numMDs >= lengthof(csi.mds))
3553 		Sys_Error("Com_ParseMapDefinition: Max mapdef hit");
3554 
3555 	OBJZERO(*md);
3556 	md->id = Mem_PoolStrDup(name, com_genericPool, 0);
3557 	md->singleplayer = true;
3558 	md->campaign = true;
3559 	md->multiplayer = false;
3560 
3561 	do {
3562 		token = Com_EParse(text, errhead, name);
3563 		if (!*text)
3564 			break;
3565 		if (*token == '}')
3566 			break;
3567 
3568 		if (!Com_ParseBlockToken(name, text, md, mapdef_vals, com_genericPool, token)) {
3569 			if (Q_streq(token, "params")) {
3570 				Com_ParseList(text, &md->params);
3571 			} else {
3572 				Com_Printf("Com_ParseMapDefinition: unknown token \"%s\" ignored (mapdef %s)\n", token, name);
3573 				continue;
3574 			}
3575 		}
3576 	} while (*text);
3577 
3578 	if (!md->map) {
3579 		Com_Printf("Com_ParseMapDefinition: mapdef \"%s\" with no map\n", name);
3580 		csi.numMDs--;
3581 	}
3582 
3583 	if (!md->description) {
3584 		Com_Printf("Com_ParseMapDefinition: mapdef \"%s\" with no description\n", name);
3585 		csi.numMDs--;
3586 	}
3587 
3588 	if (md->maxAliens <= 0) {
3589 		Com_Printf("Com_ParseMapDefinition: mapdef \"%s\" with invalid maxAlien value\n", name);
3590 		csi.numMDs--;
3591 	}
3592 
3593 	/* Skip if the hardware can't handle this map. */
3594 	if (hwclass->integer < md->hwclass) {
3595 		Com_DPrintf(DEBUG_SHARED, "Com_ParseMapDefinition: mapdef \"%s\" is skipped because hwclass doesn't match\n", name);
3596 		csi.numMDs--;
3597 	}
3598 }
3599 
Com_GetMapDefByIDX(int index)3600 mapDef_t *Com_GetMapDefByIDX (int index)
3601 {
3602 	return &csi.mds[index];
3603 }
3604 
Com_GetMapDefinitionByID(const char * mapDefID)3605 mapDef_t *Com_GetMapDefinitionByID (const char *mapDefID)
3606 {
3607 	mapDef_t *md;
3608 
3609 	assert(mapDefID);
3610 
3611 	MapDef_Foreach(md) {
3612 		if (Q_streq(md->id, mapDefID))
3613 			return md;
3614 	}
3615 
3616 	Com_DPrintf(DEBUG_SHARED, "Com_GetMapDefinition: Could not find mapdef with id: '%s'\n", mapDefID);
3617 	return nullptr;
3618 }
3619 
3620 /**
3621  * @sa CL_ParseClientData
3622  * @sa CL_ParseScriptFirst
3623  * @sa CL_ParseScriptSecond
3624  * @sa Qcommon_Init
3625  */
Com_ParseScripts(bool onlyServer)3626 void Com_ParseScripts (bool onlyServer)
3627 {
3628 	const char *type, *name, *text;
3629 
3630 	Com_Printf("\n----------- parse scripts ----------\n");
3631 
3632 	/* reset csi basic info */
3633 	INVSH_InitCSI(&csi);
3634 	csi.damNormal = csi.damBlast = csi.damFire = csi.damShock = csi.damLaser = csi.damPlasma = csi.damParticle = csi.damStunElectro = csi.damStunGas = NONE;
3635 	csi.damSmoke = csi.damIncendiary = NONE;
3636 
3637 	/* pre-stage parsing */
3638 	Com_Printf("%i script files\n", FS_BuildFileList("ufos/*.ufo"));
3639 	text = nullptr;
3640 
3641 	FS_NextScriptHeader(nullptr, nullptr, nullptr);
3642 
3643 	while ((type = FS_NextScriptHeader("ufos/*.ufo", &name, &text)) != nullptr)
3644 		if (Q_streq(type, "damagetypes"))
3645 			Com_ParseDamageTypes(name, &text);
3646 		else if (Q_streq(type, "gametype"))
3647 			Com_ParseGameTypes(name, &text);
3648 		else if (Q_streq(type, "version"))
3649 			Com_ParseVersion(name);
3650 
3651 	/* stage one parsing */
3652 	FS_NextScriptHeader(nullptr, nullptr, nullptr);
3653 	text = nullptr;
3654 
3655 	while ((type = FS_NextScriptHeader("ufos/*.ufo", &name, &text)) != nullptr) {
3656 		/* server/client scripts */
3657 		if (Q_streq(type, "item") || Q_streq(type, "craftitem"))
3658 			Com_ParseItem(name, &text);
3659 		else if (Q_streq(type, "inventory"))
3660 			Com_ParseInventory(name, &text);
3661 		else if (Q_streq(type, "terrain"))
3662 			Com_ParseTerrain(name, &text);
3663 		else if (Q_streq(type, "ugv"))
3664 			Com_ParseUGVs(name, &text);
3665 		else if (Q_streq(type, "chrtemplate"))
3666 			Com_ParseCharacterTemplate(name, &text);
3667 		else if (Q_streq(type, "mapdef"))
3668 			Com_ParseMapDefinition(name, &text);
3669 		else if (Q_streq(type, "bodydef"))
3670 			Com_ParseBodyTemplate(name, &text);
3671 		else if (Q_streq(type, "names"))
3672 			Com_ParseActorNames(name, &text);
3673 		else if (!onlyServer)
3674 			CL_ParseClientData(type, name, &text);
3675 	}
3676 
3677 	if (!versionParsed)
3678 		Sys_Error("Could not find version string for script files");
3679 
3680 	/* Stage two parsing (weapon/inventory dependant stuff). */
3681 	FS_NextScriptHeader(nullptr, nullptr, nullptr);
3682 	text = nullptr;
3683 
3684 	while ((type = FS_NextScriptHeader("ufos/*.ufo", &name, &text)) != nullptr) {
3685 		/* server/client scripts */
3686 		if (Q_streq(type, "equipment"))
3687 			Com_ParseEquipment(name, &text);
3688 		else if (Q_streq(type, "team"))
3689 			Com_ParseTeam(name, &text);
3690 		else if (Q_streq(type, "implant"))
3691 			Com_ParseImplant(name, &text);
3692 	}
3693 
3694 	Com_AddObjectLinks();	/* Add ammo<->weapon links to items.*/
3695 
3696 	/* parse ui node script */
3697 	if (!onlyServer) {
3698 		Com_Printf("%i ui script files\n", FS_BuildFileList("ufos/ui/*.ufo"));
3699 		FS_NextScriptHeader(nullptr, nullptr, nullptr);
3700 		text = nullptr;
3701 		while ((type = FS_NextScriptHeader("ufos/ui/*.ufo", &name, &text)) != nullptr)
3702 			CL_ParseClientData(type, name, &text);
3703 	}
3704 
3705 	Com_Printf("Shared Client/Server Info loaded\n");
3706 	Com_Printf("...%3i items parsed\n", csi.numODs);
3707 	Com_Printf("...%3i damage types parsed\n", csi.numDTs);
3708 	Com_Printf("...%3i equipment definitions parsed\n", csi.numEDs);
3709 	Com_Printf("...%3i inventory definitions parsed\n", csi.numIDs);
3710 	Com_Printf("...%3i team definitions parsed\n", csi.numTeamDefs);
3711 }
3712 
Com_GetScriptChecksum(void)3713 int Com_GetScriptChecksum (void)
3714 {
3715 	static int checksum = 0;
3716 	const char *buf;
3717 
3718 	if (checksum != 0)
3719 		return checksum;
3720 
3721 	while ((buf = FS_GetFileData("ufos/*.ufo")) != nullptr)
3722 		checksum += LittleLong(Com_BlockChecksum(buf, strlen(buf)));
3723 	FS_GetFileData(nullptr);
3724 
3725 	return checksum;
3726 }
3727 
Com_Shutdown(void)3728 void Com_Shutdown (void)
3729 {
3730 	OBJZERO(terrainTypesHash);
3731 	OBJZERO(com_constNameInt_hash);
3732 	com_constNameInt = nullptr;
3733 	versionParsed = false;
3734 }
3735