diff --git a/Tactical/Food.cpp b/Tactical/Food.cpp index 5feeb57d..0fc5a5a8 100644 --- a/Tactical/Food.cpp +++ b/Tactical/Food.cpp @@ -758,6 +758,29 @@ void EatFromInventory( SOLDIERTYPE *pSoldier, BOOLEAN fcanteensonly ) } } +void DrinkFromInventory(SOLDIERTYPE* pSoldier) +{ + if ( !pSoldier ) + return; + + INT8 invsize = pSoldier->inv.size(); + + for ( INT8 bLoop = 0; bLoop < invsize; ++bLoop ) + { + if ( pSoldier->inv[bLoop].exists() && ItemIsCanteen(pSoldier->inv[bLoop].usItem) ) + { + OBJECTTYPE* pObj = &(pSoldier->inv[bLoop]); + + if ( pObj && TotalPoints(pObj) > 1 ) + { + //ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_DRINKS], pSoldier->GetName()); + ApplyConsumable(pSoldier, pObj, false, true); + return; + } + } + } +} + void HourlyFoodUpdate( void ) { SoldierID bMercID, bLastTeamID; diff --git a/Tactical/Food.h b/Tactical/Food.h index c0181e02..e876aae2 100644 --- a/Tactical/Food.h +++ b/Tactical/Food.h @@ -108,5 +108,6 @@ void SoldierAutoFillCanteens(SOLDIERTYPE *pSoldier); BOOLEAN HasFoodInInventory( SOLDIERTYPE *pSoldier, BOOLEAN fCheckFood, BOOLEAN fCheckDrink ); void DrinkFromWaterTap( SOLDIERTYPE *pSoldier ); +void DrinkFromInventory(SOLDIERTYPE* pSoldier); #endif diff --git a/Tactical/Items.cpp b/Tactical/Items.cpp index c8e802e8..e3d7031c 100644 --- a/Tactical/Items.cpp +++ b/Tactical/Items.cpp @@ -12426,6 +12426,51 @@ INT8 FindCamoKit( SOLDIERTYPE * pSoldier ) return( NO_SLOT ); } +INT8 FindCanteen(SOLDIERTYPE* pSoldier) +{ + const INT8 invsize = pSoldier->inv.size(); + for ( INT8 bLoop = 0; bLoop < invsize; ++bLoop ) + { + if ( pSoldier->inv[bLoop].exists() && ItemIsCanteen(pSoldier->inv[bLoop].usItem) ) + { + OBJECTTYPE* pObj = &(pSoldier->inv[bLoop]); + if ( pObj && TotalPoints(pObj) > 1 ) + { + return(bLoop); + } + } + } + return(NO_SLOT); +} + +INT8 FindWirecutters(SOLDIERTYPE* pSoldier) +{ + const INT8 invsize = pSoldier->inv.size(); + for ( INT8 bLoop = 0; bLoop < invsize; ++bLoop ) + { + if ( pSoldier->inv[bLoop].exists() && ItemIsWirecutters(pSoldier->inv[bLoop].usItem) ) + { + return(bLoop); + } + } + + return NO_SLOT; +} + +INT8 FindTNT(SOLDIERTYPE* pSoldier) +{ + const INT8 invsize = pSoldier->inv.size(); + for ( INT8 bLoop = 0; bLoop < invsize; ++bLoop ) + { + if ( pSoldier->inv[bLoop].exists() && ItemIsTNT(pSoldier->inv[bLoop].usItem) ) + { + return(bLoop); + } + } + + return NO_SLOT; +} + //JMich_SkillModifiers: Adding a function to see if we have an item with disarm bonus INT8 FindDisarmKit( SOLDIERTYPE * pSoldier ) { @@ -16045,6 +16090,20 @@ BOOLEAN FindAttachmentRange(UINT16 usAttachment, UINT32* pStartIndex, UINT32* pE return result; } + +BOOLEAN ItemIsTNT(UINT16 usItem) +{ + if ( Item[usItem].usItemClass == IC_BOMB && + !ItemIsMine(usItem) && + !ItemIsTripwire(usItem) && + Explosive[Item[usItem].ubClassIndex].ubType == EXPLOSV_NORMAL && + Explosive[Item[usItem].ubClassIndex].ubDamage > 40 && + GetLauncherFromLaunchable(usItem) == NOTHING ) + return TRUE; + + return FALSE; +} + //////////////////////////////////// // Item flagmask utility functions // Just to improve readability diff --git a/Tactical/Items.h b/Tactical/Items.h index 05060e95..0f18cc18 100644 --- a/Tactical/Items.h +++ b/Tactical/Items.h @@ -249,6 +249,7 @@ BOOLEAN ItemIsOnlyInDisease(UINT16 usItem); BOOLEAN ItemProvidesRobotCamo(UINT16 usItem); BOOLEAN ItemProvidesRobotNightvision(UINT16 usItem); BOOLEAN ItemProvidesRobotLaserBonus(UINT16 usItem); +BOOLEAN ItemIsTNT(UINT16 usItem); //Existing functions without header def's, added them here, just incase I'll need to call //them from the editor. @@ -479,9 +480,13 @@ INT8 FindFirstAidKit( SOLDIERTYPE * pSoldier ); INT8 FindDisarmKit( SOLDIERTYPE * pSoldier ); //JMich_SkillsModifiers: Added function to check for disarm bonus INT8 FindLocksmithKit( SOLDIERTYPE * pSoldier ); INT8 FindCamoKit( SOLDIERTYPE * pSoldier ); +INT8 FindCanteen(SOLDIERTYPE* pSoldier); INT8 FindWalkman( SOLDIERTYPE * pSoldier ); INT8 FindTrigger( SOLDIERTYPE * pSoldier ); INT8 FindRemoteControl( SOLDIERTYPE * pSoldier ); +INT8 FindWirecutters(SOLDIERTYPE* pSoldier); +INT8 FindTNT(SOLDIERTYPE* pSoldier); + INT16 GetWornCamo( SOLDIERTYPE * pSoldier ); INT16 GetCamoBonus( OBJECTTYPE * pObj ); INT16 GetWornStealth( SOLDIERTYPE * pSoldier ); diff --git a/Tactical/Overhead.cpp b/Tactical/Overhead.cpp index 6920158e..a467443c 100644 --- a/Tactical/Overhead.cpp +++ b/Tactical/Overhead.cpp @@ -5031,11 +5031,11 @@ BOOLEAN NewOKDestination( SOLDIERTYPE * pCurrSoldier, INT32 sGridNo, BOOLEAN fPe BOOLEAN fOKCheckStruct; // Allow civilians and NPCs with profile to go off screen, and also enemies if tactical retreat is enabled - auto destinationOffscreen = !(GridNoOnVisibleWorldTile(sGridNo)); - auto hasProfile = pCurrSoldier->ubProfile != NO_PROFILE; - auto isCivilian = pCurrSoldier->bTeam == CIV_TEAM; - auto isEnemy = pCurrSoldier->bTeam == ENEMY_TEAM; - auto retreatAllowed = gGameExternalOptions.fAITacticalRetreat == true; + const auto destinationOffscreen = !(GridNoOnVisibleWorldTile(sGridNo)); + const auto hasProfile = pCurrSoldier->ubProfile != NO_PROFILE; + const auto isCivilian = pCurrSoldier->bTeam == CIV_TEAM; + const auto isEnemy = pCurrSoldier->bTeam == ENEMY_TEAM; + const auto retreatAllowed = gGameExternalOptions.fAITacticalRetreat == TRUE; if (destinationOffscreen && !(isCivilian || hasProfile || (isEnemy && retreatAllowed))) { @@ -5400,6 +5400,34 @@ BOOLEAN TeamMemberNear( INT8 bTeam, INT32 sGridNo, INT32 iRange ) return(FALSE); } +BOOLEAN NotDeafTeamMemberNear(INT8 bTeam, INT32 sGridNo, INT32 iRange) +{ + for ( SoldierID pSoldier = gTacticalStatus.Team[bTeam].bFirstID; pSoldier <= gTacticalStatus.Team[bTeam].bLastID; ++pSoldier ) + { + if ( pSoldier->bActive && + pSoldier->bInSector && + pSoldier->stats.bLife >= OKLIFE && + pSoldier->bDeafenedCounter == 0 ) + { + if ( PythSpacesAway(pSoldier->sGridNo, sGridNo) <= iRange ) + { + return(TRUE); + } + } + } + + return(FALSE); +} + +BOOLEAN PlayerCanHearNoise(SOLDIERTYPE* pSoldier) +{ + if ( pSoldier && + (pSoldier->bVisible == TRUE || NotDeafTeamMemberNear(gbPlayerNum, pSoldier->sGridNo, TACTICAL_RANGE / 2) || NightLight() && !(gTacticalStatus.uiFlags & TURNBASED && gTacticalStatus.uiFlags & INCOMBAT) && NotDeafTeamMemberNear(gbPlayerNum, pSoldier->sGridNo, TACTICAL_RANGE)) ) + return TRUE; + + return FALSE; +} + INT32 FindAdjacentGridEx( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 *pubDirection, INT32 *psAdjustedGridNo, BOOLEAN fForceToPerson, BOOLEAN fDoor, bool allow_diagonal ) { // psAdjustedGridNo gets the original gridno or the new one if updated diff --git a/Tactical/Overhead.h b/Tactical/Overhead.h index 26b10674..ea1cd191 100644 --- a/Tactical/Overhead.h +++ b/Tactical/Overhead.h @@ -261,6 +261,9 @@ void SelectNextAvailSoldier( SOLDIERTYPE *pSoldier ); BOOLEAN TeamMemberNear(INT8 bTeam, INT32 sGridNo, INT32 iRange); BOOLEAN IsValidTargetMerc( SoldierID ubSoldierID ); +BOOLEAN NotDeafTeamMemberNear(INT8 bTeam, INT32 sGridNo, INT32 iRange); +BOOLEAN PlayerCanHearNoise(SOLDIERTYPE* pSoldier); + // FUNCTIONS FOR MANIPULATING MERC SLOTS - A LIST OF ALL ACTIVE MERCS INT32 GetFreeMercSlot( ); INT32 AddMercSlot( SOLDIERTYPE *pSoldier ); @@ -433,4 +436,3 @@ BOOLEAN IsFreeSlotAvailable( int aTeam ); void AttemptToCapturePlayerSoldiers(); #endif - diff --git a/Tactical/Soldier Control.cpp b/Tactical/Soldier Control.cpp index 778f6ae1..b8c0139e 100644 --- a/Tactical/Soldier Control.cpp +++ b/Tactical/Soldier Control.cpp @@ -9700,6 +9700,85 @@ void SOLDIERTYPE::BeginSoldierClimbWallUp( void ) } //------------------------------------------------------------------------------------------ +void SOLDIERTYPE::BeginSoldierJumpWindowAI(void) +{ + DebugAI(AI_MSG_INFO, this, String("check if we can jump through window")); + + //UINT8 ubDirection = this->aiData.usActionData; + UINT8 ubDirection = this->ubDirection; + + INT32 sWindowGridNo = this->sGridNo; + if ( ubDirection == NORTH || ubDirection == WEST ) + sWindowGridNo = NewGridNo(this->sGridNo, (UINT16)DirectionInc((UINT8)ubDirection)); + + DebugAI(AI_MSG_INFO, this, String("sWindowGridNo %d direction %d", sWindowGridNo, ubDirection)); + + if (//CheckWindow(this->sGridNo, ubDirection, gGameExternalOptions.fCanJumpThroughClosedWindows) && + IsJumpableWindowPresentAtGridNo(sWindowGridNo, ubDirection, gGameExternalOptions.fCanJumpThroughClosedWindows) && + //FindWindowJumpDirection(this, this->sGridNo, bDirection, &bDirection) && + this->pathing.bLevel == 0 && + (ubDirection == NORTH || ubDirection == EAST || ubDirection == SOUTH || ubDirection == WEST) ) + { + // Flugente: if we are jumping through an intact window, smash it during our animation + if ( gGameExternalOptions.fCanJumpThroughClosedWindows ) + { + // is there really an intact window that we jump through? + if ( IsJumpableWindowPresentAtGridNo(sWindowGridNo, ubDirection, TRUE) && !IsJumpableWindowPresentAtGridNo(sWindowGridNo, ubDirection, FALSE) ) + { + STRUCTURE* pStructure = FindStructure(sWindowGridNo, STRUCTURE_WALLNWINDOW); + if ( pStructure && !(pStructure->fFlags & STRUCTURE_OPEN) ) + { + DebugAI(AI_MSG_INFO, this, String("jumping through closed window, damage soldier")); + // intact window found. Smash it! + WindowHit(sWindowGridNo, pStructure->usStructureID, (ubDirection == SOUTH || ubDirection == EAST), TRUE, PlayerCanHearNoise(this)); + + // we get a bit of damage for jumping through a window + this->SoldierTakeDamage(0, 2 + 2 * Random(4), 0, 1000, TAKE_DAMAGE_ELECTRICITY, NOBODY, sWindowGridNo, 0); + } + } + } + + this->sTempNewGridNo = NewGridNo(this->sGridNo, (UINT16)DirectionInc(ubDirection)); + this->flags.fDontChargeTurningAPs = TRUE; + EVENT_InternalSetSoldierDesiredDirection(this, ubDirection, FALSE, this->usAnimState); + this->flags.fTurningUntilDone = TRUE; + // ATE: Reset flag to go back to prone... + + // Flugente: In case an animation is missing (civilian bodytypes), we TELEPORT instead + if ( IsAnimationValidForBodyType(this, JUMPWINDOWS) == FALSE ) + { + DebugAI(AI_MSG_INFO, this, String("teleport soldier to %d", this->sTempNewGridNo)); + + // sevenfm: deduct APs for jumping + if ( UsingNewInventorySystem() && FindBackpackOnSoldier(this) != ITEM_NOT_FOUND ) + DeductPoints(this, GetAPsToJumpThroughWindows(this, TRUE), GetBPsToJumpThroughWindows(this, TRUE), SP_MOVEMENT_INTERRUPT); + else + DeductPoints(this, GetAPsToJumpThroughWindows(this, FALSE), GetBPsToJumpThroughWindows(this, FALSE), SP_MOVEMENT_INTERRUPT); + + TeleportSoldier(this, this->sTempNewGridNo, TRUE); + } + else + { + DebugAI(AI_MSG_INFO, this, String("start jumping")); + this->flags.bTurningFromPronePosition = TURNING_FROM_PRONE_OFF; + this->EVENT_InitNewSoldierAnim(JUMPWINDOWS, 0, FALSE); + //this->usPendingAnimation = JUMPWINDOWS; + //EndAIGuysTurn(this); + } + + // Flugente: should be fixed now, re-enable if not + // Flugente: if an AI guy, end turn (weird endless clock syndrome) + //if ( this->bTeam != OUR_TEAM ) + //EndAIGuysTurn( this); + } + else + { + ScreenMsg(FONT_LTRED, MSG_INTERFACE, L"[%d] %s cannot jump", this->ubID, this->GetName()); + DebugAI(AI_MSG_INFO, this, String("CancelAIAction: cannot jump")); + CancelAIAction(this, TRUE); + } +} + UINT32 SleepDartSuccumbChance( SOLDIERTYPE * pSoldier ) { UINT32 uiChance; @@ -26297,3 +26376,13 @@ void SOLDIERTYPE::InitializeExtraData(void) this->delayedDamageFunction = nullptr; } + +UINT8 SOLDIERTYPE::AnimHeight(void) const +{ + return gAnimControl[this->usAnimState].ubHeight; +} + +UINT8 SOLDIERTYPE::AnimEndHeight(void) const +{ + return gAnimControl[this->usAnimState].ubEndHeight; +} diff --git a/Tactical/Soldier Control.h b/Tactical/Soldier Control.h index 3bba03d3..bdb3190a 100644 --- a/Tactical/Soldier Control.h +++ b/Tactical/Soldier Control.h @@ -1788,10 +1788,13 @@ public: void ChangeToFallbackAnimation( UINT8 fallBackDirection ); // sevenfm + void BeginSoldierJumpWindowAI(void); void BreakWindow(void); BOOLEAN CanBreakWindow(void); BOOLEAN CanStartDrag(void); void StartDrag(void); + UINT8 AnimHeight(void) const; + UINT8 AnimEndHeight(void) const; void UpdateRobotControllerGivenController( void ); void UpdateRobotControllerGivenRobot( void ); diff --git a/Tactical/Weapons.cpp b/Tactical/Weapons.cpp index a7702e0f..0a39224b 100644 --- a/Tactical/Weapons.cpp +++ b/Tactical/Weapons.cpp @@ -6089,7 +6089,7 @@ void StructureHit( INT32 iBullet, UINT16 usWeaponIndex, INT16 bWeaponStatus, Sol } } -void WindowHit( INT32 sGridNo, UINT16 usStructureID, BOOLEAN fBlowWindowSouth, BOOLEAN fLargeForce ) +void WindowHit( INT32 sGridNo, UINT16 usStructureID, BOOLEAN fBlowWindowSouth, BOOLEAN fLargeForce, BOOLEAN fSound) { STRUCTURE * pWallAndWindow; DB_STRUCTURE * pWallAndWindowInDB; @@ -6210,10 +6210,12 @@ void WindowHit( INT32 sGridNo, UINT16 usStructureID, BOOLEAN fBlowWindowSouth, B pNode = CreateAnimationTile( &AniParams ); //ddd window{ -CompileWorldMovementCosts(); -//ddd window} - PlayJA2Sample( GLASS_SHATTER1 + Random(2), RATE_11025, MIDVOLUME, 1, SoundDir( sGridNo ) ); - + CompileWorldMovementCosts(); + //ddd window} + if ( fSound ) + { + PlayJA2Sample(GLASS_SHATTER1 + Random(2), RATE_11025, MIDVOLUME, 1, SoundDir(sGridNo)); + } } diff --git a/Tactical/Weapons.h b/Tactical/Weapons.h index 8f6afb7e..2d5d0300 100644 --- a/Tactical/Weapons.h +++ b/Tactical/Weapons.h @@ -456,7 +456,7 @@ INT16 ArmourVersusFirePercent( SOLDIERTYPE * pSoldier ); extern BOOLEAN FireWeapon( SOLDIERTYPE *pSoldier , INT32 sTargetGridNo ); extern void WeaponHit( SoldierID usSoldierID, UINT16 usWeaponIndex, INT16 sDamage, INT16 sBreathLoss, UINT16 usDirection, INT16 sXPos, INT16 sYPos, INT16 sZPos, INT16 sRange, SoldierID ubAttackerID, BOOLEAN fHit, UINT8 ubSpecial, UINT8 ubHitLocation ); extern void StructureHit( INT32 iBullet, UINT16 usWeaponIndex, INT16 bWeaponStatus, SoldierID ubAttackerID, UINT16 sXPos, INT16 sYPos, INT16 sZPos, UINT16 usStructureID, INT32 iImpact, BOOLEAN fStopped ); -extern void WindowHit( INT32 sGridNo, UINT16 usStructureID, BOOLEAN fBlowWindowSouth, BOOLEAN fLargeForce ); +void WindowHit(INT32 sGridNo, UINT16 usStructureID, BOOLEAN fBlowWindowSouth, BOOLEAN fLargeForce, BOOLEAN fSound = TRUE); // HEADROCK HAM 5.1: Moved to Bullets.h extern BOOLEAN InRange( SOLDIERTYPE *pSoldier, INT32 sGridNo ); extern void ShotMiss( SoldierID ubAttackerID, INT32 iBullet ); diff --git a/TacticalAI/AIMain.cpp b/TacticalAI/AIMain.cpp index 8786656e..d4935daa 100644 --- a/TacticalAI/AIMain.cpp +++ b/TacticalAI/AIMain.cpp @@ -49,6 +49,7 @@ #include "Soldier Functions.h" // added by SANDRO #include "Text.h" // sevenfm #include "english.h" // sevenfm: for ESC key +#include "Food.h" #include "connect.h" // needed to use the modularized tactical AI: @@ -2744,6 +2745,30 @@ INT8 ExecuteAction(SOLDIERTYPE *pSoldier) case AI_ACTION_JUMP_WINDOW: { pSoldier->BeginSoldierClimbWindow(); + pSoldier->BeginSoldierJumpWindowAI(); + if ( gfTurnBasedAI ) + { + //if (pSoldier->bActionPoints >= GetAPsToJumpThroughWindows(pSoldier, FALSE) + GetAPsToLook(pSoldier)) + if ( pSoldier->bActionPoints >= GetAPsToLook(pSoldier) ) + { + pSoldier->aiData.bNextAction = AI_ACTION_CHANGE_FACING; + INT32 sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL); + if ( !TileIsOutOfBounds(sClosestOpponent) ) + pSoldier->aiData.usNextActionData = AIDirection(pSoldier->sGridNo, sClosestOpponent); + else + pSoldier->aiData.usNextActionData = PreRandom(8); + } + else + { + pSoldier->aiData.bNextAction = AI_ACTION_END_TURN; + pSoldier->aiData.usNextActionData = 0; + } + } + else + { + pSoldier->aiData.bNextAction = AI_ACTION_WAIT; + pSoldier->aiData.usNextActionData = REALTIME_AI_DELAY / 10; + } ActionDone( pSoldier ); } break; @@ -2806,7 +2831,73 @@ INT8 ExecuteAction(SOLDIERTYPE *pSoldier) } break; - default: + case AI_ACTION_DRINK_CANTEEN: + DrinkFromInventory(pSoldier); + ActionDone(pSoldier); + break; + + case AI_ACTION_HANDLE_ITEM: + iRetCode = HandleItem(pSoldier, pSoldier->aiData.usActionData, pSoldier->pathing.bLevel, pSoldier->inv[HANDPOS].usItem, FALSE); + if ( iRetCode != ITEM_HANDLE_OK ) + { + DebugAI(AI_MSG_INFO, pSoldier, String("CancelAIAction (AI_ACTION_HANDLE_ITEM): HandleItem error code %d", iRetCode)); + CancelAIAction(pSoldier, FORCE); + EndAIGuysTurn(pSoldier); + } + break; + + case AI_ACTION_PLANT_BOMB: + if ( pSoldier->inv[HANDPOS].exists() && + (Item[pSoldier->inv[HANDPOS].usItem].usItemClass & IC_BOMB) ) + { + OBJECTTYPE bombobj; + INT32 sSpot = pSoldier->sGridNo; + + if ( !TileIsOutOfBounds(sSpot) && + pSoldier->inv[HANDPOS].MoveThisObjectTo(bombobj, 1) == 0 ) + { + bombobj.fFlags |= OBJECT_ARMED_BOMB; + bombobj[0]->data.misc.bDetonatorType = BOMB_TIMED; + bombobj[0]->data.misc.usBombItem = bombobj.usItem; + //bombobj[0]->data.misc.ubBombOwner = pSoldier->ubID + 2; + bombobj[0]->data.misc.ubBombOwner = 1; + bombobj[0]->data.misc.bDelay = 1 + Random(2); + //pSoldier->inv[HANDPOS][0]->data.bTrap = EffectiveExplosive(pSoldier) / 20 + EffectiveExpLevel(pSoldier, TRUE) / 2; + pSoldier->inv[HANDPOS][0]->data.bTrap = 6 + SoldierDifficultyLevel(pSoldier); + AddItemToPool(sSpot, &bombobj, INVISIBLE, pSoldier->pathing.bLevel, WORLD_ITEM_ARMED_BOMB, 0); + NotifySoldiersToLookforItems(); + DeductPoints(pSoldier, APBPConstants[AP_INVENTORY_ARM] + APBPConstants[AP_DROP_BOMB], APBPConstants[BP_INVENTORY_ARM] + APBPConstants[BP_DROP_BOMB]); + if ( gAnimControl[pSoldier->usAnimState].ubHeight == ANIM_STAND ) + { + pSoldier->EVENT_InitNewSoldierAnim(DROP_ITEM, 0, FALSE); + } + else if ( gAnimControl[pSoldier->usAnimState].ubHeight == ANIM_CROUCH ) + { + pSoldier->EVENT_InitNewSoldierAnim(CUTTING_FENCE, 0, FALSE); + } + if ( pSoldier->bVisible != -1 ) + { + PlayJA2Sample(THROW_IMPACT_2, RATE_11025, SoundVolume(MIDVOLUME, pSoldier->sGridNo), 1, SoundDir(pSoldier->sGridNo)); + } + ActionDone(pSoldier); + } + else + { + DebugAI(AI_MSG_INFO, pSoldier, String("CancelAIAction (AI_ACTION_PLANT_BOMB): failed to move object")); + CancelAIAction(pSoldier, FORCE); + EndAIGuysTurn(pSoldier); + } + } + else + { + DebugAI(AI_MSG_INFO, pSoldier, String("CancelAIAction (AI_ACTION_PLANT_BOMB): failed to find bomb in hand")); + CancelAIAction(pSoldier, FORCE); + EndAIGuysTurn(pSoldier); + } + + break; + + default: #ifdef BETAVERSION NumMessage("ExecuteAction - Illegal action type = ",pSoldier->aiData.bAction); #endif diff --git a/TacticalAI/AIUtils.cpp b/TacticalAI/AIUtils.cpp index d1160f68..454c7a96 100644 --- a/TacticalAI/AIUtils.cpp +++ b/TacticalAI/AIUtils.cpp @@ -27,6 +27,7 @@ #include "SmokeEffects.h" // sevenfm #include "GameInitOptionsScreen.h" +#include "Structure Wrap.h" ////////////////////////////////////////////////////////////////////////////// // SANDRO - In this file, all APBPConstants[AP_CROUCH] and APBPConstants[AP_PRONE] were changed to GetAPsCrouch() and GetAPsProne() @@ -812,6 +813,18 @@ BOOLEAN IsActionAffordable(SOLDIERTYPE *pSoldier, INT8 bAction) bMinPointsNeeded = 20; // TODO break; + case AI_ACTION_DRINK_CANTEEN: + bMinPointsNeeded = APBPConstants[AP_DRINK]; + break; + + case AI_ACTION_HANDLE_ITEM: + bMinPointsNeeded = 0; + break; + + case AI_ACTION_PLANT_BOMB: + bMinPointsNeeded = APBPConstants[AP_INVENTORY_ARM] + APBPConstants[AP_DROP_BOMB]; + break; + default: #ifdef BETAVERSION //NumMessage("AffordableAction - Illegal action type = ",pSoldier->aiData.bAction); @@ -6327,3 +6340,221 @@ BOOLEAN CheckSuppressionDirection(SOLDIERTYPE *pSoldier, INT32 sTargetGridNo, IN return TRUE; } + +UINT8 CountKnownEnemies(SOLDIERTYPE* pSoldier, INT32 sSpot, INT16 sDistance, INT8 bLevel) +{ + CHECKF(pSoldier); + + SOLDIERTYPE* pOpponent; + INT32 sThreatLoc; + INT8 bThreatLevel; + UINT8 ubNum = 0; + + // loop through all the enemies + for ( UINT32 uiLoop = 0; uiLoop < guiNumMercSlots; ++uiLoop ) + { + pOpponent = MercSlots[uiLoop]; + + // if this merc is inactive, at base, on assignment, dead, unconscious + if ( !pOpponent || pOpponent->stats.bLife < OKLIFE ) + { + continue; + } + + if ( !ValidOpponent(pSoldier, pOpponent) ) + { + continue; + } + + // check knowledge + if ( Knowledge(pSoldier, pOpponent->ubID) == NOT_HEARD_OR_SEEN ) + { + continue; + } + + sThreatLoc = KnownLocation(pSoldier, pOpponent->ubID); + bThreatLevel = KnownLevel(pSoldier, pOpponent->ubID); + + if ( TileIsOutOfBounds(sThreatLoc) ) + { + continue; + } + + if ( PythSpacesAway(sSpot, sThreatLoc) > sDistance ) + { + continue; + } + + if ( bLevel >= 0 && bThreatLevel != bLevel ) + { + continue; + } + + ubNum++; + } + + return ubNum; +} + +UINT8 CountKnownEnemiesInRoom(SOLDIERTYPE* pSoldier, UINT16 usRoom) +{ + CHECKF(pSoldier); + + UINT8 ubNum = 0; + for ( UINT32 uiLoop = 0; uiLoop < guiNumMercSlots; ++uiLoop ) + { + SOLDIERTYPE* pOpponent = MercSlots[uiLoop]; + + // if this merc is inactive, at base, on assignment, dead, unconscious + if ( !pOpponent || pOpponent->stats.bLife < OKLIFE ) + { + continue; + } + + if ( !ValidOpponent(pSoldier, pOpponent) ) + { + continue; + } + + // check public knowledge + if ( Knowledge(pSoldier, pOpponent->ubID) == NOT_HEARD_OR_SEEN ) + { + continue; + } + + INT32 sThreatLoc = KnownLocation(pSoldier, pOpponent->ubID); + + if ( TileIsOutOfBounds(sThreatLoc) ) + { + continue; + } + + // check room + UINT16 usRoomNo; + if ( !InARoom(sThreatLoc, &usRoomNo) ) + { + continue; + } + + if ( usRoomNo != usRoom ) + { + continue; + } + + ubNum++; + } + + return ubNum; +} + +UINT8 CountFriendsInRoom(SOLDIERTYPE* pSoldier, UINT16 usRoom) +{ + CHECKF(pSoldier); + + SOLDIERTYPE* pFriend; + UINT8 ubFriendCount = 0; + UINT16 usRoomNo; + + // Run through each friendly. + for ( SoldierID iCounter = gTacticalStatus.Team[pSoldier->bTeam].bFirstID; iCounter <= gTacticalStatus.Team[pSoldier->bTeam].bLastID; ++iCounter ) + { + pFriend = iCounter; + + if ( pFriend && + pFriend != pSoldier && + pFriend->bActive && + pFriend->stats.bLife >= OKLIFE && + InARoom(pFriend->sGridNo, &usRoomNo) && + usRoomNo == usRoom ) + { + ubFriendCount++; + } + } + + return ubFriendCount; +} + +INT32 CountCorpsesInRoom(SOLDIERTYPE* pSoldier, UINT16 usRoomNo, INT8 bLevel) +{ + CHECKF(pSoldier); + + ROTTING_CORPSE* pCorpse; + INT32 iCount = 0; + + for ( INT32 cnt = 0; cnt < giNumRottingCorpse; ++cnt ) + { + pCorpse = &(gRottingCorpse[cnt]); + + if ( pCorpse && + pCorpse->fActivated && + pCorpse->def.ubType < ROTTING_STAGE2 && + pCorpse->def.ubBodyType <= REGFEMALE && + pCorpse->def.ubAIWarningValue > 0 && + pCorpse->def.bLevel == bLevel && + !TileIsOutOfBounds(pCorpse->def.sGridNo) && + RoomNo(pCorpse->def.sGridNo) == usRoomNo && + (pSoldier->bTeam == ENEMY_TEAM && CorpseEnemyTeam(pCorpse) || pSoldier->bTeam == MILITIA_TEAM && CorpseMilitiaTeam(pCorpse) || pSoldier->bTeam == CIV_TEAM && !pSoldier->aiData.bNeutral) ) + { + iCount++; + } + } + + return iCount; +} + +BOOLEAN FindFenceAroundSpot(INT32 sSpot) +{ + if ( TileIsOutOfBounds(sSpot) ) + { + return FALSE; + } + + INT32 sTempSpot; + + // check adjacent locations + for ( UINT8 ubDirection = 0; ubDirection < NUM_WORLD_DIRECTIONS; ubDirection++ ) + { + sTempSpot = NewGridNo(sSpot, DirectionInc(ubDirection)); + + if ( sTempSpot != sSpot && IsCuttableWireFenceAtGridNo(sTempSpot) ) + { + return TRUE; + } + } + + return FALSE; +} + +BOOLEAN SameRoom(INT32 sSpot1, INT32 sSpot2) +{ + if ( RoomNo(sSpot1) == RoomNo(sSpot2) && sSpot1 != NO_ROOM ) + return TRUE; + + return FALSE; +} + +UINT16 RoomNo(INT32 sSpot) +{ + if ( TileIsOutOfBounds(sSpot) ) + return NO_ROOM; + + return gusWorldRoomInfo[sSpot]; +} + +BOOLEAN CheckWindow(INT32 sSpot, UINT8 ubDirection, BOOLEAN fAllowClosed) +{ + CHECKF(!TileIsOutOfBounds(sSpot)); + + // find window spot + INT32 sWindowSpot = sSpot; + if ( ubDirection == NORTH || ubDirection == WEST ) + sWindowSpot = NewGridNo(sSpot, (UINT16)DirectionInc(ubDirection)); + + //if (IsJumpableWindowPresentAtGridNo(sWindowSpot, ubDirection, gGameExternalOptions.fCanJumpThroughClosedWindows)) + if ( IsJumpableWindowPresentAtGridNo(sWindowSpot, ubDirection, fAllowClosed) ) + { + return TRUE; + } + + return FALSE; +} diff --git a/TacticalAI/DecideAction.cpp b/TacticalAI/DecideAction.cpp index d6bdacbe..fcc6ec4e 100644 --- a/TacticalAI/DecideAction.cpp +++ b/TacticalAI/DecideAction.cpp @@ -11065,7 +11065,7 @@ static ActionType DecideActionVIPretreat(SOLDIERTYPE* pSoldier, bool logAction) //////////////////////////////////////////////////////////////////////////// // CHANGE STANCE //////////////////////////////////////////////////////////////////////////// -ActionType DecideActionChangeStance(SOLDIERTYPE* pSoldier, UINT8 ubCanMove, ATTACKTYPE BestAttack, UINT8 ubBestAttackAction, bool logAction) +static ActionType DecideActionChangeStance(SOLDIERTYPE* pSoldier, UINT8 ubCanMove, ATTACKTYPE BestAttack, UINT8 ubBestAttackAction, bool logAction) { // if not in water and not already crouched, try to crouch down first if (!gfTurnBasedAI || GetAPsToChangeStance(pSoldier, ANIM_CROUCH) <= pSoldier->bActionPoints) @@ -11170,6 +11170,180 @@ static ActionType MoveCloserBeforeShooting(SOLDIERTYPE* pSoldier, ATTACKTYPE att return(AI_ACTION_INVALID); } + +static ActionType DecideBlowUpObstacle(SOLDIERTYPE* pSoldier, INT32 sClosestOpponent) +{ + INT8 bTNTSlot = FindTNT(pSoldier); + + if ( bTNTSlot != NO_SLOT && + pSoldier->bTeam == ENEMY_TEAM && + IsActionAffordable(pSoldier, AI_ACTION_PLANT_BOMB) && + pSoldier->pathing.bLevel == 0 && + !TileIsOutOfBounds(sClosestOpponent) && + (Chance(SoldierDifficultyLevel(pSoldier) * 10) || + InARoom(sClosestOpponent, NULL) && + CountFriendsInRoom(pSoldier, RoomNo(sClosestOpponent)) == 0 && + !SameRoom(sClosestOpponent, pSoldier->sGridNo) && + (CountCorpsesInRoom(pSoldier, RoomNo(sClosestOpponent), 0) > 0 || TeamHighPercentKilled(pSoldier->bTeam) || EstimatePathCostToLocation(pSoldier, sClosestOpponent, pSoldier->pathing.bLevel, FALSE, NULL, NULL) == 0)) ) + { + DebugAI(AI_MSG_INFO, pSoldier, String("found TNT in slot %d, check to plant bomb", bTNTSlot)); + + const INT8 bLevel = pSoldier->pathing.bLevel; + UINT8 ubDesiredDir = AIDirection(pSoldier->sGridNo, sClosestOpponent); + INT32 sCheckSpot; + INT32 sPathCost, sNewPathCost; + INT32 sOriginalGridNo; + UINT16 usRoom; + + for ( UINT8 ubCheckDir = 0; ubCheckDir < NUM_WORLD_DIRECTIONS; ubCheckDir++ ) + { + INT32 sNewSpot = NewGridNo(pSoldier->sGridNo, DirectionInc(ubCheckDir)); + INT32 sNextSpot = NewGridNo(sNewSpot, DirectionInc(ubCheckDir)); + + if ( sNewSpot == pSoldier->sGridNo || + sNextSpot == sNewSpot || + gpWorldLevelData[sNewSpot].sHeight != gpWorldLevelData[pSoldier->sGridNo].sHeight || + gpWorldLevelData[sNextSpot].sHeight != gpWorldLevelData[pSoldier->sGridNo].sHeight || + //ubCheckDir != ubDesiredDir && ubCheckDir != gOneCDirection[ubDesiredDir] && ubCheckDir != gOneCCDirection[ubDesiredDir] || + gubWorldMovementCosts[sNewSpot][ubCheckDir][bLevel] < TRAVELCOST_BLOCKED ) + { + continue; + } + + sCheckSpot = NOWHERE; + + if ( (IsCuttableWireFenceAtGridNo(sNewSpot) && !IsCutWireFenceAtGridNo(sNewSpot) || gubWorldMovementCosts[sNewSpot][ubCheckDir][bLevel] == TRAVELCOST_OBSTACLE) && + IsLocationSittable(sNextSpot, bLevel) && NewOKDestination(pSoldier, sNextSpot, TRUE, bLevel) && !Water(sNextSpot, bLevel) ) + { + // found fence, jump 2 tiles + sCheckSpot = sNextSpot; + } + else if ( (gubWorldMovementCosts[sNewSpot][ubCheckDir][bLevel] == TRAVELCOST_WALL || gubWorldMovementCosts[sNewSpot][ubCheckDir][bLevel] == TRAVELCOST_DOOR) && + IsLocationSittable(sNewSpot, bLevel) && NewOKDestination(pSoldier, sNewSpot, TRUE, bLevel) && !Water(sNewSpot, bLevel) ) + { + // found wall, jump 1 tile + sCheckSpot = sNewSpot; + } + + if ( !TileIsOutOfBounds(sCheckSpot) ) + { + DebugAI(AI_MSG_INFO, pSoldier, String("check if jumping to %d improves path cost", sCheckSpot)); + + // check if removing obstacle improves situation + sPathCost = EstimatePlotPath(pSoldier, sClosestOpponent, FALSE, FALSE, FALSE, RUNNING, pSoldier->bStealthMode, FALSE, 0); + sOriginalGridNo = pSoldier->sGridNo; + pSoldier->sGridNo = sCheckSpot; + sNewPathCost = EstimatePlotPath(pSoldier, sClosestOpponent, FALSE, FALSE, FALSE, RUNNING, pSoldier->bStealthMode, FALSE, 0); + pSoldier->sGridNo = sOriginalGridNo; + + if ( sNewPathCost > 0 && + (sPathCost == 0 || + sPathCost > sNewPathCost && sPathCost - sNewPathCost > APBPConstants[AP_MAXIMUM] || + InARoom(sCheckSpot, &usRoom) && + !SameRoom(sCheckSpot, pSoldier->sGridNo) && + CountFriendsInRoom(pSoldier, usRoom) == 0 && + CountKnownEnemiesInRoom(pSoldier, usRoom) > 0 && + (CountCorpsesInRoom(pSoldier, usRoom, 0) > 0 || TeamHighPercentKilled(pSoldier->bTeam))) ) + { + DebugAI(AI_MSG_INFO, pSoldier, String("blowing up fence/wall/obstacle improves path cost, plant bomb")); + + RearrangePocket(pSoldier, HANDPOS, bTNTSlot, FOREVER); + pSoldier->aiData.usActionData = 0; + + return AI_ACTION_PLANT_BOMB; + } + } + } + } + + return AI_ACTION_INVALID; +} + + +static ActionType DecideJumpWindow(SOLDIERTYPE* pSoldier, INT32 sClosestOpponent) +{ + if ( gGameExternalOptions.fCanJumpThroughWindows && + !pSoldier->bBlindedCounter && + !TileIsOutOfBounds(sClosestOpponent) && + pSoldier->pathing.bLevel == 0 && + pSoldier->AnimEndHeight() >= ANIM_CROUCH && + IsActionAffordable(pSoldier, AI_ACTION_JUMP_WINDOW) ) + { + DebugAI(AI_MSG_INFO, pSoldier, String("check if we can jump through window")); + + const INT8 bLevel = pSoldier->pathing.bLevel; + + for ( UINT8 ubCheckDir = 0; ubCheckDir < NUM_WORLD_DIRECTIONS; ubCheckDir++ ) + { + // cannot jump diagonally + if ( ubCheckDir % 2 != 0 ) + { + continue; + } + + INT32 sNewSpot = NewGridNo(pSoldier->sGridNo, DirectionInc(ubCheckDir)); + + if ( sNewSpot != pSoldier->sGridNo && + CheckWindow(pSoldier->sGridNo, ubCheckDir, gGameExternalOptions.fCanJumpThroughClosedWindows) && + !Water(sNewSpot, bLevel) && + IsLocationSittable(sNewSpot, bLevel) && + NewOKDestination(pSoldier, sNewSpot, TRUE, bLevel) ) + { + DebugAI(AI_MSG_INFO, pSoldier, String("found jumpable window at %d", sNewSpot)); + + // check if jumping improves situation + INT32 sPathCost = EstimatePlotPath(pSoldier, sClosestOpponent, FALSE, FALSE, FALSE, RUNNING, pSoldier->bStealthMode, FALSE, 0); + INT32 sOriginalGridNo = pSoldier->sGridNo; + pSoldier->sGridNo = sNewSpot; + INT32 sNewPathCost = EstimatePlotPath(pSoldier, sClosestOpponent, FALSE, FALSE, FALSE, RUNNING, pSoldier->bStealthMode, FALSE, 0); + pSoldier->sGridNo = sOriginalGridNo; + + if ( sNewPathCost > 0 && (sPathCost == 0 || sPathCost > sNewPathCost && sPathCost - sNewPathCost > APBPConstants[AP_MAXIMUM] || SameRoom(sClosestOpponent, sNewSpot)) ) + { + DebugAI(AI_MSG_INFO, pSoldier, String("jumping improves path cost")); + + if ( gfTurnBasedAI && pSoldier->bActionPoints < pSoldier->bInitialActionPoints ) + { + if ( pSoldier->ubDirection != ubCheckDir && + pSoldier->InternalIsValidStance(ubCheckDir, gAnimControl[pSoldier->usAnimState].ubEndHeight) && + pSoldier->bActionPoints >= GetAPsToLook(pSoldier) ) + { + DebugAI(AI_MSG_INFO, pSoldier, String("turn to window, end turn before jumping")); + pSoldier->aiData.usActionData = ubCheckDir; + pSoldier->aiData.bNextAction = AI_ACTION_END_TURN; + pSoldier->aiData.usNextActionData = 0; + return AI_ACTION_CHANGE_FACING; + } + else + { + DebugAI(AI_MSG_INFO, pSoldier, String("already facing window, end turn before jumping")); + pSoldier->aiData.usActionData = 0; + return AI_ACTION_END_TURN; + } + } + + if ( pSoldier->ubDirection == ubCheckDir ) + { + pSoldier->aiData.usActionData = 0; + return AI_ACTION_JUMP_WINDOW; + } + else if ( pSoldier->InternalIsValidStance(ubCheckDir, gAnimControl[pSoldier->usAnimState].ubEndHeight) && + pSoldier->bActionPoints >= GetAPsToJumpThroughWindows(pSoldier, FALSE) + GetAPsToLook(pSoldier) ) + { + DebugAI(AI_MSG_INFO, pSoldier, String("turn before jumping")); + pSoldier->aiData.usActionData = ubCheckDir; + pSoldier->aiData.bNextAction = AI_ACTION_JUMP_WINDOW; + pSoldier->aiData.usNextActionData = 0; + return AI_ACTION_CHANGE_FACING; + } + } + } + } + } + + return AI_ACTION_INVALID; +} + //----------------------------------------------------------------- // Boxer AI decision routines //----------------------------------------------------------------- @@ -20533,6 +20707,7 @@ INT8 DecideActionBlackSoldier(SOLDIERTYPE* pSoldier) INT8 bOpponentLevel; INT32 distanceToOpponent; INT32 sClosestOpponent = ClosestKnownOpponent(pSoldier, &sOpponentGridNo, &bOpponentLevel, NULL, &distanceToOpponent); + INT32 sClosestSeenOpponent = ClosestSeenOpponent(pSoldier, NULL, NULL); DebugAI(AI_MSG_INFO, pSoldier, String("sClosestOpponent %d", sClosestOpponent)); @@ -20623,6 +20798,25 @@ INT8 DecideActionBlackSoldier(SOLDIERTYPE* pSoldier) pSoldier->aiData.bAIMorale = CalcMorale(pSoldier); + //////////////////////////////////////////////////////////////////////// + // DRINK IF LOW IN BREATH + //////////////////////////////////////////////////////////////////////// + DebugAI(AI_MSG_TOPIC, pSoldier, String("[drink if low on breath]")); + + if ( + !bInWater && + !fDangerousSpot && + pSoldier->bBreath < OKBREATH && + pSoldier->CheckInitialAP() && + Chance(30 - 10 * pSoldier->aiData.bUnderFire) && + IsActionAffordable(pSoldier, AI_ACTION_DRINK_CANTEEN) && + FindCanteen(pSoldier) != NO_SLOT ) + { + DebugAI(AI_MSG_INFO, pSoldier, String("drink from canteen")); + return AI_ACTION_DRINK_CANTEEN; + } + + //////////////////////////////////////////////////////////////////////////// // IF GASSED, OR REALLY TIRED (ON THE VERGE OF COLLAPSING), TRY TO RUN AWAY //////////////////////////////////////////////////////////////////////////// @@ -20826,6 +21020,148 @@ INT8 DecideActionBlackSoldier(SOLDIERTYPE* pSoldier) } } + + //////////////////////////////////////////////////////////////////////////// + // USE EXPLOSIVES + //////////////////////////////////////////////////////////////////////////// + INT8 bTNTSlot = FindTNT(pSoldier); + if ( bTNTSlot != NO_SLOT && + (pSoldier->CheckInitialAP() || gfTurnBasedAI) && + IsActionAffordable(pSoldier, AI_ACTION_PLANT_BOMB) && + !pSoldier->aiData.bUnderFire && + //(pSoldier->aiData.bOrders == SEEKENEMY || pSoldier->RushAttackActive()) && + pSoldier->aiData.bOrders == SEEKENEMY && + !TileIsOutOfBounds(sClosestSeenOpponent) && + PythSpacesAway(pSoldier->sGridNo, sClosestSeenOpponent) > TACTICAL_RANGE_HALF && + CountKnownEnemies(pSoldier, pSoldier->sGridNo, 2) > 0 && + (Chance(SoldierDifficultyLevel(pSoldier) * 10) || InARoom(pSoldier->sGridNo, nullptr) && CountKnownEnemies(pSoldier, pSoldier->sGridNo, 2) > 0) ) + { + DebugAI(AI_MSG_INFO, pSoldier, String("found TNT in slot %d, plant bomb", bTNTSlot)); + + RearrangePocket(pSoldier, HANDPOS, bTNTSlot, FOREVER); + pSoldier->aiData.usActionData = 0; + return AI_ACTION_PLANT_BOMB; + } + + + if ( pSoldier->bTeam == ENEMY_TEAM && + !pSoldier->bBlindedCounter && + (pSoldier->CheckInitialAP() || gfTurnBasedAI) && + !pSoldier->aiData.bUnderFire && + pSoldier->aiData.bAIMorale >= MORALE_CONFIDENT && + //(pSoldier->aiData.bOrders == SEEKENEMY || pSoldier->RushAttackActive()) && + pSoldier->aiData.bOrders == SEEKENEMY && + !TileIsOutOfBounds(sClosestOpponent) && + (Chance(SoldierDifficultyLevel(pSoldier) * 10) || + InARoom(sClosestOpponent, nullptr) && + CountFriendsInRoom(pSoldier, RoomNo(sClosestOpponent)) == 0 && + !SameRoom(sClosestOpponent, pSoldier->sGridNo) && + (CountCorpsesInRoom(pSoldier, RoomNo(sClosestOpponent), 0) > 0 || TeamHighPercentKilled(pSoldier->bTeam) || EstimatePathCostToLocation(pSoldier, sClosestOpponent, pSoldier->pathing.bLevel, FALSE, NULL, NULL) == 0)) ) + { + decision = DecideBlowUpObstacle(pSoldier, sClosestOpponent); + if ( decision != AI_ACTION_INVALID ) + { + return decision; + } + } + + + //////////////////////////////////////////////////////////////////////////// + // CUT A FENCE WITH WIRECUTTERS + //////////////////////////////////////////////////////////////////////////// + INT8 bWirecutterSlot = FindWirecutters(pSoldier); + if ( + (pSoldier->CheckInitialAP() || gfTurnBasedAI) && + !pSoldier->aiData.bUnderFire && + bWirecutterSlot != NO_SLOT && + pSoldier->pathing.bLevel == 0 && + pSoldier->aiData.bAIMorale >= MORALE_CONFIDENT && + //(pSoldier->aiData.bOrders == SEEKENEMY || pSoldier->RushAttackActive()) && + pSoldier->aiData.bOrders == SEEKENEMY && + !TileIsOutOfBounds(sClosestOpponent) && + Chance(SoldierDifficultyLevel(pSoldier) * 20) && + pSoldier->bActionPoints >= GetAPsToCutFence(pSoldier) + GetAPsToLook(pSoldier) && + FindFenceAroundSpot(pSoldier->sGridNo) ) + { + DebugAI(AI_MSG_INFO, pSoldier, String("found wire cutter, check if we can find fence to cut")); + + UINT8 ubDesiredDir = AIDirection(pSoldier->sGridNo, sClosestOpponent); + INT32 sNewSpot; + INT32 sNextSpot; + INT32 sPathCost, sNewPathCost; + INT32 sOriginalGridNo; + + for ( UINT8 ubCheckDir = 0; ubCheckDir < NUM_WORLD_DIRECTIONS; ubCheckDir++ ) + { + // cannot cut fence diagonally, check desired dir + if ( ubCheckDir % 2 != 0 || + ubCheckDir != ubDesiredDir && + ubCheckDir != gOneCDirection[ubDesiredDir] && + ubCheckDir != gOneCCDirection[ubDesiredDir] ) + { + continue; + } + + sNewSpot = NewGridNo(pSoldier->sGridNo, DirectionInc(ubCheckDir)); + sNextSpot = NewGridNo(sNewSpot, DirectionInc(ubCheckDir)); + + if ( sNewSpot != pSoldier->sGridNo && + sNextSpot != sNewSpot && + !Water(sNewSpot, pSoldier->pathing.bLevel) && + !Water(sNextSpot, pSoldier->pathing.bLevel) && + IsCuttableWireFenceAtGridNo(sNewSpot) && + !IsCutWireFenceAtGridNo(sNewSpot) && + IsLocationSittable(sNextSpot, pSoldier->pathing.bLevel) ) + { + DebugAI(AI_MSG_INFO, pSoldier, String("found cuttable fence at %d", sNewSpot)); + + // check if cutting the fence improves situation + sPathCost = EstimatePlotPath(pSoldier, sClosestOpponent, FALSE, FALSE, FALSE, RUNNING, pSoldier->bStealthMode, FALSE, 0); + sOriginalGridNo = pSoldier->sGridNo; + pSoldier->sGridNo = sNewSpot; + sNewPathCost = EstimatePlotPath(pSoldier, sClosestOpponent, FALSE, FALSE, FALSE, RUNNING, pSoldier->bStealthMode, FALSE, 0); + pSoldier->sGridNo = sOriginalGridNo; + + if ( sNewPathCost > 0 && (sPathCost == 0 || sPathCost > sNewPathCost && sPathCost - sNewPathCost > APBPConstants[AP_MAXIMUM]) ) + { + DebugAI(AI_MSG_INFO, pSoldier, String("cutting fence improves path cost, use wire cutter")); + if ( pSoldier->ubDirection == ubCheckDir ) + { + RearrangePocket(pSoldier, HANDPOS, bWirecutterSlot, FOREVER); + pSoldier->aiData.usActionData = sNewSpot; + return AI_ACTION_HANDLE_ITEM; + } + else if ( pSoldier->InternalIsValidStance(ubCheckDir, gAnimControl[pSoldier->usAnimState].ubEndHeight) ) + { + DebugAI(AI_MSG_INFO, pSoldier, String("turn before cutting fence")); + RearrangePocket(pSoldier, HANDPOS, bWirecutterSlot, FOREVER); + pSoldier->aiData.usActionData = ubCheckDir; + pSoldier->aiData.bNextAction = AI_ACTION_HANDLE_ITEM; + pSoldier->aiData.usNextActionData = sNewSpot; + return AI_ACTION_CHANGE_FACING; + } + } + } + } + } + + + //////////////////////////////////////////////////////////////////////////// + // JUMP THROUGH WINDOW + //////////////////////////////////////////////////////////////////////////// + DebugAI(AI_MSG_TOPIC, pSoldier, String("[try to jump into window]")); + if ( (pSoldier->CheckInitialAP() || gfTurnBasedAI) && + !pSoldier->aiData.bUnderFire && + pSoldier->aiData.bAIMorale >= MORALE_CONFIDENT && + pSoldier->aiData.bOrders == SEEKENEMY && + !TileIsOutOfBounds(sClosestOpponent) ) + { + decision = DecideJumpWindow(pSoldier, sClosestOpponent); + if ( decision != AI_ACTION_INVALID ) + return decision; + } + + //////////////////////////////////////////////////////////////////////////// // THROW A SMOKE GRENADE FOR COVER //////////////////////////////////////////////////////////////////////////// diff --git a/TacticalAI/ai.h b/TacticalAI/ai.h index e6f16d45..af5aa1c9 100644 --- a/TacticalAI/ai.h +++ b/TacticalAI/ai.h @@ -106,6 +106,9 @@ typedef enum AI_ACTION_DOCTOR_SELF, // added by Flugente: AI-ONLY! bandage/surgery on self. DO NOT USE THIS FOR MERCS!!! AI_ACTION_SELFDETONATE, // added by Flugente: blow up an explosive in own inventory AI_ACTION_STOP_MEDIC, // sevenfm: stop giving aid animation + AI_ACTION_DRINK_CANTEEN, // sevenfm: drink from canteen in inventory + AI_ACTION_HANDLE_ITEM, // sevenfm: use item in hand + AI_ACTION_PLANT_BOMB, // sevenfm: plant bomb using item in hand AI_ACTION_INVALID } ActionType; @@ -215,6 +218,7 @@ INT32 FindClosestDoor( SOLDIERTYPE * pSoldier ); INT32 FindNearbyPointOnEdgeOfMap( SOLDIERTYPE * pSoldier, INT8 * pbDirection ); INT32 FindNearestEdgePoint( INT32 sGridNo ); INT32 FindNearestPassableSpot( INT32 sGridNo, UINT8 usSearchRadius = 5 ); +BOOLEAN FindFenceAroundSpot(INT32 sSpot); //Kris: Added these as I need specific searches on certain sides. enum @@ -312,6 +316,13 @@ UINT8 CountFriendsBlack( SOLDIERTYPE *pSoldier, INT32 sClosestOpponent = NOWHERE UINT16 CountTeamUnderAttack(INT8 bTeam, INT32 sGridNo, INT16 sDistance); UINT16 CountPublicKnownEnemies(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 sDistance); UINT16 CountPublicKnownEnemies(SOLDIERTYPE *pSoldier); +UINT8 CountKnownEnemies(SOLDIERTYPE* pSoldier, INT32 sSpot, INT16 sDistance, INT8 bLevel = HEARD_THIS_TURN); +UINT8 CountKnownEnemiesInRoom(SOLDIERTYPE* pSoldier, UINT16 usRoom); +UINT8 CountFriendsInRoom(SOLDIERTYPE* pSoldier, UINT16 usRoom); +INT32 CountCorpsesInRoom(SOLDIERTYPE* pSoldier, UINT16 usRoomNo, INT8 bLevel); +UINT16 RoomNo(INT32 sSpot); +BOOLEAN SameRoom(INT32 sSpot1, INT32 sSpot2); +BOOLEAN CheckWindow(INT32 sSpot, UINT8 ubDirection, BOOLEAN fAllowClosed); UINT8 SectorCurfew(BOOLEAN fNight); UINT8 TeamPercentKilled(INT8 bTeam); @@ -434,6 +445,7 @@ INT8 KnownPublicLevel(UINT8 bTeam, SoldierID ubOpponentID); #define TACTICAL_RANGE (gGameExternalOptions.ubStraightSightRange * STRAIGHT_RATIO * 2) #define TACTICAL_RANGE_CELL_COORDS TACTICAL_RANGE*CELL_X_SIZE #define BOMB_DETECTION_RANGE (TACTICAL_RANGE / 4) +#define TACTICAL_RANGE_HALF (TACTICAL_RANGE / 2) #define TACTICAL_RANGE_CLOSE (TACTICAL_RANGE / 4) #define TACTICAL_RANGE_VERYCLOSE (TACTICAL_RANGE / 6)