1 /**
2 * @file
3 * @todo remove all "token" param from function and use Com_UnParseLastToken
4 * @todo reduce use of uiGlobal (create global functions to add/get/... entities)
5 * @todo remove Com_EParseValue and use Com_ParseValue
6 */
7
8 /*
9 Copyright (C) 2002-2013 UFO: Alien Invasion.
10
11 This program is free software; you can redistribute it and/or
12 modify it under the terms of the GNU General Public License
13 as published by the Free Software Foundation; either version 2
14 of the License, or (at your option) any later version.
15
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
19
20 See the GNU General Public License for more details.
21
22 You should have received a copy of the GNU General Public License
23 along with this program; if not, write to the Free Software
24 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25
26 */
27
28 #include "../client.h"
29 #include "ui_parse.h"
30 #include "ui_main.h"
31 #include "ui_node.h"
32 #include "ui_data.h"
33 #include "ui_internal.h"
34 #include "ui_actions.h"
35 #include "ui_sprite.h"
36 #include "ui_components.h"
37 #include "node/ui_node_window.h"
38 #include "node/ui_node_selectbox.h"
39 #include "node/ui_node_abstractnode.h"
40 #include "node/ui_node_abstractoption.h"
41
42 #include "../../shared/parse.h"
43 #include "../cl_language.h"
44
45 /** prototypes */
46 static bool UI_ParseProperty(void* object, const value_t* property, const char* objectName, const char** text, const char** token);
47 static uiAction_t* UI_ParseActionList(uiNode_t* node, const char** text, const char** token);
48 static uiNode_t* UI_ParseNode(uiNode_t* parent, const char** text, const char** token, const char* errhead);
49
50 /** @brief valid properties for a UI model definition */
51 static const value_t uiModelProperties[] = {
52 {"model", V_HUNK_STRING, offsetof(uiModel_t, model), 0},
53 {"need", V_NULL, 0, 0},
54 {"anim", V_HUNK_STRING, offsetof(uiModel_t, anim), 0},
55 {"skin", V_INT, offsetof(uiModel_t, skin), sizeof(int)},
56 {"color", V_COLOR, offsetof(uiModel_t, color), sizeof(vec4_t)},
57 {"tag", V_HUNK_STRING, offsetof(uiModel_t, tag), 0},
58 {"parent", V_HUNK_STRING, offsetof(uiModel_t, parent), 0},
59
60 {nullptr, V_NULL, 0, 0},
61 };
62
63 /** @brief reserved token preventing calling a node with it
64 * @todo Use dichotomic search
65 */
66 static char const* const reservedTokens[] = {
67 "this",
68 "parent",
69 "root",
70 "null",
71 "super",
72 "node",
73 "cvar",
74 "int",
75 "float",
76 "string",
77 "var",
78 nullptr
79 };
80
UI_TokenIsReserved(const char * name)81 static bool UI_TokenIsReserved (const char* name)
82 {
83 char const* const* token = reservedTokens;
84 while (*token) {
85 if (Q_streq(*token, name))
86 return true;
87 token++;
88 }
89 return false;
90 }
91
UI_TokenIsValue(const char * name,bool isQuoted)92 static bool UI_TokenIsValue (const char* name, bool isQuoted)
93 {
94 assert(name);
95 if (isQuoted)
96 return true;
97 /* is it a number */
98 if ((name[0] >= '0' && name[0] <= '9') || name[0] == '-' || name[0] == '.')
99 return true;
100 /* is it a var (*cvar:...) */
101 if (name[0] == '*')
102 return true;
103 if (Q_streq(name, "true"))
104 return true;
105 if (Q_streq(name, "false"))
106 return true;
107
108 /* uppercase const name */
109 if ((name[0] >= 'A' && name[0] <= 'Z') || name[0] == '_') {
110 bool onlyUpperCase = true;
111 while (*name != '\0') {
112 if ((name[0] >= 'A' && name[0] <= 'Z') || name[0] == '_' || (name[0] >= '0' && name[0] <= '9')) {
113 /* available chars */
114 } else {
115 return false;
116 }
117 name++;
118 }
119 return onlyUpperCase;
120 }
121
122 return false;
123 }
124
UI_TokenIsName(const char * name,bool isQuoted)125 static bool UI_TokenIsName (const char* name, bool isQuoted)
126 {
127 assert(name);
128 if (isQuoted)
129 return false;
130 if ((name[0] >= 'a' && name[0] <= 'z') || (name[0] >= 'A' && name[0] <= 'Z') || name[0] == '_') {
131 bool onlyUpperCase = true;
132 while (*name != '\0') {
133 if (name[0] >= 'a' && name[0] <= 'z') {
134 onlyUpperCase = false;
135 } else if ((name[0] >= '0' && name[0] <= '9') || (name[0] >= 'A' && name[0] <= 'Z') || name[0] == '_') {
136 /* available chars */
137 } else {
138 return false;
139 }
140 name++;
141 }
142 return !onlyUpperCase;
143 }
144 return false;
145 }
146
147 /**
148 * @brief Find a value_t by name into a array of value_t
149 * @param[in] propertyList Array of value_t, with null termination
150 * @param[in] name Property name we search
151 * @return A value_t with the requested name, else nullptr
152 */
UI_FindPropertyByName(const value_t * propertyList,const char * name)153 const value_t* UI_FindPropertyByName (const value_t* propertyList, const char* name)
154 {
155 const value_t* current = propertyList;
156 while (current->string != nullptr) {
157 if (!Q_strcasecmp(name, current->string))
158 return current;
159 current++;
160 }
161 return nullptr;
162 }
163
164 /**
165 * @brief Allocate a float into the UI static memory
166 * @note Its not a dynamic memory allocation. Please only use it at the loading time
167 * @param[in] count number of element need to allocate
168 * @todo Assert out when we are not in parsing/loading stage
169 */
UI_AllocStaticFloat(int count)170 float* UI_AllocStaticFloat (int count)
171 {
172 float* result;
173 assert(count > 0);
174 result = (float*) UI_AllocHunkMemory(sizeof(float) * count, sizeof(float), false);
175 if (result == nullptr)
176 Com_Error(ERR_FATAL, "UI_AllocFloat: UI memory hunk exceeded - increase the size");
177 return result;
178 }
179
180 /**
181 * @brief Allocate a color into the UI static memory
182 * @note Its not a dynamic memory allocation. Please only use it at the loading time
183 * @param[in] count number of element need to allocate
184 * @todo Assert out when we are not in parsing/loading stage
185 */
UI_AllocStaticColor(int count)186 vec4_t* UI_AllocStaticColor (int count)
187 {
188 vec4_t* result;
189 assert(count > 0);
190 result = (vec4_t*) UI_AllocHunkMemory(sizeof(vec_t) * 4 * count, sizeof(vec_t), false);
191 if (result == nullptr)
192 Com_Error(ERR_FATAL, "UI_AllocColor: UI memory hunk exceeded - increase the size");
193 return result;
194 }
195
196 /**
197 * @brief Allocate a string into the UI static memory
198 * @note Its not a dynamic memory allocation. Please only use it at the loading time
199 * @param[in] string Use to initialize the string
200 * @param[in] size request a fixed memory size, if 0 the string size is used
201 * @todo Assert out when we are not in parsing/loading stage
202 */
UI_AllocStaticString(const char * string,int size)203 char* UI_AllocStaticString (const char* string, int size)
204 {
205 char* result;
206 if (size == 0) {
207 size = strlen(string) + 1;
208 }
209 result = (char*) UI_AllocHunkMemory(size, sizeof(char), false);
210 if (result == nullptr)
211 Com_Error(ERR_FATAL, "UI_AllocString: UI memory hunk exceeded - increase the size");
212 Q_strncpyz(result, string, size);
213 return result;
214 }
215
216 /**
217 * @brief Allocate an action
218 * @return An action
219 */
UI_AllocStaticAction(void)220 uiAction_t* UI_AllocStaticAction (void)
221 {
222 if (ui_global.numActions >= UI_MAX_ACTIONS)
223 Com_Error(ERR_FATAL, "UI_AllocAction: Too many UI actions");
224 return &ui_global.actions[ui_global.numActions++];
225 }
226
227 /**
228 * Parse a string according to a property type, and allocate a raw value to the static memory
229 *
230 * @param action Action to initialize
231 * @param node Current node we are parsing, only used for error message
232 * @param property Type of the value to parse, if nullptr the string is not stored as string
233 * @param string String value to parse
234 * @return True if the action is initialized
235 * @todo remove node param and catch error where we call that function
236 */
UI_InitRawActionValue(uiAction_t * action,uiNode_t * node,const value_t * property,const char * string)237 bool UI_InitRawActionValue (uiAction_t* action, uiNode_t* node, const value_t* property, const char* string)
238 {
239 if (property == nullptr) {
240 action->type = EA_VALUE_STRING;
241 action->d.terminal.d1.data = UI_AllocStaticString(string, 0);
242 action->d.terminal.d2.integer = 0;
243 return true;
244 }
245
246 if (property->type == V_UI_SPRITEREF) {
247 uiSprite_t* sprite = UI_GetSpriteByName(string);
248 if (sprite == nullptr) {
249 Com_Printf("UI_ParseSetAction: sprite '%s' not found (%s)\n", string, UI_GetPath(node));
250 return false;
251 }
252 action->type = EA_VALUE_RAW;
253 action->d.terminal.d1.data = sprite;
254 action->d.terminal.d2.integer = property->type;
255 return true;
256 } else {
257 const int baseType = property->type & V_UI_MASK;
258 if (baseType != 0 && baseType != V_UI_CVAR) {
259 Com_Printf("UI_ParseRawValue: setter for property '%s' (type %d, 0x%X) is not supported (%s)\n", property->string, property->type, property->type, UI_GetPath(node));
260 return false;
261 }
262 ui_global.curadata = (byte*) Com_AlignPtr(ui_global.curadata, (valueTypes_t) (property->type & V_BASETYPEMASK));
263 action->type = EA_VALUE_RAW;
264 action->d.terminal.d1.data = ui_global.curadata;
265 action->d.terminal.d2.integer = property->type;
266 /** @todo we should hide use of ui_global.curadata */
267 ui_global.curadata += Com_EParseValue(ui_global.curadata, string, (valueTypes_t) (property->type & V_BASETYPEMASK), 0, property->size);
268 return true;
269 }
270 }
271
272 /**
273 * @brief Parser for setter command
274 */
UI_ParseSetAction(uiNode_t * node,uiAction_t * action,const char ** text,const char ** token,const char * errhead)275 static bool UI_ParseSetAction (uiNode_t* node, uiAction_t* action, const char** text, const char** token, const char* errhead)
276 {
277 const value_t* property;
278 int type;
279 uiAction_t* localAction;
280
281 assert((*token)[0] == '*');
282
283 Com_UnParseLastToken();
284 action->d.nonTerminal.left = UI_ParseExpression(text);
285
286 type = action->d.nonTerminal.left->type;
287 if (type != EA_VALUE_CVARNAME && type != EA_VALUE_CVARNAME_WITHINJECTION
288 && type != EA_VALUE_PATHPROPERTY && type != EA_VALUE_PATHPROPERTY_WITHINJECTION) {
289 Com_Printf("UI_ParseSetAction: Cvar or Node property expected. Type '%i' found\n", type);
290 return false;
291 }
292
293 /* must use "equal" char between name and value */
294 *token = Com_EParse(text, errhead, nullptr);
295 if (!*text)
296 return false;
297 if (!Q_streq(*token, "=")) {
298 Com_Printf("UI_ParseSetAction: Assign sign '=' expected between variable and value. '%s' found in node %s.\n", *token, UI_GetPath(node));
299 return false;
300 }
301
302 /* get the value */
303 if (type == EA_VALUE_CVARNAME || type == EA_VALUE_CVARNAME_WITHINJECTION) {
304 action->d.nonTerminal.right = UI_ParseExpression(text);
305 return true;
306 }
307
308 property = (const value_t*) action->d.nonTerminal.left->d.terminal.d2.data;
309
310 *token = Com_EParse(text, errhead, nullptr);
311 if (!*text)
312 return false;
313
314 if (Q_streq(*token, "{")) {
315 uiAction_t* actionList;
316
317 if (property != nullptr && property->type != V_UI_ACTION) {
318 Com_Printf("UI_ParseSetAction: Property %s@%s do not expect code block.\n", UI_GetPath(node), property->string);
319 return false;
320 }
321
322 actionList = UI_ParseActionList(node, text, token);
323 if (actionList == nullptr)
324 return false;
325
326 localAction = UI_AllocStaticAction();
327 localAction->type = EA_VALUE_RAW;
328 localAction->d.terminal.d1.data = actionList;
329 localAction->d.terminal.d2.integer = V_UI_ACTION;
330 action->d.nonTerminal.right = localAction;
331
332 return true;
333 }
334
335 if (Q_streq(*token, "(")) {
336 Com_UnParseLastToken();
337 action->d.nonTerminal.right = UI_ParseExpression(text);
338 return true;
339 }
340
341 /* @todo everything should come from UI_ParseExpression */
342
343 if (UI_IsInjectedString(*token)) {
344 localAction = UI_AllocStaticAction();
345 localAction->type = EA_VALUE_STRING_WITHINJECTION;
346 localAction->d.terminal.d1.data = UI_AllocStaticString(*token, 0);
347 action->d.nonTerminal.right = localAction;
348 return true;
349 }
350
351 localAction = UI_AllocStaticAction();
352 UI_InitRawActionValue(localAction, node, property, *token);
353 action->d.nonTerminal.right = localAction;
354 return true;
355 }
356
357 /**
358 * @brief Parser for c command
359 */
UI_ParseCallAction(uiNode_t * node,uiAction_t * action,const char ** text,const char ** token,const char * errhead)360 static bool UI_ParseCallAction (uiNode_t* node, uiAction_t* action, const char** text, const char** token, const char* errhead)
361 {
362 uiAction_t* expression;
363 uiAction_t* lastParam = nullptr;
364 int paramID = 0;
365 expression = UI_ParseExpression(text);
366 if (expression == nullptr)
367 return false;
368
369 if (expression->type != EA_VALUE_PATHNODE_WITHINJECTION && expression->type != EA_VALUE_PATHNODE && expression->type != EA_VALUE_PATHPROPERTY && expression->type != EA_VALUE_PATHPROPERTY_WITHINJECTION) {
370 Com_Printf("UI_ParseCallAction: \"call\" keyword only support pathnode and pathproperty (node: %s)\n", UI_GetPath(node));
371 return false;
372 }
373
374 action->d.nonTerminal.left = expression;
375
376 /* check parameters */
377 *token = Com_EParse(text, errhead, nullptr);
378 if ((*token)[0] == '\0')
379 return false;
380
381 /* there is no parameters */
382 if (!Q_streq(*token, "(")) {
383 Com_UnParseLastToken();
384 return true;
385 }
386
387 /* read parameters */
388 do {
389 uiAction_t* param;
390 paramID++;
391
392 /* parameter */
393 param = UI_ParseExpression(text);
394 if (param == nullptr) {
395 Com_Printf("UI_ParseCallAction: problem with the %i parameter\n", paramID);
396 return false;
397 }
398 if (lastParam == nullptr)
399 action->d.nonTerminal.right = param;
400 else
401 lastParam->next = param;
402 lastParam = param;
403
404 /* separator */
405 *token = Com_EParse(text, errhead, nullptr);
406 if (!*token)
407 return false;
408 if (!Q_streq(*token, ",")) {
409 if (Q_streq(*token, ")"))
410 break;
411 Com_UnParseLastToken();
412 Com_Printf("UI_ParseCallAction: Invalidate end of 'call' after param %i\n", paramID);
413 return false;
414 }
415 } while(true);
416
417 return true;
418 }
419
420 /**
421 * @brief Parse actions and return action list
422 * @return The first element from a list of action
423 * @sa ea_t
424 * @todo need cleanup, compute action out of the final memory; reduce number of var
425 */
UI_ParseActionList(uiNode_t * node,const char ** text,const char ** token)426 static uiAction_t* UI_ParseActionList (uiNode_t* node, const char** text, const char** token)
427 {
428 const char* errhead = "UI_ParseActionList: unexpected end of file (in event)";
429 uiAction_t* firstAction;
430 uiAction_t* lastAction;
431 uiAction_t* action;
432
433 lastAction = nullptr;
434 firstAction = nullptr;
435
436 /* prevent bad position */
437 if ((*token)[0] != '{') {
438 Com_Printf("UI_ParseActionList: token \"{\" expected, but \"%s\" found (in event) (node: %s)\n", *token, UI_GetPath(node));
439 return nullptr;
440 }
441
442 while (true) {
443 bool result;
444 int type = EA_NULL;
445
446 /* get new token */
447 *token = Com_EParse(text, errhead, nullptr);
448 if (!*token)
449 return nullptr;
450
451 if ((*token)[0] == '}')
452 break;
453
454 type = UI_GetActionTokenType(*token, EA_ACTION);
455 /* setter form */
456 if (type == EA_NULL && (*token)[0] == '*')
457 type = EA_ASSIGN;
458
459 /* unknown, we break the parsing */
460 if (type == EA_NULL) {
461 Com_Printf("UI_ParseActionList: unknown token \"%s\" ignored (in event) (node: %s)\n", *token, UI_GetPath(node));
462 return nullptr;
463 }
464
465 /* add the action */
466 action = UI_AllocStaticAction();
467 /** @todo better to append the action after initialization */
468 if (lastAction)
469 lastAction->next = action;
470 if (!firstAction)
471 firstAction = action;
472 action->type = type;
473
474 /* decode action */
475 switch (action->type) {
476 case EA_CMD:
477 /* get parameter values */
478 *token = Com_EParse(text, errhead, nullptr);
479 if (!*text)
480 return nullptr;
481
482 /* get the value */
483 action->d.terminal.d1.constString = UI_AllocStaticString(*token, 0);
484 break;
485
486 case EA_ASSIGN:
487 result = UI_ParseSetAction(node, action, text, token, errhead);
488 if (!result)
489 return nullptr;
490 break;
491
492 case EA_CALL:
493 result = UI_ParseCallAction(node, action, text, token, errhead);
494 if (!result)
495 return nullptr;
496 break;
497
498 case EA_DELETE:
499 {
500 uiAction_t* expression;
501 expression = UI_ParseExpression(text);
502 if (expression == nullptr)
503 return nullptr;
504
505 if (expression->type != EA_VALUE_CVARNAME) {
506 Com_Printf("UI_ParseActionList: \"delete\" keyword only support cvarname (node: %s)\n", UI_GetPath(node));
507 return nullptr;
508 }
509
510 action->d.nonTerminal.left = expression;
511 break;
512 }
513
514 case EA_ELIF:
515 /* check previous action */
516 if (!lastAction || (lastAction->type != EA_IF && lastAction->type != EA_ELIF)) {
517 Com_Printf("UI_ParseActionList: 'elif' must be set after an 'if' or an 'elif' (node: %s)\n", UI_GetPath(node));
518 return nullptr;
519 }
520 /* then it execute EA_IF, no break */
521 case EA_WHILE:
522 case EA_IF:
523 {
524 uiAction_t* expression;
525
526 /* get the condition */
527 expression = UI_ParseExpression(text);
528 if (expression == nullptr)
529 return nullptr;
530 action->d.nonTerminal.left = expression;
531
532 /* get the action block */
533 *token = Com_EParse(text, errhead, nullptr);
534 if (!*text)
535 return nullptr;
536 action->d.nonTerminal.right = UI_ParseActionList(node, text, token);
537 if (action->d.nonTerminal.right == nullptr) {
538 if (action->type == EA_IF)
539 Com_Printf("UI_ParseActionList: block expected after \"if\" (node: %s)\n", UI_GetPath(node));
540 else if (action->type == EA_ELIF)
541 Com_Printf("UI_ParseActionList: block expected after \"elif\" (node: %s)\n", UI_GetPath(node));
542 else
543 Com_Printf("UI_ParseActionList: block expected after \"while\" (node: %s)\n", UI_GetPath(node));
544 return nullptr;
545 }
546 break;
547 }
548
549 case EA_ELSE:
550 /* check previous action */
551 if (!lastAction || (lastAction->type != EA_IF && lastAction->type != EA_ELIF)) {
552 Com_Printf("UI_ParseActionList: 'else' must be set after an 'if' or an 'elif' (node: %s)\n", UI_GetPath(node));
553 return nullptr;
554 }
555
556 /* get the action block */
557 *token = Com_EParse(text, errhead, nullptr);
558 if (!*text)
559 return nullptr;
560 action->d.nonTerminal.left = nullptr;
561 action->d.nonTerminal.right = UI_ParseActionList(node, text, token);
562 if (action->d.nonTerminal.right == nullptr) {
563 Com_Printf("UI_ParseActionList: block expected after \"else\" (node: %s)\n", UI_GetPath(node));
564 return nullptr;
565 }
566 break;
567
568 default:
569 assert(false);
570 }
571
572 /* step */
573 lastAction = action;
574 }
575
576 assert((*token)[0] == '}');
577
578 /* return non nullptr value */
579 if (firstAction == nullptr) {
580 firstAction = UI_AllocStaticAction();
581 }
582
583 return firstAction;
584 }
585
UI_ParseExcludeRect(uiNode_t * node,const char ** text,const char ** token,const char * errhead)586 static bool UI_ParseExcludeRect (uiNode_t* node, const char** text, const char** token, const char* errhead)
587 {
588 uiExcludeRect_t rect;
589 uiExcludeRect_t* newRect;
590
591 /* get parameters */
592 *token = Com_EParse(text, errhead, node->name);
593 if (!*text)
594 return false;
595 if ((*token)[0] != '{') {
596 Com_Printf("UI_ParseExcludeRect: node with bad excluderect ignored (node \"%s\")\n", UI_GetPath(node));
597 return true;
598 }
599
600 do {
601 *token = Com_EParse(text, errhead, node->name);
602 if (!*text)
603 return false;
604 /** @todo move it into a property array */
605 if (Q_streq(*token, "pos")) {
606 *token = Com_EParse(text, errhead, node->name);
607 if (!*text)
608 return false;
609 Com_EParseValue(&rect, *token, V_POS, offsetof(uiExcludeRect_t, pos), sizeof(vec2_t));
610 } else if (Q_streq(*token, "size")) {
611 *token = Com_EParse(text, errhead, node->name);
612 if (!*text)
613 return false;
614 Com_EParseValue(&rect, *token, V_POS, offsetof(uiExcludeRect_t, size), sizeof(vec2_t));
615 }
616 } while ((*token)[0] != '}');
617
618 newRect = (uiExcludeRect_t*) UI_AllocHunkMemory(sizeof(*newRect), STRUCT_MEMORY_ALIGN, false);
619 if (newRect == nullptr) {
620 Com_Printf("UI_ParseExcludeRect: ui hunk memory exceeded.");
621 return false;
622 }
623
624 /* move data to final memory and link to node */
625 *newRect = rect;
626 newRect->next = node->firstExcludeRect;
627 node->firstExcludeRect = newRect;
628 return true;
629 }
630
UI_ParseEventProperty(uiNode_t * node,const value_t * event,const char ** text,const char ** token,const char * errhead)631 static bool UI_ParseEventProperty (uiNode_t* node, const value_t* event, const char** text, const char** token, const char* errhead)
632 {
633 /* add new actions to end of list */
634 uiAction_t** action = &Com_GetValue<uiAction_t*>(node, event);
635 for (; *action; action = &(*action)->next) {}
636
637 /* get the action body */
638 *token = Com_EParse(text, errhead, node->name);
639 if (!*text)
640 return false;
641
642 if ((*token)[0] != '{') {
643 Com_Printf("UI_ParseEventProperty: Event '%s' without body (%s)\n", event->string, UI_GetPath(node));
644 return false;
645 }
646
647 *action = UI_ParseActionList(node, text, token);
648 if (*action == nullptr)
649 return false;
650
651 /* block terminal already read */
652 assert((*token)[0] == '}');
653
654 return true;
655 }
656
657 /**
658 * @brief Parse a property value
659 * @todo don't read the next token (need to change the script language)
660 */
UI_ParseProperty(void * object,const value_t * property,const char * objectName,const char ** text,const char ** token)661 static bool UI_ParseProperty (void* object, const value_t* property, const char* objectName, const char** text, const char** token)
662 {
663 const char* errhead = "UI_ParseProperty: unexpected end of file (object";
664 static const char* notWellFormedValue = "UI_ParseProperty: \"%s\" is not a well formed node name (it must be quoted, uppercase const, a number, or prefixed with '*')\n";
665 size_t bytes;
666 int result;
667 const int specialType = property->type & V_UI_MASK;
668
669 if (property->type == V_NULL) {
670 return false;
671 }
672
673 switch (specialType) {
674 case V_NOT_UI: /* common type */
675
676 *token = Com_EParse(text, errhead, objectName);
677 if (!*text)
678 return false;
679 if (!UI_TokenIsValue(*token, Com_GetType(text) == TT_QUOTED_WORD)) {
680 Com_Printf(notWellFormedValue, *token);
681 return false;
682 }
683
684 if (property->type == V_TRANSLATION_STRING) {
685 /* selectbox values are static arrays */
686 char* const target = Com_GetValue<char[]>(object, property);
687 const char* translatableToken = *token;
688 assert(property->size);
689 if (translatableToken[0] == '_')
690 translatableToken++;
691 Q_strncpyz(target, translatableToken, property->size);
692 } else {
693 result = Com_ParseValue(object, *token, property->type, property->ofs, property->size, &bytes);
694 if (result != RESULT_OK) {
695 Com_Printf("UI_ParseProperty: Invalid value for property '%s': %s\n", property->string, Com_GetLastParseError());
696 return false;
697 }
698 }
699 break;
700
701 case V_UI_REF:
702 *token = Com_EParse(text, errhead, objectName);
703 if (!*text)
704 return false;
705 if (!UI_TokenIsValue(*token, Com_GetType(text) == TT_QUOTED_WORD)) {
706 Com_Printf(notWellFormedValue, *token);
707 return false;
708 }
709
710 /* a reference to data is handled like this */
711 ui_global.curadata = (byte*) Com_AlignPtr(ui_global.curadata, (valueTypes_t) (property->type & V_BASETYPEMASK));
712 Com_GetValue<byte*>(object, property) = ui_global.curadata;
713
714 /** @todo check for the moment its not a cvar */
715 assert((*token)[0] != '*');
716
717 /* sanity check */
718 if ((property->type & V_BASETYPEMASK) == V_STRING && strlen(*token) > MAX_VAR - 1) {
719 Com_Printf("UI_ParseProperty: Value '%s' is too long (key %s)\n", *token, property->string);
720 return false;
721 }
722
723 result = Com_ParseValue(ui_global.curadata, *token, (valueTypes_t) (property->type & V_BASETYPEMASK), 0, property->size, &bytes);
724 if (result != RESULT_OK) {
725 Com_Printf("UI_ParseProperty: Invalid value for property '%s': %s\n", property->string, Com_GetLastParseError());
726 return false;
727 }
728 ui_global.curadata += bytes;
729
730 break;
731
732 case V_UI_CVAR: /* common type */
733 *token = Com_EParse(text, errhead, objectName);
734 if (!*text)
735 return false;
736 if (!UI_TokenIsValue(*token, Com_GetType(text) == TT_QUOTED_WORD)) {
737 Com_Printf(notWellFormedValue, *token);
738 return false;
739 }
740
741 /* references are parsed as string */
742 if ((*token)[0] == '*') {
743 /* a reference to data */
744 ui_global.curadata = (byte*) Com_AlignPtr(ui_global.curadata, V_STRING);
745 Com_GetValue<byte*>(object, property) = ui_global.curadata;
746
747 /* sanity check */
748 if (strlen(*token) > MAX_VAR - 1) {
749 Com_Printf("UI_ParseProperty: Value '%s' is too long (key %s)\n", *token, property->string);
750 return false;
751 }
752
753 result = Com_ParseValue(ui_global.curadata, *token, V_STRING, 0, 0, &bytes);
754 if (result != RESULT_OK) {
755 Com_Printf("UI_ParseProperty: Invalid value for property '%s': %s\n", property->string, Com_GetLastParseError());
756 return false;
757 }
758 ui_global.curadata += bytes;
759 } else {
760 /* a reference to data */
761 ui_global.curadata = (byte*) Com_AlignPtr(ui_global.curadata, (valueTypes_t)(property->type & V_BASETYPEMASK));
762 Com_GetValue<byte*>(object, property) = ui_global.curadata;
763
764 /* sanity check */
765 if ((property->type & V_BASETYPEMASK) == V_STRING && strlen(*token) > MAX_VAR - 1) {
766 Com_Printf("UI_ParseProperty: Value '%s' is too long (key %s)\n", *token, property->string);
767 return false;
768 }
769
770 result = Com_ParseValue(ui_global.curadata, *token, (valueTypes_t)(property->type & V_BASETYPEMASK), 0, property->size, &bytes);
771 if (result != RESULT_OK) {
772 Com_Printf("UI_ParseProperty: Invalid value for property '%s': %s\n", property->string, Com_GetLastParseError());
773 return false;
774 }
775 ui_global.curadata += bytes;
776 }
777 break;
778
779 case V_UI:
780
781 switch ((int)property->type) {
782 case V_UI_ACTION:
783 result = UI_ParseEventProperty(static_cast<uiNode_t*>(object), property, text, token, errhead);
784 if (!result)
785 return false;
786 break;
787
788 case V_UI_EXCLUDERECT:
789 result = UI_ParseExcludeRect(static_cast<uiNode_t*>(object), text, token, errhead);
790 if (!result)
791 return false;
792 break;
793
794 case V_UI_SPRITEREF:
795 {
796 *token = Com_EParse(text, errhead, objectName);
797 if (!*text)
798 return false;
799
800 uiSprite_t const*& sprite = Com_GetValue<uiSprite_t const*>(object, property);
801 sprite = UI_GetSpriteByName(*token);
802 if (!sprite) {
803 Com_Printf("UI_ParseProperty: sprite '%s' not found (object %s)\n", *token, objectName);
804 }
805 }
806 break;
807
808 case V_UI_IF:
809 {
810 *token = Com_EParse(text, errhead, objectName);
811 if (!*text)
812 return false;
813
814 uiAction_t*& expression = Com_GetValue<uiAction_t*>(object, property);
815 expression = UI_AllocStaticStringCondition(*token);
816 if (!expression)
817 return false;
818 }
819 break;
820
821 case V_UI_DATAID:
822 {
823 *token = Com_EParse(text, errhead, objectName);
824 if (!*text)
825 return false;
826
827 int& dataId = Com_GetValue<int>(object, property);
828 dataId = UI_GetDataIDByName(*token);
829 if (dataId < 0) {
830 Com_Printf("UI_ParseProperty: Could not find shared data ID '%s' (%s@%s)\n",
831 *token, objectName, property->string);
832 return false;
833 }
834 }
835 break;
836
837 default:
838 Com_Printf("UI_ParseProperty: unknown property type '%d' (0x%X) (%s@%s)\n",
839 property->type, property->type, objectName, property->string);
840 return false;
841 }
842 break;
843
844 default:
845 Com_Printf("UI_ParseProperties: unknown property type '%d' (0x%X) (%s@%s)\n",
846 property->type, property->type, objectName, property->string);
847 return false;
848 }
849
850 return true;
851 }
852
UI_ParseFunction(uiNode_t * node,const char ** text,const char ** token)853 static bool UI_ParseFunction (uiNode_t* node, const char** text, const char** token)
854 {
855 uiAction_t** action;
856 assert(UI_Node_IsFunction(node));
857
858 action = &node->onClick;
859 *action = UI_ParseActionList(node, text, token);
860 if (*action == nullptr)
861 return false;
862
863 return (*token)[0] == '}';
864 }
865
866 /**
867 * @sa UI_ParseNodeProperties
868 * @brief parse all sequencial properties into a block
869 * @note allow to use an extra block
870 * @code
871 * foobehaviour foonode {
872 * { properties }
873 * // the function stop reading here
874 * nodes
875 * }
876 * foobehaviour foonode {
877 * properties
878 * // the function stop reading here
879 * nodes
880 * }
881 * @endcode
882 */
UI_ParseNodeProperties(uiNode_t * node,const char ** text,const char ** token)883 static bool UI_ParseNodeProperties (uiNode_t* node, const char** text, const char** token)
884 {
885 const char* errhead = "UI_ParseNodeProperties: unexpected end of file (node";
886 bool nextTokenAlreadyRead = false;
887
888 if ((*token)[0] != '{')
889 nextTokenAlreadyRead = true;
890
891 do {
892 const value_t* val;
893 int result;
894
895 /* get new token */
896 if (!nextTokenAlreadyRead) {
897 *token = Com_EParse(text, errhead, node->name);
898 if (!*text)
899 return false;
900 } else {
901 nextTokenAlreadyRead = false;
902 }
903
904 /* is finished */
905 if ((*token)[0] == '}')
906 break;
907
908 /* find the property */
909 val = UI_GetPropertyFromBehaviour(node->behaviour, *token);
910 if (!val) {
911 /* unknown token, print message and continue */
912 Com_Printf("UI_ParseNodeProperties: unknown property \"%s\", node ignored (node %s)\n",
913 *token, UI_GetPath(node));
914 return false;
915 }
916
917 /* get parameter values */
918 result = UI_ParseProperty(node, val, node->name, text, token);
919 if (!result) {
920 Com_Printf("UI_ParseNodeProperties: Problem with parsing of node property '%s@%s'. See upper\n",
921 UI_GetPath(node), val->string);
922 return false;
923 }
924 } while (*text);
925
926 return true;
927 }
928
929 /**
930 * @brief Read a node body
931 * @note Node header already read, we are over the node name, or '{'
932 * @code
933 * Allowed syntax
934 * { properties }
935 * OR
936 * { nodes }
937 * OR
938 * { { properties } nodes }
939 * @endcode
940 */
UI_ParseNodeBody(uiNode_t * node,const char ** text,const char ** token,const char * errhead)941 static bool UI_ParseNodeBody (uiNode_t* node, const char** text, const char** token, const char* errhead)
942 {
943 bool result = true;
944
945 if ((*token)[0] != '{') {
946 /* read the body block start */
947 *token = Com_EParse(text, errhead, node->name);
948 if (!*text)
949 return false;
950 if ((*token)[0] != '{') {
951 Com_Printf("UI_ParseNodeBody: node doesn't have body, token '%s' read (node \"%s\")\n", *token, UI_GetPath(node));
952 ui_global.numNodes--;
953 return false;
954 }
955 }
956
957 /* functions are a special case */
958 if (UI_Node_IsFunction(node)) {
959 result = UI_ParseFunction(node, text, token);
960 } else {
961
962 /* check the content */
963 *token = Com_EParse(text, errhead, node->name);
964 if (!*text)
965 return false;
966
967 if ((*token)[0] == '{') {
968 /* we have a special block for properties */
969 result = UI_ParseNodeProperties(node, text, token);
970 if (!result)
971 return false;
972
973 /* move token over the next node behaviour */
974 *token = Com_EParse(text, errhead, node->name);
975 if (!*text)
976 return false;
977
978 /* and then read all nodes */
979 while ((*token)[0] != '}') {
980 uiNode_t* newNode = UI_ParseNode(node, text, token, errhead);
981 if (!newNode)
982 return false;
983
984 *token = Com_EParse(text, errhead, node->name);
985 if (*text == nullptr)
986 return false;
987 }
988 } else if (UI_GetPropertyFromBehaviour(node->behaviour, *token)) {
989 /* we should have a block with properties only */
990 result = UI_ParseNodeProperties(node, text, token);
991 } else {
992 /* we should have a block with nodes only */
993 while ((*token)[0] != '}') {
994 uiNode_t* newNode = UI_ParseNode(node, text, token, errhead);
995 if (!newNode)
996 return false;
997
998 *token = Com_EParse(text, errhead, node->name);
999 if (*text == nullptr)
1000 return false;
1001 }
1002 }
1003 }
1004 if (!result) {
1005 Com_Printf("UI_ParseNodeBody: node with bad body ignored (node \"%s\")\n", UI_GetPath(node));
1006 ui_global.numNodes--;
1007 return false;
1008 }
1009
1010 /* already check on UI_ParseNodeProperties */
1011 assert((*token)[0] == '}');
1012 return true;
1013 }
1014
1015 /**
1016 * @brief parse a node
1017 * @sa UI_ParseNodeProperties
1018 * @todo we can think about merging UI_ParseNodeProperties here
1019 * @note first token already read
1020 * @note dont read more than the need token (last right token is '}' of end of node)
1021 */
UI_ParseNode(uiNode_t * parent,const char ** text,const char ** token,const char * errhead)1022 static uiNode_t* UI_ParseNode (uiNode_t* parent, const char** text, const char** token, const char* errhead)
1023 {
1024 uiNode_t* node = nullptr;
1025 uiBehaviour_t* behaviour;
1026 uiNode_t* component = nullptr;
1027
1028 /* get the behaviour */
1029 behaviour = UI_GetNodeBehaviour(*token);
1030 if (!behaviour) {
1031 component = UI_GetComponent(*token);
1032 }
1033 if (behaviour == nullptr && component == nullptr) {
1034 Com_Printf("UI_ParseNode: node behaviour/component '%s' doesn't exists (%s)\n", *token, UI_GetPath(parent));
1035 return nullptr;
1036 }
1037
1038 /* get the name */
1039 *token = Com_EParse(text, errhead, "");
1040 if (!*text)
1041 return nullptr;
1042 if (!UI_TokenIsName(*token, Com_GetType(text) == TT_QUOTED_WORD)) {
1043 Com_Printf("UI_ParseNode: \"%s\" is not a well formed node name ([a-zA-Z_][a-zA-Z0-9_]*)\n", *token);
1044 return nullptr;
1045 }
1046 if (UI_TokenIsReserved(*token)) {
1047 Com_Printf("UI_ParseNode: \"%s\" is a reserved token, we can't call a node with it\n", *token);
1048 return nullptr;
1049 }
1050
1051 /* test if node already exists */
1052 /* Already existing node should only come from inherited node,we should not have 2 definitions of the same node into the same window. */
1053 if (parent)
1054 node = UI_GetNode(parent, *token);
1055
1056 /* reuse a node */
1057 if (node) {
1058 if (node->behaviour != behaviour) {
1059 Com_Printf("UI_ParseNode: we can't change node type (node \"%s\")\n", UI_GetPath(node));
1060 return nullptr;
1061 }
1062 Com_DPrintf(DEBUG_CLIENT, "... over-riding node %s\n", UI_GetPath(node));
1063
1064 /* else initialize a component */
1065 } else if (component) {
1066 node = UI_CloneNode(component, nullptr, true, *token, false);
1067 if (parent) {
1068 if (parent->root)
1069 UI_UpdateRoot(node, parent->root);
1070 UI_AppendNode(parent, node);
1071 }
1072
1073 /* else initialize a new node */
1074 } else {
1075 node = UI_AllocNode(*token, behaviour->name, false);
1076 node->parent = parent;
1077 if (parent)
1078 node->root = parent->root;
1079 /** @todo move it into caller */
1080 if (parent)
1081 UI_AppendNode(parent, node);
1082 }
1083
1084 /* get body */
1085 const bool result = UI_ParseNodeBody(node, text, token, errhead);
1086 if (!result)
1087 return nullptr;
1088
1089 /* validate properties */
1090 UI_Node_Loaded(node);
1091
1092 return node;
1093 }
1094
1095 /**
1096 * @brief parses the models.ufo and all files where UI models (menu_model) are defined
1097 * @sa CL_ParseClientData
1098 */
UI_ParseUIModel(const char * name,const char ** text)1099 bool UI_ParseUIModel (const char* name, const char** text)
1100 {
1101 uiModel_t* model;
1102 const char* token;
1103 int i;
1104 const char* errhead = "UI_ParseUIModel: unexpected end of file (names ";
1105
1106 /* search for a UI models with same name */
1107 for (i = 0; i < ui_global.numModels; i++)
1108 if (Q_streq(ui_global.models[i].id, name)) {
1109 Com_Printf("UI_ParseUIModel: menu_model \"%s\" with same name found, second ignored\n", name);
1110 return false;
1111 }
1112
1113 if (ui_global.numModels >= UI_MAX_MODELS) {
1114 Com_Printf("UI_ParseUIModel: Max UI models reached\n");
1115 return false;
1116 }
1117
1118 /* initialize the model */
1119 model = &ui_global.models[ui_global.numModels];
1120 OBJZERO(*model);
1121
1122 Vector4Set(model->color, 1, 1, 1, 1);
1123
1124 model->id = Mem_PoolStrDup(name, ui_sysPool, 0);
1125 Com_DPrintf(DEBUG_CLIENT, "Found UI model %s (%i)\n", model->id, ui_global.numModels);
1126
1127 /* get it's body */
1128 token = Com_Parse(text);
1129
1130 if (!*text || token[0] != '{') {
1131 Com_Printf("UI_ParseUIModel: Model \"%s\" without body ignored\n", model->id);
1132 return false;
1133 }
1134
1135 ui_global.numModels++;
1136
1137 do {
1138 const value_t* v = nullptr;
1139 /* get the name type */
1140 token = Com_EParse(text, errhead, name);
1141 if (!*text)
1142 return false;
1143 if (token[0] == '}')
1144 break;
1145
1146 v = UI_FindPropertyByName(uiModelProperties, token);
1147 if (!v) {
1148 Com_Printf("UI_ParseUIModel: unknown token \"%s\" ignored (UI model %s)\n", token, name);
1149 return false;
1150 }
1151
1152 if (v->type == V_NULL) {
1153 if (Q_streq(v->string, "need")) {
1154 token = Com_EParse(text, errhead, name);
1155 if (!*text)
1156 return false;
1157 if (model->next != nullptr)
1158 Sys_Error("UI_ParseUIModel: second 'need' token found in model %s", name);
1159 model->next = UI_GetUIModel(token);
1160 if (!model->next)
1161 Com_Printf("Could not find UI model %s", token);
1162 }
1163 } else {
1164 token = Com_EParse(text, errhead, name);
1165 if (!*text)
1166 return false;
1167 switch (v->type) {
1168 case V_HUNK_STRING:
1169 Mem_PoolStrDupTo(token, &Com_GetValue<char*>(model, v), ui_sysPool, 0);
1170 break;
1171 default:
1172 Com_EParseValue(model, token, v->type, v->ofs, v->size);
1173 break;
1174 }
1175 }
1176 } while (*text);
1177
1178 return true;
1179 }
1180
UI_ParseSprite(const char * name,const char ** text)1181 bool UI_ParseSprite (const char* name, const char** text)
1182 {
1183 uiSprite_t* icon;
1184 const char* token;
1185
1186 /* search for icons with same name */
1187 icon = UI_AllocStaticSprite(name);
1188
1189 /* get it's body */
1190 token = Com_Parse(text);
1191 assert(token[0] == '{');
1192
1193 /* read properties */
1194 while (true) {
1195 const value_t* property;
1196
1197 token = Com_Parse(text);
1198 if (*text == nullptr)
1199 return false;
1200
1201 if (token[0] == '}')
1202 break;
1203
1204 property = UI_FindPropertyByName(ui_spriteProperties, token);
1205 if (!property) {
1206 Com_Printf("UI_ParseIcon: unknown options property: '%s' - ignore it\n", token);
1207 return false;
1208 }
1209
1210 /* get parameter values */
1211 const bool result = UI_ParseProperty(icon, property, icon->name, text, &token);
1212 if (!result) {
1213 Com_Printf("UI_ParseIcon: Parsing for sprite '%s'. See upper\n", icon->name);
1214 return false;
1215 }
1216 }
1217
1218 return true;
1219 }
1220
1221 /**
1222 * @brief Parse a component
1223 * @sa CL_ParseClientData
1224 * @code
1225 * component panel componentName {
1226 * }
1227 * @endcode
1228 */
UI_ParseComponent(const char * type,const char * name,const char ** text)1229 bool UI_ParseComponent (const char* type, const char* name, const char** text)
1230 {
1231 const char* errhead = "UI_ParseComponent: unexpected end of file (component";
1232 const char* token;
1233
1234 if (!Q_streq(type, "component")) {
1235 Com_Error(ERR_FATAL, "UI_ParseComponent: \"component\" expected but \"%s\" found.\n", type);
1236 return false; /* never reached */
1237 }
1238
1239 /* check the name */
1240 if (!UI_TokenIsName(name, false)) {
1241 Com_Printf("UI_ParseNode: \"%s\" is not a well formed node name ([a-zA-Z_][a-zA-Z0-9_]*)\n", name);
1242 return false;
1243 }
1244 if (UI_TokenIsReserved(name)) {
1245 Com_Printf("UI_ParseNode: \"%s\" is a reserved token, we can't call a node with it\n", name);
1246 return false;
1247 }
1248
1249 token = Com_EParse(text, errhead, "");
1250 if (text == nullptr)
1251 return false;
1252
1253 /* get keyword */
1254 if (!Q_streq(token, "extends")) {
1255 Com_Printf("UI_ParseComponent: \"extends\" expected but \"%s\" found (component %s)\n", token, name);
1256 return false;
1257 }
1258 token = Com_EParse(text, errhead, "");
1259 if (text == nullptr)
1260 return false;
1261
1262 /* initialize component */
1263 uiNode_t* component = nullptr;
1264 const uiBehaviour_t* behaviour = UI_GetNodeBehaviour(token);
1265 if (behaviour) {
1266 /* initialize a new node from behaviour */
1267 component = UI_AllocNode(name, behaviour->name, false);
1268 } else {
1269 const uiNode_t* inheritedComponent = UI_GetComponent(token);
1270 if (inheritedComponent) {
1271 /* initialize from a component */
1272 component = UI_CloneNode(inheritedComponent, nullptr, true, name, false);
1273 } else {
1274 Com_Printf("UI_ParseComponent: node behaviour/component '%s' doesn't exists (component %s)\n", token, name);
1275 return false;
1276 }
1277 }
1278
1279 /* get body */
1280 token = Com_EParse(text, errhead, "");
1281 if (!*text)
1282 return false;
1283 bool result = UI_ParseNodeBody(component, text, &token, errhead);
1284 if (!result)
1285 return false;
1286
1287 /* validate properties */
1288 UI_Node_Loaded(component);
1289
1290 UI_InsertComponent(component);
1291 return true;
1292 }
1293
1294
1295 /**
1296 * @brief Parse a window
1297 * @sa CL_ParseClientData
1298 * @code
1299 * window windowName {
1300 * }
1301 * @endcode
1302 */
UI_ParseWindow(const char * type,const char * name,const char ** text)1303 bool UI_ParseWindow (const char* type, const char* name, const char** text)
1304 {
1305 const char* errhead = "UI_ParseWindow: unexpected end of file (window";
1306 uiNode_t* window;
1307 const char* token;
1308 int i;
1309
1310 if (!Q_streq(type, "window")) {
1311 Com_Error(ERR_FATAL, "UI_ParseWindow: '%s %s' is not a window node\n", type, name);
1312 return false; /* never reached */
1313 }
1314
1315 if (!UI_TokenIsName(name, Com_GetType(text) == TT_QUOTED_WORD)) {
1316 Com_Printf("UI_ParseWindow: \"%s\" is not a well formed node name ([a-zA-Z_][a-zA-Z0-9_]*)\n", name);
1317 return false;
1318 }
1319 if (UI_TokenIsReserved(name)) {
1320 Com_Printf("UI_ParseWindow: \"%s\" is a reserved token, we can't call a node with it (node \"%s\")\n", name, name);
1321 return false;
1322 }
1323
1324 /* search for windows with same name */
1325 for (i = 0; i < ui_global.numWindows; i++)
1326 if (!strncmp(name, ui_global.windows[i]->name, sizeof(ui_global.windows[i]->name)))
1327 break;
1328
1329 if (i < ui_global.numWindows) {
1330 Com_Printf("UI_ParseWindow: %s \"%s\" with same name found, second ignored\n", type, name);
1331 }
1332
1333 if (ui_global.numWindows >= UI_MAX_WINDOWS) {
1334 Com_Error(ERR_FATAL, "UI_ParseWindow: max windows exceeded (%i) - ignore '%s'\n", UI_MAX_WINDOWS, name);
1335 return false; /* never reached */
1336 }
1337
1338 /* get window body */
1339 token = Com_Parse(text);
1340
1341 /* does this window inherit data from another window? */
1342 if (Q_streq(token, "extends")) {
1343 uiNode_t* superWindow;
1344 token = Com_Parse(text);
1345 superWindow = UI_GetWindow(token);
1346 if (superWindow == nullptr)
1347 Sys_Error("Could not get the super window \"%s\"", token);
1348 window = UI_CloneNode(superWindow, nullptr, true, name, false);
1349 token = Com_Parse(text);
1350 } else {
1351 window = UI_AllocNode(name, type, false);
1352 window->root = window;
1353 }
1354
1355 UI_InsertWindow(window);
1356
1357 /* parse it's body */
1358 bool result = UI_ParseNodeBody(window, text, &token, errhead);
1359 if (!result) {
1360 Com_Error(ERR_FATAL, "UI_ParseWindow: window \"%s\" has a bad body\n", window->name);
1361 }
1362
1363 UI_Node_Loaded(window);
1364 return true;
1365 }
1366
1367 /**
1368 * @sa Com_MacroExpandString
1369 * @todo we should review this code, '*' doesn't work very well for all the needed things
1370 */
UI_GetReferenceString(const uiNode_t * const node,const char * ref)1371 const char* UI_GetReferenceString (const uiNode_t* const node, const char* ref)
1372 {
1373 if (!ref)
1374 return nullptr;
1375
1376 /* its a cvar */
1377 if (ref[0] != '*')
1378 return CL_Translate(ref);
1379
1380 /* get the reference and the name */
1381 const char* token = Com_MacroExpandString(ref);
1382 if (token)
1383 return CL_Translate(token);
1384
1385 /* skip the star */
1386 token = ref + 1;
1387 if (token[0] == '\0')
1388 return nullptr;
1389
1390 Sys_Error("UI_GetReferenceString: unknown reference %s", token);
1391 }
1392
UI_GetReferenceFloat(const uiNode_t * const node,const void * ref)1393 float UI_GetReferenceFloat (const uiNode_t* const node, const void* ref)
1394 {
1395 if (!ref)
1396 return 0.0;
1397 if (char const* const token = Q_strstart((char const*)ref, "*")) {
1398 if (token[0] == '\0')
1399 return 0.0;
1400
1401 if (char const* const cvar = Q_strstart(token, "cvar:")) {
1402 return Cvar_GetValue(cvar);
1403 }
1404
1405 Sys_Error("UI_GetReferenceFloat: unknown reference '%s' from node '%s'", token, node->name);
1406 }
1407
1408 /* just get the data */
1409 return *(const float*) ref;
1410 }
1411