#include "ai.h" #include "AIInternals.h" #include "Isometric Utils.h" #include "Points.h" #include "Overhead.h" #include "opplist.h" #include "Items.h" #include "Weapons.h" #include "NPC.h" #include "Soldier Functions.h" #include "worldman.h" #include "Scheduling.h" #include "message.h" #include "Structure Wrap.h" #include "Keys.h" #include "PATHAI.H" #include "Render Fun.h" #include "Boxing.h" // #include "Air Raid.h" #include "Soldier Profile.h" #include "soldier profile type.h" #include "Soldier macros.h" #include "LOS.h" #include "Buildings.h" #include "strategicmap.h" #include "Quests.h" #include "Map Screen Interface Map.h" #include "Soldier Ani.h" #include "Rotting Corpses.h" #include "GameSettings.h" #include "Dialogue Control.h" #include "connect.h" #include "Text.h" extern BOOLEAN gfHiddenInterrupt; extern void LogDecideInfo(SOLDIERTYPE *pSoldier); extern STR8 gStr8AlertStatus[]; extern STR8 gStr8Attitude[]; extern STR8 gStr8Orders[]; extern STR8 gStr8Team[]; extern STR8 gStr8Class[]; // ------------------------------ ZOMBIE AI -------------------------- INT8 ZombieDecideActionGreen(SOLDIERTYPE *pSoldier) { DOUBLE iChance, iSneaky = 10; INT8 bInWater; DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionGreen, orders = %d",pSoldier->aiData.bOrders)); DebugAI( AI_MSG_START, pSoldier, String("[Green Zombie]")); LogDecideInfo(pSoldier); //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", pSoldier->stats.bLife, pSoldier->stats.bLifeMax, pSoldier->bBreath, pSoldier->bBreathMax, pSoldier->aiData.bShock, CalcSuppressionTolerance(pSoldier), pSoldier->aiData.bAIMorale) ); gubNPCPathCount = 0; bInWater = Water( pSoldier->sGridNo, pSoldier->pathing.bLevel ); if ( bInWater && PreRandom( 2 ) ) { // don't do nuttin! return( AI_ACTION_NONE ); } // if real-time, and not in the way, do nothing 90% of the time (for GUARDS!) // unless in water (could've started there), then we better swim to shore! //////////////////////////////////////////////////////////////////////////// // 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 >= 25)) { if (PointPatrolAI(pSoldier)) { if (!gfTurnBasedAI) { // wait after this... pSoldier->aiData.bNextAction = AI_ACTION_WAIT; pSoldier->aiData.usNextActionData = RealtimeDelay( pSoldier ); } return(AI_ACTION_POINT_PATROL); } else { // Reset path count to avoid dedlok gubNPCPathCount = 0; } } if ((pSoldier->aiData.bOrders == RNDPTPATROL) && (pSoldier->bBreath >=25)) { if (RandomPointPatrolAI(pSoldier)) { if (!gfTurnBasedAI) { // wait after this... pSoldier->aiData.bNextAction = AI_ACTION_WAIT; pSoldier->aiData.usNextActionData = RealtimeDelay( pSoldier ); } return(AI_ACTION_POINT_PATROL); } else { // Reset path count to avoid dedlok gubNPCPathCount = 0; } } //////////////////////////////////////////////////////////////////////////// // WHEN LEFT IN WATER OR GAS, GO TO NEAREST REACHABLE SPOT OF UNGASSED LAND //////////////////////////////////////////////////////////////////////////// DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("ZombieDecideActionGreen: get out of water and gas")); if ( bInWater ) { pSoldier->aiData.usActionData = FindNearestUngassedLand(pSoldier); if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) { return(AI_ACTION_LEAVE_WATER_GAS); } } //////////////////////////////////////////////////////////////////////// // REST IF RUNNING OUT OF BREATH //////////////////////////////////////////////////////////////////////// DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("ZombieDecideActionGreen: 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 < 25) && !bInWater) { // take a breather for gods sake! // for realtime, AI will use a standard wait set outside of here pSoldier->aiData.usActionData = NOWHERE; return(AI_ACTION_NONE); } //////////////////////////////////////////////////////////////////////////// // CLIMB A BUILDING //////////////////////////////////////////////////////////////////////////// // sevenfm: disable roof climbing for zombies in Green AI /* if ( gGameExternalOptions.fZombieCanClimb && pSoldier->aiData.bLastAction != AI_ACTION_CLIMB_ROOF && !is_networked ) { iChance = 10 + pSoldier->aiData.bBypassToGreen; // set base chance and maximum seeking distance according to orders switch (pSoldier->aiData.bOrders) { case STATIONARY: iChance *= 0; break; case ONGUARD: iChance += 10; break; case ONCALL: break; case CLOSEPATROL: iChance += -20; break; case RNDPTPATROL: case POINTPATROL: iChance = -30; break; case FARPATROL: iChance += -40; break; case SEEKENEMY: iChance += -30; break; case SNIPER: iChance += 70; break; } // modify for attitude switch (pSoldier->aiData.bAttitude) { case DEFENSIVE: iChance *= 1.5; break; case BRAVESOLO: iChance /= 2; break; case BRAVEAID: iChance /= 2; break; case CUNNINGSOLO: iChance *= 1; break; case CUNNINGAID: iChance /= 1; break; case AGGRESSIVE: iChance /= 3; break; case ATTACKSLAYONLY: break; } // This is the chance that we want to be on the roof. If already there, invert the chance to see if we want back // down if (pSoldier->pathing.bLevel > 0) { iChance = 100 - iChance; } if ((INT16) PreRandom(100) < iChance) { BOOLEAN fUp = FALSE; if ( pSoldier->pathing.bLevel == 0 ) { fUp = TRUE; } else if (pSoldier->pathing.bLevel > 0 ) { fUp = FALSE; } if ( CanClimbFromHere ( pSoldier ) ) { DebugMsg ( TOPIC_JA2AI , DBG_LEVEL_3 , String("Zombie %d is climbing roof",pSoldier->ubID) ); return( AI_ACTION_CLIMB_ROOF ); } else { pSoldier->aiData.usActionData = FindClosestClimbPoint(pSoldier, fUp ); // Added the check here because sniper militia who are locked inside of a building without keys // will still have a >100% chance to want to climb, which means an infinite loop. In fact, any // time a move is desired, there probably also will be a need to check for a path. if ( !TileIsOutOfBounds(pSoldier->aiData.usActionData) && LegalNPCDestination(pSoldier,pSoldier->aiData.usActionData,ENSURE_PATH,WATEROK, 0 )) { return( AI_ACTION_MOVE_TO_CLIMB ); } } } } */ //////////////////////////////////////////////////////////////////////////// // RANDOM PATROL: determine % chance to start a new patrol route //////////////////////////////////////////////////////////////////////////// if (!gubNPCPathCount) // try to limit pathing in Green AI { iChance = 25 + pSoldier->aiData.bBypassToGreen; // sevenfm: limit chance iChance = iChance * pSoldier->bBreath / 100; // 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)) { pSoldier->aiData.usActionData = GoAsFarAsPossibleTowards( pSoldier, pSoldier->aiData.usActionData, AI_ACTION_RANDOM_PATROL ); } if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) { if (!gfTurnBasedAI) { // wait after this... pSoldier->aiData.bNextAction = AI_ACTION_WAIT; pSoldier->aiData.usNextActionData = RealtimeDelay( pSoldier ); } return(AI_ACTION_RANDOM_PATROL); } } } if (!gubNPCPathCount) // try to limit pathing in Green AI { //////////////////////////////////////////////////////////////////////////// // SEEK FRIEND: determine %chance for man to pay a friendly visit //////////////////////////////////////////////////////////////////////////// iChance = 25 + pSoldier->aiData.bBypassToGreen; // 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; case SNIPER: 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; case ATTACKSLAYONLY: break; } if ((INT16) PreRandom(100) < iChance) { if (RandomFriendWithin(pSoldier)) { if ( pSoldier->aiData.usActionData == GoAsFarAsPossibleTowards( pSoldier, pSoldier->aiData.usActionData, AI_ACTION_SEEK_FRIEND ) ) { if ( !gfTurnBasedAI) { // pause at the end of the walk! pSoldier->aiData.bNextAction = AI_ACTION_WAIT; pSoldier->aiData.usNextActionData = (UINT16) REALTIME_CIV_AI_DELAY; } return(AI_ACTION_SEEK_FRIEND); } } } } //////////////////////////////////////////////////////////////////////////// // LOOK AROUND: determine %chance for man to turn in place //////////////////////////////////////////////////////////////////////////// DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("ZombieDecideActionGreen: Soldier deciding to turn")); if (GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints) { // avoid 2 consecutive random turns in a row if (pSoldier->aiData.bLastAction != AI_ACTION_CHANGE_FACING) { iChance = 25 + pSoldier->aiData.bBypassToGreen; // set base chance according to orders if (pSoldier->aiData.bOrders == STATIONARY || pSoldier->aiData.bOrders == SNIPER) iChance += 25; if (pSoldier->aiData.bOrders == ONGUARD) iChance += 20; if (pSoldier->aiData.bAttitude == DEFENSIVE) iChance += 25; if ( pSoldier->aiData.bOrders == SNIPER && pSoldier->pathing.bLevel == 1) iChance += 35; 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.bOrders != SNIPER ) { pSoldier->aiData.usActionData = pSoldier->aiData.bDominantDir; } else { INT32 iNoiseValue; BOOLEAN fClimb; BOOLEAN fReachable; INT32 sNoiseGridNo = MostImportantNoiseHeard(pSoldier,&iNoiseValue, &fClimb, &fReachable); UINT8 ubNoiseDir; if (TileIsOutOfBounds(sNoiseGridNo) || (ubNoiseDir = AIDirection(pSoldier->sGridNo, sNoiseGridNo)) == pSoldier->ubDirection ) { pSoldier->aiData.usActionData = PreRandom(8); } else { pSoldier->aiData.usActionData = ubNoiseDir; } } } while (pSoldier->aiData.usActionData == pSoldier->ubDirection); DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionGreen: Trying to turn - checking stance validity")); if ( pSoldier->InternalIsValidStance( (INT8) pSoldier->aiData.usActionData, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) ) { if ( !gfTurnBasedAI ) { // wait after this... pSoldier->aiData.bNextAction = AI_ACTION_WAIT; pSoldier->aiData.usNextActionData = RealtimeDelay( pSoldier ); } DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionGreen: Soldier is turning")); return(AI_ACTION_CHANGE_FACING); } } } } // by default, if everything else fails, just stands in place without turning // for realtime, regular AI guys will use a standard wait set outside of here pSoldier->aiData.usActionData = NOWHERE; return(AI_ACTION_NONE); } INT8 ZombieDecideActionYellow(SOLDIERTYPE *pSoldier) { UINT8 ubNoiseDir; INT32 sNoiseGridNo; INT32 iNoiseValue; INT32 iChance; BOOLEAN fClimb; BOOLEAN fReachable; DebugAI( AI_MSG_START, pSoldier, String("[Yellow Zombie]")); LogDecideInfo(pSoldier); //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", pSoldier->stats.bLife, pSoldier->stats.bLifeMax, pSoldier->bBreath, pSoldier->bBreathMax, pSoldier->aiData.bShock, CalcSuppressionTolerance(pSoldier), pSoldier->aiData.bAIMorale) ); // determine the most important noise heard, and its relative value sNoiseGridNo = MostImportantNoiseHeard(pSoldier,&iNoiseValue, &fClimb, &fReachable); if (TileIsOutOfBounds(sNoiseGridNo)) { // then we have no business being under YELLOW status any more! #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 //////////////////////////////////////////////////////////////////////////// // determine direction from this soldier in which the noise lies ubNoiseDir = AIDirection(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 ) { if ((pSoldier->ubDirection != ubNoiseDir) && PythSpacesAway(pSoldier->sGridNo,sNoiseGridNo) <= pSoldier->GetMaxDistanceVisible(sNoiseGridNo) ) { // 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 += 15; if ((INT16)PreRandom(100) < iChance && pSoldier->InternalIsValidStance( ubNoiseDir, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) ) { pSoldier->aiData.usActionData = ubNoiseDir; return(AI_ACTION_CHANGE_FACING); } } } //////////////////////////////////////////////////////////////////////////// // RADIO YELLOW ALERT: determine %chance to call others and report noise //////////////////////////////////////////////////////////////////////////// // if 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 ( (pSoldier->bActionPoints >= APBPConstants[AP_RADIO]) && (gTacticalStatus.Team[pSoldier->bTeam].bMenInSector > 1) ) { // base chance depends on how much new info we have to radio to the others iChance = 5 * WhatIKnowThatPublicDont(pSoldier,FALSE); // use 5 * for YELLOW alert // if I actually know something they don't and I ain't swimming (deep water) if (iChance && !DeepWater( pSoldier->sGridNo, pSoldier->pathing.bLevel )) { // CJC: this addition allows for varying difficulty levels for soldier types iChance += gbDiff[ DIFF_RADIO_RED_ALERT ][ SoldierDifficultyLevel( pSoldier ) ] / 2; // Alex: this addition replaces the sectorValue/2 in original JA //iChance += gsDiff[DIFF_RADIO_RED_ALERT][GameOption[ENEMYDIFFICULTY]] / 2; // modify base chance according to orders switch (pSoldier->aiData.bOrders) { case STATIONARY: iChance += 20; break; case ONGUARD: iChance += 15; break; case ONCALL: iChance += 10; break; case CLOSEPATROL: break; case RNDPTPATROL: case POINTPATROL: break; case FARPATROL: iChance += -10; break; case SEEKENEMY: iChance += -20; break; case SNIPER: iChance += -10; break; //Madd: sniper contacts are supposed to be automatically reported } // modify base chance according to attitude switch (pSoldier->aiData.bAttitude) { case DEFENSIVE: iChance += 20; break; case BRAVESOLO: iChance += -10; break; case BRAVEAID: break; case CUNNINGSOLO:iChance += -5; break; case CUNNINGAID: break; case AGGRESSIVE: iChance += -20; break; case ATTACKSLAYONLY: iChance = 0; break; } if ((INT16)PreRandom(100) < iChance) { return(AI_ACTION_YELLOW_ALERT); } } } //////////////////////////////////////////////////////////////////////// // 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); } pSoldier->aiData.usActionData = GoAsFarAsPossibleTowards(pSoldier,sNoiseGridNo,AI_ACTION_SEEK_NOISE); if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) { if ( fClimb ) { if ( pSoldier->sGridNo == sNoiseGridNo) { if (IsActionAffordable(pSoldier, pSoldier->aiData.bAction)) { return( AI_ACTION_CLIMB_ROOF ); } else if ( gfTurnBasedAI && pSoldier->bActionPoints < pSoldier->bInitialActionPoints ) { return AI_ACTION_NONE; } } else { pSoldier->aiData.usActionData = sNoiseGridNo; return( AI_ACTION_MOVE_TO_CLIMB ); } } else { return(AI_ACTION_SEEK_NOISE); } } //////////////////////////////////////////////////////////////////////////// // SWITCH TO GREEN: determine if soldier acts as if nothing at all was wrong //////////////////////////////////////////////////////////////////////////// if ((INT16)PreRandom(100) < 50 ) { // Skip YELLOW until new situation, 15% extra chance to do GREEN actions pSoldier->aiData.bBypassToGreen = 15; return(ZombieDecideActionGreen(pSoldier)); } //////////////////////////////////////////////////////////////////////////// // DO NOTHING: Not enough points left to move, so save them for next turn //////////////////////////////////////////////////////////////////////////// // by default, if everything else fails, just stands in place without turning pSoldier->aiData.usActionData = NOWHERE; return(AI_ACTION_NONE); } INT8 ZombieDecideActionRed(SOLDIERTYPE *pSoldier) { INT32 iDummy; INT32 sClosestOpponent,sClosestFriend; INT32 sClosestDisturbance, sDistVisible; INT32 sMostImportantNoise; INT32 sClosestThreat = NOWHERE; UINT8 ubCanMove,ubOpponentDir; INT8 bInWater, bInDeepWater; INT8 bSeekPts = 0, bHelpPts = 0, bWatchPts = 0; BOOLEAN fClimb; UINT16 ubThreatID = NOBODY; INT32 sThreatSpot = NOWHERE; INT8 bThreatLevel = 0; DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("ZombieDecideActionRed: soldier orders = %d",pSoldier->aiData.bOrders)); DebugAI( AI_MSG_START, pSoldier, String("[Red Zombie]")); LogDecideInfo(pSoldier); //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", pSoldier->stats.bLife, pSoldier->stats.bLifeMax, pSoldier->bBreath, pSoldier->bBreathMax, pSoldier->aiData.bShock, CalcSuppressionTolerance(pSoldier), pSoldier->aiData.bAIMorale) ); // 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); } // can this guy move to any of the neighbouring squares ? (sets TRUE/FALSE) ubCanMove = (pSoldier->bActionPoints >= MinPtsToMove(pSoldier)); // determine if we happen to be in water (in which case we're in BIG trouble!) bInWater = Water( pSoldier->sGridNo, pSoldier->pathing.bLevel ); bInDeepWater = DeepWater( pSoldier->sGridNo, pSoldier->pathing.bLevel ); DebugAI( AI_MSG_INFO, pSoldier, String("CanMove %d InWater %d InDeepWater %d", ubCanMove, bInWater, bInDeepWater)); // calculate our morale pSoldier->aiData.bAIMorale = CalcMorale(pSoldier); //////////////////////////////////////////////////////////////////////// // CROUCH & REST IF RUNNING OUT OF BREATH //////////////////////////////////////////////////////////////////////// DebugAI( AI_MSG_INFO, pSoldier, String("[crouch and rest]")); // 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) { pSoldier->aiData.usActionData = NOWHERE; return(AI_ACTION_NONE); } DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: main red ai"); //////////////////////////////////////////////////////////////////////////// // MAIN RED AI //////////////////////////////////////////////////////////////////////////// DebugAI( AI_MSG_INFO, pSoldier, String("Main Red AI")); // get the location of the closest reachable opponent //sClosestDisturbance = ClosestReachableDisturbance(pSoldier, &fClimb, ubThreatID, sThreatSpot, bThreatLevel); sClosestDisturbance = ClosestReachableDisturbance(pSoldier, &fClimb); sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL); sMostImportantNoise = MostImportantNoiseHeard( pSoldier, NULL, NULL, NULL ); DebugAI( AI_MSG_INFO, pSoldier, String("Closest Disturbance %d Closest Opponent %d Most Important Noise %d", sClosestDisturbance, sClosestOpponent, sMostImportantNoise)); sClosestThreat = sClosestDisturbance; if(TileIsOutOfBounds( sClosestThreat )) { sClosestThreat = sClosestOpponent; } if( TileIsOutOfBounds( sClosestThreat ) ) { sClosestThreat = sMostImportantNoise; } DebugAI( AI_MSG_INFO, pSoldier, String("Closest Threat %d", sClosestThreat)); if( ubCanMove && pSoldier->bActionPoints > APBPConstants[MAX_AP_CARRIED] && !gfHiddenInterrupt ) { DebugAI( AI_MSG_INFO, pSoldier, String("[seek opponent]")); // if there is an opponent reachable if (!TileIsOutOfBounds(sClosestDisturbance) ) { DebugAI( AI_MSG_INFO, pSoldier, String("found disturbance %d climb %d", sClosestDisturbance, fClimb)); ////////////////////////////////////////////////////////////////////// // SEEK CLOSEST DISTURBANCE: GO DIRECTLY TOWARDS CLOSEST KNOWN OPPONENT ////////////////////////////////////////////////////////////////////// if (fClimb && pSoldier->sGridNo == sClosestDisturbance) { DebugAI( AI_MSG_INFO, pSoldier, String("at climb point")); // wait for next turn if turnbased (to climb with max APs) if( gfTurnBasedAI && pSoldier->bActionPoints < pSoldier->bInitialActionPoints ) { pSoldier->aiData.bNextAction = AI_ACTION_NONE; return( AI_ACTION_END_TURN ); } // climb if (IsActionAffordable(pSoldier, AI_ACTION_CLIMB_ROOF)) { pSoldier->aiData.bNextAction = AI_ACTION_NONE; return( AI_ACTION_CLIMB_ROOF ); } // cannot climb at all } // try to move towards him pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, sClosestDisturbance, 0, AI_ACTION_SEEK_OPPONENT, 0); DebugAI( AI_MSG_INFO, pSoldier, String("found seek spot %d", pSoldier->aiData.usActionData)); // if it's possible if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) { DebugAI( AI_MSG_INFO, pSoldier, String("[move to seek spot]")); // sevenfm: try to advance using sight cover if( gfTurnBasedAI && PythSpacesAway(pSoldier->sGridNo, sClosestDisturbance) < TACTICAL_RANGE * 2 && PythSpacesAway(pSoldier->sGridNo, sClosestDisturbance) > TACTICAL_RANGE / 4 && !SightCoverAtSpot(pSoldier, pSoldier->aiData.usActionData, TRUE)) { INT32 sAdvanceSpot = FindAdvanceSpot(pSoldier, sClosestDisturbance, AI_ACTION_SEEK_OPPONENT, ADVANCE_SPOT_SIGHT_COVER, TRUE); DebugAI( AI_MSG_INFO, pSoldier, String("found advance spot %d", sAdvanceSpot)); if( !TileIsOutOfBounds(sAdvanceSpot) ) { DebugAI( AI_MSG_INFO, pSoldier, String("move to advance spot")); pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, sAdvanceSpot, 0, AI_ACTION_SEEK_OPPONENT, 0); if( !TileIsOutOfBounds(pSoldier->aiData.usActionData) ) { return(AI_ACTION_SEEK_OPPONENT); } } // if we are in cover and new spot has no cover - wait for next turn if( SightCoverAtSpot(pSoldier, pSoldier->sGridNo, TRUE) && gfTurnBasedAI && pSoldier->bActionPoints != pSoldier->bInitialActionPoints ) //&& //CountFriendsBlack(pSoldier, sClosestDisturbance) == 0 ) { DebugAI( AI_MSG_INFO, pSoldier, String("check turn to closest disturbance before ending turn")); // first turn to closest threat if( GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints ) { // determine direction from this soldier to the closest opponent ubOpponentDir = AIDirection(pSoldier->sGridNo, sClosestDisturbance); DebugAI( AI_MSG_INFO, pSoldier, String("soldier direction %d disturbance direction %d", pSoldier->ubDirection, ubOpponentDir)); sDistVisible = pSoldier->GetMaxDistanceVisible(sClosestThreat, 0, CALC_FROM_ALL_DIRS ); if( pSoldier->ubDirection != ubOpponentDir && PythSpacesAway(pSoldier->sGridNo,sClosestThreat) <= sDistVisible && pSoldier->InternalIsValidStance( ubOpponentDir, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) ) { pSoldier->aiData.usActionData = ubOpponentDir; return(AI_ACTION_CHANGE_FACING); } DebugAI( AI_MSG_INFO, pSoldier, String("cannot turn")); } DebugAI( AI_MSG_INFO, pSoldier, String("wait for next turn")); //ScreenMsg(FONT_GREEN, MSG_INTERFACE, L"[%d] wait for next turn", pSoldier->ubID); return AI_ACTION_NONE; } } return(AI_ACTION_SEEK_OPPONENT); } } DebugAI( AI_MSG_INFO, pSoldier, String("[seek friend]")); // if no opponent - go to closest friend sClosestFriend = ClosestReachableFriendInTrouble(pSoldier, &fClimb ); if (!TileIsOutOfBounds(sClosestFriend) ) { DebugAI( AI_MSG_INFO, pSoldier, String("found friend %d climb %d", sClosestFriend, fClimb)); ////////////////////////////////////////////////////////////////////// // GO DIRECTLY TOWARDS CLOSEST FRIEND UNDER FIRE OR WHO LAST RADIOED ////////////////////////////////////////////////////////////////////// if ( fClimb && pSoldier->sGridNo == sClosestFriend ) { DebugAI( AI_MSG_INFO, pSoldier, String("at climb spot")); // wait for next turn to have full APs if( gfTurnBasedAI && pSoldier->bActionPoints < pSoldier->bInitialActionPoints ) { return( AI_ACTION_END_TURN); } // climb if( IsActionAffordable(pSoldier, AI_ACTION_CLIMB_ROOF) ) { return( AI_ACTION_CLIMB_ROOF ); } // don't have enough AP for climbing } else { DebugAI( AI_MSG_INFO, pSoldier, String("move to friend %d", sClosestFriend)); // try to move towards him pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, sClosestFriend, 0, AI_ACTION_SEEK_FRIEND, 0); DebugAI( AI_MSG_INFO, pSoldier, String("found move spot %d", pSoldier->aiData.usActionData)); // if it's possible if ( !TileIsOutOfBounds(pSoldier->aiData.usActionData) ) { return(AI_ACTION_SEEK_FRIEND); } else { DebugAI( AI_MSG_INFO, pSoldier, String("cannot move!")); } } } DebugAI( AI_MSG_INFO, pSoldier, String("[hide]")); // if cannot seek or help - hide if( gfTurnBasedAI && pSoldier->bActionPoints == pSoldier->bInitialActionPoints && (!SightCoverAtSpot(pSoldier, pSoldier->sGridNo, TRUE) || pSoldier->aiData.bUnderFire) ) { pSoldier->aiData.usActionData = FindBestNearbyCover(pSoldier, pSoldier->aiData.bAIMorale, &iDummy); if( !TileIsOutOfBounds(pSoldier->aiData.usActionData) ) { DebugAI( AI_MSG_INFO, pSoldier, String("found cover spot %d", pSoldier->aiData.usActionData)); return AI_ACTION_TAKE_COVER; } else if (pSoldier->aiData.bUnderFire) { pSoldier->aiData.usActionData = FindRetreatSpot(pSoldier); if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) { DebugAI(AI_MSG_INFO, pSoldier, String("found retreat spot %d", pSoldier->aiData.usActionData)); return AI_ACTION_TAKE_COVER; } } } } DebugAI( AI_MSG_INFO, pSoldier, String("[look around towards opponent]")); //////////////////////////////////////////////////////////////////////////// // LOOK AROUND TOWARD CLOSEST KNOWN OPPONENT, IF KNOWN //////////////////////////////////////////////////////////////////////////// if( GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints && !TileIsOutOfBounds(sClosestThreat) ) { // determine direction from this soldier to the closest opponent ubOpponentDir = AIDirection(pSoldier->sGridNo, sClosestThreat); DebugAI( AI_MSG_INFO, pSoldier, String("soldier direction %d opponent direction %d", pSoldier->ubDirection, ubOpponentDir)); // 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(sClosestThreat, 0, CALC_FROM_ALL_DIRS ); if( pSoldier->ubDirection != ubOpponentDir && PythSpacesAway(pSoldier->sGridNo,sClosestThreat) <= sDistVisible && pSoldier->InternalIsValidStance( ubOpponentDir, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) ) { pSoldier->aiData.usActionData = ubOpponentDir; return(AI_ACTION_CHANGE_FACING); } DebugAI( AI_MSG_INFO, pSoldier, String("cannot turn")); } //////////////////////////////////////////////////////////////////////////// // SWITCH TO GREEN: soldier does ordinary regular patrol, seeks friends //////////////////////////////////////////////////////////////////////////// DebugAI( AI_MSG_INFO, pSoldier, String("[switch to green]")); // 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(sClosestDisturbance) ) { DebugAI( AI_MSG_INFO, pSoldier, String("decide green")); // Skip RED until new situation/next turn, 30% extra chance to do GREEN actions pSoldier->aiData.bBypassToGreen = 30; return(ZombieDecideActionGreen(pSoldier) ); } //////////////////////////////////////////////////////////////////////////// // IF UNDER FIRE, FACE THE MOST IMPORTANT NOISE WE KNOW //////////////////////////////////////////////////////////////////////////// DebugAI( AI_MSG_INFO, pSoldier, String("[face the most important noise]")); if( pSoldier->aiData.bUnderFire && pSoldier->CheckInitialAP() && //pSoldier->bActionPoints == pSoldier->bInitialActionPoints && GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints && !TileIsOutOfBounds( sMostImportantNoise ) ) { ubOpponentDir = AIDirection(pSoldier->sGridNo, sMostImportantNoise); DebugAI( AI_MSG_INFO, pSoldier, String("soldier direction %d noise direction %d", pSoldier->ubDirection, ubOpponentDir)); if ( pSoldier->ubDirection != ubOpponentDir && pSoldier->InternalIsValidStance( ubOpponentDir, gAnimControl[ pSoldier->usAnimState ].ubEndHeight )) { pSoldier->aiData.usActionData = ubOpponentDir; return( AI_ACTION_CHANGE_FACING ); } DebugAI( AI_MSG_INFO, pSoldier, String("cannot turn")); } //////////////////////////////////////////////////////////////////////////// // DO NOTHING: Not enough points left to move, so save them for next turn //////////////////////////////////////////////////////////////////////////// DebugAI( AI_MSG_INFO, pSoldier, String("nothing to do!")); pSoldier->aiData.usActionData = NOWHERE; return(AI_ACTION_NONE); } INT8 ZombieDecideActionBlack(SOLDIERTYPE *pSoldier) { INT32 sClosestOpponent; INT32 sClosestDisturbance; INT16 ubMinAPCost; UINT8 ubCanMove; INT8 bInWater,bInDeepWater; INT8 bDirection; UINT8 ubBestAttackAction = AI_ACTION_NONE; INT8 bCanAttack; INT8 bWeaponIn; BOOLEAN fClimb; INT32 iDummy; UINT16 ubThreatID = NOBODY; INT32 sThreatSpot = NOWHERE; INT8 bThreatLevel = 0; DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionBlack: soldier = %d, orders = %d, attitude = %d",pSoldier->ubID,pSoldier->aiData.bOrders,pSoldier->aiData.bAttitude)); DebugAI( AI_MSG_START, pSoldier, String("[Black Zombie]")); LogDecideInfo(pSoldier); //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", pSoldier->stats.bLife, pSoldier->stats.bLifeMax, pSoldier->bBreath, pSoldier->bBreathMax, pSoldier->aiData.bShock, CalcSuppressionTolerance(pSoldier), pSoldier->aiData.bAIMorale) ); ATTACKTYPE BestStab, BestAttack; BOOLEAN fAllowCoverCheck = FALSE; // if we have absolutely no action points, we can't do a thing under BLACK! if (pSoldier->bActionPoints <= 0) { DebugAI( AI_MSG_INFO, pSoldier, String("no AP! nothing to do")); pSoldier->aiData.usActionData = NOWHERE; return(AI_ACTION_NONE); } // can this guy move to any of the neighbouring squares ? (sets TRUE/FALSE) ubCanMove = (pSoldier->bActionPoints >= MinPtsToMove(pSoldier)); // determine if we happen to be in water (in which case we're in BIG trouble!) bInWater = Water( pSoldier->sGridNo, pSoldier->pathing.bLevel ); bInDeepWater = WaterTooDeepForAttacks( pSoldier->sGridNo, pSoldier->pathing.bLevel ); // calculate our morale pSoldier->aiData.bAIMorale = CalcMorale(pSoldier); DebugAI( AI_MSG_INFO, pSoldier, String("CanMove %d InWater %d InDeepWater %d", ubCanMove, bInWater, bInDeepWater)); //////////////////////////////////////////////////////////////////////////// // 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 ( bInDeepWater && ubCanMove) { DebugAI( AI_MSG_INFO, pSoldier, String("in deep water: find nearest land")); pSoldier->aiData.usActionData = FindNearestUngassedLand(pSoldier); if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) { DebugAI( AI_MSG_INFO, pSoldier, String("leave deep water: move to %d", pSoldier->aiData.usActionData)); return(AI_ACTION_LEAVE_WATER_GAS); } DebugAI( AI_MSG_INFO, pSoldier, String("couldn't find ANY land within 25 tiles")); // couldn't find ANY land within 25 tiles(!), this should never happen... // look for best place to RUN AWAY to (farthest from the closest threat) pSoldier->aiData.usActionData = FindSpotMaxDistFromOpponents(pSoldier); if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) { DebugAI( AI_MSG_INFO, pSoldier, String("move to %d", pSoldier->aiData.usActionData)); return(AI_ACTION_RUN_AWAY); } } //////////////////////////////////////////////////////////////////////////// // SOLDIER CAN ATTACK IF NOT IN WATER/GAS AND NOT DOING SOMETHING TOO FUNKY //////////////////////////////////////////////////////////////////////////// // zombie can always attack bCanAttack = TRUE; // NPCs in deep water cannot attack if ((pSoldier->bActionPoints < 2) || bInDeepWater) { DebugAI( AI_MSG_INFO, pSoldier, String("low AP or in deep water - cannot attack")); bCanAttack = FALSE; } BestStab.ubPossible = FALSE; // by default, assume Stabbing isn't possible // if we are able attack if (bCanAttack) { DebugAI( AI_MSG_INFO, pSoldier, String("[attack]")); pSoldier->bAimShotLocation = AIM_SHOT_RANDOM; // nothing (else) to attack with so let's try hand-to-hand bWeaponIn = FindObj( pSoldier, NOTHING, HANDPOS, NUM_INV_SLOTS ); if (bWeaponIn != NO_SLOT) { DebugAI( AI_MSG_INFO, pSoldier, String("found weapon in slot %d", bWeaponIn)); BestStab.bWeaponIn = bWeaponIn; // if it's in his holster, swap it into his hand temporarily if (bWeaponIn != HANDPOS) { DebugAI( AI_MSG_INFO, pSoldier, String("rearrange pocket temporarily")); RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY); } // get the minimum cost to attack by HTH ubMinAPCost = MinAPsToAttack(pSoldier,pSoldier->sLastTarget,DONTADDTURNCOST,0,0); DebugAI( AI_MSG_INFO, pSoldier, String("min AP to attack %d", ubMinAPCost)); // if we can afford the minimum AP cost to use HTH combat if (pSoldier->bActionPoints >= ubMinAPCost) { DebugAI( AI_MSG_INFO, pSoldier, String("[find target for attack]")); // then look around for a worthy target (which sets BestStab.ubPossible) CalcBestStab(pSoldier,&BestStab, FALSE); if (BestStab.ubPossible && pSoldier->ubSoldierClass == SOLDIER_CLASS_ZOMBIE) { //ScreenMsg( FONT_LTGREEN, MSG_INTERFACE, L"[%d] can attack %s", pSoldier->ubID, MercPtrs[BestStab.ubOpponent]->GetName()); // now we KNOW FOR SURE that we will do something (stab, at least) NPCDoesAct(pSoldier); ubBestAttackAction = AI_ACTION_KNIFE_MOVE; DebugAI( AI_MSG_INFO, pSoldier, String("NPC decided to move to stab")); } } // if it was in his holster, swap it back into his holster for now if (bWeaponIn != HANDPOS) { DebugAI( AI_MSG_INFO, pSoldier, String("rearrange pocket back")); RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY); } } ////////////////////////////////////////////////////////////////////////// // CHOOSE THE BEST TYPE OF ATTACK OUT OF THOSE FOUND TO BE POSSIBLE ////////////////////////////////////////////////////////////////////////// // copy the information on the best action selected into BestAttack struct DebugAI( AI_MSG_INFO, pSoldier, String("copy the information on the best action selected into BestAttack struct")); switch (ubBestAttackAction) { case AI_ACTION_KNIFE_MOVE: DebugAI( AI_MSG_INFO, pSoldier, String("best attack = stab")); memcpy(&BestAttack,&BestStab,sizeof(BestAttack)); break; default: // set to empty DebugAI( AI_MSG_INFO, pSoldier, String("best attack = no good attack")); memset( &BestAttack, 0, sizeof( BestAttack ) ); break; } } // if attack is still desirable (meaning it's also preferred to taking cover) if (ubBestAttackAction != AI_ACTION_NONE) { DebugAI( AI_MSG_INFO, pSoldier, String("prepare attack, AP cost %d CTH %d", BestAttack.ubAPCost, BestAttack.ubChanceToReallyHit)); pSoldier->aiData.bAimTime = BestAttack.ubAimTime; // sevenfm: dynamically decide stab location if( BestAttack.ubOpponent != NOBODY ) { UINT32 uiRoll; UINT8 ubChanceHead = 0; UINT16 usRealCTH = BestAttack.ubChanceToReallyHit; // attack to head randomly if( gAnimControl[ MercPtrs[BestAttack.ubOpponent]->usAnimState ].ubEndHeight != ANIM_PRONE ) { ubChanceHead = 6; ubChanceHead += usRealCTH / 2; ubChanceHead = ubChanceHead * usRealCTH / 100; } DebugAI( AI_MSG_INFO, pSoldier, String("ubRealCTH %d", usRealCTH)); DebugAI( AI_MSG_INFO, pSoldier, String("ubChanceHead %d", ubChanceHead)); // randomly decide hit location uiRoll = PreRandom( 100 ); if (uiRoll > 100 - ubChanceHead) { pSoldier->bAimShotLocation = AIM_SHOT_HEAD; DebugAI( AI_MSG_INFO, pSoldier, String("aim at head")); } else { pSoldier->bAimShotLocation = AIM_SHOT_TORSO; DebugAI( AI_MSG_INFO, pSoldier, String("aim at torso")); } } DebugAI( AI_MSG_INFO, pSoldier, String("AimTime %d AimShotLocation %d", pSoldier->aiData.bAimTime, pSoldier->bAimShotLocation)); // swap weapon to hand if necessary if (BestAttack.bWeaponIn != HANDPOS) { DebugAI( AI_MSG_INFO, pSoldier, String("rearrange pocket for attack")); RearrangePocket(pSoldier,HANDPOS,BestAttack.bWeaponIn,FOREVER); } pSoldier->aiData.usActionData = BestAttack.sTarget; pSoldier->bTargetLevel = BestAttack.bTargetLevel; DebugAI( AI_MSG_INFO, pSoldier, String("attack opponent %d at gridno %d level %d", BestAttack.ubOpponent, pSoldier->aiData.usActionData, pSoldier->bTargetLevel)); return(ubBestAttackAction); } DebugAI( AI_MSG_INFO, pSoldier, String("[go to closest deisturbance]")); if( ubCanMove && !gfHiddenInterrupt && pSoldier->bActionPoints > APBPConstants[MAX_AP_CARRIED] ) { //sClosestDisturbance = ClosestReachableDisturbance(pSoldier, &fClimb, ubThreatID, sThreatSpot, bThreatLevel); sClosestDisturbance = ClosestReachableDisturbance(pSoldier, &fClimb); DebugAI( AI_MSG_INFO, pSoldier, String("found disturbance %d climb %d", sClosestDisturbance, fClimb)); // if there is an opponent reachable if (!TileIsOutOfBounds(sClosestDisturbance)) { ////////////////////////////////////////////////////////////////////// // SEEK CLOSEST DISTURBANCE: GO DIRECTLY TOWARDS CLOSEST KNOWN OPPONENT ////////////////////////////////////////////////////////////////////// if (fClimb && pSoldier->sGridNo == sClosestDisturbance) { DebugAI( AI_MSG_INFO, pSoldier, String("at climb point")); // wait for next turn if turnbased (to climb with max APs) if( gfTurnBasedAI && pSoldier->bActionPoints < pSoldier->bInitialActionPoints ) { pSoldier->aiData.bNextAction = AI_ACTION_NONE; return( AI_ACTION_END_TURN ); } // climb if (IsActionAffordable(pSoldier, AI_ACTION_CLIMB_ROOF)) { pSoldier->aiData.bNextAction = AI_ACTION_NONE; return( AI_ACTION_CLIMB_ROOF ); } // cannot climb at all } else { DebugAI( AI_MSG_INFO, pSoldier, String("try to move towards disturbance")); // try to move towards him pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, sClosestDisturbance, 0, AI_ACTION_SEEK_OPPONENT, 0); DebugAI( AI_MSG_INFO, pSoldier, String("found spot %d", pSoldier->aiData.usActionData)); // if it's possible if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) { // sevenfm: try to advance using sight cover if( gfTurnBasedAI && PythSpacesAway(pSoldier->sGridNo, sClosestDisturbance) < TACTICAL_RANGE * 2 && PythSpacesAway(pSoldier->sGridNo, sClosestDisturbance) > TACTICAL_RANGE / 4 && !SightCoverAtSpot(pSoldier, pSoldier->aiData.usActionData, TRUE)) { DebugAI( AI_MSG_INFO, pSoldier, String("[advance spot]")); INT32 sAdvanceSpot = FindAdvanceSpot(pSoldier, sClosestDisturbance, AI_ACTION_SEEK_OPPONENT, ADVANCE_SPOT_SIGHT_COVER, TRUE); DebugAI( AI_MSG_INFO, pSoldier, String("found advance spot %d", sAdvanceSpot)); if( !TileIsOutOfBounds(sAdvanceSpot) ) { DebugAI( AI_MSG_INFO, pSoldier, String("advance to %d", sAdvanceSpot)); pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, sAdvanceSpot, 0, AI_ACTION_SEEK_OPPONENT, 0); if( !TileIsOutOfBounds(pSoldier->aiData.usActionData) ) { return(AI_ACTION_SEEK_OPPONENT); } } else if( SightCoverAtSpot(pSoldier, pSoldier->sGridNo, TRUE) && gfTurnBasedAI && pSoldier->bActionPoints < pSoldier->bInitialActionPoints) { DebugAI( AI_MSG_INFO, pSoldier, String("cannot find advance spot, wait for next turn in sight cover")); return AI_ACTION_NONE; } } return(AI_ACTION_SEEK_OPPONENT); } } } DebugAI( AI_MSG_INFO, pSoldier, String("[hide]")); // if cannot seek or help - hide if( gfTurnBasedAI && pSoldier->bActionPoints == pSoldier->bInitialActionPoints && (!SightCoverAtSpot(pSoldier, pSoldier->sGridNo, TRUE) || pSoldier->aiData.bUnderFire) ) { pSoldier->aiData.usActionData = FindBestNearbyCover(pSoldier, pSoldier->aiData.bAIMorale, &iDummy); if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) { DebugAI(AI_MSG_INFO, pSoldier, String("found hide spot %d", pSoldier->aiData.usActionData)); return AI_ACTION_TAKE_COVER; } else if (pSoldier->aiData.bUnderFire) { pSoldier->aiData.usActionData = FindRetreatSpot(pSoldier); if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) { DebugAI(AI_MSG_INFO, pSoldier, String("found retreat spot %d", pSoldier->aiData.usActionData)); return AI_ACTION_TAKE_COVER; } } } } //////////////////////////////////////////////////////////////////////////// // TURN TO FACE CLOSEST KNOWN OPPONENT (IF NOT FACING THERE ALREADY) //////////////////////////////////////////////////////////////////////////// DebugAI( AI_MSG_INFO, pSoldier, String("[turn to closest opponent]")); if( GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints ) { sClosestOpponent = ClosestSeenOpponent(pSoldier, NULL, NULL); DebugAI( AI_MSG_INFO, pSoldier, String("closest seen opponent %d", sClosestOpponent)); // if we have a closest seen opponent if (!TileIsOutOfBounds(sClosestOpponent)) { bDirection = AIDirection(pSoldier->sGridNo, sClosestOpponent); DebugAI( AI_MSG_INFO, pSoldier, String("soldier direction %d opponent direction %d", pSoldier->ubDirection, bDirection)); // if we're not facing towards him if( pSoldier->ubDirection != bDirection && pSoldier->InternalIsValidStance( bDirection, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) ) { pSoldier->aiData.usActionData = bDirection; return(AI_ACTION_CHANGE_FACING); } DebugAI( AI_MSG_INFO, pSoldier, String("cannot turn")); } } //////////////////////////////////////////////////////////////////////////// // DO NOTHING: Not enough points left to move, so save them for next turn //////////////////////////////////////////////////////////////////////////// DebugAI( AI_MSG_INFO, pSoldier, String("nothing to do!")); // by default, if everything else fails, just stand in place and wait pSoldier->aiData.usActionData = NOWHERE; return(AI_ACTION_NONE); } INT8 ZombieDecideAction( SOLDIERTYPE *pSoldier ) { INT8 bAction = AI_ACTION_NONE; switch (pSoldier->aiData.bAlertStatus) { case STATUS_GREEN: bAction = ZombieDecideActionGreen(pSoldier); break; case STATUS_YELLOW: bAction = ZombieDecideActionYellow(pSoldier); break; case STATUS_RED: bAction = ZombieDecideActionRed(pSoldier); break; case STATUS_BLACK: bAction = ZombieDecideActionBlack(pSoldier); break; } #ifdef DEBUGDECISIONS STR tempstr; sprintf(tempstr, "ZombieDecideAction: selected action %d, actionData %d\n\n", bAction, pSoldier->aiData.usActionData); DebugAI(tempstr); #endif return(bAction); } /*void ZombieDecideAlertStatus( 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 // 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 { 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 // TODO: perhaps zombies might forget their target? They are pretty dumb after all... 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); } } 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); } } } } }*/