Files
source/TacticalAI/Movement.cpp
T
Sevenfm 25701acf7b Added LegalNPCDestination check to AI location search routines.
SearchForItems: avoid locations with bombs or red smoke danger.
FindFlankingSpot: removed check for edge of the map.

git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@8754 3b4a5df2-a311-0410-b5c6-a8a6f20db521
2020-02-15 13:38:41 +00:00

1190 lines
36 KiB
C++

#ifdef PRECOMPILEDHEADERS
#include "AI All.h"
#else
#include "ai.h"
#include "AIInternals.h"
#include "Isometric Utils.h"
#include "overhead.h"
#include "worldman.h"
#include "pathai.h"
//#include "points.h"
#include "message.h"
#include "Smell.h"
#include "mapscreen.h"
#include "strategic.h"
#include "Strategic Pathing.h"
#include "Soldier macros.h"
#include "Render Fun.h"
#include "Soldier Functions.h" // added by Flugente
#endif
#include "connect.h"
//forward declarations of common classes to eliminate includes
class OBJECTTYPE;
class SOLDIERTYPE;
// from strategic
extern INT16 DirXIncrementer[8];
extern INT16 DirYIncrementer[8];
//
// CJC's DG->JA2 conversion notes
//
// LegalNPCDestination - mode hardcoded to walking; C.O. tear gas related stuff commented out
// TryToResumeMovement - C.O. EscortedMoveCanceled call
// GoAsFarAsPossibleTowards - C.O. stuff related to current animation esp first aid
// SetCivilianDestination - C.O. stuff for if we don't control the civ
int LegalNPCDestination(SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubPathMode, UINT8 ubWaterOK, UINT8 fFlags)
{
BOOLEAN fSkipTilesWithMercs;
if ((sGridNo < 0) || (sGridNo >= GRIDSIZE))
{
#ifdef RECORDNET
fprintf(NetDebugFile,"LegalNPC->pathing.sDestination: ERROR - rcvd invalid gridno %d",GridNo);
#endif
#ifdef BETAVERSION
NumMessage("LegalNPC->pathing.sDestination: ERROR - rcvd invalid gridno ",GridNo);
#endif
return(FALSE);
}
//dnl ch53 121009 Return false if gridno on different level from merc or inside unvisible area
if ((GridNoOnVisibleWorldTile(pSoldier->sGridNo) && gpWorldLevelData[pSoldier->sGridNo].sHeight != gpWorldLevelData[sGridNo].sHeight) || !GridNoOnVisibleWorldTile(sGridNo))
return(FALSE);
// skip mercs if turnbased and adjacent AND not doing an IGNORE_PATH check (which is used almost exclusively by GoAsFarAsPossibleTowards)
fSkipTilesWithMercs = (gfTurnBasedAI && ubPathMode != IGNORE_PATH && SpacesAway( pSoldier->sGridNo, sGridNo ) == 1 );
// if this gridno is an OK destination
// AND the gridno is NOT in a tear-gassed tile when we have no gas mask
// AND someone is NOT already standing there
// AND we're NOT already standing at that gridno
// AND the gridno hasn't been black-listed for us
// Nov 28 98: skip people in destination tile if in turnbased
// sevenfm: disabled additional checks to prevent AI deadlock
if ( NewOKDestination(pSoldier, sGridNo, fSkipTilesWithMercs, pSoldier->pathing.bLevel ) &&
//!InGas( pSoldier, sGridNo ) &&
//!FindBombNearby(pSoldier, sGridNo, DAY_VISION_RANGE/8 ) &&
sGridNo != pSoldier->sGridNo &&
sGridNo != pSoldier->pathing.sBlackList )
{
// if water's a problem, and gridno is in a water tile (bridges are OK)
if (!ubWaterOK && Water(sGridNo, pSoldier->pathing.bLevel))
return(FALSE);
//Madd: added to prevent people from running into gas and fire
/*if ( (gpWorldLevelData[sGridNo].ubExtFlags[pSoldier->pathing.bLevel] & (MAPELEMENT_EXT_TEARGAS | MAPELEMENT_EXT_MUSTARDGAS)) &&
!DoesSoldierWearGasMask( pSoldier ) )
{
return( FALSE );
}
if ( gpWorldLevelData[sGridNo].ubExtFlags[pSoldier->pathing.bLevel] & MAPELEMENT_EXT_BURNABLEGAS )
{
return( FALSE );
}*/
// passed all checks, now try to make sure we can get there!
switch (ubPathMode)
{
// if finding a path wasn't asked for (could have already been done,
// for example), don't bother
case IGNORE_PATH : return(TRUE);
case ENSURE_PATH : if ( FindBestPath( pSoldier, sGridNo, pSoldier->pathing.bLevel, WALKING, COPYROUTE, fFlags ) )
{
return(TRUE); // legal destination
}
else // got this far, but found no clear path,
{
// so test fails
return(FALSE);
}
// *** NOTE: movement mode hardcoded to WALKING !!!!!
case ENSURE_PATH_COST: return(PlotPath(pSoldier,sGridNo,FALSE,FALSE,FALSE,WALKING,FALSE,FALSE,0));
default :
#ifdef BETAVERSION
NumMessage("LegalNPC->pathing.sDestination: ERROR - illegal pathMode = ",ubPathMode);
#endif
return(FALSE);
}
}
// something failed - didn't even have to test path
return(FALSE); // illegal destination
}
int TryToResumeMovement(SOLDIERTYPE *pSoldier, INT32 sGridNo)
{
UINT8 ubGottaCancel = FALSE;
UINT8 ubSuccess = FALSE;
// have to make sure the old destination is still legal (somebody may
// have occupied the destination gridno in the meantime!)
if (LegalNPCDestination(pSoldier,sGridNo,ENSURE_PATH,WATEROK,0))
{
#ifdef DEBUGDECISIONS
STR tempstr;
sprintf(tempstr, "%d CONTINUES MOVEMENT to gridno %d...\n", pSoldier->ubID,sGridno );
DebugAI( tempstr );
#endif
pSoldier->pathing.bPathStored = TRUE; // optimization - Ian
// make him go to it (needed to continue movement across multiple turns)
NewDest(pSoldier,sGridNo);
ubSuccess = TRUE;
// make sure that it worked (check that pSoldier->pathing.sDestination == pSoldier->sGridNo)
if (pSoldier->pathing.sDestination == sGridNo)
{
ubSuccess = TRUE;
}
else
{
#ifdef BETAVERSION
sprintf(tempstr,"TryToResumeMovement: ERROR - NewDest failed for %s, action CANCELED",pSoldier->name);
#ifdef RECORDNET
fprintf(NetDebugFile,"\n\t%s\n",tempstr);
#endif
PopMessage(tempstr);
SaveGame(ERROR_SAVE);
#endif
// must work even for escorted civs, can't just set the flag
CancelAIAction(pSoldier,FORCE);
}
}
else
{
// don't black-list anything here, this situation can come up quite
// legally if another soldier gets in the way between turns
#ifdef BETAVERSION
sprintf(tempstr,"TryToResumeMovement: %d can't continue to gridno %d, no longer legal!",pSoldier->ubID,gridno);
#ifdef RECORDNET
fprintf(NetDebugFile,"\n\t%s\n",tempstr);
#endif
#ifdef DEBUGDECISIONS
AIPopMessage(tempstr);
#endif
#endif
if (!pSoldier->aiData.bUnderEscort)
{
CancelAIAction(pSoldier,DONTFORCE); // no need to force this
}
else
{
// this is an escorted NPC, don't want to just completely stop
// moving, try to find a nearby "next best" destination if possible
pSoldier->aiData.usActionData = GoAsFarAsPossibleTowards(pSoldier,sGridNo,pSoldier->aiData.bAction);
// if it's not possible to get any closer
if (TileIsOutOfBounds(pSoldier->aiData.usActionData))
{
ubGottaCancel = TRUE;
}
else
{
// change his desired destination to this new one
sGridNo = pSoldier->aiData.usActionData;
// GoAsFar... sets pathStored TRUE only if he could go all the way
// make him go to it (needed to continue movement across multiple turns)
NewDest(pSoldier,sGridNo);
// make sure that it worked (check that pSoldier->pathing.sDestination == pSoldier->sGridNo)
if (pSoldier->pathing.sDestination == sGridNo)
ubSuccess = TRUE;
else
ubGottaCancel = TRUE;
}
if (ubGottaCancel)
{
// can't get close, gotta abort the movement!
CancelAIAction(pSoldier,FORCE);
// tell the player doing the escorting that civilian has stopped
//EscortedMoveCanceled(pSoldier,COMMUNICATE);
}
}
}
return(ubSuccess);
}
INT32 NextPatrolPoint(SOLDIERTYPE *pSoldier)
{
// patrol slot 0 is UNUSED, so max patrolCnt is actually only 9
if ((pSoldier->aiData.bPatrolCnt < 1) || (pSoldier->aiData.bPatrolCnt >= MAXPATROLGRIDS))
{
#ifdef BETAVERSION
sprintf(tempstr,"NextPatrolPoint: ERROR: Invalid patrol count = %d for %s",pSoldier->aiData.bPatrolCnt,pSoldier->name);
PopMessage(tempstr);
#endif
return(NOWHERE);
}
pSoldier->aiData.bNextPatrolPnt++;
// if there are no more patrol points, return back to the first one
if (pSoldier->aiData.bNextPatrolPnt > pSoldier->aiData.bPatrolCnt)
pSoldier->aiData.bNextPatrolPnt = 1; // ZERO is not used!
return(pSoldier->aiData.sPatrolGrid[pSoldier->aiData.bNextPatrolPnt]);
}
INT8 PointPatrolAI(SOLDIERTYPE *pSoldier)
{
INT32 sPatrolPoint;
INT8 bOldOrders;
#ifdef DEBUGDECISIONS
STR16 tempstr;
#endif
sPatrolPoint = pSoldier->aiData.sPatrolGrid[pSoldier->aiData.bNextPatrolPnt];
// if we're already there, advance next patrol point
if (pSoldier->sGridNo == sPatrolPoint || pSoldier->aiData.bNextPatrolPnt == 0)
{
// find next valid patrol point
do
{
sPatrolPoint = NextPatrolPoint(pSoldier);
}
while (( !TileIsOutOfBounds(sPatrolPoint)) && !NewOKDestination(pSoldier,sPatrolPoint,IGNOREPEOPLE, pSoldier->pathing.bLevel) );
// if we're back where we started, then ALL other patrol points are junk!
if (pSoldier->sGridNo == sPatrolPoint)
{
#ifdef BETAVERSION
NumMessage("PROBLEM WITH SCENARIO: All other patrol points are invalid for guynum ",pSoldier->ubID);
#endif
// force change of orders & an abort
sPatrolPoint = NOWHERE;
}
}
// if we don't have a legal patrol point
if (TileIsOutOfBounds(sPatrolPoint))
{
#ifdef BETAVERSION
NumMessage("PointPatrolAI: ERROR - no legal patrol point for %d",pSoldier->ubID);
#endif
// over-ride orders to something safer
pSoldier->aiData.bOrders = FARPATROL;
return(FALSE);
}
// make sure we can get there from here at this time, if we can't get all
// the way there, at least do our best to get close
if (LegalNPCDestination(pSoldier,sPatrolPoint,ENSURE_PATH,WATEROK,0))
{
pSoldier->pathing.bPathStored = TRUE; // optimization - Ian
pSoldier->aiData.usActionData = sPatrolPoint;
}
else
{
// temporarily extend roaming range to infinity by changing orders, else
// this won't work if the next patrol point is > 10 tiles away!
bOldOrders = pSoldier->aiData.bOrders;
pSoldier->aiData.bOrders = ONCALL;
pSoldier->aiData.usActionData = GoAsFarAsPossibleTowards(pSoldier,sPatrolPoint,pSoldier->aiData.bAction);
pSoldier->aiData.bOrders = bOldOrders;
// if it's not possible to get any closer, that's OK, but fail this call
if (TileIsOutOfBounds(pSoldier->aiData.usActionData))
return(FALSE);
}
// passed all tests - start moving towards next patrol point
#ifdef DEBUGDECISIONS
sprintf(tempstr,"%s - POINT PATROL to grid %d",pSoldier->name,pSoldier->aiData.usActionData);
AIPopMessage(tempstr);
#endif
return(TRUE);
}
INT8 RandomPointPatrolAI(SOLDIERTYPE *pSoldier)
{
#ifdef DEBUGDECISIONS
STR16 tempstr;
#endif
INT32 sPatrolPoint;
INT8 bOldOrders, bPatrolIndex;
INT8 bCnt;
sPatrolPoint = pSoldier->aiData.sPatrolGrid[pSoldier->aiData.bNextPatrolPnt];
// if we're already there, advance next patrol point
if (pSoldier->sGridNo == sPatrolPoint || pSoldier->aiData.bNextPatrolPnt == 0)
{
// find next valid patrol point
// we keep a count of the # of times we are in here to make sure we don't get into an endless
//loop
bCnt = 0;
do
{
// usPatrolGrid[0] gets used for centre of close etc patrols, so we have to add 1 to the Random #
bPatrolIndex = (INT8) PreRandom( pSoldier->aiData.bPatrolCnt ) + 1;
sPatrolPoint = pSoldier->aiData.sPatrolGrid[ bPatrolIndex];
bCnt++;
}
while ( (sPatrolPoint == pSoldier->sGridNo) || ( (!TileIsOutOfBounds(sPatrolPoint)) && (bCnt < pSoldier->aiData.bPatrolCnt) && !NewOKDestination(pSoldier,sPatrolPoint,IGNOREPEOPLE, pSoldier->pathing.bLevel )) );
if (bCnt == pSoldier->aiData.bPatrolCnt)
{
// ok, we tried doing this randomly, didn't work well, so now do a linear search
pSoldier->aiData.bNextPatrolPnt = 0;
do
{
sPatrolPoint = NextPatrolPoint(pSoldier);
}
while ((!TileIsOutOfBounds(sPatrolPoint)) && !NewOKDestination(pSoldier,sPatrolPoint,IGNOREPEOPLE, pSoldier->pathing.bLevel) );
}
// do nothing this time around
if (pSoldier->sGridNo == sPatrolPoint)
{
return( FALSE );
}
}
// if we don't have a legal patrol point
if (TileIsOutOfBounds(sPatrolPoint))
{
#ifdef BETAVERSION
NumMessage("PointPatrolAI: ERROR - no legal patrol point for %d",pSoldier->ubID);
#endif
// over-ride orders to something safer
pSoldier->aiData.bOrders = FARPATROL;
return(FALSE);
}
// make sure we can get there from here at this time, if we can't get all
// the way there, at least do our best to get close
if (LegalNPCDestination(pSoldier,sPatrolPoint,ENSURE_PATH,WATEROK,0))
{
pSoldier->pathing.bPathStored = TRUE; // optimization - Ian
pSoldier->aiData.usActionData = sPatrolPoint;
}
else
{
// temporarily extend roaming range to infinity by changing orders, else
// this won't work if the next patrol point is > 10 tiles away!
bOldOrders = pSoldier->aiData.bOrders;
pSoldier->aiData.bOrders = SEEKENEMY;
pSoldier->aiData.usActionData = GoAsFarAsPossibleTowards(pSoldier,sPatrolPoint,pSoldier->aiData.bAction);
pSoldier->aiData.bOrders = bOldOrders;
// if it's not possible to get any closer, that's OK, but fail this call
if (TileIsOutOfBounds(pSoldier->aiData.usActionData))
return(FALSE);
}
// passed all tests - start moving towards next patrol point
#ifdef DEBUGDECISIONS
sprintf(tempstr,"%s - POINT PATROL to grid %d",pSoldier->name,pSoldier->aiData.usActionData);
AIPopMessage(tempstr);
#endif
return(TRUE);
}
INT32 InternalGoAsFarAsPossibleTowards(SOLDIERTYPE *pSoldier, INT32 sDesGrid, INT16 bReserveAPs, INT8 bAction, INT8 fFlags )
{
#ifdef DEBUGDECISIONS
STR16 tempstr;
#endif
INT32 sLoop,sAPCost;
INT32 sTempDest,sGoToGrid;
INT32 sOrigin;
UINT16 usMaxDist;
UINT8 ubDirection,ubDirsLeft,ubDirChecked[8],fFound = FALSE;
// anv: changed to INT16 since Soldier->bActionPoints are INT16 too
INT16 bAPsLeft, fPathFlags;
//DBrot: More Rooms
//UINT8 ubRoomRequired = 0, ubTempRoom;
UINT16 usRoomRequired = 0, usTempRoom;
BOOLEAN fAllowDest = FALSE;
// 0verhaul: Make sure to clear the stored path since this always calculates a new one.
// This is needed if the path cannot be found.
pSoldier->pathing.bPathStored = FALSE;
if ( bReserveAPs == -1 )
{
// default reserve points
if ( CREATURE_OR_BLOODCAT( pSoldier ) )
{
bReserveAPs = 0;
}
else
{
bReserveAPs = APBPConstants[MAX_AP_CARRIED];
}
}
sTempDest = -1;
// obtain maximum roaming distance from soldier's sOrigin
usMaxDist = RoamingRange(pSoldier,&sOrigin);
if ( pSoldier->aiData.bOrders <= CLOSEPATROL && (pSoldier->bTeam == CIV_TEAM || pSoldier->ubProfile != NO_PROFILE ) )
{
if ( InARoom( pSoldier->aiData.sPatrolGrid[0], &usRoomRequired ) )
{
// make sure this doesn't interfere with pathing for scripts
if (!TileIsOutOfBounds(pSoldier->sAbsoluteFinalDestination))
{
usRoomRequired = 0;
}
}
}
pSoldier->usUIMovementMode = DetermineMovementMode(pSoldier, bAction );
if ( pSoldier->usUIMovementMode == RUNNING && fFlags & FLAG_CAUTIOUS )
{
pSoldier->usUIMovementMode = WALKING;
}
#ifdef DEBUGDECISIONS
sprintf(tempstr,"%s wants to go towards %d (has range %d)",pSoldier->name,sDesGrid,usMaxDist);
AIPopMessage(tempstr);
#endif
// if soldier is ALREADY at the desired destination, quit right away
if (sDesGrid == pSoldier->sGridNo)
{
return(NOWHERE);
}
// don't try to approach go after noises or enemies actually in water
// would be too easy to throw rocks in water, etc. & distract the AI
if (Water(sDesGrid, pSoldier->pathing.bLevel))
{
return(NOWHERE);
}
fPathFlags = 0;
if ( CREATURE_OR_BLOODCAT( pSoldier ) )
{
fPathFlags = PATH_CLOSE_GOOD_ENOUGH;
}
// first step: try to find an OK destination at or near the desired gridno
if (!LegalNPCDestination(pSoldier,sDesGrid,ENSURE_PATH,NOWATER,fPathFlags))
{
#ifdef DEBUGDECISIONS
AIPopMessage("destination Grid # itself not valid, looking around it");
#endif
if ( CREATURE_OR_BLOODCAT( pSoldier ) )
{
// we tried to get close, failed; abort!
pSoldier->pathing.usPathIndex = pSoldier->pathing.usPathDataSize = 0;
return( NOWHERE );
}
else
{
// else look at the 8 nearest gridnos to sDesGrid for a valid destination
// clear ubDirChecked flag for all 8 directions
for ( ubDirection = 0; ubDirection < NUM_WORLD_DIRECTIONS; ++ubDirection )
ubDirChecked[ubDirection] = FALSE;
ubDirsLeft = NUM_WORLD_DIRECTIONS;
// examine all 8 spots around 'sDesGrid'
// keep looking while directions remain and a satisfactory one not found
for ( ubDirsLeft = NUM_WORLD_DIRECTIONS; ubDirsLeft != 0; --ubDirsLeft )
{
if (fFound)
{
break;
}
// randomly select a direction which hasn't been 'checked' yet
do
{
ubDirection = (UINT8) Random(8);
}
while (ubDirChecked[ubDirection]);
ubDirChecked[ubDirection] = TRUE;
// determine the gridno 1 tile away from current friend in this direction
sTempDest = NewGridNo(sDesGrid,DirectionInc( ubDirection ));
// if that's out of bounds, ignore it & check next direction
if (sTempDest == sDesGrid)
continue;
if (LegalNPCDestination(pSoldier,sTempDest,ENSURE_PATH,NOWATER,0))
{
fFound = TRUE; // found a spot
#ifdef DEBUGDECISIONS
AINumMessage("Found a spot! ubDirection = ",ubDirection + 1);
#endif
break; // stop checking in other directions
}
}
if (!fFound)
{
#ifdef DEBUGDECISIONS
AINumMessage("Couldn't find OK destination around grid #",sDesGrid);
#endif
pSoldier->pathing.usPathIndex = pSoldier->pathing.usPathDataSize = 0;
return(NOWHERE);
}
// found a good grid #, this becomes our actual desired grid #
sDesGrid = sTempDest;
}
}
// HAVE FOUND AN OK destination AND PLOTTED A VALID BEST PATH TO IT
#ifdef DEBUGDECISIONS
AINumMessage("Chosen legal destination is gridno ",sDesGrid);
AINumMessage("Tracing along path, pathRouteToGo = ",pSoldier->pathing.usPathIndex);
#endif
sGoToGrid = pSoldier->sGridNo; // start back where soldier is standing now
sAPCost = 0; // initialize path cost counter
// 0verhaul: If the destination is within the patrol route, allow it. This is necessary to allow an errant soldier
// to return to his patrol route if flanking or other actions have pulled him beyond his allowed range from origin.
if (SpacesAway(sOrigin,sDesGrid) <= usMaxDist)
{
fAllowDest = TRUE;
}
// we'll only go as far along the plotted route as is within our
// permitted roaming range, and we'll stop as soon as we're down to <= 5 APs
sTempDest = pSoldier->sGridNo;
for (sLoop = 0; sLoop < (pSoldier->pathing.usPathDataSize - pSoldier->pathing.usPathIndex); sLoop++)
{
// what is the next gridno in the path?
//sTempDest = NewGridNo( sGoToGrid,DirectionInc( (INT16) (pSoldier->pathing.usPathingData[sLoop] + 1) ) );
//sTempDest = NewGridNo( sGoToGrid,DirectionInc( (UINT8) (pSoldier->pathing.usPathingData[sLoop]) ) );
sTempDest = NewGridNo( sTempDest,DirectionInc( (UINT8) (pSoldier->pathing.usPathingData[sLoop]) ) );
// this should NEVER be out of bounds
if (sTempDest == sGoToGrid)
{
#ifdef BETAVERSION
sprintf(tempstr,"GoAsFarAsPossibleTowards: ERROR - gridno along valid route is invalid! guynum %d, sTempDest = %d",pSoldier->ubID,sTempDest);
#ifdef RECORDNET
fprintf(NetDebugFile,"\n\t%s\n",tempstr);
#endif
PopMessage(tempstr);
SaveGame(ERROR_SAVE);
#endif
break; // quit here, sGoToGrid is where we are going
}
// if this takes us beyond our permitted "roaming range"
// but if it brings us closer, then allow it!
if (SpacesAway(sOrigin,sTempDest) > usMaxDist && !fAllowDest)
break; // quit here, sGoToGrid is where we are going
if ( usRoomRequired )
{
if ( !( InARoom( sTempDest, &usTempRoom ) && usTempRoom == usRoomRequired ) )
{
// quit here, limited by room!
break;
}
}
if ( (fFlags & FLAG_STOPSHORT) && SpacesAway( sDesGrid, sTempDest ) <= STOPSHORTDIST )
{
break; // quit here, sGoToGrid is where we are going
}
// CAN'T CALL PathCost() HERE! IT CALLS findBestPath() and overwrites
// pathRouteToGo !!! Gotta calculate the cost ourselves - Ian
//
//ubAPsLeft = pSoldier->bActionPoints - PathCost(pSoldier,sTempDest,FALSE,FALSE,FALSE,FALSE,FALSE);
if (gfTurnBasedAI)
{
// if we're just starting the "costing" process (first gridno)
if (sLoop == 0)
{
if (pSoldier->usUIMovementMode == RUNNING)
{
sAPCost += GetAPsStartRun( pSoldier ); // changed by SANDRO
}
}
// ATE: Direction here?
sAPCost += EstimateActionPointCost( pSoldier, sTempDest, (INT8) pSoldier->pathing.usPathingData[sLoop], pSoldier->usUIMovementMode, (INT8) sLoop, (INT8) pSoldier->pathing.usPathDataSize );
bAPsLeft = pSoldier->bActionPoints - sAPCost;
}
// if this gridno is NOT a legal NPC destination
// DONT'T test path again - that would replace the traced path! - Ian
// NOTE: It's OK to go *THROUGH* water to try and get to the destination!
// sevenfm: jump over fence code - if current gridno is not legal destination, check next gridno
if (!LegalNPCDestination(pSoldier,sTempDest,IGNORE_PATH,WATEROK,0))
{
// break;
continue;
}
// if after this, we have <= 5 APs remaining, that's far enough, break out
// (the idea is to preserve APs so we can crouch or react if
// necessary, and benefit from the carry-over next turn if not needed)
// This routine is NOT used by any GREEN AI, so such caution is warranted!
if ( gfTurnBasedAI && (bAPsLeft < bReserveAPs) )
break;
else
{
sGoToGrid = sTempDest; // we're OK up to here
// if exactly 5 APs left, don't bother checking any further
if ( gfTurnBasedAI && (bAPsLeft == bReserveAPs) )
break;
}
}
// if it turned out we couldn't go even 1 tile towards the desired gridno
if (sGoToGrid == pSoldier->sGridNo)
{
#ifdef DEBUGDECISIONS
sprintf(tempstr,"%s will go NOWHERE, path doesn't meet criteria",pSoldier->name);
AIPopMessage(tempstr);
#endif
pSoldier->pathing.usPathIndex = pSoldier->pathing.usPathDataSize = 0;
return(NOWHERE); // then go nowhere
}
else
{
// possible optimization - stored path IS good if we're going all the way
if (sGoToGrid == sDesGrid)
{
pSoldier->pathing.bPathStored = TRUE;
pSoldier->pathing.sFinalDestination = sGoToGrid;
}
else if ( pSoldier->pathing.usPathIndex == 0 )
{
// we can hack this surely! -- CJC
pSoldier->pathing.bPathStored = TRUE;
pSoldier->pathing.sFinalDestination = sGoToGrid;
pSoldier->pathing.usPathDataSize = sLoop + 1;
}
#ifdef DEBUGDECISIONS
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_BETAVERSION, L"%d to %d with %d APs left", pSoldier->ubID, sGoToGrid, pSoldier->bActionPoints );
#endif
return( sGoToGrid );
}
}
INT32 GoAsFarAsPossibleTowards(SOLDIERTYPE *pSoldier, INT32 sDesGrid, INT8 bAction)
{
return( InternalGoAsFarAsPossibleTowards( pSoldier, sDesGrid, -1, bAction, 0 ) );
}
void SoldierTriesToContinueAlongPath(SOLDIERTYPE *pSoldier)
{
INT32 usNewGridNo,bAPCost;
// turn off the flag now that we're going to do something about it...
// ATE: USed to be redundent, now if called befroe NewDest can cause some side efects...
// pSoldier->AdjustNoAPToFinishMove( FALSE );
if (pSoldier->aiData.bNewSituation == IS_NEW_SITUATION)
{
CancelAIAction(pSoldier,DONTFORCE);
return;
}
if (TileIsOutOfBounds(pSoldier->aiData.usActionData))
{
CancelAIAction(pSoldier,DONTFORCE);
return;
}
if (!NewOKDestination( pSoldier,pSoldier->aiData.usActionData, TRUE, pSoldier->pathing.bLevel ))
{
CancelAIAction(pSoldier,DONTFORCE);
return;
}
// WANNE: Disabled the following lines to fix the bug, that Fatima will not leave the sector!
// When Fatima wants to leave the sectur, the condition is met, the method returns and Fatima would stay in the sector!
/*
// SANDRO - hack! interrupt issue - we don't want to recalculate our path if no new situation and we are already on move
// i.e. in case we interrupted a soldier who has no idea about us seeing him, he should move along as if nothing is happening
if ( pSoldier->aiData.bNewSituation == NOT_NEW_SITUATION && pSoldier->aiData.bActionInProgress && !TileIsOutOfBounds(pSoldier->pathing.sFinalDestination ))
{
// just set our path to previously decided final destination
NewDest(pSoldier,pSoldier->pathing.sFinalDestination);
return;
}
*/
if (IsActionAffordable(pSoldier))
{
if (pSoldier->aiData.bActionInProgress == FALSE)
{
// start a move that didn't even get started before...
// hope this works...
NPCDoesAct(pSoldier);
// perform the chosen action
pSoldier->aiData.bActionInProgress = ExecuteAction(pSoldier); // if started, mark us as busy
}
else
{
// otherwise we shouldn't have to do anything(?)
}
}
else
{
CancelAIAction(pSoldier,DONTFORCE);
#ifdef TESTAI
DebugMsg( TOPIC_JA2AI, DBG_LEVEL_3,
String("Soldier (%d) HAS NOT ENOUGH AP to continue along path",pSoldier->ubID) );
#endif
}
usNewGridNo = NewGridNo( pSoldier->sGridNo, DirectionInc( (UINT8)pSoldier->pathing.usPathingData[ pSoldier->pathing.usPathIndex ] ) );
// Find out how much it takes to move here!
bAPCost = EstimateActionPointCost( pSoldier, usNewGridNo, (INT8)pSoldier->pathing.usPathingData[ pSoldier->pathing.usPathIndex ], pSoldier->usUIMovementMode, (INT8) pSoldier->pathing.usPathIndex, (INT8) pSoldier->pathing.usPathDataSize );
if (pSoldier->bActionPoints >= bAPCost)
{
// seems to have enough points...
NewDest(pSoldier,usNewGridNo);
// maybe we didn't actually start the action last turn...
pSoldier->aiData.bActionInProgress = TRUE;
#ifdef TESTAI
DebugMsg( TOPIC_JA2AI, DBG_LEVEL_3,
String("Soldier (%d) continues along path",pSoldier->ubID) );
#endif
}
else
{
CancelAIAction(pSoldier,DONTFORCE);
#ifdef TESTAI
DebugMsg( TOPIC_JA2AI, DBG_LEVEL_3,
String("Soldier (%d) HAS NOT ENOUGH AP to continue along path",pSoldier->ubID) );
#endif
}
}
void HaltMoveForSoldierOutOfPoints(SOLDIERTYPE *pSoldier)
{
// If a special move, ignore this!
if ( ( gAnimControl[ pSoldier->usAnimState ].uiFlags & ANIM_SPECIALMOVE ) )
{
return;
}
if (is_networked)
{
EV_S_STOP_MERC SStopMerc;
SStopMerc.sGridNo = pSoldier->sGridNo;
SStopMerc.ubDirection = pSoldier->ubDirection;
SStopMerc.usSoldierID = pSoldier->ubID;
SStopMerc.fset=TRUE;
SStopMerc.sXPos=pSoldier->sX;
SStopMerc.sYPos=pSoldier->sY;
//AddGameEvent( S_STOP_MERC, 0, &SStopMerc ); //hayden.
if(pSoldier->ubID>=120 || (!is_server && pSoldier->ubID>=20))
return;//hayden
if(is_client)
send_stop(&SStopMerc);
}
// record that this merc can no longer animate and why...
pSoldier->AdjustNoAPToFinishMove( TRUE );
// We'll keep his action intact though...
DebugAI( String("NO AP TO FINISH MOVE for %d (%d APs left)",pSoldier->ubID, pSoldier->bActionPoints) );
// if this dude is under AI right now, then pass the baton to someone else
if (pSoldier->flags.uiStatusFlags & SOLDIER_UNDERAICONTROL)
{
#ifdef TESTAICONTROL
DebugAI( String("Ending turn for %d because out of APs for movement", pSoldier->ubID ) );
#endif
EndAIGuysTurn(pSoldier);
}
}
void SetCivilianDestination(UINT8 ubWho, INT32 sGridNo)
{
SOLDIERTYPE *pSoldier;
pSoldier = MercPtrs[ubWho];
/*
// if we control the civilian
if (PTR_OURCONTROL)
{
*/
// if the destination is different from what he has now
if (pSoldier->aiData.usActionData != sGridNo)
{
// store his new destination
pSoldier->aiData.usActionData = sGridNo;
// and cancel any movement in progress that he was still engaged in
pSoldier->aiData.bAction = AI_ACTION_NONE;
pSoldier->aiData.bActionInProgress = FALSE;
}
// only set the underEscort flag once you give him a destination
// (that way AI can keep him appearing to act on his own until you
// give him orders).
//
// Either way, once set, it should stay that way, preventing AI from
// doing anything other than advance him towards destination.
pSoldier->aiData.bUnderEscort = TRUE;
// change orders to maximize roaming range so he can Go As Far As Possible
pSoldier->aiData.bOrders = ONCALL;
/*
}
else
{
NetSend.msgType = NET_CIV_DEST;
NetSend.ubID = pSoldier->ubID;
NetSend.gridno = gridno;
// only the civilian's controller needs to know this
SendNetData(pSoldier->controller);
}
*/
}
#define RADIUS 3
INT32 TrackScent( SOLDIERTYPE * pSoldier )
{
// This function returns the best gridno to go to based on the scent being followed,
// and the soldier (creature/animal)'s current direction (which is used to resolve
// ties.
INT32 iXDiff, iYDiff, iXIncr;
INT32 iStart, iXStart, iYStart;
INT32 iGridNo;
INT8 bDir;
INT32 iBestGridNo = NOWHERE;
UINT8 ubBestDirDiff = 5, ubBestStrength = 0;
UINT8 ubDirDiff, ubStrength;
UINT8 ubSoughtSmell;
MAP_ELEMENT * pMapElement;
iStart = pSoldier->sGridNo;
iXStart = iStart % WORLD_COLS;
iYStart = iStart / WORLD_COLS;
if (CREATURE_OR_BLOODCAT( pSoldier ) ) // or bloodcats
{
// tracking humans; search the edges of a 7x7 square for the
// most promising tile
ubSoughtSmell = HUMAN;
for (iYDiff = -RADIUS; iYDiff < (RADIUS + 1); iYDiff++)
{
if (iYStart + iYDiff < 0)
{
// outside of map! might be on map further down...
continue;
}
else if (iYStart + iYDiff > WORLD_ROWS)
{
// outside of bottom of map! abort!
break;
}
if (iYDiff == -RADIUS || iYDiff == RADIUS)
{
iXIncr = 1;
}
else
{
// skip over the spots in the centre of the square
iXIncr = RADIUS * 2;
}
for (iXDiff = -RADIUS; iXDiff < (RADIUS + 1); iXDiff += iXIncr)
{
iGridNo = iStart + iXDiff + iYDiff * WORLD_ROWS;
if (abs( iGridNo % WORLD_ROWS - iXStart ) > RADIUS)
{
// wrapped across map!
continue;
}
if (LegalNPCDestination(pSoldier,pSoldier->aiData.usActionData,ENSURE_PATH,WATEROK,0))
{
// check this location out
pMapElement = &(gpWorldLevelData[iGridNo]);
if (pMapElement->ubSmellInfo && (SMELL_TYPE( pMapElement->ubSmellInfo ) == ubSoughtSmell))
{
ubStrength = SMELL_STRENGTH( pMapElement->ubSmellInfo );
if (ubStrength > ubBestStrength )
{
iBestGridNo = iGridNo;
ubBestStrength = ubStrength;
bDir = atan8( (INT16) iXStart, (INT16) iYStart, (INT16) (iXStart + iXDiff), (INT16) (iYStart + iYDiff) );
// now convert it into a difference in degree between it and our current dir
ubBestDirDiff = abs( pSoldier->ubDirection - bDir );
if (ubBestDirDiff > 4 ) // dir 0 compared with dir 6, for instance
{
ubBestDirDiff = 8 - ubBestDirDiff;
}
}
else if (ubStrength == ubBestStrength)
{
if (TileIsOutOfBounds(iBestGridNo))
{
// first place we've found with the same strength
iBestGridNo = iGridNo;
ubBestStrength = ubStrength;
}
else
{
// use directions to decide between the two
// start by calculating direction to the new gridno
bDir = atan8( (INT16) iXStart, (INT16) iYStart, (INT16) (iXStart + iXDiff), (INT16) (iYStart + iYDiff) );
// now convert it into a difference in degree between it and our current dir
ubDirDiff = abs( pSoldier->ubDirection - bDir );
if (ubDirDiff > 4 ) // dir 0 compared with dir 6, for instance
{
ubDirDiff = 8 - ubDirDiff;
}
if (ubDirDiff < ubBestDirDiff || ((ubDirDiff == ubBestDirDiff) && Random( 2 )))
{
// follow this trail as its closer to the one we're following!
// (in the case of a tie, we tossed a coin)
ubBestDirDiff = ubDirDiff;
}
}
}
}
}
}
// go on to next tile
}
// go on to next row
}
else
{
// who else can track?
}
if (!TileIsOutOfBounds(iBestGridNo))
{
pSoldier->aiData.usActionData = iBestGridNo;
return( iBestGridNo );
}
return( 0 );
}
/*
UINT16 RunAway( SOLDIERTYPE * pSoldier )
{
// "Run away! Run away!!!"
// This code should figure out which directions are safe for the enemy
// to run in. They shouldn't try to run off in directions which will
// take them into enemy territory. We must presume that they inform each
// other by radio when sectors are taken by the player! :-)
// The second wrinkle would be to look at the directions to known player
// mercs and use that to influence the direction in which we run.
// we can only flee in the cardinal directions (NESW) so start with an
// alternating pattern of true/false
INT8 bOkayDir[8] = {TRUE, FALSE, TRUE, FALSE, TRUE, FALSE, TRUE, FALSE};
UINT8 ubLoop, ubBestDir, ubDistToEdge, ubBestDistToEdge = WORLD_COLS;
INT32 iSector, iSectorX, iSectorY;
INT32 iNewSectorX, iNewSectorY, iNewSector;
INT32 iRunX, iRunY, iRunGridNo;
SOLDIERTYPE * pOpponent;
iSector = pSoldier->sSectorX + pSoldier->sSectorY * MAP_WORLD_X;
// first start by scanning through opposing mercs and find out what directions are blocked.
for (ubLoop = 0,pOpponent = Menptr; ubLoop < MAXMERCS; ubLoop++,pOpponent++)
{
// if this merc is inactive, at base, on assignment, or dead
if (!pOpponent->bActive || !pOpponent->bInSector || !pOpponent->stats.bLife)
{
continue; // next merc
}
// if this man is neutral / on the same side, he's not an opponent
if (pOpponent->aiData.bNeutral || (pSoldier->bSide == pOpponent->bSide))
{
continue; // next merc
}
// we don't want to run in that direction!
bOkayDir[ atan8( pSoldier->sX, pSoldier->sY, pOpponent->sX, pOpponent->sY ) ] = FALSE;
}
for (ubLoop = 0; ubLoop < NUM_WORLD_DIRECTIONS; ubLoop += 2)
{
if (bOkayDir[ubLoop])
{
// figure out sector # in that direction
iNewSectorX = pSoldier->sSectorX + DirXIncrementer[ubLoop];
iNewSectorY = pSoldier->sSectorY + DirYIncrementer[ubLoop];
iNewSector = iSectorX + iSectorY * MAP_WORLD_X;
// check movement
if (TravelBetweenSectorsIsBlockedFromFoot( (UINT16) iSector, (UINT16) iNewSector ) || StrategicMap[iSector].fEnemyControlled)
{
// sector inaccessible or controlled by the player; skip it!
continue;
}
switch( ubLoop )
{
case 0:
ubDistToEdge = pSoldier->sGridNo / WORLD_COLS;
break;
case 2:
ubDistToEdge = WORLD_COLS - pSoldier->sGridNo % WORLD_COLS;
break;
case 4:
ubDistToEdge = WORLD_ROWS - pSoldier->sGridNo / WORLD_COLS;
break;
case 6:
ubDistToEdge = pSoldier->sGridNo % WORLD_COLS;
break;
}
if (ubDistToEdge < ubBestDistToEdge)
{
ubBestDir = ubLoop;
ubBestDistToEdge = ubDistToEdge;
}
}
}
if (ubBestDistToEdge < WORLD_COLS)
{
switch( ubBestDir )
{
case 0:
iRunX = pSoldier->sX + Random( 9 ) - 4;
iRunY = 0;
if (iRunX < 0)
{
iRunX = 0;
}
else if (iRunX >= WORLD_COLS)
{
iRunX = WORLD_COLS - 1;
}
break;
case 2:
iRunX = WORLD_COLS;
iRunY = pSoldier->sY + Random( 9 ) - 4;
if (iRunY < 0)
{
iRunY = 0;
}
else if (iRunY >= WORLD_COLS)
{
iRunY = WORLD_ROWS - 1;
}
break;
case 4:
iRunX = pSoldier->sX + Random( 9 ) - 4;
iRunY = WORLD_ROWS;
if (iRunX < 0)
{
iRunX = 0;
}
else if (iRunX >= WORLD_COLS)
{
iRunX = WORLD_COLS - 1;
}
break;
case 6:
iRunX = 0;
iRunY = pSoldier->sY + Random( 9 ) - 4;
if (iRunY < 0)
{
iRunY = 0;
}
else if (iRunY >= WORLD_COLS)
{
iRunY = WORLD_ROWS - 1;
}
break;
}
iRunGridNo = iRunX + iRunY * WORLD_COLS;
if (LegalNPCDestination( pSoldier, (UINT16) iRunGridNo, ENSURE_PATH, TRUE,0))
{
return( (UINT16) iRunGridNo );
}
// otherwise we'll try again another time
}
return( NOWHERE );
}
*/