diff --git a/GameSettings.cpp b/GameSettings.cpp index 7fa88add..3d29f3ff 100644 --- a/GameSettings.cpp +++ b/GameSettings.cpp @@ -1478,15 +1478,18 @@ void LoadGameExternalOptions() //################# 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.usTurnsToUncover = iniReader.ReadInteger("Tactical Enemy Role Settings", "ENEMYROLES_TURNSTOUNCOVER", 4, 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_SEARCHRADIUS", 20, 5, 60); + gGameExternalOptions.sEnemyMedicsSearchRadius = iniReader.ReadInteger("Tactical Enemy Role Settings", "ENEMY_MEDICS_SEARCHRADIUS", 40, 5, 60); + gGameExternalOptions.sEnemyMedicsWoundMinAmount = iniReader.ReadInteger("Tactical Enemy Role Settings", "ENEMY_MEDICS_WOUND_MINAMOUNT", 5000, 0, 50000); + gGameExternalOptions.fEnemyMedicsHealSelf = iniReader.ReadBoolean("Tactical Enemy Role Settings", "ENEMY_MEDICS_HEAL_SELF", TRUE); 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); + gGameExternalOptions.dEnemyOfficerSurrenderStrengthBonus = iniReader.ReadFloat ("Tactical Enemy Role Settings", "ENEMY_OFFICERS_SURRENDERSTRENGTHBONUS", 0.1f, 0.00f, 1.0f); //################# Tactical Cover System Settings ################## diff --git a/GameSettings.h b/GameSettings.h index 329f6561..b1afc40f 100644 --- a/GameSettings.h +++ b/GameSettings.h @@ -1351,11 +1351,14 @@ typedef struct BOOLEAN fEnemyMedics; FLOAT dEnemyMedicMedKitDrainFactor; INT16 sEnemyMedicsSearchRadius; + INT32 sEnemyMedicsWoundMinAmount; + BOOLEAN fEnemyMedicsHealSelf; BOOLEAN fEnemyOfficers; UINT16 usEnemyOfficersPerTeamSize; UINT16 usEnemyOfficersMax; INT8 sEnemyOfficerSuppressionResistanceBonus; FLOAT dEnemyOfficerMoraleModifier; + FLOAT dEnemyOfficerSurrenderStrengthBonus; // Sandro: Alternative weapon holding (rifles fired from hip / pistols fired one-handed) diff --git a/Tactical/Overhead.cpp b/Tactical/Overhead.cpp index 00c455ea..87003323 100644 --- a/Tactical/Overhead.cpp +++ b/Tactical/Overhead.cpp @@ -10265,6 +10265,26 @@ void PrisonerSurrenderMessageBoxCallBack( UINT8 ubExitValue ) } } + // hostile civs + firstid = gTacticalStatus.Team[ CIV_TEAM ].bFirstID; + lastid = gTacticalStatus.Team[ CIV_TEAM ].bLastID; + for ( uiCnt = firstid, pSoldier = MercPtrs[ uiCnt ]; uiCnt <= lastid; ++uiCnt, ++pSoldier) + { + if( pSoldier->bActive && ( pSoldier->sSectorX == gWorldSectorX ) && ( pSoldier->sSectorY == gWorldSectorY ) && ( pSoldier->bSectorZ == gbWorldSectorZ) ) + { + // if a civilian is not neutral and on the enemy side, add his strength to the team + if ( !pSoldier->aiData.bNeutral && pSoldier->bSide == 1 ) + enemysidestrength += pSoldier->GetSurrenderStrength(); + } + } + + // enemy team gets a bonus if it has officers around + BOOL officertype = OFFICER_NONE; + if ( HighestEnemyOfficersInSector( officertype ) ) + { + enemysidestrength = enemysidestrength * (1.0f + gGameExternalOptions.dEnemyOfficerSurrenderStrengthBonus * officertype); + } + // print out values if ( gGameExternalOptions.fDisplaySurrenderSValues ) ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113Message[MSG113_SURRENDER_VALUES], playersidestrength, gGameExternalOptions.fSurrenderMultiplier * enemysidestrength ); diff --git a/Tactical/Soldier Control.cpp b/Tactical/Soldier Control.cpp index 8ebd2d1a..d7bc8b26 100644 --- a/Tactical/Soldier Control.cpp +++ b/Tactical/Soldier Control.cpp @@ -12642,9 +12642,9 @@ 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) + // Flugente: AI medics are allowed to perform surgery without first aid kits, and can do this on themselves + if (pVictim->iHealableInjury > 0 && this->fDoingSurgery && (this->ubID != pVictim->ubID || (gGameExternalOptions.fEnemyMedicsHealSelf && this->bTeam == ENEMY_TEAM) ) + && gGameOptions.fNewTraitSystem && (NUM_SKILL_TRAITS( this, DOCTOR_NT ) >= gSkillTraitValues.ubDONumberTraitsNeededForSurgery) && (Item[this->inv[ HANDPOS ].usItem].medicalkit || this->bTeam == ENEMY_TEAM) ) { fOnSurgery = TRUE; @@ -16308,22 +16308,19 @@ void SOLDIERTYPE::DropSectorEquipment() // sevenfm: take item from inventory to HANDPOS void SOLDIERTYPE::TakeNewItemFromInventory(UINT16 usItem) { - INT8 i; - OBJECTTYPE* pObj = NULL; - INT8 invsize = (INT8)this->inv.size(); - if ( !UsingNewInventorySystem() ) return; - // this feature works now only in realtime - if( (gTacticalStatus.uiFlags & TURNBASED && gTacticalStatus.uiFlags & INCOMBAT) ) - return; + // this feature works now only in realtime + if( (gTacticalStatus.uiFlags & TURNBASED && gTacticalStatus.uiFlags & INCOMBAT) ) + return; if(this->inv[HANDPOS].exists()) return; // search for item with same id - for ( i = 0; i < invsize; i++) + INT8 invsize = (INT8)this->inv.size(); + for ( INT8 i = 0; i < invsize; ++i) { if ( ( this->inv[i].exists() == true ) && ( this->inv[i].usItem == usItem ) ) { @@ -16336,25 +16333,24 @@ void SOLDIERTYPE::TakeNewItemFromInventory(UINT16 usItem) // sevenfm: take item from inventory to HANDPOS void SOLDIERTYPE::TakeNewBombFromInventory(UINT16 usItem) { - INT8 i; - OBJECTTYPE* pObj = NULL; - INT8 invsize = (INT8)this->inv.size(); - - if ( !UsingNewInventorySystem() ) - return; + INT8 i; + + if ( !UsingNewInventorySystem() ) + return; - if(this->inv[HANDPOS].exists()) - return; + if(this->inv[HANDPOS].exists()) + return; - // search for item with same id - for ( i = 0; i < invsize; i++) + // search for item with same id + INT8 invsize = (INT8)this->inv.size(); + for ( i = 0; i < invsize; ++i) + { + if ( ( this->inv[i].exists() == true ) && ( this->inv[i].usItem == usItem ) ) { - if ( ( this->inv[i].exists() == true ) && ( this->inv[i].usItem == usItem ) ) - { - this->inv[i].MoveThisObjectTo(this->inv[HANDPOS], 1, this); - return; - } + this->inv[i].MoveThisObjectTo(this->inv[HANDPOS], 1, this); + return; } + } // search for any item with class IC_BOMB // take tripwire-activated item only if used item is tripwire activated @@ -16988,16 +16984,7 @@ INT8 SOLDIERTYPE::GetSuppressionResistanceBonus() BOOL officertype = OFFICER_NONE; if ( HighestEnemyOfficersInSector( officertype ) ) { - switch ( officertype ) - { - case OFFICER_LIEUTNANT: - bonus += gGameExternalOptions.sEnemyOfficerSuppressionResistanceBonus; - break; - - case OFFICER_CAPTAIN: - bonus += gGameExternalOptions.sEnemyOfficerSuppressionResistanceBonus * 2; - break; - } + bonus += gGameExternalOptions.sEnemyOfficerSuppressionResistanceBonus * officertype; } } @@ -17098,16 +17085,7 @@ FLOAT SOLDIERTYPE::GetMoraleModifier() BOOL officertype = OFFICER_NONE; if ( HighestEnemyOfficersInSector( officertype ) ) { - switch ( officertype ) - { - case OFFICER_LIEUTNANT: - mod += gGameExternalOptions.dEnemyOfficerMoraleModifier; - break; - - case OFFICER_CAPTAIN: - mod += gGameExternalOptions.dEnemyOfficerMoraleModifier * 2; - break; - } + mod += gGameExternalOptions.dEnemyOfficerMoraleModifier * officertype; } return mod; @@ -18298,6 +18276,10 @@ BOOLEAN SOLDIERTYPE::CanMedicAI() if ( !gGameExternalOptions.fEnemyRoles || !gGameExternalOptions.fEnemyMedics || this->bTeam != ENEMY_TEAM ) return FALSE; + // this is not for tanks + if ( TANK(this) ) + return FALSE; + if ( HAS_SKILL_TRAIT( this, DOCTOR_NT) ) { if ( FindFirstAidKit(this) != NO_SLOT || FindMedKit(this) != NO_SLOT ) @@ -18326,8 +18308,12 @@ BOOLEAN SOLDIERTYPE::AIDoctorFriend() 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 ) + // this is not for tanks + if ( TANK(pSoldier) ) + return FALSE; + + // if this guy is wounded, heal him (should always be the case, otherwise this function was called needlessly) + if ( pSoldier->iHealableInjury > 0 ) { // move medkit into hand - if we don't have a medkit in our hands, abort if ( !MakeSureMedKitIsInHand( this ) ) @@ -18365,6 +18351,49 @@ BOOLEAN SOLDIERTYPE::AIDoctorFriend() return FALSE; } +// AI-only: heal self. Do NOT, repeat, NOT use this with mercs! +BOOLEAN SOLDIERTYPE::AIDoctorSelf() +{ + if ( this->bTeam != ENEMY_TEAM ) + return FALSE; + + // if this guy is wounded, heal him (should always be the case, otherwise this function was called needlessly) + if ( this->iHealableInjury > 0 ) + { + // 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 = this->stats.bLife; + + UINT16 uiPointsUsed = this->SoldierDressWound( this, usKitPts, usKitPts ); + + UseKitPoints( &(this->inv[ HANDPOS ] ), (UINT16)(uiPointsUsed * gGameExternalOptions.dEnemyMedicMedKitDrainFactor), this ); + + // healing done will be displayed the next time the player sees this soldier + this->flags.fDisplayDamage = TRUE; + this->sDamage -= this->stats.bLife - oldlife; + + // alert ourself + this->aiData.bAlertStatus = min(this->aiData.bAlertStatus, STATUS_RED); + + return TRUE; + } + + return FALSE; +} + INT32 CheckBleeding( SOLDIERTYPE *pSoldier ) { @@ -20934,7 +20963,7 @@ UINT8 RegainDamagedStats( SOLDIERTYPE * pSoldier, UINT16 usAmountRegainedHundred return( 0 ); // Second, run through all damagable stats - for (cnt = 0; cnt < NUM_DAMAGABLE_STATS; cnt++) + for (cnt = 0; cnt < NUM_DAMAGABLE_STATS; ++cnt) { // if we have a damaged stat here if (pSoldier->ubCriticalStatDamage[cnt] > 0 ) @@ -21020,7 +21049,7 @@ UINT8 RegainDamagedStats( SOLDIERTYPE * pSoldier, UINT16 usAmountRegainedHundred break; } // Throw a message if healed anything - if ( gSkillTraitValues.fDORepStShouldThrowMessage ) + if ( gSkillTraitValues.fDORepStShouldThrowMessage && pSoldier->bTeam != ENEMY_TEAM ) { if ( usStatIncreasement == 1 ) ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113Message[MSG113_REGAINED_ONE_POINTS_OF_STAT], pSoldier->GetName(), sStat ); @@ -21076,7 +21105,7 @@ UINT8 RegainDamagedStats( SOLDIERTYPE * pSoldier, UINT16 usAmountRegainedHundred gMercProfiles[ pSoldier->ubProfile ].bLifeMax = pSoldier->stats.bLifeMax; // update profile // Throw a message if healed anything - if ( gSkillTraitValues.fDORepStShouldThrowMessage ) + if ( gSkillTraitValues.fDORepStShouldThrowMessage && pSoldier->bTeam != ENEMY_TEAM ) { if ( usStatIncreasement == 1 ) ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113Message[MSG113_REGAINED_ONE_POINTS_OF_STAT], pSoldier->GetName(), sStat ); @@ -21119,7 +21148,7 @@ UINT8 RegainDamagedStats( SOLDIERTYPE * pSoldier, UINT16 usAmountRegainedHundred gMercProfiles[ pSoldier->ubProfile ].bStrength = pSoldier->stats.bStrength; // update profile // Throw a message if healed anything - if ( gSkillTraitValues.fDORepStShouldThrowMessage ) + if ( gSkillTraitValues.fDORepStShouldThrowMessage && pSoldier->bTeam != ENEMY_TEAM ) { if ( usStatIncreasement == 1 ) ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113Message[MSG113_REGAINED_ONE_POINTS_OF_STAT], pSoldier->GetName(), sStat ); diff --git a/Tactical/Soldier Control.h b/Tactical/Soldier Control.h index 2e5c09d9..30d49d99 100644 --- a/Tactical/Soldier Control.h +++ b/Tactical/Soldier Control.h @@ -1828,6 +1828,7 @@ public: 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! + BOOLEAN AIDoctorSelf(); // AI-only: heal self. Do NOT, repeat, NOT use this with mercs! ////////////////////////////////////////////////////////////////////////////// diff --git a/Tactical/Turn Based Input.cpp b/Tactical/Turn Based Input.cpp index 734f801e..57c6de63 100644 --- a/Tactical/Turn Based Input.cpp +++ b/Tactical/Turn Based Input.cpp @@ -7824,7 +7824,7 @@ void HandleTacticalCoverMenu( void ) gzUserDefinedButtonColor[0] = 0; gzUserDefinedButtonColor[1] = FONT_MCOLOR_LTYELLOW; gzUserDefinedButtonColor[2] = FONT_MCOLOR_LTYELLOW; - gzUserDefinedButtonColor[4] = FONT_LTRED; + gzUserDefinedButtonColor[4] = gDisplayEnemyRoles ? FONT_MCOLOR_LTGREEN : FONT_LTRED; gzUserDefinedButtonColor[8] = FONT_ORANGE; gzUserDefinedButtonColor[9] = FONT_ORANGE; gzUserDefinedButtonColor[10] = FONT_ORANGE; diff --git a/TacticalAI/AIMain.cpp b/TacticalAI/AIMain.cpp index e0415d0e..c52af13f 100644 --- a/TacticalAI/AIMain.cpp +++ b/TacticalAI/AIMain.cpp @@ -2312,6 +2312,13 @@ INT8 ExecuteAction(SOLDIERTYPE *pSoldier) } break; + case AI_ACTION_DOCTOR_SELF: + { + pSoldier->AIDoctorSelf(); + ActionDone( pSoldier ); + } + break; + default: #ifdef BETAVERSION NumMessage("ExecuteAction - Illegal action type = ",pSoldier->aiData.bAction); diff --git a/TacticalAI/AIUtils.cpp b/TacticalAI/AIUtils.cpp index 49cf1b8c..274c6922 100644 --- a/TacticalAI/AIUtils.cpp +++ b/TacticalAI/AIUtils.cpp @@ -595,6 +595,7 @@ BOOLEAN IsActionAffordable(SOLDIERTYPE *pSoldier) break; case AI_ACTION_DOCTOR: + case AI_ACTION_DOCTOR_SELF: bMinPointsNeeded = 20; // TODO break; @@ -3089,6 +3090,10 @@ UINT8 GetClosestFlaggedSoldierID( SOLDIERTYPE * pSoldier, INT16 aRange, UINT8 au // must be on the same level if ( pFriend->pathing.bLevel != pSoldier->pathing.bLevel ) continue; + + // this is not for tanks + if ( TANK(pFriend) ) + return FALSE; // skip if this guy is dead if ( pFriend->stats.bLife <= 0 ) @@ -3131,16 +3136,20 @@ UINT8 GetClosestWoundedSoldierID( SOLDIERTYPE * pSoldier, INT16 aRange, UINT8 au if (!pFriend) continue; - // skip ourselves - if (pFriend->ubID == pSoldier->ubID) + // skip ourselves (if not allowed) + if ( !gGameExternalOptions.fEnemyMedicsHealSelf && pFriend->ubID == pSoldier->ubID) continue; // must be on the same level if ( pFriend->pathing.bLevel != pSoldier->pathing.bLevel ) continue; + + // this is not for tanks + if ( TANK(pFriend) ) + return FALSE; - // 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 ) + // skip if this guy is dead, or not wounded (enough) + if ( pFriend->stats.bLife <= 0 || pFriend->iHealableInjury < gGameExternalOptions.sEnemyMedicsWoundMinAmount ) continue; // are we close enough? @@ -3148,7 +3157,60 @@ UINT8 GetClosestWoundedSoldierID( SOLDIERTYPE * pSoldier, INT16 aRange, UINT8 au if ( friendrange < range) { // can we see this guy? - if ( SoldierTo3DLocationLineOfSightTest( pSoldier, pFriend->sGridNo, pSoldier->pathing.bLevel, 3, TRUE, CALC_FROM_WANTED_DIR ) ) + //if ( SoldierTo3DLocationLineOfSightTest( pSoldier, pFriend->sGridNo, pSoldier->pathing.bLevel, 3, TRUE, CALC_FROM_WANTED_DIR ) ) + { + range = friendrange; + id = pFriend->ubID; + } + } + } + + return id; +} + +// get the id of the closest medic (closer than x tiles) of a specific team +UINT8 GetClosestMedicSoldierID( 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 (we seek OTHER people) + if ( pFriend->ubID == pSoldier->ubID) + continue; + + // must be on the same level + if ( pFriend->pathing.bLevel != pSoldier->pathing.bLevel ) + continue; + + // this is not for tanks + if ( TANK(pFriend) ) + return FALSE; + + // skip this guy if he is dead or unconscious + if ( pFriend->stats.bLife < OKLIFE ) + continue; + + // skip if this guy if he is no medic + if ( !pFriend->CanMedicAI() ) + 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; diff --git a/TacticalAI/DecideAction.cpp b/TacticalAI/DecideAction.cpp index 16173633..d4650a85 100644 --- a/TacticalAI/DecideAction.cpp +++ b/TacticalAI/DecideAction.cpp @@ -936,7 +936,20 @@ INT8 DecideActionGreen(SOLDIERTYPE *pSoldier) { UINT8 ubPerson = GetClosestWoundedSoldierID( pSoldier, gGameExternalOptions.sEnemyMedicsSearchRadius, pSoldier->bTeam); - if ( ubPerson != NOBODY ) + // are we ourselves the patient? + if ( ubPerson == pSoldier->ubID ) + { + // 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_SELF); + } + else if ( ubPerson != NOBODY ) { if ( PythSpacesAway(pSoldier->sGridNo, MercPtrs[ubPerson]->sGridNo) < 2 ) { @@ -965,6 +978,24 @@ INT8 DecideActionGreen(SOLDIERTYPE *pSoldier) { pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, MercPtrs[ubPerson]->sGridNo, 20, AI_ACTION_SEEK_FRIEND, 0); + if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) + { + return(AI_ACTION_SEEK_FRIEND); + } + } + } + } + // if we are not a medic, but are wounded, seek a medic + else if ( pSoldier->iHealableInjury >= gGameExternalOptions.sEnemyMedicsWoundMinAmount ) + { + UINT8 ubPerson = GetClosestMedicSoldierID( pSoldier, gGameExternalOptions.sEnemyMedicsSearchRadius / 2, pSoldier->bTeam); + + if ( ubPerson != NOBODY ) + { + if ( PythSpacesAway(pSoldier->sGridNo, MercPtrs[ubPerson]->sGridNo) > 1 ) + { + pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, MercPtrs[ubPerson]->sGridNo, 20, AI_ACTION_SEEK_FRIEND, 0); + if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) { return(AI_ACTION_SEEK_FRIEND); @@ -1619,7 +1650,20 @@ INT8 DecideActionYellow(SOLDIERTYPE *pSoldier) { UINT8 ubPerson = GetClosestWoundedSoldierID( pSoldier, gGameExternalOptions.sEnemyMedicsSearchRadius, pSoldier->bTeam); - if ( ubPerson != NOBODY ) + // are we ourselves the patient? + if ( ubPerson == pSoldier->ubID ) + { + // 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_SELF); + } + else if ( ubPerson != NOBODY ) { if ( PythSpacesAway(pSoldier->sGridNo, MercPtrs[ubPerson]->sGridNo) < 2 ) { @@ -1648,6 +1692,24 @@ INT8 DecideActionYellow(SOLDIERTYPE *pSoldier) { pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, MercPtrs[ubPerson]->sGridNo, 20, AI_ACTION_SEEK_FRIEND, 0); + if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) + { + return(AI_ACTION_SEEK_FRIEND); + } + } + } + } + // if we are not a medic, but are wounded, seek a medic + else if ( pSoldier->iHealableInjury >= gGameExternalOptions.sEnemyMedicsWoundMinAmount ) + { + UINT8 ubPerson = GetClosestMedicSoldierID( pSoldier, gGameExternalOptions.sEnemyMedicsSearchRadius / 2, pSoldier->bTeam); + + if ( ubPerson != NOBODY ) + { + if ( PythSpacesAway(pSoldier->sGridNo, MercPtrs[ubPerson]->sGridNo) > 1 ) + { + pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, MercPtrs[ubPerson]->sGridNo, 20, AI_ACTION_SEEK_FRIEND, 0); + if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) { return(AI_ACTION_SEEK_FRIEND); @@ -2943,7 +3005,20 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) { UINT8 ubPerson = GetClosestWoundedSoldierID( pSoldier, gGameExternalOptions.sEnemyMedicsSearchRadius, pSoldier->bTeam); - if ( ubPerson != NOBODY ) + // are we ourselves the patient? + if ( ubPerson == pSoldier->ubID ) + { + // 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_SELF); + } + else if ( ubPerson != NOBODY ) { if ( PythSpacesAway(pSoldier->sGridNo, MercPtrs[ubPerson]->sGridNo) < 2 ) { @@ -2972,6 +3047,24 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) { pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, MercPtrs[ubPerson]->sGridNo, 20, AI_ACTION_SEEK_FRIEND, 0); + if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) + { + return(AI_ACTION_SEEK_FRIEND); + } + } + } + } + // if we are not a medic, but are wounded, seek a medic + else if ( pSoldier->iHealableInjury >= gGameExternalOptions.sEnemyMedicsWoundMinAmount ) + { + UINT8 ubPerson = GetClosestMedicSoldierID( pSoldier, gGameExternalOptions.sEnemyMedicsSearchRadius / 2, pSoldier->bTeam); + + if ( ubPerson != NOBODY ) + { + if ( PythSpacesAway(pSoldier->sGridNo, MercPtrs[ubPerson]->sGridNo) > 1 ) + { + pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, MercPtrs[ubPerson]->sGridNo, 20, AI_ACTION_SEEK_FRIEND, 0); + if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) { return(AI_ACTION_SEEK_FRIEND); diff --git a/TacticalAI/ai.h b/TacticalAI/ai.h index 9fbfaa3a..2fa6b1b8 100644 --- a/TacticalAI/ai.h +++ b/TacticalAI/ai.h @@ -105,6 +105,7 @@ typedef enum 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!!! + AI_ACTION_DOCTOR_SELF, // added by Flugente: AI-ONLY! bandage/surgery on self. DO NOT USE THIS FOR MERCS!!! } ActionType; @@ -266,4 +267,7 @@ UINT8 GetClosestFlaggedSoldierID( SOLDIERTYPE * pSoldier, INT16 aRange, UINT8 au // 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 ); +// get the id of the closest medic (closer than x tiles) of a specific team +UINT8 GetClosestMedicSoldierID( SOLDIERTYPE * pSoldier, INT16 aRange, UINT8 auTeam ); + #endif