UFO: Alien Invasion
Loading...
Searching...
No Matches
cp_parse.cpp
Go to the documentation of this file.
1
5
6/*
7Copyright (C) 2002-2025 UFO: Alien Invasion.
8
9This program is free software; you can redistribute it and/or
10modify it under the terms of the GNU General Public License
11as published by the Free Software Foundation; either version 2
12of the License, or (at your option) any later version.
13
14This program is distributed in the hope that it will be useful,
15but WITHOUT ANY WARRANTY; without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17
18See the GNU General Public License for more details.
19
20You should have received a copy of the GNU General Public License
21along with this program; if not, write to the Free Software
22Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23*/
24
25#include "../../DateTime.h"
26#include "../../cl_shared.h"
28#include "cp_campaign.h"
29#include "cp_rank.h"
30#include "cp_parse.h"
31#include "../../cl_inventory.h" /* INV_GetEquipmentDefinitionByID */
32#include "cp_component.h"
33
39{
40 if (Q_streq(type, "recon"))
42 else if (Q_streq(type, "terror"))
44 else if (Q_streq(type, "baseattack"))
46 else if (Q_streq(type, "building"))
48 else if (Q_streq(type, "supply"))
50 else if (Q_streq(type, "xvi"))
52 else if (Q_streq(type, "intercept"))
54 else if (Q_streq(type, "harvest"))
56 else if (Q_streq(type, "alienbase"))
58 else if (Q_streq(type, "ufocarrier"))
60 else if (Q_streq(type, "rescue"))
62 else {
63 cgi->Com_Printf("CP_GetAlienMissionTypeByID: unknown alien mission category '%s'\n", type);
65 }
66}
67
68static const value_t alien_group_vals[] = {
69 {"mininterest", V_INT, offsetof(alienTeamGroup_t, minInterest), 0},
70 {"maxinterest", V_INT, offsetof(alienTeamGroup_t, maxInterest), 0},
71 {"minaliencount", V_INT, offsetof(alienTeamGroup_t, minAlienCount), 0},
72 {"maxaliencount", V_INT, offsetof(alienTeamGroup_t, maxAlienCount), 0},
73 {nullptr, V_NULL, 0, 0}
74};
75
79static void CP_ParseAlienTeam (const char* name, const char** text)
80{
81 const char* errhead = "CP_ParseAlienTeam: unexpected end of file (alienteam ";
82 const char* token;
83 int i;
84 alienTeamCategory_t* alienCategory;
85
86 /* get it's body */
87 token = Com_Parse(text);
88
89 if (!*text || *token != '{') {
90 cgi->Com_Printf("CP_ParseAlienTeam: alien team category \"%s\" without body ignored\n", name);
91 return;
92 }
93
94 if (ccs.numAlienCategories >= ALIENCATEGORY_MAX) {
95 cgi->Com_Printf("CP_ParseAlienTeam: maximum number of alien team category reached (%i)\n", ALIENCATEGORY_MAX);
96 return;
97 }
98
99 /* search for category with same name */
100 for (i = 0; i < ccs.numAlienCategories; i++)
101 if (Q_streq(name, ccs.alienCategories[i].id))
102 break;
103 if (i < ccs.numAlienCategories) {
104 cgi->Com_Printf("CP_ParseAlienTeam: alien category def \"%s\" with same name found, second ignored\n", name);
105 return;
106 }
107
108 alienCategory = &ccs.alienCategories[ccs.numAlienCategories++];
109 Q_strncpyz(alienCategory->id, name, sizeof(alienCategory->id));
110
111 do {
112 token = cgi->Com_EParse(text, errhead, name);
113 if (!*text)
114 break;
115 if (*token == '}')
116 break;
117
118 if (Q_streq(token, "equipment")) {
119 linkedList_t** list = &alienCategory->equipment;
120 if (!cgi->Com_ParseList(text, list)) {
121 cgi->Com_Error(ERR_DROP, "CL_ParseAlienTeam: \"%s\" Error while parsing equipment list", name);
122 }
123 } else if (Q_streq(token, "category")) {
124 linkedList_t* list;
125 if (!cgi->Com_ParseList(text, &list)) {
126 cgi->Com_Error(ERR_DROP, "CL_ParseAlienTeam: \"%s\" Error while parsing category list", name);
127 }
128 for (linkedList_t* element = list; element != nullptr; element = element->next) {
129 alienCategory->missionCategories[alienCategory->numMissionCategories] = CP_GetAlienMissionTypeByID((const char*)element->data);
130 if (alienCategory->missionCategories[alienCategory->numMissionCategories] == INTERESTCATEGORY_NONE)
131 cgi->Com_Printf("CP_ParseAlienTeam: alien team category \"%s\" is used with no mission category. It won't be used in game.\n", name);
132 alienCategory->numMissionCategories++;
133 }
134 cgi->LIST_Delete(&list);
135 } else if (Q_streq(token, "teaminterest")) {
136 alienTeamGroup_t* group;
137
138 token = cgi->Com_EParse(text, errhead, name);
139 if (!*text || *token != '{') {
140 cgi->Com_Printf("CP_ParseAlienTeam: alien team \"%s\" has team with no opening brace\n", name);
141 break;
142 }
143
144 if (alienCategory->numAlienTeamGroups >= MAX_ALIEN_GROUP_PER_CATEGORY) {
145 cgi->Com_Printf("CP_ParseAlienTeam: maximum number of alien team reached (%i) in category \"%s\"\n", MAX_ALIEN_GROUP_PER_CATEGORY, name);
146 break;
147 }
148
149 group = &alienCategory->alienTeamGroups[alienCategory->numAlienTeamGroups];
150 group->idx = alienCategory->numAlienTeamGroups;
151 group->categoryIdx = alienCategory - ccs.alienCategories;
152 alienCategory->numAlienTeamGroups++;
153
154 do {
155 token = cgi->Com_EParse(text, errhead, name);
156
157 if (!cgi->Com_ParseBlockToken(name, text, group, alien_group_vals, cp_campaignPool, token)) {
158 if (!*text || *token == '}')
159 break;
160
161 if (Q_streq(token, "team")) {
162 linkedList_t* list;
163 if (!cgi->Com_ParseList(text, &list)) {
164 cgi->Com_Error(ERR_DROP, "CL_ParseAlienTeam: \"%s\" Error while parsing team list", name);
165 }
166 for (linkedList_t* element = list; element != nullptr; element = element->next) {
168 cgi->Com_Error(ERR_DROP, "CL_ParseAlienTeam: MAX_TEAMS_PER_MISSION hit");
169 const teamDef_t* teamDef = cgi->Com_GetTeamDefinitionByID(strtok((char*)element->data, "/"));
170 if (teamDef) {
171 group->alienTeams[group->numAlienTeams] = teamDef;
172 const chrTemplate_t* chrTemplate = CHRSH_GetTemplateByID(teamDef, strtok(nullptr, ""));
173 group->alienChrTemplates[group->numAlienTeams] = chrTemplate;
174 ++group->numAlienTeams;
175 }
176 }
177 cgi->LIST_Delete(&list);
178 } else {
179 cgi->Com_Error(ERR_DROP, "CL_ParseAlienTeam: Unknown token \"%s\"\n", token);
180 }
181 }
182 } while (*text);
183
184 if (group->minAlienCount > group->maxAlienCount) {
185 cgi->Com_Printf("CP_ParseAlienTeam: Minimum number of aliens is greater than maximum value! Swapped.\n");
186 const int swap = group->minAlienCount;
187 group->minAlienCount = group->maxAlienCount;
188 group->maxAlienCount = swap;
189 }
190 } else {
191 cgi->Com_Printf("CP_ParseAlienTeam: unknown token \"%s\" ignored (category %s)\n", token, name);
192 continue;
193 }
194 } while (*text);
195
196 if (cgi->LIST_IsEmpty(alienCategory->equipment))
197 Sys_Error("alien category equipment list is empty");
198}
199
203static void CP_ParseResearchedCampaignItems (const campaign_t* campaign, const char* name, const char** text)
204{
205 const char* errhead = "CP_ParseResearchedCampaignItems: unexpected end of file (equipment ";
206 const char* token;
207 int i;
208
209 /* Don't parse if it is not definition for current type of campaign. */
210 if (!Q_streq(campaign->researched, name))
211 return;
212
213 /* get it's body */
214 token = Com_Parse(text);
215
216 if (!*text || *token != '{') {
217 cgi->Com_Printf("CP_ParseResearchedCampaignItems: equipment def \"%s\" without body ignored (%s)\n",
218 name, token);
219 return;
220 }
221
222 cgi->Com_DPrintf(DEBUG_CLIENT, "..campaign research list '%s'\n", name);
223 do {
224 token = cgi->Com_EParse(text, errhead, name);
225 if (!*text || *token == '}')
226 return;
227
228 for (i = 0; i < ccs.numTechnologies; i++) {
230 assert(tech);
231 if (Q_streq(token, tech->id)) {
236 cgi->Com_DPrintf(DEBUG_CLIENT, "...tech %s\n", tech->id);
237 break;
238 }
239 }
240
241 if (i == ccs.numTechnologies)
242 cgi->Com_Printf("CP_ParseResearchedCampaignItems: unknown token \"%s\" ignored (tech %s)\n", token, name);
243
244 } while (*text);
245}
246
255static void CP_ParseResearchableCampaignStates (const campaign_t* campaign, const char* name, const char** text, bool researchable)
256{
257 const char* errhead = "CP_ParseResearchableCampaignStates: unexpected end of file (equipment ";
258 const char* token;
259 int i;
260
261 /* get it's body */
262 token = Com_Parse(text);
263
264 if (!*text || *token != '{') {
265 cgi->Com_Printf("CP_ParseResearchableCampaignStates: equipment def \"%s\" without body ignored\n", name);
266 return;
267 }
268
269 if (!Q_streq(campaign->researched, name)) {
270 cgi->Com_DPrintf(DEBUG_CLIENT, "..don't use '%s' as researchable list\n", name);
271 return;
272 }
273
274 cgi->Com_DPrintf(DEBUG_CLIENT, "..campaign researchable list '%s'\n", name);
275 do {
276 token = cgi->Com_EParse(text, errhead, name);
277 if (!*text || *token == '}')
278 return;
279
280 for (i = 0; i < ccs.numTechnologies; i++) {
282 if (Q_streq(token, tech->id)) {
283 if (researchable) {
286 } else {
288 }
289 cgi->Com_DPrintf(DEBUG_CLIENT, "...tech %s\n", tech->id);
290 break;
291 }
292 }
293
294 if (i == ccs.numTechnologies)
295 cgi->Com_Printf("CP_ParseResearchableCampaignStates: unknown token \"%s\" ignored (tech %s)\n", token, name);
296
297 } while (*text);
298}
299
300/* =========================================================== */
301
302static const value_t salary_vals[] = {
303 {"soldier_base", V_INT, offsetof(salary_t, base[EMPL_SOLDIER]), MEMBER_SIZEOF(salary_t, base[EMPL_SOLDIER])},
304 {"soldier_rankbonus", V_INT, offsetof(salary_t, rankBonus[EMPL_SOLDIER]), MEMBER_SIZEOF(salary_t, rankBonus[EMPL_SOLDIER])},
305 {"worker_base", V_INT, offsetof(salary_t, base[EMPL_WORKER]), MEMBER_SIZEOF(salary_t, base[EMPL_WORKER])},
306 {"worker_rankbonus", V_INT, offsetof(salary_t, rankBonus[EMPL_WORKER]), MEMBER_SIZEOF(salary_t, rankBonus[EMPL_WORKER])},
307 {"scientist_base", V_INT, offsetof(salary_t, base[EMPL_SCIENTIST]), MEMBER_SIZEOF(salary_t, base[EMPL_SCIENTIST])},
308 {"scientist_rankbonus", V_INT, offsetof(salary_t, rankBonus[EMPL_SCIENTIST]), MEMBER_SIZEOF(salary_t, rankBonus[EMPL_SCIENTIST])},
309 {"pilot_base", V_INT, offsetof(salary_t, base[EMPL_PILOT]), MEMBER_SIZEOF(salary_t, base[EMPL_PILOT])},
310 {"pilot_rankbonus", V_INT, offsetof(salary_t, rankBonus[EMPL_PILOT]), MEMBER_SIZEOF(salary_t, rankBonus[EMPL_PILOT])},
311 {"robot_base", V_INT, offsetof(salary_t, base[EMPL_ROBOT]), MEMBER_SIZEOF(salary_t, base[EMPL_ROBOT])},
312 {"robot_rankbonus", V_INT, offsetof(salary_t, rankBonus[EMPL_ROBOT]), MEMBER_SIZEOF(salary_t, rankBonus[EMPL_ROBOT])},
313 {"aircraft_factor", V_INT, offsetof(salary_t, aircraftFactor), MEMBER_SIZEOF(salary_t, aircraftFactor)},
314 {"aircraft_divisor", V_INT, offsetof(salary_t, aircraftDivisor), MEMBER_SIZEOF(salary_t, aircraftDivisor)},
315 {"base_upkeep", V_INT, offsetof(salary_t, baseUpkeep), MEMBER_SIZEOF(salary_t, baseUpkeep)},
316 {"debt_interest", V_FLOAT, offsetof(salary_t, debtInterest), MEMBER_SIZEOF(salary_t, debtInterest)},
317 {nullptr, V_NULL, 0, 0}
318};
319
330static void CP_ParseSalary (const char* name, const char** text, salary_t* s)
331{
332 cgi->Com_ParseBlock(name, text, s, salary_vals, cp_campaignPool);
333}
334
335/* =========================================================== */
336
337static const value_t campaign_vals[] = {
338 {"default", V_BOOL, offsetof(campaign_t, defaultCampaign), MEMBER_SIZEOF(campaign_t, defaultCampaign)},
339 {"team", V_TEAM, offsetof(campaign_t, team), MEMBER_SIZEOF(campaign_t, team)},
340 {"soldiers", V_INT, offsetof(campaign_t, soldiers), MEMBER_SIZEOF(campaign_t, soldiers)},
341 {"workers", V_INT, offsetof(campaign_t, workers), MEMBER_SIZEOF(campaign_t, workers)},
342 {"xvirate", V_INT, offsetof(campaign_t, maxAllowedXVIRateUntilLost), MEMBER_SIZEOF(campaign_t, maxAllowedXVIRateUntilLost)},
343 {"maxdebts", V_INT, offsetof(campaign_t, negativeCreditsUntilLost), MEMBER_SIZEOF(campaign_t, negativeCreditsUntilLost)},
344 {"minhappiness", V_FLOAT, offsetof(campaign_t, minhappiness), MEMBER_SIZEOF(campaign_t, minhappiness)},
345 {"scientists", V_INT, offsetof(campaign_t, scientists), MEMBER_SIZEOF(campaign_t, scientists)},
346 {"pilots", V_INT, offsetof(campaign_t, pilots), MEMBER_SIZEOF(campaign_t, pilots)},
347 {"ugvs", V_INT, offsetof(campaign_t, ugvs), MEMBER_SIZEOF(campaign_t, ugvs)},
348 {"equipment", V_STRING, offsetof(campaign_t, equipment), 0},
349 {"soldierequipment", V_STRING, offsetof(campaign_t, soldierEquipment), 0},
350 {"market", V_STRING, offsetof(campaign_t, market), 0},
351 {"asymptotic_market", V_STRING, offsetof(campaign_t, asymptoticMarket), 0},
352 {"researched", V_STRING, offsetof(campaign_t, researched), 0},
353 {"difficulty", V_INT, offsetof(campaign_t, difficulty), MEMBER_SIZEOF(campaign_t, difficulty)},
354 {"map", V_STRING, offsetof(campaign_t, map), 0},
355 {"credits", V_INT, offsetof(campaign_t, credits), MEMBER_SIZEOF(campaign_t, credits)},
356 {"visible", V_BOOL, offsetof(campaign_t, visible), MEMBER_SIZEOF(campaign_t, visible)},
357 {"text", V_TRANSLATION_STRING, offsetof(campaign_t, text), 0}, /* just a gettext placeholder */
358 {"name", V_TRANSLATION_STRING, offsetof(campaign_t, name), 0},
359 {"basecost", V_INT, offsetof(campaign_t, basecost), MEMBER_SIZEOF(campaign_t, basecost)},
360 {"firstbase", V_STRING, offsetof(campaign_t, firstBaseTemplate), 0},
361 {"researchrate", V_FLOAT, offsetof(campaign_t, researchRate), MEMBER_SIZEOF(campaign_t, researchRate)},
362 {"producerate", V_FLOAT, offsetof(campaign_t, produceRate), MEMBER_SIZEOF(campaign_t, produceRate)},
363 {"healingrate", V_FLOAT, offsetof(campaign_t, healingRate), MEMBER_SIZEOF(campaign_t, healingRate)},
364 {"liquidationrate", V_FLOAT, offsetof(campaign_t, liquidationRate), MEMBER_SIZEOF(campaign_t, liquidationRate)},
365 {"componentrate", V_FLOAT, offsetof(campaign_t, componentRate), MEMBER_SIZEOF(campaign_t, componentRate)},
366 {"minmissions", V_INT, offsetof(campaign_t, minMissions), MEMBER_SIZEOF(campaign_t, minMissions)},
367 {"maxmissions", V_INT, offsetof(campaign_t, maxMissions), MEMBER_SIZEOF(campaign_t, maxMissions)},
368 {"uforeductionrate", V_FLOAT, offsetof(campaign_t, ufoReductionRate), MEMBER_SIZEOF(campaign_t, ufoReductionRate)},
369 {"initialinterest", V_INT, offsetof(campaign_t, initialInterest), MEMBER_SIZEOF(campaign_t, initialInterest)},
370 {"employeerate", V_FLOAT, offsetof(campaign_t, employeeRate), MEMBER_SIZEOF(campaign_t, employeeRate)},
371 {"alienbaseinterest", V_INT, offsetof(campaign_t, alienBaseInterest), MEMBER_SIZEOF(campaign_t, alienBaseInterest)},
372 {nullptr, V_NULL, 0, 0}
373};
374
378static void CP_ParseCampaign (const char* name, const char** text)
379{
380 const char* errhead = "CP_ParseCampaign: unexpected end of file (campaign ";
381 campaign_t* cp;
382 const char* token;
383 int i;
384 salary_t* s;
385 bool drop = false;
386
387 /* search for campaigns with same name */
388 if (CP_GetCampaign(name) != nullptr) {
389 cgi->Com_Printf("CP_ParseCampaign: campaign def \"%s\" with same name found, second ignored\n", name);
390 return;
391 }
392
393 if (ccs.numCampaigns >= MAX_CAMPAIGNS) {
394 cgi->Com_Printf("CP_ParseCampaign: Max campaigns reached (%i)\n", MAX_CAMPAIGNS);
395 return;
396 }
397
398 /* initialize the campaign */
399 cp = &ccs.campaigns[ccs.numCampaigns++];
400 OBJZERO(*cp);
401 cp->idx = ccs.numCampaigns - 1;
402 Q_strncpyz(cp->id, name, sizeof(cp->id));
403 cp->team = TEAM_PHALANX;
404 Q_strncpyz(cp->researched, "researched_human", sizeof(cp->researched));
405 cp->researchRate = 0.8f;
406 cp->produceRate = 1.0f;
407 cp->healingRate = 1.0f;
408 cp->liquidationRate = 0.0f;
409 cp->componentRate = 1.0f;
410 cp->maxMissions = 17;
411 cp->minMissions = 5;
414 cp->employeeRate = 1.0f;
415 cp->alienBaseInterest = 200;
416
417 /* get it's body */
418 token = Com_Parse(text);
419
420 if (!*text || *token != '{') {
421 cgi->Com_Printf("CP_ParseCampaign: campaign def \"%s\" without body ignored\n", name);
422 ccs.numCampaigns--;
423 return;
424 }
425
426 /* set undefined markers */
427 s = &cp->salaries;
428 for (i = 0; i < MAX_EMPL; i++) {
429 s->base[i] = -1;
430 s->rankBonus[i] = -1;
431 }
432 s->aircraftFactor = -1;
433 s->aircraftDivisor = -1;
434 s->baseUpkeep = -1;
435 s->debtInterest = -1;
436
437 do {
438 token = cgi->Com_EParse(text, errhead, name);
439 if (!*text)
440 break;
441 if (*token == '}')
442 break;
443
444 /* check for some standard values */
445 if (cgi->Com_ParseBlockToken(name, text, cp, campaign_vals, nullptr, token)) {
446 continue;
447 } else if (Q_streq(token, "salary")) {
448 CP_ParseSalary(token, text, s);
449 } else if (Q_streq(token, "events")) {
450 token = cgi->Com_EParse(text, errhead, name);
451 if (!*text)
452 return;
453 cp->events = CP_GetEventsByID(token);
454 } else if (Q_streq(token, "aircraft")) {
455 cgi->Com_ParseList(text, &cp->initialCraft);
456 } else if (Q_streq(token, "date")) {
457 token = cgi->Com_EParse(text, errhead, name);
458 if (!*text)
459 return;
460 int year;
461 int day;
462 int hour;
463 if (sscanf(token, "%i %i %i", &year, &day, &hour) != 3) {
464 Com_Error(ERR_DROP, "Illegal campaign start date for campaign %s", cp->id);
465 }
467 } else {
468 cgi->Com_Printf("CP_ParseCampaign: unknown token \"%s\" ignored (campaign %s)\n", token, name);
469 cgi->Com_EParse(text, errhead, name);
470 }
471 } while (*text);
472
473 if (cp->difficulty < -4)
474 cp->difficulty = -4;
475 else if (cp->difficulty > 4)
476 cp->difficulty = 4;
477
478 /* checking for undefined values */
479 for (i = 0; i < MAX_EMPL; i++) {
480 if (s->base[i] == -1 || s->rankBonus[i] == -1) {
481 drop = true;
482 break;
483 }
484 }
485 if (drop || s->aircraftFactor == -1 || s->aircraftDivisor == -1 || s->baseUpkeep == -1
486 || s->debtInterest == -1) {
487 cgi->Com_Printf("CP_ParseCampaign: check salary definition. Campaign def \"%s\" ignored\n", name);
488 ccs.numCampaigns--;
489 return;
490 }
491}
492
501static void CP_ParseScriptFirst (const char* type, const char* name, const char** text)
502{
503 /* check for client interpretable scripts */
504 if (Q_streq(type, "up_chapter"))
505 UP_ParseChapter(name, text);
506 else if (Q_streq(type, "building"))
507 B_ParseBuildings(name, text, false);
508 else if (Q_streq(type, "installation"))
510 else if (Q_streq(type, "tech"))
512 else if (Q_streq(type, "nation"))
513 CL_ParseNations(name, text);
514 else if (Q_streq(type, "city"))
515 CITY_Parse(name, text);
516 else if (Q_streq(type, "rank"))
517 CL_ParseRanks(name, text);
518 else if (Q_streq(type, "aircraft"))
519 AIR_ParseAircraft(name, text, false);
520 else if (Q_streq(type, "mail"))
522 else if (Q_streq(type, "events"))
524 else if (Q_streq(type, "event"))
526 else if (Q_streq(type, "components"))
528 else if (Q_streq(type, "alienteam"))
529 CP_ParseAlienTeam(name, text);
530 else if (Q_streq(type, "msgoptions"))
532}
533
545static void CP_ParseScriptSecond (const char* type, const char* name, const char** text)
546{
547 /* check for client interpretable scripts */
548 if (Q_streq(type, "building"))
549 B_ParseBuildings(name, text, true);
550 else if (Q_streq(type, "aircraft"))
551 AIR_ParseAircraft(name, text, true);
552 else if (Q_streq(type, "basetemplate"))
554 else if (Q_streq(type, "campaign"))
555 CP_ParseCampaign(name, text);
556}
557
561static void CP_ParseScriptCampaignRelated (const campaign_t* campaign, const char* type, const char* name, const char** text)
562{
563 if (Q_streq(type, "researched"))
564 CP_ParseResearchedCampaignItems(campaign, name, text);
565 else if (Q_streq(type, "researchable"))
566 CP_ParseResearchableCampaignStates(campaign, name, text, true);
567 else if (Q_streq(type, "notresearchable"))
568 CP_ParseResearchableCampaignStates(campaign, name, text, false);
569}
570
574static bool CP_ItemsSanityCheck (void)
575{
576 bool result = true;
577
578 for (int i = 0; i < cgi->csi->numODs; i++) {
579 const objDef_t* item = INVSH_GetItemByIDX(i);
580
581 /* Warn if item has no size set. */
582 if (item->size <= 0 && B_ItemIsStoredInBaseStorage(item)) {
583 result = false;
584 cgi->Com_Printf("CP_ItemsSanityCheck: Item %s has zero size set.\n", item->id);
585 }
586
587 /* Warn if no price is set. */
588 if (item->price <= 0 && BS_IsOnMarket(item)) {
589 result = false;
590 cgi->Com_Printf("CP_ItemsSanityCheck: Item %s has zero price set.\n", item->id);
591 }
592
593 if (item->price > 0 && !BS_IsOnMarket(item) && !PR_ItemIsProduceable(item)) {
594 result = false;
595 cgi->Com_Printf("CP_ItemsSanityCheck: Item %s has a price set though it is neither available on the market and production.\n", item->id);
596 }
597 }
598
599 return result;
600}
601
603typedef struct {
604 bool (*check)(void);
605 const char* name;
607
610 {B_BuildingScriptSanityCheck, "buildings"},
611 {RS_ScriptSanityCheck, "tech"},
612 {AIR_ScriptSanityCheck, "aircraft"},
613 {CP_ItemsSanityCheck, "items"},
614 {NAT_ScriptSanityCheck, "nations"},
615
616 {nullptr, nullptr}
617};
618
624{
625 const sanity_functions_t* s;
626
627 cgi->Com_Printf("Sanity check for script data\n");
629 while (s->check) {
630 bool status = s->check();
631 cgi->Com_Printf("...%s %s\n", s->name, (status ? "ok" : "failed"));
632 s++;
633 }
634}
635
642{
643 const char* type, *name, *text;
644 int i;
645 campaign_t* campaign;
646
647 /* pre-stage parsing */
648 cgi->FS_BuildFileList("ufos/*.ufo");
649 cgi->FS_NextScriptHeader(nullptr, nullptr, nullptr);
650 text = nullptr;
651
652 while ((type = cgi->FS_NextScriptHeader("ufos/*.ufo", &name, &text)) != nullptr)
654
655 /* fill in IDXs for required research techs */
657
658 /* stage two parsing */
659 cgi->FS_NextScriptHeader(nullptr, nullptr, nullptr);
660 text = nullptr;
661
662 cgi->Com_DPrintf(DEBUG_CLIENT, "Second stage parsing started...\n");
663 while ((type = cgi->FS_NextScriptHeader("ufos/*.ufo", &name, &text)) != nullptr)
666
667 for (i = 0; i < cgi->csi->numTeamDefs; i++) {
668 const teamDef_t* teamDef = &cgi->csi->teamDef[i];
669 if (!CHRSH_IsTeamDefAlien(teamDef))
670 continue;
671
672 ccs.teamDefTechs[teamDef->idx] = RS_GetTechByID(teamDef->tech);
673 if (ccs.teamDefTechs[teamDef->idx] == nullptr)
674 cgi->Com_Error(ERR_DROP, "Could not find a tech for teamdef %s", teamDef->id);
675 }
676
677 for (i = 0, campaign = ccs.campaigns; i < ccs.numCampaigns; i++, campaign++) {
678 /* find the relevant markets */
679 campaign->marketDef = cgi->INV_GetEquipmentDefinitionByID(campaign->market);
680 campaign->asymptoticMarketDef = cgi->INV_GetEquipmentDefinitionByID(campaign->asymptoticMarket);
681 }
682
683 cgi->Com_Printf("Campaign data loaded - size " UFO_SIZE_T " bytes\n", sizeof(ccs));
684 cgi->Com_Printf("...techs: %i\n", ccs.numTechnologies);
685 cgi->Com_Printf("...buildings: %i\n", ccs.numBuildingTemplates);
686 cgi->Com_Printf("...ranks: %i\n", ccs.numRanks);
687 cgi->Com_Printf("...nations: %i\n", ccs.numNations);
688 cgi->Com_Printf("...cities: %i\n", ccs.numCities);
689 cgi->Com_Printf("\n");
690}
691
692void CP_ReadCampaignData (const campaign_t* campaign)
693{
694 const char* type, *name, *text;
695
696 /* stage two parsing */
697 cgi->FS_NextScriptHeader(nullptr, nullptr, nullptr);
698 text = nullptr;
699
700 cgi->Com_DPrintf(DEBUG_CLIENT, "Second stage parsing started...\n");
701 while ((type = cgi->FS_NextScriptHeader("ufos/*.ufo", &name, &text)) != nullptr)
702 CP_ParseScriptCampaignRelated(campaign, type, name, &text);
703
704 ccs.date = campaign->date;
705}
DateTime class definition.
bool CHRSH_IsTeamDefAlien(const teamDef_t *const td)
Check if a team definition is alien.
const chrTemplate_t * CHRSH_GetTemplateByID(const teamDef_t *teamDef, const char *templateId)
Header file for inventory handling and Equipment menu.
Share stuff between the different cgame implementations.
Class describing a point of time.
Definition DateTime.h:31
static const short SECONDS_PER_HOUR
Definition DateTime.h:42
static const int DAYS_PER_YEAR
Definition DateTime.h:37
void Com_Error(int code, const char *fmt,...)
Definition common.cpp:459
#define ERR_DROP
Definition common.h:211
void AIR_ParseAircraft(const char *name, const char **text, bool assignAircraftItems)
Parses all aircraft that are defined in our UFO-scripts.
bool AIR_ScriptSanityCheck(void)
Checks the parsed aircraft for errors.
interestCategory_t
@ INTERESTCATEGORY_BASE_ATTACK
@ INTERESTCATEGORY_NONE
@ INTERESTCATEGORY_ALIENBASE
@ INTERESTCATEGORY_BUILDING
@ INTERESTCATEGORY_SUPPLY
@ INTERESTCATEGORY_RECON
@ INTERESTCATEGORY_UFOCARRIER
@ INTERESTCATEGORY_XVI
@ INTERESTCATEGORY_TERROR_ATTACK
@ INTERESTCATEGORY_INTERCEPT
@ INTERESTCATEGORY_RESCUE
@ INTERESTCATEGORY_HARVEST
void B_ParseBaseTemplate(const char *name, const char **text)
Reads a base layout template.
Definition cp_base.cpp:1435
bool B_ItemIsStoredInBaseStorage(const objDef_t *obj)
Check if an item is stored in storage.
Definition cp_base.cpp:2560
bool B_BuildingScriptSanityCheck(void)
Checks the parsed buildings for errors.
void B_ParseBuildings(const char *name, const char **text, bool link)
Copies an entry from the building description file into the list of building types.
memPool_t * cp_campaignPool
campaign_t * CP_GetCampaign(const char *name)
Returns the campaign pointer from global campaign array.
ccs_t ccs
Header file for single player campaign control.
#define NON_OCCURRENCE_PROBABILITY
The probability that any new alien mission will be a non-occurrence mission.
Definition cp_campaign.h:89
const cgame_import_t * cgi
#define INITIAL_OVERALL_INTEREST
Determines the interest interval for a single campaign.
Definition cp_campaign.h:72
#define MAX_ALIEN_GROUP_PER_CATEGORY
Definition cp_campaign.h:59
#define ALIENCATEGORY_MAX
Definition cp_campaign.h:61
#define MAX_CAMPAIGNS
Definition cp_campaign.h:31
void COMP_ParseComponents(const char *name, const char **text)
Parses one "components" entry in a .ufo file and writes it into the next free entry in xxxxxxxx (comp...
Header file for Aircraft and item components.
@ EMPL_SOLDIER
Definition cp_employee.h:31
@ MAX_EMPL
Definition cp_employee.h:36
@ EMPL_ROBOT
Definition cp_employee.h:35
@ EMPL_PILOT
Definition cp_employee.h:34
@ EMPL_WORKER
Definition cp_employee.h:33
@ EMPL_SCIENTIST
Definition cp_employee.h:32
const campaignEvents_t * CP_GetEventsByID(const char *name)
Definition cp_event.cpp:139
void CL_ParseEventMails(const char *name, const char **text)
Definition cp_event.cpp:93
void CL_ParseCampaignEvents(const char *name, const char **text)
Definition cp_event.cpp:448
void CP_ParseEventTrigger(const char *name, const char **text)
Definition cp_event.cpp:359
void INS_LinkTechnologies(void)
void INS_ParseInstallations(const char *name, const char **text)
Copies an entry from the installation description file into the list of installation templates.
bool BS_IsOnMarket(const objDef_t *item)
Check if an item is on market.
Definition cp_market.cpp:42
void MSO_ParseMessageSettings(const char *name, const char **text)
parses message options settings from file.
void CITY_Parse(const char *name, const char **text)
Parse the city data from script file.
void CL_ParseNations(const char *name, const char **text)
Parse the nation data from script file.
bool NAT_ScriptSanityCheck(void)
Checks the parsed nations and cities for errors.
static const value_t salary_vals[]
Definition cp_parse.cpp:302
static void CP_ParseScriptCampaignRelated(const campaign_t *campaign, const char *type, const char *name, const char **text)
Parses the campaign specific data - this data can only be parsed once the campaign started.
Definition cp_parse.cpp:561
static const value_t campaign_vals[]
Definition cp_parse.cpp:337
static void CP_ParseResearchableCampaignStates(const campaign_t *campaign, const char *name, const char **text, bool researchable)
This function parses a list of items that should be set to researchable = true after campaign start.
Definition cp_parse.cpp:255
void CP_ScriptSanityCheck(void)
Check the parsed script values for errors after parsing every script file.
Definition cp_parse.cpp:623
void CP_ParseCampaignData(void)
Read the data for campaigns.
Definition cp_parse.cpp:641
static void CP_ParseSalary(const char *name, const char **text, salary_t *s)
Parse the salaries from campaign definition.
Definition cp_parse.cpp:330
static void CP_ParseScriptSecond(const char *type, const char *name, const char **text)
Parsing only for singleplayer.
Definition cp_parse.cpp:545
static void CP_ParseResearchedCampaignItems(const campaign_t *campaign, const char *name, const char **text)
This function parses a list of items that should be set to researched = true after campaign start.
Definition cp_parse.cpp:203
static void CP_ParseCampaign(const char *name, const char **text)
Definition cp_parse.cpp:378
void CP_ReadCampaignData(const campaign_t *campaign)
Definition cp_parse.cpp:692
static const value_t alien_group_vals[]
Definition cp_parse.cpp:68
static interestCategory_t CP_GetAlienMissionTypeByID(const char *type)
Definition cp_parse.cpp:38
static const sanity_functions_t sanity_functions[]
Data for sanity check of parsed script data.
Definition cp_parse.cpp:609
static void CP_ParseAlienTeam(const char *name, const char **text)
Definition cp_parse.cpp:79
static bool CP_ItemsSanityCheck(void)
Make sure values of items after parsing are proper.
Definition cp_parse.cpp:574
static void CP_ParseScriptFirst(const char *type, const char *name, const char **text)
Parsing campaign data.
Definition cp_parse.cpp:501
Campaign parsing header.
bool PR_ItemIsProduceable(const objDef_t *item)
check if an item is producable.
void CL_ParseRanks(const char *name, const char **text)
Parse medals and ranks defined in the medals.ufo file.
Definition cp_rank.cpp:74
void RS_MarkOneResearchable(technology_t *tech)
Marks one tech as researchable.
technology_t * RS_GetTechByID(const char *id)
return a pointer to the technology identified by given id string
technology_t * RS_GetTechByIDX(int techIdx)
Returns the technology pointer for a tech index. You can use this instead of "&ccs....
void RS_ParseTechnologies(const char *name, const char **text)
Parses one "tech" entry in the research.ufo file and writes it into the next free entry in technologi...
void RS_RequiredLinksAssign(void)
Assign Link pointers to all required techs/items/etc...
bool RS_ScriptSanityCheck(void)
Checks the parsed tech data for errors.
@ MAILSENT_FINISHED
@ MAILSENT_PROPOSAL
void UP_ParseChapter(const char *name, const char **text)
Parse the UFOpaedia chapters from scripts.
#define DEBUG_CLIENT
Definition defines.h:59
void Sys_Error(const char *error,...)
Definition g_main.cpp:421
const objDef_t * INVSH_GetItemByIDX(int index)
Returns the item that belongs to the given index or nullptr if the index is invalid.
#define MAX_TEAMS_PER_MISSION
Definition inv_shared.h:618
const char * Com_Parse(const char *data_p[], char *target, size_t size, bool replaceWhitespaces)
Parse a token out of a string.
Definition parse.cpp:107
Shared parsing functions.
#define TEAM_PHALANX
Definition q_shared.h:62
QGL_EXTERN void(APIENTRY *qglActiveTexture)(GLenum texture)
QGL_EXTERN GLint i
Definition r_gl.h:113
QGL_EXTERN GLint GLenum type
Definition r_gl.h:94
QGL_EXTERN GLuint GLsizei GLsizei GLint GLenum GLchar * name
Definition r_gl.h:110
@ V_BOOL
Definition scripts.h:50
@ V_FLOAT
Definition scripts.h:54
@ V_TRANSLATION_STRING
Definition scripts.h:59
@ V_NULL
Definition scripts.h:49
@ V_STRING
Definition scripts.h:58
@ V_TEAM
Definition scripts.h:70
@ V_INT
Definition scripts.h:52
#define MEMBER_SIZEOF(TYPE, MEMBER)
Definition scripts.h:34
#define Q_streq(a, b)
Definition shared.h:136
#define OBJZERO(obj)
Definition shared.h:178
void Q_strncpyz(char *dest, const char *src, size_t destsize)
Safe strncpy that ensures a trailing zero.
Definition shared.cpp:457
alien team category definition
linkedList_t * equipment
interestCategory_t missionCategories[INTERESTCATEGORY_MAX]
char id[MAX_VAR]
alienTeamGroup_t alienTeamGroups[MAX_ALIEN_GROUP_PER_CATEGORY]
alien team group definition.
const chrTemplate_t * alienChrTemplates[MAX_TEAMS_PER_MISSION]
const teamDef_t * alienTeams[MAX_TEAMS_PER_MISSION]
char asymptoticMarket[MAX_VAR]
salary_t salaries
const equipDef_t * asymptoticMarketDef
float healingRate
int initialInterest
class DateTime date
char researched[MAX_VAR]
const campaignEvents_t * events
float ufoReductionRate
float produceRate
float liquidationRate
char market[MAX_VAR]
const equipDef_t * marketDef
int alienBaseInterest
signed int difficulty
linkedList_t * initialCraft
float componentRate
float researchRate
float employeeRate
char id[MAX_VAR]
linkedList_t * next
Definition list.h:32
char * campaign[MAX_CAMPAIGNS]
Definition cp_research.h:94
bool markOnly[MAX_CAMPAIGNS]
Definition cp_research.h:93
Defines all attributes of objects used in the inventory.
Definition inv_shared.h:264
const char * id
Definition inv_shared.h:268
int base[MAX_EMPL]
int rankBonus[MAX_EMPL]
int aircraftFactor
float debtInterest
int baseUpkeep
int aircraftDivisor
struct that holds the sanity check data
Definition cp_parse.cpp:603
bool(* check)(void)
Definition cp_parse.cpp:604
const char * name
Definition cp_parse.cpp:605
char id[MAX_VAR]
Definition chr_shared.h:309
char tech[MAX_VAR]
Definition chr_shared.h:311
This is the technology parsed from research.ufo.
mailSentType_t mailSent
markResearched_t markResearched
#define UFO_SIZE_T
Definition ufotypes.h:89