New feature: turncoats can be created from enemies and ordered to switch sides

Requires GameDir >= r2535

For more info, see http://thepit.ja-galaxy-forum.com/index.php?t=msg&goto=360191&#msg_360191

git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@8811 3b4a5df2-a311-0410-b5c6-a8a6f20db521
This commit is contained in:
Flugente
2020-05-30 00:13:42 +00:00
parent 0f13599775
commit c5022fa1e5
44 changed files with 1587 additions and 140 deletions
+389 -5
View File
@@ -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;