1 /**
2 * @file
3 * @brief In-game message settings
4 */
5
6 /*
7 Copyright (C) 2002-2013 UFO: Alien Invasion.
8
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 2
12 of the License, or (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17
18 See the 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 #include "../../cl_shared.h"
26 #include "../../../shared/parse.h"
27 #include "cp_campaign.h"
28 #include "cp_messageoptions.h"
29 #include "cp_messageoptions_callbacks.h"
30 #include "cp_time.h"
31 #include "save/save_messageoptions.h"
32
33 /** @brief valid notification types that may cause pause / notice */
34 char const* const nt_strings[NT_NUM_NOTIFYTYPE] = {
35 N_("installation_installed"),
36 N_("installation_removed"),
37 N_("installation_replaced"),
38 N_("aircraft_refueled"),
39 N_("aircraft_cannotrefuel"),
40 N_("aircraft_arrivedhome"),
41 N_("installation_build_started"),
42 N_("installation_build_finished"),
43 N_("installation_destroyed"),
44 N_("research_new_proposed"),
45 N_("research_halted"),
46 N_("research_completed"),
47 N_("production_started"),
48 N_("production_finished"),
49 N_("production_failed"),
50 N_("production_queue_empty"),
51 N_("nation_happiness_changed"),
52 N_("nation_unhappy"),
53 N_("nation_pleased"),
54 N_("transfer_started"),
55 N_("transfer_completed_success"),
56 N_("transfer_lost"),
57 N_("transfer_aliens_defered"),
58 N_("transfer_uforecovery_finished"),
59 N_("ufo_spotted"),
60 N_("ufo_signal_lost"),
61 N_("ufo_attacking"),
62 N_("base_attack"),
63 N_("building_finished")
64 };
65 CASSERT(lengthof(nt_strings) == NT_NUM_NOTIFYTYPE);
66
67 messageSettings_t messageSettings[NT_NUM_NOTIFYTYPE]; /**< array holding actual message settings for every notify type */
68
69 /**
70 * @brief Function updates pause or notification settings.
71 * @param listIndex listIndex in menu to update via confunc
72 * @param type notification type to update
73 * @param optionType option type that should be updated
74 * @param activate flag indicating whether setting should be activated (true) or deactivated
75 * @param sendCommands flag indicating whether confunc command to update menu button should be sent
76 * @sa MSO_Toggle_f
77 * @sa MSO_Set_f
78 * @note if sendCommands is false, initialization of buttons is reactivated for next menu displaying
79 */
MSO_Set(const int listIndex,const notify_t type,const int optionType,const bool activate,const bool sendCommands)80 void MSO_Set (const int listIndex, const notify_t type, const int optionType, const bool activate, const bool sendCommands)
81 {
82 messageSettings_t* settings = &messageSettings[type];
83
84 if (activate) {
85 if ((optionType & NTMASK_PAUSE) == NTMASK_PAUSE)
86 settings->doPause = activate;
87 if ((optionType & NTMASK_SOUND)== NTMASK_SOUND)
88 settings->doSound = activate;
89 /* notification anyway*/
90 settings->doNotify = activate;
91 } else {
92 if ((optionType & NTMASK_PAUSE) == NTMASK_PAUSE)
93 settings->doPause = activate;
94 if ((optionType & NTMASK_SOUND)== NTMASK_SOUND)
95 settings->doSound = activate;
96 /* disable all if notify is disabled */
97 if (optionType == MSO_NOTIFY) {
98 settings->doNotify = activate;
99 settings->doPause = activate;
100 settings->doSound = activate;
101 }
102 }
103
104 if (sendCommands)
105 cgi->UI_ExecuteConfunc("ms_btnstate %i %i %i %i", listIndex, settings->doPause, settings->doNotify, settings->doSound);
106 else
107 /* ensure that message buttons will be initialized correctly if menu is shown next time */
108 MSO_SetMenuState(MSO_MSTATE_PREPARED, false, false);
109 }
110
111 /**
112 * @brief Parse notify type
113 * @return A NT_ number, else return -1
114 */
MSO_ParseNotifyType(const char * name)115 static int MSO_ParseNotifyType (const char* name)
116 {
117 for (int idx = 0; idx < NT_NUM_NOTIFYTYPE; idx ++) {
118 if (Q_streq(name, nt_strings[idx])) {
119 return idx;
120 }
121 }
122 return -1;
123 }
124
125 /**
126 * @brief Parse option type
127 * @return A MSO value, else 0
128 */
MSO_ParseOptionType(const char * type)129 static int MSO_ParseOptionType (const char* type)
130 {
131 if (Q_strcaseeq(type, "pause"))
132 return MSO_PAUSE;
133 else if (Q_strcaseeq(type, "notify"))
134 return MSO_NOTIFY;
135 else if (Q_strcaseeq(type, "sound"))
136 return MSO_SOUND;
137
138 Com_Printf("Unrecognized optionstype during set '%s' ignored\n", type);
139 return 0;
140 }
141
142 /**
143 * @brief Function callback used to initialize values for messageoptions and for manual setting changes.
144 * @sa MSO_Set
145 */
MSO_Set_f(void)146 static void MSO_Set_f (void)
147 {
148 if (cgi->Cmd_Argc() != 4) {
149 Com_Printf("Usage: %s <messagetypename> <pause|notify|sound> <0|1>\n", cgi->Cmd_Argv(0));
150 return;
151 }
152
153 const int optionsType = MSO_ParseOptionType(cgi->Cmd_Argv(1));
154 if (optionsType == 0)
155 return;
156
157 const char* messagetype = cgi->Cmd_Argv(1);
158 int type;
159 for (type = 0; type < NT_NUM_NOTIFYTYPE; type++) {
160 if (Q_streq(nt_strings[type], messagetype))
161 break;
162 }
163 if (type == NT_NUM_NOTIFYTYPE) {
164 Com_Printf("Unrecognized messagetype during set '%s' ignored\n", messagetype);
165 return;
166 }
167
168 MSO_Set(0, (notify_t)type, optionsType, atoi(cgi->Cmd_Argv(3)), false);
169 }
170
171 /**
172 * @brief Function callback that sets all message options settings for one option type to given value.
173 * @sa MSO_Set
174 * @sa MSO_Init_f
175 */
MSO_SetAll_f(void)176 static void MSO_SetAll_f (void)
177 {
178 if (cgi->Cmd_Argc() != 3) {
179 Com_Printf("Usage: %s <pause|notify|sound> <0|1>\n", cgi->Cmd_Argv(0));
180 return;
181 }
182
183 const bool activate = atoi(cgi->Cmd_Argv(2));
184 const int optionsType = MSO_ParseOptionType(cgi->Cmd_Argv(1));
185 if (optionsType == 0)
186 return;
187
188 /* update settings for chosen type */
189 for (int type = 0; type < NT_NUM_NOTIFYTYPE; ++type) {
190 MSO_Set(0, (notify_t)type, optionsType, activate, false);
191 }
192 /* reinit menu */
193 MSO_SetMenuState(MSO_MSTATE_PREPARED, true, true);
194 }
195
196 /**
197 * @brief Adds a new message to message stack. It uses message settings to
198 * verify whether sound should be played and whether game should stop.
199 * @param messagecategory category for notification
200 * @param[in] title Already translated message/mail title
201 * @param[in] text Already translated message/mail body
202 * @param[in] popup Show this as a popup, too?
203 * @param[in] type The message type
204 * @param[in] pedia Pointer to technology (only if needed)
205 * @return message_t pointer if message was added
206 * @sa MS_AddNewMessageSound
207 */
MSO_CheckAddNewMessage(const notify_t messagecategory,const char * title,const char * text,messageType_t type,technology_t * pedia,bool popup)208 uiMessageListNodeMessage_t* MSO_CheckAddNewMessage (const notify_t messagecategory, const char* title, const char* text, messageType_t type, technology_t* pedia, bool popup)
209 {
210 uiMessageListNodeMessage_t* result = nullptr;
211 const messageSettings_t* settings = &messageSettings[messagecategory];
212
213 if (settings->doNotify)
214 result = MS_AddNewMessage(title, text, type, pedia, popup, settings->doSound);
215 if (settings->doPause)
216 CP_GameTimeStop();
217 return result;
218 }
219
220 /**
221 * @brief saves current notification and pause settings
222 * @sa MSO_LoadXML
223 */
MSO_SaveXML(xmlNode_t * p)224 bool MSO_SaveXML (xmlNode_t* p)
225 {
226 int type;
227 xmlNode_t* n = cgi->XML_AddNode(p, SAVE_MESSAGEOPTIONS_MESSAGEOPTIONS);
228
229 /* save positive values */
230 for (type = 0; type < NT_NUM_NOTIFYTYPE; ++type) {
231 messageSettings_t actualSetting = messageSettings[type];
232 xmlNode_t* s = cgi->XML_AddNode(n, SAVE_MESSAGEOPTIONS_TYPE);
233
234 cgi->XML_AddString(s, SAVE_MESSAGEOPTIONS_NAME, nt_strings[type]);
235 cgi->XML_AddBoolValue(s, SAVE_MESSAGEOPTIONS_NOTIFY, actualSetting.doNotify);
236 cgi->XML_AddBoolValue(s, SAVE_MESSAGEOPTIONS_PAUSE, actualSetting.doPause);
237 cgi->XML_AddBoolValue(s, SAVE_MESSAGEOPTIONS_SOUND, actualSetting.doSound);
238 }
239
240 return true;
241 }
242
243 /**
244 * @brief Restores the notification and pause settings from savegame
245 * @sa MSO_SaveXML
246 */
MSO_LoadXML(xmlNode_t * p)247 bool MSO_LoadXML (xmlNode_t* p)
248 {
249 xmlNode_t* n, *s;
250
251 n = cgi->XML_GetNode(p, SAVE_MESSAGEOPTIONS_MESSAGEOPTIONS);
252 if (!n)
253 return false;
254
255 for (s = cgi->XML_GetNode(n, SAVE_MESSAGEOPTIONS_TYPE); s; s = cgi->XML_GetNextNode(s, n, SAVE_MESSAGEOPTIONS_TYPE)) {
256 const char* messagetype = cgi->XML_GetString(s, SAVE_MESSAGEOPTIONS_NAME);
257 int type;
258
259 for (type = 0; type < NT_NUM_NOTIFYTYPE; type++) {
260 if (Q_streq(nt_strings[type], messagetype))
261 break;
262 }
263
264 /** @todo (menu) check why this message is not shown anywhere in logs*/
265 if (type == NT_NUM_NOTIFYTYPE) {
266 Com_Printf("Unrecognized messagetype '%s' ignored while loading\n", messagetype);
267 continue;
268 }
269 MSO_Set(0, (notify_t)type, MSO_NOTIFY, cgi->XML_GetBool(s, SAVE_MESSAGEOPTIONS_NOTIFY, false), false);
270 MSO_Set(0, (notify_t)type, MSO_PAUSE, cgi->XML_GetBool(s, SAVE_MESSAGEOPTIONS_PAUSE, false), false);
271 MSO_Set(0, (notify_t)type, MSO_SOUND, cgi->XML_GetBool(s, SAVE_MESSAGEOPTIONS_SOUND, false), false);
272 }
273
274 MSO_SetMenuState(MSO_MSTATE_REINIT, false, false);
275 return true;
276 }
277
278 /**
279 * @brief parses message options settings from file.
280 */
MSO_ParseOption(const char * blockName,const char ** text)281 static int MSO_ParseOption (const char* blockName, const char** text)
282 {
283 const char* errhead = "MSO_ParseSettings: unexpected end of file (names ";
284 const char* token;
285
286 /* get name list body body */
287 token = Com_Parse(text);
288
289 if (!*text || *token !='{') {
290 Com_Printf("MSO_ParseOption: settingslist \"%s\" without body ignored\n", blockName);
291 return -1;
292 }
293
294 int messageType = -1;
295 linkedList_t* status = nullptr;
296
297 do {
298 /* get the msg type*/
299 token = cgi->Com_EParse(text, errhead, blockName);
300 if (!*text) {
301 Com_Printf("MSO_ParseOption: end of file not expected \"%s\"\n", blockName);
302 return -1;
303 }
304 if (token[0] == '}')
305 break;
306
307 if (Q_streq(token, "type")) {
308 token = cgi->Com_EParse(text, errhead, blockName);
309 messageType = MSO_ParseNotifyType(token);
310 } else if (Q_streq(token, "status")) {
311 if (status != nullptr) {
312 Com_Printf("MSO_ParseOption: status already defined. Previous definition ignored.\n");
313 cgi->LIST_Delete(&status);
314 } else if (!Com_ParseList(text, &status)) {
315 Com_Printf("MSO_ParseOption: error while parsing option status.\n");
316 return -1;
317 }
318 } else {
319 Com_Printf("MSO_ParseOption: token \"%s\" in \"%s\" not expected.\n", token, blockName);
320 return -1;
321 }
322 } while (*text);
323
324 if (messageType == -1) {
325 Com_Printf("MSO_ParseOption: message option type undefined.\n");
326 return -1;
327 }
328
329 for (linkedList_t* element = status; element != nullptr; element = element->next) {
330 const char* value = (const char*)element->data;
331 const int optionType = MSO_ParseOptionType(value);
332 if (optionType == 0) {
333 Com_Printf("MSO_ParseOption: message option type \"%s\" undefined.\n", value);
334 continue;
335 }
336 MSO_Set(0, (notify_t)messageType, optionType, 1, false);
337 }
338
339 /* reset menu state, was updated by msgoptions_set */
340 MSO_SetMenuState(MSO_MSTATE_REINIT, false, false);
341
342 return messageType;
343 }
344
345 /**
346 * @brief Parses a messagecategory script section. These categories are used to group notification types.
347 * @sa MSO_InitTextList
348 */
MSO_ParseCategory(const char * blockName,const char ** text)349 static bool MSO_ParseCategory (const char* blockName, const char** text)
350 {
351 const char* errhead = "MSO_ParseCategory: unexpected end of file (names ";
352 const char* token;
353 msgCategory_t* category;
354 msgCategoryEntry_t* categoryEntry;
355
356 /* get name list body body */
357 token = Com_Parse(text);
358
359 if (!*text || *token != '{') {
360 Com_Printf("MSO_ParseCategory: category without body\n");
361 return false;
362 }
363
364 /* add category */
365 if (ccs.numMsgCategories >= MAX_MESSAGECATEGORIES) {
366 Com_Printf("MSO_ParseCategory: too many messagecategory defs\n");
367 return false;
368 }
369
370 /* QUESTION this structure looks useless, categoryEntry is enough */
371 category = &ccs.messageCategories[ccs.numMsgCategories];
372
373 OBJZERO(*category);
374 category->idx = ccs.numMsgCategories; /* set self-link */
375
376 categoryEntry = &ccs.msgCategoryEntries[ccs.numMsgCategoryEntries];
377
378 /* first entry is category */
379 OBJZERO(*categoryEntry);
380 categoryEntry->category = &ccs.messageCategories[ccs.numMsgCategories];
381 category->last = category->first = &ccs.msgCategoryEntries[ccs.numMsgCategoryEntries];
382 categoryEntry->previous = nullptr;
383 categoryEntry->next = nullptr;
384 categoryEntry->isCategory = true;
385 ccs.numMsgCategoryEntries++;
386
387 do {
388 /* get entries and add them to category */
389 token = cgi->Com_EParse(text, errhead, blockName);
390 if (!*text) {
391 Com_Printf("MSO_ParseMessageSettings: end of file not expected\n");
392 return false;
393 }
394 if (token[0] == '}')
395 break;
396
397 if (Q_streq(token, "option")) {
398 int optionId = MSO_ParseOption(blockName, text);
399 if (optionId == -1) {
400 cgi->Com_Error(ERR_DROP, "MSO_ParseMessageSettings: error while parsing option from \"%s\".\n", blockName);
401 }
402 /* prepare a new msgcategory entry */
403 msgCategoryEntry_t* previous = ccs.messageCategories[ccs.numMsgCategories].last;
404 msgCategoryEntry_t* entry = &ccs.msgCategoryEntries[ccs.numMsgCategoryEntries];
405 OBJZERO(*entry);
406 ccs.messageCategories[ccs.numMsgCategories].last = entry;
407 previous->next = entry;
408
409 entry->category = &ccs.messageCategories[ccs.numMsgCategories];
410 entry->previous = previous;
411 entry->next = nullptr;
412 entry->notifyType = nt_strings[optionId];
413 entry->settings = &messageSettings[optionId];
414 ccs.numMsgCategoryEntries++;
415 } else if (Q_streq(token, "name")) {
416 token = cgi->Com_EParse(text, errhead, blockName);
417 if (!*text) {
418 Com_Printf("MSO_ParseMessageSettings: end of file not expected\n");
419 return false;
420 }
421 /* skip translation token */
422 if (token[0] == '_') {
423 token++;
424 }
425 category->name = Mem_PoolStrDup(token, cp_campaignPool, 0);
426 categoryEntry->notifyType = category->name;
427 } else {
428 cgi->Com_Error(ERR_DROP, "MSO_ParseMessageSettings: token \"%s\" in \"%s\" not expected.\n", token, blockName);
429 }
430 } while (*text);
431
432 if (category->name == nullptr) {
433 Com_Printf("MSO_ParseMessageSettings: category do not have name\n");
434 return false;
435 }
436
437 ccs.numMsgCategories++;
438 MSO_SetMenuState(MSO_MSTATE_REINIT, false, false);
439 return true;
440 }
441
442 /**
443 * @brief parses message options settings from file.
444 */
MSO_ParseMessageSettings(const char * name,const char ** text)445 void MSO_ParseMessageSettings (const char* name, const char** text)
446 {
447 const char* errhead = "MSO_ParseMessageSettings: unexpected end of file (names ";
448 const char* token;
449
450 /* settings available, reset previous settings */
451 OBJZERO(messageSettings);
452
453 /* get name list body body */
454 token = Com_Parse(text);
455
456 if (!*text || token[0] != '{') {
457 cgi->Com_Error(ERR_DROP, "MSO_ParseMessageSettings: msgoptions \"%s\" without body.\n", name);
458 return;
459 }
460
461 while (*text) {
462 /* get entries and add them to category */
463 token = cgi->Com_EParse(text, errhead, name);
464 if (!*text)
465 cgi->Com_Error(ERR_DROP, "MSO_ParseMessageSettings: end of file not expected \"%s\".\n", name);
466 if (token[0] == '}')
467 break;
468
469 if (Q_streq(token, "category")) {
470 if (!MSO_ParseCategory(name, text)) {
471 cgi->Com_Error(ERR_DROP, "MSO_ParseMessageSettings: error while parsing category from \"%s\".\n", name);
472 }
473 } else {
474 cgi->Com_Error(ERR_DROP, "MSO_ParseMessageSettings: token \"%s\" in \"%s\" not expected.\n", token, name);
475 }
476 }
477
478 }
479
MSO_Init(void)480 void MSO_Init (void)
481 {
482 cgi->Cmd_AddCommand("msgoptions_setall", MSO_SetAll_f, "Sets pause, notification or sound setting for all message categories");
483 cgi->Cmd_AddCommand("msgoptions_set", MSO_Set_f, "Sets pause, notification or sound setting for a message category");
484 MSO_InitCallbacks();
485 }
486
MSO_Shutdown(void)487 void MSO_Shutdown (void)
488 {
489 cgi->Cmd_RemoveCommand("msgoptions_setall");
490 cgi->Cmd_RemoveCommand("msgoptions_set");
491 MSO_ShutdownCallbacks();
492 }
493