/** * @file * @author Flugente (bears-pit.com) */ #include "Disease.h" #include "Overhead.h" #include "random.h" #include "Assignments.h" #include #include "soldier profile type.h" #include "Queen Command.h" #include "Quests.h" #include "finances.h" #include "Game Clock.h" #include "LaptopSave.h" #include "strategic.h" #include "DynamicDialogue.h" #include "Drugs And Alcohol.h" // for DoesMercHaveDisability( ... ) //GLOBALS DISEASE Disease[NUM_DISEASES]; extern SECTOR_EXT_DATA SectorExternalData[256][4]; // if disease in a sector is high enough, there is a chance that neighbouring sectors can be affected too #define DISEASE_STRATEGIC_ADJACENTINFECTION 0.3 #define MAPROWS (MAP_WORLD_X - 2) #define MAPCOLS (MAP_WORLD_Y - 2) // percentage of population that has at least some medical skill FLOAT GetCivPopulationDoctorRate( ) { return 0.1f; } // percentage of army population that has at least some medical skill FLOAT GetMilitaryPopulationDoctorRate( ) { return 0.34f; } // healing points of a population doctor FLOAT GetPopulationDoctorPoints( ) { return 20; } // if this fraction of a sector's population is infected, outbreak is declared regardless of infection severity // Otherwise infection would be untreatable at this point FLOAT GetSectorDiseaseOverFlowThreshold( ) { /* disease in a sector is automatically revealed if the mean severity breaches the outbreak threshold * however, we add a second condition: if a high enough percentage of the population is infected, disease is also revealed * the reason is that otherwise the AI might have no chance at all to combat an outbreak * It thus follows that * population * percentage of doctors * healing points per doctor == population * disease overflow threshold * infection gain per hour * which leads to * disease overflow threshold = percentage of doctors * healing points per doctor / infection gain per hour */ if ( Disease[0].sInfectionPtsGainPerHour > 0 ) return GetCivPopulationDoctorRate( ) * GetPopulationDoctorPoints( ) / (FLOAT)Disease[0].sInfectionPtsGainPerHour; return 0.3f; } void HandleDisease() { if ( !gGameExternalOptions.fDisease ) return; SOLDIERTYPE *pSoldier = NULL; UINT32 uiCnt = 0; // increase existing diseases for ( uiCnt = 0, pSoldier = MercPtrs[uiCnt]; uiCnt <= gTacticalStatus.Team[gbPlayerNum].bLastID; ++uiCnt, ++pSoldier ) { if ( pSoldier->bActive ) { for ( int i = 0; i < NUM_DISEASES; ++i ) { if ( pSoldier->sDiseasePoints[i] > 0 ) { INT32 pointgain = Disease[i].sInfectionPtsGainPerHour; // if the arm/leg is severely wounded, a splint increases the healing speed (assuming the gain is negative to begin with and doesn't reverse) if ( gGameExternalOptions.fDiseaseSevereLimitations && Disease[i].usDiseaseProperties & (DISEASE_PROPERTY_LIMITED_USE_ARMS| DISEASE_PROPERTY_LIMITED_USE_LEGS) && ( pSoldier->sDiseaseFlag[i] & (SOLDIERDISEASE_SPLINTAPPLIED_ARM| SOLDIERDISEASE_SPLINTAPPLIED_LEG) ) ) { pointgain *= 2; } // some diseases can reverse on certain states if ( pSoldier->sDiseaseFlag[i] & SOLDIERDISEASE_REVERSEAL ) pointgain *= -1; // add disease points - some diseases can reverse on certain states pSoldier->AddDiseasePoints( i, pointgain ); } } } } // chance for infection due to proximity to other mercs for ( uiCnt = 0, pSoldier = MercPtrs[uiCnt]; uiCnt <= gTacticalStatus.Team[gbPlayerNum].bLastID; ++uiCnt, ++pSoldier ) { if ( pSoldier->bActive && pSoldier->stats.bLife > 0 ) { // loop over all other soldiers and determine the chance that they will infect us SOLDIERTYPE *pTeamSoldier = NULL; UINT32 uiCnt2 = 0; // chance for infection due to proximity to other mercs for ( uiCnt2 = 0, pTeamSoldier = MercPtrs[uiCnt2]; uiCnt2 <= gTacticalStatus.Team[gbPlayerNum].bLastID; ++uiCnt2, ++pTeamSoldier ) { if ( pTeamSoldier->bActive && pTeamSoldier != pSoldier && pTeamSoldier->sSectorX == pSoldier->sSectorX && pTeamSoldier->sSectorY == pSoldier->sSectorY && pTeamSoldier->bSectorZ == pSoldier->bSectorZ && pTeamSoldier->stats.bLife > 0 ) { // infection chance gets modified depending on assignments FLOAT modifier = 1.0f; // if both are in the same squad or vehicle, chance is increased if (pTeamSoldier->bAssignment == pSoldier->bAssignment && (pSoldier->bAssignment < ON_DUTY || (pSoldier->bAssignment == VEHICLE && pSoldier->iVehicleId == pTeamSoldier->iVehicleId))) { modifier = 1.5; } // doctor-patient relations increase chance even more else if (IS_PATIENT(pTeamSoldier->bAssignment) && IS_PATIENT(pSoldier->bAssignment)) { modifier = 4; } // we might get a disease from this... HandlePossibleInfection( pSoldier, pTeamSoldier, INFECTION_TYPE_CONTACT_HUMAN, modifier ); } } // we can also be infected by the disease left from corpses if ( gGameExternalOptions.fDiseaseStrategic ) { SECTORINFO *pSectorInfo = &( SectorInfo[SECTOR( pSoldier->sSectorX, pSoldier->sSectorY )] ); if ( pSectorInfo && pSectorInfo->fDiseasePoints ) { HandlePossibleInfection( pSoldier, NULL, INFECTION_TYPE_CONTACT_CORPSE, ( pSectorInfo->fDiseasePoints / DISEASE_MAX_SECTOR ) ); } } // if we are infected, this might disgust other people, but only if it's visisble to them (we assume doctors dont mention this to others) if (gGameExternalOptions.fDynamicOpinions) { for (int i = 0; i < NUM_DISEASES; ++i) { if ((Disease[i].usDiseaseProperties & DISEASE_PROPERTY_DISGUSTING) && (pSoldier->sDiseaseFlag[i] & SOLDIERDISEASE_DIAGNOSED) && (pSoldier->sDiseaseFlag[i] & SOLDIERDISEASE_OUTBREAK)) { HandleDynamicOpinionChange(pSoldier, OPINIONEVENT_DISEASE_DISGUSTING, TRUE, TRUE); break; } } } } } // chance for infection due sector for ( uiCnt = 0, pSoldier = MercPtrs[uiCnt]; uiCnt <= gTacticalStatus.Team[gbPlayerNum].bLastID; ++uiCnt, ++pSoldier ) { if ( pSoldier->bActive ) { UINT8 ubSector = (UINT8)SECTOR( pSoldier->sSectorX, pSoldier->sSectorY ); UINT8 ubTraverseType = SectorInfo[ubSector].ubTraversability[THROUGH_STRATEGIC_MOVE]; switch ( ubTraverseType ) { case TROPICS_SAM_SITE: case TROPICS: case TROPICS_ROAD: { HandlePossibleInfection( pSoldier, NULL, INFECTION_TYPE_TROPICS ); } break; case SWAMP: case SWAMP_ROAD: { HandlePossibleInfection( pSoldier, NULL, INFECTION_TYPE_SWAMP ); } break; default: break; } } } } // chance gets modified by aModifier (contextual modifier) void HandlePossibleInfection( SOLDIERTYPE *pSoldier, SOLDIERTYPE* pOtherSoldier, UINT8 aInfectionType, FLOAT aModifier, BOOLEAN fStrategicOnly ) { if ( !gGameExternalOptions.fDisease ) return; // only for living mercs with a profile with a valid infection method if ( !pSoldier || pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE || pSoldier->ubProfile == NO_PROFILE || aInfectionType >= INFECTION_TYPE_MAX ) return; int max = fStrategicOnly ? 1 : NUM_DISEASES; for ( int i = 0; i < max; ++i ) { // chance of infection by insects FLOAT dChance = Disease[i].dInfectionChance[aInfectionType]; // alter chance by modifier and disease resistance dChance = dChance * aModifier * (100 - pSoldier->GetDiseaseResistance( )) / 100; if ( aInfectionType == INFECTION_TYPE_TROPICS || aInfectionType == INFECTION_TYPE_SWAMP ) { // if we are afraid of insects, we will be more careful around them - lower chance to be infected if ( DoesMercHaveDisability( pSoldier, FEAR_OF_INSECTS ) ) dChance *= 0.7f; } else if ( aInfectionType == INFECTION_TYPE_CONTACT_HUMAN ) { // if we check a specific soldier, he must have the disease himself if ( pOtherSoldier && pOtherSoldier->sDiseasePoints[i] <= 0 ) dChance = 0; // if we wear face or hand protection, lower chance of infection dChance *= (1.0f - pSoldier->GetDiseaseContactProtection( )); } else if ( aInfectionType == INFECTION_TYPE_CONTACT_CORPSE ) { // if we wear face or hand protection, lower chance of infection dChance *= (1.0f - pSoldier->GetDiseaseContactProtection( )); } // chances can be smaller than 1%, so we use a trick here by altering our 'chance function'. This allows to have much smaller chances, as for diseases, 1% can be way too high. if ( Random( 10000 ) < dChance * 100 ) { // infect us pSoldier->Infect( i ); } } } // return number of adjacent sector or -1 if there is none INT16 GetAdjacentSector( UINT8 sector, UINT8 spdir ) { switch ( spdir ) { case SP_UP: if ( SECTORY( sector ) > 0 ) return sector - MAPCOLS; break; case SP_LEFT: if ( SECTORX( sector ) > 0 ) return sector - 1; break; case SP_RIGHT: if ( SECTORX( sector ) < MAPCOLS ) return sector + 1; break; case SP_DOWN: if ( SECTORY( sector ) < MAPCOLS ) return sector + MAPCOLS; break; } return -1; } // get a sector population (not the tactical one - we use an xml estimation + troops present) UINT16 GetSectorPopulation( INT16 sX, INT16 sY, BOOLEAN fWithMilitary ) { UINT8 sector = SECTOR( sX, sY ); UINT16 population = SectorExternalData[sector][0].usCivilianPopulation; if ( fWithMilitary ) { // add number of enemies... population += NumNonPlayerTeamMembersInSector( sX, sY, ENEMY_TEAM ); } return population; } void HandleDiseaseDailyRefresh() { if ( gGameExternalOptions.fDisease && gGameExternalOptions.fDiseaseStrategic && gubFact[FACT_DISEASE_WHODATA_SUBSCRIBED] && LaptopSaveInfo.iCurrentBalance > gGameExternalOptions.sDiseaseWHOSubscriptionCost ) { gubFact[FACT_DISEASE_WHODATA_ACCESS] = TRUE; AddTransactionToPlayersBook( WHO_SUBSCRIPTION, 0, GetWorldTotalMin( ), (-1) * (INT32)gGameExternalOptions.sDiseaseWHOSubscriptionCost ); } else { gubFact[FACT_DISEASE_WHODATA_ACCESS] = FALSE; } } // heal sector population, return number of points used UINT32 HealSectorPopulation( INT16 sX, INT16 sY, UINT32 uHealpts ) { // how many points are needed to cure all disease? UINT8 sector = SECTOR( sX, sY ); SECTORINFO *pSectorInfo = &(SectorInfo[sector]); if ( !pSectorInfo ) return 0; UINT32 ptsused = uHealpts; if ( uHealpts >= pSectorInfo->fDiseasePoints ) { uHealpts -= pSectorInfo->fDiseasePoints; pSectorInfo->fDiseasePoints = 0; } else { pSectorInfo->fDiseasePoints -= uHealpts; uHealpts = 0; } // if there are no more infected, or infection is at 0, remove all traces of disease if ( pSectorInfo->fDiseasePoints <= 0 ) { pSectorInfo->usInfectionFlag &= ~(SECTORDISEASE_DIAGNOSED_PLAYER); } ptsused -= uHealpts; return ptsused; } // Flugente: get workforce effectiveness with regards to strategic disease FLOAT GetWorkforceEffectivenessWithDisease( INT8 bTownId, UINT8 usTeam ) { if ( !gGameExternalOptions.fDisease || !gGameExternalOptions.fDiseaseStrategic ) return 1.0f; FLOAT val = 0.0f; UINT16 population_healthy = 0; UINT16 population_total = 0; for ( UINT8 sX = 1; sX < MAP_WORLD_X - 1; ++sX ) { for ( UINT8 sY = 1; sY < MAP_WORLD_Y - 1; ++sY ) { UINT16 strategicsector = CALCULATE_STRATEGIC_INDEX( sX, sY ); if ( StrategicMap[strategicsector].bNameId == bTownId ) { // if we check for the player team, we look for sectors free of the enemy if ( OUR_TEAM == usTeam && StrategicMap[strategicsector].fEnemyControlled ) continue; else if ( ENEMY_TEAM == usTeam && !StrategicMap[strategicsector].fEnemyControlled ) continue; UINT8 sector = SECTOR( sX, sY ); SECTORINFO *pSector = &(SectorInfo[sector]); if ( !pSector ) continue; // at least 1 to be safe UINT16 population = max(1, GetSectorPopulation( sX, sY, FALSE ) ); population_total += population; // only civilian population is relevant here if ( population ) { // workforce effectivity is linearly dependent on disease population = max( 0, ( ( DISEASE_MAX_SECTOR - pSector->fDiseasePoints) / DISEASE_MAX_SECTOR ) * population ); } population_healthy += population; } } } val = (FLOAT)(population_healthy) / (FLOAT)(population_total); return val; }