00001
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026 #include "../client.h"
00027 #include "../ui/ui_main.h"
00028 #include "cp_campaign.h"
00029 #include "cp_overlay.h"
00030 #include "cp_mapfightequip.h"
00031 #include "cp_hospital.h"
00032 #include "cp_hospital_callbacks.h"
00033 #include "cp_base_callbacks.h"
00034 #include "cp_basedefence_callbacks.h"
00035 #include "cp_team.h"
00036 #include "cp_team_callbacks.h"
00037 #include "cp_popup.h"
00038 #include "cp_map.h"
00039 #include "cp_ufo.h"
00040 #include "cp_installation_callbacks.h"
00041 #include "cp_alien_interest.h"
00042 #include "cp_missions.h"
00043 #include "cp_mission_triggers.h"
00044 #include "cp_nations.h"
00045 #include "cp_statistics.h"
00046 #include "cp_time.h"
00047 #include "cp_xvi.h"
00048 #include "cp_aircraft_callbacks.h"
00049 #include "cp_fightequip_callbacks.h"
00050 #include "cp_produce_callbacks.h"
00051 #include "cp_transfer_callbacks.h"
00052 #include "cp_employee_callbacks.h"
00053 #include "cp_market_callbacks.h"
00054 #include "cp_research_callbacks.h"
00055 #include "cp_uforecovery_callbacks.h"
00056 #include "save/save_campaign.h"
00057
00058 struct memPool_s *cp_campaignPool;
00059 ccs_t ccs;
00060 cvar_t *cp_campaign;
00061 cvar_t *cp_start_employees;
00062 cvar_t *cp_missiontest;
00063
00064 typedef struct {
00065 int ucn;
00066 int HP;
00067 int STUN;
00068 int morale;
00069
00070 chrScoreGlobal_t chrscore;
00071 } updateCharacter_t;
00072
00082 void CP_ParseCharacterData (struct dbuffer *msg)
00083 {
00084 static linkedList_t *updateCharacters = NULL;
00085
00086 if (!msg) {
00087 const linkedList_t *l = updateCharacters;
00088 while (l) {
00089 const updateCharacter_t *c = (updateCharacter_t*)l->data;
00090 employee_t *employee = E_GetEmployeeFromChrUCN(c->ucn);
00091 character_t* chr;
00092
00093 l = l->next;
00094
00095 if (!employee) {
00096 Com_Printf("Warning: Could not get character with ucn: %i.\n", c->ucn);
00097 continue;
00098 }
00099 chr = &employee->chr;
00100 chr->HP = min(c->HP, chr->maxHP);
00101 chr->STUN = c->STUN;
00102 chr->morale = c->morale;
00103
00105 memcpy(chr->score.experience, c->chrscore.experience, sizeof(chr->score.experience));
00106 memcpy(chr->score.skills, c->chrscore.skills, sizeof(chr->score.skills));
00107 memcpy(chr->score.kills, c->chrscore.kills, sizeof(chr->score.kills));
00108 memcpy(chr->score.stuns, c->chrscore.stuns, sizeof(chr->score.stuns));
00109 chr->score.assignedMissions = c->chrscore.assignedMissions;
00110 }
00111 LIST_Delete(&updateCharacters);
00112 } else {
00113 int i, j;
00114 const int num = NET_ReadByte(msg);
00115
00116 if (num < 0)
00117 Com_Error(ERR_DROP, "CP_ParseCharacterData: NET_ReadShort error (%i)\n", num);
00118
00119 LIST_Delete(&updateCharacters);
00120
00121 for (i = 0; i < num; i++) {
00122 updateCharacter_t c;
00123 memset(&c, 0, sizeof(c));
00124 c.ucn = NET_ReadShort(msg);
00125 c.HP = NET_ReadShort(msg);
00126 c.STUN = NET_ReadByte(msg);
00127 c.morale = NET_ReadByte(msg);
00128
00129 for (j = 0; j < SKILL_NUM_TYPES + 1; j++)
00130 c.chrscore.experience[j] = NET_ReadLong(msg);
00131 for (j = 0; j < SKILL_NUM_TYPES; j++)
00132 c.chrscore.skills[j] = NET_ReadByte(msg);
00133 for (j = 0; j < KILLED_NUM_TYPES; j++)
00134 c.chrscore.kills[j] = NET_ReadShort(msg);
00135 for (j = 0; j < KILLED_NUM_TYPES; j++)
00136 c.chrscore.stuns[j] = NET_ReadShort(msg);
00137 c.chrscore.assignedMissions = NET_ReadShort(msg);
00138 LIST_Add(&updateCharacters, (const byte*)&c, sizeof(c));
00139 }
00140 }
00141 }
00142
00146 qboolean CP_IsRunning (void)
00147 {
00148 return ccs.curCampaign != NULL;
00149 }
00150
00159 static qboolean CP_MapIsSelectable (mission_t *mission, int mapIdx, const vec2_t pos)
00160 {
00161 mapDef_t *md;
00162
00163 md = Com_GetMapDefByIDX(mapIdx);
00164 if (md->storyRelated)
00165 return qfalse;
00166
00167 if (pos && !MAP_PositionFitsTCPNTypes(pos, md->terrains, md->cultures, md->populations, NULL))
00168 return qfalse;
00169
00170 if (!mission->ufo) {
00171
00172 if (md->ufos)
00173 return qfalse;
00174 } else if (md->ufos) {
00175
00176
00177 const ufoType_t type = mission->ufo->ufotype;
00178 const char *ufoID;
00179
00180 if (mission->crashed)
00181 ufoID = Com_UFOCrashedTypeToShortName(type);
00182 else
00183 ufoID = Com_UFOTypeToShortName(type);
00184
00185 if (!LIST_ContainsString(md->ufos, ufoID))
00186 return qfalse;
00187 } else {
00188 return qfalse;
00189 }
00190
00191 return qtrue;
00192 }
00193
00201 qboolean CP_ChooseMap (mission_t *mission, const vec2_t pos)
00202 {
00203 int i;
00204 int maxHits = 1;
00205 int hits = 0;
00206 int minMissionAppearance = 9999;
00207 int randomNum;
00208
00209 if (mission->mapDef)
00210 return qtrue;
00211
00212
00213 while (maxHits) {
00214 maxHits = 0;
00215 for (i = 0; i < cls.numMDs; i++) {
00216 mapDef_t *md;
00217
00218
00219 if (!CP_MapIsSelectable(mission, i, pos))
00220 continue;
00221
00222 maxHits++;
00223 md = Com_GetMapDefByIDX(i);
00224 if (md->timesAlreadyUsed < minMissionAppearance) {
00225
00226
00227
00228 hits = 0;
00229 minMissionAppearance = md->timesAlreadyUsed;
00230 break;
00231 } else if (md->timesAlreadyUsed == minMissionAppearance)
00232 hits++;
00233 }
00234
00235 if (i >= cls.numMDs) {
00236
00237 break;
00238 }
00239 }
00240
00241 if (!maxHits) {
00242
00243 if (mission->category == INTERESTCATEGORY_RESCUE) {
00244
00245 mission->mapDef = Com_GetMapDefinitionByID("rescue");
00246 mission->mapDef->timesAlreadyUsed++;
00247 if (!mission->mapDef)
00248 Com_Error(ERR_DROP, "Could not find mapdef rescue");
00249 return qtrue;
00250 } else if (mission->crashed) {
00251
00252 mission->mapDef = Com_GetMapDefinitionByID("ufocrash");
00253 mission->mapDef->timesAlreadyUsed++;
00254 if (!mission->mapDef)
00255 Com_Error(ERR_DROP, "Could not find mapdef ufocrash");
00256 return qtrue;
00257 } else {
00258 Com_Printf("CP_ChooseMap: Could not find map with required conditions:\n");
00259 Com_Printf(" ufo: %s -- pos: ", mission->ufo ? Com_UFOTypeToShortName(mission->ufo->ufotype) : "none");
00260 if (pos)
00261 Com_Printf("%s", MapIsWater(MAP_GetColor(pos, MAPTYPE_TERRAIN)) ? " (in water) " : "");
00262 if (pos)
00263 Com_Printf("(%.02f, %.02f)\n", pos[0], pos[1]);
00264 else
00265 Com_Printf("none\n");
00266 return qfalse;
00267 }
00268 }
00269
00270
00271
00272 randomNum = rand() % hits;
00273
00274
00275 for (i = 0; i < cls.numMDs; i++) {
00276 mapDef_t *md;
00277
00278
00279 if (!CP_MapIsSelectable(mission, i, pos))
00280 continue;
00281
00282 md = Com_GetMapDefByIDX(i);
00283 if (md->timesAlreadyUsed > minMissionAppearance)
00284 continue;
00285
00286
00287 assert(md->timesAlreadyUsed == minMissionAppearance);
00288
00289 if (!randomNum)
00290 break;
00291 else
00292 randomNum--;
00293 }
00294
00295
00296 mission->mapDef = Com_GetMapDefByIDX(i);
00297 mission->mapDef->timesAlreadyUsed++;
00298 if (cp_missiontest->integer)
00299 Com_Printf("Selected map '%s' (among %i possible maps)\n", mission->mapDef->id, hits);
00300 else
00301 Com_DPrintf(DEBUG_CLIENT, "Selected map '%s' (among %i possible maps)\n", mission->mapDef->id, hits);
00302
00303 return qtrue;
00304 }
00305
00309 void CP_EndCampaign (qboolean won)
00310 {
00311 Cmd_ExecuteString("game_exit");
00312
00313 if (won)
00314 UI_InitStack("endgame", NULL, qtrue, qtrue);
00315 else
00316 UI_InitStack("lostgame", NULL, qtrue, qtrue);
00317
00318 Com_Drop();
00319 }
00320
00324 void CP_CheckLostCondition (void)
00325 {
00326 qboolean endCampaign = qfalse;
00327
00328 const float nationBelowLimitPercentage = 0.5f;
00329
00330 if (cp_missiontest->integer)
00331 return;
00332
00333 if (!endCampaign && ccs.credits < -ccs.curCampaign->negativeCreditsUntilLost) {
00334 UI_RegisterText(TEXT_STANDARD, _("You've gone too far into debt."));
00335 endCampaign = qtrue;
00336 }
00337
00341 if (!ccs.numBases && ccs.credits < ccs.curCampaign->basecost - ccs.curCampaign->negativeCreditsUntilLost) {
00342 UI_RegisterText(TEXT_STANDARD, _("You've lost your bases and don't have enough money to build new ones."));
00343 endCampaign = qtrue;
00344 }
00345
00346 if (!endCampaign) {
00347 if (CP_GetAverageXVIRate() > ccs.curCampaign->maxAllowedXVIRateUntilLost) {
00348 UI_RegisterText(TEXT_STANDARD, _("You have failed in your charter to protect Earth."
00349 " Our home and our people have fallen to the alien infection. Only a handful"
00350 " of people on Earth remain human, and the remaining few no longer have a"
00351 " chance to stem the tide. Your command is no more; PHALANX is no longer"
00352 " able to operate as a functioning unit. Nothing stands between the aliens"
00353 " and total victory."));
00354 endCampaign = qtrue;
00355 } else {
00356
00357 int j, nationBelowLimit = 0;
00358 for (j = 0; j < ccs.numNations; j++) {
00359 const nation_t *nation = &ccs.nations[j];
00360 if (nation->stats[0].happiness < ccs.curCampaign->minhappiness) {
00361 nationBelowLimit++;
00362 }
00363 }
00364 if (nationBelowLimit >= nationBelowLimitPercentage * ccs.numNations) {
00365
00366 UI_RegisterText(TEXT_STANDARD, _("Under your command, PHALANX operations have"
00367 " consistently failed to protect nations."
00368 " The UN, highly unsatisfied with your performance, has decided to remove"
00369 " you from command and subsequently disbands the PHALANX project as an"
00370 " effective task force. No further attempts at global cooperation are made."
00371 " Earth's nations each try to stand alone against the aliens, and eventually"
00372 " fall one by one."));
00373 endCampaign = qtrue;
00374 }
00375 }
00376 }
00377
00378 if (endCampaign) {
00379 Cvar_SetValue("mission_uforecovered", 0);
00380 CP_EndCampaign(qfalse);
00381 }
00382 }
00383
00384
00385 #define XVI_LOST_START_PERCENTAGE 0.20f
00386 #define XVI_WON_START_PERCENTAGE 0.05f
00387
00396 void CL_HandleNationData (qboolean won, mission_t * mis)
00397 {
00398 int i, isOnEarth = 0;
00399 const float civilianSum = (float) (ccs.missionResults.civiliansSurvived + ccs.missionResults.civiliansKilled + ccs.missionResults.civiliansKilledFriendlyFire);
00400 const float alienSum = (float) (ccs.missionResults.aliensSurvived + ccs.missionResults.aliensKilled + ccs.missionResults.aliensStunned);
00401 float performance, performanceAlien, performanceCivilian;
00402 float deltaHappiness = 0.0f;
00403 float happiness_divisor = 5.0f;
00404
00406 if (civilianSum == 0) {
00407 Com_DPrintf(DEBUG_CLIENT, "CL_HandleNationData: Warning, civilianSum == 0, score for this mission will default to 0.\n");
00408 performance = 0.0f;
00409 }
00410 else {
00411
00412 performanceCivilian = (2 * civilianSum - ccs.missionResults.civiliansKilled - 2 * ccs.missionResults.civiliansKilledFriendlyFire) * 3 / (2 * civilianSum) - 2;
00415 performanceAlien = ccs.missionResults.aliensKilled + ccs.missionResults.aliensStunned - alienSum;
00416 performance = performanceCivilian + performanceAlien;
00417 }
00418
00419
00420 won ? ccs.campaignStats.missionsWon++ : ccs.campaignStats.missionsLost++;
00421
00422
00423 deltaHappiness = 0.004 * civilianSum + 0.004 * alienSum;
00424
00425
00426 if (deltaHappiness > HAPPINESS_MAX_MISSION_IMPACT)
00427 deltaHappiness = HAPPINESS_MAX_MISSION_IMPACT;
00428
00429 for (i = 0; i < ccs.numNations; i++) {
00430 nation_t *nation = &ccs.nations[i];
00431
00432
00433 if (nation == ccs.battleParameters.nation) {
00434 NAT_SetHappiness(nation, nation->stats[0].happiness + performance * deltaHappiness);
00435 isOnEarth++;
00436 }
00437 else {
00438 NAT_SetHappiness(nation, nation->stats[0].happiness + performance * deltaHappiness / happiness_divisor);
00439 }
00440 }
00441
00442 if (!isOnEarth)
00443 Com_DPrintf(DEBUG_CLIENT, "CL_HandleNationData: Warning, mission '%s' located in an unknown country '%s'.\n", mis->id, ccs.battleParameters.nation ? ccs.battleParameters.nation->id : "no nation");
00444 else if (isOnEarth > 1)
00445 Com_DPrintf(DEBUG_CLIENT, "CL_HandleNationData: Error, mission '%s' located in many countries '%s'.\n", mis->id, ccs.battleParameters.nation->id);
00446 }
00447
00451 static void CP_CheckMissionEnd (void)
00452 {
00453 const linkedList_t *list = ccs.missions;
00454
00455 while (list) {
00456
00457
00458 linkedList_t *next = list->next;
00459 mission_t *mission = (mission_t *)list->data;
00460 if (CP_CheckMissionLimitedInTime(mission) && Date_LaterThan(ccs.date, mission->finalDate)) {
00461 CP_MissionStageEnd(mission);
00462 }
00463 list = next;
00464 }
00465 }
00466
00467
00468
00474 const char* CL_SecondConvert (int second)
00475 {
00476 static char buffer[6];
00477 const int hour = second / SECONDS_PER_HOUR;
00478 const int min = (second - hour * SECONDS_PER_HOUR) / 60;
00479 Com_sprintf(buffer, sizeof(buffer), "%2i:%02i", hour, min);
00480 return buffer;
00481 }
00482
00483 static const int monthLength[MONTHS_PER_YEAR] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
00484
00494 void CL_DateConvert (const date_t * date, byte *day, byte *month, short *year)
00495 {
00496 byte i;
00497 int d;
00498
00499
00500 *year = date->day / DAYS_PER_YEAR;
00501
00502
00503 d = date->day % DAYS_PER_YEAR;
00504
00505
00506 for (i = 0; i < MONTHS_PER_YEAR; i++) {
00507 if (d < monthLength[i])
00508 break;
00509 d -= monthLength[i];
00510 }
00511
00512
00513 *day = d + 1;
00514 *month = i + 1;
00515 assert(*month >= 1 && *month <= MONTHS_PER_YEAR);
00516 assert(*day >= 1 && *day <= monthLength[i]);
00517 }
00518
00526 int CL_DateCreateDay (const short years, const byte months, const byte days)
00527 {
00528 int i;
00529 int day;
00530
00531
00532 day = DAYS_PER_YEAR * years;
00533
00534
00535 for (i = 0; i < months; i++)
00536 day += monthLength[i];
00537
00538 day += days - 1;
00539
00540 return day;
00541 }
00542
00550 int CL_DateCreateSeconds (byte hours, byte minutes, byte seconds)
00551 {
00552 int sec;
00553
00554
00555 sec = SECONDS_PER_HOUR * hours;
00556
00557
00558 sec += 60 * minutes;
00559
00560
00561 sec += seconds;
00562
00563 return sec;
00564 }
00565
00571 void CL_DateConvertLong (const date_t * date, dateLong_t * dateLong)
00572 {
00573 CL_DateConvert(date, &dateLong->day, &dateLong->month, &dateLong->year);
00575 dateLong->hour = date->sec / SECONDS_PER_HOUR;
00576 dateLong->min = (date->sec - dateLong->hour * SECONDS_PER_HOUR) / 60;
00577 dateLong->sec = date->sec - dateLong->hour * SECONDS_PER_HOUR - dateLong->min * 60;
00578 }
00579
00586 static void CL_CampaignFunctionPeriodicCall (int dt, qboolean updateRadarOverlay)
00587 {
00588 UFO_CampaignRunUFOs(dt);
00589 CL_CampaignRunAircraft(dt, updateRadarOverlay);
00590
00591 AIRFIGHT_CampaignRunBaseDefence(dt);
00592 AIRFIGHT_CampaignRunProjectiles(dt);
00593 CP_CheckNewMissionDetectedOnGeoscape();
00594
00595
00596 UFO_UpdateAlienInterestForAllBasesAndInstallations();
00597
00598
00599 AB_UpdateStealthForAllBase();
00600
00601 UFO_CampaignCheckEvents();
00602 }
00603
00604 qboolean CP_OnGeoscape (void)
00605 {
00606 return !strcmp("geoscape", UI_GetActiveWindowName());
00607 }
00608
00615 const int DETECTION_INTERVAL = (SECONDS_PER_HOUR / 2);
00616
00624 void CL_CampaignRun (void)
00625 {
00626 if (!CP_IsRunning())
00627 return;
00628
00629 if (!CP_OnGeoscape())
00630 return;
00631
00632
00633 ccs.timer += cls.frametime * ccs.gameTimeScale;
00634
00635 if (ccs.timer >= 1.0) {
00636
00637 int currenthour;
00638 int currentmin;
00639 int i;
00640 const int currentinterval = (int)floor(ccs.date.sec) % DETECTION_INTERVAL;
00641 int dt = DETECTION_INTERVAL - currentinterval;
00642 dateLong_t date;
00643 const int checks = (currentinterval + (int)floor(ccs.timer)) / DETECTION_INTERVAL;
00644
00645 currenthour = (int)floor(ccs.date.sec / SECONDS_PER_HOUR);
00646 currentmin = (int)floor(ccs.date.sec / SECONDS_PER_MINUTE);
00647
00648
00649
00650
00651
00652 for (i = 0; i < checks; i++) {
00653 ccs.date.sec += dt;
00654 ccs.timer -= dt;
00655 CL_CampaignFunctionPeriodicCall(dt, qfalse);
00656
00657
00658 if (CL_IsTimeStopped()) {
00659 ccs.timer = 0.0f;
00660 break;
00661 }
00662 dt = DETECTION_INTERVAL;
00663 }
00664
00665 dt = (int)floor(ccs.timer);
00666
00667 ccs.date.sec += dt;
00668 ccs.timer -= dt;
00669
00670
00671
00672 while (currentmin < (int)floor(ccs.date.sec / SECONDS_PER_MINUTE)) {
00673 currentmin++;
00674 PR_ProductionRun();
00675 }
00676
00677
00678
00679 while (currenthour < (int)floor(ccs.date.sec / SECONDS_PER_HOUR)) {
00680 currenthour++;
00681 RS_ResearchRun();
00682 UR_ProcessActive();
00683 AII_UpdateInstallationDelay();
00684 AII_RepairAircraft();
00685 TR_TransferCheck();
00686 CP_IncreaseAlienInterest();
00687 }
00688
00689
00690 while (ccs.date.sec > SECONDS_PER_DAY) {
00691 ccs.date.sec -= SECONDS_PER_DAY;
00692 ccs.date.day++;
00693
00694 B_UpdateBaseData();
00695 INS_UpdateInstallationData();
00696 HOS_HospitalRun();
00697 CP_SpawnNewMissions();
00698 CP_SpreadXVI();
00699 NAT_UpdateHappinessForAllNations();
00700 AB_BaseSearchedByNations();
00701 CL_CampaignRunMarket();
00702 CP_CheckCampaignEvents();
00703 CP_ReduceXVIEverywhere();
00704
00705
00706 CP_UpdateNationXVIInfection();
00707 }
00708
00709
00710
00711
00712 CL_CampaignFunctionPeriodicCall(dt, qtrue);
00713
00714 UP_GetUnreadMails();
00715 CP_CheckMissionEnd();
00716 CP_CheckLostCondition();
00717
00718 Cmd_ExecuteString("check_baseattacks");
00719 BDEF_AutoSelectTarget();
00720
00721
00722 CL_DateConvertLong(&ccs.date, &date);
00723
00724 if (date.day == 1 && ccs.paid && ccs.numBases) {
00725 CP_NationBackupMonthlyData();
00726 CP_NationHandleBudget();
00727 ccs.paid = qfalse;
00728 } else if (date.day > 1)
00729 ccs.paid = qtrue;
00730
00731 CP_UpdateXVIMapButton();
00732 CL_UpdateTime();
00733 }
00734 }
00735
00736 #define MAX_CREDITS 10000000
00737
00742 void CL_UpdateCredits (int credits)
00743 {
00744
00745 if (credits > MAX_CREDITS)
00746 credits = MAX_CREDITS;
00747 ccs.credits = credits;
00748 Cvar_Set("mn_credits", va(_("%i c"), ccs.credits));
00749 }
00750
00755 static qboolean CP_LoadMapDefStatXML (mxml_node_t *parent)
00756 {
00757 mxml_node_t *node;
00758
00759 for (node = mxml_GetNode(parent, SAVE_CAMPAIGN_MAPDEF); node; node = mxml_GetNextNode(node, parent, SAVE_CAMPAIGN_MAPDEF)) {
00760 const char *s = mxml_GetString(node, SAVE_CAMPAIGN_MAPDEF_ID);
00761 mapDef_t *map;
00762
00763 if (s[0] == '\0') {
00764 Com_Printf("Warning: MapDef with no id in xml!\n");
00765 continue;
00766 }
00767 map = Com_GetMapDefinitionByID(s);
00768 if (!map) {
00769 Com_Printf("Warning: No MapDef with id '%s'!\n", s);
00770 continue;
00771 }
00772 map->timesAlreadyUsed = mxml_GetInt(node, SAVE_CAMPAIGN_MAPDEF_COUNT, 0);
00773 }
00774
00775 return qtrue;
00776 }
00777
00782 qboolean CP_LoadXML (mxml_node_t *parent)
00783 {
00784 mxml_node_t *campaignNode;
00785 mxml_node_t *mapNode;
00786 const char *name;
00787 campaign_t *campaign;
00788 mxml_node_t *mapDefStat;
00789
00790 campaignNode = mxml_GetNode(parent, SAVE_CAMPAIGN_CAMPAIGN);
00791 if (!campaignNode) {
00792 Com_Printf("Did not find campaign entry in xml!\n");
00793 return qfalse;
00794 }
00795 if (!(name = mxml_GetString(campaignNode, SAVE_CAMPAIGN_ID))) {
00796 Com_Printf("couldn't locate campaign name in savegame\n");
00797 return qfalse;
00798 }
00799
00800 campaign = CL_GetCampaign(name);
00801 if (!campaign) {
00802 Com_Printf("......campaign \"%s\" doesn't exist.\n", name);
00803 return qfalse;
00804 }
00805
00806 CP_CampaignInit(campaign, qtrue);
00807
00808 MAP_Init();
00809
00810
00811 CL_UpdateCredits(mxml_GetLong(campaignNode, SAVE_CAMPAIGN_CREDITS, 0));
00812 ccs.paid = mxml_GetBool(campaignNode, SAVE_CAMPAIGN_PAID, qfalse);
00813
00814 cls.nextUniqueCharacterNumber = mxml_GetInt(campaignNode, SAVE_CAMPAIGN_NEXTUNIQUECHARACTERNUMBER, 0);
00815
00816 mxml_GetDate(campaignNode, SAVE_CAMPAIGN_DATE, &ccs.date.day, &ccs.date.sec);
00817
00818
00819 ccs.civiliansKilled = mxml_GetInt(campaignNode, SAVE_CAMPAIGN_CIVILIANSKILLED, 0);
00820 ccs.aliensKilled = mxml_GetInt(campaignNode, SAVE_CAMPAIGN_ALIENSKILLED, 0);
00821
00822 Com_DPrintf(DEBUG_CLIENT, "CP_LoadXML: Getting position\n");
00823
00824
00825 mapNode = mxml_GetNode(campaignNode, SAVE_CAMPAIGN_MAP);
00826 ccs.center[0] = mxml_GetFloat(mapNode, SAVE_CAMPAIGN_CENTER0, 0.0);
00827 ccs.center[1] = mxml_GetFloat(mapNode, SAVE_CAMPAIGN_CENTER1, 0.0);
00828 ccs.angles[0] = mxml_GetFloat(mapNode, SAVE_CAMPAIGN_ANGLES0, 0.0);
00829 ccs.angles[1] = mxml_GetFloat(mapNode, SAVE_CAMPAIGN_ANGLES1, 0.0);
00830 ccs.zoom = mxml_GetFloat(mapNode, SAVE_CAMPAIGN_ZOOM, 0.0);
00831
00832
00833
00834
00835 cl_geoscape_overlay->integer = mxml_GetInt(mapNode, SAVE_CAMPAIGN_CL_GEOSCAPE_OVERLAY, 0);
00836 radarOverlayWasSet = mxml_GetBool(mapNode, SAVE_CAMPAIGN_RADAROVERLAYWASSET, qfalse);
00837 ccs.XVIShowMap = mxml_GetBool(mapNode, SAVE_CAMPAIGN_XVISHOWMAP, qfalse);
00838 CP_UpdateXVIMapButton();
00839
00840 mapDefStat = mxml_GetNode(campaignNode, SAVE_CAMPAIGN_MAPDEFSTAT);
00841 if (mapDefStat && !CP_LoadMapDefStatXML(mapDefStat))
00842 return qfalse;
00843
00844 mxmlDelete(campaignNode);
00845 return qtrue;
00846 }
00847
00852 static qboolean CP_SaveMapDefStatXML (mxml_node_t *parent)
00853 {
00854 int i;
00855
00856 for (i = 0; i < cls.numMDs; i++) {
00857 const mapDef_t const* map = Com_GetMapDefByIDX(i);
00858 if (map->timesAlreadyUsed > 0) {
00859 mxml_node_t *node = mxml_AddNode(parent, SAVE_CAMPAIGN_MAPDEF);
00860 mxml_AddString(node, SAVE_CAMPAIGN_MAPDEF_ID, map->id);
00861 mxml_AddInt(node, SAVE_CAMPAIGN_MAPDEF_COUNT, map->timesAlreadyUsed);
00862 }
00863 }
00864
00865 return qtrue;
00866 }
00867
00872 qboolean CP_SaveXML (mxml_node_t *parent)
00873 {
00874 mxml_node_t *campaign;
00875 mxml_node_t *map;
00876 mxml_node_t *mapDefStat;
00877
00878 campaign = mxml_AddNode(parent, SAVE_CAMPAIGN_CAMPAIGN);
00879
00880 mxml_AddString(campaign, SAVE_CAMPAIGN_ID, ccs.curCampaign->id);
00881 mxml_AddDate(campaign, SAVE_CAMPAIGN_DATE, ccs.date.day, ccs.date.sec);
00882 mxml_AddLong(campaign, SAVE_CAMPAIGN_CREDITS, ccs.credits);
00883 mxml_AddShort(campaign, SAVE_CAMPAIGN_PAID, ccs.paid);
00884 mxml_AddShortValue(campaign, SAVE_CAMPAIGN_NEXTUNIQUECHARACTERNUMBER, cls.nextUniqueCharacterNumber);
00885
00886 mxml_AddIntValue(campaign, SAVE_CAMPAIGN_CIVILIANSKILLED, ccs.civiliansKilled);
00887 mxml_AddIntValue(campaign, SAVE_CAMPAIGN_ALIENSKILLED, ccs.aliensKilled);
00888
00889
00890 map = mxml_AddNode(campaign, SAVE_CAMPAIGN_MAP);
00891 mxml_AddFloat(map, SAVE_CAMPAIGN_CENTER0, ccs.center[0]);
00892 mxml_AddFloat(map, SAVE_CAMPAIGN_CENTER1, ccs.center[1]);
00893 mxml_AddFloat(map, SAVE_CAMPAIGN_ANGLES0, ccs.angles[0]);
00894 mxml_AddFloat(map, SAVE_CAMPAIGN_ANGLES1, ccs.angles[1]);
00895 mxml_AddFloat(map, SAVE_CAMPAIGN_ZOOM, ccs.zoom);
00896 mxml_AddShort(map, SAVE_CAMPAIGN_CL_GEOSCAPE_OVERLAY, cl_geoscape_overlay->integer);
00897 mxml_AddBool(map, SAVE_CAMPAIGN_RADAROVERLAYWASSET, radarOverlayWasSet);
00898 mxml_AddBool(map, SAVE_CAMPAIGN_XVISHOWMAP, ccs.XVIShowMap);
00899
00900 mapDefStat = mxml_AddNode(campaign, SAVE_CAMPAIGN_MAPDEFSTAT);
00901 if (!CP_SaveMapDefStatXML(mapDefStat))
00902 return qfalse;
00903
00904 return qtrue;
00905 }
00906
00913 void CP_StartSelectedMission (void)
00914 {
00915 mission_t *mis;
00916 aircraft_t *aircraft;
00917 base_t *base;
00918
00919 if (!ccs.missionAircraft) {
00920 Com_Printf("CP_StartSelectedMission: No mission aircraft\n");
00921 return;
00922 }
00923
00924 aircraft = ccs.missionAircraft;
00925 base = aircraft->homebase;
00926
00927 if (!ccs.selectedMission)
00928 ccs.selectedMission = aircraft->mission;
00929
00930 if (!ccs.selectedMission) {
00931 Com_Printf("CP_StartSelectedMission: No mission selected\n");
00932 return;
00933 }
00934
00935 mis = ccs.selectedMission;
00936
00937
00938 memset(&ccs.missionResults, 0, sizeof(ccs.missionResults));
00939
00940
00941 if (!mis->active) {
00942 Com_Printf("CP_StartSelectedMission: Dropship not near landing zone: mis->active: %i\n", mis->active);
00943 return;
00944 }
00945 if (AIR_GetTeamSize(aircraft) == 0) {
00946 Com_Printf("CP_StartSelectedMission: No team in dropship.\n");
00947 return;
00948 }
00949
00950
00951 SV_Shutdown("Server quit.", qfalse);
00952 CL_Disconnect();
00953
00954 CP_CreateBattleParameters(mis, &ccs.battleParameters);
00955 CP_SetMissionVars(mis);
00956
00957 Cvar_SetValue("mission_uforecovered", 0);
00958 Cvar_SetValue("mn_autogo", 0);
00959
00960
00961 ccs.eMission = base->storage;
00962 CL_CleanTempInventory(base);
00963 CL_CleanupAircraftCrew(aircraft, &ccs.eMission);
00964 CP_StartMissionMap(mis);
00965 }
00966
00976 static float CP_GetWinProbabilty (const mission_t *mis, const base_t *base, const aircraft_t *aircraft)
00977 {
00978 float winProbability;
00979
00980 if (mis->stage != STAGE_BASE_ATTACK) {
00981 assert(aircraft);
00982
00983 switch (mis->category) {
00984 case INTERESTCATEGORY_TERROR_ATTACK:
00985
00987 winProbability = exp((0.5 - .15 * ccs.curCampaign->difficulty) * AIR_GetTeamSize(aircraft) - ccs.battleParameters.aliens);
00988 break;
00989 case INTERESTCATEGORY_XVI:
00990
00992 winProbability = exp((0.5 - .15 * ccs.curCampaign->difficulty) * AIR_GetTeamSize(aircraft) - ccs.battleParameters.aliens);
00993 break;
00994 default:
00996 winProbability = exp((0.5 - .15 * ccs.curCampaign->difficulty) * AIR_GetTeamSize(aircraft) - ccs.battleParameters.aliens);
00997 break;
00998 }
00999 Com_DPrintf(DEBUG_CLIENT, "Aliens: %i - Soldiers: %i -- probability to win: %.02f\n", ccs.battleParameters.aliens, AIR_GetTeamSize(aircraft), winProbability);
01000
01001 return winProbability;
01002 } else {
01003 linkedList_t *hiredSoldiers = NULL;
01004 linkedList_t *ugvs = NULL;
01005 linkedList_t *listPos;
01006 const int numSoldiers = E_GetHiredEmployees(base, EMPL_SOLDIER, &hiredSoldiers);
01007 const int numUGVs = E_GetHiredEmployees(base, EMPL_ROBOT, &ugvs);
01008
01009 assert(base);
01010
01011
01012
01013 if (numSoldiers || numUGVs) {
01014 float increaseWinProbability = 1.0f;
01015 listPos = hiredSoldiers;
01016 while (listPos) {
01017 const employee_t *employee = (employee_t *)listPos->data;
01018
01019 if (!E_IsAwayFromBase(employee)) {
01020 const character_t *chr = &employee->chr;
01021 const chrScoreGlobal_t *score = &chr->score;
01022
01023 if (score->assignedMissions) {
01024 const rank_t *rank = CL_GetRankByIdx(score->rank);
01026 if (score->experience[SKILL_CLOSE] > 70) {
01027 increaseWinProbability *= rank->factor;
01028 }
01029 }
01030 }
01031 listPos = listPos->next;
01032 }
01033
01034 listPos = ugvs;
01035 while (listPos) {
01036 const employee_t *employee = (employee_t *)listPos->data;
01037
01038 if (!E_IsAwayFromBase(employee)) {
01039 const character_t *chr = &employee->chr;
01040 const chrScoreGlobal_t *score = &chr->score;
01041 const rank_t *rank = CL_GetRankByIdx(score->rank);
01043 if (score->experience[SKILL_CLOSE] > 70) {
01044 increaseWinProbability *= rank->factor;
01045 }
01046 }
01047 listPos = listPos->next;
01048 }
01049
01050 winProbability = exp((0.5 - .15 * ccs.curCampaign->difficulty) * numSoldiers - ccs.battleParameters.aliens);
01051 winProbability += increaseWinProbability;
01052
01053 Com_DPrintf(DEBUG_CLIENT, "Aliens: %i - Soldiers: %i - UGVs: %i -- probability to win: %.02f\n",
01054 ccs.battleParameters.aliens, numSoldiers, numUGVs, winProbability);
01055
01056 LIST_Delete(&hiredSoldiers);
01057 LIST_Delete(&ugvs);
01058
01059 return winProbability;
01060 } else {
01061
01062 Com_DPrintf(DEBUG_CLIENT, "Aliens: %i - Soldiers: 0 -- battle lost\n", ccs.battleParameters.aliens);
01063 return 0.0f;
01064 }
01065 }
01066 }
01067
01072 static void CL_AutoMissionAlienCollect (aircraft_t *aircraft)
01073 {
01074 int i;
01075 int aliens = ccs.battleParameters.aliens;
01076
01077 if (!aliens)
01078 return;
01079
01080 MS_AddNewMessage(_("Notice"), _("Collected dead alien bodies"), qfalse, MSG_STANDARD, NULL);
01081
01082 while (aliens > 0) {
01083 battleParam_t *param = &ccs.battleParameters;
01084 for (i = 0; i < param->alienTeamGroup->numAlienTeams; i++) {
01085 const alienTeamGroup_t *group = param->alienTeamGroup;
01086 const teamDef_t *teamDef = group->alienTeams[i];
01087 const int addDeadAlienAmount = aliens > 1 ? rand() % aliens : aliens;
01088 if (!addDeadAlienAmount)
01089 continue;
01090 assert(i < MAX_CARGO);
01091 assert(group->alienTeams[i]);
01092 AL_AddAlienTypeToAircraftCargo(aircraft, teamDef, addDeadAlienAmount, qtrue);
01093 aliens -= addDeadAlienAmount;
01094 if (!aliens)
01095 break;
01096 }
01097 }
01098 }
01099
01107 void CL_GameAutoGo (mission_t *mission)
01108 {
01109 qboolean won;
01110 float winProbability;
01111 base_t *base;
01112
01113
01114
01115 aircraft_t *aircraft = ccs.interceptAircraft;
01116
01117 assert(mission);
01118
01119 CP_CreateBattleParameters(mission, &ccs.battleParameters);
01120
01121 if (!aircraft) {
01122 Com_DPrintf(DEBUG_CLIENT, "CL_GameAutoGo: No update after automission\n");
01123 return;
01124 }
01125
01126 base = aircraft->homebase;
01127
01128 if (mission->stage != STAGE_BASE_ATTACK) {
01129 if (!mission->active) {
01130 MS_AddNewMessage(_("Notice"), _("Your dropship is not near the landing zone"), qfalse, MSG_STANDARD, NULL);
01131 return;
01132 } else if (mission->mapDef->storyRelated) {
01133 Com_DPrintf(DEBUG_CLIENT, "You have to play this mission, because it's story related\n");
01134
01135 Cvar_Set("cp_mission_autogo_available", "0");
01136 return;
01137 }
01138
01139 winProbability = CP_GetWinProbabilty(mission, NULL, aircraft);
01140 } else {
01141 winProbability = CP_GetWinProbabilty(mission, (base_t *)mission->data, NULL);
01142 }
01143
01144 UI_PopWindow(qfalse);
01145
01146 won = frand() < winProbability;
01147
01148
01149 CL_HandleNationData(won, mission);
01150
01151 CP_CheckLostCondition();
01152
01153 CL_AutoMissionAlienCollect(aircraft);
01154
01155
01156 CP_ExecuteMissionTrigger(mission, won);
01157
01158
01159 if (won && ccs.missionResults.recovery) {
01161 missionResults_t *results = &ccs.missionResults;
01162 results->aliensKilled = ccs.battleParameters.aliens;
01163 results->aliensStunned = 0;
01164 results->aliensSurvived = 0;
01165 results->civiliansKilled = 0;
01166 results->civiliansKilledFriendlyFire = 0;
01167 results->civiliansSurvived = ccs.battleParameters.civilians;
01168 results->ownKilled = 0;
01169 results->ownKilledFriendlyFire = 0;
01170 results->ownStunned = 0;
01171 results->ownSurvived = AIR_GetTeamSize(aircraft);
01172 CP_InitMissionResults(won);
01173 Cvar_SetValue("mn_autogo", 1);
01174 UI_PushWindow("won", NULL);
01175 }
01176
01177 CP_MissionEndActions(mission, aircraft, won);
01178
01179 if (won)
01180 MS_AddNewMessage(_("Notice"), _("You've won the battle"), qfalse, MSG_STANDARD, NULL);
01181 else
01182 MS_AddNewMessage(_("Notice"), _("You've lost the battle"), qfalse, MSG_STANDARD, NULL);
01183
01184 MAP_ResetAction();
01185 }
01186
01192 void CP_InitMissionResults (qboolean won)
01193 {
01194 static char resultText[1024];
01195
01196 UI_RegisterText(TEXT_STANDARD, resultText);
01197
01198
01199 Com_sprintf(resultText, sizeof(resultText), _("Aliens killed\t%i\n"), ccs.missionResults.aliensKilled);
01200 Q_strcat(resultText, va(_("Aliens captured\t%i\n"), ccs.missionResults.aliensStunned), sizeof(resultText));
01201 Q_strcat(resultText, va(_("Alien survivors\t%i\n\n"), ccs.missionResults.aliensSurvived), sizeof(resultText));
01202
01203 Q_strcat(resultText, va(_("PHALANX soldiers killed by Aliens\t%i\n"), ccs.missionResults.ownKilled), sizeof(resultText));
01204 Q_strcat(resultText, va(_("PHALANX soldiers missing in action\t%i\n"), ccs.missionResults.ownStunned), sizeof(resultText));
01205 Q_strcat(resultText, va(_("PHALANX friendly fire losses\t%i\n"), ccs.missionResults.ownKilledFriendlyFire), sizeof(resultText));
01206 Q_strcat(resultText, va(_("PHALANX survivors\t%i\n\n"), ccs.missionResults.ownSurvived), sizeof(resultText));
01207
01208 Q_strcat(resultText, va(_("Civilians killed by Aliens\t%i\n"), ccs.missionResults.civiliansKilled), sizeof(resultText));
01209 Q_strcat(resultText, va(_("Civilians killed by friendly fire\t%i\n"), ccs.missionResults.civiliansKilledFriendlyFire), sizeof(resultText));
01210 Q_strcat(resultText, va(_("Civilians saved\t%i\n\n"), ccs.missionResults.civiliansSurvived), sizeof(resultText));
01211 Q_strcat(resultText, va(_("Gathered items (types/all)\t%i/%i\n"), ccs.missionResults.itemTypes,
01212 ccs.missionResults.itemAmount), sizeof(resultText));
01213
01214 if (won && ccs.missionResults.recovery)
01215 Q_strcat(resultText, UFO_MissionResultToString(), sizeof(resultText));
01216 }
01217
01224 static qboolean CL_ShouldUpdateSoldierRank (const rank_t *rank, const character_t* chr)
01225 {
01226 if (rank->type != EMPL_SOLDIER)
01227 return qfalse;
01228
01229
01230 if (chr->score.skills[ABILITY_MIND] < rank->mind)
01231 return qfalse;
01232
01233
01234 if (chr->score.kills[KILLED_ENEMIES] < rank->killedEnemies)
01235 return qfalse;
01236
01237
01238 if (chr->score.kills[KILLED_CIVILIANS] + chr->score.kills[KILLED_TEAM] > rank->killedOthers)
01239 return qfalse;
01240
01241 return qtrue;
01242 }
01243
01250 void CL_UpdateCharacterStats (const base_t *base, const aircraft_t *aircraft)
01251 {
01252 employee_t *employee = NULL;
01253
01254 Com_DPrintf(DEBUG_CLIENT, "CL_UpdateCharacterStats: base: '%s' numTeamList: %i\n",
01255 base->name, cl.numTeamList);
01256
01257 assert(aircraft);
01258
01259
01260 while ((employee = E_GetNextFromBase(EMPL_SOLDIER, employee, aircraft->homebase))) {
01261 if (AIR_IsEmployeeInAircraft(employee, aircraft)) {
01262 character_t *chr = &employee->chr;
01263
01264
01265 chr->score.assignedMissions++;
01266
01271
01272
01273 if (ccs.numRanks >= 2) {
01274 int j;
01275 for (j = ccs.numRanks - 1; j > chr->score.rank; j--) {
01276 const rank_t *rank = CL_GetRankByIdx(j);
01277 if (CL_ShouldUpdateSoldierRank(rank, chr)) {
01278 chr->score.rank = j;
01279 if (chr->HP > 0)
01280 Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("%s has been promoted to %s.\n"), chr->name, _(rank->name));
01281 else
01282 Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("%s has been awarded the posthumous rank of %s\nfor inspirational gallantry in the face of overwhelming odds.\n"), chr->name, _(rank->name));
01283 MS_AddNewMessage(_("Soldier promoted"), cp_messageBuffer, qfalse, MSG_PROMOTION, NULL);
01284 break;
01285 }
01286 }
01287 }
01288 }
01289 }
01290 Com_DPrintf(DEBUG_CLIENT, "CL_UpdateCharacterStats: Done\n");
01291 }
01292
01293 #ifdef DEBUG
01294
01298 static void CL_DebugAllItems_f (void)
01299 {
01300 int i;
01301 base_t *base;
01302
01303 if (Cmd_Argc() < 2) {
01304 Com_Printf("Usage: %s <baseID>\n", Cmd_Argv(0));
01305 return;
01306 }
01307
01308 i = atoi(Cmd_Argv(1));
01309 if (i >= ccs.numBases) {
01310 Com_Printf("invalid baseID (%s)\n", Cmd_Argv(1));
01311 return;
01312 }
01313 base = B_GetBaseByIDX(i);
01314
01315 for (i = 0; i < csi.numODs; i++) {
01316 objDef_t *obj = INVSH_GetItemByIDX(i);
01317 if (!obj->weapon && !obj->numWeapons)
01318 continue;
01319 B_UpdateStorageAndCapacity(base, obj, 1, qfalse, qtrue);
01320 if (base->storage.numItems[i] > 0) {
01321 technology_t *tech = RS_GetTechForItem(obj);
01322 RS_MarkCollected(tech);
01323 }
01324 }
01325 }
01326
01331 static void CL_DebugShowItems_f (void)
01332 {
01333 int i;
01334 base_t *base;
01335
01336 if (Cmd_Argc() < 2) {
01337 Com_Printf("Usage: %s <baseID>\n", Cmd_Argv(0));
01338 return;
01339 }
01340
01341 i = atoi(Cmd_Argv(1));
01342 if (i >= ccs.numBases) {
01343 Com_Printf("invalid baseID (%s)\n", Cmd_Argv(1));
01344 return;
01345 }
01346 base = B_GetBaseByIDX(i);
01347
01348 for (i = 0; i < csi.numODs; i++) {
01349 const objDef_t *obj = INVSH_GetItemByIDX(i);
01350 Com_Printf("%i. %s: %i\n", i, obj->id, base->storage.numItems[i]);
01351 }
01352 }
01353
01357 static void CL_DebugFullCredits_f (void)
01358 {
01359 CL_UpdateCredits(MAX_CREDITS);
01360 }
01361
01366 static void CL_DebugNewEmployees_f (void)
01367 {
01368 int j;
01369 nation_t *nation = &ccs.nations[0];
01371 for (j = 0; j < 5; j++)
01372
01373 E_CreateEmployee(EMPL_SCIENTIST, nation, NULL);
01374
01375 for (j = 0; j < 5; j++)
01376
01377 E_CreateEmployee(EMPL_PILOT, nation, NULL);
01378
01379 for (j = 0; j < 5; j++)
01380
01381 E_CreateEmployee(EMPL_SOLDIER, nation, NULL);
01382
01383 for (j = 0; j < 5; j++)
01384
01385 E_CreateEmployee(EMPL_WORKER, nation, NULL);
01386 }
01387 #endif
01388
01389
01390
01391
01392 static const cmdList_t game_commands[] = {
01393 {"update_base_radar_coverage", RADAR_UpdateBaseRadarCoverage_f, "Update base radar coverage"},
01394 {"addeventmail", CL_EventAddMail_f, "Add a new mail (event trigger) - e.g. after a mission"},
01395 {"stats_update", CL_StatsUpdate_f, NULL},
01396 {"game_go", CP_StartSelectedMission, NULL},
01397 {"game_timestop", CL_GameTimeStop, NULL},
01398 {"game_timeslow", CL_GameTimeSlow, NULL},
01399 {"game_timefast", CL_GameTimeFast, NULL},
01400 {"game_settimeid", CL_SetGameTime_f, NULL},
01401 {"map_center", MAP_CenterOnPoint_f, "Centers the geoscape view on items on the geoscape - and cycle through them"},
01402 {"map_zoom", MAP_Zoom_f, NULL},
01403 {"map_scroll", MAP_Scroll_f, NULL},
01404 {"cp_start_xvi_spreading", CP_StartXVISpreading_f, "Start XVI spreading"},
01405 #ifdef DEBUG
01406 {"debug_listaircraftsample", AIR_ListAircraftSamples_f, "Show aircraft parameter on game console"},
01407 {"debug_listaircraft", AIR_ListAircraft_f, "Debug function to list all aircraft in all bases"},
01408 {"debug_listaircraftidx", AIR_ListCraftIndexes_f, "Debug function to list local/global aircraft indexes"},
01409 {"debug_fullcredits", CL_DebugFullCredits_f, "Debug function to give the player full credits"},
01410 {"debug_addemployees", CL_DebugNewEmployees_f, "Debug function to add 5 new unhired employees of each type"},
01411 {"debug_additems", CL_DebugAllItems_f, "Debug function to add one item of every type to base storage and mark related tech collected"},
01412 {"debug_listitem", CL_DebugShowItems_f, "Debug function to show all items in base storage"},
01413 #endif
01414 {NULL, NULL, NULL}
01415 };
01416
01424 static void CP_AddCampaignCallbackCommands (void)
01425 {
01426 AIM_InitCallbacks();
01427 AIR_InitCallbacks();
01428 B_InitCallbacks();
01429 BDEF_InitCallbacks();
01430 BS_InitCallbacks();
01431 CP_TEAM_InitCallbacks();
01432 E_InitCallbacks();
01433 HOS_InitCallbacks();
01434 INS_InitCallbacks();
01435 TR_InitCallbacks();
01436 PR_InitCallbacks();
01437 RS_InitCallbacks();
01438 UR_InitCallbacks();
01439 }
01440
01441 static void CP_AddCampaignCommands (void)
01442 {
01443 const cmdList_t *commands;
01444
01445 for (commands = game_commands; commands->name; commands++)
01446 Cmd_AddCommand(commands->name, commands->function, commands->description);
01447
01448 CP_AddCampaignCallbackCommands();
01449 }
01450
01458 static void CP_RemoveCampaignCallbackCommands (void)
01459 {
01460 AIM_ShutdownCallbacks();
01461 AIR_ShutdownCallbacks();
01462 B_ShutdownCallbacks();
01463 BDEF_ShutdownCallbacks();
01464 BS_ShutdownCallbacks();
01465 CP_TEAM_ShutdownCallbacks();
01466 E_ShutdownCallbacks();
01467 HOS_ShutdownCallbacks();
01468 INS_ShutdownCallbacks();
01469 TR_ShutdownCallbacks();
01470 PR_ShutdownCallbacks();
01471 RS_ShutdownCallbacks();
01472 UR_ShutdownCallbacks();
01473 MSO_Shutdown();
01474 UP_Shutdown();
01475 }
01476
01477 static void CP_RemoveCampaignCommands (void)
01478 {
01479 const cmdList_t *commands;
01480
01481 for (commands = game_commands; commands->name; commands++)
01482 Cmd_RemoveCommand(commands->name);
01483
01484 CP_RemoveCampaignCallbackCommands();
01485 }
01486
01492 void CP_CampaignInit (campaign_t *campaign, qboolean load)
01493 {
01494 ccs.curCampaign = campaign;
01495
01496 RS_InitTree(load);
01498 CP_AddCampaignCommands();
01499
01500 CL_GameTimeStop();
01501
01502
01503 CL_PopupInit();
01504
01505 CP_InitOverlay();
01506
01507 CP_XVIInit();
01508
01509 UI_InitStack("geoscape", "campaign_main", qtrue, qtrue);
01510
01511 if (load) {
01514 BS_InitMarket();
01515 return;
01516 }
01517
01518
01519 ccs.angles[YAW] = GLOBE_ROTATE;
01520
01521 ccs.date = campaign->date;
01522
01523 MAP_Init();
01524 PR_ProductionInit();
01525
01526
01527 while (ccs.date.sec > SECONDS_PER_DAY) {
01528 ccs.date.sec -= SECONDS_PER_DAY;
01529 ccs.date.day++;
01530 }
01531 CL_UpdateTime();
01532
01533
01534 ccs.center[0] = ccs.center[1] = 0.5;
01535 ccs.zoom = 1.0;
01536
01537 CL_UpdateCredits(campaign->credits);
01538
01539
01540 CL_ResetAlienInterest();
01541
01542
01543 Cvar_SetValue("mn_xvimap", ccs.XVIShowMap);
01544 CP_InitializeXVIOverlay(NULL);
01545
01546
01547 B_SelectBase(NULL);
01548
01549 Cmd_ExecuteString("addeventmail prolog");
01550
01551
01552 CP_InitializeSpawningDelay();
01553
01554
01555 if (!load)
01556 CL_ScriptSanityCheck();
01557 }
01558
01559 void CP_CampaignExit (void)
01560 {
01561 if (CP_IsRunning()) {
01562 cl_geoscape_overlay->integer = 0;
01563
01564 Com_DPrintf(DEBUG_CLIENT, "Remove game commands\n");
01565 CP_RemoveCampaignCommands();
01566 }
01567
01568 CP_ShutdownOverlay();
01569 }
01570
01576 campaign_t* CL_GetCampaign (const char* name)
01577 {
01578 campaign_t* campaign;
01579 int i;
01580
01581 for (i = 0, campaign = ccs.campaigns; i < ccs.numCampaigns; i++, campaign++)
01582 if (!strcmp(name, campaign->id))
01583 break;
01584
01585 if (i == ccs.numCampaigns) {
01586 Com_Printf("CL_GetCampaign: Campaign \"%s\" doesn't exist.\n", name);
01587 return NULL;
01588 }
01589 return campaign;
01590 }
01591
01597 void CL_ResetSinglePlayerData (void)
01598 {
01599 int i;
01600
01603 LIST_Delete(&ccs.missions);
01604 LIST_Delete(&ccs.alienBases);
01605 for (i = 0; i < ccs.numBases; i++) {
01606 base_t *base = B_GetBaseByIDX(i);
01607 aircraft_t *craft = NULL;
01608
01615 while ((craft = AIR_GetNextFromBase(base, craft))) {
01616 LIST_Delete(&craft->acTeam);
01617 }
01618 LIST_Delete(&base->aircraft);
01619 }
01620 for (i = 0; i < ccs.numAlienCategories; i++) {
01621 alienTeamCategory_t *alienCat = &ccs.alienCategories[i];
01622 LIST_Delete(&alienCat->equipment);
01623 }
01624 LIST_Delete(&ccs.storedUFOs);
01625 LIST_Delete(&ccs.cities);
01626 cp_messageStack = NULL;
01627
01628
01629 CL_FreeDynamicEventMail();
01630
01631 Mem_FreePool(cp_campaignPool);
01632
01633
01634
01635 RS_ResetTechs();
01636 E_ResetEmployees();
01637
01638 memset(&ccs, 0, sizeof(ccs));
01639
01640
01641 for (i = 0; i < csi.numTeamDefs; i++) {
01642 teamDef_t *td = &csi.teamDef[i];
01643 if (CHRSH_IsTeamDefAlien(td))
01644 ccs.alienTeams[ccs.numAliensTD++] = td;
01645 }
01646
01647 for (i = 0; i < cls.numMDs; i++) {
01648 mapDef_t *md = Com_GetMapDefByIDX(i);
01649 md->timesAlreadyUsed = 0;
01650 }
01651 }
01652
01653 #ifdef DEBUG
01654
01657 static void CL_DebugChangeCharacterStats_f (void)
01658 {
01659 int j;
01660 base_t *base = B_GetCurrentSelectedBase();
01661 employee_t *employee = NULL;
01662
01663 if (!base)
01664 return;
01665
01666 while ((employee = E_GetNext(EMPL_SOLDIER, employee))) {
01667 character_t *chr;
01668
01669 if (!E_IsInBase(employee, base))
01670 continue;
01671
01672 chr = &(employee->chr);
01673 assert(chr);
01674
01675 for (j = 0; j < KILLED_NUM_TYPES; j++)
01676 chr->score.kills[j]++;
01677 }
01678 if (base->aircraftCurrent)
01679 CL_UpdateCharacterStats(base, base->aircraftCurrent);
01680 }
01681
01682 #endif
01683
01692 void CP_GetRandomPosOnGeoscape (vec2_t pos, qboolean noWater)
01693 {
01694 do {
01695 pos[0] = (frand() - 0.5f) * 360.0f;
01696 pos[1] = asin((frand() - 0.5f) * 2.0f) * todeg;
01697 } while (noWater && MapIsWater(MAP_GetColor(pos, MAPTYPE_TERRAIN)));
01698
01699 Com_DPrintf(DEBUG_CLIENT, "CP_GetRandomPosOnGeoscape: Get random position on geoscape %.2f:%.2f\n", pos[0], pos[1]);
01700 }
01701
01716 qboolean CP_GetRandomPosOnGeoscapeWithParameters (vec2_t pos, const linkedList_t* terrainTypes, const linkedList_t* cultureTypes, const linkedList_t* populationTypes, const linkedList_t* nations)
01717 {
01718 float x, y;
01719 int num;
01720 int randomNum;
01721
01722
01723
01724
01725 const float numPoints = 360.0 / RASTER;
01726
01727 const float offsetX = frand() * RASTER;
01728 const float offsetY = -1.0 + frand() * 2.0 / numPoints;
01729 vec2_t posT;
01730 int hits = 0;
01731
01732
01733
01734
01735
01736 for (y = 0; y < numPoints; y++) {
01737 const float posY = asin(2.0 * y / numPoints + offsetY) * todeg;
01738 for (x = 0; x < numPoints; x++) {
01739 const float posX = x * RASTER - 180.0 + offsetX;
01740
01741 Vector2Set(posT, posX, posY);
01742
01743 if (MAP_PositionFitsTCPNTypes(posT, terrainTypes, cultureTypes, populationTypes, nations)) {
01744
01745
01747 hits++;
01748 }
01749 }
01750 }
01751
01752
01753 if (hits == 0)
01754 return qfalse;
01755
01756
01757
01758 randomNum = num = rand() % hits;
01759
01760
01761 for (y = 0; y < numPoints; y++) {
01762 const float posY = asin(2.0 * y / numPoints + offsetY) * todeg;
01763 for (x = 0; x < numPoints; x++) {
01764 const float posX = x * RASTER - 180.0 + offsetX;
01765
01766 Vector2Set(posT,posX,posY);
01767
01768 if (MAP_PositionFitsTCPNTypes(posT, terrainTypes, cultureTypes, populationTypes, nations)) {
01769 num--;
01770
01771 if (num < 1) {
01772 Vector2Set(pos, posX, posY);
01773 Com_DPrintf(DEBUG_CLIENT, "CP_GetRandomPosOnGeoscapeWithParameters: New random coords for a mission are %.0f:%.0f, chosen as #%i out of %i possible locations\n",
01774 pos[0], pos[1], randomNum, hits);
01775 return qtrue;
01776 }
01777 }
01778 }
01779 }
01780
01781 Com_DPrintf(DEBUG_CLIENT, "CP_GetRandomPosOnGeoscapeWithParameters: New random coordinates for a mission are %.0f:%.0f, chosen as #%i out of %i possible locations\n",
01782 pos[0], pos[1], num, hits);
01783
01785
01786 assert(pos[0] >= -180);
01787 assert(pos[0] <= 180);
01788 assert(pos[1] >= -90);
01789 assert(pos[1] <= 90);
01790
01791 return qtrue;
01792 }
01793
01794 int CP_GetSalaryAdministrative (void)
01795 {
01796 int i, costs = SALARY_ADMIN_INITIAL;
01797 for (i = 0; i < MAX_EMPL; i++)
01798 costs += ccs.numEmployees[i] * CP_GetSalaryAdminEmployee(i);
01799 return costs;
01800 }
01801
01802 int CP_GetSalaryBaseEmployee (employeeType_t type)
01803 {
01804 const salary_t *salary = &ccs.salaries[ccs.curCampaign->idx];
01805 return salary->base[type];
01806 }
01807
01808 int CP_GetSalaryAdminEmployee (employeeType_t type)
01809 {
01810 const salary_t *salary = &ccs.salaries[ccs.curCampaign->idx];
01811 return salary->admin[type];
01812 }
01813
01814 int CP_GetSalaryRankBonusEmployee (employeeType_t type)
01815 {
01816 const salary_t *salary = &ccs.salaries[ccs.curCampaign->idx];
01817 return salary->rankBonus[type];
01818 }
01819
01820 int CP_GetSalaryUpKeepBase (const base_t *base)
01821 {
01822 int cost = SALARY_BASE_UPKEEP;
01823 building_t *building = NULL;
01824 while ((building = B_GetNextBuilding(base, building))) {
01825 if (building->buildingStatus == B_STATUS_WORKING
01826 || building->buildingStatus == B_STATUS_CONSTRUCTION_FINISHED)
01827 cost += building->varCosts;
01828 }
01829 return cost;
01830 }
01831
01833 void CP_InitStartup (void)
01834 {
01835 cp_campaignPool = Mem_CreatePool("Client: Local (per game)");
01836
01837 SAV_Init();
01838
01839
01840 #ifdef DEBUG
01841 Cmd_AddCommand("debug_statsupdate", CL_DebugChangeCharacterStats_f, "Debug function to increase the kills and test the ranks");
01842 #endif
01843 Cmd_AddCommand("check_baseattacks", CP_CheckBaseAttacks_f, "Check if baseattack mission available and start it.");
01844
01845 cp_missiontest = Cvar_Get("cp_missiontest", "0", 0, "This will never stop the time on geoscape and print information about spawned missions");
01846
01847 CP_MissionsInit();
01848 MS_MessageInit();
01849
01850
01851 UP_InitStartup();
01852 B_InitStartup();
01853 INS_InitStartup();
01854 RS_InitStartup();
01855 E_InitStartup();
01856 HOS_InitStartup();
01857 AC_InitStartup();
01858 MAP_InitStartup();
01859 UFO_InitStartup();
01860 TR_InitStartup();
01861 AB_InitStartup();
01862 AIRFIGHT_InitStartup();
01863 NAT_InitStartup();
01864 STATS_InitStartup();
01865 }