Migrate +AI features

* Drink canteen
* Jump through window
* Use explosives
* Use wirecutters
This commit is contained in:
Asdow
2026-01-23 23:19:07 +02:00
parent 41945842d5
commit bd9cbd9944
14 changed files with 896 additions and 14 deletions
+92 -1
View File
@@ -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
+231
View File
@@ -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;
}
+337 -1
View File
@@ -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
////////////////////////////////////////////////////////////////////////////
+12
View File
@@ -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)