New option AI_DECISION_INFO enables extended logging for AI decisions.

Moved zombie AI into separate file ZombieDecideAction.cpp
New zombie AI:
- simplified code
- advancing using cover
- if zombie cannot reach any opponent, he tries to hide
- if zombie cannot reach opponent and cannot hide and he is under fire, he will retreat, seeking cover away from known opponents
- if zombie doesn't know enemy location, he will try to run to closest friend who knows enemy location
- new zombie AI has extended AI logging
More water splash sounds (Data\Sounds\Misc\Splash1.ogg .. SplashN.ogg).
CalcCoverValue: 
- allow moving into shallow water if it provides better position
- special calculations for zombies: zombie is very dangerous at close range
- if soldier has no gun, use different calculation based on distance instead of CTGT
FindBestNearbyCover: zombies only choose spots that provide sight cover.
Added functions for extended AI log.
ClosestReachableDisturbance:
- if soldier is zombie and he cannot climb, skip location
- zombies do not attack vehicles
- improved check to avoid choosing dying opponents
EstimatePathCostToLocation: return 0 if checking for zombie which cannot climb and path needs climbing.
RoamingRange: always max roaming range for zombies.
SightCoverAtSpot, ProneSightCoverAtSpot:
- improved max sight range calculation
- added option for unlimited sight range check
New AI functions:
- AIDirection
- AICheckIsSniper
- AICheckIsMarksman
- AICheckIsMedic
- AICheckIsMortarOperator
- AICheckIsGLOperator
- AICheckIsOfficer
- AICheckIsCommander
- AICheckIsMachinegunner
- AIGunInHandScoped
- AIGunScoped
- AIGunRange
- AIGunClass
- AIGunType
- AIGunDeadliness
- AIGunAmmo
- AIGunAutofireCapable
- FindObstacleNearSpot
- InSmoke
- CorpseWarning
- CorpseEnemyTeam
- CorpseMilitiaTeam
- AICheckUnderground
- AnyCoverAtSpot
- AICheckHasWeaponOfType
- AICheckHasGun
- AICheckShortWeaponRange
GetCorpseAtGridNo: additional check for valid gridno.
More zombie raise sounds (Data\Sounds\Misc\ZombieRaise1.ogg .. ZombieRaiseN.ogg).
GetCorpseRotFactor: improved code to return FLOAT value between 0 and 1.
New debug topic id TOPIC_DECISIONS for logging AI decisions.

git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@8760 3b4a5df2-a311-0410-b5c6-a8a6f20db521
This commit is contained in:
Sevenfm
2020-02-21 23:33:25 +00:00
parent 73cbc27ea4
commit 32482f20f6
24 changed files with 3296 additions and 310 deletions
+1
View File
@@ -2295,6 +2295,7 @@ void LoadGameExternalOptions()
gGameExternalOptions.fAITacticalRetreat = iniReader.ReadBoolean("Tactical AI Settings", "AI_TACTICAL_RETREAT", FALSE); gGameExternalOptions.fAITacticalRetreat = iniReader.ReadBoolean("Tactical AI Settings", "AI_TACTICAL_RETREAT", FALSE);
gGameExternalOptions.fAIMovementMode = iniReader.ReadBoolean("Tactical AI Settings", "AI_MOVEMENT_MODE", TRUE); gGameExternalOptions.fAIMovementMode = iniReader.ReadBoolean("Tactical AI Settings", "AI_MOVEMENT_MODE", TRUE);
gGameExternalOptions.fAIPathTweaks = iniReader.ReadBoolean("Tactical AI Settings", "AI_PATH_TWEAKS", TRUE); gGameExternalOptions.fAIPathTweaks = iniReader.ReadBoolean("Tactical AI Settings", "AI_PATH_TWEAKS", TRUE);
gGameExternalOptions.fAIDecisionInfo = iniReader.ReadBoolean("Tactical AI Settings", "AI_DECISION_INFO", false, false);
} }
+1
View File
@@ -1555,6 +1555,7 @@ typedef struct
BOOLEAN fAITacticalRetreat; BOOLEAN fAITacticalRetreat;
BOOLEAN fAIMovementMode; BOOLEAN fAIMovementMode;
BOOLEAN fAIPathTweaks; BOOLEAN fAIPathTweaks;
BOOLEAN fAIDecisionInfo;
} GAME_EXTERNAL_OPTIONS; } GAME_EXTERNAL_OPTIONS;
+1
View File
@@ -3,6 +3,7 @@
const unsigned INVALID_TOPIC = 0xffff; const unsigned INVALID_TOPIC = 0xffff;
const unsigned TOPIC_DECISIONS = 25;
const unsigned TOPIC_MEMORY_MANAGER = 24; const unsigned TOPIC_MEMORY_MANAGER = 24;
const unsigned TOPIC_FILE_MANAGER = 23; const unsigned TOPIC_FILE_MANAGER = 23;
const unsigned TOPIC_DATABASE_MANAGER = 22; const unsigned TOPIC_DATABASE_MANAGER = 22;
+3
View File
@@ -988,6 +988,9 @@ void DeductPoints( SOLDIERTYPE *pSoldier, INT16 sAPCost, INT32 iBPCost, UINT8 ub
if ( pSoldier->usSoldierFlagMask & SOLDIER_COVERT_TEMPORARY_OVERT && pSoldier->usSkillCooldown[SOLDIER_COOLDOWN_COVERTOPS_TEMPORARYOVERT_APS] > 0 ) if ( pSoldier->usSoldierFlagMask & SOLDIER_COVERT_TEMPORARY_OVERT && pSoldier->usSkillCooldown[SOLDIER_COOLDOWN_COVERTOPS_TEMPORARYOVERT_APS] > 0 )
pSoldier->usSkillCooldown[SOLDIER_COOLDOWN_COVERTOPS_TEMPORARYOVERT_APS] = max( 0, (INT16)(pSoldier->usSkillCooldown[SOLDIER_COOLDOWN_COVERTOPS_TEMPORARYOVERT_APS] - sAPCost) ); pSoldier->usSkillCooldown[SOLDIER_COOLDOWN_COVERTOPS_TEMPORARYOVERT_APS] = max( 0, (INT16)(pSoldier->usSkillCooldown[SOLDIER_COOLDOWN_COVERTOPS_TEMPORARYOVERT_APS] - sAPCost) );
// sevenfm: indicate in realtime that soldier spent some action point this turn
pSoldier->usSoldierFlagMask2 |= SOLDIER_SPENT_AP;
pSoldier->StopChatting(); pSoldier->StopChatting();
} }
+33 -11
View File
@@ -60,10 +60,6 @@
class OBJECTTYPE; class OBJECTTYPE;
class SOLDIERTYPE; class SOLDIERTYPE;
#define CORPSE_WARNING_MAX 5
#define CORPSE_WARNING_DIST 5
#define CORPSE_INDEX_OFFSET 10000 #define CORPSE_INDEX_OFFSET 10000
// Flugente: these are ini values now // Flugente: these are ini values now
@@ -501,7 +497,7 @@ INT32 AddRottingCorpse( ROTTING_CORPSE_DEFINITION *pCorpseDef )
uiDirectionUseFlag = ANITILE_USE_DIRECTION_FOR_START_FRAME; uiDirectionUseFlag = ANITILE_USE_DIRECTION_FOR_START_FRAME;
// If we are a soecial type... // If we are a special type...
switch( pCorpseDef->ubType ) switch( pCorpseDef->ubType )
{ {
case SMERC_FALL: case SMERC_FALL:
@@ -527,7 +523,7 @@ INT32 AddRottingCorpse( ROTTING_CORPSE_DEFINITION *pCorpseDef )
if( !(gTacticalStatus.uiFlags & LOADING_SAVED_GAME ) ) if( !(gTacticalStatus.uiFlags & LOADING_SAVED_GAME ) )
{ {
// OK, AS WE ADD, CHECK FOR TOD AND DECAY APPROPRIATELY // OK, AS WE ADD, CHECK FOR TOD AND DECAY APPROPRIATELY
if ( ((GetWorldTotalMin( ) - pCorpse->def.uiTimeOfDeath) > gGameExternalOptions.usCorpseDelayUntilRotting) && (pCorpse->def.ubType < ROTTING_STAGE2) ) if ( ((GetWorldTotalMin( ) - pCorpse->def.uiTimeOfDeath) >= gGameExternalOptions.usCorpseDelayUntilRotting) && (pCorpse->def.ubType < ROTTING_STAGE2) )
{ {
if ( pCorpse->def.ubType <= FMERC_FALLF ) if ( pCorpse->def.ubType <= FMERC_FALLF )
{ {
@@ -1752,8 +1748,6 @@ INT32 FindNearestAvailableGridNoForCorpse( ROTTING_CORPSE_DEFINITION *pDef, INT8
//(use the fake soldier and the pathing settings) //(use the fake soldier and the pathing settings)
FindBestPath( &soldier, GRIDSIZE, 0, WALKING, COPYREACHABLE, 0 );//dnl ch50 071009 FindBestPath( &soldier, GRIDSIZE, 0, WALKING, COPYREACHABLE, 0 );//dnl ch50 071009
uiLowestRange = 999999;
for( cnt1 = sBottom; cnt1 <= sTop; cnt1++ ) for( cnt1 = sBottom; cnt1 <= sTop; cnt1++ )
{ {
leftmost = ( ( sSweetGridNo + ( WORLD_COLS * cnt1 ) )/ WORLD_COLS ) * WORLD_COLS; leftmost = ( ( sSweetGridNo + ( WORLD_COLS * cnt1 ) )/ WORLD_COLS ) * WORLD_COLS;
@@ -1847,14 +1841,17 @@ ROTTING_CORPSE *GetCorpseAtGridNo( INT32 sGridNo, INT8 bLevel )
// Get base.... // Get base....
pBaseStructure = FindBaseStructure( pStructure ); pBaseStructure = FindBaseStructure( pStructure );
if ( pBaseStructure != NULL )
{
// Find base gridno... // Find base gridno...
sBaseGridNo = pBaseStructure->sGridNo; sBaseGridNo = pBaseStructure->sGridNo;
if ( pBaseStructure != NULL ) if (!TileIsOutOfBounds(sBaseGridNo))
{ {
return(FindCorpseBasedOnStructure(sBaseGridNo, bLevel, pBaseStructure)); return(FindCorpseBasedOnStructure(sBaseGridNo, bLevel, pBaseStructure));
} }
} }
}
return( NULL ); return( NULL );
} }
@@ -2692,6 +2689,7 @@ std::vector<INT16> GetCorpseIDsNearGridNo( INT32 sGridNo, INT8 bLevel, INT8 sRad
} }
extern UNDERGROUND_SECTORINFO* FindUnderGroundSector( INT16 sMapX, INT16 sMapY, UINT8 bMapZ ); extern UNDERGROUND_SECTORINFO* FindUnderGroundSector( INT16 sMapX, INT16 sMapY, UINT8 bMapZ );
INT16 gsZombieRaiseSoundNum = -1;
// Flugente Zombies: resurrect zombies // Flugente Zombies: resurrect zombies
void RaiseZombies( void ) void RaiseZombies( void )
@@ -2801,8 +2799,32 @@ void RaiseZombies( void )
#ifdef JA2TESTVERSION #ifdef JA2TESTVERSION
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_BETAVERSION, L"A wave of zombies is created"); ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_BETAVERSION, L"A wave of zombies is created");
#endif #endif
// Play sound CHAR8 zFilename[512];
// prepare zombie raise sound
if (gsZombieRaiseSoundNum < 0)
{
gsZombieRaiseSoundNum = 0;
do
{
gsZombieRaiseSoundNum++;
sprintf(zFilename, "Sounds\\Misc\\ZombieRaise%d.ogg", gsZombieRaiseSoundNum);
} while (FileExists(zFilename));
gsZombieRaiseSoundNum--;
}
if (gsZombieRaiseSoundNum > 0)
{
sprintf(zFilename, "Sounds\\Misc\\ZombieRaise%d.ogg", Random(gsZombieRaiseSoundNum) + 1);
if (FileExists(zFilename))
{
//PlayJA2SampleFromFile(zFilename, RATE_11025, SoundVolume(MIDVOLUME, this->sGridNo), 1, SoundDir(this->sGridNo));
PlayJA2SampleFromFile(zFilename, RATE_11025, MIDVOLUME, 1, MIDDLEPAN);
}
}
else
{
// Play default sound
PlayJA2SampleFromFile("Sounds\\zombie1.wav", RATE_11025, HIGHVOLUME, 1, MIDDLEPAN); PlayJA2SampleFromFile("Sounds\\zombie1.wav", RATE_11025, HIGHVOLUME, 1, MIDDLEPAN);
}
UseCreatureMusic(TRUE); // Madd: music when zombies rise UseCreatureMusic(TRUE); // Madd: music when zombies rise
#ifdef NEWMUSIC #ifdef NEWMUSIC
@@ -3251,5 +3273,5 @@ FLOAT GetCorpseRotFactor( ROTTING_CORPSE* pCorpse )
if ( pCorpse->def.ubType == ROTTING_STAGE2 ) if ( pCorpse->def.ubType == ROTTING_STAGE2 )
return 1.0f; return 1.0f;
return (GetWorldTotalMin( ) - pCorpse->def.uiTimeOfDeath) / gGameExternalOptions.usCorpseDelayUntilRotting; return (FLOAT)(min(gGameExternalOptions.usCorpseDelayUntilRotting, GetWorldTotalMin() - pCorpse->def.uiTimeOfDeath)) / gGameExternalOptions.usCorpseDelayUntilRotting;
} }
+2
View File
@@ -4,6 +4,8 @@
#include "tile animation.h" #include "tile animation.h"
#define NUM_CORPSE_SHADES 17 #define NUM_CORPSE_SHADES 17
#define CORPSE_WARNING_MAX 5
#define CORPSE_WARNING_DIST 5
enum RottingCorpseDefines enum RottingCorpseDefines
{ {
+11 -1
View File
@@ -16522,6 +16522,16 @@ BOOLEAN SOLDIERTYPE::IsAssassin( )
return FALSE; return FALSE;
} }
BOOLEAN SOLDIERTYPE::CheckInitialAP(void)
{
if (this->bActionPoints < this->bInitialActionPoints || this->usSoldierFlagMask2 & SOLDIER_SPENT_AP)
{
return FALSE;
}
return TRUE;
}
UINT8 SOLDIERTYPE::GetMultiTurnAction( ) UINT8 SOLDIERTYPE::GetMultiTurnAction( )
{ {
return usMultiTurnAction; return usMultiTurnAction;
@@ -17679,7 +17689,7 @@ void SOLDIERTYPE::SoldierPropertyUpkeep( )
{ {
// these effects last only one turn // these effects last only one turn
this->usSoldierFlagMask &= ~(SOLDIER_AIRDROP_TURN | SOLDIER_ASSAULT_BONUS | SOLDIER_RAISED_REDALERT); this->usSoldierFlagMask &= ~(SOLDIER_AIRDROP_TURN | SOLDIER_ASSAULT_BONUS | SOLDIER_RAISED_REDALERT);
this->usSoldierFlagMask2 &= ~SOLDIER_CONCEALINSERTION; this->usSoldierFlagMask2 &= ~(SOLDIER_CONCEALINSERTION | SOLDIER_SPENT_AP);
// this looks bizarre, but is required // this looks bizarre, but is required
if ( this->usSoldierFlagMask2 & SOLDIER_CONCEALINSERTION_DISCOVERED ) if ( this->usSoldierFlagMask2 & SOLDIER_CONCEALINSERTION_DISCOVERED )
+4
View File
@@ -425,6 +425,7 @@ enum
#define SOLDIER_SURGERY_BOOSTED 0x00040000 // we are a boosted performing surgery (e.g. by using up a blood bag) #define SOLDIER_SURGERY_BOOSTED 0x00040000 // we are a boosted performing surgery (e.g. by using up a blood bag)
#define SOLDIER_DRAG_SOUND 0x00080000 // played sound when started dragging #define SOLDIER_DRAG_SOUND 0x00080000 // played sound when started dragging
#define SOLDIER_SPENT_AP 0x00100000 // soldier has spent some AP this turn (including realtime)
#define SOLDIER_INTERROGATE_ALL 0x000001F8 // all interrogation flags #define SOLDIER_INTERROGATE_ALL 0x000001F8 // all interrogation flags
// ---------------------------------------------------------------- // ----------------------------------------------------------------
@@ -1854,6 +1855,9 @@ public:
// Flugente: are we an assassin? // Flugente: are we an assassin?
BOOLEAN IsAssassin(); BOOLEAN IsAssassin();
// sevenfm: service functions
BOOLEAN CheckInitialAP(void);
// Flugente: multi-turn actions // Flugente: multi-turn actions
UINT8 GetMultiTurnAction(); UINT8 GetMultiTurnAction();
void StartMultiTurnAction( UINT8 usActionType, INT32 asGridNo ); void StartMultiTurnAction( UINT8 usActionType, INT32 asGridNo );
+1
View File
@@ -232,6 +232,7 @@ INT32 GoAsFarAsPossibleTowards(SOLDIERTYPE *pSoldier, INT32 sDesGrid, INT8 bActi
INT8 HeadForTheStairCase( SOLDIERTYPE * pSoldier ); INT8 HeadForTheStairCase( SOLDIERTYPE * pSoldier );
BOOLEAN InSmoke(INT32 sGridNo, INT8 bLevel);
BOOLEAN InGas( SOLDIERTYPE *pSoldier, INT32 sGridNo ); BOOLEAN InGas( SOLDIERTYPE *pSoldier, INT32 sGridNo );
BOOLEAN InGasOrSmoke( SOLDIERTYPE *pSoldier, INT32 sGridNo ); BOOLEAN InGasOrSmoke( SOLDIERTYPE *pSoldier, INT32 sGridNo );
BOOLEAN InWaterGasOrSmoke( SOLDIERTYPE *pSoldier, INT32 sGridNo ); BOOLEAN InWaterGasOrSmoke( SOLDIERTYPE *pSoldier, INT32 sGridNo );
+196
View File
@@ -134,6 +134,191 @@ void DebugAI( STR szOutput )
#endif #endif
} }
STR szAction[] = {
"AI_ACTION_NONE",
"AI_ACTION_RANDOM_PATROL",
"AI_ACTION_SEEK_FRIEND",
"AI_ACTION_SEEK_OPPONENT",
"AI_ACTION_TAKE_COVER",
"AI_ACTION_GET_CLOSER",
"AI_ACTION_POINT_PATROL",
"AI_ACTION_LEAVE_WATER_GAS",
"AI_ACTION_SEEK_NOISE",
"AI_ACTION_ESCORTED_MOVE",
"AI_ACTION_RUN_AWAY",
"AI_ACTION_KNIFE_MOVE",
"AI_ACTION_APPROACH_MERC",
"AI_ACTION_TRACK",
"AI_ACTION_EAT",
"AI_ACTION_PICKUP_ITEM",
"AI_ACTION_SCHEDULE_MOVE",
"AI_ACTION_WALK",
"AI_ACTION_RUN",
"AI_ACTION_WITHDRAW",
"AI_ACTION_FLANK_LEFT",
"AI_ACTION_FLANK_RIGHT",
"AI_ACTION_MOVE_TO_CLIMB",
"AI_ACTION_CHANGE_FACING",
"AI_ACTION_CHANGE_STANCE",
"AI_ACTION_YELLOW_ALERT",
"AI_ACTION_RED_ALERT",
"AI_ACTION_CREATURE_CALL",
"AI_ACTION_PULL_TRIGGER",
"AI_ACTION_USE_DETONATOR",
"AI_ACTION_FIRE_GUN",
"AI_ACTION_TOSS_PROJECTILE",
"AI_ACTION_KNIFE_STAB",
"AI_ACTION_THROW_KNIFE",
"AI_ACTION_GIVE_AID",
"AI_ACTION_WAIT",
"AI_ACTION_PENDING_ACTION",
"AI_ACTION_DROP_ITEM",
"AI_ACTION_COWER",
"AI_ACTION_STOP_COWERING",
"AI_ACTION_OPEN_OR_CLOSE_DOOR",
"AI_ACTION_UNLOCK_DOOR",
"AI_ACTION_LOCK_DOOR",
"AI_ACTION_LOWER_GUN",
"AI_ACTION_ABSOLUTELY_NONE",
"AI_ACTION_CLIMB_ROOF",
"AI_ACTION_END_TURN",
"AI_ACTION_END_COWER_AND_MOVE",
"AI_ACTION_TRAVERSE_DOWN",
"AI_ACTION_OFFER_SURRENDER",
"AI_ACTION_RAISE_GUN",
"AI_ACTION_STEAL_MOVE",
"AI_ACTION_RELOAD_GUN",
"AI_ACTION_JUMP_WINDOW",
"AI_ACTION_FREE_PRISONER",
"AI_ACTION_USE_SKILL",
"AI_ACTION_DOCTOR",
"AI_ACTION_DOCTOR_SELF",
"AI_ACTION_SELFDETONATE",
"AI_ACTION_STOP_MEDIC"
};
// sevenfm
UINT32 guiAIStartCounter = 0, guiAILastCounter = 0;
//UINT8 gubAISelectedSoldier = NOBODY;
void DebugAI( INT8 bMsgType, SOLDIERTYPE *pSoldier, STR szOutput, INT8 bAction )
{
FILE* DebugFile;
CHAR8 msg[1024];
CHAR8 buf[1024];
if (!gGameExternalOptions.fAIDecisionInfo)
{
return;
}
memset(buf, 0, 1024 * sizeof(char));
/*if (bMsgType == AI_MSG_TOPIC && !gGameExternalOptions.fDecisionTopicInfo)
{
return;
}*/
if (bMsgType == AI_MSG_START)
{
guiAIStartCounter = GetJA2Clock();
guiAILastCounter = GetJA2Clock();
}
sprintf(msg, "");
if (pSoldier)
{
sprintf(buf, "[%d] (%d)", pSoldier->ubID, pSoldier->sGridNo);
strcat(msg, buf);
if (pSoldier->ubProfile != NO_PROFILE)
{
wcstombs(buf, pSoldier->GetName(), 1024 - 1);
strcat(msg, " ");
strcat(msg, buf);
}
}
strcat(msg, " ");
if (bAction >= 0)
{
sprintf(buf, " (total %d ms)", GetJA2Clock() - guiAIStartCounter);
strcat(msg, buf);
}
else
{
sprintf(buf, " (%d ms)", GetJA2Clock() - guiAILastCounter);
strcat(msg, buf);
}
if (bAction >= AI_ACTION_NONE && bAction <= AI_ACTION_LAST)
{
strcat(msg, " ");
strcat(msg, szAction[bAction]);
if (pSoldier)
{
sprintf(buf, " %d", pSoldier->aiData.usActionData);
strcat(msg, buf);
}
if (pSoldier && pSoldier->aiData.bNextAction != AI_ACTION_NONE)
{
strcat(msg, " ");
strcat(msg, szAction[pSoldier->aiData.bNextAction]);
sprintf(buf, " %d", pSoldier->aiData.usNextActionData);
strcat(msg, buf);
}
}
strcat(msg, " ");
strcat(msg, szOutput);
guiAILastCounter = GetJA2Clock();
DebugMsg(TOPIC_DECISIONS, DBG_LEVEL_3, szOutput);
if ((DebugFile = fopen("Logs\\AI_Decisions.txt", "a+t")) != NULL)
{
if (bMsgType == AI_MSG_START)
{
fputs("\n", DebugFile);
}
fputs(msg, DebugFile);
fputs("\n", DebugFile);
fclose(DebugFile);
}
// also log to individual file for selected soldier
sprintf(buf, "Logs\\AI_Decisions [%d].txt", pSoldier->ubID);
if ((DebugFile = fopen(buf, "a+t")) != NULL)
{
if (bMsgType == AI_MSG_START)
{
fputs("\n", DebugFile);
}
fputs(msg, DebugFile);
fputs("\n", DebugFile);
fclose(DebugFile);
}
}
BOOLEAN InitAI( void ) BOOLEAN InitAI( void )
{ {
@@ -164,6 +349,17 @@ BOOLEAN InitAI( void )
} }
#endif #endif
// sevenfm: Clear the AI debug txt file to prevent it from getting huge
remove("Logs\\AI_Decisions.txt");
// remove all individual files
CHAR8 buf[1024];
for (UINT8 cnt = 0; cnt < TOTAL_SOLDIERS; cnt++)
{
sprintf(buf, "Logs\\AI_Decisions [%d].txt", cnt);
remove(buf);
}
return( TRUE ); return( TRUE );
} }
+725 -39
View File
@@ -839,7 +839,7 @@ INT16 RandomFriendWithin(SOLDIERTYPE *pSoldier)
UINT8 ubDirection; UINT8 ubDirection;
UINT8 ubDirsLeft; UINT8 ubDirsLeft;
BOOLEAN fDirChecked[8]; BOOLEAN fDirChecked[8];
BOOLEAN fRangeRestricted = FALSE, fFound = FALSE; BOOLEAN fFound = FALSE;
INT32 usDest, usOrigin; INT32 usDest, usOrigin;
SOLDIERTYPE * pFriend; SOLDIERTYPE * pFriend;
@@ -847,19 +847,8 @@ INT16 RandomFriendWithin(SOLDIERTYPE *pSoldier)
// obtain maximum roaming distance from soldier's origin // obtain maximum roaming distance from soldier's origin
usMaxDist = RoamingRange(pSoldier,&usOrigin); usMaxDist = RoamingRange(pSoldier,&usOrigin);
// if our movement range is restricted
// CJC: since RandomFriendWithin is only used in non-combat, ALWAYS restrict range.
fRangeRestricted = TRUE;
/*
if (usMaxDist < MAX_ROAMING_RANGE)
{
fRangeRestricted = TRUE;
}
*/
// if range is restricted, make sure origin is a legal gridno! // if range is restricted, make sure origin is a legal gridno!
if (fRangeRestricted && ((usOrigin < 0) || (usOrigin >= GRIDSIZE))) if (usOrigin < 0 || usOrigin >= GRIDSIZE)
{ {
#ifdef BETAVERSION #ifdef BETAVERSION
NameMessage(pSoldier,"has illegal origin, but his roaming range is restricted!",1000); NameMessage(pSoldier,"has illegal origin, but his roaming range is restricted!",1000);
@@ -902,7 +891,6 @@ INT16 RandomFriendWithin(SOLDIERTYPE *pSoldier)
} }
} }
while (ubFriendCount && !fFound) while (ubFriendCount && !fFound)
{ {
// randomly select one of the remaining friends in the list // randomly select one of the remaining friends in the list
@@ -910,8 +898,7 @@ INT16 RandomFriendWithin(SOLDIERTYPE *pSoldier)
// if our movement range is NOT restricted, or this friend's within range // if our movement range is NOT restricted, or this friend's within range
// use distance - 1, because there must be at least 1 tile 1 space closer // use distance - 1, because there must be at least 1 tile 1 space closer
if (!fRangeRestricted || if (SpacesAway(usOrigin,Menptr[ubFriendID].sGridNo) - 1 <= usMaxDist)
(SpacesAway(usOrigin,Menptr[ubFriendID].sGridNo) - 1) <= usMaxDist)
{ {
// should be close enough, try to find a legal->pathing.sDestination within 1 tile // should be close enough, try to find a legal->pathing.sDestination within 1 tile
@@ -1208,12 +1195,12 @@ INT32 ClosestReachableDisturbance(SOLDIERTYPE *pSoldier, BOOLEAN * pfChangeLevel
// this is possible if get here from BLACK AI in one of those rare // this is possible if get here from BLACK AI in one of those rare
// instances when we couldn't get a meaningful shot off at a guy in sight // instances when we couldn't get a meaningful shot off at a guy in sight
if ((*pbPersOL == SEEN_CURRENTLY) && (pOpponent->stats.bLife >= OKLIFE)) /*if ((*pbPersOL == SEEN_CURRENTLY) && (pOpponent->stats.bLife >= OKLIFE))
{ {
// don't allow this to return any valid values, this guy remains a // don't allow this to return any valid values, this guy remains a
// serious threat and the last thing we want to do is approach him! // serious threat and the last thing we want to do is approach him!
return(NOWHERE); return(NOWHERE);
} }*/
// if personal knowledge is more up to date or at least equal // if personal knowledge is more up to date or at least equal
if ((gubKnowledgeValue[*pbPublOL - OLDEST_HEARD_VALUE][*pbPersOL - OLDEST_HEARD_VALUE] > 0) || if ((gubKnowledgeValue[*pbPublOL - OLDEST_HEARD_VALUE][*pbPersOL - OLDEST_HEARD_VALUE] > 0) ||
@@ -1242,6 +1229,18 @@ INT32 ClosestReachableDisturbance(SOLDIERTYPE *pSoldier, BOOLEAN * pfChangeLevel
continue; continue;
} }
// sevenfm: if soldier is zombie and he cannot climb, skip location
if (pSoldier->IsZombie() && pSoldier->pathing.bLevel != bLevel && !gGameExternalOptions.fZombieCanClimb)
{
continue;
}
// sevenfm: zombies do not attack vehicles
if (pSoldier->IsZombie() && (ARMED_VEHICLE(pOpponent) || (pOpponent->flags.uiStatusFlags & SOLDIER_VEHICLE)))
{
continue;
}
// sevenfm: when in deep water, skip opponents in deep water // sevenfm: when in deep water, skip opponents in deep water
if (DeepWater(pSoldier->sGridNo, pSoldier->pathing.bLevel) && DeepWater(sGridNo, bLevel)) if (DeepWater(pSoldier->sGridNo, pSoldier->pathing.bLevel) && DeepWater(sGridNo, bLevel))
{ {
@@ -1256,6 +1255,7 @@ INT32 ClosestReachableDisturbance(SOLDIERTYPE *pSoldier, BOOLEAN * pfChangeLevel
// if we can get there and it's first reachable enemy or closer than other known enemies // if we can get there and it's first reachable enemy or closer than other known enemies
if (iPathCost != 0 && if (iPathCost != 0 &&
(!pClosestOpponent || pClosestOpponent->stats.bLife < OKLIFE || pOpponent->stats.bLife >= OKLIFE) &&
(TileIsOutOfBounds(sClosestDisturbance) || (TileIsOutOfBounds(sClosestDisturbance) ||
iPathCost < iShortestPath || iPathCost < iShortestPath ||
pClosestOpponent && !pClosestOpponent->IsZombie() && pClosestOpponent->stats.bLife < OKLIFE && pOpponent->stats.bLife >= OKLIFE)) pClosestOpponent && !pClosestOpponent->IsZombie() && pClosestOpponent->stats.bLife < OKLIFE && pOpponent->stats.bLife >= OKLIFE))
@@ -1474,7 +1474,7 @@ INT32 ClosestKnownOpponent(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLev
if (sClosestOpponent == NOWHERE || if (sClosestOpponent == NOWHERE ||
iRange < iClosestRange || iRange < iClosestRange ||
pClosestOpponent && !pClosestOpponent->IsZombie() && pClosestOpponent->stats.bLife < OKLIFE && pOpponent->stats.bLife >= OKLIFE) pClosestOpponent && !pClosestOpponent->IsZombie() && !(pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) && pClosestOpponent->stats.bLife < OKLIFE && pOpponent->stats.bLife >= OKLIFE)
{ {
iClosestRange = iRange; iClosestRange = iRange;
sClosestOpponent = sGridNo; sClosestOpponent = sGridNo;
@@ -1942,6 +1942,12 @@ INT16 EstimatePathCostToLocation( SOLDIERTYPE * pSoldier, INT32 sDestGridNo, INT
} }
else else
{ {
// sevenfm: check if zombie cannot climb
if (pSoldier->IsZombie() && !gGameExternalOptions.fZombieCanClimb)
{
return 0;
}
// different levels // different levels
if (pSoldier->pathing.bLevel == 0) if (pSoldier->pathing.bLevel == 0)
{ {
@@ -2048,6 +2054,8 @@ INT32 ClosestReachableFriendInTrouble(SOLDIERTYPE *pSoldier, BOOLEAN * pfClimbin
INT32 sPathCost, sClosestFriend = NOWHERE, sShortestPath = 1000, sClimbGridNo; INT32 sPathCost, sClosestFriend = NOWHERE, sShortestPath = 1000, sClimbGridNo;
BOOLEAN fClimbingNecessary, fClosestClimbingNecessary = FALSE; BOOLEAN fClimbingNecessary, fClosestClimbingNecessary = FALSE;
SOLDIERTYPE *pFriend; SOLDIERTYPE *pFriend;
INT32 sClosestKnownOpponent;
BOOLEAN fCallHelp;
// civilians don't really have any "friends", so they don't bother with this // civilians don't really have any "friends", so they don't bother with this
if (PTR_CIVILIAN) if (PTR_CIVILIAN)
@@ -2078,10 +2086,24 @@ INT32 ClosestReachableFriendInTrouble(SOLDIERTYPE *pSoldier, BOOLEAN * pfClimbin
continue; // next merc continue; // next merc
} }
// CJC: restrict "last one to radio" to only if that guy saw us this turn or last turn sClosestKnownOpponent = ClosestKnownOpponent(pFriend, NULL, NULL);
// if this friend is not under fire, and isn't the last one to radio // CJC: restrict "last one to radio" to only if that guy saw us this turn or last turn
if ( !(pFriend->aiData.bUnderFire || (pFriend->ubID == gTacticalStatus.Team[pFriend->bTeam].ubLastMercToRadio && GuySawEnemy( pFriend ))) ) fCallHelp = FALSE;
// if this friend is under fire or he called for help recently
if (pFriend->aiData.bUnderFire || (pFriend->ubID == gTacticalStatus.Team[pFriend->bTeam].ubLastMercToRadio && GuySawEnemy(pFriend)))
{
fCallHelp = TRUE;
}
// zombies always call for help if they know enemy position
if (pSoldier->IsZombie() && pFriend->IsZombie() && !TileIsOutOfBounds(sClosestKnownOpponent))
{
fCallHelp = TRUE;
}
if (!fCallHelp)
{ {
continue; // next merc continue; // next merc
} }
@@ -2752,6 +2774,11 @@ INT16 RoamingRange(SOLDIERTYPE *pSoldier, INT32 * pusFromGridNo)
return(MAX_ROAMING_RANGE); return(MAX_ROAMING_RANGE);
} }
} }
// sevenfm: no limits for zombies
if (pSoldier->IsZombie())
{
return(MAX_ROAMING_RANGE);
}
if (pSoldier->aiData.bOrders == POINTPATROL || pSoldier->aiData.bOrders == RNDPTPATROL) if (pSoldier->aiData.bOrders == POINTPATROL || pSoldier->aiData.bOrders == RNDPTPATROL)
{ {
// roam near NEXT PATROL POINT, not from where merc starts out // roam near NEXT PATROL POINT, not from where merc starts out
@@ -2774,9 +2801,6 @@ INT16 RoamingRange(SOLDIERTYPE *pSoldier, INT32 * pusFromGridNo)
} }
} }
//TODO: Externalize if people want?
BOOL fLessRestrictiveRoaming = TRUE;
switch (pSoldier->aiData.bOrders) switch (pSoldier->aiData.bOrders)
{ {
// JA2 GOLD: give non-NPCs a 5 tile roam range for cover in combat when being shot at // JA2 GOLD: give non-NPCs a 5 tile roam range for cover in combat when being shot at
@@ -2792,13 +2816,14 @@ INT16 RoamingRange(SOLDIERTYPE *pSoldier, INT32 * pusFromGridNo)
} }
case ONGUARD: case ONGUARD:
return(5); return(5);
case CLOSEPATROL: if (pSoldier->aiData.bAlertStatus < STATUS_RED) case CLOSEPATROL:
if (pSoldier->aiData.bAlertStatus < STATUS_RED)
{ {
return(5); return(5);
} }
else else
{ {
if(!OppPosKnown || !fLessRestrictiveRoaming) if (!OppPosKnown)
{ {
return(15); return(15);
} }
@@ -2808,13 +2833,14 @@ INT16 RoamingRange(SOLDIERTYPE *pSoldier, INT32 * pusFromGridNo)
//return( MAX_ROAMING_RANGE ); //return( MAX_ROAMING_RANGE );
} }
} }
case POINTPATROL: if (pSoldier->aiData.bAlertStatus < STATUS_RED) case POINTPATROL:
if (pSoldier->aiData.bAlertStatus < STATUS_RED)
{ {
return(10); return(10);
} }
else else
{ {
if(!OppPosKnown || !fLessRestrictiveRoaming) if (!OppPosKnown)
{ {
return(20); return(20);
} }
@@ -2824,13 +2850,14 @@ INT16 RoamingRange(SOLDIERTYPE *pSoldier, INT32 * pusFromGridNo)
//return( MAX_ROAMING_RANGE ); //return( MAX_ROAMING_RANGE );
} }
} // from nextPatrolGrid, not whereIWas } // from nextPatrolGrid, not whereIWas
case RNDPTPATROL: if (pSoldier->aiData.bAlertStatus < STATUS_RED) case RNDPTPATROL:
if (pSoldier->aiData.bAlertStatus < STATUS_RED)
{ {
return(10); return(10);
} }
else else
{ {
if(!OppPosKnown || !fLessRestrictiveRoaming) if (!OppPosKnown)
{ {
return(20); return(20);
} }
@@ -2840,13 +2867,14 @@ INT16 RoamingRange(SOLDIERTYPE *pSoldier, INT32 * pusFromGridNo)
return(MAX_ROAMING_RANGE); return(MAX_ROAMING_RANGE);
} }
}// from nextPatrolGrid, not whereIWas }// from nextPatrolGrid, not whereIWas
case FARPATROL: if (pSoldier->aiData.bAlertStatus < STATUS_RED) case FARPATROL:
if (pSoldier->aiData.bAlertStatus < STATUS_RED)
{ {
return(15); return(15);
} }
else else
{ {
if(!OppPosKnown || !fLessRestrictiveRoaming) if (!OppPosKnown)
{ {
return(30); return(30);
} }
@@ -2855,13 +2883,14 @@ INT16 RoamingRange(SOLDIERTYPE *pSoldier, INT32 * pusFromGridNo)
return(MAX_ROAMING_RANGE); return(MAX_ROAMING_RANGE);
} }
} }
case ONCALL: if (pSoldier->aiData.bAlertStatus < STATUS_RED) case ONCALL:
if (pSoldier->aiData.bAlertStatus < STATUS_RED)
{ {
return(10); return(10);
} }
else else
{ {
if(!OppPosKnown || !fLessRestrictiveRoaming) if (!OppPosKnown)
{ {
return(30); return(30);
} }
@@ -4021,7 +4050,7 @@ UINT8 CountFriendsBlack( SOLDIERTYPE *pSoldier, INT32 sClosestOpponent )
} }
// check if we have a prone sight cover from known enemies at spot // check if we have a prone sight cover from known enemies at spot
BOOLEAN ProneSightCoverAtSpot( SOLDIERTYPE *pSoldier, INT32 sSpot ) BOOLEAN ProneSightCoverAtSpot(SOLDIERTYPE *pSoldier, INT32 sSpot, BOOLEAN fUnlimited)
{ {
CHECKF(pSoldier); CHECKF(pSoldier);
@@ -4036,6 +4065,8 @@ BOOLEAN ProneSightCoverAtSpot( SOLDIERTYPE *pSoldier, INT32 sSpot )
//INT32 iThreatCertainty; //INT32 iThreatCertainty;
INT8 iThreatLevel; INT8 iThreatLevel;
INT32 iDistanceVisible;
// look through all opponents for those we know of // look through all opponents for those we know of
for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++) for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++)
{ {
@@ -4082,7 +4113,24 @@ BOOLEAN ProneSightCoverAtSpot( SOLDIERTYPE *pSoldier, INT32 sSpot )
//iThreatCertainty = ThreatPercent[*pbPublOL - OLDEST_HEARD_VALUE]; //iThreatCertainty = ThreatPercent[*pbPublOL - OLDEST_HEARD_VALUE];
} }
if( LocationToLocationLineOfSightTest( sThreatLoc, iThreatLevel, sSpot, pSoldier->pathing.bLevel, TRUE, CALC_FROM_ALL_DIRS, PRONE_LOS_POS, PRONE_LOS_POS) ) if (TileIsOutOfBounds(sThreatLoc))
{
continue;
}
if (fUnlimited)
{
iDistanceVisible = NO_DISTANCE_LIMIT;
}
else
{
gbForceWeaponReady = true;
iDistanceVisible = DistanceVisible(pSoldier, DIRECTION_IRRELEVANT, DIRECTION_IRRELEVANT, sSpot, pSoldier->pathing.bLevel, CoweringShockLevel(pSoldier), GetPercentTunnelVision(pSoldier));
gbForceWeaponReady = false;
//iDistanceVisible = pSoldier->GetMaxDistanceVisible(sSpot, pSoldier->pathing.bLevel, CALC_FROM_WANTED_DIR);
}
if (LocationToLocationLineOfSightTest(sThreatLoc, iThreatLevel, sSpot, pSoldier->pathing.bLevel, TRUE, iDistanceVisible, STANDING_LOS_POS, PRONE_LOS_POS))
{ {
return FALSE; return FALSE;
} }
@@ -4092,7 +4140,7 @@ BOOLEAN ProneSightCoverAtSpot( SOLDIERTYPE *pSoldier, INT32 sSpot )
} }
// check if we have a sight cover from known enemies at spot // check if we have a sight cover from known enemies at spot
BOOLEAN SightCoverAtSpot( SOLDIERTYPE *pSoldier, INT32 sSpot ) BOOLEAN SightCoverAtSpot(SOLDIERTYPE *pSoldier, INT32 sSpot, BOOLEAN fUnlimited)
{ {
CHECKF(pSoldier); CHECKF(pSoldier);
@@ -4107,6 +4155,8 @@ BOOLEAN SightCoverAtSpot( SOLDIERTYPE *pSoldier, INT32 sSpot )
//INT32 iThreatCertainty; //INT32 iThreatCertainty;
INT8 iThreatLevel; INT8 iThreatLevel;
INT32 iDistanceVisible;
// look through all opponents for those we know of // look through all opponents for those we know of
for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++) for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++)
{ {
@@ -4153,7 +4203,24 @@ BOOLEAN SightCoverAtSpot( SOLDIERTYPE *pSoldier, INT32 sSpot )
//iThreatCertainty = ThreatPercent[*pbPublOL - OLDEST_HEARD_VALUE]; //iThreatCertainty = ThreatPercent[*pbPublOL - OLDEST_HEARD_VALUE];
} }
if( LocationToLocationLineOfSightTest( sThreatLoc, iThreatLevel, sSpot, pSoldier->pathing.bLevel, TRUE, CALC_FROM_ALL_DIRS) ) if (TileIsOutOfBounds(sThreatLoc))
{
continue;
}
if(fUnlimited)
{
iDistanceVisible = NO_DISTANCE_LIMIT;
}
else
{
gbForceWeaponReady = true;
iDistanceVisible = DistanceVisible(pSoldier, DIRECTION_IRRELEVANT, DIRECTION_IRRELEVANT, sSpot, pSoldier->pathing.bLevel, CoweringShockLevel(pSoldier), GetPercentTunnelVision(pSoldier));
gbForceWeaponReady = false;
//iDistanceVisible = pSoldier->GetMaxDistanceVisible(sSpot, pSoldier->pathing.bLevel, CALC_FROM_WANTED_DIR);
}
if (LocationToLocationLineOfSightTest(sThreatLoc, iThreatLevel, sSpot, pSoldier->pathing.bLevel, TRUE, iDistanceVisible))
{ {
return FALSE; return FALSE;
} }
@@ -4801,3 +4868,622 @@ BOOLEAN AllowDeepWaterFlanking(SOLDIERTYPE *pSoldier)
return FALSE; return FALSE;
} }
UINT8 AIDirection(INT32 sSpot1, INT32 sSpot2)
{
if (TileIsOutOfBounds(sSpot1) || TileIsOutOfBounds(sSpot2))
{
return DIRECTION_IRRELEVANT;
}
return atan8(CenterX(sSpot1), CenterY(sSpot1), CenterX(sSpot2), CenterY(sSpot2));
}
BOOLEAN AICheckIsSniper(SOLDIERTYPE *pSoldier)
{
CHECKF(pSoldier);
if (!SoldierAI(pSoldier))
{
return FALSE;
}
/*if( AIGunType(pSoldier) != GUN_SN_RIFLE )
{
return FALSE;
}*/
if ((AIGunRange(pSoldier) > DAY_VISION_RANGE || AICheckHasWeaponOfType(pSoldier, GUN_SN_RIFLE)) &&
AIGunScoped(pSoldier) &&
pSoldier->stats.bMarksmanship > 90 &&
gGameOptions.fNewTraitSystem &&
HAS_SKILL_TRAIT(pSoldier, SNIPER_NT))
{
return TRUE;
}
return FALSE;
}
BOOLEAN AICheckIsMarksman(SOLDIERTYPE *pSoldier)
{
CHECKF(pSoldier);
if (!SoldierAI(pSoldier))
{
return FALSE;
}
if (!AICheckHasGun(pSoldier))
{
return FALSE;
}
if (AIGunType(pSoldier) != GUN_SN_RIFLE &&
AIGunType(pSoldier) != GUN_RIFLE &&
AIGunType(pSoldier) != GUN_AS_RIFLE)
{
return FALSE;
}
if (AIGunRange(pSoldier) >= DAY_VISION_RANGE &&
(AIGunScoped(pSoldier) || pSoldier->stats.bMarksmanship > 90 || gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT(pSoldier, SNIPER_NT)))
{
return TRUE;
}
return FALSE;
}
BOOLEAN AICheckIsRadioOperator(SOLDIERTYPE *pSoldier)
{
CHECKF(pSoldier);
if (!SoldierAI(pSoldier))
{
return FALSE;
}
if (pSoldier->CanUseRadio(FALSE))
return TRUE;
return FALSE;
}
BOOLEAN AICheckIsMedic(SOLDIERTYPE *pSoldier)
{
CHECKF(pSoldier);
if (!SoldierAI(pSoldier))
{
return FALSE;
}
if (pSoldier->CanMedicAI() && gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT(pSoldier, DOCTOR_NT))
{
return TRUE;
}
/*if( !HAS_SKILL_TRAIT( pSoldier, DOCTOR_NT) )
{
return FALSE;
}
if( FindFirstAidKit( pSoldier ) != NO_SLOT ||
FindMedKit( pSoldier ) != NO_SLOT )
{
return TRUE;
}*/
return FALSE;
}
BOOLEAN AICheckIsMortarOperator(SOLDIERTYPE *pSoldier)
{
CHECKF(pSoldier);
if (!SoldierAI(pSoldier))
{
return FALSE;
}
if (pSoldier->HasMortar())
{
return TRUE;
}
return FALSE;
}
BOOLEAN AICheckIsGLOperator(SOLDIERTYPE *pSoldier)
{
CHECKF(pSoldier);
if (!SoldierAI(pSoldier))
{
return FALSE;
}
// find GL
INT8 bWeaponIn = FindAIUsableObjClass(pSoldier, IC_LAUNCHER);
if (bWeaponIn != NO_SLOT &&
(EnoughAmmo(pSoldier, FALSE, bWeaponIn) || FindAmmoToReload(pSoldier, bWeaponIn, NO_SLOT) != NO_SLOT))
{
return TRUE;
}
// check for attached GL
INT8 bGunSlot = FindAIUsableObjClass(pSoldier, IC_GUN);
INT8 bRealWeaponMode = pSoldier->bWeaponMode;
pSoldier->bWeaponMode = WM_ATTACHED_GL; // So that EnoughAmmo will check for a grenade not a bullet
if (bGunSlot != NO_SLOT &&
IsGrenadeLauncherAttached(&pSoldier->inv[bGunSlot]) &&
(EnoughAmmo(pSoldier, FALSE, bGunSlot) || FindAmmoToReload(pSoldier, bGunSlot, NO_SLOT) != NO_SLOT))
{
pSoldier->bWeaponMode = bRealWeaponMode;
return TRUE;
}
pSoldier->bWeaponMode = bRealWeaponMode;
return FALSE;
}
BOOLEAN AICheckIsOfficer(SOLDIERTYPE *pSoldier)
{
CHECKF(pSoldier);
if (!SoldierAI(pSoldier))
{
return FALSE;
}
if (gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT(pSoldier, SQUADLEADER_NT))
{
return TRUE;
}
return FALSE;
}
BOOLEAN AICheckIsCommander(SOLDIERTYPE *pSoldier)
{
CHECKF(pSoldier);
if (!SoldierAI(pSoldier))
{
return FALSE;
}
if (gGameOptions.fNewTraitSystem && NUM_SKILL_TRAITS(pSoldier, SQUADLEADER_NT) > 1)
{
return TRUE;
}
return FALSE;
}
BOOLEAN AICheckIsMachinegunner(SOLDIERTYPE *pSoldier)
{
CHECKF(pSoldier);
if (!SoldierAI(pSoldier))
{
return FALSE;
}
if (AIGunType(pSoldier) == GUN_LMG)
{
return TRUE;
}
return FALSE;
}
BOOLEAN AIGunInHandScoped(SOLDIERTYPE *pSoldier)
{
CHECKF(pSoldier);
if (UsingNewCTHSystem() == false && IsScoped(&pSoldier->inv[HANDPOS]))
{
return TRUE;
}
if (UsingNewCTHSystem() == true && NCTHIsScoped(&pSoldier->inv[HANDPOS]))
{
return TRUE;
}
return FALSE;
}
// check if gun that AI can use is scoped
BOOLEAN AIGunScoped(SOLDIERTYPE *pSoldier)
{
CHECKF(pSoldier);
INT8 bWeaponIn;
bWeaponIn = FindAIUsableObjClass(pSoldier, IC_GUN);
if (bWeaponIn == NO_SLOT)
{
return FALSE;
}
if (pSoldier->inv[bWeaponIn].exists())
{
if (UsingNewCTHSystem())
{
return NCTHIsScoped(&pSoldier->inv[bWeaponIn]);
}
else
{
return IsScoped(&pSoldier->inv[bWeaponIn]);
}
}
return FALSE;
}
// return range for the AI gun in tiles
UINT16 AIGunRange(SOLDIERTYPE *pSoldier)
{
CHECKF(pSoldier);
INT8 bWeaponIn;
bWeaponIn = FindAIUsableObjClass(pSoldier, IC_GUN);
if (bWeaponIn == NO_SLOT)
{
return 0;
}
if (pSoldier->inv[bWeaponIn].exists())
{
return GunRange(&pSoldier->inv[bWeaponIn], pSoldier) / CELL_X_SIZE;
}
return 0;
}
UINT16 AIGunClass(SOLDIERTYPE *pSoldier)
{
CHECKF(pSoldier);
INT8 bWeaponIn;
bWeaponIn = FindAIUsableObjClass(pSoldier, IC_GUN);
if (bWeaponIn == NO_SLOT)
{
return 0;
}
if (pSoldier->inv[bWeaponIn].exists())
{
return Weapon[pSoldier->inv[bWeaponIn].usItem].ubWeaponClass;
}
return 0;
}
UINT16 AIGunType(SOLDIERTYPE *pSoldier)
{
CHECKF(pSoldier);
INT8 bWeaponIn;
bWeaponIn = FindAIUsableObjClass(pSoldier, IC_GUN);
if (bWeaponIn == NO_SLOT)
{
return 0;
}
if (pSoldier->inv[bWeaponIn].exists())
{
return Weapon[pSoldier->inv[bWeaponIn].usItem].ubWeaponType;
}
return 0;
}
UINT8 AIGunDeadliness(SOLDIERTYPE *pSoldier)
{
CHECKF(pSoldier);
INT8 bWeaponIn;
bWeaponIn = FindAIUsableObjClass(pSoldier, IC_GUN);
if (bWeaponIn == NO_SLOT)
{
return 0;
}
if (pSoldier->inv[bWeaponIn].exists())
{
return Weapon[pSoldier->inv[bWeaponIn].usItem].ubDeadliness;
}
return 0;
}
UINT16 AIGunAmmo(SOLDIERTYPE *pSoldier)
{
CHECKF(pSoldier);
INT8 bWeaponIn = FindAIUsableObjClass(pSoldier, IC_GUN);
if (bWeaponIn == NO_SLOT)
{
return 0;
}
if (pSoldier->inv[bWeaponIn].exists())
{
return pSoldier->inv[bWeaponIn][0]->data.gun.ubGunShotsLeft;
}
return 0;
}
BOOLEAN AIGunAutofireCapable(SOLDIERTYPE *pSoldier)
{
CHECKF(pSoldier);
INT8 bWeaponIn = FindAIUsableObjClass(pSoldier, IC_GUN);
if (bWeaponIn == NO_SLOT)
{
return FALSE;
}
if (pSoldier->inv[bWeaponIn].exists())
{
return IsGunAutofireCapable(&pSoldier->inv[bWeaponIn]);
}
return FALSE;
}
BOOLEAN FindObstacleNearSpot(INT32 sSpot, INT8 bLevel)
{
if (TileIsOutOfBounds(sSpot))
{
return FALSE;
}
/*if( !IsLocationSittableExcludingPeople(sSpot, bLevel) )
{
return FALSE;
}*/
UINT8 ubMovementCost;
INT32 sTempGridNo;
UINT8 ubDirection;
// check adjacent reachable tiles
for (ubDirection = 0; ubDirection < NUM_WORLD_DIRECTIONS; ubDirection++)
{
sTempGridNo = NewGridNo(sSpot, DirectionInc(ubDirection));
if (sTempGridNo != sSpot)
{
ubMovementCost = gubWorldMovementCosts[sTempGridNo][ubDirection][bLevel];
if (ubMovementCost >= TRAVELCOST_BLOCKED || !IsLocationSittableExcludingPeople(sTempGridNo, bLevel))
{
return(TRUE);
}
}
}
return FALSE;
}
BOOLEAN InSmoke(INT32 sGridNo, INT8 bLevel)
{
if (TileIsOutOfBounds(sGridNo))
{
return FALSE;
}
if (gpWorldLevelData[sGridNo].ubExtFlags[bLevel] & (MAPELEMENT_EXT_SMOKE))
{
return TRUE;
}
return FALSE;
}
BOOLEAN CorpseWarning(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT8 bLevel)
{
CHECKF(pSoldier);
INT32 cnt;
ROTTING_CORPSE *pCorpse;
UINT8 ubDistance = CORPSE_WARNING_DIST;
for (cnt = 0; cnt < giNumRottingCorpse; ++cnt)
{
pCorpse = &(gRottingCorpse[cnt]);
if (pCorpse &&
pCorpse->fActivated &&
pCorpse->def.ubType < ROTTING_STAGE2 &&
//pCorpse->def.ubType <= FMERC_FALLF &&
pCorpse->def.ubBodyType <= REGFEMALE &&
pCorpse->def.ubAIWarningValue > 0 &&
pCorpse->def.bLevel == bLevel &&
!TileIsOutOfBounds(pCorpse->def.sGridNo) &&
PythSpacesAway(sGridNo, pCorpse->def.sGridNo) <= ubDistance &&
(pSoldier->bTeam == ENEMY_TEAM && CorpseEnemyTeam(pCorpse) || pSoldier->bTeam == MILITIA_TEAM && CorpseMilitiaTeam(pCorpse) || pSoldier->bTeam == CIV_TEAM && !pSoldier->aiData.bNeutral))
{
return TRUE;
}
}
return FALSE;
}
BOOLEAN CorpseEnemyTeam(ROTTING_CORPSE *pCorpse)
{
CHECKF(pCorpse);
// check whether corpse has soldier's uniform
for (UINT8 i = UNIFORM_ENEMY_ADMIN; i <= UNIFORM_ENEMY_ELITE; ++i)
{
if (COMPARE_PALETTEREP_ID(pCorpse->def.VestPal, gUniformColors[i].vest) && COMPARE_PALETTEREP_ID(pCorpse->def.PantsPal, gUniformColors[i].pants))
{
return TRUE;
}
}
return FALSE;
}
BOOLEAN CorpseMilitiaTeam(ROTTING_CORPSE *pCorpse)
{
CHECKF(pCorpse);
// check whether corpse has soldier's uniform
for (UINT8 i = UNIFORM_MILITIA_ROOKIE; i <= UNIFORM_MILITIA_ELITE; ++i)
{
if (COMPARE_PALETTEREP_ID(pCorpse->def.VestPal, gUniformColors[i].vest) && COMPARE_PALETTEREP_ID(pCorpse->def.PantsPal, gUniformColors[i].pants))
{
return TRUE;
}
}
return FALSE;
}
// check that current loaded sector is underground
BOOLEAN AICheckUnderground(void)
{
// not underground
if (gbWorldSectorZ > 0)
{
return TRUE;
}
return FALSE;
}
// check if we have any sight cover from known enemies at spot
BOOLEAN AnyCoverAtSpot(SOLDIERTYPE *pSoldier, INT32 sSpot)
{
CHECKF(pSoldier);
UINT32 uiLoop;
SOLDIERTYPE *pOpponent;
INT32 *pusLastLoc;
INT8 *pbPersOL;
INT8 *pbPublOL;
INT8 *pbLastLevel;
INT32 sThreatLoc;
//INT32 iThreatCertainty;
INT8 iThreatLevel;
// look through all opponents for those we know of
for (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; // next merc
}
// if this man is neutral / on the same side, he's not an opponent
if (CONSIDERED_NEUTRAL(pSoldier, pOpponent) || (pSoldier->bSide == pOpponent->bSide))
{
continue; // next merc
}
pbPersOL = pSoldier->aiData.bOppList + pOpponent->ubID;
pbPublOL = gbPublicOpplist[pSoldier->bTeam] + pOpponent->ubID;
pusLastLoc = gsLastKnownOppLoc[pSoldier->ubID] + pOpponent->ubID;
pbLastLevel = gbLastKnownOppLevel[pSoldier->ubID] + pOpponent->ubID;
// if this opponent is unknown personally and publicly
if ((*pbPersOL == NOT_HEARD_OR_SEEN) && (*pbPublOL == NOT_HEARD_OR_SEEN))
{
continue; // next merc
}
// if personal knowledge is more up to date or at least equal
// sevenfm: fix for unknown public location
if ((gubKnowledgeValue[*pbPublOL - OLDEST_HEARD_VALUE][*pbPersOL - OLDEST_HEARD_VALUE] > 0) ||
(*pbPersOL == *pbPublOL) ||
*pbPersOL != NOT_HEARD_OR_SEEN && TileIsOutOfBounds(gsPublicLastKnownOppLoc[pSoldier->bTeam][pOpponent->ubID]) && !TileIsOutOfBounds(gsLastKnownOppLoc[pSoldier->ubID][pOpponent->ubID]))
{
// using personal knowledge, obtain opponent's "best guess" gridno
sThreatLoc = *pusLastLoc;
iThreatLevel = *pbLastLevel;
//iThreatCertainty = ThreatPercent[*pbPersOL - OLDEST_HEARD_VALUE];
}
else
{
// using public knowledge, obtain opponent's "best guess" gridno
sThreatLoc = gsPublicLastKnownOppLoc[pSoldier->bTeam][pOpponent->ubID];
iThreatLevel = gbPublicLastKnownOppLevel[pSoldier->bTeam][pOpponent->ubID];
//iThreatCertainty = ThreatPercent[*pbPublOL - OLDEST_HEARD_VALUE];
}
if (!AnyCoverFromSpot(sSpot, pSoldier->pathing.bLevel, sThreatLoc, iThreatLevel) &&
LocationToLocationLineOfSightTest(sThreatLoc, iThreatLevel, sSpot, pSoldier->pathing.bLevel, TRUE, NO_DISTANCE_LIMIT, STANDING_LOS_POS, PRONE_LOS_POS))
{
return FALSE;
}
}
return TRUE;
}
BOOLEAN AICheckHasWeaponOfType(SOLDIERTYPE *pSoldier, UINT8 ubWeaponType)
{
CHECKF(pSoldier);
INT8 invsize = (INT8)pSoldier->inv.size();
for (INT8 bLoop = 0; bLoop < invsize; ++bLoop)
{
if (pSoldier->inv[bLoop].exists() &&
Item[pSoldier->inv[bLoop].usItem].usItemClass == IC_GUN &&
Weapon[Item[pSoldier->inv[bLoop].usItem].ubClassIndex].ubWeaponType == ubWeaponType)
{
return TRUE;
}
}
return FALSE;
}
BOOLEAN AICheckHasGun(SOLDIERTYPE *pSoldier)
{
CHECKF(pSoldier);
if (FindAIUsableObjClass(pSoldier, IC_GUN) != NO_SLOT)
{
return TRUE;
}
return FALSE;
}
BOOLEAN AICheckShortWeaponRange(SOLDIERTYPE *pSoldier)
{
CHECKF(pSoldier);
if (!AICheckHasGun(pSoldier))
{
return TRUE;
}
if (AIGunRange(pSoldier) < DAY_VISION_RANGE / 2)
{
return TRUE;
}
return FALSE;
}
+5
View File
@@ -15,6 +15,11 @@
class OBJECTTYPE; class OBJECTTYPE;
class SOLDIERTYPE; class SOLDIERTYPE;
extern STR8 gStr8AlertStatus[];
extern STR8 gStr8Attitude[];
extern STR8 gStr8Orders[];
extern STR8 gStr8Team[];
extern STR8 gStr8Class[];
extern INT8 STRAIGHT; //lal extern INT8 STRAIGHT; //lal
+97 -87
View File
@@ -46,6 +46,8 @@
extern BOOLEAN gfHiddenInterrupt; extern BOOLEAN gfHiddenInterrupt;
extern BOOLEAN gfUseAlternateQueenPosition; extern BOOLEAN gfUseAlternateQueenPosition;
extern UINT16 PickSoldierReadyAnimation( SOLDIERTYPE *pSoldier, BOOLEAN fEndReady, BOOLEAN fHipStance ); extern UINT16 PickSoldierReadyAnimation( SOLDIERTYPE *pSoldier, BOOLEAN fEndReady, BOOLEAN fHipStance );
void LogDecideInfo(SOLDIERTYPE *pSoldier);
void LogKnowledgeInfo(SOLDIERTYPE *pSoldier);
// global status time counters to determine what takes the most time // global status time counters to determine what takes the most time
@@ -68,6 +70,13 @@ INT8 ArmedVehicleDecideActionYellow(SOLDIERTYPE *pSoldier);
INT8 ArmedVehicleDecideActionRed(SOLDIERTYPE *pSoldier); INT8 ArmedVehicleDecideActionRed(SOLDIERTYPE *pSoldier);
INT8 ArmedVehicleDecideActionBlack(SOLDIERTYPE *pSoldier); INT8 ArmedVehicleDecideActionBlack(SOLDIERTYPE *pSoldier);
STR8 gStr8AlertStatus[] = { "Green", "Yellow", "Red", "Black" };
STR8 gStr8Attitude[] = { "DEFENSIVE", "BRAVESOLO", "BRAVEAID", "CUNNINGSOLO", "CUNNINGAID", "AGGRESSIVE", "MAXATTITUDES", "ATTACKSLAYONLY" };
STR8 gStr8Orders[] = { "STATIONARY", "ONGUARD", "CLOSEPATROL", "FARPATROL", "POINTPATROL", "ONCALL", "SEEKENEMY", "RNDPTPATROL", "SNIPER" };
STR8 gStr8Team[] = { "OUR_TEAM", "ENEMY_TEAM", "CREATURE_TEAM", "MILITIA_TEAM", "CIV_TEAM", "LAST_TEAM", "PLAYER_PLAN", "LAN_TEAM_ONE", "LAN_TEAM_TWO", "LAN_TEAM_THREE", "LAN_TEAM_FOUR" };
STR8 gStr8Class[] = { "SOLDIER_CLASS_NONE", "SOLDIER_CLASS_ADMINISTRATOR", "SOLDIER_CLASS_ELITE", "SOLDIER_CLASS_ARMY", "SOLDIER_CLASS_GREEN_MILITIA", "SOLDIER_CLASS_REG_MILITIA", "SOLDIER_CLASS_ELITE_MILITIA", "SOLDIER_CLASS_CREATURE", "SOLDIER_CLASS_MINER", "SOLDIER_CLASS_ZOMBIE", "SOLDIER_CLASS_TANK", "SOLDIER_CLASS_JEEP", "SOLDIER_CLASS_BANDIT" };
STR8 gStr8Knowledge[] = { "HEARD_3_TURNS_AGO", "HEARD_2_TURNS_AGO", "HEARD_LAST_TURN", "HEARD_THIS_TURN", "NOT_HEARD_OR_SEEN", "SEEN_CURRENTLY", "SEEN_THIS_TURN", "SEEN_LAST_TURN", "SEEN_2_TURNS_AGO", "SEEN_3_TURNS_AGO" };
void DoneScheduleAction( SOLDIERTYPE * pSoldier ) void DoneScheduleAction( SOLDIERTYPE * pSoldier )
{ {
pSoldier->aiData.fAIFlags &= (~AI_CHECK_SCHEDULE); pSoldier->aiData.fAIFlags &= (~AI_CHECK_SCHEDULE);
@@ -695,10 +704,6 @@ INT8 DecideActionGreen(SOLDIERTYPE *pSoldier)
STR16 tempstr; STR16 tempstr;
#endif #endif
// Flugente: to prevent an accidental call
if ( pSoldier->IsZombie() )
return( ZombieDecideActionGreen(pSoldier) );
if ( ARMED_VEHICLE( pSoldier ) ) if ( ARMED_VEHICLE( pSoldier ) )
return ArmedVehicleDecideActionGreen( pSoldier ); return ArmedVehicleDecideActionGreen( pSoldier );
@@ -1547,10 +1552,6 @@ INT8 DecideActionYellow(SOLDIERTYPE *pSoldier)
STR16 tempstr; STR16 tempstr;
#endif #endif
// Flugente: to prevent an accidental call
if ( pSoldier->IsZombie() )
return( ZombieDecideActionYellow(pSoldier) );
if (fCivilian || (gGameExternalOptions.fAllNamedNpcsDecideAction && pSoldier->ubProfile != NO_PROFILE)) if (fCivilian || (gGameExternalOptions.fAllNamedNpcsDecideAction && pSoldier->ubProfile != NO_PROFILE))
{ {
if (pSoldier->flags.uiStatusFlags & SOLDIER_COWERING) if (pSoldier->flags.uiStatusFlags & SOLDIER_COWERING)
@@ -2467,10 +2468,6 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier)
(pSoldier->aiData.bNeutral && gTacticalStatus.fCivGroupHostile[pSoldier->ubCivilianGroup] == CIV_GROUP_NEUTRAL) || (pSoldier->aiData.bNeutral && gTacticalStatus.fCivGroupHostile[pSoldier->ubCivilianGroup] == CIV_GROUP_NEUTRAL) ||
(pSoldier->ubBodyType >= FATCIV && pSoldier->ubBodyType <= CRIPPLECIV) ) ); (pSoldier->ubBodyType >= FATCIV && pSoldier->ubBodyType <= CRIPPLECIV) ) );
// Flugente: to prevent an accidental call
if ( pSoldier->IsZombie() )
return ZombieDecideActionRed(pSoldier);
// WANNE: Headrock informed me that I should remove that because it needs a lot of CPU! // WANNE: Headrock informed me that I should remove that because it needs a lot of CPU!
// HEADROCK HAM B2.7: Calculate the overall tactical situation // HEADROCK HAM B2.7: Calculate the overall tactical situation
//INT16 ubOverallTacticalSituation = AssessTacticalSituation(pSoldier->bSide); //INT16 ubOverallTacticalSituation = AssessTacticalSituation(pSoldier->bSide);
@@ -2484,7 +2481,7 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier)
return(AI_ACTION_NONE); return(AI_ACTION_NONE);
} }
fProneSightCover = ProneSightCoverAtSpot(pSoldier, pSoldier->sGridNo); fProneSightCover = ProneSightCoverAtSpot(pSoldier, pSoldier->sGridNo, FALSE);
if( !fProneSightCover || pSoldier->aiData.bUnderFire ) if( !fProneSightCover || pSoldier->aiData.bUnderFire )
{ {
fDangerousSpot = TRUE; fDangerousSpot = TRUE;
@@ -4534,10 +4531,6 @@ INT16 ubMinAPCost;
#endif #endif
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionBlack: soldier = %d, orders = %d, attitude = %d",pSoldier->ubID,pSoldier->aiData.bOrders,pSoldier->aiData.bAttitude)); DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionBlack: soldier = %d, orders = %d, attitude = %d",pSoldier->ubID,pSoldier->aiData.bOrders,pSoldier->aiData.bAttitude));
// Flugente: to prevent an accidental call
if ( pSoldier->IsZombie() )
return( ZombieDecideActionBlack(pSoldier) );
ATTACKTYPE BestShot, BestThrow, BestStab ,BestAttack;//dnl ch69 150913 ATTACKTYPE BestShot, BestThrow, BestStab ,BestAttack;//dnl ch69 150913
BOOLEAN fCivilian = (PTR_CIVILIAN && (pSoldier->ubCivilianGroup == NON_CIV_GROUP || pSoldier->aiData.bNeutral || (pSoldier->ubBodyType >= FATCIV && pSoldier->ubBodyType <= CRIPPLECIV) ) ); BOOLEAN fCivilian = (PTR_CIVILIAN && (pSoldier->ubCivilianGroup == NON_CIV_GROUP || pSoldier->aiData.bNeutral || (pSoldier->ubBodyType >= FATCIV && pSoldier->ubBodyType <= CRIPPLECIV) ) );
UINT8 ubBestStance = 1; UINT8 ubBestStance = 1;
@@ -6697,51 +6690,6 @@ void DecideAlertStatus( SOLDIERTYPE *pSoldier )
} }
} }
#if 0
else
{
if (pSoldier->bOppCnt > 0)
{
pSoldier->aiData.bAlertStatus = STATUS_BLACK; // opponent(s) in sight
}
else
{
pSoldier->aiData.bAlertStatus = STATUS_RED; // enemy sector
/*
// wow, JA1 stuff...
// good guys all have a built-in, magic, "enemy detecting radar"...
if (Status.enemies)
pSoldier->aiData.bAlertStatus = STATUS_RED; // enemy sector
else
{
pSoldier->aiData.bAlertStatus = STATUS_GREEN; // secured sector
// if he just dropped back from alert status, and it's a GUARD
if ((oldStatus >= STATUS_RED) && (pSoldier->manCategory == MAN_GUARD))
{
if (TileIsOutOfBounds(pSoldier->whereIWas)) // not assigned to any trees
// FUTURE ENHANCEMENT: Look for unguarded trees with tappers
pSoldier->orders = ONCALL;
else // assigned to trees
// FUTURE ENHANCEMENT: If his tree is now tapperless, go ONCALL
pSoldier->orders = CLOSEPATROL; // go back to his tree area
// turn off any existing bypass to Green and its "hyper-activity"
pSoldier->bypassToGreen = FALSE;
// turn off the "inTheWay" flag, may have been set during TurnBased
pSoldier->inTheWay = FALSE;
// make the guard put his gun away if he has it drawn
HandleNoMoreTarget(pSoldier);
}
}
*/
}
}
#endif
if ( gTacticalStatus.bBoxingState == NOT_BOXING ) if ( gTacticalStatus.bBoxingState == NOT_BOXING )
{ {
@@ -6806,7 +6754,7 @@ void DecideAlertStatus( SOLDIERTYPE *pSoldier )
// ------------------------------ ZOMBIE AI -------------------------- // ------------------------------ ZOMBIE AI --------------------------
INT8 ZombieDecideActionGreen(SOLDIERTYPE *pSoldier) /*INT8 ZombieDecideActionGreen(SOLDIERTYPE *pSoldier)
{ {
DOUBLE iChance, iSneaky = 10; DOUBLE iChance, iSneaky = 10;
INT8 bInWater; INT8 bInWater;
@@ -7206,9 +7154,9 @@ INT8 ZombieDecideActionGreen(SOLDIERTYPE *pSoldier)
// for realtime, regular AI guys will use a standard wait set outside of here // for realtime, regular AI guys will use a standard wait set outside of here
pSoldier->aiData.usActionData = NOWHERE; pSoldier->aiData.usActionData = NOWHERE;
return(AI_ACTION_NONE); return(AI_ACTION_NONE);
} }*/
INT8 ZombieDecideActionYellow(SOLDIERTYPE *pSoldier) /*INT8 ZombieDecideActionYellow(SOLDIERTYPE *pSoldier)
{ {
UINT8 ubNoiseDir; UINT8 ubNoiseDir;
INT32 sNoiseGridNo; INT32 sNoiseGridNo;
@@ -7440,10 +7388,10 @@ INT8 ZombieDecideActionYellow(SOLDIERTYPE *pSoldier)
// by default, if everything else fails, just stands in place without turning // by default, if everything else fails, just stands in place without turning
pSoldier->aiData.usActionData = NOWHERE; pSoldier->aiData.usActionData = NOWHERE;
return(AI_ACTION_NONE); return(AI_ACTION_NONE);
} }*/
INT8 ZombieDecideActionRed(SOLDIERTYPE *pSoldier) /*INT8 ZombieDecideActionRed(SOLDIERTYPE *pSoldier)
{ {
INT32 iDummy; INT32 iDummy;
INT32 iChance,sClosestOpponent,sClosestFriend; INT32 iChance,sClosestOpponent,sClosestFriend;
@@ -8072,14 +8020,6 @@ INT8 ZombieDecideActionRed(SOLDIERTYPE *pSoldier)
// if not in combat or under fire, and we COULD have moved, just chose not to // if not in combat or under fire, and we COULD have moved, just chose not to
if ( (pSoldier->aiData.bAlertStatus != STATUS_BLACK) && !pSoldier->aiData.bUnderFire && ubCanMove && (!gfTurnBasedAI || pSoldier->bActionPoints >= pSoldier->bInitialActionPoints) && ( TileIsOutOfBounds(ClosestReachableDisturbance(pSoldier, &fClimb))) ) if ( (pSoldier->aiData.bAlertStatus != STATUS_BLACK) && !pSoldier->aiData.bUnderFire && ubCanMove && (!gfTurnBasedAI || pSoldier->bActionPoints >= pSoldier->bInitialActionPoints) && ( TileIsOutOfBounds(ClosestReachableDisturbance(pSoldier, &fClimb))) )
{ {
// addition: if soldier is bleeding then reduce bleeding and do nothing
/*if ( pSoldier->bBleeding > MIN_BLEEDING_THRESHOLD )
{
// no! zombies don't bandage themselves!
// reduce bleeding by 1 point per AP (in RT, APs will get recalculated so it's okay)
// pSoldier->bBleeding = __max( 0, pSoldier->bBleeding - (pSoldier->bActionPoints/2) );
return( AI_ACTION_NONE ); // will end-turn/wait depending on whether we're in TB or realtime
}*/
#ifdef RECORDNET #ifdef RECORDNET
fprintf(NetDebugFile,"\tZombieDecideActionRed: guynum %d switching to GREEN AI...\n",pSoldier->ubID); fprintf(NetDebugFile,"\tZombieDecideActionRed: guynum %d switching to GREEN AI...\n",pSoldier->ubID);
#endif #endif
@@ -8126,9 +8066,9 @@ INT8 ZombieDecideActionRed(SOLDIERTYPE *pSoldier)
pSoldier->aiData.usActionData = NOWHERE; pSoldier->aiData.usActionData = NOWHERE;
return(AI_ACTION_NONE); return(AI_ACTION_NONE);
} }*/
INT8 ZombieDecideActionBlack(SOLDIERTYPE *pSoldier) /*INT8 ZombieDecideActionBlack(SOLDIERTYPE *pSoldier)
{ {
INT32 iCoverPercentBetter = 0; INT32 iCoverPercentBetter = 0;
INT32 iOffense, iDefense, iChance; INT32 iOffense, iDefense, iChance;
@@ -8685,12 +8625,12 @@ INT8 ZombieDecideActionBlack(SOLDIERTYPE *pSoldier)
} }
// get the location of the closest reachable opponent // get the location of the closest reachable opponent
/* Flugente 22.02.2012 - A few clarifications: I changed ClosestSeenOpponent so that for zombies, this function also returns an opponent if he is on the // Flugente 22.02.2012 - A few clarifications: I changed ClosestSeenOpponent so that for zombies, this function also returns an opponent if he is on the
* roof of a building, we are not, but our GridNo belongs to that same building. // roof of a building, we are not, but our GridNo belongs to that same building.
* If that is the case, it is clear that we have to get on that roof. However, we cannot do that in BlackState. If, by pure chance, we can still see our // If that is the case, it is clear that we have to get on that roof. However, we cannot do that in BlackState. If, by pure chance, we can still see our
* enemy, we cannot climb (there is no climbing option in BlackState sofar). // enemy, we cannot climb (there is no climbing option in BlackState sofar).
* So, I changed the code so that now we will climb the roof. // So, I changed the code so that now we will climb the roof.
*/
INT32 targetGridNo = -1; INT32 targetGridNo = -1;
INT8 targetbLevel = 0; INT8 targetbLevel = 0;
sClosestOpponent = ClosestSeenOpponentWithRoof(pSoldier, &targetGridNo, &targetbLevel); sClosestOpponent = ClosestSeenOpponentWithRoof(pSoldier, &targetGridNo, &targetbLevel);
@@ -8838,9 +8778,9 @@ INT8 ZombieDecideActionBlack(SOLDIERTYPE *pSoldier)
pSoldier->aiData.usActionData = NOWHERE; pSoldier->aiData.usActionData = NOWHERE;
return(AI_ACTION_NONE); return(AI_ACTION_NONE);
} }*/
INT8 ZombieDecideAction( SOLDIERTYPE *pSoldier ) /*INT8 ZombieDecideAction( SOLDIERTYPE *pSoldier )
{ {
INT8 bAction = AI_ACTION_NONE; INT8 bAction = AI_ACTION_NONE;
@@ -8882,7 +8822,7 @@ INT8 ZombieDecideAction( SOLDIERTYPE *pSoldier )
#endif #endif
return(bAction); return(bAction);
} }*/
INT8 ArmedVehicleDecideAction( SOLDIERTYPE *pSoldier ) INT8 ArmedVehicleDecideAction( SOLDIERTYPE *pSoldier )
@@ -9845,7 +9785,7 @@ INT8 ArmedVehicleDecideActionRed( SOLDIERTYPE *pSoldier)
return(AI_ACTION_NONE); return(AI_ACTION_NONE);
} }
fProneSightCover = ProneSightCoverAtSpot( pSoldier, pSoldier->sGridNo ); fProneSightCover = ProneSightCoverAtSpot(pSoldier, pSoldier->sGridNo, FALSE);
if ( !fProneSightCover || pSoldier->aiData.bUnderFire ) if ( !fProneSightCover || pSoldier->aiData.bUnderFire )
{ {
fDangerousSpot = TRUE; fDangerousSpot = TRUE;
@@ -11901,3 +11841,73 @@ INT8 ArmedVehicleDecideActionBlack( SOLDIERTYPE *pSoldier )
pSoldier->aiData.usActionData = NOWHERE; pSoldier->aiData.usActionData = NOWHERE;
return(AI_ACTION_NONE); return(AI_ACTION_NONE);
} }
extern UINT32 guiTurnCnt;
extern UINT32 guiReinforceTurn;
extern UINT32 guiArrived;
void LogDecideInfo(SOLDIERTYPE *pSoldier)
{
DebugAI(AI_MSG_INFO, pSoldier, String("Turn num %d aware %d", guiTurnCnt, gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition));
DebugAI(AI_MSG_INFO, pSoldier, String("current team %d interrupt occurred %d", gTacticalStatus.ubCurrentTeam, gTacticalStatus.fInterruptOccurred));
DebugAI(AI_MSG_INFO, pSoldier, String("AP=%d/%d %s %s %s %s %s", pSoldier->bActionPoints, pSoldier->bInitialActionPoints, gStr8AlertStatus[pSoldier->aiData.bAlertStatus], gStr8Orders[pSoldier->aiData.bOrders], gStr8Attitude[pSoldier->aiData.bAttitude], gStr8Team[pSoldier->bTeam], gStr8Class[pSoldier->ubSoldierClass]));
DebugAI(AI_MSG_INFO, pSoldier, String("Health %d/%d Breath %d/%d Shock %d Tolerance %d AI Morale %d Morale %d", pSoldier->stats.bLife, pSoldier->stats.bLifeMax, pSoldier->bBreath, pSoldier->bBreathMax, pSoldier->aiData.bShock, CalcSuppressionTolerance(pSoldier), pSoldier->aiData.bAIMorale, pSoldier->aiData.bMorale));
DebugAI(AI_MSG_INFO, pSoldier, String("Spot %d level %d opponents %d", pSoldier->sGridNo, pSoldier->pathing.bLevel, pSoldier->aiData.bOppCnt));
DebugAI(AI_MSG_INFO, pSoldier, String("ubServiceCount %d ubServicePartner %d fDoingSurgery %d", pSoldier->ubServiceCount, pSoldier->ubServicePartner, pSoldier->fDoingSurgery));
if (pSoldier->usAnimState == COWERING || pSoldier->usAnimState == COWERING_PRONE)
{
DebugAI(AI_MSG_INFO, pSoldier, String("Cowering"));
}
if (pSoldier->usAnimState == GIVING_AID || pSoldier->usAnimState == GIVING_AID_PRN)
{
DebugAI(AI_MSG_INFO, pSoldier, String("Giving aid"));
}
//CHAR8 str8[1024];
// show watched locations
INT8 bLoop;
for (bLoop = 0; bLoop < NUM_WATCHED_LOCS; bLoop++)
{
if (!TileIsOutOfBounds(gsWatchedLoc[pSoldier->ubID][bLoop]))
{
DebugAI(AI_MSG_INFO, pSoldier, String("Watched location %d level %d points %d", gsWatchedLoc[pSoldier->ubID][bLoop], gbWatchedLocLevel[pSoldier->ubID][bLoop], gubWatchedLocPoints[pSoldier->ubID][bLoop]));
}
}
LogKnowledgeInfo(pSoldier);
DebugAI(AI_MSG_INFO, pSoldier, String("What I know %d", WhatIKnowThatPublicDont(pSoldier, FALSE)));
DebugAI(AI_MSG_INFO, pSoldier, String("Has Gun %d, Short range weapon %d, Gun Range %d, Gun Ammo %d, Gun Scoped %d ", AICheckHasGun(pSoldier), AICheckShortWeaponRange(pSoldier), AIGunRange(pSoldier), AIGunAmmo(pSoldier), AIGunScoped(pSoldier)));
}
void LogKnowledgeInfo(SOLDIERTYPE *pSoldier)
{
//CHAR8 str8[1024];
//memset(str8, 0, 1024 * sizeof(char));
// show public opponents
for (UINT16 oppID = 0; oppID < MAX_NUM_SOLDIERS; oppID++)
{
if (gbPublicOpplist[pSoldier->bTeam][oppID] != NOT_HEARD_OR_SEEN &&
!MercPtrs[oppID]->aiData.bNeutral)
{
//wcstombs(str8, MercPtrs[oppID]->GetName(), wcslen(MercPtrs[oppID]->GetName())+1);
//wcstombs(str8, MercPtrs[oppID]->GetName(), 1024 - 1);
DebugAI(AI_MSG_INFO, pSoldier, String("public opponent [%d] knowledge %s gridno %d level %d", oppID, gStr8Knowledge[gbPublicOpplist[pSoldier->bTeam][oppID] - OLDEST_HEARD_VALUE], gsPublicLastKnownOppLoc[pSoldier->bTeam][oppID], gbPublicLastKnownOppLevel[pSoldier->bTeam][oppID]));
//swprintf( pStrInfo, L"%s[%d] %s %s\n", pStrInfo, oppID, MercPtrs[oppID]->GetName(), SeenStr(gbPublicOpplist[pSoldier->bTeam][oppID]) );
}
}
// show personal opponents
for (UINT16 oppID = 0; oppID < MAX_NUM_SOLDIERS; oppID++)
{
if (pSoldier->aiData.bOppList[oppID] != NOT_HEARD_OR_SEEN &&
!MercPtrs[oppID]->aiData.bNeutral)
{
//wcstombs(str8, MercPtrs[oppID]->GetName(), wcslen(MercPtrs[oppID]->GetName())+1);
//wcstombs(str8, MercPtrs[oppID]->GetName(), 1024 - 1);
DebugAI(AI_MSG_INFO, pSoldier, String("personal opponent [%d] knowledge %s gridno %d level %d", oppID, gStr8Knowledge[pSoldier->aiData.bOppList[oppID] - OLDEST_HEARD_VALUE], gsLastKnownOppLoc[pSoldier->ubID][oppID], gbLastKnownOppLevel[pSoldier->ubID][oppID]));
//swprintf( pStrInfo, L"%s[%d] %s %s\n", pStrInfo, oppID, MercPtrs[oppID]->GetName(), SeenStr(pSoldier->aiData.bOppList[oppID]) );
}
}
}
+541 -24
View File
@@ -290,14 +290,13 @@ INT32 CalcCoverValue(SOLDIERTYPE *pMe, INT32 sMyGridNo, INT32 iMyThreat, INT32 i
INT16 sTempX, sTempY; INT16 sTempX, sTempY;
FLOAT dMyX, dMyY, dHisX, dHisY; FLOAT dMyX, dMyY, dHisX, dHisY;
INT8 bHisBestCTGT, bHisActualCTGT, bHisCTGT, bMyCTGT; INT8 bHisBestCTGT, bHisActualCTGT, bHisCTGT, bMyCTGT;
INT32 iRangeChange, iRangeFactor, iRangeFactorMultiplier; INT32 iRangeChange, iRangeFactor, iRCD;
SOLDIERTYPE *pHim; SOLDIERTYPE *pHim;
// sevenfm // sevenfm
INT8 bHisLevel; INT8 bHisLevel;
INT8 bMyLevel; INT8 bMyLevel;
INT32 sDist; INT32 sDist;
UINT8 ubCoverReduction;
dMyX = dMyY = dHisX = dHisY = -1.0; dMyX = dMyY = dHisX = dHisY = -1.0;
@@ -338,7 +337,8 @@ INT32 CalcCoverValue(SOLDIERTYPE *pMe, INT32 sMyGridNo, INT32 iMyThreat, INT32 i
} }
if (InWaterOrGas(pHim,sHisGridNo)) if (DeepWater(sHisGridNo, bHisLevel) || InGas(pHim, sHisGridNo))
//if (InWaterOrGas(pHim,sHisGridNo))
{ {
bHisActualCTGT = 0; bHisActualCTGT = 0;
} }
@@ -378,7 +378,8 @@ INT32 CalcCoverValue(SOLDIERTYPE *pMe, INT32 sMyGridNo, INT32 iMyThreat, INT32 i
// if my intended gridno is in water or gas, I can't attack at all from there // if my intended gridno is in water or gas, I can't attack at all from there
// here, for smoke, consider bad // here, for smoke, consider bad
if (InWaterGasOrSmoke(pMe,sMyGridNo)) if (DeepWater(sMyGridNo, bMyLevel) || InGas(pMe, sMyGridNo))
//if (InWaterGasOrSmoke(pMe,sMyGridNo))
{ {
bMyCTGT = 0; bMyCTGT = 0;
} }
@@ -392,8 +393,6 @@ INT32 CalcCoverValue(SOLDIERTYPE *pMe, INT32 sMyGridNo, INT32 iMyThreat, INT32 i
pHim->dXPos = (FLOAT) sTempX; pHim->dXPos = (FLOAT) sTempX;
pHim->dYPos = (FLOAT) sTempY; pHim->dYPos = (FLOAT) sTempY;
} }
// bMyCTGT = ChanceToGetThrough(pMe,sHisGridNo,FAKE,ACTUAL,TESTWALLS,9999,M9PISTOL,NOT_FOR_LOS); // assume a gunshot
// bMyCTGT = SoldierToLocationChanceToGetThrough( pMe, sHisGridNo, pMe->bTargetLevel, pMe->bTargetCubeLevel );
// let's not assume anything about the stance the enemy might take, so take an average // let's not assume anything about the stance the enemy might take, so take an average
// value... no cover give a higher value than partial cover // value... no cover give a higher value than partial cover
@@ -423,6 +422,39 @@ INT32 CalcCoverValue(SOLDIERTYPE *pMe, INT32 sMyGridNo, INT32 iMyThreat, INT32 i
pHim->dYPos = dHisY; // and the 'y' pHim->dYPos = dHisY; // and the 'y'
} }
sDist = PythSpacesAway(sMyGridNo, sHisGridNo);
if (gGameExternalOptions.fAIBetterCover)
{
// sevenfm: special calculations for zombies: zombie is very dangerous at close range
if (pHim->IsZombie() || !AICheckHasGun(pHim))
{
if (sDist < (INT32)(DAY_VISION_RANGE / 2))
{
// 100 - 25
bHisCTGT = 100 - 150 * sDist / DAY_VISION_RANGE;
}
else
{
// 25 - 0
bHisCTGT = 100 * DAY_VISION_RANGE * DAY_VISION_RANGE / (16 * sDist * sDist);
}
}
// sevenfm: if soldier has no gun, use different calculation
if (pMe->IsZombie() || !AICheckHasGun(pMe))
{
if (sDist < (INT32)(DAY_VISION_RANGE / 2))
{
bMyCTGT = 100 - 150 * sDist / DAY_VISION_RANGE;
}
else
{
bMyCTGT = 100 * DAY_VISION_RANGE * DAY_VISION_RANGE / (16 * sDist * sDist);
}
}
}
// these value should be < 1 million each // these value should be < 1 million each
iHisPosValue = bHisCTGT * Threat[uiThreatIndex].iValue * Threat[uiThreatIndex].iAPs; iHisPosValue = bHisCTGT * Threat[uiThreatIndex].iValue * Threat[uiThreatIndex].iAPs;
iMyPosValue = bMyCTGT * iMyThreat * iMyAPsLeft; iMyPosValue = bMyCTGT * iMyThreat * iMyAPsLeft;
@@ -431,18 +463,20 @@ INT32 CalcCoverValue(SOLDIERTYPE *pMe, INT32 sMyGridNo, INT32 iMyThreat, INT32 i
// max 25% at DAY_VISION_RANGE/2, 0 at zero range // max 25% at DAY_VISION_RANGE/2, 0 at zero range
if( gGameExternalOptions.fAIBetterCover ) if( gGameExternalOptions.fAIBetterCover )
{ {
if( AnyCoverFromSpot(sMyGridNo, bMyLevel, sHisGridNo, bHisLevel) ) UINT8 ubCoverBonus = 25;
{
ubCoverReduction = 25;
sDist = PythSpacesAway(sMyGridNo, sHisGridNo);
if (sDist < (INT32)(DAY_VISION_RANGE / 2)) if (sDist < (INT32)(DAY_VISION_RANGE / 2))
{ {
ubCoverReduction = ubCoverReduction * 2 * sDist / DAY_VISION_RANGE; ubCoverBonus = ubCoverBonus * 2 * sDist / DAY_VISION_RANGE;
} }
iHisPosValue -= iHisPosValue * ubCoverReduction / 100; if (!pHim->IsZombie() && AICheckHasGun(pHim) && AnyCoverFromSpot(sMyGridNo, bMyLevel, sHisGridNo, bHisLevel))
iMyPosValue += iMyPosValue * ubCoverReduction / 100; {
iHisPosValue -= iHisPosValue * ubCoverBonus / 100;
iMyPosValue += iMyPosValue * ubCoverBonus / 100;
}
if (!pMe->IsZombie() && AICheckHasGun(pMe) && AnyCoverFromSpot(sHisGridNo, bHisLevel, sMyGridNo, bMyLevel))
{
iHisPosValue += iHisPosValue * ubCoverBonus / 100;
iMyPosValue -= iMyPosValue * ubCoverBonus / 100;
} }
} }
@@ -474,19 +508,16 @@ INT32 CalcCoverValue(SOLDIERTYPE *pMe, INT32 sMyGridNo, INT32 iMyThreat, INT32 i
// high morale prefers decreasing the range (positive factor), while very // high morale prefers decreasing the range (positive factor), while very
// low morale (HOPELESS) prefers increasing it // low morale (HOPELESS) prefers increasing it
// if (bHisCTGT < 100 || (morale - 1 < 0)) iRCD = RangeChangeDesire(pMe);
{
iRangeFactorMultiplier = RangeChangeDesire( pMe ); if (iRCD)
if (iRangeFactorMultiplier)
{ {
iRangeChange = Threat[uiThreatIndex].iOrigRange - iRange; iRangeChange = Threat[uiThreatIndex].iOrigRange - iRange;
if (iRangeChange) if (iRangeChange)
{ {
//iRangeFactor = (iRangeChange * (morale - 1)) / 4; //iRangeFactor = (iRangeChange * (morale - 1)) / 4;
iRangeFactor = (iRangeChange * iRangeFactorMultiplier) / 2; iRangeFactor = (iRangeChange * iRCD) / 2;
// sevenfm: reduce range bonus depending on cover // sevenfm: reduce range bonus depending on cover
if (gGameExternalOptions.fAIBetterCover) if (gGameExternalOptions.fAIBetterCover)
@@ -520,14 +551,13 @@ INT32 CalcCoverValue(SOLDIERTYPE *pMe, INT32 sMyGridNo, INT32 iMyThreat, INT32 i
} }
} }
} }
}
// the farther apart we are, the less important the cover differences are // the farther apart we are, the less important the cover differences are
// the less certain his position, the less important cover differences are // the less certain his position, the less important cover differences are
iReductionFactor = ((MAX_THREAT_RANGE - iRange) * Threat[uiThreatIndex].iCertainty) / iReductionFactor = ((MAX_THREAT_RANGE - iRange) * Threat[uiThreatIndex].iCertainty) /
MAX_THREAT_RANGE; MAX_THREAT_RANGE;
// divide by a 100 to make the numbers more managable and avoid 32-bit limit // divide by a 100 to make the numbers more manageable and avoid 32-bit limit
iThisScale = max( iMyPosValue, iHisPosValue) / 100; iThisScale = max( iMyPosValue, iHisPosValue) / 100;
iThisScale = (iThisScale * iReductionFactor) / 100; iThisScale = (iThisScale * iReductionFactor) / 100;
*iTotalScale += iThisScale; *iTotalScale += iThisScale;
@@ -535,7 +565,7 @@ INT32 CalcCoverValue(SOLDIERTYPE *pMe, INT32 sMyGridNo, INT32 iMyThreat, INT32 i
// POSITIVE COVER VALUE INDICATES THE COVER BENEFITS ME, NEGATIVE RESULT // POSITIVE COVER VALUE INDICATES THE COVER BENEFITS ME, NEGATIVE RESULT
// MEANS IT BENEFITS THE OTHER GUY. // MEANS IT BENEFITS THE OTHER GUY.
// divide by a 100 to make the numbers more managable and avoid 32-bit limit // divide by a 100 to make the numbers more manageable and avoid 32-bit limit
iCoverValue = (iMyPosValue - iHisPosValue) / 100; iCoverValue = (iMyPosValue - iHisPosValue) / 100;
iCoverValue = (iCoverValue * iReductionFactor) / 100; iCoverValue = (iCoverValue * iReductionFactor) / 100;
@@ -1057,6 +1087,13 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
continue; continue;
} }
// zombies only want to hide
if (pSoldier->IsZombie() && !SightCoverAtSpot(pSoldier, sGridNo, TRUE))
{
//DebugCover(pSoldier, String("zombie: no sight cover at new spot, skip"));
continue;
}
iPathCost = gubAIPathCosts[AI_PATHCOST_RADIUS + sXOffset][AI_PATHCOST_RADIUS + sYOffset]; iPathCost = gubAIPathCosts[AI_PATHCOST_RADIUS + sXOffset][AI_PATHCOST_RADIUS + sYOffset];
/* /*
// water is OK, if the only good hiding place requires us to get wet, OK // water is OK, if the only good hiding place requires us to get wet, OK
@@ -3145,3 +3182,483 @@ INT32 FindNearestPassableSpot( INT32 sGridNo, UINT8 usSearchRadius )
return( sNearestSpot ); return( sNearestSpot );
} }
INT32 FindAdvanceSpot(SOLDIERTYPE *pSoldier, INT32 sTargetSpot, INT8 bAction, UINT8 ubType, BOOLEAN fUnlimited)
{
INT32 sGridNo, sRealGridNo;
INT32 sBestSpot = NOWHERE;
INT32 iSearchRange = min(AI_PATHCOST_RADIUS, MAX_TILES_MOVE_TURN);
INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
INT32 iPathCost, iBestPathCost = 0;
INT16 usMovementMode;
INT32 iRoamRange, iDistFromOrigin, sOrigin;
//BOOLEAN fClimbingNecessary;
//INT32 sClimbGridNo;
BOOLEAN fHasMortar;
if (!pSoldier)
{
return NOWHERE;
}
// check target location
if (!NewOKDestination(pSoldier, sTargetSpot, FALSE, pSoldier->pathing.bLevel))
{
//ScreenMsg(FONT_ORANGE, MSG_INTERFACE, L"bad destination %d", sTargetSpot);
return NOWHERE;
}
fHasMortar = AICheckIsMortarOperator(pSoldier);
usMovementMode = DetermineMovementMode(pSoldier, bAction);
UINT8 ubReserveAP = 0;
if (ubType != ADVANCE_SPOT_SIGHT_COVER)
{
if (usMovementMode == RUNNING || usMovementMode == WALKING)
{
ubReserveAP = APBPConstants[AP_CHANGE_FACING] + GetAPsCrouch(pSoldier, TRUE) + GetAPsProne(pSoldier, TRUE);
}
else
{
ubReserveAP = APBPConstants[AP_LOOK_CROUCHED] + GetAPsProne(pSoldier, TRUE);
}
}
if (pSoldier->bActionPoints <= ubReserveAP)
{
return NOWHERE;
}
// check that location is reachable
iBestPathCost = EstimatePlotPath(pSoldier, sTargetSpot, FALSE, FALSE, FALSE, usMovementMode, pSoldier->bStealthMode, FALSE, 0);
if (iBestPathCost == 0)
{
return NOWHERE;
}
iRoamRange = RoamingRange(pSoldier, &sOrigin);
// set AP limit
gubNPCAPBudget = pSoldier->bActionPoints - ubReserveAP;
// set the distance limit of the square region
gubNPCDistLimit = (UINT8)iSearchRange;
// determine maximum horizontal limits
sMaxLeft = min(iSearchRange, (pSoldier->sGridNo % MAXCOL));
//NumMessage("sMaxLeft = ",sMaxLeft);
sMaxRight = min(iSearchRange, MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1));
//NumMessage("sMaxRight = ",sMaxRight);
// determine maximum vertical limits
sMaxUp = min(iSearchRange, (pSoldier->sGridNo / MAXROW));
//NumMessage("sMaxUp = ",sMaxUp);
sMaxDown = min(iSearchRange, MAXROW - ((pSoldier->sGridNo / MAXROW) + 1));
//NumMessage("sMaxDown = ",sMaxDown);
// Call FindBestPath to set flags in all locations that we can
// walk into within range. We have to set some things up first...
// reset the "reachable" flags in the region we're looking at
for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
{
for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
{
sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
if (!(sGridNo >= 0 && sGridNo < WORLD_MAX))
{
continue;
}
gpWorldLevelData[sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE);
}
}
FindBestPath(pSoldier, GRIDSIZE, pSoldier->pathing.bLevel, usMovementMode, COPYREACHABLE, 0);
// Turn off the "reachable" flag for his current location
// so we don't consider it
gpWorldLevelData[pSoldier->sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE);
for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
{
for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
{
// calculate the next potential gridno
sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
if (!(sGridNo >= 0 && sGridNo < WORLD_MAX))
{
continue;
}
if (!(gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_REACHABLE))
{
continue;
}
if (sGridNo == pSoldier->pathing.sBlackList)
{
continue;
}
// check roaming range
if (iRoamRange < MAX_ROAMING_RANGE && !TileIsOutOfBounds(sOrigin))
{
iDistFromOrigin = SpacesAway(sOrigin, sGridNo);
if (iDistFromOrigin > iRoamRange)
{
continue;
}
}
// check if we will be closer to target spot
if (PythSpacesAway(sGridNo, sTargetSpot) > PythSpacesAway(pSoldier->sGridNo, sTargetSpot))
{
continue;
}
// exclude locations with tear/mustard gas (at this point, smoke is cool!)
if (InGas(pSoldier, sGridNo))
{
continue;
}
// skip lighted spots
if (InLightAtNight(sGridNo, pSoldier->pathing.bLevel) &&
!InLightAtNight(pSoldier->sGridNo, pSoldier->pathing.bLevel))
continue;
// skip deep water
if (DeepWater(sGridNo, pSoldier->pathing.bLevel))
{
continue;
}
// skip water
if (Water(sGridNo, pSoldier->pathing.bLevel))
{
continue;
}
// skip locations too close to target spot
if (PythSpacesAway(sGridNo, sTargetSpot) < (INT16)(DAY_VISION_RANGE / 4))
{
continue;
}
// avoid smoke
if (InSmoke(sGridNo, pSoldier->pathing.bLevel))
{
continue;
}
// skip if this spot has no cover nearby
if ((ubType == ADVANCE_SPOT_ANY_COVER || fUnlimited) &&
!FindObstacleNearSpot(sGridNo, pSoldier->pathing.bLevel))
{
continue;
}
// avoid locations near fresh corpses
if (!pSoldier->IsZombie() && GetNearestRottingCorpseAIWarning(sGridNo) > 0)
//if (CorpseWarning(pSoldier, sGridNo, pSoldier->pathing.bLevel, TRUE))
{
continue;
}
// avoid overcrowding
if (!pSoldier->IsZombie() && NumberOfTeamMatesAdjacent(pSoldier, sGridNo) > 1)
{
continue;
}
if (!CheckNPCDestination(pSoldier, sGridNo))
{
continue;
}
if (!LegalNPCDestination(pSoldier, sGridNo, IGNORE_PATH, NOWATER, 0))
{
continue; // skip on to the next potential grid
}
// sevenfm: avoid rooms for mortar operators
if (!AICheckUnderground() &&
pSoldier->pathing.bLevel == 0 &&
fHasMortar &&
InARoom(sGridNo, NULL))
{
continue;
}
if (ubType == ADVANCE_SPOT_SIGHT_COVER)
{
// skip locations with no sight cover
if (!SightCoverAtSpot(pSoldier, sGridNo, fUnlimited))
{
continue;
}
}
else if (ubType == ADVANCE_SPOT_PRONE_COVER)
{
// check prone sight cover at spot
if (!ProneSightCoverAtSpot(pSoldier, sGridNo, fUnlimited))
{
continue;
}
// check that we'll have enough APs to go prone at target spot
/*iPathCost = EstimatePlotPath( pSoldier, sGridNo, FALSE, FALSE, FALSE, usMovementMode, pSoldier->bStealthMode, FALSE, 0);
if( pSoldier->bActionPoints - iPathCost < GetAPsCrouch(pSoldier, TRUE) + GetAPsProne(pSoldier, TRUE) + APBPConstants[AP_CHANGE_FACING] )
{
continue;
}*/
}
else if (ubType == ADVANCE_SPOT_ANY_COVER)
{
// check any cover at spot
if (!AnyCoverAtSpot(pSoldier, sGridNo))
{
continue;
}
// check that we'll have enough APs to go prone at target spot
/*iPathCost = EstimatePlotPath( pSoldier, sGridNo, FALSE, FALSE, FALSE, usMovementMode, pSoldier->bStealthMode, FALSE, 0);
if( pSoldier->bActionPoints - iPathCost < GetAPsCrouch(pSoldier, TRUE) + GetAPsProne(pSoldier, TRUE) + APBPConstants[AP_CHANGE_FACING] )
{
continue;
}*/
}
sRealGridNo = pSoldier->sGridNo;
pSoldier->sGridNo = sGridNo;
iPathCost = EstimatePlotPath(pSoldier, sTargetSpot, FALSE, FALSE, FALSE, usMovementMode, pSoldier->bStealthMode, FALSE, 0);
//iPathCost = PlotPath(pSoldier, sTargetSpot, FALSE, FALSE, FALSE, usMovementMode, pSoldier->bStealthMode, FALSE, 0);
//iPathCost = EstimatePathCostToLocation( pSoldier, sTargetSpot, pSoldier->pathing.bLevel, TRUE, &fClimbingNecessary, &sClimbGridNo );
pSoldier->sGridNo = sRealGridNo;
// skip location if no path to target spot
if (iPathCost == 0)
{
//ScreenMsg(FONT_ORANGE, MSG_INTERFACE, L"cannot find path to destination %d at %d", sTargetSpot, sGridNo);
continue;
}
//if( sBestSpot == NOWHERE || iPathCost < iBestPathCost )
if (iPathCost < iBestPathCost)
{
sBestSpot = sGridNo;
iBestPathCost = iPathCost;
}
}
}
gubNPCAPBudget = 0;
gubNPCDistLimit = 0;
return(sBestSpot);
}
// find spot with cover, max dist from opponents
INT32 FindRetreatSpot(SOLDIERTYPE *pSoldier)
{
INT32 sGridNo;
INT32 sBestSpot = NOWHERE;
INT32 iSearchRange = MAX_TILES_MOVE_TURN;
INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
INT16 usMovementMode;
INT32 iRoamRange, iDistFromOrigin, sOrigin;
//BOOLEAN fClimbingNecessary;
//INT32 sClimbGridNo;
UINT8 ubReserveAP;
INT8 bAction = AI_ACTION_TAKE_COVER;
INT32 sClosestOpponent;
INT16 sDistance, sClosestDistance;
if (!pSoldier)
{
return NOWHERE;
}
sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL);
if (TileIsOutOfBounds(sClosestOpponent))
{
// no one to hide from
return NOWHERE;
}
sClosestDistance = PythSpacesAway(pSoldier->sGridNo, sClosestOpponent);
//ubReserveAP = GetAPsCrouch(pSoldier, TRUE) + GetAPsProne(pSoldier, TRUE) + APBPConstants[AP_CHANGE_FACING];
ubReserveAP = GetAPsCrouch(pSoldier, TRUE) + APBPConstants[AP_CHANGE_FACING];
if (pSoldier->bActionPoints <= ubReserveAP)
{
return NOWHERE;
}
iRoamRange = RoamingRange(pSoldier, &sOrigin);
// set AP limit
gubNPCAPBudget = pSoldier->bActionPoints;
// set the distance limit of the square region
gubNPCDistLimit = (UINT8)iSearchRange;
// determine maximum horizontal limits
sMaxLeft = min(iSearchRange, (pSoldier->sGridNo % MAXCOL));
//NumMessage("sMaxLeft = ",sMaxLeft);
sMaxRight = min(iSearchRange, MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1));
//NumMessage("sMaxRight = ",sMaxRight);
// determine maximum vertical limits
sMaxUp = min(iSearchRange, (pSoldier->sGridNo / MAXROW));
//NumMessage("sMaxUp = ",sMaxUp);
sMaxDown = min(iSearchRange, MAXROW - ((pSoldier->sGridNo / MAXROW) + 1));
//NumMessage("sMaxDown = ",sMaxDown);
// Call FindBestPath to set flags in all locations that we can
// walk into within range. We have to set some things up first...
// reset the "reachable" flags in the region we're looking at
for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
{
for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
{
sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
if (!(sGridNo >= 0 && sGridNo < WORLD_MAX))
{
continue;
}
gpWorldLevelData[sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE);
}
}
usMovementMode = DetermineMovementMode(pSoldier, bAction);
FindBestPath(pSoldier, GRIDSIZE, pSoldier->pathing.bLevel, usMovementMode, COPYREACHABLE, 0);
// Turn off the "reachable" flag for his current location
// so we don't consider it
gpWorldLevelData[pSoldier->sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE);
for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
{
for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
{
// calculate the next potential gridno
sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
if (!(sGridNo >= 0 && sGridNo < WORLD_MAX))
{
continue;
}
if (!(gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_REACHABLE))
{
continue;
}
if (sGridNo == pSoldier->pathing.sBlackList)
{
continue;
}
// check roaming range
if (iRoamRange < MAX_ROAMING_RANGE && !TileIsOutOfBounds(sOrigin))
{
iDistFromOrigin = SpacesAway(sOrigin, sGridNo);
if (iDistFromOrigin > iRoamRange)
{
continue;
}
}
// exclude locations with tear/mustard gas (at this point, smoke is cool!)
if (InGas(pSoldier, sGridNo))
{
continue;
}
// skip lighted spots
if (InLightAtNight(sGridNo, pSoldier->pathing.bLevel) &&
!InLightAtNight(pSoldier->sGridNo, pSoldier->pathing.bLevel))
continue;
// skip deep water
if (DeepWater(sGridNo, pSoldier->pathing.bLevel))
{
continue;
}
// skip water
if (Water(sGridNo, pSoldier->pathing.bLevel))
{
continue;
}
// avoid locations near fresh corpses
if (!pSoldier->IsZombie() && GetNearestRottingCorpseAIWarning(sGridNo) > 0)
//if (CorpseWarning(pSoldier, sGridNo, pSoldier->pathing.bLevel, TRUE))
{
continue;
}
if (!pSoldier->IsZombie() && NumberOfTeamMatesAdjacent(pSoldier, sGridNo) > 0)
{
continue;
}
if (!CheckNPCDestination(pSoldier, sGridNo))
{
continue;
}
// avoid smoke
/*if( InSmoke(sGridNo, pSoldier->pathing.bLevel, FALSE) )
{
continue;
}*/
if (!LegalNPCDestination(pSoldier, sGridNo, IGNORE_PATH, NOWATER, 0))
{
continue; // skip on to the next potential grid
}
// skip if too close to enemy
//sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL);
sDistance = PythSpacesAway(sGridNo, sClosestOpponent);
if (sDistance < (INT16)(DAY_VISION_RANGE / 2))
{
continue;
}
// skip if this spot has no cover nearby
if (!FindObstacleNearSpot(sGridNo, pSoldier->pathing.bLevel))
{
continue;
}
if (!AnyCoverAtSpot(pSoldier, sGridNo))
{
continue;
}
// check prone sight cover at spot
/*if( !ProneSightCoverAtSpot(pSoldier, sGridNo) )
{
continue;
}*/
if (sDistance > sClosestDistance)
{
sBestSpot = sGridNo;
sClosestDistance = sDistance;
}
}
}
gubNPCAPBudget = 0;
gubNPCDistLimit = 0;
return(sBestSpot);
}
+1
View File
@@ -45,6 +45,7 @@
<ClCompile Include="PanicButtons.cpp" /> <ClCompile Include="PanicButtons.cpp" />
<ClCompile Include="QuestDebug.cpp" /> <ClCompile Include="QuestDebug.cpp" />
<ClCompile Include="Realtime.cpp" /> <ClCompile Include="Realtime.cpp" />
<ClCompile Include="ZombieDecideAction.cpp" />
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Globals"> <PropertyGroup Label="Globals">
<ProjectGuid>{B369A125-E62E-46AE-9285-58003D688301}</ProjectGuid> <ProjectGuid>{B369A125-E62E-46AE-9285-58003D688301}</ProjectGuid>
@@ -71,5 +71,8 @@
<ClCompile Include="Realtime.cpp"> <ClCompile Include="Realtime.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="ZombieDecideAction.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
</Project> </Project>
+1
View File
@@ -45,6 +45,7 @@
<ClCompile Include="PanicButtons.cpp" /> <ClCompile Include="PanicButtons.cpp" />
<ClCompile Include="QuestDebug.cpp" /> <ClCompile Include="QuestDebug.cpp" />
<ClCompile Include="Realtime.cpp" /> <ClCompile Include="Realtime.cpp" />
<ClCompile Include="ZombieDecideAction.cpp" />
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Globals"> <PropertyGroup Label="Globals">
<ProjectGuid>{B369A125-E62E-46AE-9285-58003D688301}</ProjectGuid> <ProjectGuid>{B369A125-E62E-46AE-9285-58003D688301}</ProjectGuid>
@@ -71,5 +71,8 @@
<ClCompile Include="Realtime.cpp"> <ClCompile Include="Realtime.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="ZombieDecideAction.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
</Project> </Project>
+1
View File
@@ -45,6 +45,7 @@
<ClCompile Include="PanicButtons.cpp" /> <ClCompile Include="PanicButtons.cpp" />
<ClCompile Include="QuestDebug.cpp" /> <ClCompile Include="QuestDebug.cpp" />
<ClCompile Include="Realtime.cpp" /> <ClCompile Include="Realtime.cpp" />
<ClCompile Include="ZombieDecideAction.cpp" />
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Globals"> <PropertyGroup Label="Globals">
<ProjectGuid>{B369A125-E62E-46AE-9285-58003D688301}</ProjectGuid> <ProjectGuid>{B369A125-E62E-46AE-9285-58003D688301}</ProjectGuid>
File diff suppressed because it is too large Load Diff
+52 -3
View File
@@ -5,6 +5,7 @@
#include "worlddef.h" #include "worlddef.h"
#include "Soldier Control.h" #include "Soldier Control.h"
#include "Isometric Utils.h" #include "Isometric Utils.h"
#include "Rotting Corpses.h"
#define TESTAICONTROL #define TESTAICONTROL
@@ -109,6 +110,7 @@ typedef enum
AI_ACTION_DOCTOR_SELF, // added by Flugente: AI-ONLY! bandage/surgery on self. DO NOT USE THIS FOR MERCS!!! AI_ACTION_DOCTOR_SELF, // added by Flugente: AI-ONLY! bandage/surgery on self. DO NOT USE THIS FOR MERCS!!!
AI_ACTION_SELFDETONATE, // added by Flugente: blow up an explosive in own inventory AI_ACTION_SELFDETONATE, // added by Flugente: blow up an explosive in own inventory
AI_ACTION_STOP_MEDIC, // sevenfm: stop giving aid animation AI_ACTION_STOP_MEDIC, // sevenfm: stop giving aid animation
AI_ACTION_LAST = AI_ACTION_STOP_MEDIC
} ActionType; } ActionType;
@@ -153,6 +155,12 @@ enum QuoteActionType
extern INT8 gbDiff[MAX_DIFF_PARMS][5]; extern INT8 gbDiff[MAX_DIFF_PARMS][5];
enum{
ADVANCE_SPOT_SIGHT_COVER,
ADVANCE_SPOT_PRONE_COVER,
ADVANCE_SPOT_ANY_COVER
};
void ActionDone(SOLDIERTYPE *pSoldier); void ActionDone(SOLDIERTYPE *pSoldier);
INT16 ActionInProgress(SOLDIERTYPE *pSoldier); INT16 ActionInProgress(SOLDIERTYPE *pSoldier);
@@ -173,6 +181,8 @@ INT32 ClosestUnDisguisedPC( SOLDIERTYPE *pSoldier, INT32 * psDistance ); // Flug
BOOLEAN CanAutoBandage( BOOLEAN fDoFullCheck ); BOOLEAN CanAutoBandage( BOOLEAN fDoFullCheck );
void DebugAI( STR szOutput ); void DebugAI( STR szOutput );
enum { AI_MSG_START, AI_MSG_DECIDE, AI_MSG_INFO, AI_MSG_TOPIC };
void DebugAI(INT8 bMsgType, SOLDIERTYPE *pSoldier, STR szOutput, INT8 bAction = -1);
INT8 DecideAction(SOLDIERTYPE *pSoldier); INT8 DecideAction(SOLDIERTYPE *pSoldier);
INT8 DecideActionBlack(SOLDIERTYPE *pSoldier); INT8 DecideActionBlack(SOLDIERTYPE *pSoldier);
INT8 DecideActionEscort(SOLDIERTYPE *pSoldier); INT8 DecideActionEscort(SOLDIERTYPE *pSoldier);
@@ -293,8 +303,13 @@ BOOLEAN AICheckIsFlanking( SOLDIERTYPE *pSoldier );
INT8 CalcMoraleNew(SOLDIERTYPE *pSoldier); INT8 CalcMoraleNew(SOLDIERTYPE *pSoldier);
BOOLEAN ProneSightCoverAtSpot( SOLDIERTYPE *pSoldier, INT32 sSpot ); BOOLEAN ProneSightCoverAtSpot(SOLDIERTYPE *pSoldier, INT32 sSpot, BOOLEAN fUnlimited);
BOOLEAN SightCoverAtSpot( SOLDIERTYPE *pSoldier, INT32 sSpot ); BOOLEAN SightCoverAtSpot(SOLDIERTYPE *pSoldier, INT32 sSpot, BOOLEAN fUnlimited);
BOOLEAN AnyCoverAtSpot(SOLDIERTYPE *pSoldier, INT32 sSpot);
BOOLEAN AnyCoverFromSpot(INT32 sSpot, INT8 bLevel, INT32 sThreatLoc, INT8 bThreatLevel);
INT32 FindAdvanceSpot(SOLDIERTYPE *pSoldier, INT32 sTargetSpot, INT8 bAction, UINT8 ubType, BOOLEAN fUnlimited);
INT32 FindRetreatSpot(SOLDIERTYPE *pSoldier);
BOOLEAN CheckDoorAtGridno(UINT32 usGridNo); BOOLEAN CheckDoorAtGridno(UINT32 usGridNo);
BOOLEAN CheckDoorNearGridno(UINT32 usGridNo); BOOLEAN CheckDoorNearGridno(UINT32 usGridNo);
@@ -307,19 +322,53 @@ BOOLEAN FindNearbyExplosiveStructure(INT32 sSpot, INT8 bLevel);
INT16 DistanceToClosestActiveOpponent(SOLDIERTYPE *pSoldier, INT32 sSpot); INT16 DistanceToClosestActiveOpponent(SOLDIERTYPE *pSoldier, INT32 sSpot);
BOOLEAN ValidOpponent(SOLDIERTYPE* pSoldier, SOLDIERTYPE* pOpponent); BOOLEAN ValidOpponent(SOLDIERTYPE* pSoldier, SOLDIERTYPE* pOpponent);
BOOLEAN AnyCoverFromSpot( INT32 sSpot, INT8 bLevel, INT32 sThreatLoc, INT8 bThreatLevel );
UINT8 CountSeenEnemiesLastTurn( SOLDIERTYPE *pSoldier ); UINT8 CountSeenEnemiesLastTurn( SOLDIERTYPE *pSoldier );
BOOLEAN FindObstacleNearSpot(INT32 sSpot, INT8 bLevel);
BOOLEAN CheckNPCDestination(SOLDIERTYPE *pSoldier, INT32 sGridNo); BOOLEAN CheckNPCDestination(SOLDIERTYPE *pSoldier, INT32 sGridNo);
UINT8 SpotDangerLevel(SOLDIERTYPE *pSoldier, INT32 sGridNo); UINT8 SpotDangerLevel(SOLDIERTYPE *pSoldier, INT32 sGridNo);
BOOLEAN AllowDeepWaterFlanking(SOLDIERTYPE *pSoldier); BOOLEAN AllowDeepWaterFlanking(SOLDIERTYPE *pSoldier);
BOOLEAN AICheckUnderground(void);
BOOLEAN CorpseWarning(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT8 bLevel);
BOOLEAN CorpseEnemyTeam(ROTTING_CORPSE *pCorpse);
BOOLEAN CorpseMilitiaTeam(ROTTING_CORPSE *pCorpse);
BOOLEAN NorthSpot(INT32 sSpot, INT8 bLevel); BOOLEAN NorthSpot(INT32 sSpot, INT8 bLevel);
BOOLEAN SoldierAI(SOLDIERTYPE *pSoldier); BOOLEAN SoldierAI(SOLDIERTYPE *pSoldier);
UINT8 AIDirection(INT32 sSpot1, INT32 sSpot2);
BOOLEAN AIGunScoped(SOLDIERTYPE *pSoldier);
BOOLEAN AIGunInHandScoped(SOLDIERTYPE *pSoldier);
UINT16 AIGunRange(SOLDIERTYPE *pSoldier); // gun range in tiles
UINT16 AIGunClass(SOLDIERTYPE *pSoldier);
UINT16 AIGunType(SOLDIERTYPE *pSoldier);
UINT16 AIGunAmmo(SOLDIERTYPE *pSoldier);
BOOLEAN AIGunAutofireCapable(SOLDIERTYPE *pSoldier);
UINT8 AIGunDeadliness(SOLDIERTYPE *pSoldier);
BOOLEAN AICheckNVG(SOLDIERTYPE *pSoldier);
BOOLEAN AICheckHasWeaponOfType(SOLDIERTYPE *pSoldier, UINT8 ubWeaponType);
BOOLEAN AICheckHasGun(SOLDIERTYPE *pSoldier);
BOOLEAN AICheckShortWeaponRange(SOLDIERTYPE *pSoldier);
BOOLEAN AICheckIsSniper(SOLDIERTYPE *pSoldier);
BOOLEAN AICheckIsMarksman(SOLDIERTYPE *pSoldier);
BOOLEAN AICheckIsMachinegunner(SOLDIERTYPE *pSoldier);
BOOLEAN AICheckIsRadioOperator(SOLDIERTYPE *pSoldier);
BOOLEAN AICheckIsMedic(SOLDIERTYPE *pSoldier);
BOOLEAN AICheckIsMortarOperator(SOLDIERTYPE *pSoldier);
BOOLEAN AICheckIsGLOperator(SOLDIERTYPE *pSoldier);
BOOLEAN AICheckIsOfficer(SOLDIERTYPE *pSoldier);
BOOLEAN AICheckIsCommander(SOLDIERTYPE *pSoldier);
#define MAX_FLANKS_RED 25 #define MAX_FLANKS_RED 25
#define MAX_FLANKS_YELLOW 25 #define MAX_FLANKS_YELLOW 25
#define MAX_TILES_MOVE_TURN (APBPConstants[AP_MAXIMUM] / (APBPConstants[AP_MOVEMENT_FLAT] + APBPConstants[AP_MODIFIER_RUN]))
// vision range defines // vision range defines
#define DAY_VISION_RANGE (gGameExternalOptions.ubStraightSightRange * STRAIGHT_RATIO * 2) #define DAY_VISION_RANGE (gGameExternalOptions.ubStraightSightRange * STRAIGHT_RATIO * 2)
#define NIGHT_VISION_RANGE (gGameExternalOptions.ubStraightSightRange * STRAIGHT_RATIO ) #define NIGHT_VISION_RANGE (gGameExternalOptions.ubStraightSightRange * STRAIGHT_RATIO )
+31
View File
@@ -26,6 +26,9 @@
#include "Campaign Types.h" #include "Campaign Types.h"
#include "Tactical Save.h" #include "Tactical Save.h"
#include "strategicmap.h" #include "strategicmap.h"
// sevenfm
#include "environment.h"
#include "Render Fun.h"
#endif #endif
#include "SaveLoadGame.h" #include "SaveLoadGame.h"
@@ -1188,6 +1191,34 @@ BOOL GetRandomSignalSmokeGridNo(INT32* psGridNo)
return TRUE; return TRUE;
} }
// sevenfm: check smoke effect of certain type at gridno
BOOLEAN CheckSmokeEffect(INT32 sGridNo, INT8 bLevel, INT8 bType)
{
UINT32 uiCnt;
INT8 bSmokeEffectLevel;
//loop through all smoke effects
for (uiCnt = 0; uiCnt < guiNumSmokeEffects; uiCnt++)
{
if (gSmokeEffectData[uiCnt].fAllocated &&
gSmokeEffectData[uiCnt].sGridNo == sGridNo &&
gSmokeEffectData[uiCnt].bType == bType)
{
if (gSmokeEffectData[uiCnt].bFlags & SMOKE_EFFECT_ON_ROOF)
bSmokeEffectLevel = 1;
else
bSmokeEffectLevel = 0;
if (bSmokeEffectLevel == bLevel)
{
return TRUE;
}
}
}
return FALSE;
}
BOOLEAN FindVisibleSmokeEffect(INT8 bType) BOOLEAN FindVisibleSmokeEffect(INT8 bType)
{ {
UINT32 uiCnt; UINT32 uiCnt;
+2
View File
@@ -71,6 +71,8 @@ void UpdateSmokeEffectGraphics( );
// Flugente: get the gridno and blevel of a random smoke signal, if one exists // Flugente: get the gridno and blevel of a random smoke signal, if one exists
BOOL GetRandomSignalSmokeGridNo(INT32* psGridNo); BOOL GetRandomSignalSmokeGridNo(INT32* psGridNo);
// sevenfm: check if smoke effect of certain type is present at gridno
BOOLEAN CheckSmokeEffect(INT32 sGridNo, INT8 bLevel, INT8 bType);
// find smoke effect on visible screen // find smoke effect on visible screen
BOOLEAN FindVisibleSmokeEffect(INT8 bType); BOOLEAN FindVisibleSmokeEffect(INT8 bType);
+18 -1
View File
@@ -793,6 +793,7 @@ void PhysicsDeleteObject( REAL_OBJECT *pObject )
} }
} }
INT16 gsWaterSplashSoundNum = -1;
BOOLEAN PhysicsCheckForCollisions( REAL_OBJECT *pObject, INT32 *piCollisionID ) BOOLEAN PhysicsCheckForCollisions( REAL_OBJECT *pObject, INT32 *piCollisionID )
{ {
@@ -1113,11 +1114,27 @@ BOOLEAN PhysicsCheckForCollisions( REAL_OBJECT *pObject, INT32 *piCollisionID )
// sevenfm: also play sound // sevenfm: also play sound
CHAR8 zFilename[512]; CHAR8 zFilename[512];
sprintf(zFilename, "sounds\\misc\\Splash%d.ogg", Random(3) + 1); // prepare water splash sound
if (gsWaterSplashSoundNum < 0)
{
gsWaterSplashSoundNum = 0;
do
{
gsWaterSplashSoundNum++;
sprintf(zFilename, "sounds\\misc\\Splash%d.ogg", gsWaterSplashSoundNum);
} while (FileExists(zFilename));
gsWaterSplashSoundNum--;
}
if (gsWaterSplashSoundNum > 0)
{
sprintf(zFilename, "sounds\\misc\\Splash%d.ogg", Random(gsWaterSplashSoundNum) + 1);
if (FileExists(zFilename)) if (FileExists(zFilename))
{
PlayJA2SampleFromFile(zFilename, RATE_11025, SoundVolume(MIDVOLUME, pObject->sGridNo), 1, SoundDir(pObject->sGridNo)); PlayJA2SampleFromFile(zFilename, RATE_11025, SoundVolume(MIDVOLUME, pObject->sGridNo), 1, SoundDir(pObject->sGridNo));
} }
} }
}
}
} }
else if ( iCollisionCode == COLLISION_ROOF || iCollisionCode == COLLISION_INTERIOR_ROOF ) else if ( iCollisionCode == COLLISION_ROOF || iCollisionCode == COLLISION_INTERIOR_ROOF )