- New Feature: enemies roles allows the AI to use medics and officers, and the player can now identify high-value targets among the enemy.

Requires GameDir >= r1987.

- If an enemy is spawned with a radio set, he is more likely to become a radio operator.
- It is now possible to mount a gun on prone allies if you are facing them on the right angle.

git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@7072 3b4a5df2-a311-0410-b5c6-a8a6f20db521
This commit is contained in:
Flugente
2014-03-16 18:24:40 +00:00
parent 306fae059c
commit 2e773a029d
27 changed files with 702 additions and 123 deletions
+14 -1
View File
@@ -1472,6 +1472,19 @@ void LoadGameExternalOptions()
gGameExternalOptions.fAccessOtherMercInventories = iniReader.ReadBoolean("Tactical Gameplay Settings","ACCESS_OTHER_MERC_INVENTORIES", TRUE);
gGameExternalOptions.fBackPackWeightLowersAP = iniReader.ReadBoolean("Tactical Gameplay Settings","BACKPACKWEIGHT_LOWERS_AP", TRUE);
//################# Tactical Enemy Role Settings ##################
// Flugente: enemy roles
gGameExternalOptions.fEnemyRoles = iniReader.ReadBoolean("Tactical Enemy Role Settings", "ENEMYROLES", TRUE);
gGameExternalOptions.usTurnsToUncover = iniReader.ReadInteger("Tactical Enemy Role Settings", "ENEMYROLES_TURNSTOUNCOVER", 6, 0, 20);
gGameExternalOptions.fEnemyMedics = iniReader.ReadBoolean("Tactical Enemy Role Settings", "ENEMY_MEDICS", TRUE);
gGameExternalOptions.dEnemyMedicMedKitDrainFactor = iniReader.ReadFloat ("Tactical Enemy Role Settings", "ENEMY_MEDICS_MEDKITDRAINFACTOR", 0.1f, 0.01f, 1.0f);
gGameExternalOptions.sEnemyMedicsSearchRadius = iniReader.ReadInteger("Tactical Enemy Role Settings", "ENEMY_MEDICS_SEARCHRAIUS", 20, 5, 60);
gGameExternalOptions.fEnemyOfficers = iniReader.ReadBoolean("Tactical Enemy Role Settings", "ENEMY_OFFICERS", TRUE);
gGameExternalOptions.usEnemyOfficersPerTeamSize = iniReader.ReadInteger("Tactical Enemy Role Settings", "ENEMY_OFFICERS_REQUIREDTEAMSIZE", 10, 1, 64);
gGameExternalOptions.usEnemyOfficersMax = iniReader.ReadInteger("Tactical Enemy Role Settings", "ENEMY_OFFICERS_MAX", 4, 1, 10);
gGameExternalOptions.sEnemyOfficerSuppressionResistanceBonus = iniReader.ReadInteger("Tactical Enemy Role Settings", "ENEMY_OFFICERS_SUPPRESSION_RESISTANCE_BONUS", 10, 0, 50);
gGameExternalOptions.dEnemyOfficerMoraleModifier = iniReader.ReadFloat ("Tactical Enemy Role Settings", "ENEMY_OFFICERS_MORALE_MODIFIER", 0.1f, 0.00f, 1.0f);
//################# Tactical Cover System Settings ##################
// CPT: Cover System Settings
@@ -1768,7 +1781,7 @@ void LoadGameExternalOptions()
gGameExternalOptions.ubPrisonerInterrogationPoints[PRISONER_REGULAR] = iniReader.ReadInteger("Strategic Gameplay Settings","PRISONER_INTERROGATION_POINTS_REGULAR", 100, gGameExternalOptions.ubPrisonerInterrogationPoints[PRISONER_ADMIN], 1000);
gGameExternalOptions.ubPrisonerInterrogationPoints[PRISONER_ELITE] = iniReader.ReadInteger("Strategic Gameplay Settings","PRISONER_INTERROGATION_POINTS_ELITE", 100, gGameExternalOptions.ubPrisonerInterrogationPoints[PRISONER_REGULAR], 1000);
gGameExternalOptions.ubPrisonerInterrogationPoints[PRISONER_SPECIAL] = iniReader.ReadInteger("Strategic Gameplay Settings","PRISONER_INTERROGATION_POINTS_SPECIAL", 100, gGameExternalOptions.ubPrisonerInterrogationPoints[PRISONER_ELITE], 1000);
// CHRISL: Determine how Skyrider should handle landing in enemy occupied sectors
gGameExternalOptions.ubSkyriderHotLZ = iniReader.ReadInteger("Strategic Gameplay Settings", "ALLOW_SKYRIDER_HOT_LZ", 0, 0, 3);
+13
View File
@@ -1337,6 +1337,19 @@ typedef struct
INT8 sMoraleModRacism;
INT8 sMoraleModSexism;
INT8 sMoraleModXenophobicBackGround;
// Flugente: enemy roles
BOOLEAN fEnemyRoles;
UINT8 usTurnsToUncover;
BOOLEAN fEnemyMedics;
FLOAT dEnemyMedicMedKitDrainFactor;
INT16 sEnemyMedicsSearchRadius;
BOOLEAN fEnemyOfficers;
UINT16 usEnemyOfficersPerTeamSize;
UINT16 usEnemyOfficersMax;
INT8 sEnemyOfficerSuppressionResistanceBonus;
FLOAT dEnemyOfficerMoraleModifier;
// Sandro: Alternative weapon holding (rifles fired from hip / pistols fired one-handed)
UINT8 ubAllowAlternativeWeaponHolding;
-2
View File
@@ -540,7 +540,6 @@ void CheckForAndHandleHospitalPatients( void );
void HealHospitalPatient( SOLDIERTYPE *pPatient, UINT16 usHealingPtsLeft );
BOOLEAN MakeSureToolKitIsInHand( SOLDIERTYPE *pSoldier );
BOOLEAN MakeSureMedKitIsInHand( SOLDIERTYPE *pSoldier );
void RepositionMouseRegions( void );
void CheckAndUpdateTacticalAssignmentPopUpPositions( void );
@@ -8634,7 +8633,6 @@ BOOLEAN MakeSureMedKitIsInHand( SOLDIERTYPE *pSoldier )
{
INT8 bPocket = 0;
fTeamPanelDirty = TRUE;
// if there is a MEDICAL BAG in his hand, we're set
+2
View File
@@ -454,6 +454,8 @@ void HandleGatheringInformationBySoldier( SOLDIERTYPE* pSoldier );
BOOLEAN MercStaffsMilitaryHQ();
BOOLEAN MakeSureMedKitIsInHand( SOLDIERTYPE *pSoldier );
#endif
+119 -2
View File
@@ -253,6 +253,7 @@ UINT32 guiBURSTACCUM;
UINT32 guiITEMPOINTERHATCHES;
UINT32 guiUNDERWATER; // added by Flugente
UINT32 guiENEMYROLES; // added by Flugente
// UI Globals
MOUSE_REGION gViewportRegion;
@@ -299,6 +300,9 @@ extern void CalculateCoverForSoldier( SOLDIERTYPE* pForSoldier, const INT32& sTa
extern FLOAT Distance2D( FLOAT dDeltaX, FLOAT dDeltaY );
// Flugente: toggle display of enemy role indicators
BOOLEAN gDisplayEnemyRoles = TRUE;
BOOLEAN InitializeFaceGearGraphics()
{
VOBJECT_DESC VObjectDesc;
@@ -545,6 +549,12 @@ BOOLEAN InitializeTacticalInterface( )
if( !AddVideoObject( &VObjectDesc, &guiUNDERWATER ) )
AssertMsg(0, "Missing INTERFACE\\fish.sti" );
// Flugente: enemy role symbols
VObjectDesc.fCreateFlags = VOBJECT_CREATE_FROMFILE;
FilenameForBPP("INTERFACE\\RoleIcons.sti", VObjectDesc.ImageFile);
if( !AddVideoObject( &VObjectDesc, &guiENEMYROLES ) )
AssertMsg(0, "Missing INTERFACE\\RoleIcons.sti" );
//CHRISL: Moved to seperate function so we can call seperately
InitializeTacticalPortraits();
@@ -1822,6 +1832,11 @@ void DrawSelectedUIAboveGuy( UINT16 usSoldierID )
}
}
if ( gDisplayEnemyRoles && pSoldier->bTeam == ENEMY_TEAM )
{
ShowSoldierRoleSymbol(pSoldier);
}
if ( !pSoldier->flags.fShowLocator )
{
// RETURN IF MERC IS NOT SELECTED
@@ -2618,10 +2633,10 @@ void DrawSelectedUIAboveGuy( UINT16 usSoldierID )
if(gGameExternalOptions.fEnemyRank)
sY+=10;
iBack = RegisterBackgroundRect(BGND_FLAG_SINGLE, NULL, sX, sY, (INT16)(sX + 11 ), (INT16)(sY + 14 ) );
iBack = RegisterBackgroundRect(BGND_FLAG_SINGLE, NULL, sX, sY, (INT16)(sX + 22 ), (INT16)(sY + 14 ) );
if ( iBack != -1 )
SetBackgroundRectFilled( iBack );
DrawRankIcon( pSoldier->stats.bExpLevel, sX, sY );
}
}
@@ -6279,3 +6294,105 @@ void DrawEnemyHealthBar( SOLDIERTYPE* pSoldier, INT32 sX, INT32 sY, UINT8 ubLine
UnLockVideoSurface( FRAME_BUFFER );
}
// Flugente: show enemy role
BOOLEAN ShowSoldierRoleSymbol(SOLDIERTYPE* pSoldier)
{
// this only works on enemy soldiers
if ( pSoldier->bTeam != ENEMY_TEAM )
return false;
if ( pSoldier->usSkillCounter[SOLDIER_COUNTER_ROLE_OBSERVED] < gGameExternalOptions.usTurnsToUncover )
return false;
INT16 sXPos = 0;
INT16 sYPos = 0;
INT32 iBack = 0;
GetSoldierAboveGuyPositions( pSoldier, &sXPos, &sYPos, TRUE );
// Adjust for bars!
sXPos += 50;
sYPos += 25;
// is this guy an officer?
if ( pSoldier->bSoldierFlagMask & SOLDIER_ENEMY_OFFICER )
{
// 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, 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;
}
return true;
}
+3
View File
@@ -533,4 +533,7 @@ BOOLEAN CoverColorCode( INT8 cover, INT16 &color8, INT16 &color16 );
void DrawRankIcon( INT8 rank, INT32 baseX, INT32 baseY );
void DrawLine( INT32 x1, INT32 y1, INT32 x2, INT32 y2, UINT16 color8, UINT16 color16, UINT8 *pDestBuf );
// Flugente: show enemy role
BOOLEAN ShowSoldierRoleSymbol(SOLDIERTYPE* pSoldier);
#endif
+14 -5
View File
@@ -129,21 +129,27 @@ INT8 GetMoraleModifier( SOLDIERTYPE * pSoldier )
}
else
{
INT8 morale = 0;
// use AI morale
switch( pSoldier->aiData.bAIMorale )
{
case MORALE_HOPELESS:
return( -15 );
morale = -15;
case MORALE_WORRIED:
return( -7 );
morale = -7;
case MORALE_CONFIDENT:
return( 2 );
morale = -2;
case MORALE_FEARLESS:
return( 5 );
morale = -5;
default:
return( 0 );
morale = 0;
}
// Flugente: morale modifiers
morale = max(morale, morale * pSoldier->GetMoraleModifier());
return morale;
}
}
@@ -366,6 +372,9 @@ void RefreshSoldierMorale( SOLDIERTYPE * pSoldier )
}
}
// Flugente: morale modifiers
iActualMorale = iActualMorale * pSoldier->GetMoraleModifier();
// Flugente: ubMaxMorale can now be influenced by our food situation
if ( gGameOptions.fFoodSystem )
FoodMaxMoraleModifiy(pSoldier, &ubMaxMorale);
+25
View File
@@ -10619,3 +10619,28 @@ INT8 CalcEffectiveShockLevel( SOLDIERTYPE * pSoldier )
}
return bShockForCower;
}
// Flugente: count number of enemy officers
UINT8 HighestEnemyOfficersInSector( BOOL& aType )
{
SOLDIERTYPE* pSoldier;
INT32 cnt = 0;
UINT8 num = 0;
UINT8 type = OFFICER_NONE;
for ( cnt = gTacticalStatus.Team[ ENEMY_TEAM ].bFirstID, pSoldier = MercPtrs[cnt]; cnt < gTacticalStatus.Team[ ENEMY_TEAM ].bLastID; pSoldier++, ++cnt )
{
if ( pSoldier->bActive && pSoldier->bInSector && pSoldier->stats.bLife > 0 )
{
if ( pSoldier->bSoldierFlagMask & SOLDIER_ENEMY_OFFICER )
{
// officers with double squadleader trait are captains, otherwise lieutnant
aType = max( aType, NUM_SKILL_TRAITS( pSoldier, SQUADLEADER_NT) );
++num;
}
}
}
return num;
}
+12
View File
@@ -388,5 +388,17 @@ void KillOnePrisoner( SECTORINFO *pSectorInfo );
// sevenfm: calc shock level with modifiers (traits, backgrounds, ...)
INT8 CalcEffectiveShockLevel( SOLDIERTYPE * pSoldier );
// Flugente: enemy officers
enum {
OFFICER_NONE = 0,
OFFICER_LIEUTNANT,
OFFICER_CAPTAIN,
OFFICER_MAX,
};
// Flugente: return number of enemy officers and highest rank found
UINT8 HighestEnemyOfficersInSector(BOOL& aType);
#endif
+169 -9
View File
@@ -12636,8 +12636,10 @@ UINT32 SOLDIERTYPE::SoldierDressWound( SOLDIERTYPE *pVictim, INT16 sKitPts, INT1
}
// if we are going to do the surgery
// Flugente: AI medics are allowed to perform surgery without first aid kits
if (pVictim->iHealableInjury > 0 && this->fDoingSurgery && this->ubID != pVictim->ubID && gGameOptions.fNewTraitSystem
&& (NUM_SKILL_TRAITS( this, DOCTOR_NT ) >= gSkillTraitValues.ubDONumberTraitsNeededForSurgery) && Item[this->inv[ HANDPOS ].usItem].medicalkit)
&& (NUM_SKILL_TRAITS( this, DOCTOR_NT ) >= gSkillTraitValues.ubDONumberTraitsNeededForSurgery)
&& (Item[this->inv[ HANDPOS ].usItem].medicalkit || this->bTeam == ENEMY_TEAM) )
{
fOnSurgery = TRUE;
}
@@ -12877,9 +12879,12 @@ UINT32 SOLDIERTYPE::SoldierDressWound( SOLDIERTYPE *pVictim, INT16 sKitPts, INT1
// We are finished !!!
this->fDoingSurgery = FALSE;
gTacticalStatus.ubLastRequesterSurgeryTargetID = NOBODY; // reset last target
// throw message
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113Message[ MSG113_SURGERY_FINISHED ], pVictim->GetName() );
if ( this->bTeam != ENEMY_TEAM )
{
// throw message
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113Message[ MSG113_SURGERY_FINISHED ], pVictim->GetName() );
}
// add to record - another surgery undergoed
if ( pVictim->ubProfile != NO_PROFILE && usLifeReturned >= 100 )
@@ -14164,9 +14169,25 @@ BOOLEAN SOLDIERTYPE::IsWeaponMounted( void )
{
// for some reason I find EXTREMELY FRUSTRATING, we might get a heigth of 2 on a totally empty tile... so we check if we could occupy the tile
if ( !IsLocationSittable( nextGridNoinSight, 0 ) )
{
// resting our gun on people would be rude - only allow if nobody is there
if( WhoIsThere2( nextGridNoinSight, 0 ) == NOBODY )
applybipod = TRUE;
UINT usPersonID = WhoIsThere2( nextGridNoinSight, this->pathing.bLevel );
if( usPersonID == NOBODY )
applybipod = TRUE;
else
{
SOLDIERTYPE* pSoldier = MercPtrs[ usPersonID ];
// if the other person is an ally an prone
if ( this->bSide == pSoldier->bSide && gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_PRONE )
{
// if we are facing the other guy in a 90 degree angle, we can mount our gun on his back
// Once merc's relationship allows angering mercs through actions of others, add a penalty here
if ( this->ubDirection == gTwoCCDirection[ pSoldier->ubDirection ] || this->ubDirection == gTwoCDirection[ pSoldier->ubDirection ] )
applybipod = TRUE;
}
}
}
}
}
else if ( adjacenttileheight == 4 && (gAnimControl[ this->usAnimState ].ubEndHeight == ANIM_CROUCH || (gAnimControl[ this->usAnimState ].ubEndHeight == ANIM_STAND && (gAnimControl[ this->usAnimState ].uiFlags &(ANIM_ALT_WEAPON_HOLDING)))))
@@ -16954,6 +16975,26 @@ INT8 SOLDIERTYPE::GetSuppressionResistanceBonus()
bonus += this->GetBackgroundValue(BG_RESI_SUPPRESSION);
// Flugente: enemy roles
if ( this->bTeam == ENEMY_TEAM )
{
// bonus if we have an officer around
BOOL officertype = OFFICER_NONE;
if ( HighestEnemyOfficersInSector( officertype ) )
{
switch ( officertype )
{
case OFFICER_LIEUTNANT:
bonus += gGameExternalOptions.sEnemyOfficerSuppressionResistanceBonus;
break;
case OFFICER_CAPTAIN:
bonus += gGameExternalOptions.sEnemyOfficerSuppressionResistanceBonus * 2;
break;
}
}
}
return min( 100, max( -100, bonus) );
}
@@ -17039,12 +17080,33 @@ UINT8 SOLDIERTYPE::GetMoraleThreshold()
{
UINT8 threshold = 100;
UINT8 moraledamage = 0;
moraledamage = (moraledamage * (100 - GetFearResistanceBonus())) / 100;
return min(threshold, max(0, threshold - moraledamage));
}
FLOAT SOLDIERTYPE::GetMoraleModifier()
{
FLOAT mod = 1.0f;
BOOL officertype = OFFICER_NONE;
if ( HighestEnemyOfficersInSector( officertype ) )
{
switch ( officertype )
{
case OFFICER_LIEUTNANT:
mod += gGameExternalOptions.dEnemyOfficerMoraleModifier;
break;
case OFFICER_CAPTAIN:
mod += gGameExternalOptions.dEnemyOfficerMoraleModifier * 2;
break;
}
}
return mod;
}
INT16 SOLDIERTYPE::GetInterruptModifier( UINT8 usDistance )
{
INT16 bonus = 0;
@@ -17086,12 +17148,22 @@ void SOLDIERTYPE::SoldierPropertyUpkeep()
// effects eventually run out
for (UINT8 counter = 0; counter < SOLDIER_COUNTER_MAX; ++counter)
{
if ( counter == SOLDIER_COUNTER_SPOTTER && usSkillCounter[counter] > 0 )
if ( counter == SOLDIER_COUNTER_ROLE_OBSERVED )
continue;
else if ( counter == SOLDIER_COUNTER_SPOTTER && usSkillCounter[counter] > 0 )
usSkillCounter[counter] = min(255, usSkillCounter[counter] + 1 );
else
usSkillCounter[counter] = max(0, usSkillCounter[counter] - 1 );
}
// if soldier was seen this turn, increase his observed counter
if ( this->bSoldierFlagMask & SOLDIER_ENEMY_OBSERVEDTHISTURN )
{
this->bSoldierFlagMask &= ~SOLDIER_ENEMY_OBSERVEDTHISTURN;
++usSkillCounter[SOLDIER_COUNTER_ROLE_OBSERVED];
}
// if there is a combat going and we are in sector, note that in the battle report
if ( this->bInSector && (gTacticalStatus.uiFlags & INCOMBAT || gTacticalStatus.fEnemyInSector) )
{
@@ -18199,6 +18271,94 @@ BOOLEAN SOLDIERTYPE::BecomeSpotter( INT32 sTargetGridNo )
return TRUE;
}
// Flugente: enemy roles
BOOLEAN SOLDIERTYPE::HasSniper()
{
INT8 invsize = (INT8)inv.size(); // remember inventorysize, so we don't call size() repeatedly
for ( INT8 bLoop = 0; bLoop < invsize; ++bLoop)
{
if (inv[bLoop].exists() && Item[inv[ bLoop ].usItem].usItemClass == IC_GUN && Weapon[Item[inv[bLoop].usItem].ubClassIndex].ubWeaponType == GUN_SN_RIFLE )
{
return TRUE;
}
}
return FALSE;
}
// AI-only: can we heal a wounded ally? Do NOT, repeat, NOT use this with mercs!
BOOLEAN SOLDIERTYPE::CanMedicAI()
{
if ( !gGameExternalOptions.fEnemyRoles || !gGameExternalOptions.fEnemyMedics || this->bTeam != ENEMY_TEAM )
return FALSE;
if ( HAS_SKILL_TRAIT( this, DOCTOR_NT) )
{
if ( FindFirstAidKit(this) != NO_SLOT || FindMedKit(this) != NO_SLOT )
return TRUE;
}
return FALSE;
}
// AI-only: heal a nearby friend. Do NOT, repeat, NOT use this with mercs!
BOOLEAN SOLDIERTYPE::AIDoctorFriend()
{
if ( this->bTeam != ENEMY_TEAM )
return FALSE;
// we can only free people we are facing
INT32 nextGridNoinSight = NewGridNo( this->sGridNo, DirectionInc( this->ubDirection ) );
UINT8 target = WhoIsThere2( nextGridNoinSight, this->pathing.bLevel );
// is there somebody?
if ( target != NOBODY )
{
SOLDIERTYPE* pSoldier = MercPtrs[target];
if ( pSoldier->bTeam != ENEMY_TEAM )
return FALSE;
// if this guy is wounded should always be the case, otherwise this function was calleed needlessly
if ( pSoldier->stats.bLife < pSoldier->stats.bLifeMax )
{
// move medkit into hand - if we don't have a medkit in our hands, abort
if ( !MakeSureMedKitIsInHand( this ) )
return FALSE;
if( gAnimControl[ this->usAnimState ].ubEndHeight == ANIM_CROUCH )
{
this->EVENT_InitNewSoldierAnim( START_AID, 0 , FALSE );
}
// AI medics always perform surgery
this->fDoingSurgery = TRUE;
UINT16 usKitPts = TotalPoints( &(this->inv[ HANDPOS ] ) );
// note the current hp
INT8 oldlife = pSoldier->stats.bLife;
UINT16 uiPointsUsed = this->SoldierDressWound( pSoldier, usKitPts, usKitPts );
UseKitPoints( &(this->inv[ HANDPOS ] ), (UINT16)(uiPointsUsed * gGameExternalOptions.dEnemyMedicMedKitDrainFactor), this );
// healing done will be displayed the next time the player sees this soldier
pSoldier->flags.fDisplayDamage = TRUE;
pSoldier->sDamage -= pSoldier->stats.bLife - oldlife;
// alert both soldiers
this->aiData.bAlertStatus = min(this->aiData.bAlertStatus, STATUS_RED);
pSoldier->aiData.bAlertStatus = min(pSoldier->aiData.bAlertStatus, STATUS_RED);
return TRUE;
}
}
return FALSE;
}
INT32 CheckBleeding( SOLDIERTYPE *pSoldier )
{
+10 -3
View File
@@ -384,10 +384,10 @@ enum
#define SOLDIER_BATTLE_PARTICIPATION 0x01000000 //16777216 // campaign stats: soldier took part in this battle
#define SOLDIER_RAISED_REDALERT 0x02000000 //33554432 // this (AI) soldier has raised red alert. Don't allow him to do so again this turn - either it already worked, or the signal is blocked
/*#define PLAYER_NET_3_LVL_3 0x04000000 //67108864
#define PLAYER_NET_4_LVL_3 0x08000000 //134217728
#define SOLDIER_ENEMY_OFFICER 0x04000000 //67108864 // soldier is an enemy officer
#define SOLDIER_ENEMY_OBSERVEDTHISTURN 0x08000000 //134217728 // enemy soldier was seen by the player this turn
#define PLAYER_NET_1_LVL_4 0x10000000 //268435456
/*#define PLAYER_NET_1_LVL_4 0x10000000 //268435456
#define PLAYER_NET_2_LVL_4 0x20000000 //536870912
#define WH40K_SOLDIER_ILLUSION 0x40000000 //1073741824 // Soldier is an Illusion
#define WH40K_SOLDIER_KILLTHISTURN 0x80000000 //2147483648 // Soldier is on a kill streak*/
@@ -565,6 +565,7 @@ enum{
enum {
SOLDIER_COUNTER_RADIO_ARTILLERY, // there is actually no need for an artillery timer, but we use one to forbid the same AI guy ordering multiple strikes at once
SOLDIER_COUNTER_SPOTTER, // used to determine wether we are a spotter
SOLDIER_COUNTER_ROLE_OBSERVED, // every turn that the player observes an enemy, the enemies counter is increased. If it is high enough, we know his role
SOLDIER_COUNTER_MAX = 20, // enough space for fillers
};
@@ -1779,6 +1780,7 @@ public:
INT16 GetAPBonus();
INT8 GetFearResistanceBonus(); // fear resistance lowers shock and morale damage from horror
UINT8 GetMoraleThreshold();
FLOAT GetMoraleModifier();
INT16 GetInterruptModifier( UINT8 usDistance );
void SoldierPropertyUpkeep(); // update functions for various properties (updating counters, resetting flags etc.)
@@ -1820,6 +1822,11 @@ public:
BOOLEAN CanSpot( INT32 sTargetGridNo = -1 );
BOOLEAN BecomeSpotter( INT32 sTargetGridNo );
// Flugente: enemy roles
BOOLEAN HasSniper();
BOOLEAN CanMedicAI(); // AI-only: can we heal a wounded ally? Do NOT, repeat, NOT use this with mercs!
BOOLEAN AIDoctorFriend(); // AI-only: heal a wounded friend. Do NOT, repeat, NOT use this with mercs!
//////////////////////////////////////////////////////////////////////////////
}; // SOLDIERTYPE;
+92 -71
View File
@@ -1669,56 +1669,25 @@ BOOLEAN TacticalCopySoldierFromCreateStruct( SOLDIERTYPE *pSoldier, SOLDIERCREAT
// We have a function for this
type = pSoldier->GetSoldierProfileType( pCreateStruct->bTeam );
/* if ( pCreateStruct->bTeam == ENEMY_TEAM && gGameExternalOptions.fSoldierProfiles_Enemy )
{
switch( pCreateStruct->ubSoldierClass )
if ( type > -1 )
{
case SOLDIER_CLASS_ELITE :
type = 2;
break;
case SOLDIER_CLASS_ARMY :
type = 1;
break;
case SOLDIER_CLASS_ADMINISTRATOR :
type = 0;
break;
}
}
else if ( pCreateStruct->bTeam == MILITIA_TEAM && gGameExternalOptions.fSoldierProfiles_Militia )
{
switch( pCreateStruct->ubSoldierClass )
{
case SOLDIER_CLASS_ELITE_MILITIA :
type = 5;
break;
case SOLDIER_CLASS_REG_MILITIA :
type = 4;
break;
case SOLDIER_CLASS_GREEN_MILITIA :
type = 3;
break;
}
}*/
}
INT16 availablenames[NUM_SOLDIER_PROFILES];
UINT16 cnt = 0;
if ( type > -1 )
{
INT16 availablenames[NUM_SOLDIER_PROFILES];
UINT16 cnt = 0;
for (UINT16 i = 1; i < num_found_soldier_profiles[type]; ++i)
{
// make sure the name isn't already currently in use!
if ( !IsProfileInUse(pCreateStruct->bTeam, type, i) )
availablenames[cnt++] = i;
}
for (UINT16 i = 1; i < num_found_soldier_profiles[type]; ++i)
{
// make sure the name isn't already currently in use!
if ( !IsProfileInUse(pCreateStruct->bTeam, type, i) )
availablenames[cnt++] = i;
}
if ( cnt > 0 )
{
pSoldier->usSoldierProfile = availablenames[Random(cnt)];
if ( cnt > 0 )
{
pSoldier->usSoldierProfile = availablenames[Random(cnt)];
if ( zSoldierProfile[type][pSoldier->usSoldierProfile].uiBodyType > 0 && zSoldierProfile[type][pSoldier->usSoldierProfile].uiBodyType < 5 )
pSoldier->ubBodyType = zSoldierProfile[type][pSoldier->usSoldierProfile].uiBodyType - 1;
if ( zSoldierProfile[type][pSoldier->usSoldierProfile].uiBodyType > 0 && zSoldierProfile[type][pSoldier->usSoldierProfile].uiBodyType < 5 )
pSoldier->ubBodyType = zSoldierProfile[type][pSoldier->usSoldierProfile].uiBodyType - 1;
}
}
}
}
@@ -1737,6 +1706,22 @@ BOOLEAN TacticalCopySoldierFromCreateStruct( SOLDIERTYPE *pSoldier, SOLDIERCREAT
AssignTraitsToSoldier( pSoldier, pCreateStruct );
if ( gGameExternalOptions.fAssignTraitsToMilitia && SOLDIER_CLASS_MILITIA( pSoldier->ubSoldierClass ) )
AssignTraitsToSoldier( pSoldier, pCreateStruct );
// Flugente: enemy roles
if ( gGameExternalOptions.fEnemyRoles && gGameExternalOptions.fEnemyOfficers && SOLDIER_CLASS_ENEMY( pSoldier->ubSoldierClass ) )
{
if ( HAS_SKILL_TRAIT( pSoldier, SQUADLEADER_NT) )
{
UINT8 numenemies = NumEnemyInSector();
BOOL officertype = OFFICER_NONE;
UINT8 numofficers = HighestEnemyOfficersInSector( officertype );
// this guy becomes an officer if there are enough soldiers around, and we aren't already at max of officers
if ( numenemies > gGameExternalOptions.usEnemyOfficersPerTeamSize * numofficers && numofficers < gGameExternalOptions.usEnemyOfficersMax )
pSoldier->bSoldierFlagMask |= SOLDIER_ENEMY_OFFICER;
}
}
// SANDRO - If neither of these two options are activated, use the original code
// however, "no traits" means no traits at all, so commented out
/*if ( !(gGameExternalOptions.fAssignTraitsToEnemy) && !(gGameExternalOptions.fAssignTraitsToMilitia) )
@@ -4649,6 +4634,8 @@ BOOLEAN AssignTraitsToSoldier( SOLDIERTYPE *pSoldier, SOLDIERCREATE_STRUCT *pCre
BOOLEAN foundHtH = FALSE;
BOOLEAN foundMelee = FALSE;
BOOLEAN fRadioSetFound = FALSE;
BOOLEAN fFirstAidKitFound = FALSE;
BOOLEAN fMedKitFound = FALSE;
// FIRST FIND OUT THE COMPOSITION OF OUR GEAR
INT8 invsize = (INT8)pSoldier->inv.size();
@@ -4675,10 +4662,69 @@ BOOLEAN AssignTraitsToSoldier( SOLDIERTYPE *pSoldier, SOLDIERCREATE_STRUCT *pCre
foundMelee = TRUE;
else if ( HasItemFlag(pCreateStruct->Inv[bLoop].usItem, RADIO_SET) )
fRadioSetFound = TRUE;
else if ( Item[pCreateStruct->Inv[bLoop].usItem].firstaidkit )
fFirstAidKitFound = TRUE;
else if ( Item[pCreateStruct->Inv[bLoop].usItem].medicalkit )
{
// Flugente: for enemy medic purposes, med kits also count as first aid kits
fFirstAidKitFound = TRUE;
fMedKitFound = TRUE;
}
}
}
// NOW BASED ON WHAT WE HAVE FOUND, ASSIGN UNASSIGNED TRAITS
// Flugente: radio operator
if ( gGameOptions.fNewTraitSystem && (!ATraitAssigned || !BTraitAssigned || !CTraitAssigned ) )
{
// if we have a radio set, give us the corresponding trait so we can use it...
if ( fRadioSetFound )
{
// this is a minor trait, so try to first fill the minor slot if possible
if ( ubSolClass == SOLDIER_CLASS_ELITE || ubSolClass == SOLDIER_CLASS_ELITE_MILITIA )
{
pSoldier->stats.ubSkillTraits[2] = RADIO_OPERATOR_NT;
CTraitAssigned = TRUE;
return( TRUE ); // We no longer need to continue from here
}
else if ( !ATraitAssigned )
{
pSoldier->stats.ubSkillTraits[0] = RADIO_OPERATOR_NT;
ATraitAssigned = TRUE;
}
else if ( !BTraitAssigned )
{
pSoldier->stats.ubSkillTraits[1] = RADIO_OPERATOR_NT;
BTraitAssigned = TRUE;
}
}
}
// Flugente: enemy roles allow medics
if ( gGameExternalOptions.fEnemyRoles && gGameExternalOptions.fEnemyMedics && gGameOptions.fNewTraitSystem && (!ATraitAssigned || !BTraitAssigned ) )
{
// if we have a radio set, give us the corresponding trait so we can use it...
if ( fFirstAidKitFound )
{
if ( !ATraitAssigned )
{
pSoldier->stats.ubSkillTraits[0] = DOCTOR_NT;
ATraitAssigned = TRUE;
// if we found a medkit, chance to become a full doctor
if ( !BTraitAssigned && fMedKitFound && Chance(50) )
{
pSoldier->stats.ubSkillTraits[1] = DOCTOR_NT;
BTraitAssigned = TRUE;
}
}
else if ( !BTraitAssigned )
{
pSoldier->stats.ubSkillTraits[1] = DOCTOR_NT;
BTraitAssigned = TRUE;
}
}
}
// HEAVY WEAPONS TRAIT
if ( foundMortar || foundRocketlauncher || foundGrenadelauncher )
@@ -5213,31 +5259,6 @@ BOOLEAN AssignTraitsToSoldier( SOLDIERTYPE *pSoldier, SOLDIERCREATE_STRUCT *pCre
}
}
}
// Flugente: new traits
if ( gGameOptions.fNewTraitSystem && (!ATraitAssigned || !BTraitAssigned || !CTraitAssigned ) )
{
// if we have a radio set, give us the corresponding trait so we can use it...
if ( fRadioSetFound )
{
if ( !ATraitAssigned )
{
pSoldier->stats.ubSkillTraits[0] = RADIO_OPERATOR_NT;
ATraitAssigned = TRUE;
}
else if ( !BTraitAssigned )
{
pSoldier->stats.ubSkillTraits[1] = RADIO_OPERATOR_NT;
BTraitAssigned = TRUE;
}
else if ( ubSolClass == SOLDIER_CLASS_ELITE || ubSolClass == SOLDIER_CLASS_ELITE_MILITIA )
{
pSoldier->stats.ubSkillTraits[2] = RADIO_OPERATOR_NT;
CTraitAssigned = TRUE;
return( TRUE ); // We no longer need to continue from here
}
}
}
}
// RETURN TRUE IF ALL TRAITS ASSIGNED OTHERWISE FALSE
if (( !gGameOptions.fNewTraitSystem && ATraitAssigned && BTraitAssigned ) || ( gGameOptions.fNewTraitSystem && ATraitAssigned && BTraitAssigned && CTraitAssigned ) )
+8 -3
View File
@@ -276,6 +276,9 @@ void SwapMercPortraits ( SOLDIERTYPE *pSoldier, INT8 bDirection );
extern INT8 GetTeamSlotFromPlayerID( UINT8 ubID );
extern FACETYPE *gpCurrentTalkingFace;
// Flugente: toggle display of enemy role indicators
extern BOOLEAN gDisplayEnemyRoles;
// sevenfm: new mouse commands
void HandleAltMouseTBWheel( void );
void HandleAltMouseTBMButton( UINT32 *puiNewEvent );
@@ -2999,7 +3002,7 @@ void GetKeyboardInput( UINT32 *puiNewEvent )
else
{
HandleTacticalCoverMenu();
}
}
}
else
{
@@ -7820,7 +7823,8 @@ void HandleTacticalCoverMenu( void )
}
gzUserDefinedButtonColor[0] = 0;
gzUserDefinedButtonColor[1] = FONT_MCOLOR_LTYELLOW;
gzUserDefinedButtonColor[2] = FONT_MCOLOR_LTYELLOW;
gzUserDefinedButtonColor[2] = FONT_MCOLOR_LTYELLOW;
gzUserDefinedButtonColor[4] = FONT_LTRED;
gzUserDefinedButtonColor[8] = FONT_ORANGE;
gzUserDefinedButtonColor[9] = FONT_ORANGE;
gzUserDefinedButtonColor[10] = FONT_ORANGE;
@@ -7853,7 +7857,8 @@ void TacticalCoverMessageBoxCallBack( UINT8 ubExitValue )
break;
case 4:
break;
case 5:
case 5:
gDisplayEnemyRoles = !gDisplayEnemyRoles;
break;
case 6:
break;
+7 -2
View File
@@ -2170,7 +2170,12 @@ void ManSeesMan(SOLDIERTYPE *pSoldier, SOLDIERTYPE *pOpponent, INT32 sOppGridNo,
if ( pOpponent->UsesScubaGear() )
return;
// Flugente: update our sight concerning this guy, otherwise we could get way with open attacks because this does not get updated
pSoldier->RecognizeAsCombatant(pOpponent->ubID);
if ( pSoldier->RecognizeAsCombatant(pOpponent->ubID) )
{
// Flugente: note that this enemy has been seen by mercs this turn
if ( pOpponent->bTeam == ENEMY_TEAM && pSoldier->bTeam == OUR_TEAM )
pOpponent->bSoldierFlagMask |= SOLDIER_ENEMY_OBSERVEDTHISTURN;
}
// if we're seeing a guy we didn't see on our last chance to look for him
if (pSoldier->aiData.bOppList[pOpponent->ubID] != SEEN_CURRENTLY)
@@ -2506,7 +2511,7 @@ void ManSeesMan(SOLDIERTYPE *pSoldier, SOLDIERTYPE *pOpponent, INT32 sOppGridNo,
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!)
// ... 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) )
{
+7
View File
@@ -2305,6 +2305,13 @@ INT8 ExecuteAction(SOLDIERTYPE *pSoldier)
}
break;
case AI_ACTION_DOCTOR:
{
pSoldier->AIDoctorFriend();
ActionDone( pSoldier );
}
break;
default:
#ifdef BETAVERSION
NumMessage("ExecuteAction - Illegal action type = ",pSoldier->aiData.bAction);
+68 -15
View File
@@ -594,6 +594,10 @@ BOOLEAN IsActionAffordable(SOLDIERTYPE *pSoldier)
bMinPointsNeeded = 10; // TODO
break;
case AI_ACTION_DOCTOR:
bMinPointsNeeded = 20; // TODO
break;
default:
#ifdef BETAVERSION
//NumMessage("AffordableAction - Illegal action type = ",pSoldier->aiData.bAction);
@@ -3064,40 +3068,44 @@ INT32 CalcStraightThreatValue( SOLDIERTYPE *pEnemy )
// Flugente: get the id of the closest soldier with a specific flag that we can currently see
UINT8 GetClosestFlaggedSoldierID( SOLDIERTYPE * pSoldier, INT16 aRange, UINT8 auTeam, UINT32 aFlag )
{
UINT8 id = NOBODY;
UINT8 id = NOBODY;
UINT32 uiLoop;
BOOLEAN fRangeRestricted = FALSE, fFound = FALSE;
SOLDIERTYPE * pFriend;
INT16 range = aRange;
// go through each soldier, looking for "friends" (soldiers on same side)
for (uiLoop = 0; uiLoop < guiNumMercSlots; ++uiLoop)
// go through each soldier, looking for "friends" (soldiers on same team)
for (uiLoop = gTacticalStatus.Team[ auTeam ].bFirstID; uiLoop < gTacticalStatus.Team[ auTeam ].bLastID; ++uiLoop)
{
pFriend = MercSlots[ uiLoop ];
pFriend = MercPtrs[ uiLoop ];
// if this merc is inactive, not in sector, or dead
if (!pFriend)
continue;
// skip ourselves
if (pFriend->ubID == pSoldier->ubID)
continue;
// must be on the same level
if ( pFriend->pathing.bLevel != pSoldier->pathing.bLevel )
continue;
if ( auTeam != pFriend->bTeam )
// skip if this guy is dead
if ( pFriend->stats.bLife <= 0 )
continue;
// check for flag
if ( !(pFriend->bSoldierFlagMask & aFlag) )
continue;
// skip ourselves
if (pFriend->ubID == pSoldier->ubID)
continue;
// if we're not already neighbors
if (SpacesAway(pSoldier->sGridNo, pFriend->sGridNo) < range)
// are we close enough?
INT16 friendrange = SpacesAway(pSoldier->sGridNo, pFriend->sGridNo);
if ( friendrange < range)
{
// can we see this guy?
if ( SoldierTo3DLocationLineOfSightTest( pSoldier, pFriend->sGridNo, pSoldier->pathing.bLevel, 3, TRUE, CALC_FROM_WANTED_DIR ) )
{
range = SpacesAway(pSoldier->sGridNo,pFriend->sGridNo);
range = friendrange;
id = pFriend->ubID;
}
}
@@ -3105,3 +3113,48 @@ UINT8 GetClosestFlaggedSoldierID( SOLDIERTYPE * pSoldier, INT16 aRange, UINT8 au
return id;
}
// get the id of the closest soldier (closer than x tiles) of a specific team that is wounded that we can currently see
UINT8 GetClosestWoundedSoldierID( SOLDIERTYPE * pSoldier, INT16 aRange, UINT8 auTeam )
{
UINT8 id = NOBODY;
UINT32 uiLoop;
SOLDIERTYPE * pFriend;
INT16 range = aRange;
// go through each soldier, looking for "friends" (soldiers on same team)
for (uiLoop = gTacticalStatus.Team[ auTeam ].bFirstID; uiLoop < gTacticalStatus.Team[ auTeam ].bLastID; ++uiLoop)
{
pFriend = MercPtrs[ uiLoop ];
// if this merc is inactive, not in sector, or dead
if (!pFriend)
continue;
// skip ourselves
if (pFriend->ubID == pSoldier->ubID)
continue;
// must be on the same level
if ( pFriend->pathing.bLevel != pSoldier->pathing.bLevel )
continue;
// skip if this guy is dead, not wounded or cannot be healed
if ( pFriend->stats.bLife <= 0 || pFriend->iHealableInjury <= 0 || pFriend->stats.bLife >= pFriend->stats.bLifeMax )
continue;
// are we close enough?
INT16 friendrange = SpacesAway(pSoldier->sGridNo, pFriend->sGridNo);
if ( friendrange < range)
{
// can we see this guy?
if ( SoldierTo3DLocationLineOfSightTest( pSoldier, pFriend->sGridNo, pSoldier->pathing.bLevel, 3, TRUE, CALC_FROM_WANTED_DIR ) )
{
range = friendrange;
id = pFriend->ubID;
}
}
}
return id;
}
+126 -1
View File
@@ -930,6 +930,48 @@ INT8 DecideActionGreen(SOLDIERTYPE *pSoldier)
return( AI_ACTION_RED_ALERT );
}
}
// if we are a doctor with medical gear, we might be able to help a wounded ally
if ( pSoldier->CanMedicAI() )
{
UINT8 ubPerson = GetClosestWoundedSoldierID( pSoldier, gGameExternalOptions.sEnemyMedicsSearchRadius, pSoldier->bTeam);
if ( ubPerson != NOBODY )
{
if ( PythSpacesAway(pSoldier->sGridNo, MercPtrs[ubPerson]->sGridNo) < 2 )
{
// see if we are facing this person
UINT8 ubDesiredMercDir = atan8(CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX(MercPtrs[ubPerson]->sGridNo),CenterY(MercPtrs[ubPerson]->sGridNo));
// if not already facing in that direction,
if ( pSoldier->ubDirection != ubDesiredMercDir )
{
pSoldier->aiData.usActionData = ubDesiredMercDir;
return( AI_ACTION_CHANGE_FACING );
}
// if not already crouched, crouch down first
if ( gAnimControl[ pSoldier->usAnimState ].ubHeight != ANIM_CROUCH && IsValidStance( pSoldier, ANIM_CROUCH ) && GetAPsToChangeStance( pSoldier, ANIM_CROUCH ) <= pSoldier->bActionPoints )
{
pSoldier->aiData.usActionData = ANIM_CROUCH;
return(AI_ACTION_CHANGE_STANCE);
}
return(AI_ACTION_DOCTOR);
}
else
{
pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, MercPtrs[ubPerson]->sGridNo, 20, AI_ACTION_SEEK_FRIEND, 0);
if (!TileIsOutOfBounds(pSoldier->aiData.usActionData))
{
return(AI_ACTION_SEEK_FRIEND);
}
}
}
}
}
//ddd}
@@ -1528,7 +1570,6 @@ INT8 DecideActionYellow(SOLDIERTYPE *pSoldier)
// IF YOU SEE CAPTURED FRIENDS, FREE THEM!
////////////////////////////////////////////////////////////////////////////
// Flugente: if we see one of our buddies in handcuffs, its a clear sign of enemy activity!
// Flugente: if we see one of our buddies captured, it is a clear sign of enemy activity!
if ( gGameExternalOptions.fAllowPrisonerSystem && pSoldier->bTeam == ENEMY_TEAM )
{
@@ -1572,6 +1613,48 @@ INT8 DecideActionYellow(SOLDIERTYPE *pSoldier)
}
}
}
// if we are a doctor with medical gear, we might be able to help a wounded ally
if ( pSoldier->CanMedicAI() )
{
UINT8 ubPerson = GetClosestWoundedSoldierID( pSoldier, gGameExternalOptions.sEnemyMedicsSearchRadius, pSoldier->bTeam);
if ( ubPerson != NOBODY )
{
if ( PythSpacesAway(pSoldier->sGridNo, MercPtrs[ubPerson]->sGridNo) < 2 )
{
// see if we are facing this person
UINT8 ubDesiredMercDir = atan8(CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX(MercPtrs[ubPerson]->sGridNo),CenterY(MercPtrs[ubPerson]->sGridNo));
// if not already facing in that direction,
if ( pSoldier->ubDirection != ubDesiredMercDir )
{
pSoldier->aiData.usActionData = ubDesiredMercDir;
return( AI_ACTION_CHANGE_FACING );
}
// if not already crouched, crouch down first
if ( gAnimControl[ pSoldier->usAnimState ].ubHeight != ANIM_CROUCH && IsValidStance( pSoldier, ANIM_CROUCH ) && GetAPsToChangeStance( pSoldier, ANIM_CROUCH ) <= pSoldier->bActionPoints )
{
pSoldier->aiData.usActionData = ANIM_CROUCH;
return(AI_ACTION_CHANGE_STANCE);
}
return(AI_ACTION_DOCTOR);
}
else
{
pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, MercPtrs[ubPerson]->sGridNo, 20, AI_ACTION_SEEK_FRIEND, 0);
if (!TileIsOutOfBounds(pSoldier->aiData.usActionData))
{
return(AI_ACTION_SEEK_FRIEND);
}
}
}
}
}
////////////////////////////////////////////////////////////////////////////
@@ -2854,6 +2937,48 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK)
}
}
}
// if we are a doctor with medical gear, we might be able to help a wounded ally
if ( pSoldier->CanMedicAI() )
{
UINT8 ubPerson = GetClosestWoundedSoldierID( pSoldier, gGameExternalOptions.sEnemyMedicsSearchRadius, pSoldier->bTeam);
if ( ubPerson != NOBODY )
{
if ( PythSpacesAway(pSoldier->sGridNo, MercPtrs[ubPerson]->sGridNo) < 2 )
{
// see if we are facing this person
UINT8 ubDesiredMercDir = atan8(CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX(MercPtrs[ubPerson]->sGridNo),CenterY(MercPtrs[ubPerson]->sGridNo));
// if not already facing in that direction,
if ( pSoldier->ubDirection != ubDesiredMercDir )
{
pSoldier->aiData.usActionData = ubDesiredMercDir;
return( AI_ACTION_CHANGE_FACING );
}
// if not already crouched, crouch down first
if ( gAnimControl[ pSoldier->usAnimState ].ubHeight != ANIM_CROUCH && IsValidStance( pSoldier, ANIM_CROUCH ) && GetAPsToChangeStance( pSoldier, ANIM_CROUCH ) <= pSoldier->bActionPoints )
{
pSoldier->aiData.usActionData = ANIM_CROUCH;
return(AI_ACTION_CHANGE_STANCE);
}
return(AI_ACTION_DOCTOR);
}
else
{
pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, MercPtrs[ubPerson]->sGridNo, 20, AI_ACTION_SEEK_FRIEND, 0);
if (!TileIsOutOfBounds(pSoldier->aiData.usActionData))
{
return(AI_ACTION_SEEK_FRIEND);
}
}
}
}
}
////////////////////////////////////////////////////////////////////////////
+4
View File
@@ -104,6 +104,7 @@ typedef enum
AI_ACTION_JUMP_WINDOW, // added by Flugente: jump through a window
AI_ACTION_FREE_PRISONER, // added by Flugente: free a prisoner
AI_ACTION_USE_SKILL, // added by Flugente: perform a skill, which one is stored in usAISkillUse
AI_ACTION_DOCTOR, // added by Flugente: AI-ONLY! bandage/surgery on fellow AI. DO NOT USE THIS FOR MERCS!!!
} ActionType;
@@ -262,4 +263,7 @@ UINT8 NumberOfTeamMatesAdjacent( SOLDIERTYPE * pSoldier, INT32 sGridNo );
// Flugente: get the id of the closest soldier (coser than x tiles) of a specific team with a specific flag that we can currently see
UINT8 GetClosestFlaggedSoldierID( SOLDIERTYPE * pSoldier, INT16 aRange, UINT8 auTeam, UINT32 aFlag );
// get the id of the closest soldier (closer than x tiles) of a specific team that is wounded that we can currently see
UINT8 GetClosestWoundedSoldierID( SOLDIERTYPE * pSoldier, INT16 aRange, UINT8 auTeam );
#endif
+1 -1
View File
@@ -8479,7 +8479,7 @@ STR16 szTacticalCoverDialogString[]=
L"Merc",
L"",
L"",
L"Roles", // TODO.Translate
L"",
L"",
L"",
+1 -1
View File
@@ -8493,7 +8493,7 @@ STR16 szTacticalCoverDialogString[]=
L"Merc",
L"",
L"",
L"Roles", // TODO.Translate
L"",
L"",
L"",
+1 -1
View File
@@ -8477,7 +8477,7 @@ STR16 szTacticalCoverDialogString[]=
L"Merc",
L"",
L"",
L"Roles",
L"",
L"",
L"",
+1 -1
View File
@@ -8475,7 +8475,7 @@ STR16 szTacticalCoverDialogString[]=
L"Merc.",
L"",
L"",
L"Roles", // TODO.Translate
L"",
L"",
L"",
+1 -1
View File
@@ -8303,7 +8303,7 @@ STR16 szTacticalCoverDialogString[]=
L"Merc",
L"",
L"",
L"Roles", // TODO.Translate
L"",
L"",
L"",
+1 -1
View File
@@ -8485,7 +8485,7 @@ STR16 szTacticalCoverDialogString[]=
L"Merc",
L"",
L"",
L"Roles", // TODO.Translate
L"",
L"",
L"",
+1 -1
View File
@@ -8503,7 +8503,7 @@ STR16 szTacticalCoverDialogString[]=
L"Merc",
L"",
L"",
L"Roles", // TODO.Translate
L"",
L"",
L"",
+1 -1
View File
@@ -8482,7 +8482,7 @@ STR16 szTacticalCoverDialogString[]=
L"Merc",
L"",
L"",
L"Roles", // TODO.Translate
L"",
L"",
L"",
+1 -1
View File
@@ -8493,7 +8493,7 @@ STR16 szTacticalCoverDialogString[]=
L"Merc",
L"",
L"",
L"Roles", // TODO.Translate
L"",
L"",
L"",