#include "types.h" //#include "soldier control.h" #include "ai.h" #include "AIInternals.h" #include "opplist.h" #include "Items.h" #include "Rotting Corpses.h" #include "soldier add.h" //forward declarations of common classes to eliminate includes class OBJECTTYPE; class SOLDIERTYPE; extern STR8 gStr8AlertStatus[]; extern STR8 gStr8Attitude[]; extern STR8 gStr8Orders[]; extern STR8 gStr8Team[]; extern STR8 gStr8Class[]; extern INT8 STRAIGHT; //lal #define CAN_CALL( s ) (s->ubBodyType != BLOODCAT && s->ubBodyType != LARVAE_MONSTER && s->ubBodyType != INFANT_MONSTER) #define CAN_LISTEN_TO_CALL( s ) (s->ubBodyType != BLOODCAT && s->ubBodyType != LARVAE_MONSTER) enum { CALLER_FEMALE = 0, CALLER_MALE, CALLER_INFANT, CALLER_QUEEN, NUM_CREATURE_CALLERS } CreatureCaller; enum { CREATURE_MOBILE = 0, CREATURE_CRAWLER, CREATURE_IMMOBILE } CreatureMobility; #define FRENZY_THRESHOLD 8 #define MAX_EAT_DIST 5 INT8 gbCallPriority[NUM_CREATURE_CALLS][NUM_CREATURE_CALLERS] = { {0, 0, 0 },//CALL_NONE {3, 5, 12},//CALL_1_PREY {5, 9, 12},//CALL_MULTIPLE_PREY {4, 7, 12},//CALL_ATTACKED {6, 9, 12},//CALL_CRIPPLED }; INT8 gbHuntCallPriority[NUM_CREATURE_CALLS] = { 4, //CALL_1_PREY 5, //CALL_MULTIPLE_PREY 7, //CALL_ATTACKED 8 //CALL_CRIPPLED }; #define PRIORITY_DECR_DISTANCE 30 #define CALL_1_OPPONENT CALL_1_PREY #define CALL_MULTIPLE_OPPONENT CALL_MULTIPLE_PREY void CreatureCall( SOLDIERTYPE * pCaller ) { UINT8 ubCallerType=0; UINT8 ubReceiver; INT8 bFullPriority; INT8 bPriority; SOLDIERTYPE * pReceiver; UINT16 usDistToCaller; // communicate call to all creatures on map through ultrasonics gTacticalStatus.Team[pCaller->bTeam].bAwareOfOpposition = TRUE; // bAction should be AI_ACTION_CREATURE_CALL (new) // usActionData is call enum # switch (pCaller->ubBodyType) { case ADULTFEMALEMONSTER: case YAF_MONSTER: ubCallerType = CALLER_FEMALE; break; case QUEENMONSTER: ubCallerType = CALLER_QUEEN; break; // need to add male case AM_MONSTER: case YAM_MONSTER: ubCallerType = CALLER_MALE; break; default: ubCallerType = CALLER_FEMALE; break; } if (pCaller->aiData.bHunting) // which should only be set for females outside of the hive { bFullPriority = gbHuntCallPriority[pCaller->aiData.usActionData]; } else { bFullPriority = gbCallPriority[pCaller->aiData.usActionData][ubCallerType]; } // OK, do animation based on body type... switch (pCaller->ubBodyType) { case ADULTFEMALEMONSTER: case YAF_MONSTER: case AM_MONSTER: case YAM_MONSTER: pCaller->EVENT_InitNewSoldierAnim( MONSTER_UP, 0 , FALSE ); break; case QUEENMONSTER: pCaller->EVENT_InitNewSoldierAnim( QUEEN_CALL, 0 , FALSE ); break; } for (ubReceiver = gTacticalStatus.Team[ pCaller->bTeam ].bFirstID; ubReceiver <= gTacticalStatus.Team[ pCaller->bTeam ].bLastID; ubReceiver++) { pReceiver = MercPtrs[ubReceiver]; if (pReceiver->bActive && pReceiver->bInSector && (pReceiver->stats.bLife >= OKLIFE) && (pReceiver != pCaller) && (pReceiver->aiData.bAlertStatus < STATUS_BLACK)) { if (pReceiver->ubBodyType != LARVAE_MONSTER && pReceiver->ubBodyType != INFANT_MONSTER && pReceiver->ubBodyType != QUEENMONSTER) { usDistToCaller = PythSpacesAway( pReceiver->sGridNo, pCaller->sGridNo ); bPriority = bFullPriority - (INT8) (usDistToCaller / PRIORITY_DECR_DISTANCE); if (bPriority > pReceiver->aiData.bCallPriority) { pReceiver->aiData.bCallPriority = bPriority; pReceiver->aiData.bAlertStatus = STATUS_RED; // our status can't be more than red to begin with pReceiver->aiData.ubCaller = pCaller->ubID; pReceiver->aiData.sCallerGridNo = pCaller->sGridNo; pReceiver->aiData.bCallActedUpon = FALSE; CancelAIAction(pReceiver, FORCE); if ((bPriority > FRENZY_THRESHOLD) && (pReceiver->ubBodyType == ADULTFEMALEMONSTER || pReceiver->ubBodyType == YAF_MONSTER)) { // go berzerk! pReceiver->aiData.bFrenzied = TRUE; } } } } } } INT8 CreatureDecideActionGreen( SOLDIERTYPE * pSoldier ) { INT32 iChance, iSneaky = 10; //INT8 bInWater; INT8 bInGas; //bInWater = pSoldier->MercInWater(); // NB creatures would ignore smoke completely :-) if ( pSoldier->aiData.bMobility == CREATURE_CRAWLER && pSoldier->bActionPoints < pSoldier->bInitialActionPoints) { return( AI_ACTION_NONE ); } bInGas = InGas( pSoldier, pSoldier->sGridNo ); if (pSoldier->aiData.bMobility == CREATURE_MOBILE) { if (TrackScent( pSoldier )) { return( AI_ACTION_TRACK ); } //////////////////////////////////////////////////////////////////////////// // POINT PATROL: move towards next point unless getting a bit winded //////////////////////////////////////////////////////////////////////////// // this takes priority over water/gas checks, so that point patrol WILL work // from island to island, and through gas covered areas, too if ((pSoldier->aiData.bOrders == POINTPATROL) && (pSoldier->bBreath >= 50)) { if (PointPatrolAI(pSoldier)) { if (!gfTurnBasedAI) { // pause at the end of the walk! pSoldier->aiData.bNextAction = AI_ACTION_WAIT; pSoldier->aiData.usNextActionData = (UINT16) REALTIME_CREATURE_AI_DELAY; } return(AI_ACTION_POINT_PATROL); } } if ((pSoldier->aiData.bOrders == RNDPTPATROL) && (pSoldier->bBreath >=50)) { if (RandomPointPatrolAI(pSoldier)) { if (!gfTurnBasedAI) { // pause at the end of the walk! pSoldier->aiData.bNextAction = AI_ACTION_WAIT; pSoldier->aiData.usNextActionData = (UINT16) REALTIME_CREATURE_AI_DELAY; } return(AI_ACTION_POINT_PATROL); } } //////////////////////////////////////////////////////////////////////////// // WHEN LEFT IN WATER OR GAS, GO TO NEAREST REACHABLE SPOT OF UNGASSED LAND //////////////////////////////////////////////////////////////////////////// if ( /*bInWater || */ bInGas) { pSoldier->aiData.usActionData = FindNearestUngassedLand(pSoldier); if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) { #ifdef DEBUGDECISIONS STR16 tempstr; sprintf(tempstr,"%s - SEEKING NEAREST UNGASSED LAND at grid %d",pSoldier->name,pSoldier->aiData.usActionData); AIPopMessage(tempstr); #endif return(AI_ACTION_LEAVE_WATER_GAS); } } } //////////////////////////////////////////////////////////////////////// // REST IF RUNNING OUT OF BREATH //////////////////////////////////////////////////////////////////////// // if our breath is running a bit low, and we're not in the way or in water if ((pSoldier->bBreath < 75) /*&& !bInWater*/) { // take a breather for gods sake! pSoldier->aiData.usActionData = NOWHERE; return(AI_ACTION_NONE); } //////////////////////////////////////////////////////////////////////////// // RANDOM PATROL: determine % chance to start a new patrol route //////////////////////////////////////////////////////////////////////////// if (pSoldier->aiData.bMobility != CREATURE_IMMOBILE ) { iChance = 25; // set base chance according to orders switch (pSoldier->aiData.bOrders) { case STATIONARY: iChance += -20; break; case ONGUARD: iChance += -15; break; case ONCALL: break; case CLOSEPATROL: iChance += +15; break; case RNDPTPATROL: case POINTPATROL: iChance = 0; break; case FARPATROL: iChance += +25; break; case SEEKENEMY: iChance += -10; break; } // modify chance of patrol (and whether it's a sneaky one) by attitude switch (pSoldier->aiData.bAttitude) { case DEFENSIVE: iChance += -10; break; case BRAVESOLO: iChance += 5; break; case BRAVEAID: break; case CUNNINGSOLO: iChance += 5; iSneaky += 10; break; case CUNNINGAID: iSneaky += 5; break; case AGGRESSIVE: iChance += 10; iSneaky += -5; break; } // reduce chance for any injury, less likely to wander around when hurt iChance -= (pSoldier->stats.bLifeMax - pSoldier->stats.bLife); // reduce chance if breath is down, less likely to wander around when tired iChance -= (100 - pSoldier->bBreath); // if we're in water with land miles (> 25 tiles) away, // OR if we roll under the chance calculated if ( /*bInWater ||*/ ((INT16) PreRandom(100) < iChance)) { pSoldier->aiData.usActionData = RandDestWithinRange(pSoldier); if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) { #ifdef DEBUGDECISIONS STR16 tempstr; sprintf(tempstr,"%s - RANDOM PATROL to grid %d",pSoldier->name,pSoldier->aiData.usActionData); AIPopMessage(tempstr); #endif if (!gfTurnBasedAI) { // pause at the end of the walk! pSoldier->aiData.bNextAction = AI_ACTION_WAIT; pSoldier->aiData.usNextActionData = (UINT16) REALTIME_CREATURE_AI_DELAY; if (pSoldier->aiData.bMobility == CREATURE_CRAWLER) { pSoldier->aiData.usNextActionData *= 2; } } return(AI_ACTION_RANDOM_PATROL); } } /* if (pSoldier->aiData.bMobility == CREATURE_MOBILE) { //////////////////////////////////////////////////////////////////////////// // SEEK FRIEND: determine %chance for man to pay a friendly visit //////////////////////////////////////////////////////////////////////////// iChance = 25; // set base chance and maximum seeking distance according to orders switch (pSoldier->aiData.bOrders) { case STATIONARY: iChance += -20; break; case ONGUARD: iChance += -15; break; case ONCALL: break; case CLOSEPATROL: iChance += +10; break; case RNDPTPATROL: case POINTPATROL: iChance = -10; break; case FARPATROL: iChance += +20; break; case SEEKENEMY: iChance += -10; break; } // modify for attitude switch (pSoldier->aiData.bAttitude) { case DEFENSIVE: break; case BRAVESOLO: iChance /= 2; break; // loners case BRAVEAID: iChance += 10; break; // friendly case CUNNINGSOLO: iChance /= 2; break; // loners case CUNNINGAID: iChance += 10; break; // friendly case AGGRESSIVE: break; } // reduce chance for any injury, less likely to wander around when hurt iChance -= (pSoldier->stats.bLifeMax - pSoldier->stats.bLife); // reduce chance if breath is down iChance -= (100 - pSoldier->bBreath); // very likely to wait when exhausted if ((INT16) PreRandom(100) < iChance) { if (RandomFriendWithin(pSoldier)) { #ifdef DEBUGDECISIONS sprintf(tempstr,"%s - SEEK FRIEND at grid %d",pSoldier->name,pSoldier->aiData.usActionData); AIPopMessage(tempstr); #endif if (!gfTurnBasedAI) { // pause at the end of the walk! pSoldier->aiData.bNextAction = AI_ACTION_WAIT; pSoldier->aiData.usNextActionData = (UINT16) REALTIME_CREATURE_AI_DELAY; } return(AI_ACTION_SEEK_FRIEND); } } } */ //////////////////////////////////////////////////////////////////////////// // LOOK AROUND: determine %chance for man to turn in place //////////////////////////////////////////////////////////////////////////// // avoid 2 consecutive random turns in a row if (pSoldier->aiData.bLastAction != AI_ACTION_CHANGE_FACING && (GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints)) { iChance = 25; // set base chance according to orders if (pSoldier->aiData.bOrders == STATIONARY) iChance += 25; if (pSoldier->aiData.bOrders == ONGUARD) iChance += 20; if (pSoldier->aiData.bAttitude == DEFENSIVE) iChance += 25; if ((INT16)PreRandom(100) < iChance) { // roll random directions (stored in actionData) until different from current do { // if man has a LEGAL dominant facing, and isn't facing it, he will turn // back towards that facing 50% of the time here (normally just enemies) if ((pSoldier->aiData.bDominantDir >= 0) && (pSoldier->aiData.bDominantDir <= 8) && (pSoldier->ubDirection != pSoldier->aiData.bDominantDir) && PreRandom(2)) { pSoldier->aiData.usActionData = pSoldier->aiData.bDominantDir; } else { pSoldier->aiData.usActionData = (UINT16)PreRandom(8); } } while (pSoldier->aiData.usActionData == pSoldier->ubDirection); #ifdef DEBUGDECISIONS STR16 tempstr; sprintf(tempstr,"%s - TURNS to face direction %d",pSoldier->name,pSoldier->aiData.usActionData); AIPopMessage(tempstr); #endif if ( ValidCreatureTurn( pSoldier, (INT8) pSoldier->aiData.usActionData ) ) //pSoldier->InternalIsValidStance( (INT8) pSoldier->usActionData, ANIM_STAND ) ) { if (!gfTurnBasedAI) { // pause at the end of the turn! pSoldier->aiData.bNextAction = AI_ACTION_WAIT; pSoldier->aiData.usNextActionData = (UINT16) REALTIME_CREATURE_AI_DELAY; } return(AI_ACTION_CHANGE_FACING); } } } } //////////////////////////////////////////////////////////////////////////// // NONE: //////////////////////////////////////////////////////////////////////////// // by default, if everything else fails, just stands in place without turning pSoldier->aiData.usActionData = NOWHERE; return(AI_ACTION_NONE); } INT8 CreatureDecideActionYellow( SOLDIERTYPE * pSoldier ) { // monster AI - heard something UINT8 ubNoiseDir; INT32 sNoiseGridNo; INT32 iNoiseValue; INT32 iChance, iSneaky; BOOLEAN fClimb; BOOLEAN fReachable; // INT16 sClosestFriend; if ( pSoldier->aiData.bMobility == CREATURE_CRAWLER && pSoldier->bActionPoints < pSoldier->bInitialActionPoints) { return( AI_ACTION_NONE ); } // determine the most important noise heard, and its relative value sNoiseGridNo = MostImportantNoiseHeard(pSoldier,&iNoiseValue,&fClimb, &fReachable); //NumMessage("iNoiseValue = ",iNoiseValue); if (TileIsOutOfBounds(sNoiseGridNo)) { // then we have no business being under YELLOW status any more! #ifdef RECORDNET fprintf(NetDebugFile,"\nDecideActionYellow: ERROR - No important noise known by guynum %d\n\n",pSoldier->ubID); #endif #ifdef BETAVERSION NumMessage("DecideActionYellow: ERROR - No important noise known by guynum ",pSoldier->ubID); #endif return(AI_ACTION_NONE); } //////////////////////////////////////////////////////////////////////////// // LOOK AROUND TOWARD NOISE: determine %chance for man to turn towards noise //////////////////////////////////////////////////////////////////////////// if (pSoldier->aiData.bMobility != CREATURE_IMMOBILE) { // determine direction from this soldier in which the noise lies ubNoiseDir = GetDirectionFromCenterCellXYGridNo(pSoldier->sGridNo, sNoiseGridNo); // if soldier is not already facing in that direction, // and the noise source is close enough that it could possibly be seen if ((GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints) && (pSoldier->ubDirection != ubNoiseDir) && PythSpacesAway(pSoldier->sGridNo,sNoiseGridNo) <= STRAIGHT) { // set base chance according to orders if ((pSoldier->aiData.bOrders == STATIONARY) || (pSoldier->aiData.bOrders == ONGUARD)) iChance = 60; else // all other orders iChance = 35; if (pSoldier->aiData.bAttitude == DEFENSIVE) iChance += 15; if ((INT16)PreRandom(100) < iChance) { pSoldier->aiData.usActionData = ubNoiseDir; #ifdef DEBUGDECISIONS STR16 tempstr; sprintf(tempstr,"%s - TURNS TOWARDS NOISE to face direction %d",pSoldier->name,pSoldier->aiData.usActionData); AIPopMessage(tempstr); #endif //if ( pSoldier->InternalIsValidStance( (INT8) pSoldier->usActionData, ANIM_STAND ) ) if ( ValidCreatureTurn( pSoldier, (INT8) pSoldier->aiData.usActionData ) ) { return(AI_ACTION_CHANGE_FACING); } } } } //////////////////////////////////////////////////////////////////////// // REST IF RUNNING OUT OF BREATH //////////////////////////////////////////////////////////////////////// // if our breath is running a bit low, and we're not in water if ((pSoldier->bBreath < 25) /*&& !pSoldier->MercInWater() */ ) { // take a breather for gods sake! pSoldier->aiData.usActionData = NOWHERE; return(AI_ACTION_NONE); } if (pSoldier->aiData.bMobility != CREATURE_IMMOBILE && fReachable) { //////////////////////////////////////////////////////////////////////////// // SEEK NOISE //////////////////////////////////////////////////////////////////////////// // remember that noise value is negative, and closer to 0 => more important! iChance = 75 + iNoiseValue; iSneaky = 30; // set base chance according to orders switch (pSoldier->aiData.bOrders) { case STATIONARY: iChance += -20; break; case ONGUARD: iChance += -15; break; case ONCALL: break; case CLOSEPATROL: iChance += -10; break; case RNDPTPATROL: case POINTPATROL: break; case FARPATROL: iChance += 10; break; case SEEKENEMY: iChance += 25; break; } // modify chance of patrol (and whether it's a sneaky one) by attitude switch (pSoldier->aiData.bAttitude) { case DEFENSIVE: iChance += -10; iSneaky += 15; break; case BRAVESOLO: iChance += 10; break; case BRAVEAID: iChance += 5; break; case CUNNINGSOLO: iChance += 5; iSneaky += 30; break; case CUNNINGAID: iSneaky += 30; break; case AGGRESSIVE: iChance += 20; iSneaky += -10; break; } // reduce chance if breath is down, less likely to wander around when tired iChance -= (100 - pSoldier->bBreath); if ((INT16) PreRandom(100) < iChance) { pSoldier->aiData.usActionData = GoAsFarAsPossibleTowards(pSoldier,sNoiseGridNo, AI_ACTION_SEEK_NOISE); if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) { #ifdef DEBUGDECISIONS STR16 tempstr; sprintf(tempstr,"%s - INVESTIGATING NOISE at grid %d, moving to %d", pSoldier->name,sNoiseGridNo,pSoldier->aiData.usActionData); AIPopMessage(tempstr); #endif return(AI_ACTION_SEEK_NOISE); } } // Okay, we're not following up on the noise... but let's follow any // scent trails available if (TrackScent( pSoldier )) { return( AI_ACTION_TRACK ); } } //////////////////////////////////////////////////////////////////////////// // DO NOTHING: Not enough points left to move, so save them for next turn //////////////////////////////////////////////////////////////////////////// #ifdef DEBUGDECISIONS AINameMessage(pSoldier,"- DOES NOTHING (YELLOW)",1000); #endif // by default, if everything else fails, just stands in place without turning pSoldier->aiData.usActionData = NOWHERE; return(AI_ACTION_NONE); } INT8 CreatureDecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) { // monster AI - hostile mammals somewhere around! INT32 iChance, sClosestOpponent /*,sClosestOpponent,sClosestFriend*/; INT32 sClosestDisturbance; INT32 sDistVisible; UINT8 ubCanMove,ubOpponentDir; //INT8 bInWater; INT8 bInGas; INT8 bSeekPts = 0, bHelpPts = 0, bHidePts = 0; INT32 sAdjustedGridNo; BOOLEAN fChangeLevel; // if we have absolutely no action points, we can't do a thing under RED! if (!pSoldier->bActionPoints) { pSoldier->aiData.usActionData = NOWHERE; return(AI_ACTION_NONE); } if ( pSoldier->aiData.bMobility == CREATURE_CRAWLER && pSoldier->bActionPoints < pSoldier->bInitialActionPoints) { return( AI_ACTION_NONE ); } // can this guy move to any of the neighbouring squares ? (sets TRUE/FALSE) ubCanMove = ((pSoldier->aiData.bMobility != CREATURE_IMMOBILE) && (pSoldier->bActionPoints >= MinPtsToMove(pSoldier))); // determine if we happen to be in water (in which case we're in BIG trouble!) //bInWater = pSoldier->MercInWater(); // check if standing in tear gas without a gas mask on bInGas = InGas( pSoldier, pSoldier->sGridNo ); //////////////////////////////////////////////////////////////////////////// // WHEN IN GAS, GO TO NEAREST REACHABLE SPOT OF UNGASSED LAND //////////////////////////////////////////////////////////////////////////// if (bInGas && ubCanMove) { pSoldier->aiData.usActionData = FindNearestUngassedLand(pSoldier); if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) { #ifdef DEBUGDECISIONS STR16 tempstr; sprintf(tempstr,"%s - SEEKING NEAREST UNGASSED LAND at grid %d",pSoldier->name,pSoldier->aiData.usActionData); AIPopMessage(tempstr); #endif return(AI_ACTION_LEAVE_WATER_GAS); } } //////////////////////////////////////////////////////////////////////////// // CALL FOR AID IF HURT //////////////////////////////////////////////////////////////////////////// if ( CAN_CALL( pSoldier ) ) { if ((pSoldier->bActionPoints >= APBPConstants[AP_RADIO]) && (gTacticalStatus.Team[pSoldier->bTeam].bMenInSector > 1)) { if (pSoldier->stats.bLife < pSoldier->bOldLife) { // got injured, maybe call if ((pSoldier->bOldLife == pSoldier->stats.bLifeMax) && (pSoldier->bOldLife - pSoldier->stats.bLife > 10)) { // hurt for first time! pSoldier->aiData.usActionData = CALL_CRIPPLED; pSoldier->bOldLife = pSoldier->stats.bLife; // don't want to call more than once return(AI_ACTION_CREATURE_CALL); } else if (pSoldier->stats.bLifeMax / pSoldier->stats.bLife > 2) { // crippled, 1/3 or less health! pSoldier->aiData.usActionData = CALL_ATTACKED; pSoldier->bOldLife = pSoldier->stats.bLife; // don't want to call more than once return(AI_ACTION_CREATURE_CALL); } } } } //////////////////////////////////////////////////////////////////////// // CROUCH & REST IF RUNNING OUT OF BREATH //////////////////////////////////////////////////////////////////////// // if our breath is running a bit low, and we're not in water or under fire if ((pSoldier->bBreath < 25) /*&& !bInWater*/ && !pSoldier->aiData.bUnderFire) { #ifdef DEBUGDECISIONS STR16 tempstr; sprintf(tempstr,"%s RESTS (STATUS RED), breath = %d",pSoldier->name,pSoldier->bBreath); AIPopMessage(tempstr); #endif pSoldier->aiData.usActionData = NOWHERE; return(AI_ACTION_NONE); } //////////////////////////////////////////////////////////////////////////// // CALL IN SIGHTING: determine %chance to call others and report contact //////////////////////////////////////////////////////////////////////////// // if we're a computer merc, and we have the action points remaining to RADIO // (we never want NPCs to choose to radio if they would have to wait a turn) if ( CAN_CALL( pSoldier ) && (!gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition) ) { if ((pSoldier->bActionPoints >= APBPConstants[AP_RADIO]) && (gTacticalStatus.Team[pSoldier->bTeam].bMenInSector > 1)) { // if there hasn't been a general sighting call sent yet // might want to check the specifics of who we see iChance = 20; if (iChance) { #ifdef DEBUGDECISIONS AINumMessage("Chance to call sighting = ",iChance); #endif if ((INT16) PreRandom(100) < iChance) { #ifdef DEBUGDECISIONS AINameMessage(pSoldier,"decides to call an alert!",1000); #endif pSoldier->aiData.usActionData = CALL_1_PREY; return(AI_ACTION_CREATURE_CALL); } } } } if ( pSoldier->aiData.bMobility != CREATURE_IMMOBILE ) { if ( FindAIUsableObjClass( pSoldier, IC_WEAPON ) == ITEM_NOT_FOUND ) { // probably a baby bug... run away! run away! // look for best place to RUN AWAY to (farthest from the closest threat) pSoldier->aiData.usActionData = FindSpotMaxDistFromOpponents( pSoldier ); if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) { return(AI_ACTION_RUN_AWAY); } else { return( AI_ACTION_NONE ); } } // Respond to call if any if ( CAN_LISTEN_TO_CALL( pSoldier ) && pSoldier->aiData.ubCaller != NOBODY ) { if ( PythSpacesAway( pSoldier->sGridNo, pSoldier->aiData.sCallerGridNo ) <= STOPSHORTDIST ) { // call completed... hmm, nothing found pSoldier->aiData.ubCaller = NOBODY; } else { pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, pSoldier->aiData.sCallerGridNo, -1, AI_ACTION_SEEK_FRIEND, FLAG_STOPSHORT); if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) { #ifdef DEBUGDECISIONS STR16 tempstr; sprintf(tempstr,"%s - SEEKING FRIEND at %d, MOVING to %d", pSoldier->name,sClosestFriend,pSoldier->aiData.usActionData); AIPopMessage(tempstr); #endif return(AI_ACTION_SEEK_FRIEND); } } } // get the location of the closest reachable opponent sClosestDisturbance = ClosestReachableDisturbance(pSoldier, &fChangeLevel); // if there is an opponent reachable if (!TileIsOutOfBounds(sClosestDisturbance)) { ////////////////////////////////////////////////////////////////////// // SEEK CLOSEST DISTURBANCE: GO DIRECTLY TOWARDS CLOSEST KNOWN OPPONENT ////////////////////////////////////////////////////////////////////// // try to move towards him pSoldier->aiData.usActionData = GoAsFarAsPossibleTowards(pSoldier,sClosestDisturbance,AI_ACTION_SEEK_OPPONENT); // if it's possible if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) { #ifdef DEBUGDECISIONS // do it! STR16 tempstr; sprintf(tempstr,"%s - SEEKING OPPONENT at grid %d, MOVING to %d", pSoldier->name,sClosestDisturbance,pSoldier->aiData.usActionData); AIPopMessage(tempstr); #endif return(AI_ACTION_SEEK_OPPONENT); } } //////////////////////////////////////////////////////////////////////////// // TAKE A BITE, PERHAPS //////////////////////////////////////////////////////////////////////////// if (pSoldier->aiData.bHunting) { pSoldier->aiData.usActionData = FindNearestRottingCorpse( pSoldier ); // need smell/visibility check? if (PythSpacesAway( pSoldier->sGridNo, pSoldier->aiData.usActionData) < MAX_EAT_DIST ) { INT32 sGridNo; sGridNo = FindAdjacentGridEx( pSoldier, pSoldier->aiData.usActionData, &ubOpponentDir, &sAdjustedGridNo, FALSE, FALSE ); if ( sGridNo != -1 ) { pSoldier->aiData.usActionData = sGridNo; return( AI_ACTION_APPROACH_MERC ); } } } //////////////////////////////////////////////////////////////////////////// // TRACK A SCENT, IF ONE IS PRESENT //////////////////////////////////////////////////////////////////////////// if (TrackScent( pSoldier )) { return( AI_ACTION_TRACK ); } //////////////////////////////////////////////////////////////////////////// // LOOK AROUND TOWARD CLOSEST KNOWN OPPONENT, IF KNOWN //////////////////////////////////////////////////////////////////////////// if (GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints) { // determine the location of the known closest opponent // (don't care if he's conscious, don't care if he's reachable at all) sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL); if (!TileIsOutOfBounds(sClosestOpponent)) { // determine direction from this soldier to the closest opponent ubOpponentDir = GetDirectionFromCenterCellXYGridNo(pSoldier->sGridNo, sClosestOpponent); // if soldier is not already facing in that direction, // and the opponent is close enough that he could possibly be seen // note, have to change this to use the level returned from ClosestKnownOpponent sDistVisible = pSoldier->GetMaxDistanceVisible(sClosestOpponent, 0 ); if ((pSoldier->ubDirection != ubOpponentDir) && (PythSpacesAway(pSoldier->sGridNo,sClosestOpponent) <= sDistVisible)) { // set base chance according to orders if ((pSoldier->aiData.bOrders == STATIONARY) || (pSoldier->aiData.bOrders == ONGUARD)) iChance = 50; else // all other orders iChance = 25; if (pSoldier->aiData.bAttitude == DEFENSIVE) iChance += 25; //if ( (INT16)PreRandom(100) < iChance && pSoldier->InternalIsValidStance( ubOpponentDir, ANIM_STAND ) ) if ( (INT16)PreRandom(100) < iChance && ValidCreatureTurn( pSoldier, ubOpponentDir ) ) { pSoldier->aiData.usActionData = ubOpponentDir; #ifdef DEBUGDECISIONS STR16 tempstr; sprintf(tempstr,"%s - TURNS TOWARDS CLOSEST ENEMY to face direction %d",pSoldier->name,pSoldier->aiData.usActionData); AIPopMessage(tempstr); #endif return(AI_ACTION_CHANGE_FACING); } } } } } //////////////////////////////////////////////////////////////////////////// // LEAVE THE SECTOR //////////////////////////////////////////////////////////////////////////// // NOT IMPLEMENTED //////////////////////////////////////////////////////////////////////////// // DO NOTHING: Not enough points left to move, so save them for next turn //////////////////////////////////////////////////////////////////////////// #ifdef DEBUGDECISIONS AINameMessage(pSoldier,"- DOES NOTHING (RED)",1000); #endif pSoldier->aiData.usActionData = NOWHERE; return(AI_ACTION_NONE); } INT8 CreatureDecideActionBlack( SOLDIERTYPE * pSoldier ) { // monster AI - hostile mammals in sense range INT32 sClosestOpponent,sBestCover = NOWHERE; INT32 sClosestDisturbance; INT16 ubMinAPCost; UINT8 ubCanMove/*,bInWater*/,bInGas; INT8 bDirection; UINT8 ubBestAttackAction; INT8 bCanAttack; INT8 bSpitIn, bWeaponIn; UINT32 uiChance; ATTACKTYPE BestShot, BestStab, BestAttack, CurrStab;//dnl ch69 150913 BOOLEAN fRunAway = FALSE; BOOLEAN fChangeLevel; // if we have absolutely no action points, we can't do a thing under BLACK! if (!pSoldier->bActionPoints) { pSoldier->aiData.usActionData = NOWHERE; return(AI_ACTION_NONE); } if ( pSoldier->aiData.bMobility == CREATURE_CRAWLER && pSoldier->bActionPoints < pSoldier->bInitialActionPoints) { return( AI_ACTION_NONE ); } //////////////////////////////////////////////////////////////////////////// // CALL FOR AID IF HURT OR IF OTHERS ARE UNAWARE //////////////////////////////////////////////////////////////////////////// if ( CAN_CALL( pSoldier ) ) { if ((pSoldier->bActionPoints >= APBPConstants[AP_RADIO]) && (gTacticalStatus.Team[pSoldier->bTeam].bMenInSector > 1)) { if (pSoldier->stats.bLife < pSoldier->bOldLife) { // got injured, maybe call /* // don't call when crippled and have target... save breath for attacking! if ((pSoldier->bOldLife == pSoldier->stats.bLifeMax) && (pSoldier->bOldLife - pSoldier->stats.bLife > 10)) { // hurt for first time! pSoldier->aiData.usActionData = CALL_CRIPPLED; pSoldier->bOldLife = pSoldier->stats.bLife; // don't want to call more than once return(AI_ACTION_CREATURE_CALL); } else */ if (pSoldier->stats.bLifeMax / pSoldier->stats.bLife > 2) { // crippled, 1/3 or less health! pSoldier->aiData.usActionData = CALL_ATTACKED; pSoldier->bOldLife = pSoldier->stats.bLife; // don't want to call more than once return(AI_ACTION_CREATURE_CALL); } } else { if (!(gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition)) { if (pSoldier->ubBodyType == QUEENMONSTER) { uiChance = 100; } else { uiChance = 20 * pSoldier->aiData.bOppCnt; } if ( Random( 100 ) < uiChance ) { // alert! alert! if (pSoldier->aiData.bOppCnt > 1) { pSoldier->aiData.usActionData = CALL_MULTIPLE_PREY; } else { pSoldier->aiData.usActionData = CALL_1_PREY; } return(AI_ACTION_CREATURE_CALL); } } } } } // can this guy move to any of the neighbouring squares ? (sets TRUE/FALSE) ubCanMove = ((pSoldier->aiData.bMobility != CREATURE_IMMOBILE) && (pSoldier->bActionPoints >= MinPtsToMove(pSoldier))); // determine if we happen to be in water (in which case we're in BIG trouble!) //bInWater = pSoldier->MercInWater(); // check if standing in tear gas without a gas mask on bInGas = InGas( pSoldier, pSoldier->sGridNo ); //////////////////////////////////////////////////////////////////////////// // IF GASSED, OR REALLY TIRED (ON THE VERGE OF COLLAPSING), TRY TO RUN AWAY //////////////////////////////////////////////////////////////////////////// // if we're desperately short on breath (it's OK if we're in water, though!) if (bInGas || (pSoldier->bBreath < 5)) { // if soldier has enough APs left to move at least 1 square's worth if (ubCanMove) { // look for best place to RUN AWAY to (farthest from the closest threat) pSoldier->aiData.usActionData = FindSpotMaxDistFromOpponents(pSoldier); if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) { #ifdef DEBUGDECISIONS STR16 tempstr; sprintf(tempstr,"%s - GASSED or LOW ON BREATH (%d), RUNNING AWAY to grid %d",pSoldier->name,pSoldier->bBreath,pSoldier->aiData.usActionData); AIPopMessage(tempstr); #endif return(AI_ACTION_RUN_AWAY); } } } //////////////////////////////////////////////////////////////////////////// // STUCK IN WATER OR GAS, NO COVER, GO TO NEAREST SPOT OF UNGASSED LAND //////////////////////////////////////////////////////////////////////////// // if soldier in water/gas has enough APs left to move at least 1 square if ((/*bInWater ||*/ bInGas) && ubCanMove) { pSoldier->aiData.usActionData = FindNearestUngassedLand(pSoldier); if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) { #ifdef DEBUGDECISIONS STR16 tempstr; sprintf(tempstr,"%s - SEEKING NEAREST UNGASSED LAND at grid %d",pSoldier->name,pSoldier->aiData.usActionData); AIPopMessage(tempstr); #endif return(AI_ACTION_LEAVE_WATER_GAS); } } //////////////////////////////////////////////////////////////////////////// // SOLDIER CAN ATTACK IF NOT IN WATER/GAS AND NOT DOING SOMETHING TOO FUNKY //////////////////////////////////////////////////////////////////////////// // NPCs in water/tear gas without masks are not permitted to shoot/stab/throw if ((pSoldier->bActionPoints < 2) /*|| bInWater*/ || bInGas) { bCanAttack = FALSE; } else { bCanAttack = CanNPCAttack(pSoldier); if (bCanAttack != TRUE) { if ( bCanAttack == NOSHOOT_NOAMMO ) { pSoldier->inv[HANDPOS].fFlags |= OBJECT_AI_UNUSABLE; // try to find a bladed weapon if (pSoldier->ubBodyType == QUEENMONSTER) { bWeaponIn = FindObjClass( pSoldier, IC_TENTACLES ); } else { bWeaponIn = FindObjClass( pSoldier, IC_BLADE ); } if ( bWeaponIn != NO_SLOT ) { DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"creaturedecideactionblack: swapping item into hand"); RearrangePocket(pSoldier,HANDPOS,bWeaponIn,FOREVER); bCanAttack = TRUE; } else { // infants who exhaust their spit should flee! fRunAway = TRUE; bCanAttack = FALSE; } } else { bCanAttack = FALSE; } } } BestShot.ubPossible = FALSE; // by default, assume Shooting isn't possible BestStab.ubPossible = FALSE; // by default, assume Stabbing isn't possible BestAttack.ubChanceToReallyHit = 0; bSpitIn = NO_SLOT; // if we are able attack if (bCanAttack) { ////////////////////////////////////////////////////////////////////////// // FIRE A GUN AT AN OPPONENT ////////////////////////////////////////////////////////////////////////// pSoldier->bAimShotLocation = AIM_SHOT_RANDOM; bWeaponIn = FindObjClass( pSoldier, IC_GUN ); if (bWeaponIn != NO_SLOT) { if (Item[pSoldier->inv[bWeaponIn].usItem].usItemClass == IC_GUN && pSoldier->inv[bWeaponIn][0]->data.gun.bGunStatus >= USABLE) { if (pSoldier->inv[bWeaponIn][0]->data.gun.ubGunShotsLeft > 0) { bSpitIn = bWeaponIn; // if it's in another pocket, swap it into his hand temporarily if (bWeaponIn != HANDPOS) { DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"creaturedecideactionblack: swapping gun into hand"); RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY); } // now it better be a gun, or the guy can't shoot (but has other attack(s)) // get the minimum cost to attack the same target with this gun ubMinAPCost = MinAPsToAttack(pSoldier,pSoldier->sLastTarget,DONTADDTURNCOST,0); // if we have enough action points to shoot with this gun if (pSoldier->bActionPoints >= ubMinAPCost) { // look around for a worthy target (which sets BestShot.ubPossible) CalcBestShot(pSoldier,&BestShot); if (BestShot.ubPossible) { BestShot.bWeaponIn = bWeaponIn; // if the selected opponent is not a threat (unconscious & !serviced) // (usually, this means all the guys we see our unconscious, but, on // rare occasions, we may not be able to shoot a healthy guy, too) if ((Menptr[BestShot.ubOpponent].stats.bLife < OKLIFE) && !Menptr[BestShot.ubOpponent].bService) { // if our attitude is NOT aggressive if (pSoldier->aiData.bAttitude != AGGRESSIVE) { // get the location of the closest CONSCIOUS reachable opponent sClosestDisturbance = ClosestReachableDisturbance(pSoldier, &fChangeLevel); // if we found one if (!TileIsOutOfBounds(sClosestDisturbance)) { // don't bother checking GRENADES/KNIVES, he can't have conscious targets #ifdef RECORDNET fprintf(NetDebugFile,"\tDecideActionBlack: all visible opponents unconscious, switching to RED AI...\n"); #endif // then make decision as if at alert status RED, but make sure // we don't try to SEEK OPPONENT the unconscious guy! return(CreatureDecideActionRed(pSoldier,FALSE)); } // else kill the guy, he could be the last opponent alive in this sector } // else aggressive guys will ALWAYS finish off unconscious opponents } // now we KNOW FOR SURE that we will do something (shoot, at least) NPCDoesAct(pSoldier); } } // if it was in his holster, swap it back into his holster for now if (bWeaponIn != HANDPOS) { DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"creaturedecideactionblack: swapping gun into holster"); RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY); } } else { // out of ammo! reload if possible! } } } ////////////////////////////////////////////////////////////////////////// // GO STAB AN OPPONENT WITH A KNIFE ////////////////////////////////////////////////////////////////////////// // if soldier has a knife in his hand if (pSoldier->ubBodyType == QUEENMONSTER) { bWeaponIn = FindObjClass( pSoldier, IC_TENTACLES ); } else if ( pSoldier->ubBodyType == BLOODCAT ) { // 1 in 3 attack with teeth, otherwise with claws if ( PreRandom( 3 ) ) { bWeaponIn = FindObj( pSoldier, BLOODCAT_CLAW_ATTACK ); } else { bWeaponIn = FindObj( pSoldier, BLOODCAT_BITE ); } } else { if (bSpitIn != NO_SLOT && Random( 4 ) ) { // spitters only consider a blade attack 1 time in 4 bWeaponIn = NO_SLOT; } else { bWeaponIn = FindObjClass( pSoldier, IC_BLADE ); } } BestStab.iAttackValue = 0; // if the soldier does have a usable knife somewhere // spitters don't always consider using their claws if ( bWeaponIn != NO_SLOT ) { // if it's in his holster, swap it into his hand temporarily if (bWeaponIn != HANDPOS) { DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"creaturedecideactionblack: swapping knife into hand"); RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY); } // get the minimum cost to attack with this knife ubMinAPCost = MinAPsToAttack(pSoldier,pSoldier->sLastTarget,DONTADDTURNCOST,0); //sprintf(tempstr,"%s - ubMinAPCost = %d",pSoldier->name,ubMinAPCost); //PopMessage(tempstr); // if we can afford the minimum AP cost to stab with this knife weapon if (pSoldier->bActionPoints >= ubMinAPCost) { // then look around for a worthy target (which sets BestStab.ubPossible) if (pSoldier->ubBodyType == QUEENMONSTER) { CalcTentacleAttack( pSoldier, &CurrStab ); } else { CalcBestStab(pSoldier, &CurrStab, TRUE); } if (CurrStab.ubPossible) { // now we KNOW FOR SURE that we will do something (stab, at least) NPCDoesAct(pSoldier); } // if it was in his holster, swap it back into his holster for now if (bWeaponIn != HANDPOS) { DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"creaturedecideactionblack: swapping knife into holster"); RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY); } if (CurrStab.iAttackValue > BestStab.iAttackValue) { CurrStab.bWeaponIn = bWeaponIn; memcpy(&BestStab,&CurrStab,sizeof(BestStab)); } } } ////////////////////////////////////////////////////////////////////////// // CHOOSE THE BEST TYPE OF ATTACK OUT OF THOSE FOUND TO BE POSSIBLE ////////////////////////////////////////////////////////////////////////// if (BestShot.ubPossible) { BestAttack.iAttackValue = BestShot.iAttackValue; ubBestAttackAction = AI_ACTION_FIRE_GUN; } else { BestAttack.iAttackValue = 0; ubBestAttackAction = AI_ACTION_NONE; } if (BestStab.ubPossible && BestStab.iAttackValue > (BestAttack.iAttackValue * 12) / 10 ) { BestAttack.iAttackValue = BestStab.iAttackValue; ubBestAttackAction = AI_ACTION_KNIFE_MOVE; } // if attack is still desirable (meaning it's also preferred to taking cover) if (ubBestAttackAction != AI_ACTION_NONE) { // copy the information on the best action selected into BestAttack struct switch (ubBestAttackAction) { case AI_ACTION_FIRE_GUN: memcpy(&BestAttack,&BestShot,sizeof(BestAttack)); break; case AI_ACTION_KNIFE_MOVE: memcpy(&BestAttack,&BestStab,sizeof(BestAttack)); break; } // if necessary, swap the weapon into the hand position if (BestAttack.bWeaponIn != HANDPOS) { // IS THIS NOT BEING SET RIGHT????? DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"creaturedecideactionblack: swapping best weapon into hand"); RearrangePocket(pSoldier,HANDPOS,BestAttack.bWeaponIn,FOREVER); } ////////////////////////////////////////////////////////////////////////// // GO AHEAD & ATTACK! ////////////////////////////////////////////////////////////////////////// pSoldier->aiData.usActionData = BestAttack.sTarget; //POSSIBLE STRUCTURE PROBLEM WITH INT8/INT16. GOTTHARD 7/14/08 pSoldier->aiData.bAimTime = BestAttack.ubAimTime; if ( ubBestAttackAction == AI_ACTION_FIRE_GUN && BestAttack.ubChanceToReallyHit > 50 ) { pSoldier->bAimShotLocation = AIM_SHOT_HEAD; } else { pSoldier->bAimShotLocation = AIM_SHOT_RANDOM; } #ifdef DEBUGDECISIONS STR tempstr; sprintf( tempstr, "%d(%s) %s %d(%s) at gridno %d (%d APs aim)\n", pSoldier->ubID,pSoldier->name, (ubBestAttackAction == AI_ACTION_FIRE_GUN)?"SHOOTS":((ubBestAttackAction == AI_ACTION_TOSS_PROJECTILE)?"TOSSES AT":"STABS"), BestAttack.ubOpponent,ExtMen[BestAttack.ubOpponent].name, BestAttack.sTarget,BestAttack.ubAimTime) ; DebugAI( tempstr ); #endif return(ubBestAttackAction); } } //////////////////////////////////////////////////////////////////////////// // CLOSE ON THE CLOSEST KNOWN OPPONENT or TURN TO FACE HIM //////////////////////////////////////////////////////////////////////////// if ( !fRunAway ) { if ( (GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints) ) { // determine the location of the known closest opponent // (don't care if he's conscious, don't care if he's reachable at all) sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL); // if we have a closest reachable opponent if (!TileIsOutOfBounds(sClosestOpponent)) { if ( ubCanMove && PythSpacesAway( pSoldier->sGridNo, sClosestOpponent ) > 2 ) { if ( bSpitIn != NO_SLOT ) { pSoldier->aiData.usActionData = AdvanceToFiringRange( pSoldier, sClosestOpponent ); if (TileIsOutOfBounds(pSoldier->aiData.usActionData)) { pSoldier->aiData.usActionData = GoAsFarAsPossibleTowards(pSoldier,sClosestOpponent,AI_ACTION_SEEK_OPPONENT); } } else { pSoldier->aiData.usActionData = GoAsFarAsPossibleTowards(pSoldier,sClosestOpponent,AI_ACTION_SEEK_OPPONENT); } } else { pSoldier->aiData.usActionData = NOWHERE; } if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) // charge! { return( AI_ACTION_SEEK_OPPONENT ); } else if (GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints) // turn to face enemy { bDirection = GetDirectionFromCenterCellXYGridNo(pSoldier->sGridNo, sClosestOpponent); // if we're not facing towards him if (pSoldier->ubDirection != bDirection && ValidCreatureTurn( pSoldier, bDirection ) ) { pSoldier->aiData.usActionData = bDirection; #ifdef DEBUGDECISIONS STR16 tempstr; sprintf(tempstr,"%s - TURNS to face CLOSEST OPPONENT in direction %d",pSoldier->name,pSoldier->aiData.usActionData); AIPopMessage(tempstr); #endif return(AI_ACTION_CHANGE_FACING); } } } } } else { // run away! if ( ubCanMove ) { // look for best place to RUN AWAY to (farthest from the closest threat) //pSoldier->aiData.usActionData = RunAway( pSoldier ); pSoldier->aiData.usActionData = FindSpotMaxDistFromOpponents( pSoldier ); if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) { return(AI_ACTION_RUN_AWAY); } } } //////////////////////////////////////////////////////////////////////////// // DO NOTHING: Not enough points left to move, so save them for next turn //////////////////////////////////////////////////////////////////////////// #ifdef DEBUGDECISIONS AINameMessage(pSoldier,"- DOES NOTHING (BLACK)",1000); #endif // by default, if everything else fails, just stand in place and wait pSoldier->aiData.usActionData = NOWHERE; return(AI_ACTION_NONE); } INT8 CreatureDecideAction( SOLDIERTYPE *pSoldier ) { INT8 bAction = AI_ACTION_NONE; switch (pSoldier->aiData.bAlertStatus) { case STATUS_GREEN: #ifdef DEBUGDECISIONS AIPopMessage("AlertStatus = GREEN"); #endif bAction = CreatureDecideActionGreen(pSoldier); break; case STATUS_YELLOW: #ifdef DEBUGDECISIONS AIPopMessage("AlertStatus = YELLOW"); #endif bAction = CreatureDecideActionYellow(pSoldier); break; case STATUS_RED: #ifdef DEBUGDECISIONS AIPopMessage("AlertStatus = RED"); #endif bAction = CreatureDecideActionRed(pSoldier, TRUE); break; case STATUS_BLACK: #ifdef DEBUGDECISIONS AIPopMessage("AlertStatus = BLACK"); #endif bAction = CreatureDecideActionBlack(pSoldier); break; } #ifdef DEBUGDECISIONS STR tempstr; sprintf( tempstr, "DecideAction: selected action %d, actionData %d\n\n",bAction,pSoldier->aiData.usActionData ); DebugAI(tempstr); #endif return(bAction); } void CreatureDecideAlertStatus( SOLDIERTYPE *pSoldier ) { INT8 bOldStatus; INT32 iDummy; BOOLEAN fClimbDummy, fReachableDummy; // THE FOUR (4) POSSIBLE ALERT STATUSES ARE: // GREEN - No one sensed, no suspicious noise heard, go about doing regular stuff // YELLOW - Suspicious noise was heard personally // RED - Either saw OPPONENTS in person, or definite contact had been called // BLACK - Currently has one or more OPPONENTS in sight // set mobility switch (pSoldier->ubBodyType) { case ADULTFEMALEMONSTER: case YAF_MONSTER: case AM_MONSTER: case YAM_MONSTER: case INFANT_MONSTER: pSoldier->aiData.bMobility = CREATURE_MOBILE; break; case QUEENMONSTER: pSoldier->aiData.bMobility = CREATURE_IMMOBILE; break; case LARVAE_MONSTER: pSoldier->aiData.bMobility = CREATURE_CRAWLER; break; } if (pSoldier->ubBodyType == LARVAE_MONSTER) { // larvae never do anything much! pSoldier->aiData.bAlertStatus = STATUS_GREEN; return; } // save the man's previous status bOldStatus = pSoldier->aiData.bAlertStatus; // determine the current alert status for this category of man if (pSoldier->aiData.bOppCnt > 0) // opponent(s) in sight { // must search through list of people to see if any of them have // attacked us, or do some check to see if we have been attacked switch (bOldStatus) { case STATUS_GREEN: case STATUS_YELLOW: pSoldier->aiData.bAlertStatus = STATUS_BLACK; break; case STATUS_RED: case STATUS_BLACK: pSoldier->aiData.bAlertStatus = STATUS_BLACK; } } else // no opponents are in sight { switch (bOldStatus) { case STATUS_BLACK: // then drop back to RED status pSoldier->aiData.bAlertStatus = STATUS_RED; break; case STATUS_RED: // RED can never go back down below RED, only up to BLACK break; case STATUS_YELLOW: // if all enemies have been RED alerted, or we're under fire if (gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition || pSoldier->aiData.bUnderFire) { pSoldier->aiData.bAlertStatus = STATUS_RED; } else { // if we are NOT aware of any uninvestigated noises right now // and we are not currently in the middle of an action // (could still be on his way heading to investigate a noise!) if (( TileIsOutOfBounds(MostImportantNoiseHeard(pSoldier,&iDummy,&fClimbDummy,&fReachableDummy))) && !pSoldier->aiData.bActionInProgress) { // then drop back to GREEN status pSoldier->aiData.bAlertStatus = STATUS_GREEN; } } break; case STATUS_GREEN: // if all enemies have been RED alerted, or we're under fire if (gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition || pSoldier->aiData.bUnderFire) { pSoldier->aiData.bAlertStatus = STATUS_RED; } else { // if we ARE aware of any uninvestigated noises right now if ( !TileIsOutOfBounds(MostImportantNoiseHeard(pSoldier,&iDummy,&fClimbDummy,&fReachableDummy))) { // then move up to YELLOW status pSoldier->aiData.bAlertStatus = STATUS_YELLOW; } } break; } // otherwise, RED stays RED, YELLOW stays YELLOW, GREEN stays GREEN } // if the creatures alert status has changed in any way if (pSoldier->aiData.bAlertStatus != bOldStatus) { // HERE ARE TRYING TO AVOID NPCs SHUFFLING BACK & FORTH BETWEEN RED & BLACK // if either status is < RED (ie. anything but RED->BLACK && BLACK->RED) if ((bOldStatus < STATUS_RED) || (pSoldier->aiData.bAlertStatus < STATUS_RED)) { // force a NEW action decision on next pass through HandleManAI() SetNewSituation( pSoldier ); } // if this guy JUST discovered that there were opponents here for sure... if ((bOldStatus < STATUS_RED) && (pSoldier->aiData.bAlertStatus >= STATUS_RED)) { // might want to make custom to let them go anywhere CheckForChangingOrders(pSoldier); } #ifdef DEBUGDECISIONS // don't report status changes for human-controlled mercs // if (!pSoldier->human) // { STR16 tempstr; sprintf(tempstr,"%s's Alert Status changed from %d to %d", pSoldier->name ,bOldStatus,pSoldier->aiData.bAlertStatus); AIPopMessage(tempstr); // } #endif } else // status didn't change { // if a guy on status GREEN or YELLOW is running low on breath if (((pSoldier->aiData.bAlertStatus == STATUS_GREEN) && (pSoldier->bBreath < 75)) || ((pSoldier->aiData.bAlertStatus == STATUS_YELLOW) && (pSoldier->bBreath < 50))) { // as long as he's not in water (standing on a bridge is OK) if (!pSoldier->MercInWater()) { // force a NEW decision so that he can get some rest SetNewSituation( pSoldier ); // current action will be canceled. if noise is no longer important if ((pSoldier->aiData.bAlertStatus == STATUS_YELLOW) && (TileIsOutOfBounds(MostImportantNoiseHeard(pSoldier,&iDummy,&fClimbDummy,&fReachableDummy)))) { // then drop back to GREEN status pSoldier->aiData.bAlertStatus = STATUS_GREEN; CheckForChangingOrders(pSoldier); } } } } }