- New Feature: enemy assassins disguise as militia and can ambush player mercs

- Fix: invalid GridNo for mounting weapons

git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@5753 3b4a5df2-a311-0410-b5c6-a8a6f20db521
This commit is contained in:
Flugente
2012-12-30 19:20:36 +00:00
parent 3e859f91a8
commit e193f05865
31 changed files with 600 additions and 139 deletions
+17
View File
@@ -953,6 +953,23 @@ void StartCivQuote( SOLDIERTYPE *pCiv )
}
// Flugente: if we are an assassin, we speak like the militia we emulate
if ( pCiv->bSoldierFlagMask & SOLDIER_ASSASSIN )
{
switch ( pCiv->GetUniformType() )
{
case UNIFORM_MILITIA_REGULAR:
ubCivQuoteID = CIV_QUOTE_MEDIUM_MILITIA;
break;
case UNIFORM_MILITIA_ELITE:
ubCivQuoteID = CIV_QUOTE_ELITE_MILITIA;
break;
default:
ubCivQuoteID = CIV_QUOTE_GREEN_MILITIA;
break;
}
}
// Determine location...
// Get location of civ on screen.....
GetSoldierScreenPos( pCiv, &sScreenX, &sScreenY );
+1 -1
View File
@@ -2125,7 +2125,7 @@ void HandleNPCDoAction( UINT8 ubTargetNPC, UINT16 usActionCode, UINT8 ubQuoteNum
{
//MakeCivHostile( pSoldier, 2 );
}
if (pSoldier->ubProfile != NO_PROFILE && pSoldier->stats.bLife >= OKLIFE)
if ( ( pSoldier->ubProfile != NO_PROFILE || pSoldier->IsAssassin() ) && pSoldier->stats.bLife >= OKLIFE )
{
// trigger quote!
//TriggerNPCWithIHateYouQuote( pSoldier->ubProfile );
+1 -1
View File
@@ -358,7 +358,7 @@ enum
UNNAMED_CIV_GROUP_17,
UNNAMED_CIV_GROUP_18,
UNNAMED_CIV_GROUP_19,
UNNAMED_CIV_GROUP_20,
ASSASSIN_CIV_GROUP, // Flugente: enemy assassins belong to this group
UNNAMED_CIV_GROUP_21,
UNNAMED_CIV_GROUP_22,
UNNAMED_CIV_GROUP_23,
+12 -26
View File
@@ -3066,35 +3066,21 @@ BOOLEAN HandleAtNewGridNo( SOLDIERTYPE *pSoldier, BOOLEAN *pfKeepMoving )
}
}
else if ( pSoldier->bTeam == CIV_TEAM && pSoldier->ubProfile != NO_PROFILE && pSoldier->aiData.bNeutral )
else if ( pSoldier->bTeam == CIV_TEAM && pSoldier->IsAssassin() && pSoldier->aiData.bNeutral )
{
switch( pSoldier->ubProfile )
{
case JIM:
case JACK:
case OLAF:
case RAY:
case OLGA:
case TYRONE:
{
INT32 sDesiredMercDist;
INT32 sDesiredMercDist;
if (!TileIsOutOfBounds(ClosestPC( pSoldier, &sDesiredMercDist )))
{
if ( sDesiredMercDist <= NPC_TALK_RADIUS * 2 )
{
// stop
CancelAIAction( pSoldier, TRUE );
// aaaaaaaaaaaaaaaaaaaaatttaaaack!!!!
AddToShouldBecomeHostileOrSayQuoteList( pSoldier->ubID );
//MakeCivHostile( pSoldier, 2 );
//TriggerNPCWithIHateYouQuote( pSoldier->ubProfile );
}
}
if (!TileIsOutOfBounds(ClosestPC( pSoldier, &sDesiredMercDist )))
{
if ( sDesiredMercDist <= NPC_TALK_RADIUS * 2 )
{
// stop
CancelAIAction( pSoldier, TRUE );
// aaaaaaaaaaaaaaaaaaaaatttaaaack!!!!
AddToShouldBecomeHostileOrSayQuoteList( pSoldier->ubID );
//MakeCivHostile( pSoldier, 2 );
//TriggerNPCWithIHateYouQuote( pSoldier->ubProfile );
}
break;
default:
break;
}
}
+1 -1
View File
@@ -2048,7 +2048,7 @@ void TakeCorpse( SOLDIERTYPE *pSoldier, INT32 sGridNo, INT8 bLevel )
else if ( pCorpse->def.ubBodyType == REGFEMALE || pCorpse->def.ubBodyType == MINICIV || pCorpse->def.ubBodyType == DRESSCIV )
gTempObject[0]->data.sObjectFlag |= CORPSE_F;
if ( pCorpse->def.usFlags & ROTTING_CORPSE_HEAD_TAKEN )
if ( pCorpse->def.usFlags & ROTTING_CORPSE_HEAD_TAKEN || pCorpse->def.fHeadTaken )
gTempObject[0]->data.sObjectFlag |= CORPSE_NO_HEAD;
if ( pCorpse->def.usFlags & ROTTING_CORPSE_NO_VEST )
+50 -23
View File
@@ -13828,6 +13828,10 @@ BOOLEAN SOLDIERTYPE::IsWeaponMounted( void )
if ( this->pathing.bLevel == 1 )
return( FALSE );
// this is odd - invalid GridNo... well, not mounted then
if ( TileIsOutOfBounds(this->sGridNo) )
return( FALSE );
// we determine the height of the next tile in our direction. Because of the way structures are handled, we sometimes have to take the very tile we're occupying right now
INT32 nextGridNoinSight = this->sGridNo;
if ( this->ubDirection == NORTH || this->ubDirection == SOUTHWEST || this->ubDirection == WEST || this->ubDirection == NORTHWEST )
@@ -14718,17 +14722,23 @@ BOOLEAN SOLDIERTYPE::EquipmentTooGood( BOOLEAN fCloselook )
INT8 uniformtype = GetUniformType();
// adjust max coolness depending on uniform
// enemy spies get a small bonus here
switch ( uniformtype )
{
case UNIFORM_ENEMY_ADMIN:
maxcoolnessallowed += 1;
break;
case UNIFORM_ENEMY_TROOP:
case UNIFORM_MILITIA_ROOKIE:
maxcoolnessallowed += 2;
break;
case UNIFORM_ENEMY_ELITE:
case UNIFORM_MILITIA_REGULAR:
maxcoolnessallowed += 3;
break;
case UNIFORM_MILITIA_ELITE:
maxcoolnessallowed += 4;
break;
default:
// we do not wear a proper army uniform, uncover us. Note: This should never happen - if this message shows, somewhere, something is wrong
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, szCovertTextStr[STR_COVERT_UNIFORM_NOORDER], this->name );
@@ -15108,16 +15118,17 @@ BOOLEAN SOLDIERTYPE::SeemsLegit( UINT8 ubObserverID )
// are we targeting a buddy of our observer?
if ( this->ubTargetID && MercPtrs[this->ubTargetID] && MercPtrs[this->ubTargetID]->bTeam == pSoldier->bTeam )
{
// if we aiming at a soldier, others will notice our intent... not covert!
// if we are aiming at a soldier, others will notice our intent... not covert!
if ( WeaponReady(this) )
{
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, szCovertTextStr[STR_COVERT_TARGETTING_SOLDIER], this->name );
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, szCovertTextStr[STR_COVERT_TARGETTING_SOLDIER], this->name, MercPtrs[this->ubTargetID]->name );
return FALSE;
}
}
// even as a soldier, we will be caught around fresh corpses
if (distance < gSkillTraitValues.sCOCloseDetectionRangeSoldierCorpse)
// assassins will not be uncovered around corpses, as the AI cannot willingly evade them... one could 'ward' against assassins by surrounding yourself with fresh corpses
if (distance < gSkillTraitValues.sCOCloseDetectionRangeSoldierCorpse && !(this->bSoldierFlagMask & SOLDIER_ASSASSIN) )
{
// check wether we are around a fresh corpse - this will make us much more suspicious
// I deem this necessary, to avoid cheap exploits by nefarious players :-)
@@ -15208,6 +15219,11 @@ BOOLEAN SOLDIERTYPE::RecognizeAsCombatant(UINT8 ubTargetID)
// we have uncovered a spy! Get alerted, if we aren't already
if ( this->aiData.bAlertStatus < STATUS_BLACK )
this->aiData.bAlertStatus = STATUS_BLACK;
// reset our sight of this guy
this->aiData.bOppList[pSoldier->ubID] = NOT_HEARD_OR_SEEN;
ManSeesMan(this, pSoldier, pSoldier->sGridNo, pSoldier->pathing.bLevel, 0, 0);
}
return TRUE;
@@ -15216,11 +15232,22 @@ BOOLEAN SOLDIERTYPE::RecognizeAsCombatant(UINT8 ubTargetID)
return FALSE;
}
// dress up in our natural state
// loose covert property
void SOLDIERTYPE::LooseDisguise( void )
{
// loose any covert flags
this->bSoldierFlagMask &= ~(SOLDIER_COVERT_CIV|SOLDIER_COVERT_SOLDIER);
// 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 )
{
if ( pSoldier->bActive && pSoldier->bInSector && pSoldier->stats.bLife > 0 )
{
RecalculateOppCntsDueToNoLongerNeutral( pSoldier );
}
}
}
// undisguise or take off any clothes item and switch back to original clothes
@@ -15327,7 +15354,7 @@ BOOLEAN SOLDIERTYPE::CanProcessPrisoners()
if ( !prisonhere )
return FALSE;
// Are there any prisoners in this prison?
// Are there any prisoners in this prison? note that there are no underground prisons
if ( !this->bSectorZ )
{
SECTORINFO *pSectorInfo = &( SectorInfo[ SECTOR( this->sSectorX, this->sSectorY ) ] );
@@ -15335,14 +15362,6 @@ BOOLEAN SOLDIERTYPE::CanProcessPrisoners()
if ( pSectorInfo->uiNumberOfPrisonersOfWar > 0 )
return TRUE;
}
// no underground prisons anyway
/*else
{
UNDERGROUND_SECTORINFO *pSectorInfo = FindUnderGroundSector( gWorldSectorX, gWorldSectorY, gbWorldSectorZ );
if ( pSectorInfo->uiNumberOfPrisonersOfWar > 0 )
return TRUE;
}*/
return FALSE;
}
@@ -15383,6 +15402,20 @@ BOOLEAN SOLDIERTYPE::UsesScubaGear()
return TRUE;
}
// Flugente: are we an assassin?
BOOLEAN SOLDIERTYPE::IsAssassin()
{
// kingpin's hitmen are assassins
if ( this->ubProfile >= JIM && this->ubProfile <= TYRONE )
return TRUE;
// there can be non-NPC assassins too
if ( this->bSoldierFlagMask & SOLDIER_ASSASSIN )
return TRUE;
return FALSE;
}
INT32 CheckBleeding( SOLDIERTYPE *pSoldier )
{
INT8 bBandaged; //,savedOurTurn;
@@ -16731,23 +16764,17 @@ BOOLEAN SOLDIERTYPE::PlayerSoldierStartTalking( UINT8 ubTargetID, BOOLEAN fValid
}
else if (pTSoldier->aiData.bNeutral)
{
switch( pTSoldier->ubProfile )
if ( pTSoldier->IsAssassin() )
{
case JIM:
case JACK:
case OLAF:
case RAY:
case OLGA:
case TYRONE:
// Start combat etc
DeleteTalkingMenu();
CancelAIAction( pTSoldier, TRUE );
AddToShouldBecomeHostileOrSayQuoteList( pTSoldier->ubID );
break;
default:
}
else
{
// Start talking!
return( InitiateConversation( pTSoldier, this, NPC_INITIAL_QUOTE, 0 ) );
break;
}
}
else
+6 -3
View File
@@ -358,9 +358,9 @@ enum
#define SOLDIER_DAMAGED_PANTS 0x00000100 //256 // Soldier's vest is damaged (and thus can't be taken off)
#define SOLDIER_HEADSHOT 0x00000200 //512 // last hit received was a headshot (attack to the head, so knifes/punches also work)
#define SOLDIER_POW 0x00000400 //1024 // we are a prisoner of war
/*#define ENEMY_NET_4_LVL_3 0x00000800 //2048
#define SOLDIER_ASSASSIN 0x00000800 //2048 // we are an enemy assassin, and thus we will behave very different from normal enemies
#define ENEMY_NET_1_LVL_4 0x00001000 //4096
/*#define ENEMY_NET_1_LVL_4 0x00001000 //4096
#define ENEMY_NET_2_LVL_4 0x00002000 //8192
#define ENEMY_NET_3_LVL_4 0x00004000 //16384
#define ENEMY_NET_4_LVL_4 0x00008000 //32768
@@ -1494,7 +1494,7 @@ public:
// do we recognize someone else as a combatant?
BOOLEAN RecognizeAsCombatant(UINT8 ubTargetID);
// loose disguise, dress up in our natural state
// loose covert property
void LooseDisguise( void );
// lose disguise or take off any clothes item and switch back to original clothes
@@ -1506,6 +1506,9 @@ public:
// Flugente: scuba gear
BOOLEAN UsesScubaGear();
// Flugente: are we an assassin?
BOOLEAN IsAssassin();
//////////////////////////////////////////////////////////////////////////////
}; // SOLDIERTYPE;
+124 -3
View File
@@ -1249,8 +1249,8 @@ BOOLEAN TacticalCopySoldierFromProfile( SOLDIERTYPE *pSoldier, SOLDIERCREATE_STR
//}
}
// Flugente: if playing with the covert trait, the assasins come covert, so they are tougher to find
if ( gGameExternalOptions.fAssassinsAreDisguised && gGameOptions.fNewTraitSystem && pSoldier->ubProfile >= JIM && pSoldier->ubProfile <= TYRONE )
// Flugente: if playing with the covert trait, the assassins come covert, so they are tougher to find
if ( gGameExternalOptions.fAssassinsAreDisguised && gGameOptions.fNewTraitSystem && pSoldier->IsAssassin() )
{
pSoldier->bSoldierFlagMask |= (SOLDIER_COVERT_SOLDIER|SOLDIER_COVERT_NPC_SPECIAL|SOLDIER_NEW_VEST|SOLDIER_NEW_PANTS);
@@ -3015,6 +3015,127 @@ SOLDIERTYPE* TacticalCreateCreature( INT8 bCreatureBodyType )
return TacticalCreateSoldier( &pp, &ubID );
}
// Flugente: assassins are elite soldiers of the civ team that go hostile on a certain event, otherwsie they jsut blend in
SOLDIERTYPE* TacticalCreateEnemyAssassin(UINT8 disguisetype)
{
BASIC_SOLDIERCREATE_STRUCT bp;
SOLDIERCREATE_STRUCT pp;
UINT8 ubID;
SOLDIERTYPE * pSoldier;
// this needs the covert ops trait, and thus the new trait system
if ( !gGameOptions.fNewTraitSystem )
return NULL;
// not in autoresolve!
if( guiCurrentScreen == AUTORESOLVE_SCREEN )
{
return NULL;
}
memset( &bp, 0, sizeof( BASIC_SOLDIERCREATE_STRUCT ) );
RandomizeRelativeLevel( &( bp.bRelativeAttributeLevel ), SOLDIER_CLASS_ELITE );
RandomizeRelativeLevel( &( bp.bRelativeEquipmentLevel ), SOLDIER_CLASS_ELITE );
bp.bTeam = CIV_TEAM;
bp.bOrders = SEEKENEMY;
bp.bAttitude = (INT8) Random( MAXATTITUDES );
bp.bBodyType = Random(4);
bp.ubSoldierClass = SOLDIER_CLASS_ELITE;
CreateDetailedPlacementGivenBasicPlacementInfo( &pp, &bp );
pSoldier = TacticalCreateSoldier( &pp, &ubID );
if ( pSoldier )
{
// set correct stats
pSoldier->stats.bLife = pSoldier->stats.bLifeMax = (INT8)( 70 + Random( 26 ) );
pSoldier->stats.bAgility = (INT8)( 70 + Random( 16 ) );
// add assassin flag
pSoldier->bSoldierFlagMask |= (SOLDIER_COVERT_SOLDIER|SOLDIER_ASSASSIN);
// add spy trait lvl2
pSoldier->stats.ubSkillTraits[0] = COVERT_NT;
pSoldier->stats.ubSkillTraits[1] = COVERT_NT;
// set correct civ group
pSoldier->ubCivilianGroup = ASSASSIN_CIV_GROUP;
// set militia name to further irritate the player
swprintf( pSoldier->name, TacticalStr[ MILITIA_TEAM_MERC_NAME ] );
// wear a militia uniform
UINT16 usPaletteAnimSurface = LoadSoldierAnimationSurface( pSoldier, pSoldier->usAnimState );
if ( usPaletteAnimSurface != INVALID_ANIMATION_SURFACE )
{
switch( disguisetype )
{
case ELITE_MILITIA:
SET_PALETTEREP_ID( pSoldier->VestPal, gUniformColors[ UNIFORM_MILITIA_ELITE ].vest );
SET_PALETTEREP_ID( pSoldier->PantsPal, gUniformColors[ UNIFORM_MILITIA_ELITE ].pants );
break;
case REGULAR_MILITIA:
SET_PALETTEREP_ID( pSoldier->VestPal, gUniformColors[ UNIFORM_MILITIA_REGULAR ].vest );
SET_PALETTEREP_ID( pSoldier->PantsPal, gUniformColors[ UNIFORM_MILITIA_REGULAR ].pants );
break;
default:
SET_PALETTEREP_ID( pSoldier->VestPal, gUniformColors[ UNIFORM_MILITIA_ROOKIE ].vest );
SET_PALETTEREP_ID( pSoldier->PantsPal, gUniformColors[ UNIFORM_MILITIA_ROOKIE ].pants );
break;
}
// Use palette from HVOBJECT, then use substitution for pants, etc
memcpy( pSoldier->p8BPPPalette, gAnimSurfaceDatabase[ usPaletteAnimSurface ].hVideoObject->pPaletteEntry, sizeof( pSoldier->p8BPPPalette ) * 256 );
SetPaletteReplacement( pSoldier->p8BPPPalette, pSoldier->HeadPal );
SetPaletteReplacement( pSoldier->p8BPPPalette, pSoldier->VestPal );
SetPaletteReplacement( pSoldier->p8BPPPalette, pSoldier->PantsPal );
SetPaletteReplacement( pSoldier->p8BPPPalette, pSoldier->SkinPal );
pSoldier->CreateSoldierPalettes();
}
// send soldier to centre of map, roughly
pSoldier->aiData.sNoiseGridno = (CENTRAL_GRIDNO + ( Random( CENTRAL_RADIUS * 2 + 1 ) - CENTRAL_RADIUS ) + ( Random( CENTRAL_RADIUS * 2 + 1 ) - CENTRAL_RADIUS ) * WORLD_COLS);
pSoldier->aiData.ubNoiseVolume = MAX_MISC_NOISE_DURATION;
}
return( pSoldier );
}
void CreateAssassin(UINT8 disguisetype)
{
SOLDIERTYPE* pSoldier = TacticalCreateEnemyAssassin( disguisetype );
if ( pSoldier )
{
// find a valid starting gridno
if ( pSoldier->sInsertionGridNo <= 0 )
{
UINT8 tries = 0; // counter for gridno function... if we fail to get a valid starting gridno multiple times, do not place assassin
INT32 sGridNo = NOWHERE;
do
{
if ( ++tries > 20 )
return;
sGridNo = RandomGridNo();
}
// a valid starting gridno must be valid, not in a structure, not in water, and not too near to our mercs
while( TileIsOutOfBounds(sGridNo) || FindStructure( sGridNo, STRUCTURE_BLOCKSMOVES ) || TERRAIN_IS_WATER( gpWorldLevelData[ sGridNo ].ubTerrainID) || GridNoNearPlayerMercs(sGridNo, 12) );
pSoldier->sInsertionGridNo = sGridNo;
}
AddSoldierToSector( pSoldier->ubID );
// So we can see them!
AllTeamsLookForAll(NO_INTERRUPTS);
gTacticalStatus.fCivGroupHostile[ ASSASSIN_CIV_GROUP ] = CIV_GROUP_WILL_BECOME_HOSTILE;
}
}
void RandomizeRelativeLevel( INT8 *pbRelLevel, UINT8 ubSoldierClass )
{
@@ -4794,4 +4915,4 @@ BOOLEAN AssignTraitsToSoldier( SOLDIERTYPE *pSoldier, SOLDIERCREATE_STRUCT *pCre
return( TRUE );
else
return( FALSE );
}
}
+4
View File
@@ -426,6 +426,10 @@ SOLDIERTYPE* TacticalCreateArmyTroop();
SOLDIERTYPE* TacticalCreateMilitia( UINT8 ubMilitiaClass );
SOLDIERTYPE* TacticalCreateCreature( INT8 bCreatureBodyType );
// Flugente: assassins are elite soldiers of the civ team that go hostile on a certain event, otherwise they just blend in
SOLDIERTYPE* TacticalCreateEnemyAssassin(UINT8 disguisetype);
void CreateAssassin(UINT8 disguisetype);
// randomly generates a relative level rating (attributes or equipment)
void RandomizeRelativeLevel( INT8 *pbRelLevel, UINT8 ubSoldierClass );
+92 -1
View File
@@ -54,6 +54,8 @@
#include "Queen Command.h"
#include "Map Edgepoints.h"
#include "Campaign.h" // added by Flugente for HighestPlayerProgressPercentage()
BOOLEAN gfOriginalList = TRUE;
SOLDIERINITNODE *gSoldierInitHead = NULL;
@@ -911,7 +913,12 @@ UINT8 AddSoldierInitListTeamToWorld( INT8 bTeam, UINT8 ubMaxNum )
//we now have the number, so compared it to the num we can add, and determine how we will
//randomly determine which nodes to add.
if( !ubSlotsAvailable )
{ //There aren't any basic placements of desired team, so exit.
{
// Flugente: decide wether to spawn enemy assassins in this sector (not kingpin's hitmen, they are handled elsewhere)
if ( bTeam == MILITIA_TEAM )
SectorAddAssassins(gWorldSectorX, gWorldSectorY, gbWorldSectorZ);
//There aren't any basic placements of desired team, so exit.
return ubNumAdded;
}
curr = mark;
@@ -953,6 +960,11 @@ UINT8 AddSoldierInitListTeamToWorld( INT8 bTeam, UINT8 ubMaxNum )
}
curr = curr->next;
}
// Flugente: decide wether to spawn enemy assassins in this sector (not kingpin's hitmen, they are handled elsewhere)
if ( bTeam == MILITIA_TEAM )
SectorAddAssassins(gWorldSectorX, gWorldSectorY, gbWorldSectorZ);
return ubNumAdded;
}
@@ -2676,3 +2688,82 @@ void AddSoldierInitListMilitiaOnEdge( UINT8 ubStrategicInsertionCode, UINT8 ubNu
}
}
}
extern UINT32 GetWorldTotalMin( );
// Flugente: decide wether to spawn enemy assassins in this sector (not kingpin's hitmen, they are handled elsewhere)
void SectorAddAssassins( INT16 sMapX, INT16 sMapY, INT16 sMapZ )
{
// this needs to be turned on on, and new trait system needs to be active
if ( !gGameExternalOptions.fEnemyAssassins || !gGameOptions.fNewTraitSystem )
return;
// only possible if a certain progress has been reached - the queen only sends out assassins after a while
if ( HighestPlayerProgressPercentage() < gGameExternalOptions.usAssassinMinimumProgress )
return;
// not in underground sectors
if ( sMapZ > 0 )
return;
SECTORINFO *pSector = &SectorInfo[ SECTOR( sMapX, sMapY ) ];
if ( !pSector )
return;
// does not work atm, time gets reset too early
// not if we have recently been in this sector
//if ( pSector->uiTimeCurrentSectorWasLastLoaded + 10 > GetWorldTotalMin() )
//return;
// do not spawn if militia size is very small (we will be spotted much easier by the player)
UINT32 totalmilitia = pSector->ubNumberOfCivsAtLevel[ GREEN_MILITIA ] + pSector->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ] + pSector->ubNumberOfCivsAtLevel[ ELITE_MILITIA ];
if ( totalmilitia < gGameExternalOptions.usAssassinMinimumMilitia )
return;
// count current number of civilians
UINT16 numberofcivs = 0;
SOLDIERTYPE* pTeamSoldier = NULL;
INT32 cnt = gTacticalStatus.Team[ CIV_TEAM ].bFirstID;
INT32 lastid = gTacticalStatus.Team[ CIV_TEAM ].bLastID;
for ( pTeamSoldier = MercPtrs[ cnt ]; cnt < lastid; ++cnt, ++pTeamSoldier)
{
// check if teamsoldier exists in this sector
if ( pTeamSoldier && pTeamSoldier->bActive && pTeamSoldier->bInSector && pTeamSoldier->sSectorX == sMapX && pTeamSoldier->sSectorY == sMapY && pTeamSoldier->bSectorZ == sMapZ )
++numberofcivs;
}
// we can't spawn if all civilian slots are already taken
if ( numberofcivs >= gGameExternalOptions.ubGameMaximumNumberOfCivilians )
return;
// now count militia, and which type (green, regular, elite) is most numerous - that will be the best type to blend in
UINT8 militiacnt = pSector->ubNumberOfCivsAtLevel[ GREEN_MILITIA ];
UINT8 militiadisguise = GREEN_MILITIA;
if ( pSector->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ] > militiacnt )
{
militiacnt = pSector->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ];
militiadisguise = REGULAR_MILITIA;
}
if ( pSector->ubNumberOfCivsAtLevel[ ELITE_MILITIA ] > militiacnt )
{
militiacnt = pSector->ubNumberOfCivsAtLevel[ ELITE_MILITIA ];
militiadisguise = ELITE_MILITIA;
}
// the bigger the militia, the more likely infiltration
// if militia is at maximum, there is a 10% chance of infiltration
UINT32 resultrange = gGameExternalOptions.ubGameMaximumNumberOfRebels * 10;
UINT32 modifiedmilitianumber = totalmilitia * gGameExternalOptions.usAssassinPropabilityModifier / 100;
if ( Random(resultrange) >= modifiedmilitianumber )
return;
CreateAssassin( militiadisguise );
++numberofcivs;
// if there is still room for a second assassin, and we are feeling very lucky again, add a second one
if ( numberofcivs < gGameExternalOptions.ubGameMaximumNumberOfCivilians && Random(resultrange) < modifiedmilitianumber )
{
CreateAssassin( militiadisguise );
++numberofcivs;
}
}
+3
View File
@@ -59,4 +59,7 @@ void RemoveDetailedPlacementInfo( UINT8 ubNodeID );
void AddProfilesUsingProfileInsertionData();
void AddProfilesNotUsingProfileInsertionData();
// Flugente: decide wether to spawn enemy assassins in this sector (not kingpin's hitmen, they are handled elsewhere)
void SectorAddAssassins( INT16 sMapX, INT16 sMapY, INT16 sMapZ );
#endif
+64 -3
View File
@@ -770,6 +770,11 @@ void CheckHostileOrSayQuoteList( void )
{
gTacticalStatus.fCivGroupHostile[ pSoldier->ubCivilianGroup ] = CIV_GROUP_HOSTILE;
}
// reevaluate sight - we might already see people that weren't enemies until now
ManLooksForOtherTeams(pSoldier);
pSoldier->aiData.bAlertStatus = STATUS_RED;
}
}
@@ -2370,6 +2375,42 @@ void ManSeesMan(SOLDIERTYPE *pSoldier, SOLDIERTYPE *pOpponent, INT32 sOppGridNo,
}
}
}
// Flugente: for assassins without profiles
else if ( pSoldier->IsAssassin() && pSoldier->bTeam == CIV_TEAM )
{
// if we are an assassin and still neutral and undercover, approach target and then become hostile
if ( pSoldier->aiData.bNeutral && pSoldier->bSoldierFlagMask & (SOLDIER_COVERT_CIV|SOLDIER_COVERT_SOLDIER) )
{
// only if this guy isn't disguised himself!
if ( (pOpponent->bSoldierFlagMask & (SOLDIER_COVERT_CIV|SOLDIER_COVERT_SOLDIER)) == 0)
{
if ( pSoldier->ubCivilianGroup != NON_CIV_GROUP && gTacticalStatus.fCivGroupHostile[ pSoldier->ubCivilianGroup ] >= CIV_GROUP_WILL_BECOME_HOSTILE )
{
// measure distance to our opponent, only go hostile if he is close enough
if ( PythSpacesAway( pSoldier->sGridNo, pOpponent->sGridNo ) <= NPC_TALK_RADIUS * 2 )
{
AddToShouldBecomeHostileOrSayQuoteList( pSoldier->ubID );
fNotAddedToList = FALSE;
}
}
}
}
if ( fNotAddedToList )
{
// change orders, reset action!
if ( pSoldier->aiData.bOrders != SEEKENEMY )
{
pSoldier->aiData.bOrders = SEEKENEMY;
if ( pSoldier->aiData.bOppCnt == 0 )
{
// didn't see anyone before!
CancelAIAction( pSoldier, TRUE );
SetNewSituation( pSoldier );
}
}
}
}
else
{
if ( pSoldier->bTeam == CIV_TEAM )
@@ -2515,9 +2556,29 @@ void ManSeesMan(SOLDIERTYPE *pSoldier, SOLDIERTYPE *pOpponent, INT32 sOppGridNo,
*/
}
// if both of us are not neutral, AND
// if this man is actually a true opponent (we're not on the same side)
if (!CONSIDERED_NEUTRAL( pOpponent, pSoldier ) && !CONSIDERED_NEUTRAL( pSoldier, pOpponent ) && (pSoldier->bSide != pOpponent->bSide) && pSoldier->RecognizeAsCombatant(pOpponent->ubID) )
// Flugente: reworked this to account for covert ops and assassin mechanisms
// if we are not neutral against this guy, we are truly opponents (we're not on the same side) and recognize him as an opponent...
BOOLEAN fAddAsOpponent = FALSE;
if ( !CONSIDERED_NEUTRAL( pSoldier, pOpponent ) && (pSoldier->bSide != pOpponent->bSide) && pSoldier->RecognizeAsCombatant(pOpponent->ubID) )
{
// ... check wether he is not neutral against us (account for the fact that we might be covert!)
// if we are an NPC assassin
if ( pSoldier->bSoldierFlagMask & SOLDIER_ASSASSIN && pSoldier->bSoldierFlagMask & (SOLDIER_COVERT_CIV|SOLDIER_COVERT_SOLDIER) )
{
// check wether our opponent would see us as an opponent if we weren't covert
if ( !( (pSoldier->aiData.bNeutral || pSoldier->bSoldierFlagMask & SOLDIER_POW) && ( pOpponent->bTeam != CREATURE_TEAM || pOpponent->flags.uiStatusFlags & SOLDIER_VEHICLE ) ) )
fAddAsOpponent = TRUE;
}
else
{
// simply check wether this guy sees us as an opponent too
if ( !CONSIDERED_NEUTRAL( pOpponent, pSoldier ) )
fAddAsOpponent = TRUE;
}
}
if ( fAddAsOpponent )
{
AddOneOpponent(pSoldier);