mirror of
https://github.com/1dot13/source.git
synced 2026-07-22 13:40:22 +02:00
git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@4971 3b4a5df2-a311-0410-b5c6-a8a6f20db521
1236 lines
37 KiB
C++
1236 lines
37 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"
|
|
#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
|
|
if ( ( NewOKDestination(pSoldier, sGridNo, fSkipTilesWithMercs, pSoldier->pathing.bLevel ) ) &&
|
|
( !InGas( pSoldier, sGridNo ) ) &&
|
|
( sGridNo != pSoldier->sGridNo ) &&
|
|
( sGridNo != pSoldier->pathing.sBlackList ) )
|
|
/*
|
|
if ( ( NewOKDestination(pSoldier, sGridno, FALSE, pSoldier->pathing.bLevel ) ) &&
|
|
( !(gpWorldLevelData[ sGridno ].ubExtFlags[0] & (MAPELEMENT_EXT_SMOKE | MAPELEMENT_EXT_TEARGAS | MAPELEMENT_EXT_MUSTARDGAS)) || ( pSoldier->inv[ HEAD1POS ].usItem == GASMASK || pSoldier->inv[ HEAD2POS ].usItem == GASMASK ) ) &&
|
|
( sGridno != pSoldier->sGridNo ) &&
|
|
( sGridno != pSoldier->pathing.sBlackList ) )*/
|
|
/*
|
|
if ( ( NewOKDestination(pSoldier,sGridno,ALLPEOPLE, pSoldier->pathing.bLevel ) ) &&
|
|
( !(gpWorldLevelData[ sGridno ].ubExtFlags[0] & (MAPELEMENT_EXT_SMOKE | MAPELEMENT_EXT_TEARGAS | MAPELEMENT_EXT_MUSTARDGAS)) || ( pSoldier->inv[ HEAD1POS ].usItem == GASMASK || pSoldier->inv[ HEAD2POS ].usItem == GASMASK ) ) &&
|
|
( 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))
|
|
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)) &&
|
|
FindGasMask(pSoldier) == NO_SLOT )
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
else // 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) < 1));
|
|
|
|
// 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 ) < 1)) );
|
|
|
|
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) < 1));
|
|
}
|
|
|
|
// 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;
|
|
INT8 bAPsLeft, fPathFlags;
|
|
UINT8 ubRoomRequired = 0, ubTempRoom;
|
|
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], &ubRoomRequired ) )
|
|
{
|
|
// make sure this doesn't interfere with pathing for scripts
|
|
if (!TileIsOutOfBounds(pSoldier->sAbsoluteFinalDestination))
|
|
{
|
|
ubRoomRequired = 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))
|
|
{
|
|
return(NOWHERE);
|
|
}
|
|
|
|
fPathFlags = 0;
|
|
if ( CREATURE_OR_BLOODCAT( pSoldier ) )
|
|
{ /*
|
|
if ( PythSpacesAway( pSoldier->sGridNo, sDesGrid ) <= PATH_CLOSE_RADIUS )
|
|
{
|
|
// then do a limited range path search and see if we can get there
|
|
gubNPCDistLimit = 10;
|
|
if ( !LegalNPCDestination( pSoldier, sDesGrid, ENSURE_PATH, NOWATER, fPathFlags) )
|
|
{
|
|
gubNPCDistLimit = 0;
|
|
return( NOWHERE );
|
|
}
|
|
else
|
|
{
|
|
// allow attempt to path without 'good enough' flag on
|
|
gubNPCDistLimit = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*/
|
|
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 < 8; ubDirection++)
|
|
ubDirChecked[ubDirection] = FALSE;
|
|
|
|
ubDirsLeft = 8;
|
|
|
|
// examine all 8 spots around 'sDesGrid'
|
|
// keep looking while directions remain and a satisfactory one not found
|
|
for (ubDirsLeft = 8; 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
|
|
|
|
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]) ) );
|
|
//NumMessage("sTempDest = ",sTempDest);
|
|
|
|
// 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 ( ubRoomRequired )
|
|
{
|
|
if ( !( InARoom( sTempDest, &ubTempRoom ) && ubTempRoom == ubRoomRequired ) )
|
|
{
|
|
// quit here, limited by room!
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( (fFlags & FLAG_STOPSHORT) && SpacesAway( sDesGrid, sTempDest ) <= STOPSHORTDIST )
|
|
{
|
|
break; // quit here, sGoToGrid is where we are going
|
|
}
|
|
|
|
// 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!
|
|
if (!LegalNPCDestination(pSoldier,sTempDest,IGNORE_PATH,WATEROK,0))
|
|
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)
|
|
{
|
|
/*
|
|
// first, add any additional costs - such as intermediate animations, etc.
|
|
switch(pSoldier->anitype[pSoldier->anim])
|
|
{
|
|
// in theory, no NPC should ever be in one of these animations as
|
|
// things stand (they don't medic anyone), but leave it for robustness
|
|
case START_AID :
|
|
case GIVING_AID : sAnimCost = APBPConstants[AP_STOP_FIRST_AID];
|
|
break;
|
|
|
|
case TWISTOMACH :
|
|
case COLLAPSED : sAnimCost = APBPConstants[AP_GET_UP];
|
|
break;
|
|
|
|
case TWISTBACK :
|
|
case UNCONSCIOUS : sAnimCost = (APBPConstants[AP_ROLL_OVER] + APBPConstants[AP_GET_UP]);
|
|
break;
|
|
|
|
default : sAnimCost = 0;
|
|
}
|
|
|
|
// this is our first cost
|
|
sAPCost += sAnimCost;
|
|
*/
|
|
|
|
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 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 < 8; 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 );
|
|
}
|
|
*/
|