diff --git a/GameSettings.cpp b/GameSettings.cpp index f8eeaa99e..f91488c19 100644 --- a/GameSettings.cpp +++ b/GameSettings.cpp @@ -2577,6 +2577,10 @@ void LoadSkillTraitsExternalSettings() // Flugente: as the 'redisguise after x turns' part is missing the x turns part, this is now permanently set to TRUE until that changes gSkillTraitValues.fCOStripIfUncovered = TRUE;// iniReader.ReadBoolean( "Covert Ops", "COVERT_STRIPIFUNCOVERED", FALSE ); + + gSkillTraitValues.fCOTurncoats = iniReader.ReadBoolean( "Covert Ops", "COVERT_TURNCOATS", TRUE ); + gSkillTraitValues.fCOTurncoats_SectorActivationRequiresRadioOperator = iniReader.ReadBoolean( "Covert Ops", "COVERT_TURNCOATS_SECTOR_ACTIVATION_REQUIRES_RADIOOPERATOR", FALSE ); + gSkillTraitValues.sCOTurncoats_PlayerConvinctionBonus = iniReader.ReadInteger( "Covert Ops", "COVERT_TURNCOATS_PLAYER_CONVINCTION_BONUS", 0, -100, 100 ); // Flugente: RADIO OPERATOR gSkillTraitValues.fROAllowArtillery = iniReader.ReadBoolean("Radio Operator","RADIO_OPERATOR_ARTILLERY", TRUE); diff --git a/GameSettings.h b/GameSettings.h index bbbae45ab..0a6ca1fe7 100644 --- a/GameSettings.h +++ b/GameSettings.h @@ -1927,6 +1927,9 @@ typedef struct UINT16 usCOEliteUncoverRadius; BOOLEAN fCODetectIfBleeding; // will we be discovered if we are bleeding? BOOLEAN fCOStripIfUncovered; + BOOLEAN fCOTurncoats; // can we create enemy turncoats that can be ordered to betray their comrades? + BOOLEAN fCOTurncoats_SectorActivationRequiresRadioOperator; // activating turncoats sector-wide requires a radio operator + INT32 sCOTurncoats_PlayerConvinctionBonus; // RADIO OPERATOR BOOLEAN fROAllowArtillery; // artillery can be forbidden for all teams with this setting diff --git a/GameVersion.cpp b/GameVersion.cpp index 22b3f7bd6..8076c2237 100644 --- a/GameVersion.cpp +++ b/GameVersion.cpp @@ -55,8 +55,8 @@ #endif -CHAR8 czVersionNumber[16] = { "Build 20.05.17" }; //YY.MM.DD +CHAR8 czVersionNumber[16] = { "Build 20.05.30" }; //YY.MM.DD CHAR16 zTrackingNumber[16] = { L"Z" }; -CHAR16 zRevisionNumber[16] = { L"Revision 8804" }; +CHAR16 zRevisionNumber[16] = { L"Revision 8811" }; // SAVE_GAME_VERSION is defined in header, change it there diff --git a/Laptop/MilitiaWebsite.cpp b/Laptop/MilitiaWebsite.cpp index ad68a5177..662882b7d 100644 --- a/Laptop/MilitiaWebsite.cpp +++ b/Laptop/MilitiaWebsite.cpp @@ -516,11 +516,13 @@ STR16 OperationText( UINT32 aNum ) STR16 operationame = GetIncidentName( battlereport.id ); - if ( battlereport.flagmask & MILITIA_BATTLEREPORT_FLAG_RECRUITED ) + if ( battlereport.flagmask & (MILITIA_BATTLEREPORT_FLAG_RECRUITED| MILITIA_BATTLEREPORT_FLAG_RECRUITED_TURNCOAT) ) { STR16 originsectorstr = militia.GetOriginSector( ); - if ( militia.origin == MO_PMC ) + if ( battlereport.flagmask & MILITIA_BATTLEREPORT_FLAG_RECRUITED_TURNCOAT ) + swprintf( gMilitiaOperationText, szIndividualMilitiaBattleReportText[11], day, hours, minutes, originsectorstr ); + else if ( militia.origin == MO_PMC ) swprintf( gMilitiaOperationText, szIndividualMilitiaBattleReportText[8], day, hours, minutes, originsectorstr ); else if ( militia.origin == MO_DEFECTOR ) swprintf( gMilitiaOperationText, szIndividualMilitiaBattleReportText[9], day, hours, minutes, originsectorstr ); diff --git a/Laptop/personnel.cpp b/Laptop/personnel.cpp index 4e09db10f..a08270f40 100644 --- a/Laptop/personnel.cpp +++ b/Laptop/personnel.cpp @@ -8223,6 +8223,13 @@ void AssignPersonnelSkillTraitHelpText( UINT8 ubTraitNumber, BOOLEAN fExpertLeve INT16 apreduction = ( gSkillTraitValues.sCODisguiseAPReduction * (fExpertLevel ? 2 : 1)); swprintf( atStr, gzIMPMajorTraitsHelpTextsCovertOps[6], apreduction, sSpecialCharacters[0]); wcscat( apStr, atStr ); + + if ( gSkillTraitValues.fCOTurncoats ) + { + swprintf( atStr, gzIMPMajorTraitsHelpTextsCovertOps[7] ); + wcscat( apStr, atStr ); + } + break; } diff --git a/Strategic/Assignments.cpp b/Strategic/Assignments.cpp index e7fc23d65..3acc675b4 100644 --- a/Strategic/Assignments.cpp +++ b/Strategic/Assignments.cpp @@ -6671,13 +6671,13 @@ void HandleSpyAssignments() if ( Chance( uncoverrisk ) ) { // if we are already in hiding, we will be uncovered - if ( pSoldier->usSkillCooldown[SOLDIER_COOLDOWN_INTEL_PENALTY] ) + if ( pSoldier->usSkillCooldown[SOLDIER_COOLDOWN_INTEL_PENALTY] > 10 ) vector_uncoveredmercs.push_back( uiCnt ); // we get a penalty, and a chance to be uncovered - pSoldier->usSkillCooldown[SOLDIER_COOLDOWN_INTEL_PENALTY] += 2 + Random( 3 ); + pSoldier->usSkillCooldown[SOLDIER_COOLDOWN_INTEL_PENALTY] += 20 + Random( 30 ); - ScreenMsg( FONT_MCOLOR_RED, MSG_INTERFACE, szIntelText[1], pSoldier->GetName(), (pSoldier->usSkillCooldown[SOLDIER_COOLDOWN_INTEL_PENALTY] - 1) ); + ScreenMsg( FONT_MCOLOR_RED, MSG_INTERFACE, szIntelText[1], pSoldier->GetName(), (pSoldier->usSkillCooldown[SOLDIER_COOLDOWN_INTEL_PENALTY] - 10) / 10 ); continue; } @@ -6688,8 +6688,10 @@ void HandleSpyAssignments() } // penalty runs out for every soldier, regardless of whether they are on an intel assignment - if ( pSoldier->usSkillCooldown[SOLDIER_COOLDOWN_INTEL_PENALTY] ) - pSoldier->usSkillCooldown[SOLDIER_COOLDOWN_INTEL_PENALTY]--; + if ( pSoldier->usSkillCooldown[SOLDIER_COOLDOWN_INTEL_PENALTY] > 10) + pSoldier->usSkillCooldown[SOLDIER_COOLDOWN_INTEL_PENALTY] -= 10; + else + pSoldier->usSkillCooldown[SOLDIER_COOLDOWN_INTEL_PENALTY] = 0; } } diff --git a/Strategic/Campaign Types.h b/Strategic/Campaign Types.h index c12d5042c..84ecc0952 100644 --- a/Strategic/Campaign Types.h +++ b/Strategic/Campaign Types.h @@ -570,7 +570,13 @@ typedef struct SECTORINFO // Flugente: burial assignment FLOAT dBurial_UnappliedProgress; // progress done via assignment work. This way no work gets lost - INT8 bPadding[ 12 ]; + // Flugente: number of turncoats + UINT8 ubNumAdmins_Turncoat; + UINT8 ubNumTroops_Turncoat; + UINT8 ubNumElites_Turncoat; + UINT8 bPadding_1; + + INT8 bPadding[ 8 ]; }SECTORINFO; diff --git a/Strategic/Map Screen Interface Map.cpp b/Strategic/Map Screen Interface Map.cpp index 74aab2314..952bc773b 100644 --- a/Strategic/Map Screen Interface Map.cpp +++ b/Strategic/Map Screen Interface Map.cpp @@ -6636,6 +6636,15 @@ UINT32 WhatPlayerKnowsAboutEnemiesInSector( INT16 sSectorX, INT16 sSectorY ) fCount = TRUE; } } + + // Flugente: turncoats allow vision as well + if ( gSkillTraitValues.fCOTurncoats + && TurncoatsInSector( sSectorX, sSectorY ) ) + { + fDetection = TRUE; + fCount = TRUE; + fDirection = TRUE; + } // Flugente: we can buy info on enemy groups with intel GetInfoFromGroupsInSector( sSectorX, sSectorY, ENEMY_TEAM, fDetection, fCount, fDirection ); @@ -8466,6 +8475,27 @@ void DetermineMapIntelData( INT32 asSectorZ ) } } } + + // Flugente: turncoats + if ( gSkillTraitValues.fCOTurncoats ) + { + for ( UINT16 sector = 0; sector < 256; ++sector ) + { + CorrectTurncoatCount( SECTORX( sector ), SECTORY( sector ) ); + + UINT16 numturncoats = NumTurncoatsOfClassInSector( SECTORX( sector ), SECTORY( sector ), SOLDIER_CLASS_ADMINISTRATOR ) + + NumTurncoatsOfClassInSector( SECTORX( sector ), SECTORY( sector ), SOLDIER_CLASS_ARMY ) + + NumTurncoatsOfClassInSector( SECTORX( sector ), SECTORY( sector ), SOLDIER_CLASS_ELITE ); + + if ( numturncoats ) + { + CHAR16 str[128]; + swprintf( str, szTurncoatText[9], numturncoats ); + + AddIntelAndQuestMapDataForSector( SECTORX( sector ), SECTORY( sector ), -1, 23, str, L"" ); + } + } + } } // add lua data diff --git a/Strategic/MilitiaIndividual.cpp b/Strategic/MilitiaIndividual.cpp index d23fb387e..7552ebf72 100644 --- a/Strategic/MilitiaIndividual.cpp +++ b/Strategic/MilitiaIndividual.cpp @@ -580,6 +580,103 @@ UINT32 CreateNewIndividualMilitia( UINT8 aMilitiaRank, UINT8 aOrigin, UINT8 aSec return newmilitia.id; } +UINT32 CreateNewIndividualMilitiaFromSoldier( SOLDIERTYPE* pSoldier, UINT8 aOrigin ) +{ + if ( !gGameExternalOptions.fIndividualMilitia || !pSoldier ) + return 0; + + MILITIA newmilitia; + + newmilitia.id = GetFreeIndividualMilitiaId(); + + switch ( pSoldier->ubSoldierClass ) + { + case SOLDIER_CLASS_ADMINISTRATOR: + case SOLDIER_CLASS_GREEN_MILITIA: + case SOLDIER_CLASS_BANDIT: + newmilitia.militiarank = GREEN_MILITIA; + break; + + case SOLDIER_CLASS_ELITE: + case SOLDIER_CLASS_ELITE_MILITIA: + newmilitia.militiarank = ELITE_MILITIA; + break; + + case SOLDIER_CLASS_ARMY: + case SOLDIER_CLASS_REG_MILITIA: + newmilitia.militiarank = REGULAR_MILITIA; + break; + + default: + return 0; + break; + } + + // according to origin, we choose bodytype, skin, hair, fore- and surname + newmilitia.origin = aOrigin; + + newmilitia.originsector = SECTOR( pSoldier->sSectorX, pSoldier->sSectorY ); + newmilitia.sector = newmilitia.originsector; + + newmilitia.bodytype = pSoldier->ubBodyType; + + if ( COMPARE_PALETTEREP_ID( pSoldier->SkinPal, "PINKSKIN" ) ) newmilitia.skin = PINKSKIN; + else if ( COMPARE_PALETTEREP_ID( pSoldier->SkinPal, "TANSKIN" ) ) newmilitia.skin = TANSKIN; + else if ( COMPARE_PALETTEREP_ID( pSoldier->SkinPal, "DARKSKIN" ) ) newmilitia.skin = DARKSKIN; + else if ( COMPARE_PALETTEREP_ID( pSoldier->SkinPal, "BLACKSKIN" ) ) newmilitia.skin = BLACKSKIN; + + if ( COMPARE_PALETTEREP_ID( pSoldier->HeadPal, "BROWNHEAD" ) ) newmilitia.hair = BROWNHEAD; + if ( COMPARE_PALETTEREP_ID( pSoldier->HeadPal, "BLACKHEAD" ) ) newmilitia.hair = BLACKHEAD; + if ( COMPARE_PALETTEREP_ID( pSoldier->HeadPal, "WHITEHEAD" ) ) newmilitia.hair = WHITEHEAD; + if ( COMPARE_PALETTEREP_ID( pSoldier->HeadPal, "BLONDHEAD" ) ) newmilitia.hair = BLONDEHEAD; + if ( COMPARE_PALETTEREP_ID( pSoldier->HeadPal, "REDHEAD" ) ) newmilitia.hair = REDHEAD; + + // age is random + newmilitia.age = 16 + Random( 2 ) * ( 1 + Random( 8 ) ) + + Random( 2 ) * ( 1 + Random( 8 ) ) + + Random( 2 ) * ( 1 + Random( 8 ) ) + + Random( 2 ) * ( 1 + Random( 8 ) ) + + Random( 2 ) * ( 1 + Random( 8 ) ) + + Random( 2 ) * ( 1 + Random( 8 ) ); + + if ( newmilitia.hair == WHITEHEAD ) + newmilitia.age = max( newmilitia.age, 38 + Random( 2 ) * ( 1 + Random( 8 ) ) ); + + // we have to choose a new name, as we cannot take the soldiers name, as those are set in separate arrays + if ( newmilitia.bodytype == REGFEMALE ) + { + newmilitia.forename = Random( gMilitiaOriginData[newmilitia.origin].szFemale_Forename.size() ); + newmilitia.surname = Random( gMilitiaOriginData[newmilitia.origin].szFemale_Surname.size() ); + } + else + { + newmilitia.forename = Random( gMilitiaOriginData[newmilitia.origin].szMale_Forename.size() ); + newmilitia.surname = Random( gMilitiaOriginData[newmilitia.origin].szMale_Surname.size() ); + } + + newmilitia.flagmask = 0; + + newmilitia.healthratio = 100.0f * pSoldier->stats.bLife / pSoldier->stats.bLifeMax; + + newmilitia.kills = 0; + newmilitia.assists = 0; + newmilitia.promotionpoints = 0.0f; + + // promotion points are based on kills and assists. For proper accounting, award points for current rank + newmilitia.AddKills( 0, 0 ); + + // make up some history + MILITIA_BATTLEREPORT report; + report.flagmask |= MILITIA_BATTLEREPORT_FLAG_RECRUITED_TURNCOAT; + report.id = GetWorldTotalMin(); + + newmilitia.history.push_back( report ); + + gIndividualMilitiaVector.push_back( newmilitia ); + + return newmilitia.id; +} + // search for a individual militia that is alive and not currently in use in this sector, and return its id // if none is found, create new and return that one UINT32 GetIdOfUnusedIndividualMilitia( UINT8 aSoldierClass, UINT8 aSector ) diff --git a/Strategic/MilitiaIndividual.h b/Strategic/MilitiaIndividual.h index dda8f8fcd..e03e7a953 100644 --- a/Strategic/MilitiaIndividual.h +++ b/Strategic/MilitiaIndividual.h @@ -38,6 +38,8 @@ NUM_TOTAL_STATS #define MILITIA_BATTLEREPORT_FLAG_KILLEDENEMY 0x00000040 // killed an enemy #define MILITIA_BATTLEREPORT_FLAG_FIRED 0x00000080 // militia was fired +#define MILITIA_BATTLEREPORT_FLAG_RECRUITED_TURNCOAT 0x00000100 // defected to our side + struct MILITIA_BATTLEREPORT { MILITIA_BATTLEREPORT( ) : id( 0 ), flagmask( 0 ) {} @@ -166,6 +168,8 @@ UINT32 CreateRandomIndividualMilitia( UINT8 aMilitiaRank, UINT8 aOrigin, UINT8 a UINT32 CreateNewIndividualMilitia( UINT8 aMilitiaRank, UINT8 aOrigin, UINT8 aSector ); +UINT32 CreateNewIndividualMilitiaFromSoldier( SOLDIERTYPE* pSoldier, UINT8 aOrigin ); + UINT32 GetIdOfUnusedIndividualMilitia( UINT8 aSoldierClass, UINT8 aSector ); BOOLEAN GetIdOfIndividualMilitiaWithClassSector( UINT8 aSoldierClass, UINT8 aSector, UINT32& arId ); diff --git a/Strategic/Queen Command.cpp b/Strategic/Queen Command.cpp index 263142032..ccd6cb46e 100644 --- a/Strategic/Queen Command.cpp +++ b/Strategic/Queen Command.cpp @@ -3039,3 +3039,408 @@ void HandleBloodCatDeaths( SECTORINFO *pSector ) } } #endif + + +UINT16 NumTurncoatsOfClassInSector( INT16 sSectorX, INT16 sSectorY, UINT8 aSoldierClass ) +{ + // HEADROCK: This is a TEMPORARY fix to avoid the assertion error. Not sure this is the best solution, + // probably isn't. But I need this bit to work. + if ( sSectorX < MINIMUM_VALID_X_COORDINATE || + sSectorX > MAXIMUM_VALID_X_COORDINATE || + sSectorY < MINIMUM_VALID_Y_COORDINATE || + sSectorY > MAXIMUM_VALID_Y_COORDINATE ) + { + return ( 0 ); + } + + AssertGE( sSectorX, MINIMUM_VALID_X_COORDINATE ); + AssertLE( sSectorX, MAXIMUM_VALID_X_COORDINATE ); + AssertGE( sSectorY, MINIMUM_VALID_Y_COORDINATE ); + AssertLE( sSectorY, MAXIMUM_VALID_Y_COORDINATE ); + + SECTORINFO* pSector = &SectorInfo[SECTOR( sSectorX, sSectorY )]; + + UINT16 num = 0; + + switch ( aSoldierClass ) + { + case SOLDIER_CLASS_ADMINISTRATOR: num += pSector->ubNumAdmins_Turncoat; break; + case SOLDIER_CLASS_ARMY: num += pSector->ubNumTroops_Turncoat; break; + case SOLDIER_CLASS_ELITE: num += pSector->ubNumElites_Turncoat; break; + default: break; + } + + GROUP* pGroup = gpGroupList; + while ( pGroup ) + { + if ( pGroup->usGroupTeam == ENEMY_TEAM && !pGroup->fVehicle && pGroup->ubSectorX == sSectorX && pGroup->ubSectorY == sSectorY ) + { + switch ( aSoldierClass ) + { + case SOLDIER_CLASS_ADMINISTRATOR: num += pGroup->pEnemyGroup->ubNumAdmins_Turncoat; break; + case SOLDIER_CLASS_ARMY: num += pGroup->pEnemyGroup->ubNumTroops_Turncoat; break; + case SOLDIER_CLASS_ELITE: num += pGroup->pEnemyGroup->ubNumElites_Turncoat; break; + default: break; + } + } + pGroup = pGroup->next; + } + + return num; +} + +BOOLEAN TurncoatsInSector( INT16 sSectorX, INT16 sSectorY ) +{ + // HEADROCK: This is a TEMPORARY fix to avoid the assertion error. Not sure this is the best solution, + // probably isn't. But I need this bit to work. + if ( sSectorX < MINIMUM_VALID_X_COORDINATE || + sSectorX > MAXIMUM_VALID_X_COORDINATE || + sSectorY < MINIMUM_VALID_Y_COORDINATE || + sSectorY > MAXIMUM_VALID_Y_COORDINATE ) + { + return ( 0 ); + } + + AssertGE( sSectorX, MINIMUM_VALID_X_COORDINATE ); + AssertLE( sSectorX, MAXIMUM_VALID_X_COORDINATE ); + AssertGE( sSectorY, MINIMUM_VALID_Y_COORDINATE ); + AssertLE( sSectorY, MAXIMUM_VALID_Y_COORDINATE ); + + SECTORINFO* pSector = &SectorInfo[SECTOR( sSectorX, sSectorY )]; + + if ( pSector->ubNumAdmins_Turncoat + || pSector->ubNumTroops_Turncoat + || pSector->ubNumElites_Turncoat ) + return TRUE; + + GROUP* pGroup = gpGroupList; + while ( pGroup ) + { + if ( pGroup->usGroupTeam == ENEMY_TEAM && !pGroup->fVehicle && pGroup->ubSectorX == sSectorX && pGroup->ubSectorY == sSectorY ) + { + if ( pGroup->pEnemyGroup->ubNumAdmins_Turncoat + || pGroup->pEnemyGroup->ubNumTroops_Turncoat + || pGroup->pEnemyGroup->ubNumElites_Turncoat ) + return TRUE; + } + pGroup = pGroup->next; + } + + return FALSE; +} + +bool RemoveOneTurncoat( INT16 sSectorX, INT16 sSectorY, UINT8 aSoldierClass ) +{ + // HEADROCK: This is a TEMPORARY fix to avoid the assertion error. Not sure this is the best solution, + // probably isn't. But I need this bit to work. + if ( sSectorX < MINIMUM_VALID_X_COORDINATE || + sSectorX > MAXIMUM_VALID_X_COORDINATE || + sSectorY < MINIMUM_VALID_Y_COORDINATE || + sSectorY > MAXIMUM_VALID_Y_COORDINATE ) + { + return false; + } + + AssertGE( sSectorX, MINIMUM_VALID_X_COORDINATE ); + AssertLE( sSectorX, MAXIMUM_VALID_X_COORDINATE ); + AssertGE( sSectorY, MINIMUM_VALID_Y_COORDINATE ); + AssertLE( sSectorY, MAXIMUM_VALID_Y_COORDINATE ); + + SECTORINFO* pSector = &SectorInfo[SECTOR( sSectorX, sSectorY )]; + + // count how many turncoats we have + UINT16 num_static = 0; + UINT16 num_groups = 0; + + switch ( aSoldierClass ) + { + case SOLDIER_CLASS_ADMINISTRATOR: + num_static = pSector->ubNumAdmins_Turncoat; + break; + case SOLDIER_CLASS_ARMY: + num_static = pSector->ubNumTroops_Turncoat; + break; + case SOLDIER_CLASS_ELITE: + num_static = pSector->ubNumElites_Turncoat; + break; + default: + break; + } + + GROUP* pGroup = gpGroupList; + while ( pGroup ) + { + if ( pGroup->usGroupTeam == ENEMY_TEAM && !pGroup->fVehicle && pGroup->ubSectorX == sSectorX && pGroup->ubSectorY == sSectorY ) + { + switch ( aSoldierClass ) + { + case SOLDIER_CLASS_ADMINISTRATOR: + num_groups += pGroup->pEnemyGroup->ubNumAdmins_Turncoat; + break; + case SOLDIER_CLASS_ARMY: + num_groups += pGroup->pEnemyGroup->ubNumTroops_Turncoat; + break; + case SOLDIER_CLASS_ELITE: + num_groups += pGroup->pEnemyGroup->ubNumElites_Turncoat; + break; + default: + break; + } + } + pGroup = pGroup->next; + } + + if ( num_static + num_groups ) + { + if ( Chance( 100 * num_static / ( num_static + num_groups ) ) ) + { + // add to garrison + switch ( aSoldierClass ) + { + case SOLDIER_CLASS_ADMINISTRATOR: + pSector->ubNumAdmins_Turncoat--; + return true; + break; + case SOLDIER_CLASS_ARMY: + pSector->ubNumTroops_Turncoat--; + return true; + break; + case SOLDIER_CLASS_ELITE: + pSector->ubNumElites_Turncoat--; + return true; + break; + default: + break; + } + } + else + { + GROUP* pGroup = gpGroupList; + while ( pGroup ) + { + if ( pGroup->usGroupTeam == ENEMY_TEAM && !pGroup->fVehicle && pGroup->ubSectorX == sSectorX && pGroup->ubSectorY == sSectorY ) + { + switch ( aSoldierClass ) + { + case SOLDIER_CLASS_ADMINISTRATOR: + if ( pGroup->pEnemyGroup->ubNumAdmins_Turncoat ) + { + pGroup->pEnemyGroup->ubNumAdmins_Turncoat--; + return true; + } + break; + case SOLDIER_CLASS_ARMY: + if ( pGroup->pEnemyGroup->ubNumTroops_Turncoat ) + { + pGroup->pEnemyGroup->ubNumTroops_Turncoat--; + return true; + } + break; + case SOLDIER_CLASS_ELITE: + if ( pGroup->pEnemyGroup->ubNumElites_Turncoat ) + { + pGroup->pEnemyGroup->ubNumElites_Turncoat--; + return true; + } + break; + default: + break; + } + } + pGroup = pGroup->next; + } + } + } + + return false; +} + +bool AddOneTurncoat( INT16 sSectorX, INT16 sSectorY, UINT8 aSoldierClass ) +{ + // HEADROCK: This is a TEMPORARY fix to avoid the assertion error. Not sure this is the best solution, + // probably isn't. But I need this bit to work. + if ( sSectorX < MINIMUM_VALID_X_COORDINATE || + sSectorX > MAXIMUM_VALID_X_COORDINATE || + sSectorY < MINIMUM_VALID_Y_COORDINATE || + sSectorY > MAXIMUM_VALID_Y_COORDINATE ) + { + return false; + } + + AssertGE( sSectorX, MINIMUM_VALID_X_COORDINATE ); + AssertLE( sSectorX, MAXIMUM_VALID_X_COORDINATE ); + AssertGE( sSectorY, MINIMUM_VALID_Y_COORDINATE ); + AssertLE( sSectorY, MAXIMUM_VALID_Y_COORDINATE ); + + SECTORINFO* pSector = &SectorInfo[SECTOR( sSectorX, sSectorY )]; + + // count how many 'slots' we have for more turncoats, then add one at a random position + UINT16 freeslots_static = 0; + UINT16 freeslots_groups = 0; + + switch ( aSoldierClass ) + { + case SOLDIER_CLASS_ADMINISTRATOR: + if ( pSector->ubNumAdmins_Turncoat < pSector->ubNumAdmins ) + freeslots_static = pSector->ubNumAdmins - pSector->ubNumAdmins_Turncoat; + break; + case SOLDIER_CLASS_ARMY: + if ( pSector->ubNumTroops_Turncoat < pSector->ubNumTroops ) + freeslots_static = pSector->ubNumTroops - pSector->ubNumTroops_Turncoat; + break; + case SOLDIER_CLASS_ELITE: + if ( pSector->ubNumElites_Turncoat < pSector->ubNumElites ) + freeslots_static = pSector->ubNumElites - pSector->ubNumElites_Turncoat; + break; + default: + break; + } + + GROUP* pGroup = gpGroupList; + while ( pGroup ) + { + if ( pGroup->usGroupTeam == ENEMY_TEAM && !pGroup->fVehicle && pGroup->ubSectorX == sSectorX && pGroup->ubSectorY == sSectorY ) + { + switch ( aSoldierClass ) + { + case SOLDIER_CLASS_ADMINISTRATOR: + if ( pGroup->pEnemyGroup->ubNumAdmins_Turncoat < pGroup->pEnemyGroup->ubNumAdmins ) + freeslots_groups += pGroup->pEnemyGroup->ubNumAdmins - pGroup->pEnemyGroup->ubNumAdmins_Turncoat; + break; + case SOLDIER_CLASS_ARMY: + if ( pGroup->pEnemyGroup->ubNumTroops_Turncoat < pGroup->pEnemyGroup->ubNumTroops ) + freeslots_groups += pGroup->pEnemyGroup->ubNumTroops - pGroup->pEnemyGroup->ubNumTroops_Turncoat; + break; + case SOLDIER_CLASS_ELITE: + if ( pGroup->pEnemyGroup->ubNumElites_Turncoat < pGroup->pEnemyGroup->ubNumElites ) + freeslots_groups += pGroup->pEnemyGroup->ubNumElites - pGroup->pEnemyGroup->ubNumElites_Turncoat; + break; + default: + break; + } + } + pGroup = pGroup->next; + } + + if ( freeslots_static + freeslots_groups ) + { + if ( Chance( 100 * freeslots_static / ( freeslots_static + freeslots_groups ) ) ) + { + // add to garrison + switch ( aSoldierClass ) + { + case SOLDIER_CLASS_ADMINISTRATOR: + pSector->ubNumAdmins_Turncoat++; + return true; + break; + case SOLDIER_CLASS_ARMY: + pSector->ubNumTroops_Turncoat++; + return true; + break; + case SOLDIER_CLASS_ELITE: + pSector->ubNumElites_Turncoat++; + return true; + break; + default: + break; + } + } + else + { + GROUP* pGroup = gpGroupList; + while ( pGroup ) + { + if ( pGroup->usGroupTeam == ENEMY_TEAM && !pGroup->fVehicle && pGroup->ubSectorX == sSectorX && pGroup->ubSectorY == sSectorY ) + { + switch ( aSoldierClass ) + { + case SOLDIER_CLASS_ADMINISTRATOR: + if ( pGroup->pEnemyGroup->ubNumAdmins_Turncoat < pGroup->pEnemyGroup->ubNumAdmins ) + { + pGroup->pEnemyGroup->ubNumAdmins_Turncoat++; + return true; + } + break; + case SOLDIER_CLASS_ARMY: + if ( pGroup->pEnemyGroup->ubNumTroops_Turncoat < pGroup->pEnemyGroup->ubNumTroops ) + { + pGroup->pEnemyGroup->ubNumTroops_Turncoat++; + return true; + } + break; + case SOLDIER_CLASS_ELITE: + if ( pGroup->pEnemyGroup->ubNumElites_Turncoat < pGroup->pEnemyGroup->ubNumElites ) + { + pGroup->pEnemyGroup->ubNumElites_Turncoat++; + return true; + } + break; + default: + break; + } + } + pGroup = pGroup->next; + } + } + } + + return false; +} + +void CorrectTurncoatCount( INT16 sSectorX, INT16 sSectorY ) +{ + // HEADROCK: This is a TEMPORARY fix to avoid the assertion error. Not sure this is the best solution, + // probably isn't. But I need this bit to work. + if ( sSectorX < MINIMUM_VALID_X_COORDINATE || + sSectorX > MAXIMUM_VALID_X_COORDINATE || + sSectorY < MINIMUM_VALID_Y_COORDINATE || + sSectorY > MAXIMUM_VALID_Y_COORDINATE ) + { + return; + } + + AssertGE( sSectorX, MINIMUM_VALID_X_COORDINATE ); + AssertLE( sSectorX, MAXIMUM_VALID_X_COORDINATE ); + AssertGE( sSectorY, MINIMUM_VALID_Y_COORDINATE ); + AssertLE( sSectorY, MAXIMUM_VALID_Y_COORDINATE ); + + SECTORINFO* pSector = &SectorInfo[SECTOR( sSectorX, sSectorY )]; + + if ( pSector->ubNumAdmins_Turncoat > pSector->ubNumAdmins ) + { + pSector->ubNumTroops_Turncoat += pSector->ubNumAdmins_Turncoat - pSector->ubNumAdmins; + pSector->ubNumAdmins_Turncoat = pSector->ubNumAdmins; + } + if ( pSector->ubNumTroops_Turncoat > pSector->ubNumTroops ) + { + pSector->ubNumElites_Turncoat += pSector->ubNumTroops_Turncoat - pSector->ubNumTroops; + pSector->ubNumTroops_Turncoat = pSector->ubNumTroops; + } + if ( pSector->ubNumElites_Turncoat > pSector->ubNumElites ) + { + pSector->ubNumElites_Turncoat = pSector->ubNumElites; + } + + GROUP* pGroup = gpGroupList; + while ( pGroup ) + { + if ( pGroup->usGroupTeam == ENEMY_TEAM && !pGroup->fVehicle && pGroup->ubSectorX == sSectorX && pGroup->ubSectorY == sSectorY ) + { + if ( pGroup->pEnemyGroup->ubNumAdmins_Turncoat > pGroup->pEnemyGroup->ubNumAdmins ) + { + pGroup->pEnemyGroup->ubNumTroops_Turncoat += pGroup->pEnemyGroup->ubNumAdmins_Turncoat - pGroup->pEnemyGroup->ubNumAdmins; + pGroup->pEnemyGroup->ubNumAdmins_Turncoat = pGroup->pEnemyGroup->ubNumAdmins; + } + if ( pGroup->pEnemyGroup->ubNumTroops_Turncoat > pGroup->pEnemyGroup->ubNumTroops ) + { + pGroup->pEnemyGroup->ubNumElites_Turncoat += pGroup->pEnemyGroup->ubNumTroops_Turncoat - pGroup->pEnemyGroup->ubNumTroops; + pGroup->pEnemyGroup->ubNumTroops_Turncoat = pGroup->pEnemyGroup->ubNumTroops; + } + if ( pGroup->pEnemyGroup->ubNumElites_Turncoat > pGroup->pEnemyGroup->ubNumElites ) + { + pGroup->pEnemyGroup->ubNumElites_Turncoat = pGroup->pEnemyGroup->ubNumElites; + } + } + pGroup = pGroup->next; + } +} diff --git a/Strategic/Queen Command.h b/Strategic/Queen Command.h index a915ae78d..9081bf0b0 100644 --- a/Strategic/Queen Command.h +++ b/Strategic/Queen Command.h @@ -73,6 +73,13 @@ extern INT32 gsGridNoForMapEdgePointInfo; // This currently does not exist for MILITIA_TEAM, making such a check relatively useless BOOLEAN CheckPendingNonPlayerTeam(UINT8 usTeam); +// Flugente: turncoats +UINT16 NumTurncoatsOfClassInSector( INT16 sSectorX, INT16 sSectorY, UINT8 aSoldierClass ); +BOOLEAN TurncoatsInSector( INT16 sSectorX, INT16 sSectorY ); +bool RemoveOneTurncoat( INT16 sSectorX, INT16 sSectorY, UINT8 aSoldierClass ); +bool AddOneTurncoat( INT16 sSectorX, INT16 sSectorY, UINT8 aSoldierClass ); +void CorrectTurncoatCount( INT16 sSectorX, INT16 sSectorY ); + extern UINT32 guiTurnCnt, guiReinforceTurn, guiMilitiaReinforceTurn;//dnl ch68 080913 #endif diff --git a/Strategic/Strategic AI.cpp b/Strategic/Strategic AI.cpp index 52ed99d2f..9cd0f0703 100644 --- a/Strategic/Strategic AI.cpp +++ b/Strategic/Strategic AI.cpp @@ -5815,6 +5815,13 @@ void InvestigateSector( UINT8 ubSectorID ) ubNumToSend--; ubAdmins[i]++; ubTotal++; + + // Flugente: turncoats might move as well + if ( pSector->ubNumAdmins_Turncoat > pSector->ubNumAdmins ) + { + SectorInfo[ubSectorID].ubNumAdmins_Turncoat += pSector->ubNumAdmins_Turncoat - pSector->ubNumAdmins; + pSector->ubNumAdmins_Turncoat = pSector->ubNumAdmins; + } } else if( pSector->ubNumTroops ) { @@ -5822,6 +5829,13 @@ void InvestigateSector( UINT8 ubSectorID ) ubNumToSend--; ubTroops[i]++; ubTotal++; + + // Flugente: turncoats might move as well + if ( pSector->ubNumTroops_Turncoat > pSector->ubNumTroops ) + { + SectorInfo[ubSectorID].ubNumTroops_Turncoat += pSector->ubNumTroops_Turncoat - pSector->ubNumTroops; + pSector->ubNumTroops_Turncoat = pSector->ubNumTroops; + } } else if( pSector->ubNumElites ) { @@ -5829,6 +5843,13 @@ void InvestigateSector( UINT8 ubSectorID ) ubNumToSend--; ubElites[i]++; ubTotal++; + + // Flugente: turncoats might move as well + if ( pSector->ubNumElites_Turncoat > pSector->ubNumElites ) + { + SectorInfo[ubSectorID].ubNumElites_Turncoat += pSector->ubNumElites_Turncoat - pSector->ubNumElites; + pSector->ubNumElites_Turncoat = pSector->ubNumElites; + } } else { @@ -6571,6 +6592,13 @@ void UpgradeAdminsToTroops() --ubAdminsToCheck; } + + // Flugente: make sure turncoat numbers are plausible + if ( pSector->ubNumAdmins_Turncoat > pSector->ubNumAdmins ) + { + pSector->ubNumTroops_Turncoat += pSector->ubNumAdmins_Turncoat - pSector->ubNumAdmins; + pSector->ubNumAdmins_Turncoat = pSector->ubNumAdmins; + } } } } @@ -6628,6 +6656,13 @@ void UpgradeAdminsToTroops() ubAdminsToCheck--; } + + // Flugente: make sure turncoat numbers are plausible + if ( pGroup->pEnemyGroup->ubNumAdmins_Turncoat > pGroup->pEnemyGroup->ubNumAdmins ) + { + pGroup->pEnemyGroup->ubNumTroops_Turncoat += pGroup->pEnemyGroup->ubNumAdmins_Turncoat - pGroup->pEnemyGroup->ubNumAdmins; + pGroup->pEnemyGroup->ubNumAdmins_Turncoat = pGroup->pEnemyGroup->ubNumAdmins; + } } } } diff --git a/Strategic/Strategic Movement.h b/Strategic/Strategic Movement.h index 2c751981e..d946d8941 100644 --- a/Strategic/Strategic Movement.h +++ b/Strategic/Strategic Movement.h @@ -75,7 +75,14 @@ typedef struct ENEMYGROUP UINT8 ubTanksInBattle; UINT8 ubNumJeeps; UINT8 ubJeepsInBattle; - INT8 bPadding[16]; + + // Flugente: number of turncoats + UINT8 ubNumAdmins_Turncoat; + UINT8 ubNumTroops_Turncoat; + UINT8 ubNumElites_Turncoat; + UINT8 bPadding_1; + + INT8 bPadding[12]; }ENEMYGROUP; //NOTE: ALL FLAGS ARE CLEARED WHENEVER A GROUP ARRIVES IN A SECTOR, OR ITS WAYPOINTS ARE diff --git a/Strategic/Town Militia.cpp b/Strategic/Town Militia.cpp index cc8690514..9c2ba1f30 100644 --- a/Strategic/Town Militia.cpp +++ b/Strategic/Town Militia.cpp @@ -431,12 +431,15 @@ INT8 SoldierClassToMilitiaRank(UINT8 ubSoldierClass) switch( ubSoldierClass ) { + case SOLDIER_CLASS_ADMINISTRATOR: case SOLDIER_CLASS_GREEN_MILITIA: bRank = GREEN_MILITIA; break; + case SOLDIER_CLASS_ARMY: case SOLDIER_CLASS_REG_MILITIA: bRank = REGULAR_MILITIA; break; + case SOLDIER_CLASS_ELITE: case SOLDIER_CLASS_ELITE_MILITIA: bRank = ELITE_MILITIA; break; diff --git a/Tactical/Faces.cpp b/Tactical/Faces.cpp index 974f76c0c..862e3051b 100644 --- a/Tactical/Faces.cpp +++ b/Tactical/Faces.cpp @@ -2395,9 +2395,9 @@ void HandleRenderFaceAdjustments( FACETYPE *pFace, BOOLEAN fDisplayBuffer, BOOLE fDoIcon_Assignment = TRUE; fShowCustomText = TRUE; - if (pSoldier->usSkillCooldown[SOLDIER_COOLDOWN_INTEL_PENALTY]) + if (pSoldier->usSkillCooldown[SOLDIER_COOLDOWN_INTEL_PENALTY] > 10) { - swprintf(sString, L"Hide %dh", pSoldier->usSkillCooldown[SOLDIER_COOLDOWN_INTEL_PENALTY]); + swprintf(sString, L"Hide %dh", pSoldier->usSkillCooldown[SOLDIER_COOLDOWN_INTEL_PENALTY] / 10); } else { diff --git a/Tactical/Interface.cpp b/Tactical/Interface.cpp index e4fcbda3b..44e4d9458 100644 --- a/Tactical/Interface.cpp +++ b/Tactical/Interface.cpp @@ -6283,10 +6283,7 @@ BOOLEAN ShowSoldierRoleSymbol(SOLDIERTYPE* pSoldier) // this only works on enemy soldiers if ( pSoldier->bTeam != ENEMY_TEAM && pSoldier->bTeam != CIV_TEAM ) return false; - - if ( pSoldier->usSkillCounter[SOLDIER_COUNTER_ROLE_OBSERVED] < gGameExternalOptions.usTurnsToUncover ) - return false; - + INT16 sXPos = 0; INT16 sYPos = 0; INT32 iBack = 0; @@ -6297,113 +6294,29 @@ BOOLEAN ShowSoldierRoleSymbol(SOLDIERTYPE* pSoldier) sXPos += 50; sYPos += 25; - // are we a VIP? show that only when the player knows a VIP is in this sector. otherwise, don't even show our officer property - if ( pSoldier->usSoldierFlagMask & SOLDIER_VIP && !pSoldier->bSectorZ ) + if ( pSoldier->usSkillCounter[SOLDIER_COUNTER_ROLE_OBSERVED] >= gGameExternalOptions.usTurnsToUncover ) { - if ( PlayerKnowsAboutVIP( pSoldier->sSectorX, pSoldier->sSectorY ) ) + // are we a VIP? show that only when the player knows a VIP is in this sector. otherwise, don't even show our officer property + if ( pSoldier->usSoldierFlagMask & SOLDIER_VIP && !pSoldier->bSectorZ ) { - // Add bars - iBack = RegisterBackgroundRect( BGND_FLAG_SINGLE, NULL, sXPos, sYPos, (INT16)(sXPos + 20), (INT16)(sYPos + 20) ); - - if ( iBack != -1 ) + if ( PlayerKnowsAboutVIP( pSoldier->sSectorX, pSoldier->sSectorY ) ) { - SetBackgroundRectFilled( iBack ); + // Add bars + iBack = RegisterBackgroundRect( BGND_FLAG_SINGLE, NULL, sXPos, sYPos, (INT16)( sXPos + 20 ), (INT16)( sYPos + 20 ) ); + + if ( iBack != -1 ) + { + SetBackgroundRectFilled( iBack ); + } + + BltVideoObjectFromIndex( FRAME_BUFFER, guiENEMYROLES, 6, sXPos, sYPos, VO_BLT_TRANSSHADOW, NULL ); + + sYPos += 20; } - - BltVideoObjectFromIndex( FRAME_BUFFER, guiENEMYROLES, 6, sXPos, sYPos, VO_BLT_TRANSSHADOW, NULL ); - - sYPos += 20; - } - } - - // is this guy an officer? - if ( pSoldier->usSoldierFlagMask & SOLDIER_ENEMY_OFFICER ) - { - // Add bars - iBack = RegisterBackgroundRect( BGND_FLAG_SINGLE, NULL, sXPos, sYPos, (INT16)(sXPos + 20 ), (INT16)(sYPos + 20 ) ); - - if ( iBack != -1 ) - { - SetBackgroundRectFilled( iBack ); } - // if we look at this guy long enough, we might even learn that he is an advanced officer - if ( pSoldier->usSkillCounter[SOLDIER_COUNTER_ROLE_OBSERVED] > 1.5 * gGameExternalOptions.usTurnsToUncover && NUM_SKILL_TRAITS( pSoldier, SQUADLEADER_NT ) > 1 ) - BltVideoObjectFromIndex( FRAME_BUFFER, guiENEMYROLES, 5, sXPos, sYPos, VO_BLT_TRANSSHADOW, NULL ); - else - BltVideoObjectFromIndex( FRAME_BUFFER, guiENEMYROLES, 0, sXPos, sYPos, VO_BLT_TRANSSHADOW, NULL ); - - sYPos += 20; - } - - // is this guy radio operator with a radio? - if ( pSoldier->CanUseRadio(false) ) - { - // Add bars - iBack = RegisterBackgroundRect( BGND_FLAG_SINGLE, NULL, sXPos, sYPos, (INT16)(sXPos + 20 ), (INT16)(sYPos + 20 ) ); - - if ( iBack != -1 ) - { - SetBackgroundRectFilled( iBack ); - } - - BltVideoObjectFromIndex( FRAME_BUFFER, guiENEMYROLES, 3, sXPos, sYPos, VO_BLT_TRANSSHADOW, NULL ); - - sYPos += 20; - } - - // is this guy a doctor? we do not check for medical gear, as we would not have that information from a glance - if ( HAS_SKILL_TRAIT( pSoldier, DOCTOR_NT) ) - { - // Add bars - iBack = RegisterBackgroundRect( BGND_FLAG_SINGLE, NULL, sXPos, sYPos, (INT16)(sXPos + 20 ), (INT16)(sYPos + 20 ) ); - - if ( iBack != -1 ) - { - SetBackgroundRectFilled( iBack ); - } - - BltVideoObjectFromIndex( FRAME_BUFFER, guiENEMYROLES, 1, sXPos, sYPos, VO_BLT_TRANSSHADOW, NULL ); - - sYPos += 20; - } - - // is this guy a sniper? Just check for the gun, trait is not necessary - if ( pSoldier->HasSniper() ) - { - // Add bars - iBack = RegisterBackgroundRect( BGND_FLAG_SINGLE, NULL, sXPos, sYPos, (INT16)(sXPos + 20 ), (INT16)(sYPos + 20 ) ); - - if ( iBack != -1 ) - { - SetBackgroundRectFilled( iBack ); - } - - BltVideoObjectFromIndex( FRAME_BUFFER, guiENEMYROLES, 2, sXPos, sYPos, VO_BLT_TRANSSHADOW, NULL ); - - sYPos += 20; - } - - // is this guy a mortar guy? Just check for the gun, trait is not necessary - if ( pSoldier->HasMortar() ) - { - // Add bars - iBack = RegisterBackgroundRect( BGND_FLAG_SINGLE, NULL, sXPos, sYPos, (INT16)(sXPos + 20 ), (INT16)(sYPos + 20 ) ); - - if ( iBack != -1 ) - { - SetBackgroundRectFilled( iBack ); - } - - BltVideoObjectFromIndex( FRAME_BUFFER, guiENEMYROLES, 4, sXPos, sYPos, VO_BLT_TRANSSHADOW, NULL ); - - sYPos += 20; - } - - // do we carry a key? - for ( int ubLoop = 0; ubLoop < NUM_KEYS; ++ubLoop ) - { - if ( SoldierHasKey( pSoldier, ubLoop ) ) + // is this guy an officer? + if ( pSoldier->usSoldierFlagMask & SOLDIER_ENEMY_OFFICER ) { // Add bars iBack = RegisterBackgroundRect( BGND_FLAG_SINGLE, NULL, sXPos, sYPos, (INT16)( sXPos + 20 ), (INT16)( sYPos + 20 ) ); @@ -6413,12 +6326,115 @@ BOOLEAN ShowSoldierRoleSymbol(SOLDIERTYPE* pSoldier) SetBackgroundRectFilled( iBack ); } - BltVideoObjectFromIndex( FRAME_BUFFER, guiENEMYROLES, 11, sXPos, sYPos, VO_BLT_TRANSSHADOW, NULL ); + // if we look at this guy long enough, we might even learn that he is an advanced officer + if ( pSoldier->usSkillCounter[SOLDIER_COUNTER_ROLE_OBSERVED] > 1.5 * gGameExternalOptions.usTurnsToUncover && NUM_SKILL_TRAITS( pSoldier, SQUADLEADER_NT ) > 1 ) + BltVideoObjectFromIndex( FRAME_BUFFER, guiENEMYROLES, 5, sXPos, sYPos, VO_BLT_TRANSSHADOW, NULL ); + else + BltVideoObjectFromIndex( FRAME_BUFFER, guiENEMYROLES, 0, sXPos, sYPos, VO_BLT_TRANSSHADOW, NULL ); sYPos += 20; - - break; } + + // is this guy radio operator with a radio? + if ( pSoldier->CanUseRadio( false ) ) + { + // Add bars + iBack = RegisterBackgroundRect( BGND_FLAG_SINGLE, NULL, sXPos, sYPos, (INT16)( sXPos + 20 ), (INT16)( sYPos + 20 ) ); + + if ( iBack != -1 ) + { + SetBackgroundRectFilled( iBack ); + } + + BltVideoObjectFromIndex( FRAME_BUFFER, guiENEMYROLES, 3, sXPos, sYPos, VO_BLT_TRANSSHADOW, NULL ); + + sYPos += 20; + } + + // is this guy a doctor? we do not check for medical gear, as we would not have that information from a glance + if ( HAS_SKILL_TRAIT( pSoldier, DOCTOR_NT ) ) + { + // Add bars + iBack = RegisterBackgroundRect( BGND_FLAG_SINGLE, NULL, sXPos, sYPos, (INT16)( sXPos + 20 ), (INT16)( sYPos + 20 ) ); + + if ( iBack != -1 ) + { + SetBackgroundRectFilled( iBack ); + } + + BltVideoObjectFromIndex( FRAME_BUFFER, guiENEMYROLES, 1, sXPos, sYPos, VO_BLT_TRANSSHADOW, NULL ); + + sYPos += 20; + } + + // is this guy a sniper? Just check for the gun, trait is not necessary + if ( pSoldier->HasSniper() ) + { + // Add bars + iBack = RegisterBackgroundRect( BGND_FLAG_SINGLE, NULL, sXPos, sYPos, (INT16)( sXPos + 20 ), (INT16)( sYPos + 20 ) ); + + if ( iBack != -1 ) + { + SetBackgroundRectFilled( iBack ); + } + + BltVideoObjectFromIndex( FRAME_BUFFER, guiENEMYROLES, 2, sXPos, sYPos, VO_BLT_TRANSSHADOW, NULL ); + + sYPos += 20; + } + + // is this guy a mortar guy? Just check for the gun, trait is not necessary + if ( pSoldier->HasMortar() ) + { + // Add bars + iBack = RegisterBackgroundRect( BGND_FLAG_SINGLE, NULL, sXPos, sYPos, (INT16)( sXPos + 20 ), (INT16)( sYPos + 20 ) ); + + if ( iBack != -1 ) + { + SetBackgroundRectFilled( iBack ); + } + + BltVideoObjectFromIndex( FRAME_BUFFER, guiENEMYROLES, 4, sXPos, sYPos, VO_BLT_TRANSSHADOW, NULL ); + + sYPos += 20; + } + + // do we carry a key? + for ( int ubLoop = 0; ubLoop < NUM_KEYS; ++ubLoop ) + { + if ( SoldierHasKey( pSoldier, ubLoop ) ) + { + // Add bars + iBack = RegisterBackgroundRect( BGND_FLAG_SINGLE, NULL, sXPos, sYPos, (INT16)( sXPos + 20 ), (INT16)( sYPos + 20 ) ); + + if ( iBack != -1 ) + { + SetBackgroundRectFilled( iBack ); + } + + BltVideoObjectFromIndex( FRAME_BUFFER, guiENEMYROLES, 11, sXPos, sYPos, VO_BLT_TRANSSHADOW, NULL ); + + sYPos += 20; + + break; + } + } + } + + // turncoats are instantly visible + if ( gSkillTraitValues.fCOTurncoats && pSoldier->usSoldierFlagMask2 & SOLDIER_TURNCOAT ) + { + // Add bars + iBack = RegisterBackgroundRect( BGND_FLAG_SINGLE, NULL, sXPos, sYPos, (INT16)( sXPos + 20 ), (INT16)( sYPos + 20 ) ); + + if ( iBack != -1 ) + { + SetBackgroundRectFilled( iBack ); + } + + BltVideoObjectFromIndex( FRAME_BUFFER, guiENEMYROLES, 12, sXPos, sYPos, VO_BLT_TRANSSHADOW, NULL ); + + sYPos += 20; } return true; diff --git a/Tactical/Overhead.cpp b/Tactical/Overhead.cpp index a81ac2bad..2d63b7a79 100644 --- a/Tactical/Overhead.cpp +++ b/Tactical/Overhead.cpp @@ -114,6 +114,8 @@ #include "DynamicDialogue.h" // added by Flugente for HandleDynamicOpinions() #include "DropDown.h" // added by Flugente #include "Creature Spreading.h" // added by Flugente forResetCreatureAttackVariables() +#include "finances.h" // added by Flugente +#include "MilitiaIndividual.h" // added by Flugente #endif #include "connect.h" @@ -153,6 +155,10 @@ class OBJECTTYPE; class SOLDIERTYPE; extern void HandleBestSightingPositionInRealtime(); +extern INT32 GetCurrentBalance( void ); + +extern CHAR16 gzUserDefinedButton1[128]; +extern CHAR16 gzUserDefinedButton2[128]; void UpdateFastForwardMode(SOLDIERTYPE* pSoldier, INT8 bAction); // sevenfm: to call in HandleAtNewGridNo @@ -4214,15 +4220,13 @@ void SetSoldierNeutral( SOLDIERTYPE * pSoldier ) } void MakeCivHostile(SOLDIERTYPE *pSoldier) { - INT8 bNewSide; - if ( pSoldier->ubBodyType == COW ) { return; } // default is hostile to player, allied to army - bNewSide = 1; + INT8 bNewSide = 1; switch( pSoldier->ubProfile ) { @@ -4267,6 +4271,11 @@ void MakeCivHostile(SOLDIERTYPE *pSoldier) { bNewSide = zCivGroupName[pSoldier->ubCivilianGroup].bSide; } + // Flugente: enemies are already hostile, let's assume we want to make them friendly instead + else if ( pSoldier->bTeam == ENEMY_TEAM ) + { + bNewSide = 0; + } if ( !pSoldier->aiData.bNeutral && bNewSide == pSoldier->bSide ) { @@ -4302,6 +4311,63 @@ void MakeCivHostile(SOLDIERTYPE *pSoldier) pSoldier->aiData.bOrders = FARPATROL; } + // Flugente: turncoats + if ( pSoldier->bTeam == ENEMY_TEAM ) + { + // change to enemy team + SetSoldierNonNeutral( pSoldier ); + pSoldier->bSide = bNewSide; + + // remember old class and switch to its equivalent + UINT8 oldclass = pSoldier->ubSoldierClass; + UINT16 sector = SECTOR( pSoldier->sSectorX, pSoldier->sSectorY ); + + // extract militia profile from pSoldier, so that a fitting profile is ready to be taken during creation + CreateNewIndividualMilitiaFromSoldier( pSoldier, MO_DEFECTOR ); + + pSoldier = ChangeSoldierTeam( pSoldier, MILITIA_TEAM ); + + // it doesn't make sense to force a militia reset when all changes are in the sector, so set this back to original value afterwards + //BOOLEAN tmp = gfStrategicMilitiaChangesMade; + + //gfStrategicMilitiaChangesMade = tmp; + + if ( oldclass == SOLDIER_CLASS_ARMY ) + { + pSoldier->ubSoldierClass = SOLDIER_CLASS_REG_MILITIA; + + SectorInfo[sector].ubNumTroops = max( 0, SectorInfo[sector].ubNumTroops - 1 ); + SectorInfo[sector].ubTroopsInBattle = max( 0, SectorInfo[sector].ubTroopsInBattle - 1 ); + } + else if ( oldclass == SOLDIER_CLASS_ELITE ) + { + pSoldier->ubSoldierClass = SOLDIER_CLASS_ELITE_MILITIA; + + SectorInfo[sector].ubNumElites = max( 0, SectorInfo[sector].ubNumElites - 1 ); + SectorInfo[sector].ubElitesInBattle = max( 0, SectorInfo[sector].ubElitesInBattle - 1 ); + } + else + { + pSoldier->ubSoldierClass = SOLDIER_CLASS_GREEN_MILITIA; + + SectorInfo[sector].ubNumAdmins = max( 0, SectorInfo[sector].ubNumAdmins - 1 ); + SectorInfo[sector].ubAdminsInBattle = max( 0, SectorInfo[sector].ubAdminsInBattle - 1 ); + } + + StrategicAddMilitiaToSector( pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->ubSoldierClass - 4, 1 ); + + // rehandle sight for everybody + SOLDIERTYPE* pTeamSoldier; + UINT16 iLoop = gTacticalStatus.Team[OUR_TEAM].bFirstID; + for ( pTeamSoldier = MercPtrs[iLoop]; iLoop <= gTacticalStatus.Team[CIV_TEAM].bLastID; ++iLoop, ++pTeamSoldier ) + { + if ( pTeamSoldier->bActive && pTeamSoldier->bInSector && pTeamSoldier->stats.bLife > 0 ) + { + RecalculateOppCntsDueToNoLongerNeutral( pTeamSoldier ); + } + } + } + pSoldier->bSide = bNewSide; if ( pSoldier->aiData.bNeutral ) @@ -6903,6 +6969,10 @@ void RemoveCapturedEnemiesFromSectorInfo( INT16 sMapX, INT16 sMapY, INT8 bMapZ ) if ( pTeamSoldier->usSoldierFlagMask & SOLDIER_VIP ) DeleteVIP( pTeamSoldier->sSectorX, pTeamSoldier->sSectorY ); + // Flugente: turncoats + if ( pTeamSoldier->usSoldierFlagMask2 & SOLDIER_TURNCOAT ) + RemoveOneTurncoat( sMapX, sMapY, pTeamSoldier->ubSoldierClass ); + // Flugente: campaign stats gCurrentIncident.AddStat( pTeamSoldier, CAMPAIGNHISTORY_TYPE_PRISONER ); @@ -10520,7 +10590,138 @@ void HandleDisplayingOfPlayerLostDialogue( ) } #endif -static UINT8 prisonerdialoguetargetID = 0; +static UINT8 prisonerdialoguetargetID = NOBODY; + +void TurnCoatAttemptMessageBoxCallBack( UINT8 ubExitValue ) +{ + // check ubExitValue to see whether we actually want to go through (2) + if ( ubExitValue != 2 + || prisonerdialoguetargetID == NOBODY + || gusSelectedSoldier == NOBODY ) + return; + + SOLDIERTYPE* pSoldier = MercPtrs[prisonerdialoguetargetID]; + + if ( !pSoldier ) + return; + + INT16 approachselected = DropDownTemplate::getInstance().GetSelectedEntryKey(); + + UINT8 approachchance = MercPtrs[gusSelectedSoldier]->GetTurncoatConvinctionChance( prisonerdialoguetargetID, approachselected ); + + // you can never turn a VIP (though we don't tell the player if someone is a VIP, lest they have an exploit to find out) + if ( pSoldier->usSoldierFlagMask & SOLDIER_VIP ) + approachchance = 0; + + // as using random numbers to pass the check would result in players savescumming, use a number based on the soldier's stats + UINT32 soldierconsistentnumber = ( 23 * EffectiveAgility( pSoldier, FALSE ) + 83 * EffectiveExplosive( pSoldier ) + 19 * EffectiveMarksmanship( pSoldier ) + 92 * pSoldier->stats.bLifeMax ) % 100; + + if ( soldierconsistentnumber < approachchance ) + { + pSoldier->usSoldierFlagMask2 |= SOLDIER_TURNCOAT; + + AddOneTurncoat( pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->ubSoldierClass ); + + ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, szTurncoatText[0], pSoldier->GetName() ); + + // increase intel penalty. We can only try to convert enemies if the penalty is low, and having a high penalty means we can't mine intel for a few hours but have to hide + MercPtrs[gusSelectedSoldier]->usSkillCooldown[SOLDIER_COOLDOWN_INTEL_PENALTY] += 1; + + StatChange( MercPtrs[gusSelectedSoldier], EXPERAMT, 2, FROM_SUCCESS ); + StatChange( MercPtrs[gusSelectedSoldier], LDRAMT, 4, FROM_SUCCESS ); + } + else + { + ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, szTurncoatText[1], pSoldier->GetName() ); + + // increase intel penalty. We can only try to convert enemies if the penalty is low, and having a high penalty means we can't mine intel for a few hours but have to hide + MercPtrs[gusSelectedSoldier]->usSkillCooldown[SOLDIER_COOLDOWN_INTEL_PENALTY] += 4; + + StatChange( MercPtrs[gusSelectedSoldier], EXPERAMT, 1, FROM_FAILURE ); + StatChange( MercPtrs[gusSelectedSoldier], LDRAMT, 1, FROM_FAILURE ); + } + + // explain that suspicion is so high that we have to stop + if ( MercPtrs[gusSelectedSoldier]->usSkillCooldown[SOLDIER_COOLDOWN_INTEL_PENALTY] >= 20 ) + ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, szTurncoatText[2] ); + + // use up resources spent, regardless of whether or not we were successful + if ( approachselected == 3 ) + { + INT32 bribeamount = 10 * min( 10, CurrentPlayerProgressPercentage() ); + + // substract money spent + AddTransactionToPlayersBook ( TRANSFER_FUNDS_TO_MERC, MercPtrs[gusSelectedSoldier]->ubProfile, GetWorldTotalMin(), -bribeamount ); + } + else if ( approachselected == 4 ) + { + int intelbribeneeded = max( 2, ( CurrentPlayerProgressPercentage() + 5 ) / 10 ); + + AddIntel( -intelbribeneeded, TRUE ); + } + + //ReduceAttackBusyCount(); +} + +CHAR16 gTurncoatDropdownText[4][256]; + +void HandleTurncoatAttempt( SOLDIERTYPE* pSoldier ) +{ + // abort if bad pointer, or not an enemy and not a capturable civilian + if ( pSoldier + && pSoldier->bTeam == ENEMY_TEAM + && pSoldier->ubProfile == NO_PROFILE + && !( pSoldier->usSoldierFlagMask2 & SOLDIER_TURNCOAT ) + && !gTacticalStatus.Team[ENEMY_TEAM].bAwareOfOpposition + && !pSoldier->RecognizeAsCombatant( gusSelectedSoldier ) ) + { + // remember the target's ID + prisonerdialoguetargetID = pSoldier->ubID; + + CHAR16 buf[256]; + UINT8 ubCurrentProgress = CurrentPlayerProgressPercentage(); + + std::vector > dropdownvector_1; + int cnt = 1; + + UINT8 chance = MercPtrs[gusSelectedSoldier]->GetTurncoatConvinctionChance( prisonerdialoguetargetID, cnt ); + swprintf( gTurncoatDropdownText[cnt-1], szTurncoatText[3], chance ); + dropdownvector_1.push_back( std::make_pair( cnt, gTurncoatDropdownText[cnt - 1] ) ); + + ++cnt; + chance = MercPtrs[gusSelectedSoldier]->GetTurncoatConvinctionChance( prisonerdialoguetargetID, cnt ); + swprintf( gTurncoatDropdownText[cnt - 1], szTurncoatText[4], chance ); + dropdownvector_1.push_back( std::make_pair( cnt, gTurncoatDropdownText[cnt - 1] ) ); + + ++cnt; + INT32 balance = GetCurrentBalance(); + INT32 bribeamount = 10 * min( 10, ubCurrentProgress ); + if ( bribeamount <= balance ) + { + chance = MercPtrs[gusSelectedSoldier]->GetTurncoatConvinctionChance( prisonerdialoguetargetID, cnt ); + swprintf( gTurncoatDropdownText[cnt - 1], szTurncoatText[5], bribeamount, chance ); + dropdownvector_1.push_back( std::make_pair( cnt, gTurncoatDropdownText[cnt - 1] ) ); + } + + ++cnt; + FLOAT intelreserve = GetIntel(); + int intelbribeneeded = max( 2, ( ubCurrentProgress + 5 ) / 10 ); + if ( intelbribeneeded <= intelreserve ) + { + chance = MercPtrs[gusSelectedSoldier]->GetTurncoatConvinctionChance( prisonerdialoguetargetID, cnt ); + swprintf( gTurncoatDropdownText[cnt - 1], szTurncoatText[6], intelbribeneeded, chance ); + dropdownvector_1.push_back( std::make_pair( cnt, gTurncoatDropdownText[cnt - 1] ) ); + } + + DropDownTemplate::getInstance().SetEntries( dropdownvector_1 ); + + wcscpy( gzUserDefinedButton1, szTurncoatText[8] ); + wcscpy( gzUserDefinedButton2, pEpcMenuStrings[4] ); + + DoMessageBox( MSG_BOX_BASIC_STYLE, szTurncoatText[7], guiCurrentScreen, ( MSG_BOX_FLAG_GENERIC_TWO_BUTTONS | MSG_BOX_FLAG_DROPDOWN_1 ), + TurnCoatAttemptMessageBoxCallBack, NULL ); + } +} void PrisonerSurrenderMessageBoxCallBack( UINT8 ubExitValue ) { @@ -10679,11 +10880,11 @@ void PrisonerSurrenderMessageBoxCallBack( UINT8 ubExitValue ) } } } - // we offered to surrender OURSELVES TO the enemy + // we offered to surrender OURSELVES TO the enemy, or are attempting to turn a soldier into a turncoat else if ( ubExitValue == 2 ) { // we cannot surrender to bandits, talk instead - if ( MercPtrs[prisonerdialoguetargetID]->bTeam != CREATURE_TEAM ) + if ( MercPtrs[prisonerdialoguetargetID]->bTeam == CREATURE_TEAM ) { // normal dialog StartCivQuote( MercPtrs[prisonerdialoguetargetID] ); @@ -10740,7 +10941,7 @@ void PrisonerSurrenderMessageBoxCallBack( UINT8 ubExitValue ) else if ( ubExitValue == 3 ) { // Flugente: if we are disguised and talk to a non-profile NPC, we will continue to 'chat' with the enemy as long as we aren't ordered to do something else. - // This way we can easily order our spies to 'distract 'enemies' + // This way we can easily order our spies to 'distract' enemies if ( GetSoldier( &pSoldier, gusSelectedSoldier ) && pSoldier->bTeam == gbPlayerNum && MercPtrs[prisonerdialoguetargetID] && @@ -11128,6 +11329,27 @@ UINT16 NumSoldiersWithFlagInSector( UINT8 aTeam, UINT32 aFlag ) return num; } +UINT16 NumSoldiersofClassWithFlag2InSector( UINT8 aTeam, UINT8 aSoldierClass, UINT32 aFlag ) +{ + SOLDIERTYPE* pSoldier; + INT32 cnt = 0; + UINT16 num = 0; + + for ( cnt = gTacticalStatus.Team[aTeam].bFirstID, pSoldier = MercPtrs[cnt]; cnt <= gTacticalStatus.Team[aTeam].bLastID; pSoldier++, ++cnt ) + { + if ( pSoldier->bActive && pSoldier->bInSector && pSoldier->stats.bLife > 0 ) + { + if ( (pSoldier->usSoldierFlagMask2 & aFlag) + && pSoldier->ubSoldierClass == aSoldierClass ) + { + ++num; + } + } + } + + return num; +} + INT32 GetClosestSoldierWithFlag( UINT8 aTeam, UINT32 aFlag ) { INT32 sBestGridNo = NOWHERE; @@ -11414,3 +11636,20 @@ BOOLEAN IsCivFactionMemberAliveInSector( UINT8 usCivilianGroup ) return FALSE; } + +BOOLEAN IsFreeSlotAvailable( int aTeam ) +{ + SOLDIERTYPE *pSoldier; + INT32 cnt = gTacticalStatus.Team[aTeam].bFirstID; + + // run through list + for ( pSoldier = MercPtrs[cnt]; cnt <= gTacticalStatus.Team[aTeam].bLastID; ++cnt, ++pSoldier ) + { + if ( !pSoldier->bActive ) + { + return TRUE; + } + } + + return FALSE; +} diff --git a/Tactical/Overhead.h b/Tactical/Overhead.h index 7498fe74b..e0739fff0 100644 --- a/Tactical/Overhead.h +++ b/Tactical/Overhead.h @@ -357,6 +357,9 @@ extern UINT8 NumZombiesInSector(); // Flugente: offer the enemy the chance to surrender void HandleSurrenderOffer( SOLDIERTYPE* pSoldier ); +// Flugente: attempt to convince a soldier to become a turncoat +void HandleTurncoatAttempt( SOLDIERTYPE* pSoldier ); + // Flugente: disguised mercs can distract enemies by talking to them. In order to display that, we sometimes display excerpts of their 'chats' void CheckChatPartners(); @@ -401,6 +404,7 @@ UINT8 HighestEnemyOfficersInSector(UINT8& aType); // count all soldiers in the current sector that have a specific flag set UINT16 NumSoldiersWithFlagInSector(UINT8 aTeam, UINT32 aFlag); +UINT16 NumSoldiersofClassWithFlag2InSector( UINT8 aTeam, UINT8 aSoldierClass, UINT32 aFlag ); INT32 GetClosestSoldierWithFlag( UINT8 aTeam, UINT32 aFlag ); // Flugente: VIP targets @@ -426,5 +430,7 @@ void VIPFleesToMeduna(); // Flugente: is any member of a specific civilian faction still alive in the current sector? BOOLEAN IsCivFactionMemberAliveInSector( UINT8 usCivilianGroup ); +BOOLEAN IsFreeSlotAvailable( int aTeam ); + #endif diff --git a/Tactical/SkillCheck.cpp b/Tactical/SkillCheck.cpp index f0c30bded..9456aacd0 100644 --- a/Tactical/SkillCheck.cpp +++ b/Tactical/SkillCheck.cpp @@ -300,6 +300,11 @@ void ReducePointsForFatigue( SOLDIERTYPE *pSoldier, UINT32 *pusPoints ) *pusPoints -= (*pusPoints * GetPenaltyForFatigue( pSoldier )) / 100; } +void ReducePointsForFatigue( SOLDIERTYPE *pSoldier, INT32 *psPoints ) +{ + *psPoints -= ( *psPoints * GetPenaltyForFatigue( pSoldier ) ) / 100; +} + INT32 GetSkillCheckPenaltyForFatigue( SOLDIERTYPE *pSoldier, INT32 iSkill ) { // use only half the full effect of fatigue for skill checks diff --git a/Tactical/SkillCheck.h b/Tactical/SkillCheck.h index 3aad20351..d1e203b75 100644 --- a/Tactical/SkillCheck.h +++ b/Tactical/SkillCheck.h @@ -5,6 +5,7 @@ #include "Soldier Control.h" void ReducePointsForFatigue( SOLDIERTYPE *pSoldier, UINT32 *pusPoints ); +void ReducePointsForFatigue( SOLDIERTYPE *pSoldier, INT32 *psPoints ); INT32 GetSkillCheckPenaltyForFatigue( SOLDIERTYPE *pSoldier, INT32 iSkill ); INT32 SkillCheck( SOLDIERTYPE *pSoldier, INT8 bReason, INT8 bDifficulty ); INT16 CalcTrapDetectLevel( SOLDIERTYPE *pSoldier, BOOLEAN fExamining ); diff --git a/Tactical/SkillMenu.cpp b/Tactical/SkillMenu.cpp index 60ed07daa..9deb64f3a 100644 --- a/Tactical/SkillMenu.cpp +++ b/Tactical/SkillMenu.cpp @@ -249,7 +249,7 @@ SkillSelection::Setup( UINT32 aVal ) pOption = new POPUP_OPTION( &std::wstring( pStr ), new popupCallbackFunction( &Wrapper_Function_SkillSelection, uiCounter ) ); // if we cannot perform this skill, grey it out - if ( !( pSoldier->CanUseSkill( uiCounter, TRUE ) ) ) + if ( !( pSoldier->CanUseSkill( uiCounter, TRUE, sTraitsMenuTargetGridNo ) ) ) { // Set this option off. pOption->setAvail( new popupCallbackFunction( &Popup_OptionOff, NULL ) ); @@ -309,7 +309,7 @@ SkillSelection::Setup( UINT32 aVal ) { for(UINT32 uiCounter = SKILLS_RADIO_FIRST; uiCounter <= SKILLS_RADIO_LAST; ++uiCounter) { - SetRegionFastHelpText( &(GetPopup()->MenuRegion[cnt++]), pSoldier->PrintSkillDesc(uiCounter) ); + SetRegionFastHelpText( &(GetPopup()->MenuRegion[cnt++]), pSoldier->PrintSkillDesc( uiCounter, sTraitsMenuTargetGridNo ) ); } } break; @@ -318,7 +318,7 @@ SkillSelection::Setup( UINT32 aVal ) { for ( UINT32 uiCounter = SKILLS_INTEL_FIRST; uiCounter <= SKILLS_INTEL_LAST; ++uiCounter ) { - SetRegionFastHelpText( &( GetPopup()->MenuRegion[cnt++] ), pSoldier->PrintSkillDesc( uiCounter ) ); + SetRegionFastHelpText( &( GetPopup()->MenuRegion[cnt++] ), pSoldier->PrintSkillDesc( uiCounter, sTraitsMenuTargetGridNo ) ); } } break; @@ -327,7 +327,7 @@ SkillSelection::Setup( UINT32 aVal ) { for(UINT32 uiCounter = SKILLS_VARIOUS_FIRST; uiCounter <= SKILLS_VARIOUS_LAST; ++uiCounter) { - SetRegionFastHelpText( &(GetPopup()->MenuRegion[cnt++]), pSoldier->PrintSkillDesc(uiCounter) ); + SetRegionFastHelpText( &(GetPopup()->MenuRegion[cnt++]), pSoldier->PrintSkillDesc(uiCounter, sTraitsMenuTargetGridNo ) ); } } break; diff --git a/Tactical/Soldier Ani.cpp b/Tactical/Soldier Ani.cpp index 99acb2ac5..7b0d7c4a3 100644 --- a/Tactical/Soldier Ani.cpp +++ b/Tactical/Soldier Ani.cpp @@ -114,6 +114,8 @@ extern UINT8 gubInterruptProvoker; extern UINT16 PickSoldierReadyAnimation( SOLDIERTYPE *pSoldier, BOOLEAN fEndReady, BOOLEAN fHipStance ); +extern bool RemoveOneTurncoat( INT16 sSectorX, INT16 sSectorY, UINT8 aSoldierClass ); + // Animation code explanations! // // 0-399: Actual animation frame indices @@ -4131,6 +4133,10 @@ BOOLEAN HandleSoldierDeath( SOLDIERTYPE *pSoldier , BOOLEAN *pfMadeCorpse ) { DeleteVIP( pSoldier->sSectorX, pSoldier->sSectorY ); } + + // Flugente: turncoats + if ( pSoldier->usSoldierFlagMask2 & SOLDIER_TURNCOAT ) + RemoveOneTurncoat( pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->ubSoldierClass ); // Flugente: additional dialogue if ( pSoldier->ubProfile != NO_PROFILE ) diff --git a/Tactical/Soldier Control.cpp b/Tactical/Soldier Control.cpp index 4f2f85c29..1860d1e69 100644 --- a/Tactical/Soldier Control.cpp +++ b/Tactical/Soldier Control.cpp @@ -10168,6 +10168,14 @@ UINT8 SOLDIERTYPE::SoldierTakeDamage( INT8 bHeight, INT16 sLifeDeduct, INT16 sBr if ( this->ubBodyType != CROW && this->ubBodyType != COW ) HandleDynamicOpinionChange( MercPtrs[ubAttacker], OPINIONEVENT_CIV_ATTACKER, TRUE, TRUE ); } + + // if we are a turncoat, lose the flag if we were attacked by player forces + if ( (this->usSoldierFlagMask2 & SOLDIER_TURNCOAT) && MercPtrs[ubAttacker]->bSide == 0 ) + { + this->usSoldierFlagMask2 &= ~SOLDIER_TURNCOAT; + + RemoveOneTurncoat( this->sSectorX, this->sSectorY, this->ubSoldierClass ); + } } // CJC Jan 21 99: add check to see if we are hurting an enemy in an enemy-controlled @@ -16200,7 +16208,7 @@ void SOLDIERTYPE::LooseDisguise( void ) // rehandle sight for everybody SOLDIERTYPE* pSoldier; UINT8 iLoop = gTacticalStatus.Team[OUR_TEAM].bFirstID; - for ( pSoldier = MercPtrs[iLoop]; iLoop <= gTacticalStatus.Team[LAST_TEAM].bLastID; ++iLoop, ++pSoldier ) + for ( pSoldier = MercPtrs[iLoop]; iLoop <= gTacticalStatus.Team[CIV_TEAM].bLastID; ++iLoop, ++pSoldier ) { if ( pSoldier->bActive && pSoldier->bInSector && pSoldier->stats.bLife > 0 ) { @@ -17794,6 +17802,16 @@ BOOLEAN SOLDIERTYPE::CanUseSkill( INT8 iSkill, BOOLEAN fAPCheck, INT32 sGridNo ) canuse = TRUE; break; + case SKILLS_RADIO_ACTIVATE_TURNCOATS_ALL: + if ( ( !fAPCheck || EnoughPoints( this, APBPConstants[AP_RADIO], APBPConstants[BP_RADIO], FALSE ) ) + && CanUseRadio() + && gSkillTraitValues.fCOTurncoats + && !gbWorldSectorZ + && gTacticalStatus.ubInterruptPending == DISABLED_INTERRUPT + && IsFreeSlotAvailable( MILITIA_TEAM ) ) + canuse = TRUE; + break; + case SKILLS_INTEL_CONCEAL: case SKILLS_INTEL_GATHERINTEL: // in order to conceal, we need: @@ -17855,6 +17873,52 @@ BOOLEAN SOLDIERTYPE::CanUseSkill( INT8 iSkill, BOOLEAN fAPCheck, INT32 sGridNo ) } break; + case SKILLS_CREATE_TURNCOAT: + // in order to try to create a turncoat, we need: + // - a non-profile, not-already-turncoat enemy soldier + // - enemy team not aware of us + // - valid disguise + { + SOLDIERTYPE* pSoldier = SimpleFindSoldier( sGridNo, gsInterfaceLevel ); + if ( pSoldier + && InPositionForTurncoatAttempt( pSoldier->ubID ) ) + { + canuse = TRUE; + } + } + break; + + case SKILLS_ACTIVATE_TURNCOATS: + // not during an interrupt + if ( gSkillTraitValues.fCOTurncoats + && !gbWorldSectorZ + && gTacticalStatus.ubInterruptPending == DISABLED_INTERRUPT + && IsFreeSlotAvailable( MILITIA_TEAM ) ) + { + SOLDIERTYPE* pSoldier = SimpleFindSoldier( sGridNo, gsInterfaceLevel ); + if ( pSoldier + && pSoldier->bTeam == ENEMY_TEAM + && pSoldier->ubProfile == NO_PROFILE + && ( pSoldier->usSoldierFlagMask2 & SOLDIER_TURNCOAT ) + && SOLDIER_CLASS_ENEMY( pSoldier->ubSoldierClass ) ) + { + canuse = TRUE; + } + } + break; + + case SKILLS_ACTIVATE_TURNCOATS_ALL: + // not during an interrupt + if ( gSkillTraitValues.fCOTurncoats + && !gbWorldSectorZ + && gTacticalStatus.ubInterruptPending == DISABLED_INTERRUPT + && !gSkillTraitValues.fCOTurncoats_SectorActivationRequiresRadioOperator + && IsFreeSlotAvailable( MILITIA_TEAM ) ) + { + canuse = TRUE; + } + break; + case SKILLS_SPOTTER: if ( (!fAPCheck || EnoughPoints( this, APBPConstants[AP_SPOTTER], 0, FALSE )) && CanSpot( ) ) canuse = TRUE; @@ -17928,6 +17992,10 @@ BOOLEAN SOLDIERTYPE::UseSkill( UINT8 iSkill, INT32 usMapPos, UINT32 ID ) return SwitchOffRadio( ); break; + case SKILLS_RADIO_ACTIVATE_TURNCOATS_ALL: + return RadioOrderAllTurnCoatToSwitchSides(); + break; + case SKILLS_INTEL_CONCEAL: case SKILLS_INTEL_GATHERINTEL: { @@ -17955,6 +18023,18 @@ BOOLEAN SOLDIERTYPE::UseSkill( UINT8 iSkill, INT32 usMapPos, UINT32 ID ) } break; + case SKILLS_CREATE_TURNCOAT: + AttemptToCreateTurncoat ( ID ); + break; + + case SKILLS_ACTIVATE_TURNCOATS: + OrderTurnCoatToSwitchSides( ID ); + break; + + case SKILLS_ACTIVATE_TURNCOATS_ALL: + OrderAllTurnCoatToSwitchSides(); + break; + case SKILLS_SPOTTER: return BecomeSpotter( usMapPos ); break; @@ -18001,9 +18081,9 @@ BOOLEAN SOLDIERTYPE::IsAIAllowedtoUseSkill( INT8 iSkill ) static CHAR16 skilldescarray[500]; // print a small description of the skill if we can use it, or its requirements if we cannot -STR16 SOLDIERTYPE::PrintSkillDesc( INT8 iSkill ) +STR16 SOLDIERTYPE::PrintSkillDesc( INT8 iSkill, INT32 sGridNo ) { - if ( CanUseSkill( iSkill, TRUE ) ) + if ( CanUseSkill( iSkill, TRUE, sGridNo ) ) { return pTraitSkillsMenuDescStrings[iSkill]; } @@ -18036,6 +18116,25 @@ STR16 SOLDIERTYPE::PrintSkillDesc( INT8 iSkill ) break; + case SKILLS_RADIO_ACTIVATE_TURNCOATS_ALL: + + swprintf( atStr, pTraitSkillsDenialStrings[TEXT_SKILL_DENIAL_X_TXT], gzMercSkillTextNew[RADIO_OPERATOR_NT] ); + wcscat( skilldescarray, atStr ); + + swprintf( atStr, pTraitSkillsDenialStrings[TEXT_SKILL_DENIAL_X_TXT], New113Message[MSG113_WORKING_RADIO_SET] ); + wcscat( skilldescarray, atStr ); + + swprintf( atStr, pTraitSkillsDenialStrings[TEXT_SKILL_DENIAL_NOT_DURING_INTERRUPT] ); + wcscat( skilldescarray, atStr ); + + swprintf( atStr, pTraitSkillsDenialStrings[TEXT_SKILL_DENIAL_TURNED_ENEMY] ); + wcscat( skilldescarray, atStr ); + + swprintf( atStr, pTraitSkillsDenialStrings[TEXT_SKILL_DENIAL_SURFACELEVEL] ); + wcscat( skilldescarray, atStr ); + + break; + case SKILLS_INTEL_CONCEAL: case SKILLS_INTEL_GATHERINTEL: @@ -18056,6 +18155,39 @@ STR16 SOLDIERTYPE::PrintSkillDesc( INT8 iSkill ) break; + case SKILLS_CREATE_TURNCOAT: + + swprintf( atStr, pTraitSkillsDenialStrings[TEXT_SKILL_DENIAL_ENEMY] ); + wcscat( skilldescarray, atStr ); + + swprintf( atStr, pTraitSkillsDenialStrings[TEXT_SKILL_DENIAL_NOALARM] ); + wcscat( skilldescarray, atStr ); + + swprintf( atStr, pTraitSkillsDenialStrings[TEXT_SKILL_DENIAL_DISGUISE_CIV_OR_MIL] ); + wcscat( skilldescarray, atStr ); + + swprintf( atStr, pTraitSkillsDenialStrings[TEXT_SKILL_DENIAL_SURFACELEVEL] ); + wcscat( skilldescarray, atStr ); + + swprintf( atStr, pTraitSkillsDenialStrings[TEXT_SKILL_DENIAL_STRATEGIC_SUSPICION] ); + wcscat( skilldescarray, atStr ); + + break; + + case SKILLS_ACTIVATE_TURNCOATS: + case SKILLS_ACTIVATE_TURNCOATS_ALL: + + swprintf( atStr, pTraitSkillsDenialStrings[TEXT_SKILL_DENIAL_NOT_DURING_INTERRUPT] ); + wcscat( skilldescarray, atStr ); + + swprintf( atStr, pTraitSkillsDenialStrings[TEXT_SKILL_DENIAL_TURNED_ENEMY] ); + wcscat( skilldescarray, atStr ); + + swprintf( atStr, pTraitSkillsDenialStrings[TEXT_SKILL_DENIAL_SURFACELEVEL] ); + wcscat( skilldescarray, atStr ); + + break; + case SKILLS_SPOTTER: swprintf( atStr, pTraitSkillsDenialStrings[TEXT_SKILL_DENIAL_X_AP], APBPConstants[AP_SPOTTER] ); wcscat( skilldescarray, atStr ); @@ -18836,6 +18968,21 @@ BOOLEAN SOLDIERTYPE::SwitchOffRadio( ) return TRUE; } +BOOLEAN SOLDIERTYPE::RadioOrderAllTurnCoatToSwitchSides() +{ + // not possible if already scanning + if ( !gSkillTraitValues.fCOTurncoats ) + return FALSE; + + // use the radio, this handles animation, batteries etc. + if ( !UseRadio() ) + return FALSE; + + OrderAllTurnCoatToSwitchSides(); + + return TRUE; +} + // display and error sound used either when the radio set fails or the sector is jammed - the player knows of the error, but cannot be sure of the cause void SOLDIERTYPE::RadioFail( ) @@ -20233,7 +20380,7 @@ UINT8 SOLDIERTYPE::GetUncoverRisk() // if we do this disguised as a soldier, risk will be much higer, as we are under much more scrutiny. This makes up for the increased gain in soldier disguise // less risk if we are asleep, just hiding or forced to hide UINT8 typemultiplier = ( this->usSoldierFlagMask & SOLDIER_COVERT_SOLDIER ) ? 5 : 2; - if ( ( this->bAssignment == CONCEALED ) || this->flags.fMercAsleep || this->usSkillCooldown[SOLDIER_COOLDOWN_INTEL_PENALTY] ) + if ( ( this->bAssignment == CONCEALED ) || this->flags.fMercAsleep || this->usSkillCooldown[SOLDIER_COOLDOWN_INTEL_PENALTY] > 10 ) typemultiplier = 1; // we now take the sector coolness as a measurement of how important the sector is, and thus how intel we gain @@ -20259,7 +20406,7 @@ FLOAT SOLDIERTYPE::GetIntelGain() return 0.0f; // if we're asleep, or on a penalty, we accomplish nothing - if ( this->flags.fMercAsleep || this->usSkillCooldown[SOLDIER_COOLDOWN_INTEL_PENALTY] ) + if ( this->flags.fMercAsleep || this->usSkillCooldown[SOLDIER_COOLDOWN_INTEL_PENALTY] > 10 ) return 0.0f; // the covert trait isn't that important in determining the intel gain. It is much more important in mitigating the risk of exposure, however @@ -20624,6 +20771,243 @@ UINT8 SOLDIERTYPE::GetThiefEvadeDetectionChance() return totalvalue; } +BOOLEAN SOLDIERTYPE::InPositionForTurncoatAttempt( UINT16 usID ) +{ + if ( !gSkillTraitValues.fCOTurncoats + || gbWorldSectorZ + || gTacticalStatus.Team[ENEMY_TEAM].bAwareOfOpposition ) + return FALSE; + + if ( this->stats.bLife < OKLIFE + || this->flags.fMercAsleep + || this->bCollapsed + || ( this->usSoldierFlagMask & SOLDIER_POW ) + || this->usSkillCooldown[SOLDIER_COOLDOWN_INTEL_PENALTY] > 20 + || usID == NOBODY ) + return FALSE; + + SOLDIERTYPE* pSoldier = MercPtrs[usID]; + + if ( !pSoldier + || pSoldier->bTeam != ENEMY_TEAM + || pSoldier->ubProfile != NO_PROFILE + || pSoldier->stats.bLife != pSoldier->stats.bLifeMax + || pSoldier->bCollapsed + || ( pSoldier->usSoldierFlagMask2 & SOLDIER_TURNCOAT ) + || !SOLDIER_CLASS_ENEMY( pSoldier->ubSoldierClass ) + || !SeemsLegit( pSoldier->ubID ) ) + return FALSE; + + // additional checks if we want to know wether we can target a specific location + if ( PythSpacesAway( this->sGridNo, pSoldier->sGridNo ) < 10 ) + { + INT32 val = SoldierToVirtualSoldierLineOfSightTest( this, pSoldier->sGridNo, this->pathing.bLevel, gAnimControl[this->usAnimState].ubEndHeight, FALSE, 10 ); + + // error if we cannot see the target + if ( !val ) + return FALSE; + else + return TRUE; + } + + return FALSE; +} + +UINT8 SOLDIERTYPE::GetTurncoatConvinctionChance( UINT16 usID, UINT8 usApproach ) +{ + if ( usID >= NOBODY ) + return 0; + + SOLDIERTYPE* pSoldier = MercPtrs[usID]; + + if ( !pSoldier + || pSoldier->bTeam != ENEMY_TEAM ) + return 0; + + if ( this->stats.bLife < OKLIFE ) + return 0; + + // determine effectiveness of merc + // nominally in [0; 1000] + INT32 basestatrating = 6 * EffectiveLeadership( this ) + 40 * EffectiveExpLevel( this, FALSE ); + + FLOAT recruitmodifier = ( 100 + this->GetBackgroundValue( BG_PERC_APPROACH_RECRUIT ) ) / 100.0f; + + // personality/disability modifiers + FLOAT persmodifier = 1.0f; + if ( DoesMercHaveDisability( this, NERVOUS ) ) persmodifier -= 0.10f; + + if ( gGameOptions.fNewTraitSystem ) + { + if ( DoesMercHavePersonality( this, CHAR_TRAIT_SOCIABLE ) ) persmodifier += 0.08f; + if ( DoesMercHavePersonality( this, CHAR_TRAIT_LONER ) ) persmodifier -= 0.04f; + if ( DoesMercHavePersonality( this, CHAR_TRAIT_ASSERTIVE ) ) persmodifier += 0.05f; + if ( DoesMercHavePersonality( this, CHAR_TRAIT_AGGRESSIVE ) ) persmodifier -= 0.05f; + if ( DoesMercHavePersonality( this, CHAR_TRAIT_PHLEGMATIC ) ) persmodifier -= 0.02f; + if ( DoesMercHavePersonality( this, CHAR_TRAIT_DAUNTLESS ) ) persmodifier += 0.03f; + if ( DoesMercHavePersonality( this, CHAR_TRAIT_SHOWOFF ) ) persmodifier += 0.04f; + if ( DoesMercHavePersonality( this, CHAR_TRAIT_COWARD ) ) persmodifier -= 0.07f; + } + + // nominally in [0; 100] + INT32 recruitrating = basestatrating * recruitmodifier * persmodifier * gMercProfiles[this->ubProfile].usApproachFactor[3] / 1000; + + // optional ini bonus + recruitrating += gSkillTraitValues.sCOTurncoats_PlayerConvinctionBonus; + + ReducePointsForFatigue( this, &recruitrating ); + + // determine resistance of soldier to our subversion + INT32 ubLocationModifier = 2 * max( 1, min( 20, gCoolnessBySector[SECTOR( this->sSectorX, this->sSectorY )] ) ); + + // enemy resistance is dependent on their level, class and the sector rating + INT32 enemyresistancerating = ubLocationModifier + 8 * EffectiveExpLevel( pSoldier, FALSE ); + + switch ( pSoldier->ubSoldierClass ) + { + case SOLDIER_CLASS_ADMINISTRATOR: enemyresistancerating -= 30; break; + case SOLDIER_CLASS_ELITE: enemyresistancerating += 30; break; + default: break; + } + + if ( pSoldier->usSoldierFlagMask & SOLDIER_ENEMY_OFFICER ) + enemyresistancerating += 30; + + ReducePointsForFatigue( pSoldier, &enemyresistancerating ); + + switch ( usApproach ) + { + // base approach + case 1: + break; + + // we use our looks for seduction + // thus looking attractive lowers enemy resistance, while being ugly can increase it + // however this fails if the soldier is not attracted to us + case 2: + { + // determine whether the soldier is attracted to us in the first place (don't display this, otherwise people will want to set sexual orientation and whatnot) + INT32 stat_dependant_roll = ( 37 * EffectiveStrength( pSoldier, FALSE ) + 92 * EffectiveMedical( pSoldier ) + 51 * EffectiveDexterity( pSoldier, FALSE ) + 61 * pSoldier->stats.bLife ) % 100; + bool samesexattraction = ( stat_dependant_roll < 8 ); + + bool female_player = ( this->ubBodyType == REGFEMALE ); + bool female_soldier = ( pSoldier->ubBodyType == REGFEMALE ); + + bool fittingattraction = false; + if ( female_player != female_soldier && !samesexattraction ) + fittingattraction = true; + else if ( female_player == female_soldier && samesexattraction ) + fittingattraction = true; + + if ( gMercProfiles[this->ubProfile].bAppearance == APPEARANCE_UGLY ) enemyresistancerating += 50 - ( fittingattraction ? 5 : 0 ); + else if ( gMercProfiles[this->ubProfile].bAppearance == APPEARANCE_HOMELY ) enemyresistancerating += 40 - ( fittingattraction ? 15 : 0 ); + else if ( gMercProfiles[this->ubProfile].bAppearance == APPEARANCE_AVERAGE ) enemyresistancerating += 30 - ( fittingattraction ? 30 : 0 ); + else if ( gMercProfiles[this->ubProfile].bAppearance == APPEARANCE_ATTRACTIVE ) enemyresistancerating += 20 - ( fittingattraction ? 45 : 0 ); + else if ( gMercProfiles[this->ubProfile].bAppearance == APPEARANCE_BABE ) enemyresistancerating += 10 - ( fittingattraction ? 60 : 0 ); + + // seduction works better in civilian clothing + if ( this->usSoldierFlagMask & SOLDIER_COVERT_CIV ) + enemyresistancerating -= 5; + } + break; + + // we try to bribe the soldier with money + case 3: + { + // the amount of money depends on progress and unimportant in this case + // the worse the location, the poorer the soldier, thus the more effective money is + enemyresistancerating -= 30 + (40 - ubLocationModifier); + } + break; + + // we try to bribe the soldier with intel + case 4: + { + // the amount of intel depends on progress and unimportant in this case + enemyresistancerating -= 80; + } + break; + + default: + break; + } + + if ( enemyresistancerating > recruitrating ) + return 0; + + return max( 0, min( 100, recruitrating - enemyresistancerating ) ); +} + +void SOLDIERTYPE::AttemptToCreateTurncoat( UINT16 usID ) +{ + if ( usID >= NOBODY ) + return; + + SOLDIERTYPE* pSoldier = MercPtrs[usID]; + + if ( !pSoldier + || pSoldier->bTeam != ENEMY_TEAM + || ( pSoldier->usSoldierFlagMask2 & SOLDIER_TURNCOAT ) ) + return; + + HandleTurncoatAttempt( pSoldier ); +} + +BOOLEAN SOLDIERTYPE::OrderTurnCoatToSwitchSides( UINT16 usID ) +{ + if ( usID >= NOBODY ) + return FALSE; + + SOLDIERTYPE* pSoldier = MercPtrs[usID]; + + if (!pSoldier + || pSoldier->bTeam != ENEMY_TEAM + || !( pSoldier->usSoldierFlagMask2 & SOLDIER_TURNCOAT ) ) + return FALSE; + + if ( IsFreeSlotAvailable( MILITIA_TEAM ) ) + { + // remove turncoat property + pSoldier->usSoldierFlagMask2 &= ~SOLDIER_TURNCOAT; + RemoveOneTurncoat( pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->ubSoldierClass ); + + MakeCivHostile( pSoldier ); + + return TRUE; + } + + return FALSE; +} + +void SOLDIERTYPE::OrderAllTurnCoatToSwitchSides() +{ + SOLDIERTYPE *pSoldier; + INT32 cnt = gTacticalStatus.Team[ENEMY_TEAM].bFirstID; + + // run through list + for ( pSoldier = MercPtrs[cnt]; cnt <= gTacticalStatus.Team[ENEMY_TEAM].bLastID; ++cnt, ++pSoldier ) + { + if ( pSoldier->bActive && pSoldier->bInSector ) + { + if ( pSoldier->usSoldierFlagMask2 & SOLDIER_TURNCOAT ) + { + if ( IsFreeSlotAvailable( MILITIA_TEAM ) ) + { + // remove turncoat property + pSoldier->usSoldierFlagMask2 &= ~SOLDIER_TURNCOAT; + RemoveOneTurncoat( pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->ubSoldierClass ); + + MakeCivHostile( pSoldier ); + } + else + { + return; + } + } + } + } +} + INT32 CheckBleeding( SOLDIERTYPE *pSoldier ) { INT8 bBandaged; //,savedOurTurn; diff --git a/Tactical/Soldier Control.h b/Tactical/Soldier Control.h index 1cd6fb5b6..a4d88f2b8 100644 --- a/Tactical/Soldier Control.h +++ b/Tactical/Soldier Control.h @@ -426,6 +426,7 @@ enum #define SOLDIER_DRAG_SOUND 0x00080000 // played sound when started dragging #define SOLDIER_SPENT_AP 0x00100000 // soldier has spent some AP this turn (including realtime) +#define SOLDIER_TURNCOAT 0x00200000 // this enemy soldier will switch to the militia team if ordered to #define SOLDIER_INTERROGATE_ALL 0x000001F8 // all interrogation flags // ---------------------------------------------------------------- @@ -597,13 +598,17 @@ enum{ SKILLS_RADIO_LISTEN, SKILLS_RADIO_CALLREINFORCEMENTS, SKILLS_RADIO_TURNOFF, - SKILLS_RADIO_LAST = SKILLS_RADIO_TURNOFF, + SKILLS_RADIO_ACTIVATE_TURNCOATS_ALL, // order all enemy turncoats to turn into militia right now (in case this requires a radio) + SKILLS_RADIO_LAST = SKILLS_RADIO_ACTIVATE_TURNCOATS_ALL, // spy SKILLS_INTEL_FIRST, SKILLS_INTEL_CONCEAL = SKILLS_INTEL_FIRST, // assignment: spy hides among the population SKILLS_INTEL_GATHERINTEL, // assignment: spy gathers information while disguised - SKILLS_INTEL_LAST = SKILLS_INTEL_GATHERINTEL, + SKILLS_CREATE_TURNCOAT, + SKILLS_ACTIVATE_TURNCOATS, // order enemy turncoat to turn into militia right now + SKILLS_ACTIVATE_TURNCOATS_ALL, // order all enemy turncoats to turn into militia right now + SKILLS_INTEL_LAST = SKILLS_ACTIVATE_TURNCOATS_ALL, // various SKILLS_VARIOUS_FIRST, @@ -1915,7 +1920,7 @@ public: BOOLEAN IsAIAllowedtoUseSkill( INT8 iSkill ); // print a small description of the skill if we can use it, or its requirements if we cannot - STR16 PrintSkillDesc( INT8 iSkill); + STR16 PrintSkillDesc( INT8 iSkill, INT32 sGridNo = -1 ); // Flugente: functions for the radio operator trait BOOLEAN CanUseRadio(BOOLEAN fCheckForAP = TRUE); // can we use radio, if we even have one? @@ -1932,6 +1937,7 @@ public: BOOLEAN RadioListen(); BOOLEAN RadioCallReinforcements( UINT32 usSector, UINT16 sNumber ); BOOLEAN SwitchOffRadio(); + BOOLEAN RadioOrderAllTurnCoatToSwitchSides(); void RadioFail(); // display and error sound used either when the radio set fails or the sector is jammed - the player knows of the error, but cannot be sure of the cause // Flugente: spotter @@ -2025,6 +2031,13 @@ public: // Flugente: those with the background occasionally steal money from the locals UINT8 GetThiefStealMoneyChance(); UINT8 GetThiefEvadeDetectionChance(); + + // Flugente: turncoats + BOOLEAN InPositionForTurncoatAttempt( UINT16 usID ); + UINT8 GetTurncoatConvinctionChance( UINT16 usID, UINT8 usApproach ); + void AttemptToCreateTurncoat( UINT16 usID ); + BOOLEAN OrderTurnCoatToSwitchSides( UINT16 usID ); + void OrderAllTurnCoatToSwitchSides(); ////////////////////////////////////////////////////////////////////////////// }; // SOLDIERTYPE; diff --git a/Tactical/Soldier Create.cpp b/Tactical/Soldier Create.cpp index b4ab3a565..f69670814 100644 --- a/Tactical/Soldier Create.cpp +++ b/Tactical/Soldier Create.cpp @@ -1864,6 +1864,15 @@ BOOLEAN TacticalCopySoldierFromCreateStruct( SOLDIERTYPE *pSoldier, SOLDIERCREAT } } } + + // Flugente: turncoats + if ( gSkillTraitValues.fCOTurncoats && pCreateStruct->bTeam == ENEMY_TEAM && SOLDIER_CLASS_ENEMY( pSoldier->ubSoldierClass ) ) + { + if ( NumSoldiersofClassWithFlag2InSector( pCreateStruct->bTeam, pSoldier->ubSoldierClass, SOLDIER_TURNCOAT ) < NumTurncoatsOfClassInSector( gWorldSectorX, gWorldSectorY, pSoldier->ubSoldierClass ) ) + { + pSoldier->usSoldierFlagMask2 |= SOLDIER_TURNCOAT; + } + } // SANDRO - If neither of these two options are activated, use the original code // however, "no traits" means no traits at all, so commented out diff --git a/Tactical/Soldier Profile.cpp b/Tactical/Soldier Profile.cpp index 2941f94c7..fdde2bdc6 100644 --- a/Tactical/Soldier Profile.cpp +++ b/Tactical/Soldier Profile.cpp @@ -1587,6 +1587,11 @@ SOLDIERTYPE *ChangeSoldierTeam( SOLDIERTYPE *pSoldier, UINT8 ubTeam ) MercCreateStruct.fPlayerMerc = TRUE; } + if ( ubTeam == MILITIA_TEAM ) + { + MercCreateStruct.ubSoldierClass = pSoldier->ubSoldierClass; + } + if ( TacticalCreateSoldier( &MercCreateStruct, &ubID ) ) { pNewSoldier = MercPtrs[ ubID ]; diff --git a/Utils/Text.h b/Utils/Text.h index 3091f35c8..659c2ce70 100644 --- a/Utils/Text.h +++ b/Utils/Text.h @@ -1147,6 +1147,7 @@ enum IMPROVEGEARPICKUPMAG_STR, DISTRACT_STOP_STR, + DISTRACT_TRY_TO_TURNCOAT, TEXT_NUM_TACTICAL_STR }; @@ -2673,6 +2674,11 @@ enum TEXT_SKILL_DENIAL_SINGLEMERC, TEXT_SKILL_DENIAL_NOALARM, TEXT_SKILL_DENIAL_DISGUISE_CIV_OR_MIL, + TEXT_SKILL_DENIAL_NOT_DURING_INTERRUPT, + TEXT_SKILL_DENIAL_TURNED_ENEMY, + TEXT_SKILL_DENIAL_ENEMY, + TEXT_SKILL_DENIAL_SURFACELEVEL, + TEXT_SKILL_DENIAL_STRATEGIC_SUSPICION, TEXT_SKILL_DENIAL_MAX, }; @@ -3093,6 +3099,8 @@ extern STR16 szMilitiaText[]; extern STR16 szFactoryText[]; +extern STR16 szTurncoatText[]; + #define TACTICAL_INVENTORY_DIALOG_NUM 16 #define TACTICAL_COVER_DIALOG_NUM 16 diff --git a/Utils/_ChineseText.cpp b/Utils/_ChineseText.cpp index e71a67800..5e0e1d254 100644 --- a/Utils/_ChineseText.cpp +++ b/Utils/_ChineseText.cpp @@ -11597,6 +11597,21 @@ STR16 szFactoryText[] = // TODO.Translate L"h/item", }; +STR16 szTurncoatText[] = +{ + L"%s now secretly works for us!", + L"%s is not swayed by our offer. Suspicion against us rises...", + L"Suspicion against us is high. We should stop trying to turn more soldiers to our side and lay low for a while.", + L"Recruit approach (%d)", + L"Use seduction (%d)", + + L"Bribe ($%d) (%d)", // 5 + L"Offer %d intel (%d)", + L"How to convince the soldier to join your forces?", + L"Do it", + L"%d turncoats present", +}; + // WANNE: Some Chinese specific strings that needs to be in unicode! STR16 ChineseSpecString1 = L"%%"; //defined in _ChineseText.cpp as this file is already unicode STR16 ChineseSpecString2 = L"*%3d%%%%"; //defined in _ChineseText.cpp as this file is already unicode diff --git a/Utils/_DutchText.cpp b/Utils/_DutchText.cpp index 5d61840f1..95f484fa3 100644 --- a/Utils/_DutchText.cpp +++ b/Utils/_DutchText.cpp @@ -11613,4 +11613,19 @@ STR16 szFactoryText[] = // TODO.Translate L"h/item", }; +STR16 szTurncoatText[] = +{ + L"%s now secretly works for us!", + L"%s is not swayed by our offer. Suspicion against us rises...", + L"Suspicion against us is high. We should stop trying to turn more soldiers to our side and lay low for a while.", + L"Recruit approach (%d)", + L"Use seduction (%d)", + + L"Bribe ($%d) (%d)", // 5 + L"Offer %d intel (%d)", + L"How to convince the soldier to join your forces?", + L"Do it", + L"%d turncoats present", +}; + #endif //DUTCH diff --git a/Utils/_EnglishText.cpp b/Utils/_EnglishText.cpp index 8436cce74..eebcb3de7 100644 --- a/Utils/_EnglishText.cpp +++ b/Utils/_EnglishText.cpp @@ -2814,10 +2814,14 @@ STR16 pTraitSkillsMenuStrings[] = L"Eavesdrop", L"Call reinforcements", L"Switch off radio set", + L"Radio: Activate all turncoats", // spy L"Hide assignment", L"Get Intel assignment", + L"Recruit turncoat", + L"Activate turncoat", + L"Activate all turncoats", // various L"Spotter", @@ -2835,10 +2839,14 @@ STR16 pTraitSkillsMenuDescStrings[] = L"Use your radio equipment to continously listen for enemy movement.", L"Call in reinforcements from neighbouring sectors.", L"Turn off radio set.", + L"Order all previously turned soldiers in the sector to betray their comrades and join you.", // spy L"Assignment: hide among the population.", L"Assignment: hide among the population and gather intel.", + L"Try to turn an enemy into a turncoat.", + L"Order previously turned soldier to betray their comrades and join you.", + L"Order all previously turned soldiers in the sector to betray their comrades and join you.", // various L"Observe an area, granting allied snipers a bonus to cth on anything you see.", @@ -2867,6 +2875,11 @@ STR16 pTraitSkillsDenialStrings[] = L" - single merc\n", L" - no alarm raised\n", L" - civilian or soldier disguise\n", + L" - being our turn\n", + L" - turned enemy soldier\n", + L" - enemy soldier\n", + L" - surface sector\n", + L" - not being under suspicion\n", }; STR16 pSkillMenuStrings[] = @@ -4075,6 +4088,7 @@ CHAR16 TacticalStr[][ MED_STRING_LENGTH ] = L"%s picked up %s", L"%s has stopped chatting with %s", + L"Attempt to turn", }; //Varying helptext explains (for the "Go to Sector/Map" checkbox) what will happen given different circumstances in the "exiting sector" interface. @@ -11260,6 +11274,7 @@ STR16 szIndividualMilitiaBattleReportText[] = L"Hired from Kerberus on Day %d, %d:%02d in %s", L"Defected to us on Day %d, %d:%02d in %s", L"Contract terminated on Day %d, %d:%02d", + L"Defected to us on Day %d, %d:%02d in %s", }; STR16 szIndividualMilitiaTraitRequirements[] = @@ -11597,4 +11612,19 @@ STR16 szFactoryText[] = L"h/item", }; +STR16 szTurncoatText[] = +{ + L"%s now secretly works for us!", + L"%s is not swayed by our offer. Suspicion against us rises...", + L"Suspicion against us is high. We should stop trying to turn more soldiers to our side and lay low for a while.", + L"Recruit approach (%d)", + L"Use seduction (%d)", + + L"Bribe ($%d) (%d)", // 5 + L"Offer %d intel (%d)", + L"How to convince the soldier to join your forces?", + L"Do it", + L"%d turncoats present", +}; + #endif //ENGLISH diff --git a/Utils/_FrenchText.cpp b/Utils/_FrenchText.cpp index 72b70b1fb..1d00bfe4a 100644 --- a/Utils/_FrenchText.cpp +++ b/Utils/_FrenchText.cpp @@ -11595,4 +11595,19 @@ STR16 szFactoryText[] = // TODO.Translate L"h/item", }; +STR16 szTurncoatText[] = +{ + L"%s now secretly works for us!", + L"%s is not swayed by our offer. Suspicion against us rises...", + L"Suspicion against us is high. We should stop trying to turn more soldiers to our side and lay low for a while.", + L"Recruit approach (%d)", + L"Use seduction (%d)", + + L"Bribe ($%d) (%d)", // 5 + L"Offer %d intel (%d)", + L"How to convince the soldier to join your forces?", + L"Do it", + L"%d turncoats present", +}; + #endif //FRENCH diff --git a/Utils/_GermanText.cpp b/Utils/_GermanText.cpp index c0bf3cd42..898411329 100644 --- a/Utils/_GermanText.cpp +++ b/Utils/_GermanText.cpp @@ -11425,4 +11425,19 @@ STR16 szFactoryText[] = // TODO.Translate L"h/item", }; +STR16 szTurncoatText[] = +{ + L"%s now secretly works for us!", + L"%s is not swayed by our offer. Suspicion against us rises...", + L"Suspicion against us is high. We should stop trying to turn more soldiers to our side and lay low for a while.", + L"Recruit approach (%d)", + L"Use seduction (%d)", + + L"Bribe ($%d) (%d)", // 5 + L"Offer %d intel (%d)", + L"How to convince the soldier to join your forces?", + L"Do it", + L"%d turncoats present", +}; + #endif //GERMAN diff --git a/Utils/_ItalianText.cpp b/Utils/_ItalianText.cpp index 495a6b004..0ac089b11 100644 --- a/Utils/_ItalianText.cpp +++ b/Utils/_ItalianText.cpp @@ -11604,4 +11604,19 @@ STR16 szFactoryText[] = // TODO.Translate L"h/item", }; +STR16 szTurncoatText[] = +{ + L"%s now secretly works for us!", + L"%s is not swayed by our offer. Suspicion against us rises...", + L"Suspicion against us is high. We should stop trying to turn more soldiers to our side and lay low for a while.", + L"Recruit approach (%d)", + L"Use seduction (%d)", + + L"Bribe ($%d) (%d)", // 5 + L"Offer %d intel (%d)", + L"How to convince the soldier to join your forces?", + L"Do it", + L"%d turncoats present", +}; + #endif //ITALIAN diff --git a/Utils/_Ja25ChineseText.cpp b/Utils/_Ja25ChineseText.cpp index c4ffda509..37753a3fb 100644 --- a/Utils/_Ja25ChineseText.cpp +++ b/Utils/_Ja25ChineseText.cpp @@ -259,6 +259,7 @@ STR16 gzIMPMajorTraitsHelpTextsCovertOps[]= L"伪装状态下使用近战武器攻击时,命中率增加 +%d%s \n",//L"+%d%s CTH with covert melee weapons\n", L"伪装状态下使用近战武器攻击时,秒杀几率增加 +%d%s \n",//L"+%d%s chance of instakill with covert melee weapons\n", L"伪装动作消耗的行动点 -%d%s \n",//L"Disguise AP cost lowered by %d%s.\n", + L"Can convince enemy soldiers to secretly change sides.\n", // TODO.Translate }; STR16 gzIMPMajorTraitsHelpTextsRadioOperator[]= diff --git a/Utils/_Ja25DutchText.cpp b/Utils/_Ja25DutchText.cpp index 317c6de07..9acac57f8 100644 --- a/Utils/_Ja25DutchText.cpp +++ b/Utils/_Ja25DutchText.cpp @@ -259,6 +259,7 @@ STR16 gzIMPMajorTraitsHelpTextsCovertOps[]= L"+%d%s CtH with covert melee weapons\n", L"+%d%s chance of instakill with covert melee weapons\n", L"Disguise AP cost lowered by %d%s.\n", + L"Can convince enemy soldiers to secretly change sides.\n", // TODO.Translate }; STR16 gzIMPMajorTraitsHelpTextsRadioOperator[]= // TODO.Translate diff --git a/Utils/_Ja25EnglishText.cpp b/Utils/_Ja25EnglishText.cpp index 6be61db7d..3bc1b9268 100644 --- a/Utils/_Ja25EnglishText.cpp +++ b/Utils/_Ja25EnglishText.cpp @@ -259,6 +259,7 @@ STR16 gzIMPMajorTraitsHelpTextsCovertOps[]= L"+%d%s CtH with covert melee weapons\n", L"+%d%s chance of instakill with covert melee weapons\n", L"Disguise AP cost lowered by %d%s.\n", + L"Can convince enemy soldiers to secretly change sides.\n", }; STR16 gzIMPMajorTraitsHelpTextsRadioOperator[]= diff --git a/Utils/_Ja25FrenchText.cpp b/Utils/_Ja25FrenchText.cpp index 646c5f7ca..2023a68af 100644 --- a/Utils/_Ja25FrenchText.cpp +++ b/Utils/_Ja25FrenchText.cpp @@ -259,6 +259,7 @@ STR16 gzIMPMajorTraitsHelpTextsCovertOps[]= L"+%d%s de chance de toucher avec une arme d'espion\n", L"+%d%s de chance de tuer un ennemi instantanément avec une arme d'espion\n", L"Le nombre de PA pour se déguiser, est réduit de %d%s\n", + L"Can convince enemy soldiers to secretly change sides.\n", // TODO.Translate }; STR16 gzIMPMajorTraitsHelpTextsRadioOperator[]= diff --git a/Utils/_Ja25GermanText.cpp b/Utils/_Ja25GermanText.cpp index e8d5398ac..a8e31c396 100644 --- a/Utils/_Ja25GermanText.cpp +++ b/Utils/_Ja25GermanText.cpp @@ -259,6 +259,7 @@ STR16 gzIMPMajorTraitsHelpTextsCovertOps[]= L"+%d%s Trefferwahrscheinlichkeit mit verdeckten Nahkampfwaffen (z.B. Garotte)\n", L"+%d%s Wahrscheinlichkeit für einen tödlichen Angriff mit verdeckten Nahkampfwaffen (z.B. Garotte)\n", L"Um %d%s verringerte AP-Kosten zum Verkleiden\n", + L"Kann feindliche Soldaten zum Überlaufen überreden.\n", }; STR16 gzIMPMajorTraitsHelpTextsRadioOperator[]= diff --git a/Utils/_Ja25ItalianText.cpp b/Utils/_Ja25ItalianText.cpp index 227f7e3da..29209cd40 100644 --- a/Utils/_Ja25ItalianText.cpp +++ b/Utils/_Ja25ItalianText.cpp @@ -257,6 +257,7 @@ STR16 gzIMPMajorTraitsHelpTextsCovertOps[]= L"+%d%s CtH with covert melee weapons\n", L"+%d%s chance of instakill with covert melee weapons\n", L"Disguise AP cost lowered by %d%s.\n", + L"Can convince enemy soldiers to secretly change sides.\n", // TODO.Translate }; STR16 gzIMPMajorTraitsHelpTextsRadioOperator[]= // TODO.Translate diff --git a/Utils/_Ja25PolishText.cpp b/Utils/_Ja25PolishText.cpp index ab8de9f73..62855b540 100644 --- a/Utils/_Ja25PolishText.cpp +++ b/Utils/_Ja25PolishText.cpp @@ -259,6 +259,7 @@ STR16 gzIMPMajorTraitsHelpTextsCovertOps[]= L"+%d%s CtH with covert melee weapons\n", L"+%d%s chance of instakill with covert melee weapons\n", L"Disguise AP cost lowered by %d%s.\n", + L"Can convince enemy soldiers to secretly change sides.\n", // TODO.Translate }; STR16 gzIMPMajorTraitsHelpTextsRadioOperator[]= // TODO.Translate diff --git a/Utils/_Ja25RussianText.cpp b/Utils/_Ja25RussianText.cpp index 1afa94c52..1164df683 100644 --- a/Utils/_Ja25RussianText.cpp +++ b/Utils/_Ja25RussianText.cpp @@ -259,6 +259,7 @@ STR16 gzIMPMajorTraitsHelpTextsCovertOps[]= L"+%d%s к шансу попадания скрытным оружием ближнего боя.\n", L"+%d%s к шансу на смертельный удар скрытным оружием ближнего боя.\n", L"ОД на переодевание снижены на %d%s.\n", + L"Can convince enemy soldiers to secretly change sides.\n", // TODO.Translate }; STR16 gzIMPMajorTraitsHelpTextsRadioOperator[]= diff --git a/Utils/_PolishText.cpp b/Utils/_PolishText.cpp index bdb14ee11..8d3c406cf 100644 --- a/Utils/_PolishText.cpp +++ b/Utils/_PolishText.cpp @@ -11617,4 +11617,19 @@ STR16 szFactoryText[] = // TODO.Translate L"h/item", }; +STR16 szTurncoatText[] = +{ + L"%s now secretly works for us!", + L"%s is not swayed by our offer. Suspicion against us rises...", + L"Suspicion against us is high. We should stop trying to turn more soldiers to our side and lay low for a while.", + L"Recruit approach (%d)", + L"Use seduction (%d)", + + L"Bribe ($%d) (%d)", // 5 + L"Offer %d intel (%d)", + L"How to convince the soldier to join your forces?", + L"Do it", + L"%d turncoats present", +}; + #endif //POLISH diff --git a/Utils/_RussianText.cpp b/Utils/_RussianText.cpp index fa191c3d0..437cbbf21 100644 --- a/Utils/_RussianText.cpp +++ b/Utils/_RussianText.cpp @@ -11597,4 +11597,19 @@ STR16 szFactoryText[] = // TODO.Translate L"h/item", }; +STR16 szTurncoatText[] = +{ + L"%s now secretly works for us!", + L"%s is not swayed by our offer. Suspicion against us rises...", + L"Suspicion against us is high. We should stop trying to turn more soldiers to our side and lay low for a while.", + L"Recruit approach (%d)", + L"Use seduction (%d)", + + L"Bribe ($%d) (%d)", // 5 + L"Offer %d intel (%d)", + L"How to convince the soldier to join your forces?", + L"Do it", + L"%d turncoats present", +}; + #endif //RUSSIAN