cp_campaign.c

Go to the documentation of this file.
00001 
00006 /*
00007 Copyright (C) 2002-2010 UFO: Alien Invasion.
00008 
00009 This program is free software; you can redistribute it and/or
00010 modify it under the terms of the GNU General Public License
00011 as published by the Free Software Foundation; either version 2
00012 of the License, or (at your option) any later version.
00013 
00014 This program is distributed in the hope that it will be useful,
00015 but WITHOUT ANY WARRANTY; without even the implied warranty of
00016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00017 
00018 See the GNU General Public License for more details.
00019 
00020 You should have received a copy of the GNU General Public License
00021 along with this program; if not, write to the Free Software
00022 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
00023 
00024 */
00025 
00026 #include "../client.h" /* cls, cl */
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         /* a mission without UFO should not use a map with UFO */
00172         if (md->ufos)
00173             return qfalse;
00174     } else if (md->ufos) {
00175         /* A mission with UFO should use a map with UFO
00176          * first check that list is not empty */
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     /* Set maxHits and hits. */
00213     while (maxHits) {
00214         maxHits = 0;
00215         for (i = 0; i < cls.numMDs; i++) {
00216             mapDef_t *md;
00217 
00218             /* Check if mission fulfill conditions */
00219             if (!CP_MapIsSelectable(mission, i, pos))
00220                 continue;
00221 
00222             maxHits++;
00223             md = Com_GetMapDefByIDX(i);
00224             if (md->timesAlreadyUsed < minMissionAppearance) {
00225                 /* at least one fulfilling mission as been used less time than minMissionAppearance:
00226                  * restart the loop with this number of time.
00227                  * note: this implies that hits must be > 0 after the loop */
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             /* We scanned all maps in memory without finding a map used less than minMissionAppearance: exit while loop */
00237             break;
00238         }
00239     }
00240 
00241     if (!maxHits) {
00242         /* no map fulfill the conditions */
00243         if (mission->category == INTERESTCATEGORY_RESCUE) {
00244             /* default map for rescue mission is the rescue random map assembly */
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             /* default map for crashsite mission is the crashsite random map assembly */
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     /* If we reached this point, that means that at least 1 map fulfills the conditions of the mission
00271      * set number of mission to select randomly between 0 and hits - 1 */
00272     randomNum = rand() % hits;
00273 
00274     /* Select mission mission number 'randomnumber' that fulfills the conditions */
00275     for (i = 0; i < cls.numMDs; i++) {
00276         mapDef_t *md;
00277 
00278         /* Check if mission fulfill conditions */
00279         if (!CP_MapIsSelectable(mission, i, pos))
00280             continue;
00281 
00282         md = Com_GetMapDefByIDX(i);
00283         if (md->timesAlreadyUsed > minMissionAppearance)
00284             continue;
00285 
00286         /* There shouldn't be mission fulfilling conditions used less time than minMissionAppearance */
00287         assert(md->timesAlreadyUsed == minMissionAppearance);
00288 
00289         if (!randomNum)
00290             break;
00291         else
00292             randomNum--;
00293     }
00294 
00295     /* A mission must have been selected */
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     /* fraction of nation that can be below min happiness before the game is lost */
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             /* check for nation happiness */
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                 /* lost the game */
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 /* Initial fraction of the population in the country where a mission has been lost / won */
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         /* Calculate how well the mission went. */
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     /* Book-keeping. */
00420     won ? ccs.campaignStats.missionsWon++ : ccs.campaignStats.missionsLost++;
00421 
00422     /* Calculate the actual happiness delta. The bigger the mission, the more potential influence. */
00423     deltaHappiness = 0.004 * civilianSum + 0.004 * alienSum;
00424 
00425     /* There is a maximum base happiness delta. */
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         /* update happiness. */
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         /* the mission might be removed inside this loop, so we have to ensure
00457          * that we have the correct next pointer */
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     /* Get the year */
00500     *year = date->day / DAYS_PER_YEAR;
00501 
00502     /* Get the days in the year. */
00503     d = date->day % DAYS_PER_YEAR;
00504 
00505     /* Subtract days until no full month is left. */
00506     for (i = 0; i < MONTHS_PER_YEAR; i++) {
00507         if (d < monthLength[i])
00508             break;
00509         d -= monthLength[i];
00510     }
00511 
00512     /* Prepare return values. */
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     /* Add days of years */
00532     day = DAYS_PER_YEAR * years;
00533 
00534     /* Add days until no full month is left. */
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     /* Add seconds of the hours */
00555     sec = SECONDS_PER_HOUR * hours;
00556 
00557     /* Add seconds of the minutes. */
00558     sec += 60 * minutes;
00559 
00560     /* Add the rest of the seconds */
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     /* Update alien interest for bases */
00596     UFO_UpdateAlienInterestForAllBasesAndInstallations();
00597 
00598     /* Update how phalanx troop know alien bases */
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     /* advance time */
00633     ccs.timer += cls.frametime * ccs.gameTimeScale;
00634 
00635     if (ccs.timer >= 1.0) {
00636         /* calculate new date */
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         /* Execute every actions that needs to be independent of time speed : every DETECTION_INTERVAL
00649          *  - Run UFOs and craft at least every DETECTION_INTERVAL. If detection occurred, break.
00650          *  - Check if any new mission is detected
00651          *  - Update stealth value of phalanx bases and installations ; alien bases */
00652         for (i = 0; i < checks; i++) {
00653             ccs.date.sec += dt;
00654             ccs.timer -= dt;
00655             CL_CampaignFunctionPeriodicCall(dt, qfalse);
00656 
00657             /* if something stopped time, we must stop here the loop */
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         /* compute minutely events  */
00671         /* (this may run multiple times if the time stepping is > 1 minute at a time) */
00672         while (currentmin < (int)floor(ccs.date.sec / SECONDS_PER_MINUTE)) {
00673             currentmin++;
00674             PR_ProductionRun();
00675         }
00676 
00677         /* compute hourly events  */
00678         /* (this may run multiple times if the time stepping is > 1 hour at a time) */
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         /* daily events */
00690         while (ccs.date.sec > SECONDS_PER_DAY) {
00691             ccs.date.sec -= SECONDS_PER_DAY;
00692             ccs.date.day++;
00693             /* every day */
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             /* should be executed after all daily event that could
00705              * change XVI overlay */
00706             CP_UpdateNationXVIInfection();
00707         }
00708 
00709         /* check for campaign events
00710          * aircraft and UFO already moved during radar detection (see above),
00711          * just make them move the missing part -- if any */
00712         CL_CampaignFunctionPeriodicCall(dt, qtrue);
00713 
00714         UP_GetUnreadMails();
00715         CP_CheckMissionEnd();
00716         CP_CheckLostCondition();
00717         /* Check if there is a base attack mission */
00718         Cmd_ExecuteString("check_baseattacks");
00719         BDEF_AutoSelectTarget();
00720 
00721         /* set time cvars */
00722         CL_DateConvertLong(&ccs.date, &date);
00723         /* every first day of a month */
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     /* credits */
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     /* init the map images and reset the map actions */
00808     MAP_Init();
00809 
00810     /* read credits */
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     /* read other campaign data */
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     /* read map view */
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     /* restore the overlay.
00832     * do not use Cvar_SetValue, because this function check if value->string are equal to skip calculation
00833     * and we never set r_geoscape_overlay->string in game: cl_geoscape_overlay won't be updated if the loaded
00834     * value is 0 (and that's a problem if you're loading a game when cl_geoscape_overlay is set to another value */
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     /* Map and user interface */
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     /* Before we start, we should clear the missionResults array. */
00938     memset(&ccs.missionResults, 0, sizeof(ccs.missionResults));
00939 
00940     /* Various sanity checks. */
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     /* if we retry a mission we have to drop from the current game before */
00951     SV_Shutdown("Server quit.", qfalse);
00952     CL_Disconnect();
00953 
00954     CP_CreateBattleParameters(mis, &ccs.battleParameters);
00955     CP_SetMissionVars(mis);
00956     /* Set the states of mission Cvars to proper values. */
00957     Cvar_SetValue("mission_uforecovered", 0);
00958     Cvar_SetValue("mn_autogo", 0);
00959 
00960     /* manage inventory */
00961     ccs.eMission = base->storage; /* copied, including arrays inside! */
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             /* very hard to win this */
00987             winProbability = exp((0.5 - .15 * ccs.curCampaign->difficulty) * AIR_GetTeamSize(aircraft) - ccs.battleParameters.aliens);
00988             break;
00989         case INTERESTCATEGORY_XVI:
00990             /* not that hard to win this, they want to spread xvi - no real terror mission */
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         /* a base defence mission can only be won if there are soldiers that
01012          * defend the attacked base */
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                 /* don't use an employee that is currently being transfered */
01019                 if (!E_IsAwayFromBase(employee)) {
01020                     const character_t *chr = &employee->chr;
01021                     const chrScoreGlobal_t *score = &chr->score;
01022                     /* if the soldier was ever on a mission */
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             /* now handle the ugvs */
01034             listPos = ugvs;
01035             while (listPos) {
01036                 const employee_t *employee = (employee_t *)listPos->data;
01037                 /* don't use an employee that is currently being transfered */
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             /* No soldier to defend the base */
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     /* maybe ccs.interceptAircraft is changed in some functions we call here
01113      * so store a local pointer to guarantee that we access the right aircraft
01114      * note that ccs.interceptAircraft is a fake aircraft for base attack missions */
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             /* ensure, that the automatic button is no longer visible */
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     /* update nation opinions */
01149     CL_HandleNationData(won, mission);
01150 
01151     CP_CheckLostCondition();
01152 
01153     CL_AutoMissionAlienCollect(aircraft);
01154 
01155     /* onwin and onlose triggers */
01156     CP_ExecuteMissionTrigger(mission, won);
01157 
01158     /* if a UFO has been recovered, send it to a base */
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     /* init result text */
01196     UI_RegisterText(TEXT_STANDARD, resultText);
01197 
01198     /* needs to be cleared and then append to it */
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     /* team stats */
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     /* mind is not yet enough */
01230     if (chr->score.skills[ABILITY_MIND] < rank->mind)
01231         return qfalse;
01232 
01233     /* not enough killed enemies yet */
01234     if (chr->score.kills[KILLED_ENEMIES] < rank->killedEnemies)
01235         return qfalse;
01236 
01237     /* too many civilians and team kills */
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     /* only soldiers have stats and ranks, ugvs not */
01260     while ((employee = E_GetNextFromBase(EMPL_SOLDIER, employee, aircraft->homebase))) {
01261         if (AIR_IsEmployeeInAircraft(employee, aircraft)) {
01262             character_t *chr = &employee->chr;
01263 
01264             /* Remember the number of assigned mission for this character. */
01265             chr->score.assignedMissions++;
01266 
01271             /* Check if the soldier meets the requirements for a higher rank
01272              * and do a promotion. */
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         /* Create a scientist */
01373         E_CreateEmployee(EMPL_SCIENTIST, nation, NULL);
01374 
01375     for (j = 0; j < 5; j++)
01376         /* Create a pilot. */
01377         E_CreateEmployee(EMPL_PILOT, nation, NULL);
01378 
01379     for (j = 0; j < 5; j++)
01380         /* Create a soldier. */
01381         E_CreateEmployee(EMPL_SOLDIER, nation, NULL);
01382 
01383     for (j = 0; j < 5; j++)
01384         /* Create a worker. */
01385         E_CreateEmployee(EMPL_WORKER, nation, NULL);
01386 }
01387 #endif
01388 
01389 /* ===================================================================== */
01390 
01391 /* these commands are only available in singleplayer */
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     /* Init popup and map/geoscape */
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     /* initialise view angle for 3D geoscape so that europe is seen */
01519     ccs.angles[YAW] = GLOBE_ROTATE;
01520     /* initialise date */
01521     ccs.date = campaign->date;
01522 
01523     MAP_Init();
01524     PR_ProductionInit();
01525 
01526     /* get day */
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     /* set map view */
01534     ccs.center[0] = ccs.center[1] = 0.5;
01535     ccs.zoom = 1.0;
01536 
01537     CL_UpdateCredits(campaign->credits);
01538 
01539     /* Initialize alien interest */
01540     CL_ResetAlienInterest();
01541 
01542     /* Initialize XVI overlay */
01543     Cvar_SetValue("mn_xvimap", ccs.XVIShowMap);
01544     CP_InitializeXVIOverlay(NULL);
01545 
01546     /* create a base as first step */
01547     B_SelectBase(NULL);
01548 
01549     Cmd_ExecuteString("addeventmail prolog");
01550 
01551     /* Spawn first missions of the game */
01552     CP_InitializeSpawningDelay();
01553 
01554     /* now check the parsed values for errors that are not caught at parsing stage */
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         /* singleplayer commands are no longer available */
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     /* cleanup dynamic mails */
01629     CL_FreeDynamicEventMail();
01630 
01631     Mem_FreePool(cp_campaignPool);
01632 
01633     /* called to flood the hash list - because the parse tech function
01634      * was maybe already called */
01635     RS_ResetTechs();
01636     E_ResetEmployees();
01637 
01638     memset(&ccs, 0, sizeof(ccs));
01639 
01640     /* Collect and count Alien team definitions. */
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     /* Clear mapDef usage staistics */
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 /* DEBUG */
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     /* RASTER might reduce amount of tested locations to get a better performance */
01723     /* Number of points in latitude and longitude that will be tested. Therefore, the total number of position tried
01724      * will be numPoints * numPoints */
01725     const float numPoints = 360.0 / RASTER;
01726     /* RASTER is minimizing the amount of locations, so an offset is introduced to enable access to all locations, depending on a random factor */
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     /* check all locations for suitability in 2 iterations */
01733     /* prepare 1st iteration */
01734 
01735     /* ITERATION 1 */
01736     for (y = 0; y < numPoints; y++) {
01737         const float posY = asin(2.0 * y / numPoints + offsetY) * todeg; /* Use non-uniform distribution otherwise we favour the poles */
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                 /* the location given in pos belongs to the terrain, culture, population types and nations
01745                  * that are acceptable, so count it */
01747                 hits++;
01748             }
01749         }
01750     }
01751 
01752     /* if there have been no hits, the function failed to find a position */
01753     if (hits == 0)
01754         return qfalse;
01755 
01756     /* the 2nd iteration goes through the locations again, but does so only until a random point */
01757     /* prepare 2nd iteration */
01758     randomNum = num = rand() % hits;
01759 
01760     /* ITERATION 2 */
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     /* Make sure that position is within bounds */
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;  /* base cost */
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     /* commands */
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     /* init UFOpaedia, basemanagement and other subsystems */
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 }

Generated by  doxygen 1.6.2