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 = >->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